From 45d716993f45fa7b7a95ba5a530a3ed11442700b Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 9 Mar 2023 17:06:00 +0000 Subject: [PATCH 01/20] README: Add install instructions. --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b8aca81..faab322 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,44 @@ -# Badger 2040 & Badger 2040 W -## Firmware, Examples & Documentation +# Badger 2040 & Badger 2040 W +## Firmware, Examples & Documentation + +Badger 2040 and Badger 2040 W are maker-friendly all-in-one badge wearables, featuring a 2.9", 296x128 pixel, monochrome e-paper display. + +- [Install](#install) + - [Badger 2040](#badger-2040) + - [Badger 2040 W](#badger-2040-w) + +## Install + +Grab the latest release from [https://github.com/pimoroni/badger2040/releases/latest](https://github.com/pimoroni/badger2040/releases/latest) + +There are four .uf2 files to pick from. + +:warning: Those marked `with-badger-os` contain a full filesystem image that will overwrite both the firmware *and* filesystem of your Badger: + +* pimoroni-badger2040-vX.X.X-micropython-with-badger-os.uf2 +* pimoroni-badger2040w-vX.X.X-micropython-with-badger-os.uf2 + +The regular builds just include the firmware, and leave your files alone: + +* pimoroni-badger2040-vX.X.X-micropython.uf2 +* pimoroni-badger2040w-vX.X.X-micropython.uf2 + +### Badger 2040 + +1. Connect your Badger 2040 W to your computer using a USB A to C cable. + +2. Reset your device into bootloader mode by holding BOOT/USR and pressing the RST button next to it. + +3. Drag and drop one of the `badger2040` .uf2 files to the "RPI-RP2" drive that appears. + +4. Your device should reset and, if you used a `with-badger-os` variant, show the Badger OS Launcher. + +### Badger 2040 W + +1. Connect your Badger 2040 to your computer using a USB A to microB cable. + +2. Reset your device into bootloader mode by holding BOOTSEL (onboard the Pico W) and pressing RESET (next to the qw/st connector). + +3. Drag and drop one of the `badger2040w` .uf2 files to the "RPI-RP2" drive that appears. + +4. Your device should reset and, if you used a `with-badger-os` variant, show the Badger OS Launcher. From 603f15f9a3bf3cf6c3590a3a07a251b293fd1792 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 9 Mar 2023 20:04:13 +0000 Subject: [PATCH 02/20] Docs: Add porting guide & reference. --- docs/porting-guide.md | 68 +++++++++++ docs/reference.md | 265 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 docs/porting-guide.md create mode 100644 docs/reference.md diff --git a/docs/porting-guide.md b/docs/porting-guide.md new file mode 100644 index 0000000..f836751 --- /dev/null +++ b/docs/porting-guide.md @@ -0,0 +1,68 @@ +# Badger 2040: Porting Guide + +The original Badger 2040 release predated the all-encompassing PicoGraphics and used its own custom drawing library. + +Badger has since been updated to use PicoGraphics, and some parts of your code will need to change to be compatible. + +- [Badger OS Changes](#badger-os-changes) + - [Structure \& Filesystem](#structure--filesystem) + - [Add Your Own](#add-your-own) +- [Function Changes](#function-changes) + - [Thick Lines](#thick-lines) + - [Images](#images) + + +## Badger OS Changes + +### Structure & Filesystem + +Apps have been moved to `examples/` to keep things tidy. + +Many apps have top level directories for their associated files, these include: + +* `badges/` for badge .txt files and images +* `images/` for image viewer .jpg files +* `books/` for text books +* `icons/` for weather icons + +### Add Your Own + +You no longer need to edit `launcher.py` to include your own custom apps, just place them in `examples/app_name.py` and include a corresponding `examples/icon-app_name.py`. + +## Function Changes + +The switch from Badger's own library to PicoGraphics changed a few minor things: + +* `pen()` is now `set_pen()` +* `update_speed()` is now `set_update_speed()` +* `thickness()` is now `set_thickness()` and *only* applies to Hershey fonts + +Additionally some features have been outright dropped: + +* `image()` and `icon()` are deprecated, use JPEGs instead. +* `invert()` is no longer supported. + +If you're porting from Badger 2040 to Badger 2040 W, note that it does not have a `USER` button. You'll have to adjust your control scheme accordingly. + +### Thick Lines + +While `set_thickness()` no longer applies to drawing operations, you can draw thick lines using `line(x1, y1, x2, y2, thickness)`. + +### Images + +Using `.bin` files for images is discouraged, albeit still possible. They were always tricky to convert and not cross-compatible with other displays. + +Now you can use `jpegdec` to load and display a JPEG image: + +```python +import badger2040 +import jpegdec + +badger = badger2040.Badger2040() +jpeg = jpegdec.JPEG(badger.display) + +jpeg.open_file("image_file.jpg") +jpeg.decode(x, y) +``` + +JPEG files will be dithered to 1-bit and you might find that low-quality JPEGs have the odd random black pixel caused by compression artifacts. Bump up the quality until you're happy with the result. \ No newline at end of file diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 0000000..ab3df02 --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,265 @@ +# Badger 2040 & Badger 2040 W: Reference + +Badger 2040 W is a Raspberry Pi Pico W powered E Ink badge. + +- [Summary](#summary) + - [Differences between Badger 2040 W and Badger 2040](#differences-between-badger-2040-w-and-badger-2040) + - [Getting Started](#getting-started) + - [Constants](#constants) + - [Screen Size](#screen-size) + - [E Ink Pins](#e-ink-pins) + - [Power Pins](#power-pins) + - [Activity LED Pin](#activity-led-pin) +- [Function Reference](#function-reference) + - [Basic Drawing Settings](#basic-drawing-settings) + - [Pen Colour](#pen-colour) + - [Pen Thickness](#pen-thickness) + - [Displaying Images](#displaying-images) + - [Updating The Display](#updating-the-display) + - [Update](#update) + - [Clear](#clear) + - [Partial Update](#partial-update) + - [Update Speed](#update-speed) + - [LED](#led) + - [Buttons](#buttons) + - [Waking From Sleep](#waking-from-sleep) + - [Button Presses](#button-presses) + - [Real-time Clock](#real-time-clock) + - [Update Speed](#update-speed-1) + - [System speed](#system-speed) + +# Summary + +## Differences between Badger 2040 W and Badger 2040 + +Badger 2040 W includes networking support, which eats a chunk of flash and RAM for associated libraries and drivers. It includes the following additional modules: + +* `network` - for establishing and managing a WiFi connection +* `mip` - for installing MicroPython packages +* `ntptime` - for setting the RTC time +* `urequests` - for making web requests +* `urllib.urequest` - a slightly different library for the above +* `umqtt.simple` - a simple mqtt client + +These, plus the baked-in WiFi drivers, reduce the available filesystem size from 1,408K (on Badger 2040) to 848K (on Badger 2040 W). + +WiFi also eats some system RAM, reducing MicroPython's available RAM from 192K (Badger 2040) down to 166K (Badger 2040 W). + +Badger 2040 W does not have a "user" button since the BOOTSEL button (which originally doubled as "user") is now aboard the attached Pico W. + +## Getting Started + +:info: If you're using a Badger 2040 W you should first populate `WIFI_CONFIG.py` with your WiFi details. + +To start coding your Badger 2040, you will need to add the following lines of code to the start of your code file. + +```python +import badger2040 +badger = badger2040.Badger2040() +``` + +This will create a `Badger2040` class called `badger` that will be used in the rest of the examples going forward. + +## Constants + +Below is a list of other constants that have been made available, to help with the creation of more advanced programs. + +### Screen Size + +* `WIDTH` = `296` +* `HEIGHT` = `128` + +### E Ink Pins + +* `BUSY` = `26` + +### Power Pins + +* `ENABLE_3V3` = `10` + +### Activity LED Pin + +* `LED` = `22` + +# Function Reference + +## Basic Drawing Settings + +Since Badger 2040 is based upon PicoGraphics you should read the [PicoGraphics function reference]([../picographics/README.md](https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/modules/picographics/README.md)) for more information about how to draw to the display. + +### Pen Colour + +There are 16 pen colours - or "shades of grey" - to choose, from 0 (black) to 15 (white). + +Since Badger 2040 cannot display colours other than black and white, any value from 1 to 14 will apply dithering when drawn, to simulate a shade of grey. + +```python +set_pen( + colour # int: colour from 0 to 15 +) +``` + +### Pen Thickness + +:warning: Applies to Hershey fonts only. + +Thickness affects Hershey text and governs how thick the component lines should be, making it appear bolder: + +```python +set_thickness( + value # int: thickness in pixels +) +``` + +## Displaying Images + +Badger 2040 can display basic JPEG images. They must not be progressive. It will attempt to dither them to the black/white display. + +To display a JPEG, import and set up the `jpegdec` module like so: + +```python +import badger2040 +import jpegdec + +badger = badger2040.Badger2040() +jpeg = jpegdec.JPEG(badger.display) +``` + +`badger.display` points to the PicoGraphics instance that the Badger2040 class manages for you. + +You can open and display a JPEG file like so: + +```python +jpeg.open_file("/image.jpg") +jpeg.decode(x, y) +``` + +Where `x, y` is the position at which you want to display the JPEG. + +## Updating The Display + +### Update + +Starts a full update of the screen. Will block until the update has finished. + +Update takes no parameters, but the update time will vary depending on which update speed you've selected. + +```python +badger.update() +``` + +### Clear + +Before drawing again it can be useful to `clear` your display. + +`clear` fills the drawing buffer with the pen colour, giving you a clean slate: + +```python +badger.clear() +``` + +### Partial Update + +Starts a partial update of the screen. Will block until the update has finished. + +A partial update allows you to update a portion of the screen rather than the whole thing. + +That portion *must* be a multiple of 8 pixels tall, but can be any number of pixels wide. + +```python +partial_update( + x, # int: x coordinate of the update region + y, # int: y coordinate of the update region (must be a multiple of 8) + w, # int: width of the update region + h # int: height of the update region (must be a multiple of 8) +) +``` + +### Update Speed + +Badger 2040 is capable of updating the display at multiple different speeds. + +These offer a tradeoff between the quality of the final image and the speed of the update. + +There are currently four constants naming the different update speeds from 0 to 3: + +* `UPDATE_NORMAL` - a normal update, great for display the first screen of your application and ensuring good contrast and no ghosting +* `UPDATE_MEDIUM` - a good balance of speed and clarity, you probably want this most of the time +* `UPDATE_FAST` - a fast update, good for stepping through screens such as the pages of a book or the launcher +* `UPDATE_TURBO` - a super fast update, prone to ghosting, great for making minor changes such as moving a cursor through a menu + +```python +set_update_speed( + speed # int: one of the update constants +) +``` + +## LED + +The white indicator LED can be controlled, with brightness ranging from 0 (off) to 255: + +```python +led( + brightness # int: 0 (off) to 255 (full) +) +``` + +## Buttons + +Badger 2040 and Badger 2040 W feature five buttons on its front, labelled A, B, C, ↑ (up) and ↓ (down). These can be read using the `pressed(button)` method, which accepts the button's pin number. For convenience, each button can be referred to using these constants: + +* `BUTTON_A` = `12` +* `BUTTON_B` = `13` +* `BUTTON_C` = `14` +* `BUTTON_UP` = `15` +* `BUTTON_DOWN` = `11` + +Additionally you can use `pressed_any()` to see if _any_ button has been pressed. + +Badger 2040 has an additional "user" button (which doubles as boot select), availble as: + +* `BUTTON_USER` = `23` + +On Badger 2040 W the `BUTTON_USER` constant is set to `None`. + +## Waking From Sleep + +### Button Presses + +When running on battery, pressing a button on Badger 2040 will power the unit on. It will automatically be latched on and `main.py` will be executed. + +There are some useful functions to determine if Badger 2040 has been woken by a button, and figure out which one: + +* `badger2040.woken_by_button()` - determine if any button was pressed during power-on. +* `badger2040.pressed_to_wake(button)` - determine if the given button was pressed during power-on. +* `badger2040.reset_pressed_to_wake()` - clear the wakeup GPIO state. +* `badger2040.pressed_to_wake_get_once(button)` - returns `True` if the given button was pressed to wake Badger, and then clears the state of that pin. + +### Real-time Clock + +Badger 2040 includes a PCF85063a RTC which continues to run from battery when the Badger is off. It can be used to wake the Badger on a schedule. + +## Update Speed + +The E Ink display on Badger 2040 supports several update speeds. These can be set using `set_update_speed(speed)` where `speed` is a value from `0` to `3`. For convenience these speeds have been given the following constants: + +* `UPDATE_NORMAL` = `0` +* `UPDATE_MEDIUM` = `1` +* `UPDATE_FAST` = `2` +* `UPDATE_TURBO` = `3` + +## System speed + +The system clock speed of the RP2040 can be controlled, allowing power to be saved if on battery, or faster computations to be performed. Use `badger2040.system_speed(speed)` where `speed` is one of the following constants: + +* `SYSTEM_VERY_SLOW` = `0` _4 MHz if on battery, 48 MHz if connected to USB_ +* `SYSTEM_SLOW` = `1` _12 MHz if on battery, 48 MHz if connected to USB_ +* `SYSTEM_NORMAL` = `2` _48 MHz_ +* `SYSTEM_FAST` = `3` _133 MHz_ +* `SYSTEM_TURBO` = `4` _250 MHz_ + +On USB, the system will not run slower than 48MHz, as that is the minimum clock speed required to keep the USB connection stable. + +It is best to set the clock speed as the first thing in your program, and you must not change it after initializing any drivers for any I2C hardware connected to the Qwiic port. To allow you to set the speed at the top of your program, this method is on the `badger2040` module, rather than the `badger` instance, although we have made sure that it is safe to call it after creating a `badger` instance. + +Note that `SYSTEM_TURBO` overclocks the RP2040 to 250MHz, and applies a small over voltage to ensure this is stable. We've found that every RP2040 we've tested is happy to run at this speed without any issues. From 565633a9d95313faee5b5008ab57f90d0d43dfa7 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 10 Mar 2023 13:24:24 +0000 Subject: [PATCH 03/20] Docs: Basic overview. --- docs/overview.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/overview.md diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000..08a3f5f --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,15 @@ +# Badger 2040 & Badger 2040 W: Overview + +Badger 2040 and Badger 2040 W are small, low-power, badge-style boards with a 2.9", 296x128 pixel, monochrome e-paper display. + +They are best used with MicroPython, and we've created a custom firmware with built-in drivers so you can get the most out of it. + +## Badger 2040 + +Badger 2040 uses an onboard RP2040 chip paired with 2MB flash. + +## Badger 2040 W + +Badger 2040 W mounts a Pico W in lieu of an RP2040. This includes wireless functionality onboard. + +Badger 2040 W also includes a PCF85063A real-time clock, which can wake Badger up from its power-off state. \ No newline at end of file From 597100d412d2bef6a14daa5db27101e347d8123d Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 10 Mar 2023 13:24:58 +0000 Subject: [PATCH 04/20] Add and document RTC functions. --- docs/reference.md | 25 ++++++- .../PIMORONI_BADGER2040/lib/badger2040.py | 33 +++++++-- .../PIMORONI_BADGER2040W/lib/badger2040.py | 73 +++++++++++++++++-- 3 files changed, 117 insertions(+), 14 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index ab3df02..8afdd0c 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -1,6 +1,8 @@ # Badger 2040 & Badger 2040 W: Reference -Badger 2040 W is a Raspberry Pi Pico W powered E Ink badge. +Badger 2040 W and Badger 2040 are Raspberry Pi Pico W powered E Ink badges. + +This function reference should give you a basic understanding of how to programming for them in MicroPython. - [Summary](#summary) - [Differences between Badger 2040 W and Badger 2040](#differences-between-badger-2040-w-and-badger-2040) @@ -47,6 +49,8 @@ WiFi also eats some system RAM, reducing MicroPython's available RAM from 192K ( Badger 2040 W does not have a "user" button since the BOOTSEL button (which originally doubled as "user") is now aboard the attached Pico W. +Badger 2040 W includes a PCF85063A real-time clock, which can wake Badger up from its power-off state. + ## Getting Started :info: If you're using a Badger 2040 W you should first populate `WIFI_CONFIG.py` with your WiFi details. @@ -58,7 +62,7 @@ import badger2040 badger = badger2040.Badger2040() ``` -This will create a `Badger2040` class called `badger` that will be used in the rest of the examples going forward. +This will create a `Badger2040` class instance called `badger` that will be used in the rest of the examples going forward. ## Constants @@ -224,6 +228,12 @@ On Badger 2040 W the `BUTTON_USER` constant is set to `None`. ## Waking From Sleep +Turning off Badger 2040 and Badger 2040 W will put them into a low-power mode with - in the case of Badger 2040 W - only the RTC running. + +* `turn_off()` - cut system power, on USB this will block until a button or alarm state is raised + +There are several ways to wake your Badger back up: + ### Button Presses When running on battery, pressing a button on Badger 2040 will power the unit on. It will automatically be latched on and `main.py` will be executed. @@ -237,7 +247,14 @@ There are some useful functions to determine if Badger 2040 has been woken by a ### Real-time Clock -Badger 2040 includes a PCF85063a RTC which continues to run from battery when the Badger is off. It can be used to wake the Badger on a schedule. +Badger 2040 W includes a PCF85063a RTC which continues to run from battery when the Badger is off. It can be used to wake the Badger on a schedule. + +The following functions provide a simple API to the RTC features: + +* `badger2040.sleep_for(minutes)` - set the RTC alarm for the desired number of minutes and turn off Badger 2040 W. +* `badger2040.pico_rtc_to_pcf()` - copy the time from the Pico W's onboard RTC to the PCF85063a (useful since Pico W's own RTC is set automatically by Thonny.) +* `badger2040.pcf_to_pico_rtc()` - copy the PCF85063a time to the Pico W's onboard RTC. +* `badger2040.woken_by_rtc()` - returns `True` if the RTC alarm was set when the Badger 2040 W powered on. ## Update Speed @@ -262,4 +279,4 @@ On USB, the system will not run slower than 48MHz, as that is the minimum clock It is best to set the clock speed as the first thing in your program, and you must not change it after initializing any drivers for any I2C hardware connected to the Qwiic port. To allow you to set the speed at the top of your program, this method is on the `badger2040` module, rather than the `badger` instance, although we have made sure that it is safe to call it after creating a `badger` instance. -Note that `SYSTEM_TURBO` overclocks the RP2040 to 250MHz, and applies a small over voltage to ensure this is stable. We've found that every RP2040 we've tested is happy to run at this speed without any issues. +:info: Note that `SYSTEM_TURBO` overclocks the RP2040 to 250MHz, and applies a small over voltage to ensure this is stable. We've found that every RP2040 we've tested is happy to run at this speed without any issues. diff --git a/firmware/PIMORONI_BADGER2040/lib/badger2040.py b/firmware/PIMORONI_BADGER2040/lib/badger2040.py index 16fa622..ee0a467 100644 --- a/firmware/PIMORONI_BADGER2040/lib/badger2040.py +++ b/firmware/PIMORONI_BADGER2040/lib/badger2040.py @@ -56,6 +56,10 @@ def is_wireless(): return False +def woken_by_rtc(): + return False # Badger 2040 does not include an RTC + + def woken_by_button(): return wakeup.get_gpio_state() & BUTTON_MASK > 0 @@ -84,6 +88,29 @@ def system_speed(speed): pass +def turn_off(): + time.sleep(0.05) + enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT) + enable.off() + # Simulate an idle state on USB power by blocking + # until a button event + while True: + for pin, button in BUTTONS.items(): + if pin == BUTTON_USER: + if not button.value(): + return + continue + if button.value(): + return + + +def sleep_for(minutes=None): + raise RuntimeError("Badger 2040 does not include an RTC.") + + +pico_rtc_to_pcf = pcf_to_pico_rtc = sleep_for + + class Badger2040(): def __init__(self): self.display = PicoGraphics(DISPLAY_INKY_PACK) @@ -121,11 +148,7 @@ def thickness(self, thickness): raise RuntimeError("Thickness not supported in PicoGraphics.") def halt(self): - time.sleep(0.05) - enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT) - enable.off() - while not self.pressed_any(): - pass + turn_off() def pressed(self, button): return BUTTONS[button].value() == (0 if button == BUTTON_USER else 1) or pressed_to_wake_get_once(button) diff --git a/firmware/PIMORONI_BADGER2040W/lib/badger2040.py b/firmware/PIMORONI_BADGER2040W/lib/badger2040.py index 8912112..5765b0a 100644 --- a/firmware/PIMORONI_BADGER2040W/lib/badger2040.py +++ b/firmware/PIMORONI_BADGER2040W/lib/badger2040.py @@ -8,6 +8,7 @@ import time import gc import wakeup +import pcf85063a BUTTON_DOWN = 11 @@ -30,6 +31,7 @@ UPDATE_FAST = 2 UPDATE_TURBO = 3 +RTC_ALARM = 8 LED = 22 ENABLE_3V3 = 10 BUSY = 26 @@ -55,11 +57,20 @@ WAKEUP_MASK = 0 +i2c = machine.I2C(0) +rtc = pcf85063a.PCF85063A(i2c) +i2c.writeto_mem(0x51, 0x00, b'\x00') # ensure rtc is running (this should be default?) +rtc.enable_timer_interrupt(False) + def is_wireless(): return True +def woken_by_rtc(): + return bool(wakeup.get_gpio_state() & (1 << RTC_ALARM)) + + def woken_by_button(): return wakeup.get_gpio_state() & BUTTON_MASK > 0 @@ -86,6 +97,62 @@ def system_speed(speed): pass +def turn_off(): + time.sleep(0.05) + enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT) + enable.off() + # Simulate an idle state on USB power by blocking + # until an RTC alarm or button event + rtc_alarm = machine.Pin(RTC_ALARM) + while True: + if rtc_alarm.value(): + return + for button in BUTTONS.values(): + if button.value(): + return + + +def pico_rtc_to_pcf(): + # Set the PCF85063A to the time stored by Pico W's RTC + year, month, day, dow, hour, minute, second, _ = machine.RTC().datetime() + rtc.datetime((year, month, day, hour, minute, second, dow)) + + +def pcf_to_pico_rtc(): + # Set Pico W's RTC to the time stored by the PCF85063A + t = rtc.datetime() + # BUG ERRNO 22, EINVAL, when date read from RTC is invalid for the Pico's RTC. + try: + machine.RTC().datetime((t[0], t[1], t[2], t[6], t[3], t[4], t[5], 0)) + return True + except OSError: + return False + + +def sleep_for(minutes): + year, month, day, hour, minute, second, dow = rtc.datetime() + + # if the time is very close to the end of the minute, advance to the next minute + # this aims to fix the edge case where the board goes to sleep right as the RTC triggers, thus never waking up + if second >= 55: + minute += 1 + + minute += minutes + + while minute >= 60: + minute -= 60 + hour += 1 + + if hour >= 24: + hour -= 24 + + rtc.clear_alarm_flag() + rtc.set_alarm(0, minute, hour) + rtc.enable_alarm_interrupt(True) + + turn_off() + + class Badger2040(): def __init__(self): self.display = PicoGraphics(DISPLAY_INKY_PACK) @@ -123,11 +190,7 @@ def thickness(self, thickness): raise RuntimeError("Thickness not supported in PicoGraphics.") def halt(self): - time.sleep(0.05) - enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT) - enable.off() - while not self.pressed_any(): - pass + turn_off() def pressed(self, button): return BUTTONS[button].value() == 1 or pressed_to_wake_get_once(button) From 83c35bed4b7037e296eda86e8f705b3f12fd79b2 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 10 Mar 2023 16:02:42 +0000 Subject: [PATCH 05/20] CI: Bump MicroPython version. This version includes the MicroPython greedy heap, which is bad news for some of our modules. --- .github/workflows/micropython.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/micropython.yml b/.github/workflows/micropython.yml index 8696b66..a810cb3 100644 --- a/.github/workflows/micropython.yml +++ b/.github/workflows/micropython.yml @@ -7,7 +7,7 @@ on: types: [created] env: - MICROPYTHON_VERSION: f80d040c038c343b0709eba537014fb52bc8115e + MICROPYTHON_VERSION: 668a7bd28a49980b239fd7666684885382526988 jobs: deps: From f940d60843cc9d30fbeaca83d2929ecf90e9f04b Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 10 Mar 2023 20:49:03 +0000 Subject: [PATCH 06/20] CI: Build against pimoroni-pico v1.19.17. v1.19.17 includes many fixes for compatibility with changes to MicroPython, specifically: https://github.com/micropython/micropython/commit/c80e7c14e6305e50e3b39f97172d4d8fe1214d3b. Requires C++17 enabling in .cmake files. --- .github/workflows/micropython.yml | 2 ++ firmware/PIMORONI_BADGER2040/micropython.cmake | 4 ++++ firmware/PIMORONI_BADGER2040W/micropython.cmake | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/.github/workflows/micropython.yml b/.github/workflows/micropython.yml index a810cb3..df9ea52 100644 --- a/.github/workflows/micropython.yml +++ b/.github/workflows/micropython.yml @@ -8,6 +8,7 @@ on: env: MICROPYTHON_VERSION: 668a7bd28a49980b239fd7666684885382526988 + PIMORONI_PICO_VERSION: v1.19.17 jobs: deps: @@ -104,6 +105,7 @@ jobs: - uses: actions/checkout@v3 with: repository: pimoroni/pimoroni-pico + ref: ${{env.PIMORONI_PICO_VERSION}} submodules: true path: pimoroni-pico diff --git a/firmware/PIMORONI_BADGER2040/micropython.cmake b/firmware/PIMORONI_BADGER2040/micropython.cmake index 796b4a6..8d1a240 100644 --- a/firmware/PIMORONI_BADGER2040/micropython.cmake +++ b/firmware/PIMORONI_BADGER2040/micropython.cmake @@ -6,6 +6,10 @@ include_directories(${PIMORONI_PICO_PATH}/micropython) list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython") list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython/modules") +# Enable support for string_view (for PicoGraphics) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + # Essential include(pimoroni_i2c/micropython) include(pimoroni_bus/micropython) diff --git a/firmware/PIMORONI_BADGER2040W/micropython.cmake b/firmware/PIMORONI_BADGER2040W/micropython.cmake index 796b4a6..8d1a240 100644 --- a/firmware/PIMORONI_BADGER2040W/micropython.cmake +++ b/firmware/PIMORONI_BADGER2040W/micropython.cmake @@ -6,6 +6,10 @@ include_directories(${PIMORONI_PICO_PATH}/micropython) list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython") list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython/modules") +# Enable support for string_view (for PicoGraphics) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + # Essential include(pimoroni_i2c/micropython) include(pimoroni_bus/micropython) From 217df40d5baf0d1ad4d96cc51d16d1fa7d58539e Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 10 Mar 2023 22:51:03 +0000 Subject: [PATCH 07/20] Badger2040W: 60s connect timeout. Set PM before connect. --- firmware/PIMORONI_BADGER2040W/lib/network_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/PIMORONI_BADGER2040W/lib/network_manager.py b/firmware/PIMORONI_BADGER2040W/lib/network_manager.py index 78b53f5..fb21c2c 100644 --- a/firmware/PIMORONI_BADGER2040W/lib/network_manager.py +++ b/firmware/PIMORONI_BADGER2040W/lib/network_manager.py @@ -7,7 +7,7 @@ class NetworkManager: _ifname = ("Client", "Access Point") - def __init__(self, country="GB", client_timeout=30, access_point_timeout=5, status_handler=None, error_handler=None): + def __init__(self, country="GB", client_timeout=60, access_point_timeout=5, status_handler=None, error_handler=None): rp2.country(country) self._ap_if = network.WLAN(network.AP_IF) self._sta_if = network.WLAN(network.STA_IF) @@ -74,8 +74,8 @@ async def client(self, ssid, psk): self._ap_if.active(False) self._sta_if.active(True) - self._sta_if.connect(ssid, psk) self._sta_if.config(pm=0xa11140) + self._sta_if.connect(ssid, psk) try: await uasyncio.wait_for(self.wait(network.STA_IF), self._client_timeout) From e82478bb0f6737fa69d8374760e9a4d9574f6f86 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 20 Mar 2023 15:59:10 +0000 Subject: [PATCH 08/20] Badger OS: Fix qrgen for #6. --- badger_os/examples/qrgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/badger_os/examples/qrgen.py b/badger_os/examples/qrgen.py index f1d7ff8..8dea8a8 100644 --- a/badger_os/examples/qrgen.py +++ b/badger_os/examples/qrgen.py @@ -109,7 +109,7 @@ def draw_qr_file(n): badger_os.state_load("qrcodes", state) -changed = not badger2040.woken_by_button() +changed = True while True: if TOTAL_CODES > 1: From 516974dcd44adbee86dfcbb166be62ead05fc9a8 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 20 Mar 2023 15:59:48 +0000 Subject: [PATCH 09/20] Badger 2040W: Leaner startup for #6. --- firmware/PIMORONI_BADGER2040W/lib/badger2040.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/firmware/PIMORONI_BADGER2040W/lib/badger2040.py b/firmware/PIMORONI_BADGER2040W/lib/badger2040.py index 5765b0a..e077c1c 100644 --- a/firmware/PIMORONI_BADGER2040W/lib/badger2040.py +++ b/firmware/PIMORONI_BADGER2040W/lib/badger2040.py @@ -1,12 +1,7 @@ import machine import micropython from picographics import PicoGraphics, DISPLAY_INKY_PACK -import network -from network_manager import NetworkManager -import WIFI_CONFIG -import uasyncio import time -import gc import wakeup import pcf85063a @@ -234,12 +229,19 @@ def status_handler(self, mode, status, ip): self.update() def isconnected(self): + import network return network.WLAN(network.STA_IF).isconnected() def ip_address(self): + import network return network.WLAN(network.STA_IF).ifconfig()[0] def connect(self): + from network_manager import NetworkManager + import WIFI_CONFIG + import uasyncio + import gc + if WIFI_CONFIG.COUNTRY == "": raise RuntimeError("You must populate WIFI_CONFIG.py for networking.") self.display.set_update_speed(2) From 480e13d3815e37d3021b1b2255963c03a5672a02 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 20 Mar 2023 16:56:05 +0000 Subject: [PATCH 10/20] Experimental: Patch-free wakeup latching. --- .github/workflows/micropython.yml | 7 - .../PIMORONI_BADGER2040/micropython.cmake | 10 +- .../PIMORONI_BADGER2040/wakeup_gpio.patch | 138 ------------------ .../PIMORONI_BADGER2040W/micropython.cmake | 11 +- .../PIMORONI_BADGER2040W/wakeup_gpio.patch | 138 ------------------ firmware/modules/wakeup/micropython.cmake | 22 +++ firmware/modules/wakeup/wakeup.c | 22 +++ firmware/modules/wakeup/wakeup.config.hpp | 36 +++++ firmware/modules/wakeup/wakeup.cpp | 54 +++++++ firmware/modules/wakeup/wakeup.h | 5 + 10 files changed, 158 insertions(+), 285 deletions(-) delete mode 100644 firmware/PIMORONI_BADGER2040/wakeup_gpio.patch delete mode 100644 firmware/PIMORONI_BADGER2040W/wakeup_gpio.patch create mode 100644 firmware/modules/wakeup/micropython.cmake create mode 100644 firmware/modules/wakeup/wakeup.c create mode 100644 firmware/modules/wakeup/wakeup.config.hpp create mode 100644 firmware/modules/wakeup/wakeup.cpp create mode 100644 firmware/modules/wakeup/wakeup.h diff --git a/.github/workflows/micropython.yml b/.github/workflows/micropython.yml index df9ea52..c2c5265 100644 --- a/.github/workflows/micropython.yml +++ b/.github/workflows/micropython.yml @@ -116,13 +116,6 @@ jobs: ref: v0.0.1 path: dir2uf2 - # HACK: Patch Wakeup GPIO features into Pico SDK - - name: "HACK: Wakeup GPIO Patch" - shell: bash - working-directory: micropython/lib/pico-sdk - run: | - git apply "${{env.BOARD_DIR}}/wakeup_gpio.patch" - # Install apt packages - name: Install CCache & Compiler shell: bash diff --git a/firmware/PIMORONI_BADGER2040/micropython.cmake b/firmware/PIMORONI_BADGER2040/micropython.cmake index 8d1a240..a17bb2e 100644 --- a/firmware/PIMORONI_BADGER2040/micropython.cmake +++ b/firmware/PIMORONI_BADGER2040/micropython.cmake @@ -3,6 +3,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../pimoroni_pico_import.cmake) include_directories(${PIMORONI_PICO_PATH}/micropython) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../") list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython") list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython/modules") @@ -29,7 +30,14 @@ include(pcf85063a/micropython) # Utility include(adcfft/micropython) -include(wakeup/micropython) + +# Use our LOCAL wakeup module from firmware/modules/wakeup +include(firmware/modules/wakeup/micropython) +target_compile_definitions(usermod_wakeup INTERFACE + -DWAKEUP_PIN_MASK=0b10000000000000010000000000 + -DWAKEUP_PIN_DIR=0b10000000000000010000000000 + -DWAKEUP_PIN_VALUE=0b10000000000000010000000000 +) # LEDs & Matrices include(plasma/micropython) diff --git a/firmware/PIMORONI_BADGER2040/wakeup_gpio.patch b/firmware/PIMORONI_BADGER2040/wakeup_gpio.patch deleted file mode 100644 index 3cad91b..0000000 --- a/firmware/PIMORONI_BADGER2040/wakeup_gpio.patch +++ /dev/null @@ -1,138 +0,0 @@ -diff --git a/src/rp2_common/pico_runtime/runtime.c b/src/rp2_common/pico_runtime/runtime.c -index f9018d0..ae8c479 100644 ---- a/src/rp2_common/pico_runtime/runtime.c -+++ b/src/rp2_common/pico_runtime/runtime.c -@@ -20,6 +20,7 @@ - #include "hardware/clocks.h" - #include "hardware/irq.h" - #include "hardware/resets.h" -+#include "hardware/gpio.h" - - #include "pico/mutex.h" - #include "pico/time.h" -@@ -35,6 +36,21 @@ - #include "pico/bootrom.h" - #endif - -+// Pins to toggle on wakeup -+#ifndef PICO_WAKEUP_PIN_MASK -+#define PICO_WAKEUP_PIN_MASK ((0b1 << 10) | (0b1 << 25)) -+#endif -+ -+// Direction -+#ifndef PICO_WAKEUP_PIN_DIR -+#define PICO_WAKEUP_PIN_DIR ((0b1 << 10) | (0b1 << 25)) -+#endif -+ -+// Value -+#ifndef PICO_WAKEUP_PIN_VALUE -+#define PICO_WAKEUP_PIN_VALUE ((0b1 << 10) | (0b1 << 25)) -+#endif -+ - extern char __StackLimit; /* Set by linker. */ - - uint32_t __attribute__((section(".ram_vector_table"))) ram_vector_table[48]; -@@ -64,7 +80,13 @@ void runtime_install_stack_guard(void *stack_bottom) { - | 0x10000000; // XN = disable instruction fetch; no other bits means no permissions - } - --void runtime_init(void) { -+void runtime_user_init(void) { -+ gpio_init_mask(PICO_WAKEUP_PIN_MASK); -+ gpio_set_dir_masked(PICO_WAKEUP_PIN_MASK, PICO_WAKEUP_PIN_DIR); -+ gpio_put_masked(PICO_WAKEUP_PIN_MASK, PICO_WAKEUP_PIN_VALUE); -+} -+ -+void runtime_reset_peripherals(void) { - // Reset all peripherals to put system into a known state, - // - except for QSPI pads and the XIP IO bank, as this is fatal if running from flash - // - and the PLLs, as this is fatal if clock muxing has not been reset on this boot -@@ -89,7 +111,9 @@ void runtime_init(void) { - RESETS_RESET_UART1_BITS | - RESETS_RESET_USBCTRL_BITS - )); -+} - -+void runtime_init(void) { - // pre-init runs really early since we need it even for memcpy and divide! - // (basically anything in aeabi that uses bootrom) - -diff --git a/src/rp2_common/pico_standard_link/crt0.S b/src/rp2_common/pico_standard_link/crt0.S -index d061108..e48d870 100644 ---- a/src/rp2_common/pico_standard_link/crt0.S -+++ b/src/rp2_common/pico_standard_link/crt0.S -@@ -10,6 +10,8 @@ - #include "hardware/regs/sio.h" - #include "pico/asm_helper.S" - #include "pico/binary_info/defs.h" -+#include "hardware/regs/resets.h" -+#include "hardware/regs/rosc.h" - - #ifdef NDEBUG - #ifndef COLLAPSE_IRQS -@@ -226,6 +228,23 @@ _reset_handler: - cmp r0, #0 - bne hold_non_core0_in_bootrom - -+ // Increase ROSC frequency to ~48MHz (range 14.4 - 96) -+ // Startup drops from ~160ms to ~32ms on Pico W MicroPython -+ ldr r0, =(ROSC_BASE + ROSC_DIV_OFFSET) -+ ldr r1, =0xaa2 -+ str r1, [r0] -+ -+ ldr r1, =runtime_reset_peripherals -+ blx r1 -+ -+ ldr r1, =runtime_user_init -+ blx r1 -+ -+ // Read GPIO state for front buttons and store -+ movs r3, 0xd0 // Load 0xd0 into r3 -+ lsls r3, r3, 24 // Shift left 24 to get 0xd0000000 -+ ldr r6, [r3, 4] // Load GPIO state (0xd0000004) into r6 -+ - // In a NO_FLASH binary, don't perform .data copy, since it's loaded - // in-place by the SRAM load. Still need to clear .bss - #if !PICO_NO_FLASH -@@ -252,6 +271,10 @@ bss_fill_test: - cmp r1, r2 - bne bss_fill_loop - -+ // runtime_wakeup_gpio_state gets zero init above -+ ldr r2, =runtime_wakeup_gpio_state // Load output var addr into r2 -+ str r6, [r2] // Store r6 to r2 -+ - platform_entry: // symbol for stack traces - // Use 32-bit jumps, in case these symbols are moved out of branch range - // (e.g. if main is in SRAM and crt0 in flash) -@@ -311,6 +334,19 @@ data_cpy_table: - runtime_init: - bx lr - -+.weak runtime_user_init -+.type runtime_user_init,%function -+.thumb_func -+runtime_user_init: -+ bx lr -+ -+.weak runtime_reset_peripherals -+.type runtime_reset_peripherals,%function -+.thumb_func -+runtime_reset_peripherals: -+ bx lr -+ -+ - // ---------------------------------------------------------------------------- - // If core 1 somehow gets into crt0 due to a spectacular VTOR mishap, we need to - // catch it and send back to the sleep-and-launch code in the bootrom. Shouldn't -@@ -335,3 +371,9 @@ hold_non_core0_in_bootrom: - .align 2 - .equ HeapSize, PICO_HEAP_SIZE - .space HeapSize -+ -+.section .data._reset_handler -+.global runtime_wakeup_gpio_state -+.align 4 -+runtime_wakeup_gpio_state: -+.word 0x00000000 -\ No newline at end of file diff --git a/firmware/PIMORONI_BADGER2040W/micropython.cmake b/firmware/PIMORONI_BADGER2040W/micropython.cmake index 8d1a240..3dde77c 100644 --- a/firmware/PIMORONI_BADGER2040W/micropython.cmake +++ b/firmware/PIMORONI_BADGER2040W/micropython.cmake @@ -3,6 +3,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../pimoroni_pico_import.cmake) include_directories(${PIMORONI_PICO_PATH}/micropython) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../") list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython") list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython/modules") @@ -29,7 +30,15 @@ include(pcf85063a/micropython) # Utility include(adcfft/micropython) -include(wakeup/micropython) + +# Use our LOCAL wakeup module from firmware/modules/wakeup +include(firmware/modules/wakeup/micropython) +target_compile_definitions(usermod_wakeup INTERFACE + -DWAKEUP_HAS_RTC=1 + -DWAKEUP_PIN_MASK=0b10000000000010000000000 + -DWAKEUP_PIN_DIR=0b10000000000010000000000 + -DWAKEUP_PIN_VALUE=0b10000000000010000000000 +) # LEDs & Matrices include(plasma/micropython) diff --git a/firmware/PIMORONI_BADGER2040W/wakeup_gpio.patch b/firmware/PIMORONI_BADGER2040W/wakeup_gpio.patch deleted file mode 100644 index 4eee288..0000000 --- a/firmware/PIMORONI_BADGER2040W/wakeup_gpio.patch +++ /dev/null @@ -1,138 +0,0 @@ -diff --git a/src/rp2_common/pico_runtime/runtime.c b/src/rp2_common/pico_runtime/runtime.c -index f9018d0..ae8c479 100644 ---- a/src/rp2_common/pico_runtime/runtime.c -+++ b/src/rp2_common/pico_runtime/runtime.c -@@ -20,6 +20,7 @@ - #include "hardware/clocks.h" - #include "hardware/irq.h" - #include "hardware/resets.h" -+#include "hardware/gpio.h" - - #include "pico/mutex.h" - #include "pico/time.h" -@@ -35,6 +36,21 @@ - #include "pico/bootrom.h" - #endif - -+// Pins to toggle on wakeup -+#ifndef PICO_WAKEUP_PIN_MASK -+#define PICO_WAKEUP_PIN_MASK ((0b1 << 10) | (0b1 << 22)) -+#endif -+ -+// Direction -+#ifndef PICO_WAKEUP_PIN_DIR -+#define PICO_WAKEUP_PIN_DIR ((0b1 << 10) | (0b1 << 22)) -+#endif -+ -+// Value -+#ifndef PICO_WAKEUP_PIN_VALUE -+#define PICO_WAKEUP_PIN_VALUE ((0b1 << 10) | (0b1 << 22)) -+#endif -+ - extern char __StackLimit; /* Set by linker. */ - - uint32_t __attribute__((section(".ram_vector_table"))) ram_vector_table[48]; -@@ -64,7 +80,13 @@ void runtime_install_stack_guard(void *stack_bottom) { - | 0x10000000; // XN = disable instruction fetch; no other bits means no permissions - } - --void runtime_init(void) { -+void runtime_user_init(void) { -+ gpio_init_mask(PICO_WAKEUP_PIN_MASK); -+ gpio_set_dir_masked(PICO_WAKEUP_PIN_MASK, PICO_WAKEUP_PIN_DIR); -+ gpio_put_masked(PICO_WAKEUP_PIN_MASK, PICO_WAKEUP_PIN_VALUE); -+} -+ -+void runtime_reset_peripherals(void) { - // Reset all peripherals to put system into a known state, - // - except for QSPI pads and the XIP IO bank, as this is fatal if running from flash - // - and the PLLs, as this is fatal if clock muxing has not been reset on this boot -@@ -89,7 +111,9 @@ void runtime_init(void) { - RESETS_RESET_UART1_BITS | - RESETS_RESET_USBCTRL_BITS - )); -+} - -+void runtime_init(void) { - // pre-init runs really early since we need it even for memcpy and divide! - // (basically anything in aeabi that uses bootrom) - -diff --git a/src/rp2_common/pico_standard_link/crt0.S b/src/rp2_common/pico_standard_link/crt0.S -index d061108..e48d870 100644 ---- a/src/rp2_common/pico_standard_link/crt0.S -+++ b/src/rp2_common/pico_standard_link/crt0.S -@@ -10,6 +10,8 @@ - #include "hardware/regs/sio.h" - #include "pico/asm_helper.S" - #include "pico/binary_info/defs.h" -+#include "hardware/regs/resets.h" -+#include "hardware/regs/rosc.h" - - #ifdef NDEBUG - #ifndef COLLAPSE_IRQS -@@ -226,6 +228,23 @@ _reset_handler: - cmp r0, #0 - bne hold_non_core0_in_bootrom - -+ // Increase ROSC frequency to ~48MHz (range 14.4 - 96) -+ // Startup drops from ~160ms to ~32ms on Pico W MicroPython -+ ldr r0, =(ROSC_BASE + ROSC_DIV_OFFSET) -+ ldr r1, =0xaa2 -+ str r1, [r0] -+ -+ ldr r1, =runtime_reset_peripherals -+ blx r1 -+ -+ ldr r1, =runtime_user_init -+ blx r1 -+ -+ // Read GPIO state for front buttons and store -+ movs r3, 0xd0 // Load 0xd0 into r3 -+ lsls r3, r3, 24 // Shift left 24 to get 0xd0000000 -+ ldr r6, [r3, 4] // Load GPIO state (0xd0000004) into r6 -+ - // In a NO_FLASH binary, don't perform .data copy, since it's loaded - // in-place by the SRAM load. Still need to clear .bss - #if !PICO_NO_FLASH -@@ -252,6 +271,10 @@ bss_fill_test: - cmp r1, r2 - bne bss_fill_loop - -+ // runtime_wakeup_gpio_state gets zero init above -+ ldr r2, =runtime_wakeup_gpio_state // Load output var addr into r2 -+ str r6, [r2] // Store r6 to r2 -+ - platform_entry: // symbol for stack traces - // Use 32-bit jumps, in case these symbols are moved out of branch range - // (e.g. if main is in SRAM and crt0 in flash) -@@ -311,6 +334,19 @@ data_cpy_table: - runtime_init: - bx lr - -+.weak runtime_user_init -+.type runtime_user_init,%function -+.thumb_func -+runtime_user_init: -+ bx lr -+ -+.weak runtime_reset_peripherals -+.type runtime_reset_peripherals,%function -+.thumb_func -+runtime_reset_peripherals: -+ bx lr -+ -+ - // ---------------------------------------------------------------------------- - // If core 1 somehow gets into crt0 due to a spectacular VTOR mishap, we need to - // catch it and send back to the sleep-and-launch code in the bootrom. Shouldn't -@@ -335,3 +371,9 @@ hold_non_core0_in_bootrom: - .align 2 - .equ HeapSize, PICO_HEAP_SIZE - .space HeapSize -+ -+.section .data._reset_handler -+.global runtime_wakeup_gpio_state -+.align 4 -+runtime_wakeup_gpio_state: -+.word 0x00000000 -\ No newline at end of file diff --git a/firmware/modules/wakeup/micropython.cmake b/firmware/modules/wakeup/micropython.cmake new file mode 100644 index 0000000..c067074 --- /dev/null +++ b/firmware/modules/wakeup/micropython.cmake @@ -0,0 +1,22 @@ +add_library(usermod_wakeup INTERFACE) + +target_sources(usermod_wakeup INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/wakeup.c + ${CMAKE_CURRENT_LIST_DIR}/wakeup.cpp +) + +target_include_directories(usermod_wakeup INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_compile_definitions(usermod_wakeup INTERFACE + -DMODULE_WAKEUP_ENABLED=1 +) + +target_link_libraries(usermod INTERFACE usermod_wakeup) + +set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/wakeup.c + PROPERTIES COMPILE_FLAGS + "-Wno-discarded-qualifiers" +) diff --git a/firmware/modules/wakeup/wakeup.c b/firmware/modules/wakeup/wakeup.c new file mode 100644 index 0000000..f570f64 --- /dev/null +++ b/firmware/modules/wakeup/wakeup.c @@ -0,0 +1,22 @@ +#include "wakeup.h" + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(Wakeup_get_gpio_state_obj, Wakeup_get_gpio_state); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(Wakeup_reset_gpio_state_obj, Wakeup_reset_gpio_state); + +STATIC const mp_map_elem_t wakeup_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_wakeup) }, + { MP_ROM_QSTR(MP_QSTR_get_gpio_state), MP_ROM_PTR(&Wakeup_get_gpio_state_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_gpio_state), MP_ROM_PTR(&Wakeup_reset_gpio_state_obj) } +}; +STATIC MP_DEFINE_CONST_DICT(mp_module_wakeup_globals, wakeup_globals_table); + +const mp_obj_module_t wakeup_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_wakeup_globals, +}; + +#if MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_wakeup, wakeup_user_cmodule, MODULE_WAKEUP_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_wakeup, wakeup_user_cmodule); +#endif \ No newline at end of file diff --git a/firmware/modules/wakeup/wakeup.config.hpp b/firmware/modules/wakeup/wakeup.config.hpp new file mode 100644 index 0000000..539f5e0 --- /dev/null +++ b/firmware/modules/wakeup/wakeup.config.hpp @@ -0,0 +1,36 @@ +#include "hardware/i2c.h" + +// Pins to toggle on wakeup +#ifndef WAKEUP_PIN_MASK +#define WAKEUP_PIN_MASK ((0b1 << 10) | (0b1 << 25)) +#endif + +// Direction +#ifndef WAKEUP_PIN_DIR +#define WAKEUP_PIN_DIR ((0b1 << 10) | (0b1 << 25)) +#endif + +// Value +#ifndef WAKEUP_PIN_VALUE +#define WAKEUP_PIN_VALUE ((0b1 << 10) | (0b1 << 25)) +#endif + +#ifndef WAKEUP_HAS_RTC +#define WAKEUP_HAS_RTC (0) +#endif + +#ifndef WAKEUP_RTC_SDA +#define WAKEUP_RTC_SDA (4) +#endif + +#ifndef WAKEUP_RTC_SCL +#define WAKEUP_RTC_SCL (5) +#endif + +#ifndef WAKEUP_RTC_I2C_ADDR +#define WAKEUP_RTC_I2C_ADDR 0x51 +#endif + +#ifndef WAKEUP_RTC_I2C_INST +#define WAKEUP_RTC_I2C_INST i2c0 +#endif \ No newline at end of file diff --git a/firmware/modules/wakeup/wakeup.cpp b/firmware/modules/wakeup/wakeup.cpp new file mode 100644 index 0000000..d0fe635 --- /dev/null +++ b/firmware/modules/wakeup/wakeup.cpp @@ -0,0 +1,54 @@ +#include "hardware/gpio.h" +#include "wakeup.config.hpp" + + +struct Wakeup { + public: + uint32_t wakeup_gpio_state = 0; + + Wakeup() { + // Assert wakeup pins (indicator LEDs, VSYS hold etc) + gpio_init_mask(WAKEUP_PIN_MASK); + gpio_set_dir_masked(WAKEUP_PIN_MASK, WAKEUP_PIN_DIR); + gpio_put_masked(WAKEUP_PIN_MASK, WAKEUP_PIN_VALUE); + + wakeup_gpio_state = gpio_get_all(); + sleep_ms(5); + wakeup_gpio_state |= gpio_get_all(); + +#if WAKEUP_HAS_RTC==1 + // Set up RTC I2C pins and send reset command + i2c_init(WAKEUP_RTC_I2C_INST, 100000); + gpio_init(WAKEUP_RTC_SDA); + gpio_init(WAKEUP_RTC_SCL); + gpio_set_function(WAKEUP_RTC_SDA, GPIO_FUNC_I2C); gpio_pull_up(WAKEUP_RTC_SDA); + gpio_set_function(WAKEUP_RTC_SCL, GPIO_FUNC_I2C); gpio_pull_up(WAKEUP_RTC_SCL); + + // Turn off CLOCK_OUT by writing 0b111 to CONTROL_2 (0x01) register + uint8_t data[] = {0x01, 0b111}; + i2c_write_blocking(WAKEUP_RTC_I2C_INST, WAKEUP_RTC_I2C_ADDR, data, 2, false); + + i2c_deinit(WAKEUP_RTC_I2C_INST); + + // Cleanup + gpio_init(WAKEUP_RTC_SDA); + gpio_init(WAKEUP_RTC_SCL); +#endif + } +}; + +Wakeup wakeup __attribute__ ((init_priority (101))); + +extern "C" { +#include "wakeup.h" + +mp_obj_t Wakeup_get_gpio_state() { + return mp_obj_new_int(wakeup.wakeup_gpio_state); +} + +mp_obj_t Wakeup_reset_gpio_state() { + wakeup.wakeup_gpio_state = 0; + return mp_const_none; +} + +} \ No newline at end of file diff --git a/firmware/modules/wakeup/wakeup.h b/firmware/modules/wakeup/wakeup.h new file mode 100644 index 0000000..9a4aa67 --- /dev/null +++ b/firmware/modules/wakeup/wakeup.h @@ -0,0 +1,5 @@ +#include "py/runtime.h" +#include "py/objstr.h" + +extern mp_obj_t Wakeup_get_gpio_state(); +extern mp_obj_t Wakeup_reset_gpio_state(); \ No newline at end of file From e925abdf554945d1573879f3dcccf01ce9cf2d7a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 21 Mar 2023 11:44:46 +0000 Subject: [PATCH 11/20] Experimental: Overclock patch. --- .github/workflows/micropython.yml | 8 ++++++++ firmware/startup_overclock.patch | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 firmware/startup_overclock.patch diff --git a/.github/workflows/micropython.yml b/.github/workflows/micropython.yml index c2c5265..3607ba6 100644 --- a/.github/workflows/micropython.yml +++ b/.github/workflows/micropython.yml @@ -75,6 +75,7 @@ jobs: env: RELEASE_FILE: pimoroni-${{matrix.shortname}}-${{github.event.release.tag_name || github.sha}}-micropython.uf2 RELEASE_FILE_WITH_OS: pimoroni-${{matrix.shortname}}-${{github.event.release.tag_name || github.sha}}-micropython-with-badger-os.uf2 + FIRMWARE_DIR: "$GITHUB_WORKSPACE/badger2040/firmware" BOARD_DIR: "$GITHUB_WORKSPACE/badger2040/firmware/${{matrix.board}}" BADGER_OS_DIR: "$GITHUB_WORKSPACE/badger2040/badger_os" @@ -116,6 +117,13 @@ jobs: ref: v0.0.1 path: dir2uf2 + # HACK: Patch startup overclock into Pico SDK + - name: "HACK: Startup Overclock Patch" + shell: bash + working-directory: micropython/lib/pico-sdk + run: | + git apply "${{env.FIRMWARE_DIR}}/startup_overclock.patch" + # Install apt packages - name: Install CCache & Compiler shell: bash diff --git a/firmware/startup_overclock.patch b/firmware/startup_overclock.patch new file mode 100644 index 0000000..0d88477 --- /dev/null +++ b/firmware/startup_overclock.patch @@ -0,0 +1,26 @@ +diff --git a/src/rp2_common/pico_standard_link/crt0.S b/src/rp2_common/pico_standard_link/crt0.S +index d061108..864d31f 100644 +--- a/src/rp2_common/pico_standard_link/crt0.S ++++ b/src/rp2_common/pico_standard_link/crt0.S +@@ -10,6 +10,8 @@ + #include "hardware/regs/sio.h" + #include "pico/asm_helper.S" + #include "pico/binary_info/defs.h" ++#include "hardware/regs/resets.h" ++#include "hardware/regs/rosc.h" + + #ifdef NDEBUG + #ifndef COLLAPSE_IRQS +@@ -226,6 +228,12 @@ _reset_handler: + cmp r0, #0 + bne hold_non_core0_in_bootrom + ++ // Increase ROSC frequency to ~48MHz (range 14.4 - 96) ++ // Speeds up memory zero init and preinit phases. ++ ldr r0, =(ROSC_BASE + ROSC_DIV_OFFSET) ++ ldr r1, =0xaa2 ++ str r1, [r0] ++ + // In a NO_FLASH binary, don't perform .data copy, since it's loaded + // in-place by the SRAM load. Still need to clear .bss + #if !PICO_NO_FLASH From 0123e6b40a36f66b493d6cc274b87209e388c5e7 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 21 Mar 2023 12:57:47 +0000 Subject: [PATCH 12/20] Badger OS: Add keepalive to fix #7. Holding down a button, or pressing a button with the "right" timing would keep the Badger on, despite the 3v3 en pin being turned off. This would result in abrupt power loss when the button is released, causing screen corruption. --- badger_os/examples/badge.py | 4 ++++ badger_os/examples/ebook.py | 4 ++++ badger_os/examples/fonts.py | 4 ++++ badger_os/examples/help.py | 1 + badger_os/examples/image.py | 4 ++++ badger_os/examples/info.py | 1 + badger_os/examples/list.py | 4 ++++ badger_os/examples/net_info.py | 1 + badger_os/examples/news.py | 3 +-- badger_os/examples/qrgen.py | 4 ++++ badger_os/examples/weather.py | 1 + badger_os/launcher.py | 4 ++++ firmware/PIMORONI_BADGER2040/lib/badger2040.py | 11 ++++++++++- firmware/PIMORONI_BADGER2040W/lib/badger2040.py | 11 ++++++++++- 14 files changed, 53 insertions(+), 4 deletions(-) diff --git a/badger_os/examples/badge.py b/badger_os/examples/badge.py index aedf171..c376fd8 100644 --- a/badger_os/examples/badge.py +++ b/badger_os/examples/badge.py @@ -160,6 +160,10 @@ def draw_badge(): draw_badge() 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) or display.pressed(badger2040.BUTTON_B) or display.pressed(badger2040.BUTTON_C) or display.pressed(badger2040.BUTTON_UP) or display.pressed(badger2040.BUTTON_DOWN): badger_os.warning(display, "To change the text, connect Badger2040 to a PC, load up Thonny, and modify badge.txt") time.sleep(4) diff --git a/badger_os/examples/ebook.py b/badger_os/examples/ebook.py index 0ffb75d..cf4394a 100644 --- a/badger_os/examples/ebook.py +++ b/badger_os/examples/ebook.py @@ -189,6 +189,10 @@ def render_page(): state["offsets"] = [] while True: + # Sometimes a button press or hold will keep the system + # powered *through* HALT, so latch the power back on. + display.keepalive() + # Was the next page button pressed? if display.pressed(badger2040.BUTTON_DOWN): state["current_page"] += 1 diff --git a/badger_os/examples/fonts.py b/badger_os/examples/fonts.py index d4329fd..f9d949d 100644 --- a/badger_os/examples/fonts.py +++ b/badger_os/examples/fonts.py @@ -109,6 +109,10 @@ def draw_fonts(): # ------------------------------ 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_UP): state["selected_font"] -= 1 if state["selected_font"] < 0: diff --git a/badger_os/examples/help.py b/badger_os/examples/help.py index df33f48..890926b 100644 --- a/badger_os/examples/help.py +++ b/badger_os/examples/help.py @@ -38,4 +38,5 @@ # 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() diff --git a/badger_os/examples/image.py b/badger_os/examples/image.py index 85fda9d..d721746 100644 --- a/badger_os/examples/image.py +++ b/badger_os/examples/image.py @@ -90,6 +90,10 @@ def show_image(n): 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_UP): if state["current_image"] > 0: state["current_image"] -= 1 diff --git a/badger_os/examples/info.py b/badger_os/examples/info.py index aa6f430..e592bfd 100644 --- a/badger_os/examples/info.py +++ b/badger_os/examples/info.py @@ -41,4 +41,5 @@ # 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() diff --git a/badger_os/examples/list.py b/badger_os/examples/list.py index 5b74125..bf9591c 100644 --- a/badger_os/examples/list.py +++ b/badger_os/examples/list.py @@ -213,6 +213,10 @@ def draw_checkbox(x, y, size, background, foreground, thickness, tick, padding): # ------------------------------ while True: + # Sometimes a button press or hold will keep the system + # powered *through* HALT, so latch the power back on. + display.keepalive() + if len(list_items) > 0: if display.pressed(badger2040.BUTTON_A): if state["current_item"] > 0: diff --git a/badger_os/examples/net_info.py b/badger_os/examples/net_info.py index 0e5ff01..96db200 100644 --- a/badger_os/examples/net_info.py +++ b/badger_os/examples/net_info.py @@ -45,4 +45,5 @@ # 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() diff --git a/badger_os/examples/news.py b/badger_os/examples/news.py index 87aa553..db08275 100644 --- a/badger_os/examples/news.py +++ b/badger_os/examples/news.py @@ -176,8 +176,7 @@ def draw_page(): draw_page() -while 1: - +while True: changed = False if button_down.value(): diff --git a/badger_os/examples/qrgen.py b/badger_os/examples/qrgen.py index 8dea8a8..e0f3209 100644 --- a/badger_os/examples/qrgen.py +++ b/badger_os/examples/qrgen.py @@ -112,6 +112,10 @@ def draw_qr_file(n): changed = True while True: + # Sometimes a button press or hold will keep the system + # powered *through* HALT, so latch the power back on. + display.keepalive() + if TOTAL_CODES > 1: if display.pressed(badger2040.BUTTON_UP): if state["current_qr"] > 0: diff --git a/badger_os/examples/weather.py b/badger_os/examples/weather.py index 1e4e97a..c5692fa 100644 --- a/badger_os/examples/weather.py +++ b/badger_os/examples/weather.py @@ -103,4 +103,5 @@ def draw_page(): # 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() diff --git a/badger_os/launcher.py b/badger_os/launcher.py index 645cfe6..b54f102 100644 --- a/badger_os/launcher.py +++ b/badger_os/launcher.py @@ -164,6 +164,10 @@ def button(pin): display.set_update_speed(badger2040.UPDATE_FAST) 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): diff --git a/firmware/PIMORONI_BADGER2040/lib/badger2040.py b/firmware/PIMORONI_BADGER2040/lib/badger2040.py index ee0a467..b01c971 100644 --- a/firmware/PIMORONI_BADGER2040/lib/badger2040.py +++ b/firmware/PIMORONI_BADGER2040/lib/badger2040.py @@ -51,6 +51,9 @@ WAKEUP_MASK = 0 +enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT) +enable.on() + def is_wireless(): return False @@ -88,9 +91,12 @@ def system_speed(speed): pass +def turn_on(): + enable.on() + + def turn_off(): time.sleep(0.05) - enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT) enable.off() # Simulate an idle state on USB power by blocking # until a button event @@ -150,6 +156,9 @@ def thickness(self, thickness): def halt(self): turn_off() + def keepalive(self): + turn_on() + def pressed(self, button): return BUTTONS[button].value() == (0 if button == BUTTON_USER else 1) or pressed_to_wake_get_once(button) diff --git a/firmware/PIMORONI_BADGER2040W/lib/badger2040.py b/firmware/PIMORONI_BADGER2040W/lib/badger2040.py index e077c1c..6cc1e40 100644 --- a/firmware/PIMORONI_BADGER2040W/lib/badger2040.py +++ b/firmware/PIMORONI_BADGER2040W/lib/badger2040.py @@ -57,6 +57,9 @@ i2c.writeto_mem(0x51, 0x00, b'\x00') # ensure rtc is running (this should be default?) rtc.enable_timer_interrupt(False) +enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT) +enable.on() + def is_wireless(): return True @@ -92,9 +95,12 @@ def system_speed(speed): pass +def turn_on(): + enable.on() + + def turn_off(): time.sleep(0.05) - enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT) enable.off() # Simulate an idle state on USB power by blocking # until an RTC alarm or button event @@ -187,6 +193,9 @@ def thickness(self, thickness): def halt(self): turn_off() + def keepalive(self): + turn_on() + def pressed(self, button): return BUTTONS[button].value() == 1 or pressed_to_wake_get_once(button) From f0a5883ad07603e337fb35fe914ce3b98ab8892d Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 21 Mar 2023 15:16:44 +0000 Subject: [PATCH 13/20] Clock: Add manual time set back. Fix "import ntptime" breaking clock on Badger 2040 (non W). Use "is_wireless()" and don't squash unexpected errors. --- badger_os/examples/clock.py | 161 +++++++++++++++++++++++++++++++----- 1 file changed, 141 insertions(+), 20 deletions(-) diff --git a/badger_os/examples/clock.py b/badger_os/examples/clock.py index 536c6ca..560ac32 100644 --- a/badger_os/examples/clock.py +++ b/badger_os/examples/clock.py @@ -1,6 +1,5 @@ import time import machine -import ntptime import badger2040 @@ -10,17 +9,105 @@ WIDTH, HEIGHT = display.get_bounds() +if badger2040.is_wireless(): + import ntptime + try: + display.connect() + if display.isconnected(): + ntptime.settime() + except (RuntimeError, OSError) as e: + print(f"Wireless Error: {e.value}") + +# Thonny overwrites the Pico RTC so re-sync from the physical RTC if we can try: - display.connect() - if display.isconnected(): - ntptime.settime() -except (RuntimeError, OSError): - pass # no WiFI + badger2040.pcf_to_pico_rtc() +except RuntimeError: + pass rtc = machine.RTC() display.set_font("gothic") +cursors = ["year", "month", "day", "hour", "minute"] +set_clock = False +toggle_set_clock = False +cursor = 0 +last = 0 + +button_a = badger2040.BUTTONS[badger2040.BUTTON_A] +button_b = badger2040.BUTTONS[badger2040.BUTTON_B] +button_c = badger2040.BUTTONS[badger2040.BUTTON_C] + +button_up = badger2040.BUTTONS[badger2040.BUTTON_UP] +button_down = badger2040.BUTTONS[badger2040.BUTTON_DOWN] + + +# Button handling function +def button(pin): + global last, set_clock, toggle_set_clock, cursor, year, month, day, hour, minute + + time.sleep(0.01) + if not pin.value(): + return + + if button_a.value() and button_c.value(): + machine.reset() + + adjust = 0 + + if pin == button_b: + toggle_set_clock = True + if set_clock: + rtc.datetime((year, month, day, 0, hour, minute, second, 0)) + if badger2040.is_wireless(): + badger2040.pico_rtc_to_pcf() + return + + if set_clock: + if pin == button_c: + cursor += 1 + cursor %= len(cursors) + + if pin == button_a: + cursor -= 1 + cursor %= len(cursors) + + if pin == button_up: + adjust = 1 + + if pin == button_down: + adjust = -1 + + if cursors[cursor] == "year": + year += adjust + year = max(year, 2022) + day = min(day, days_in_month(month, year)) + + if cursors[cursor] == "month": + month += adjust + month = min(max(month, 1), 12) + day = min(day, days_in_month(month, year)) + + if cursors[cursor] == "day": + day += adjust + day = min(max(day, 1), days_in_month(month, year)) + + if cursors[cursor] == "hour": + hour += adjust + hour %= 24 + + if cursors[cursor] == "minute": + minute += adjust + minute %= 60 + + draw_clock() + + +def days_in_month(month, year): + if month == 2 and ((year % 4 == 0 and year % 100 != 0) or year % 400 == 0): + return 29 + return (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[month - 1] + def draw_clock(): global second_offset, second_unit_offset @@ -29,10 +116,18 @@ def draw_clock(): ymd = "{:04}/{:02}/{:02}".format(year, month, day) hms_width = display.measure_text(hms, 1.8) - hms_offset = int((WIDTH / 2) - (hms_width / 2)) + hms_offset = int((badger2040.WIDTH / 2) - (hms_width / 2)) + h_width = display.measure_text(hms[0:2], 1.8) + mi_width = display.measure_text(hms[3:5], 1.8) + mi_offset = display.measure_text(hms[0:3], 1.8) ymd_width = display.measure_text(ymd, 1.0) - ymd_offset = int((WIDTH / 2) - (ymd_width / 2)) + ymd_offset = int((badger2040.WIDTH / 2) - (ymd_width / 2)) + y_width = display.measure_text(ymd[0:4], 1.0) + m_width = display.measure_text(ymd[5:7], 1.0) + m_offset = display.measure_text(ymd[0:5], 1.0) + d_width = display.measure_text(ymd[8:10], 1.0) + d_offset = display.measure_text(ymd[0:8], 1.0) display.set_pen(15) display.clear() @@ -41,15 +136,29 @@ def draw_clock(): display.text(hms, hms_offset, 40, 0, 1.8) display.text(ymd, ymd_offset, 100, 0, 1.0) - display.set_update_speed(2) - display.update() - display.set_update_speed(3) - hms = "{:02}:{:02}:".format(hour, minute) second_offset = hms_offset + display.measure_text(hms, 1.8) hms = "{:02}:{:02}:{}".format(hour, minute, second // 10) second_unit_offset = hms_offset + display.measure_text(hms, 1.8) + if set_clock: + display.set_pen(0) + if cursors[cursor] == "year": + display.line(ymd_offset, 120, ymd_offset + y_width, 120, 4) + if cursors[cursor] == "month": + display.line(ymd_offset + m_offset, 120, ymd_offset + m_offset + m_width, 120, 4) + if cursors[cursor] == "day": + display.line(ymd_offset + d_offset, 120, ymd_offset + d_offset + d_width, 120, 4) + + if cursors[cursor] == "hour": + display.line(hms_offset, 70, hms_offset + h_width, 70, 4) + if cursors[cursor] == "minute": + display.line(hms_offset + mi_offset, 70, hms_offset + mi_offset + mi_width, 70, 4) + + display.set_update_speed(2) + display.update() + display.set_update_speed(3) + def draw_second(): global second_offset, second_unit_offset @@ -70,8 +179,12 @@ def draw_second(): s = "{}".format(second % 10) display.text(s, second_unit_offset, 40, 0, 1.8) display.partial_update(second_unit_offset, 8, 75 - (second_unit_offset - second_offset), 56) + time.sleep(0.9) +for b in badger2040.BUTTONS.values(): + b.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + year, month, day, wd, hour, minute, second, _ = rtc.datetime() if (year, month, day) == (2021, 1, 1): @@ -83,12 +196,20 @@ def draw_second(): while True: - year, month, day, wd, hour, minute, second, _ = rtc.datetime() - if second != last_second: - if minute != last_minute: - draw_clock() - last_minute = minute - else: - draw_second() - last_second = second + if not set_clock: + year, month, day, wd, hour, minute, second, _ = rtc.datetime() + if second != last_second: + if minute != last_minute: + draw_clock() + last_minute = minute + else: + draw_second() + last_second = second + + if toggle_set_clock: + set_clock = not set_clock + print(f"Set clock changed to: {set_clock}") + toggle_set_clock = False + draw_clock() + time.sleep(0.01) From ad3f924da71170f0035bafa82b274b0337ee4904 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 21 Mar 2023 15:59:42 +0000 Subject: [PATCH 14/20] Badger OS: Fix fonts, image and list for #6. Using `not badger2040.woken_by_button()` to determine if the display should be refreshed on first run prevented these examples from showing their initial display on battery. --- badger_os/examples/fonts.py | 3 ++- badger_os/examples/image.py | 5 ++++- badger_os/examples/list.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/badger_os/examples/fonts.py b/badger_os/examples/fonts.py index f9d949d..b66dea4 100644 --- a/badger_os/examples/fonts.py +++ b/badger_os/examples/fonts.py @@ -102,7 +102,7 @@ def draw_fonts(): display.led(128) display.set_update_speed(badger2040.UPDATE_FAST) -changed = not badger2040.woken_by_button() +changed = True # ------------------------------ # Main program loop @@ -118,6 +118,7 @@ def draw_fonts(): if state["selected_font"] < 0: state["selected_font"] = len(FONT_NAMES) - 1 changed = True + if display.pressed(badger2040.BUTTON_DOWN): state["selected_font"] += 1 if state["selected_font"] >= len(FONT_NAMES): diff --git a/badger_os/examples/image.py b/badger_os/examples/image.py index d721746..95e4036 100644 --- a/badger_os/examples/image.py +++ b/badger_os/examples/image.py @@ -86,7 +86,7 @@ def show_image(n): badger_os.state_load("image", state) -changed = not badger2040.woken_by_button() +changed = True while True: @@ -98,13 +98,16 @@ def show_image(n): if state["current_image"] > 0: state["current_image"] -= 1 changed = True + if display.pressed(badger2040.BUTTON_DOWN): if state["current_image"] < TOTAL_IMAGES - 1: state["current_image"] += 1 changed = True + if display.pressed(badger2040.BUTTON_A): state["show_info"] = not state["show_info"] changed = True + if display.pressed(badger2040.BUTTON_B) or display.pressed(badger2040.BUTTON_C): display.set_pen(15) display.clear() diff --git a/badger_os/examples/list.py b/badger_os/examples/list.py index bf9591c..3232124 100644 --- a/badger_os/examples/list.py +++ b/badger_os/examples/list.py @@ -161,7 +161,7 @@ def draw_checkbox(x, y, size, background, foreground, thickness, tick, padding): # Program setup # ------------------------------ -changed = not badger2040.woken_by_button() +changed = True state = { "current_item": 0, } From 02daaf8cb28a865268529e0db5864c608e8e1606 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 21 Mar 2023 16:35:46 +0000 Subject: [PATCH 15/20] Image: Tidyup and fix battery quirks. Simplified image.py by removing help text and readme creation. Tried to avoid "state_save" right before updating the display. --- badger_os/examples/image.py | 38 +++---------------------------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/badger_os/examples/image.py b/badger_os/examples/image.py index 95e4036..ab72e91 100644 --- a/badger_os/examples/image.py +++ b/badger_os/examples/image.py @@ -1,39 +1,20 @@ import os -import sys -import time import badger2040 from badger2040 import HEIGHT, WIDTH import badger_os import jpegdec -REAMDE = """ -Images must be 296x128 pixel JPEGs - -Create a new "images" directory via Thonny, and upload your .jpg files there. -""" - -OVERLAY_BORDER = 40 -OVERLAY_SPACING = 20 -OVERLAY_TEXT_SIZE = 0.5 - TOTAL_IMAGES = 0 # Turn the act LED on as soon as possible display = badger2040.Badger2040() display.led(128) +display.set_update_speed(badger2040.UPDATE_NORMAL) jpeg = jpegdec.JPEG(display.display) -# Try to preload BadgerPunk image -try: - os.mkdir("/images") - with open("/images/readme.txt", "w") as f: - f.write(REAMDE) - f.flush() -except (OSError, ImportError): - pass # Load images try: @@ -77,11 +58,7 @@ def show_image(n): if TOTAL_IMAGES == 0: - display.set_pen(15) - display.clear() - badger_os.warning(display, "To run this demo, create an /images directory on your device and upload some 1bit 296x128 pixel images.") - time.sleep(4.0) - sys.exit() + raise RuntimeError("To run this demo, create an /images directory on your device and upload some 1bit 296x128 pixel images.") badger_os.state_load("image", state) @@ -108,18 +85,9 @@ def show_image(n): state["show_info"] = not state["show_info"] changed = True - if display.pressed(badger2040.BUTTON_B) or display.pressed(badger2040.BUTTON_C): - display.set_pen(15) - display.clear() - badger_os.warning(display, "To add images connect Badger2040 to a PC, load up Thonny, and see readme.txt in images/") - display.update() - print(state["current_image"]) - time.sleep(4) - changed = True - if changed: - badger_os.state_save("image", state) show_image(state["current_image"]) + badger_os.state_save("image", state) changed = False # Halt the Badger to save power, it will wake up if any of the front buttons are pressed From 0a1e84923c7e0fd52c8ccb5a4ab41f9483b616d6 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 21 Mar 2023 16:59:30 +0000 Subject: [PATCH 16/20] Badge: Remove unhelpful help text. It's too easy to accidentally trigger this when launching an app. --- badger_os/examples/badge.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/badger_os/examples/badge.py b/badger_os/examples/badge.py index c376fd8..8b8ec77 100644 --- a/badger_os/examples/badge.py +++ b/badger_os/examples/badge.py @@ -1,8 +1,7 @@ -import time import badger2040 -import badger_os import jpegdec + # Global Constants WIDTH = badger2040.WIDTH HEIGHT = badger2040.HEIGHT @@ -110,6 +109,8 @@ def draw_badge(): display.text(detail2_title, LEFT_PADDING, HEIGHT - (DETAILS_HEIGHT // 2), WIDTH, DETAILS_TEXT_SIZE) display.text(detail2_text, LEFT_PADDING + name_length + DETAIL_SPACING, HEIGHT - (DETAILS_HEIGHT // 2), WIDTH, DETAILS_TEXT_SIZE) + display.update() + # ------------------------------ # Program setup @@ -164,13 +165,5 @@ def draw_badge(): # powered *through* HALT, so latch the power back on. display.keepalive() - if display.pressed(badger2040.BUTTON_A) or display.pressed(badger2040.BUTTON_B) or display.pressed(badger2040.BUTTON_C) or display.pressed(badger2040.BUTTON_UP) or display.pressed(badger2040.BUTTON_DOWN): - badger_os.warning(display, "To change the text, connect Badger2040 to a PC, load up Thonny, and modify badge.txt") - time.sleep(4) - - draw_badge() - - display.update() - # If on battery, halt the Badger to save power, it will wake up if any of the front buttons are pressed display.halt() From 7e1e7d282e61a427fc51c75a9819d8c169557bdd Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 21 Mar 2023 17:10:53 +0000 Subject: [PATCH 17/20] QRGen: Correct details for each Badger. --- badger_os/examples/qrgen.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/badger_os/examples/qrgen.py b/badger_os/examples/qrgen.py index e0f3209..0914819 100644 --- a/badger_os/examples/qrgen.py +++ b/badger_os/examples/qrgen.py @@ -15,16 +15,28 @@ text = open("/qrcodes/qrcode.txt", "r") except OSError: text = open("/qrcodes/qrcode.txt", "w") - text.write("""https://pimoroni.com/badger2040 + if badger2040.is_wireless(): + text.write("""https://pimoroni.com/badger2040w Badger 2040 W * 296x128 1-bit e-ink -* 2.4GHz wireless +* 2.4GHz wireless & RTC * five user buttons * user LED * 2MB QSPI flash Scan this code to learn more about Badger 2040 W. +""") + else: + text.write("""https://pimoroni.com/badger2040 +Badger 2040 +* 296x128 1-bit e-ink +* five user buttons +* user LED +* 2MB QSPI flash + +Scan this code to learn +more about Badger 2040. """) text.flush() text.seek(0) From a0ec36789d5803001b75924f3d5ee88795f63ac7 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 21 Mar 2023 18:37:55 +0000 Subject: [PATCH 18/20] Docs: Tidyup examples readme. --- badger_os/{readme.md => README.md} | 73 ++++++++++++++++++------------ 1 file changed, 45 insertions(+), 28 deletions(-) rename badger_os/{readme.md => README.md} (56%) diff --git a/badger_os/readme.md b/badger_os/README.md similarity index 56% rename from badger_os/readme.md rename to badger_os/README.md index ab8139b..e1707b6 100644 --- a/badger_os/readme.md +++ b/badger_os/README.md @@ -1,7 +1,9 @@ -# Badger 2040 W MicroPython Examples - -- [About Badger 2040 W](#about-badger-2040-w) -- [Badger 2040 W and PicoGraphics](#badger-2040-w-and-picographics) +# Badger 2040 MicroPython Examples + +These MicroPython examples demonstrate a variety of applications and are distributed as a sort of "OS" for Badger 2040. + +They should help you get started quickly and give you something to modify for your own requirements. + - [Examples](#examples) - [Badge](#badge) - [Clock](#clock) @@ -17,25 +19,6 @@ - [Weather](#weather) - [Other Resources](#other-resources) - -## About Badger 2040 W - -Badger 2040 W is a programmable E Paper/eInk/EPD badge with 2.4GHz wireless connectivity, powered by Raspberry Pi Pico W. It can go into a deep sleep mode between updates to preserve battery. - -- :link: [Badger 2040 W store page](https://shop.pimoroni.com/products/badger-2040-w) - -Badger 2040 W ships with MicroPython firmware pre-loaded, but you can download the most recent version at the link below (you'll want the `pimoroni-badger2040w` .uf2). If you download the `-with-examples` file, it will come with examples built in. - -- [MicroPython releases](https://github.com/pimoroni/pimoroni-pico/releases) -- [Installing MicroPython](../../../setting-up-micropython.md) - -## Badger 2040 W and PicoGraphics - -The easiest way to start displaying cool stuff on Badger is by using our `badger2040w` module (which contains helpful functions for interacting with the board hardware) and our PicoGraphics library (which contains a bunch of functions for drawing on the E Ink display). - -- [Badger 2040 W function reference](../../modules/badger2040w/README.md) -- [PicoGraphics function reference](../../modules/picographics/README.md) - ## Examples Find out more about how to use these examples in our Learn guide: @@ -47,40 +30,74 @@ Find out more about how to use these examples in our Learn guide: Customisable name badge example. +Loads badge details from the `/badges` directory on the device, using a file called `badge.txt` which should contain: + +* Company +* Name +* Detail 1 title +* Detail 1 text +* Detail 2 title +* Detail 2 text +* Badge image path + +For example: + +```txt +mustelid inc +H. Badger +RP2040 +2MB Flash +E ink +296x128px +/badges/badge.jpg +``` + +The image should be a 104x128 pixel JPEG. Any colours other than black/white will be dithered. + ### Clock [clock.py](examples/clock.py) Clock example with (optional) NTP synchronization and partial screen updates. +Press button B to switch the clock into time set mode. + +On Badger 2040 this allows you to set the internal RTC, but it will not survive a sleep/wake cycle even with a connected battery. + +On Badger 2040 W it will set the external RTC and the time will persist even after sleep/wake. + ### Ebook [ebook.py](examples/ebook.py) View text files on Badger. +Currently reads an abridged copy of "The Wind in the Willows" out of the `/books` directory. + ### Fonts [fonts.py](examples/fonts.py) -View all the built in fonts. +A basic example that lets you preview how all of the built-in fonts will appear on the display. ### Help [help.py](examples/help.py) -How to navigate the launcher. +Gives instructions on to navigate the launcher. ### Image [image.py](examples/image.py) -Display .jpegs on Badger. +Display JPEG images. Images are read out of the `/images` directory on device. + +Press button B to show/hide the image filename. ### Info [info.py](examples/info.py) -Info about Badger 2040 W. +Info about Badger 2040. ### List [list.py](examples/list.py) -A checklist to keep track of to-dos or shopping. +A checklist to keep track of to-dos or shopping. Use A/C and Up/Down to navigate and B to check/uncheck items. ### Net Info [net_info.py](examples/net_info.py) From 1f629555824c517bdbd79a0b431b6cd2f6959981 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 28 Mar 2023 10:05:42 +0100 Subject: [PATCH 19/20] CI: Bump MicroPython & Pimoroni Pico versions. --- .github/workflows/micropython.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/micropython.yml b/.github/workflows/micropython.yml index 3607ba6..9780570 100644 --- a/.github/workflows/micropython.yml +++ b/.github/workflows/micropython.yml @@ -7,8 +7,8 @@ on: types: [created] env: - MICROPYTHON_VERSION: 668a7bd28a49980b239fd7666684885382526988 - PIMORONI_PICO_VERSION: v1.19.17 + MICROPYTHON_VERSION: 38e7b842c6bc8122753cbf0845eb141f28fbcb72 + PIMORONI_PICO_VERSION: v1.19.18 jobs: deps: From 62deec7434a01da330a8a39eb999deb8b013ddb2 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 28 Mar 2023 10:06:03 +0100 Subject: [PATCH 20/20] Firmware: Add cppmem module. This module redirects `malloc` and `free` calls to MicroPython's heap, allowing C++ code and MicroPython's "greedy heap" to coexist. See: https://github.com/micropython/micropython/issues/11116 --- firmware/PIMORONI_BADGER2040/micropython.cmake | 4 ++++ firmware/PIMORONI_BADGER2040W/micropython.cmake | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/firmware/PIMORONI_BADGER2040/micropython.cmake b/firmware/PIMORONI_BADGER2040/micropython.cmake index a17bb2e..b1207d4 100644 --- a/firmware/PIMORONI_BADGER2040/micropython.cmake +++ b/firmware/PIMORONI_BADGER2040/micropython.cmake @@ -39,6 +39,10 @@ target_compile_definitions(usermod_wakeup INTERFACE -DWAKEUP_PIN_VALUE=0b10000000000000010000000000 ) +# Note: cppmem is *required* for C++ code to function on MicroPython +# it redirects `malloc` and `free` calls to MicroPython's heap +include(cppmem/micropython) + # LEDs & Matrices include(plasma/micropython) diff --git a/firmware/PIMORONI_BADGER2040W/micropython.cmake b/firmware/PIMORONI_BADGER2040W/micropython.cmake index 3dde77c..2901f6c 100644 --- a/firmware/PIMORONI_BADGER2040W/micropython.cmake +++ b/firmware/PIMORONI_BADGER2040W/micropython.cmake @@ -40,6 +40,10 @@ target_compile_definitions(usermod_wakeup INTERFACE -DWAKEUP_PIN_VALUE=0b10000000000010000000000 ) +# Note: cppmem is *required* for C++ code to function on MicroPython +# it redirects `malloc` and `free` calls to MicroPython's heap +include(cppmem/micropython) + # LEDs & Matrices include(plasma/micropython) @@ -49,5 +53,5 @@ include(servo/micropython) include(encoder/micropython) include(motor/micropython) -# version.py and pimoroni.py +# version.py, pimoroni.py and boot.py include(modules_py/modules_py)