Raspberry Pico Badger: Hardware Features and OS
Tags: raspberrypi, microcontroller, micropython
Published: 2024-01-15 06:57:45

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](https://admantium.com/blog/micro21_raspberry_pico_badger/)_.

## Raspberry Pico Badger

The Pico Badger consumer product, advertised and offered for sale on [pimoroni.com](https://shop.pimoroni.com/products/badger-2040-w?variant=40514062188627) 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:

![](https://admantium.com/images/blog/micro21_badger_frontside.jpg)

And this is the backside:

![](https://admantium.com/images/blog/micro21_badger_backside.jpg)

## 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](https://github.com/pimoroni/badger2040). 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](https://thonny.org/). Accessing the Badger with reveals the following file structure:

![](https://admantium.com/images/blog/micro21_dir_structure.png)

```bash
.
├── 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](https://github.com/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.

```py
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.

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

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

```py
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.

```py
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:

```py
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:

```py
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](https://github.com/pimoroni/badger2040/tree/main/firmware/PIMORONI_BADGER2040W/lib).

The aforementioned `badger_os.launch()` function is this:

```py
# 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.

```py
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](https://circuitpython.org/board/pimoroni_badger2040w/)
- The [BadOS](https://github.com/GuidosLabs/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 a[Tic-Tac-Toe computer game](https://www.penguintutor.com/programming/circlealgorithm)
 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.