Raspberry Pico Badger: Hardware Features and OS

Sebastian
8 min readJan 15, 2024

The Raspberry Pico Badger is a consumer product by the Raspberry Pi company Pimorino. Advertised as a portable badge with a 2.9 inch e-ink display and a Pico W, its a portable, programmable microcomputer. This article investigates this unique product from its hardware side as well as the software, apptly named Badger OS.

The technical context of this article is MicroPython v1.21 and Badger OS v0.04. All examples should work with newer releases too.

This article originally appeared at my blog admantium.com.

Raspberry Pico Badger

The Pico Badger consumer product, advertised and offered for sale on pimoroni.com is a complete portable mini computer with these components:

  • 2.9inch e-ink display with 296x128 pixels
  • 5 onboard buttons
  • JST-PH battery connector (e.g. for LiPo, max 5.5V)
  • Qwiic/STEMMA QT connector
  • Rasperyy Pico W (133Mhz Dual Arm Cortex M0+, 264kB SRAM, 2MB Flash RAM, BLE and WiFi)

All components are soldered, the board is robust and a decent amount of varnish protects all surfaces. It can be powered with the battery connector, or the Micro-USB port of the Raspberry Pico W.

This is the frontside:

And this is the backside:

Badger OS

The Pico Badger comes with a preinstalled software called Bader OS. This software is completly written in Micropython, and its code can be accessed on github.com. When powered up, it boots into a main menu (or the last screen that was shown). By simultanously pressing the buttons A and C, the main menu is shown, and the following applications are accessible:

  • Badge: Show a customizabel badge with different text fields and a rendered image
  • Clock: A realtime clock that synchronizes via Wi-Fi
  • eBook: A full-fledge eBook reade with a preinstalled project Gutenberg version of “The Wind in the Willows”
  • Fonts: Showcasing how different fonts can be rendered on the screen
  • Help: Manal showing how to navigate via the onboard buttons
  • Image: Renders a provided image file
  • Info: System and hardware information
  • List: A todo app displaying items and enabling to mark them as done
  • Net Info: Show connectivty information about the Wi-Fi connection
  • News: An RSS reader showing headlines from BBC
  • QRGen: A QR code generator
  • Weather: Using your Wi-Fi hotspor geo information, show the current temperature, wind and rain status

Configuring Badger OS

All Badger OS functions and configuration are stored as Python and configuration files on the board itself. Like a normal Raspberyy Pico, you need to keep pressing the bootsel button, then connect the device to a computer, and its internal file system becomes mountable as a USB drive.

The recommended way to programm MicroPython on the Raspberry Pico is to use the Thonny IDE. Accessing the Badger with reveals the following file structure:

.
├── WIFI_CONFIG.py
├── badges
│ ├── badge.jpg
│ └── badge.txt
├── books
│ └── 289-0-wind-in-the-willows-abridged.txt
├── checklist.txt
├── examples
│ ├── badge.py
│ ├── clock.py
│ ├── ebook.py
│ ├── fonts.py
│ ├── help.py
│ ├── icon-badge.jpg
│ ├── icon-clock.jpg
│ ├── icon-ebook.jpg
│ ├── icon-fonts.jpg
│ ├── icon-help.jpg
│ ├── icon-image.jpg
│ ├── icon-info.jpg
│ ├── icon-list.jpg
│ ├── icon-net-info.jpg
│ ├── icon-news.jpg
│ ├── icon-qrgen.jpg
│ ├── icon-weather.jpg
│ ├── image.py
│ ├── info.py
│ ├── list.py
│ ├── net_info.py
│ ├── news.py
│ ├── qrgen.py
│ └── weather.py
├── icon-cloud.jpg
├── icon-rain.jpg
├── icon-snow.jpg
├── icon-storm.jpg
├── icon-sun.jpg
├── icons
├── images
│ └── badgerpunk.jpg
├── launcher.py
├── main.py
└── state
├── ebook.json
├── image.json
├── launcher.json
├── list.json
└── news.json

6 directories, 42 files

The first thing that you will want to do is to configure access to a Wi-Fi hotspot. For this to follow:

  • Open the file WIFI_CONFIG.py
  • Enter the Wi-Fi Credentials
  • Reboot and try the buitin RSS reader

If you see the log message Connecting..., followed by Connected!, the Badger is ready to access any website and other internet resources.

Exploring Badger OS: Main Programm

Note: all of the following source code stems from the Github repository pimoroni/badger2040

Lets investigate the Badger OS main.py programm to understand how to add a new application.

First, several modules are imported, and global variables defined, for example for the display.

import gc
import os
import time
import math
import badger2040
import badger_os
import jpegdec

APP_DIR = "/examples"
FONT_SIZE = 2
display = badger2040.Badger2040()
display.set_font("bitmap8")
display.led(128)
jpeg = jpegdec.JPEG(display.display)

Next, the available applications are determined by crawling the ./examples directory for every Python file.

examples = [x[:-3] for x in os.listdir("/examples") if x.endswith(".py")]

Two methods for drawing the on-screen menu bars follow:

def draw_disk_usage(x):

def render():
display.set_pen(15)
display.clear()
display.set_pen(0)
max_icons = min(3, len(examples[(state["page"] * 3):]))
...

Halfway through the example, we find the method that actually starts an app.

def launch_example(index):
wait_for_user_to_release_buttons()

file = examples[(state["page"] * 3) + index]
file = f"{APP_DIR}/{file}"
for k in locals().keys():
if k not in ("gc", "file", "badger_os"):
del locals()[k]
gc.collect()
badger_os.launch(file)

Lets note this down: badger_os.launch(file). Continuing the main programm to its end, several more methods define how to interpret pressed buttons:

def button(pin):
global changed
changed = True

if pin == badger2040.BUTTON_A:
launch_example(0)
if pin == badger2040.BUTTON_B:
launch_example(1)
if pin == badger2040.BUTTON_C:
launch_example(2)
if pin == badger2040.BUTTON_UP:
if state["page"] > 0:
state["page"] -= 1
render()
if pin == badger2040.BUTTON_DOWN:
if state["page"] < MAX_PAGE - 1:
state["page"] += 1
render()

And finally a continous running while loop that constantly monitors for pressed buttons:

while True:
# Sometimes a button press or hold will keep the system
# powered *through* HALT, so latch the power back on.
display.keepalive()

if display.pressed(badger2040.BUTTON_A):
button(badger2040.BUTTON_A)
if display.pressed(badger2040.BUTTON_B):
button(badger2040.BUTTON_B)
if display.pressed(badger2040.BUTTON_C):
button(badger2040.BUTTON_C)
if display.pressed(badger2040.BUTTON_UP):
button(badger2040.BUTTON_UP)
if display.pressed(badger2040.BUTTON_DOWN):
button(badger2040.BUTTON_DOWN)
if changed:
badger_os.state_save("launcher", state)
changed = False
display.halt()

Now, lets check how an app is started, and how an app file actually looks like.

Exploring Badger OS: Badger OS Module an App File

Interestingly, the imported badger_os module is not part fo the device files, but included in the Micropython distribution that is flashed onto the device. This module, and the badger2040 and network_manager.py can be found in projects GitHub repository at path main/firmware/PIMORONI_BADGER2040W/lib.

The aforementioned badger_os.launch() function is this:

# Source: https://github.com/pimoroni/badger2040/blob/main/firmware/PIMORONI_BADGER2040W/lib/badger_os.py

def launch(file):
state_set_running(file)
gc.collect()
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)

def quit_to_launcher(pin):
if button_a.value() and button_c.value():
machine.reset()
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher)
button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher)

try:
__import__(file)
except ImportError:
# If the app doesn't exist, notify the user
warning(None, f"Could not launch: {file}")
time.sleep(4.0)
except Exception as e:
# If the app throws an error, catch it and display!
print(e)
warning(None, str(e))
time.sleep(4.0)
# If the app exits or errors, do not relaunch!
state_clear_running()
machine.reset() # Exit back to launcher

As you see, it will run a garbace collection routing to cleanup RAM resources, then define buttons, and simply imports the launched app file.

So, what inside this? Lets check the source code of the info page.

import badger2040
from badger2040 import WIDTH

TEXT_SIZE = 1
LINE_HEIGHT = 15

display = badger2040.Badger2040()
display.led(128)

# Clear to white
display.set_pen(15)
display.clear()

display.set_font("bitmap8")
display.set_pen(0)
display.rectangle(0, 0, WIDTH, 16)

display.set_pen(15)
display.text("badgerOS", 3, 4, WIDTH, 1)
display.text("info", WIDTH - display.measure_text("help", 0.4) - 4, 4, WIDTH, 1)

display.set_pen(0)
y = 16 + int(LINE_HEIGHT / 2)
display.text("Made by Pimoroni, powered by MicroPython", 5, y, WIDTH, TEXT_SIZE)
y += LINE_HEIGHT
display.text("Dual-core RP2040, 133MHz, 264KB RAM", 5, y, WIDTH, TEXT_SIZE)
y += LINE_HEIGHT
display.text("2MB Flash (1MB OS, 1MB Storage)", 5, y, WIDTH, TEXT_SIZE)
y += LINE_HEIGHT
display.text("296x128 pixel Black/White e-Ink", 5, y, WIDTH, TEXT_SIZE)
y += LINE_HEIGHT
y += LINE_HEIGHT
display.text("For more info:", 5, y, WIDTH, TEXT_SIZE)
y += LINE_HEIGHT

display.text("https://pimoroni.com/badger2040", 5, y, WIDTH, TEXT_SIZE)
display.update()

# Call halt in a loop, on battery this switches off power.
# On USB, the app will exit when A+C is pressed because the launcher picks that up.
while True:
display.keepalive()
display.halt()

This source code contains instructions how to draw shapes and texts on the screen. It starts by importing the rp20240 library, then activates the onboard led with display.led(), and clears the screen with display.clear(). This is followed by a series of statements using display.pen(), a method that defines a new origin, and display.rectangle() as well as display.text() to fill the screen. Finally, the screen is drawn on with display.update() and kept in this state forever with display.update(). Since this app is purely static, no additional code regarding button interactions is defined.

So, in essence an app files accesses help classes from rp2040 to render text on the screen and, if required, define the behavior of the onboard buttons.

Other Use Cases

While Badger OS is the official MicroPython based library, it is not the only option you have. To give youm some ideas what else can be accomplished:

  • You can install a custom port of CircuitPython
  • The BadOS project is a custom OS with Circuit Python, its Github repository shows how to develop different kind of apps too
  • There is a programming course how to create aTic-Tac-Toe computer game showing the same capabilties as the official software

Conclusion

The Badger Pico W is a unique product. With its 2.9 inch e-ink display and access to Wi-Fi and Bluetooth, you can programm stateless applicaation that remain shown even when no power is provided. The aptly named Badger OS is a complete open-source MicroPython project that shows examples for all necessary interactions: Navigating menus by pressing a button, drawing text and images, connect to Wi-Fi, make HTTP request, parse the results and show them. You just need to add portable batteries and have a credit-card sized wearable computer. This board can be used for many projects, for example to build a small e-book reader, a portable RSS reader, a badge.

This article started to explore the Badger OS source code, which is written entirely in MicroPython. You learned that the programs main method imports required libraies, setups method for handling the buttons, defines a state and then display all installed apps. And these apps are single MicroPython files as well that access the buildin library rp2040 to draw text and shapes on the screen as well as to define button behavior.

Following this understanding, the next article will investigate how to program a custom app.

--

--