Merge pull request #633 from pimoroni/feature/badger2040w

Badger2040w support
This commit is contained in:
ZodiusInfuser 2023-02-16 10:43:25 +00:00 committed by GitHub
commit 943ea1390a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 4631 additions and 18 deletions

View File

@ -7,7 +7,7 @@ on:
types: [created]
env:
MICROPYTHON_VERSION: 67fac4ebc53db6337008ba06df7932faec80f57c
MICROPYTHON_VERSION: 35524a6fda1e44692ad599a39c802c168c897de9
BOARD_TYPE: PIMORONI_BADGER2040
# MicroPython version will be contained in github.event.release.tag_name for releases
RELEASE_FILE: pimoroni-badger2040-${{github.event.release.tag_name || github.sha}}-micropython

View File

@ -0,0 +1,153 @@
name: MicroPython for Badger2040W
on:
push:
pull_request:
release:
types: [created]
env:
MICROPYTHON_VERSION: 67fac4ebc53db6337008ba06df7932faec80f57c
BOARD_TYPE: PIMORONI_BADGER2040W
# MicroPython version will be contained in github.event.release.tag_name for releases
RELEASE_FILE: pimoroni-badger2040w-${{github.event.release.tag_name || github.sha}}-micropython
jobs:
deps:
runs-on: ubuntu-20.04
name: Dependencies
steps:
- name: Workspace Cache
id: cache
uses: actions/cache@v3
with:
path: ${{runner.workspace}}
key: workspace-micropython-${{env.MICROPYTHON_VERSION}}
restore-keys: |
workspace-micropython-${{env.MICROPYTHON_VERSION}}
# Check out MicroPython
- name: Checkout MicroPython
if: steps.cache.outputs.cache-hit != 'true'
uses: actions/checkout@v3
with:
repository: micropython/micropython
ref: ${{env.MICROPYTHON_VERSION}}
submodules: false # MicroPython submodules are hideously broken
path: micropython
- name: Fetch base MicroPython submodules
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
working-directory: micropython
run: git submodule update --init
- name: Fetch Pico SDK submodules
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
working-directory: micropython/lib/pico-sdk
run: git submodule update --init
- name: Build mpy-cross
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
working-directory: micropython/mpy-cross
run: make
build:
needs: deps
name: Build Badger 2040W
runs-on: ubuntu-20.04
steps:
- name: Compiler Cache
uses: actions/cache@v3
with:
path: /home/runner/.ccache
key: ccache-micropython-badger2040w-${{github.ref}}-${{github.sha}}
restore-keys: |
ccache-micropython-badger2040w-${{github.ref}}
ccache-micropython-badger2040w-
- name: Workspace Cache
uses: actions/cache@v3
with:
path: ${{runner.workspace}}
key: workspace-micropython-${{env.MICROPYTHON_VERSION}}
restore-keys: |
workspace-micropython-${{env.MICROPYTHON_VERSION}}
- uses: actions/checkout@v3
with:
submodules: true
path: pimoroni-pico-${{ github.sha }}
# Check out dir2u2f
- uses: actions/checkout@v3
with:
repository: gadgetoid/dir2uf2
ref: v0.0.1
path: dir2uf2
- name: "HACK: MicroPython Board Fixups"
shell: bash
working-directory: micropython/ports/rp2
run: |
../../../pimoroni-pico-${GITHUB_SHA}/micropython/_board/board-fixup.sh badger2040w ${{env.BOARD_TYPE}} ../../../pimoroni-pico-${GITHUB_SHA}/micropython/_board
# Linux deps
- name: Install Compiler & CCache
if: runner.os == 'Linux'
run: |
sudo apt update && sudo apt install ccache gcc-arm-none-eabi
python3 -m pip install pillow
# Build firmware
- name: Configure MicroPython
shell: bash
working-directory: micropython/ports/rp2
run: |
cmake -S . -B build-${{env.BOARD_TYPE}} -DPICO_BUILD_DOCS=0 -DUSER_C_MODULES=../../../pimoroni-pico-${GITHUB_SHA}/micropython/modules/micropython-badger2040w.cmake -DMICROPY_BOARD=${{env.BOARD_TYPE}} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build MicroPython
shell: bash
working-directory: micropython/ports/rp2
run: |
ccache --zero-stats || true
cmake --build build-${{env.BOARD_TYPE}} -j 2
ccache --show-stats || true
- name: Rename .uf2 for artifact
shell: bash
working-directory: micropython/ports/rp2/build-${{env.BOARD_TYPE}}
run: |
cp firmware.uf2 ${{env.RELEASE_FILE}}.uf2
- name: Append Filesystem
shell: bash
run: |
python3 -m pip install littlefs-python
./dir2uf2/dir2uf2 --append-to micropython/ports/rp2/build-${{env.BOARD_TYPE}}/${{env.RELEASE_FILE}}.uf2 --manifest pimoroni-pico-${{ github.sha }}/micropython/examples/badger2040w/uf2-manifest.txt --filename with-examples.uf2 pimoroni-pico-${{ github.sha }}/micropython/examples/badger2040w/
- name: Store .uf2 as artifact
uses: actions/upload-artifact@v3
with:
name: ${{env.RELEASE_FILE}}.uf2
path: micropython/ports/rp2/build-${{env.BOARD_TYPE}}/${{env.RELEASE_FILE}}.uf2
- name: Store .uf2 + examples as artifact
uses: actions/upload-artifact@v3
with:
name: ${{env.RELEASE_FILE}}-with-examples.uf2
path: ${{env.RELEASE_FILE}}-with-examples.uf2
- name: Upload .uf2
if: github.event_name == 'release'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
with:
asset_path: micropython/ports/rp2/build-${{env.BOARD_TYPE}}/${{env.RELEASE_FILE}}.uf2
upload_url: ${{github.event.release.upload_url}}
asset_name: ${{env.RELEASE_FILE}}.uf2
asset_content_type: application/octet-stream

View File

@ -39,13 +39,13 @@ namespace pimoroni {
}
void ICP10125::reset() {
uint16_t command = __bswap16(SOFT_RESET);
uint16_t command = __builtin_bswap16(SOFT_RESET);
i2c->write_blocking(address, (uint8_t *)&command, 2, false);
sleep_ms(10); // Soft reset time is 170us but you can never be too sure...
}
ICP10125::reading ICP10125::measure(meas_command cmd) {
uint16_t command = __bswap16(cmd);
uint16_t command = __builtin_bswap16(cmd);
reading result = {0.0f, 0.0f, OK};
uint16_result results[3];
@ -74,9 +74,9 @@ namespace pimoroni {
if(results[1].crc8 != crc8((uint8_t *)&results[1].data, 2)) {result.status = CRC_FAIL; return result;};
if(results[2].crc8 != crc8((uint8_t *)&results[2].data, 2)) {result.status = CRC_FAIL; return result;};
int temperature = __bswap16(results[0].data);
int temperature = __builtin_bswap16(results[0].data);
// Due to all the byte swapping nonsense I'm not sure if I've discarded the LLSB or LMSB here...
int pressure = ((int32_t)__bswap16(results[1].data) << 8) | (__bswap16(results[2].data >> 8)); // LLSB is discarded
int pressure = ((int32_t)__builtin_bswap16(results[1].data) << 8) | (__builtin_bswap16(results[2].data >> 8)); // LLSB is discarded
process_data(pressure, temperature, &result.pressure, &result.temperature);
return result;
@ -84,7 +84,7 @@ namespace pimoroni {
int ICP10125::chip_id() {
uint16_result result;
uint16_t command = __bswap16(READ_ID);
uint16_t command = __builtin_bswap16(READ_ID);
i2c->write_blocking(address, (uint8_t *)&command, 2, true);
i2c->read_blocking(address, (uint8_t *)&result, 3, false);
@ -93,12 +93,12 @@ namespace pimoroni {
return -1;
}
return __bswap16(result.data) & 0x3f;
return __builtin_bswap16(result.data) & 0x3f;
}
bool ICP10125::read_otp() {
uint16_result result[4];
uint16_t command = __bswap16(READ_OTP);
uint16_t command = __builtin_bswap16(READ_OTP);
uint8_t move_address_ptr[] = {
MOVE_ADDRESS_PTR >> 8, MOVE_ADDRESS_PTR & 0xff,
0x00,
@ -114,7 +114,7 @@ namespace pimoroni {
if(result[x].crc8 != crc8((uint8_t *)&result[x].data, 2)) {
return false;
}
sensor_constants[x] = (float)__bswap16(result[x].data);
sensor_constants[x] = (float)__builtin_bswap16(result[x].data);
}
return true;

View File

@ -205,7 +205,7 @@ namespace pimoroni {
i2c->read_blocking(address, (uint8_t *)&value, 2, false);
// TODO do we need to bswap this return value?
return __bswap16(value);
return __builtin_bswap16(value);
}
// Read a 32-bit register

View File

@ -164,9 +164,15 @@ namespace pimoroni {
}
if (hershey_font) {
hershey::text(hershey_font, [this](int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
line(Point(x1, y1), Point(x2, y2));
}, t, p.x, p.y, s, a);
if(thickness == 1) {
hershey::text(hershey_font, [this](int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
line(Point(x1, y1), Point(x2, y2));
}, t, p.x, p.y, s, a);
} else {
hershey::text(hershey_font, [this](int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
thick_line(Point(x1, y1), Point(x2, y2), thickness);
}, t, p.x, p.y, s, a);
}
return;
}
}
@ -292,6 +298,42 @@ namespace pimoroni {
}
}
void PicoGraphics::thick_line(Point p1, Point p2, uint thickness) {
// general purpose line
// lines are either "shallow" or "steep" based on whether the x delta
// is greater than the y delta
int32_t dx = p2.x - p1.x;
int32_t dy = p2.y - p1.y;
bool shallow = std::abs(dx) > std::abs(dy);
if(shallow) {
// shallow version
int32_t s = std::abs(dx); // number of steps
int32_t sx = dx < 0 ? -1 : 1; // x step value
int32_t sy = (dy << 16) / s; // y step value in fixed 16:16
int32_t x = p1.x;
int32_t y = p1.y << 16;
while(s--) {
int32_t ht = thickness / 2;
rectangle({x - ht, (y >> 16) - ht, ht * 2, ht * 2});
y += sy;
x += sx;
}
}else{
// steep version
int32_t s = std::abs(dy); // number of steps
int32_t sy = dy < 0 ? -1 : 1; // y step value
int32_t sx = (dx << 16) / s; // x step value in fixed 16:16
int32_t y = p1.y;
int32_t x = p1.x << 16;
while(s--) {
int32_t ht = thickness / 2;
rectangle({(x >> 16) - ht, y - ht, ht * 2, ht * 2});
y += sy;
x += sx;
}
}
}
void PicoGraphics::line(Point p1, Point p2) {
// fast horizontal line
if(p1.y == p2.y) {

View File

@ -175,6 +175,7 @@ namespace pimoroni {
PenType pen_type;
Rect bounds;
Rect clip;
uint thickness = 1;
@ -228,6 +229,7 @@ namespace pimoroni {
virtual void set_pen(uint8_t r, uint8_t g, uint8_t b) = 0;
virtual void set_pixel(const Point &p) = 0;
virtual void set_pixel_span(const Point &p, uint l) = 0;
virtual void set_thickness(uint t) = 0;
virtual int create_pen(uint8_t r, uint8_t g, uint8_t b);
virtual int create_pen_hsv(float h, float s, float v);
@ -264,6 +266,7 @@ namespace pimoroni {
void triangle(Point p1, Point p2, Point p3);
void line(Point p1, Point p2);
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b);
void thick_line(Point p1, Point p2, uint thickness);
protected:
void frame_convert_rgb565(conversion_callback_func callback, next_pixel_func get_next_pixel);
@ -276,6 +279,7 @@ namespace pimoroni {
PicoGraphics_Pen1Bit(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override;
void set_pixel(const Point &p) override;
void set_pixel_span(const Point &p, uint l) override;
@ -292,6 +296,7 @@ namespace pimoroni {
PicoGraphics_Pen1BitY(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override;
void set_pixel(const Point &p) override;
void set_pixel_span(const Point &p, uint l) override;
@ -334,6 +339,7 @@ namespace pimoroni {
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
void set_pixel(const Point &p) override;
void set_pixel_span(const Point &p, uint l) override;
@ -360,6 +366,7 @@ namespace pimoroni {
PicoGraphics_PenP4(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override;
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int reset_pen(uint8_t i) override;
@ -389,6 +396,7 @@ namespace pimoroni {
PicoGraphics_PenP8(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override;
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int reset_pen(uint8_t i) override;
@ -410,6 +418,7 @@ namespace pimoroni {
PicoGraphics_PenRGB332(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_pixel(const Point &p) override;
void set_pixel_span(const Point &p, uint l) override;
@ -431,6 +440,7 @@ namespace pimoroni {
PicoGraphics_PenRGB565(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
void set_pixel(const Point &p) override;
@ -447,6 +457,7 @@ namespace pimoroni {
PicoGraphics_PenRGB888(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
void set_pixel(const Point &p) override;

View File

@ -18,6 +18,10 @@ namespace pimoroni {
color = std::max(r, std::max(g, b)) >> 4;
}
void PicoGraphics_Pen1Bit::set_thickness(uint t) {
thickness = t;
}
void PicoGraphics_Pen1Bit::set_pixel(const Point &p) {
// pointer to byte in framebuffer that contains this pixel
uint8_t *buf = (uint8_t *)frame_buffer;

View File

@ -18,6 +18,10 @@ namespace pimoroni {
color = std::max(r, std::max(g, b));
}
void PicoGraphics_Pen1BitY::set_thickness(uint t) {
thickness = t;
}
void PicoGraphics_Pen1BitY::set_pixel(const Point &p) {
// pointer to byte in framebuffer that contains this pixel
uint8_t *buf = (uint8_t *)frame_buffer;

View File

@ -0,0 +1,17 @@
{
"deploy": [
"../deploy.md"
],
"docs": "",
"features": [
"296*128 e-Ink",
"Buttons"
],
"images": [
],
"mcu": "rp2040",
"product": "Badger2040 W (2MiB)",
"thumbnail": "",
"url": "https://shop.pimoroni.com/products/badger-2040w",
"vendor": "Pimoroni"
}

View File

@ -0,0 +1,7 @@
include("../manifest.py")
require("mip")
require("ntptime")
require("urequests")
require("urllib.urequest")
require("umqtt.simple")

View File

@ -0,0 +1,8 @@
# cmake file for Pimoroni Badger 2040W
set(MICROPY_BOARD PICO_W)
set(MICROPY_PY_LWIP ON)
set(MICROPY_PY_NETWORK_CYW43 ON)
# Board specific version of the frozen manifest
set(MICROPY_FROZEN_MANIFEST ${CMAKE_SOURCE_DIR}/boards/PIMORONI_BADGER2040W/manifest.py)

View File

@ -0,0 +1,20 @@
// This is a hack! Need to replace with upstream board definition.
#define MICROPY_HW_BOARD_NAME "Pimoroni Badger2040W 2MB"
#define MICROPY_HW_FLASH_STORAGE_BYTES (848 * 1024)
// Enable networking.
#define MICROPY_PY_NETWORK 1
// CYW43 driver configuration.
#define CYW43_USE_SPI (1)
#define CYW43_LWIP (1)
#define CYW43_GPIO (1)
#define CYW43_SPI_PIO (1)
// For debugging mbedtls - also set
// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose
// #define MODUSSL_MBEDTLS_DEBUG_LEVEL 1
#define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT
#define MICROPY_HW_PIN_RESERVED(i) ((i) == CYW43_PIN_WL_HOST_WAKE || (i) == CYW43_PIN_WL_REG_ON)

View File

@ -0,0 +1,30 @@
GP0,GPIO0
GP1,GPIO1
GP2,GPIO2
GP3,GPIO3
GP4,GPIO4
GP5,GPIO5
GP6,GPIO6
GP7,GPIO7
GP8,GPIO8
GP9,GPIO9
GP10,GPIO10
GP11,GPIO11
GP12,GPIO12
GP13,GPIO13
GP14,GPIO14
GP15,GPIO15
GP16,GPIO16
GP17,GPIO17
GP18,GPIO18
GP19,GPIO19
GP20,GPIO20
GP21,GPIO21
GP22,GPIO22
GP26,GPIO26
GP27,GPIO27
GP28,GPIO28
WL_GPIO0,EXT_GPIO0
WL_GPIO1,EXT_GPIO1
WL_GPIO2,EXT_GPIO2
LED,EXT_GPIO0
1 GP0 GPIO0
2 GP1 GPIO1
3 GP2 GPIO2
4 GP3 GPIO3
5 GP4 GPIO4
6 GP5 GPIO5
7 GP6 GPIO6
8 GP7 GPIO7
9 GP8 GPIO8
10 GP9 GPIO9
11 GP10 GPIO10
12 GP11 GPIO11
13 GP12 GPIO12
14 GP13 GPIO13
15 GP14 GPIO14
16 GP15 GPIO15
17 GP16 GPIO16
18 GP17 GPIO17
19 GP18 GPIO18
20 GP19 GPIO19
21 GP20 GPIO20
22 GP21 GPIO21
23 GP22 GPIO22
24 GP26 GPIO26
25 GP27 GPIO27
26 GP28 GPIO28
27 WL_GPIO0 EXT_GPIO0
28 WL_GPIO1 EXT_GPIO1
29 WL_GPIO2 EXT_GPIO2
30 LED EXT_GPIO0

View File

@ -0,0 +1,6 @@
SRC_DIR=$1
DST_DIR=$2
echo "Applying wakeup_gpio.patch"
cd "$DST_DIR/../../lib/pico-sdk"
git apply "$SRC_DIR/wakeup_gpio.patch"

View File

@ -0,0 +1,137 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// -----------------------------------------------------
// NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO
// SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES
// -----------------------------------------------------
#ifndef _BOARDS_PIMORONI_BADGER2040_H
#define _BOARDS_PIMORONI_BADGER2040_H
// For board detection
#define RASPBERRYPI_PICO_W
#define PIMORONI_BADGER2040
#define PIMORONI_BADGER2040W
// --- BOARD SPECIFIC ---
#define BADGER2040_UART 0
#define BADGER2040_TX_PIN 0
#define BADGER2040_RX_PIN 1
#define BADGER2040_I2C 0
#define BADGER2040_INT_PIN 3
#define BADGER2040_SDA_PIN 4
#define BADGER2040_SCL_PIN 5
#define BADGER2040_3V3_EN_PIN 10
#define BADGER2040_SW_DOWN_PIN 11
#define BADGER2040_SW_A_PIN 12
#define BADGER2040_SW_B_PIN 13
#define BADGER2040_SW_C_PIN 14
#define BADGER2040_SW_UP_PIN 15
#define BADGER2040_INKY_SPI 0
#define BADGER2040_INKY_MISO_PIN 16
#define BADGER2040_INKY_CSN_PIN 17
#define BADGER2040_INKY_SCK_PIN 18
#define BADGER2040_INKY_MOSI_PIN 19
#define BADGER2040_INKY_DC_PIN 20
#define BADGER2040_INKY_RESET_PIN 21
#define BADGER2040_INKY_BUSY_PIN 26
#define BADGER2040_USER_SW_PIN 23
#define BADGER2040_USER_LED_PIN 25
#define BADGER2040_VBUS_DETECT_PIN 24
#define BADGER2040_VREF_POWER_PIN 27
#define BADGER2040_1V2_REF_PIN 28
#define BADGER2040_BAT_SENSE_PIN 29
// --- UART ---
#ifndef PICO_DEFAULT_UART
#define PICO_DEFAULT_UART BADGER2040_UART
#endif
#ifndef PICO_DEFAULT_UART_TX_PIN
#define PICO_DEFAULT_UART_TX_PIN BADGER2040_TX_PIN
#endif
#ifndef PICO_DEFAULT_UART_RX_PIN
#define PICO_DEFAULT_UART_RX_PIN BADGER2040_RX_PIN
#endif
// --- LED ---
#ifndef PICO_DEFAULT_LED_PIN
#define PICO_DEFAULT_LED_PIN BADGER2040_USER_LED_PIN
#endif
// no PICO_DEFAULT_WS2812_PIN
// --- I2C ---
#ifndef PICO_DEFAULT_I2C
#define PICO_DEFAULT_I2C BADGER2040_I2C
#endif
#ifndef PICO_DEFAULT_I2C_SDA_PIN
#define PICO_DEFAULT_I2C_SDA_PIN BADGER2040_SDA_PIN
#endif
#ifndef PICO_DEFAULT_I2C_SCL_PIN
#define PICO_DEFAULT_I2C_SCL_PIN BADGER2040_SCL_PIN
#endif
// --- SPI ---
#ifndef PICO_DEFAULT_SPI
#define PICO_DEFAULT_SPI BADGER2040_INKY_SPI
#endif
#ifndef PICO_DEFAULT_SPI_SCK_PIN
#define PICO_DEFAULT_SPI_SCK_PIN BADGER2040_INKY_SCK_PIN
#endif
#ifndef PICO_DEFAULT_SPI_TX_PIN
#define PICO_DEFAULT_SPI_TX_PIN BADGER2040_INKY_MOSI_PIN
#endif
#ifndef PICO_DEFAULT_SPI_RX_PIN
#define PICO_DEFAULT_SPI_RX_PIN BADGER2040_INKY_MISO_PIN
#endif
#ifndef PICO_DEFAULT_SPI_CSN_PIN
#define PICO_DEFAULT_SPI_CSN_PIN BADGER2040_INKY_CSN_PIN
#endif
// --- FLASH ---
#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1
#ifndef PICO_FLASH_SPI_CLKDIV
#define PICO_FLASH_SPI_CLKDIV 2
#endif
#ifndef PICO_FLASH_SIZE_BYTES
#define PICO_FLASH_SIZE_BYTES (2 * 1024 * 1024)
#endif
#ifndef PICO_RP2040_B0_SUPPORTED
#define PICO_RP2040_B0_SUPPORTED 0
#endif
#ifndef PICO_RP2040_B1_SUPPORTED
#define PICO_RP2040_B1_SUPPORTED 0
#endif
#ifndef CYW43_PIN_WL_HOST_WAKE
#define CYW43_PIN_WL_HOST_WAKE 24
#endif
#ifndef CYW43_PIN_WL_REG_ON
#define CYW43_PIN_WL_REG_ON 23
#endif
#ifndef CYW43_WL_GPIO_COUNT
#define CYW43_WL_GPIO_COUNT 3
#endif
#ifndef CYW43_WL_GPIO_LED_PIN
#define CYW43_WL_GPIO_LED_PIN 0
#endif
#endif

View File

@ -0,0 +1,143 @@
diff --git a/src/rp2_common/pico_runtime/runtime.c b/src/rp2_common/pico_runtime/runtime.c
index 70dd3bb..b8c1ed0 100644
--- a/src/rp2_common/pico_runtime/runtime.c
+++ b/src/rp2_common/pico_runtime/runtime.c
@@ -17,6 +17,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"
@@ -32,6 +33,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];
@@ -61,11 +77,18 @@ 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
// - and USB, syscfg, as this disturbs USB-to-SWD on core 1
+
reset_block(~(
RESETS_RESET_IO_QSPI_BITS |
RESETS_RESET_PADS_QSPI_BITS |
@@ -86,7 +109,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 b2992f6..6091e70 100644
--- a/src/rp2_common/pico_standard_link/crt0.S
+++ b/src/rp2_common/pico_standard_link/crt0.S
@@ -9,6 +9,8 @@
#include "hardware/regs/addressmap.h"
#include "hardware/regs/sio.h"
#include "pico/binary_info/defs.h"
+#include "hardware/regs/resets.h"
+#include "hardware/regs/rosc.h"
#ifdef NDEBUG
#ifndef COLLAPSE_IRQS
@@ -225,6 +227,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
@@ -251,6 +270,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)
@@ -314,6 +337,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
@@ -345,3 +381,9 @@ __get_current_exception:
.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

View File

@ -0,0 +1,3 @@
SSID = ""
PSK = ""
COUNTRY = ""

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -0,0 +1,7 @@
mustelid inc
H. Badger
RP2040
2MB Flash
E ink
296x128px
/badges/badge.jpg

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,172 @@
import time
import badger2040w
import badger_os
import jpegdec
# Global Constants
WIDTH = badger2040w.WIDTH
HEIGHT = badger2040w.HEIGHT
IMAGE_WIDTH = 104
COMPANY_HEIGHT = 30
DETAILS_HEIGHT = 20
NAME_HEIGHT = HEIGHT - COMPANY_HEIGHT - (DETAILS_HEIGHT * 2) - 2
TEXT_WIDTH = WIDTH - IMAGE_WIDTH - 1
COMPANY_TEXT_SIZE = 0.6
DETAILS_TEXT_SIZE = 0.5
LEFT_PADDING = 5
NAME_PADDING = 20
DETAIL_SPACING = 10
BADGE_PATH = "/badges/badge.txt"
DEFAULT_TEXT = """mustelid inc
H. Badger
RP2040
2MB Flash
E ink
296x128px
/badges/badge.jpg
"""
# ------------------------------
# Utility functions
# ------------------------------
# Reduce the size of a string until it fits within a given width
def truncatestring(text, text_size, width):
while True:
length = display.measure_text(text, text_size)
if length > 0 and length > width:
text = text[:-1]
else:
text += ""
return text
# ------------------------------
# Drawing functions
# ------------------------------
# Draw the badge, including user text
def draw_badge():
display.set_pen(0)
display.clear()
# Draw badge image
jpeg.open_file(badge_image)
jpeg.decode(WIDTH - IMAGE_WIDTH, 0)
# Draw a border around the image
display.set_pen(0)
display.line(WIDTH - IMAGE_WIDTH, 0, WIDTH - 1, 0)
display.line(WIDTH - IMAGE_WIDTH, 0, WIDTH - IMAGE_WIDTH, HEIGHT - 1)
display.line(WIDTH - IMAGE_WIDTH, HEIGHT - 1, WIDTH - 1, HEIGHT - 1)
display.line(WIDTH - 1, 0, WIDTH - 1, HEIGHT - 1)
# Uncomment this if a white background is wanted behind the company
# display.set_pen(15)
# display.rectangle(1, 1, TEXT_WIDTH, COMPANY_HEIGHT - 1)
# Draw the company
display.set_pen(15) # Change this to 0 if a white background is used
display.set_font("serif")
display.text(company, LEFT_PADDING, (COMPANY_HEIGHT // 2) + 1, WIDTH, COMPANY_TEXT_SIZE)
# Draw a white background behind the name
display.set_pen(15)
display.rectangle(1, COMPANY_HEIGHT + 1, TEXT_WIDTH, NAME_HEIGHT)
# Draw the name, scaling it based on the available width
display.set_pen(0)
display.set_font("sans")
name_size = 2.0 # A sensible starting scale
while True:
name_length = display.measure_text(name, name_size)
if name_length >= (TEXT_WIDTH - NAME_PADDING) and name_size >= 0.1:
name_size -= 0.01
else:
display.text(name, (TEXT_WIDTH - name_length) // 2, (NAME_HEIGHT // 2) + COMPANY_HEIGHT + 1, WIDTH, name_size)
break
# Draw a white backgrounds behind the details
display.set_pen(15)
display.rectangle(1, HEIGHT - DETAILS_HEIGHT * 2, TEXT_WIDTH, DETAILS_HEIGHT - 1)
display.rectangle(1, HEIGHT - DETAILS_HEIGHT, TEXT_WIDTH, DETAILS_HEIGHT - 1)
# Draw the first detail's title and text
display.set_pen(0)
display.set_font("sans")
name_length = display.measure_text(detail1_title, DETAILS_TEXT_SIZE)
display.text(detail1_title, LEFT_PADDING, HEIGHT - ((DETAILS_HEIGHT * 3) // 2), WIDTH, DETAILS_TEXT_SIZE)
display.text(detail1_text, 5 + name_length + DETAIL_SPACING, HEIGHT - ((DETAILS_HEIGHT * 3) // 2), WIDTH, DETAILS_TEXT_SIZE)
# Draw the second detail's title and text
name_length = display.measure_text(detail2_title, DETAILS_TEXT_SIZE)
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)
# ------------------------------
# Program setup
# ------------------------------
# Create a new Badger and set it to update NORMAL
display = badger2040w.Badger2040W()
display.led(128)
display.set_update_speed(badger2040w.UPDATE_NORMAL)
display.set_thickness(2)
jpeg = jpegdec.JPEG(display.display)
# Open the badge file
try:
badge = open(BADGE_PATH, "r")
except OSError:
with open(BADGE_PATH, "w") as f:
f.write(DEFAULT_TEXT)
f.flush()
badge = open(BADGE_PATH, "r")
# Read in the next 6 lines
company = badge.readline() # "mustelid inc"
name = badge.readline() # "H. Badger"
detail1_title = badge.readline() # "RP2040"
detail1_text = badge.readline() # "2MB Flash"
detail2_title = badge.readline() # "E ink"
detail2_text = badge.readline() # "296x128px"
badge_image = badge.readline() # /badges/badge.jpg
# Truncate all of the text (except for the name as that is scaled)
company = truncatestring(company, COMPANY_TEXT_SIZE, TEXT_WIDTH)
detail1_title = truncatestring(detail1_title, DETAILS_TEXT_SIZE, TEXT_WIDTH)
detail1_text = truncatestring(detail1_text, DETAILS_TEXT_SIZE,
TEXT_WIDTH - DETAIL_SPACING - display.measure_text(detail1_title, DETAILS_TEXT_SIZE))
detail2_title = truncatestring(detail2_title, DETAILS_TEXT_SIZE, TEXT_WIDTH)
detail2_text = truncatestring(detail2_text, DETAILS_TEXT_SIZE,
TEXT_WIDTH - DETAIL_SPACING - display.measure_text(detail2_title, DETAILS_TEXT_SIZE))
# ------------------------------
# Main program
# ------------------------------
draw_badge()
while True:
if display.pressed(badger2040w.BUTTON_A) or display.pressed(badger2040w.BUTTON_B) or display.pressed(badger2040w.BUTTON_C) or display.pressed(badger2040w.BUTTON_UP) or display.pressed(badger2040w.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()

View File

@ -0,0 +1,94 @@
import time
import machine
import ntptime
import badger2040w
display = badger2040w.Badger2040W()
display.set_update_speed(2)
display.set_thickness(4)
WIDTH, HEIGHT = display.get_bounds()
try:
display.connect()
if display.isconnected():
ntptime.settime()
except (RuntimeError, OSError):
pass # no WiFI
rtc = machine.RTC()
display.set_font("gothic")
def draw_clock():
global second_offset, second_unit_offset
hms = "{:02}:{:02}:{:02}".format(hour, minute, second)
ymd = "{:04}/{:02}/{:02}".format(year, month, day)
hms_width = display.measure_text(hms, 1.8)
hms_offset = int((WIDTH / 2) - (hms_width / 2))
ymd_width = display.measure_text(ymd, 1.0)
ymd_offset = int((WIDTH / 2) - (ymd_width / 2))
display.set_pen(15)
display.clear()
display.set_pen(0)
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)
def draw_second():
global second_offset, second_unit_offset
display.set_pen(15)
display.rectangle(second_offset, 8, 75, 56)
display.set_pen(0)
if second // 10 != last_second // 10:
s = "{:02}".format(second)
display.text(s, second_offset, 40, 0, 1.8)
display.partial_update(second_offset, 8, 75, 56)
s = "{}".format(second // 10)
second_unit_offset = second_offset + display.measure_text(s, 1.8)
else:
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)
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
if (year, month, day) == (2021, 1, 1):
rtc.datetime((2022, 2, 28, 0, 12, 0, 0, 0))
last_second = second
last_minute = minute
draw_clock()
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
time.sleep(0.01)

View File

@ -0,0 +1,244 @@
import badger2040w
import gc
import badger_os
# **** Put the name of your text file here *****
text_file = "/books/289-0-wind-in-the-willows-abridged.txt" # File must be on the MicroPython device
gc.collect()
# Global Constants
WIDTH = badger2040w.WIDTH
HEIGHT = badger2040w.HEIGHT
ARROW_THICKNESS = 3
ARROW_WIDTH = 18
ARROW_HEIGHT = 14
ARROW_PADDING = 2
TEXT_PADDING = 4
TEXT_WIDTH = WIDTH - TEXT_PADDING - TEXT_PADDING - ARROW_WIDTH
FONTS = ["sans", "gothic", "cursive", "serif"]
THICKNESSES = [2, 1, 1, 2]
# ------------------------------
# Drawing functions
# ------------------------------
# Draw a upward arrow
def draw_up(x, y, width, height, thickness, padding):
border = (thickness // 4) + padding
display.line(x + border, y + height - border,
x + (width // 2), y + border)
display.line(x + (width // 2), y + border,
x + width - border, y + height - border)
# Draw a downward arrow
def draw_down(x, y, width, height, thickness, padding):
border = (thickness // 2) + padding
display.line(x + border, y + border,
x + (width // 2), y + height - border)
display.line(x + (width // 2), y + height - border,
x + width - border, y + border)
# Draw the frame of the reader
def draw_frame():
display.set_pen(15)
display.clear()
display.set_pen(12)
display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT)
display.set_pen(0)
if state["current_page"] > 0:
draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2),
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2),
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
# ------------------------------
# Program setup
# ------------------------------
# Global variables
state = {
"last_offset": 0,
"current_page": 0,
"font_idx": 0,
"text_size": 0.5,
"offsets": []
}
badger_os.state_load("ebook", state)
text_spacing = int(34 * state["text_size"])
# Create a new Badger and set it to update FAST
display = badger2040w.Badger2040W()
display.led(128)
display.set_update_speed(badger2040w.UPDATE_FAST)
# ------------------------------
# Render page
# ------------------------------
def render_page():
row = 0
line = ""
pos = ebook.tell()
next_pos = pos
add_newline = False
display.set_font(FONTS[state["font_idx"]])
display.set_thickness(THICKNESSES[state["font_idx"]])
while True:
# Read a full line and split it into words
words = ebook.readline().split(" ")
# Take the length of the first word and advance our position
next_word = words[0]
if len(words) > 1:
next_pos += len(next_word) + 1
else:
next_pos += len(next_word) # This is the last word on the line
# Advance our position further if the word contains special characters
if '\u201c' in next_word:
next_word = next_word.replace('\u201c', '\"')
next_pos += 2
if '\u201d' in next_word:
next_word = next_word.replace('\u201d', '\"')
next_pos += 2
if '\u2019' in next_word:
next_word = next_word.replace('\u2019', '\'')
next_pos += 2
# Rewind the file back from the line end to the start of the next word
ebook.seek(next_pos)
# Strip out any new line characters from the word
next_word = next_word.strip()
# If an empty word is encountered assume that means there was a blank line
if len(next_word) == 0:
add_newline = True
# Append the word to the current line and measure its length
appended_line = line
if len(line) > 0 and len(next_word) > 0:
appended_line += " "
appended_line += next_word
appended_length = display.measure_text(appended_line, state["text_size"])
# Would this appended line be longer than the text display area, or was a blank line spotted?
if appended_length >= TEXT_WIDTH or add_newline:
# Yes, so write out the line prior to the append
print(line)
display.set_pen(0)
display.text(line, TEXT_PADDING, (row * text_spacing) + (text_spacing // 2) + TEXT_PADDING, WIDTH, state["text_size"])
# Clear the line and move on to the next row
line = ""
row += 1
# Have we reached the end of the page?
if (row * text_spacing) + text_spacing >= HEIGHT:
print("+++++")
display.update()
# Reset the position to the start of the word that made this line too long
ebook.seek(pos)
return
else:
# Set the line to the word and advance the current position
line = next_word
pos = next_pos
# A new line was spotted, so advance a row
if add_newline:
print("")
row += 1
if (row * text_spacing) + text_spacing >= HEIGHT:
print("+++++")
display.update()
return
add_newline = False
else:
# The appended line was not too long, so set it as the line and advance the current position
line = appended_line
pos = next_pos
# ------------------------------
# Main program loop
# ------------------------------
launch = True
changed = False
# Open the book file
ebook = open(text_file, "r")
if len(state["offsets"]) > state["current_page"]:
ebook.seek(state["offsets"][state["current_page"]])
else:
state["current_page"] = 0
state["offsets"] = []
while True:
# Was the next page button pressed?
if display.pressed(badger2040w.BUTTON_DOWN):
state["current_page"] += 1
changed = True
# Was the previous page button pressed?
if display.pressed(badger2040w.BUTTON_UP):
if state["current_page"] > 0:
state["current_page"] -= 1
if state["current_page"] == 0:
ebook.seek(0)
else:
ebook.seek(state["offsets"][state["current_page"] - 1]) # Retrieve the start position of the last page
changed = True
if display.pressed(badger2040w.BUTTON_A):
state["text_size"] += 0.1
if state["text_size"] > 0.8:
state["text_size"] = 0.5
text_spacing = int(34 * state["text_size"])
state["offsets"] = []
ebook.seek(0)
state["current_page"] = 0
changed = True
if display.pressed(badger2040w.BUTTON_B):
state["font_idx"] += 1
if (state["font_idx"] >= len(FONTS)):
state["font_idx"] = 0
state["offsets"] = []
ebook.seek(0)
state["current_page"] = 0
changed = True
if launch and not changed:
if state["current_page"] > 0 and len(state["offsets"]) > state["current_page"] - 1:
ebook.seek(state["offsets"][state["current_page"] - 1])
changed = True
launch = False
if changed:
draw_frame()
render_page()
# Is the next page one we've not displayed before?
if state["current_page"] >= len(state["offsets"]):
state["offsets"].append(ebook.tell()) # Add its start position to the state["offsets"] list
badger_os.state_save("ebook", state)
changed = False
display.halt()

View File

@ -0,0 +1,129 @@
import badger2040w
import badger_os
# Global Constants
FONT_NAMES = (
("sans", 0.7, 2),
("gothic", 0.7, 2),
("cursive", 0.7, 2),
("serif", 0.7, 2),
("serif_italic", 0.7, 2),
("bitmap6", 3, 1),
("bitmap8", 2, 1),
("bitmap14_outline", 1, 1)
)
WIDTH = badger2040w.WIDTH
HEIGHT = badger2040w.HEIGHT
MENU_TEXT_SIZE = 0.5
MENU_SPACING = 16
MENU_WIDTH = 84
MENU_PADDING = 5
TEXT_INDENT = MENU_WIDTH + 10
ARROW_THICKNESS = 3
ARROW_WIDTH = 18
ARROW_HEIGHT = 14
ARROW_PADDING = 2
# ------------------------------
# Drawing functions
# ------------------------------
# Draw a upward arrow
def draw_up(x, y, width, height, thickness, padding):
border = (thickness // 4) + padding
display.line(x + border, y + height - border,
x + (width // 2), y + border)
display.line(x + (width // 2), y + border,
x + width - border, y + height - border)
# Draw a downward arrow
def draw_down(x, y, width, height, thickness, padding):
border = (thickness // 2) + padding
display.line(x + border, y + border,
x + (width // 2), y + height - border)
display.line(x + (width // 2), y + height - border,
x + width - border, y + border)
# Draw the frame of the reader
def draw_frame():
display.set_pen(15)
display.clear()
display.set_pen(12)
display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT)
display.set_pen(0)
draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2),
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2),
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
# Draw the fonts and menu
def draw_fonts():
display.set_font("bitmap8")
for i in range(len(FONT_NAMES)):
name, size, thickness = FONT_NAMES[i]
display.set_pen(0)
if i == state["selected_font"]:
display.rectangle(0, i * MENU_SPACING, MENU_WIDTH, MENU_SPACING)
display.set_pen(15)
display.text(name, MENU_PADDING, (i * MENU_SPACING) + int((MENU_SPACING - 8) / 2), WIDTH, MENU_TEXT_SIZE)
name, size, thickness = FONT_NAMES[state["selected_font"]]
display.set_font(name)
y = 0 if name.startswith("bitmap") else 10
display.set_pen(0)
for line in ("The quick", "brown fox", "jumps over", "the lazy dog.", "0123456789", "!\"£$%^&*()"):
display.text(line, TEXT_INDENT, y, WIDTH, size)
y += 22
display.update()
# ------------------------------
# Program setup
# ------------------------------
# Global variables
state = {"selected_font": 0}
badger_os.state_load("fonts", state)
# Create a new Badger and set it to update FAST
display = badger2040w.Badger2040W()
display.led(128)
display.set_update_speed(badger2040w.UPDATE_FAST)
changed = not badger2040w.woken_by_button()
# ------------------------------
# Main program loop
# ------------------------------
while True:
if display.pressed(badger2040w.BUTTON_UP):
state["selected_font"] -= 1
if state["selected_font"] < 0:
state["selected_font"] = len(FONT_NAMES) - 1
changed = True
if display.pressed(badger2040w.BUTTON_DOWN):
state["selected_font"] += 1
if state["selected_font"] >= len(FONT_NAMES):
state["selected_font"] = 0
changed = True
if changed:
draw_frame()
draw_fonts()
badger_os.state_save("fonts", state)
changed = False
display.halt()

View File

@ -0,0 +1,41 @@
import badger2040w
from badger2040w import WIDTH
TEXT_SIZE = 0.45
LINE_HEIGHT = 20
display = badger2040w.Badger2040W()
display.led(128)
display.set_thickness(2)
# 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("help", WIDTH - display.measure_text("help", 0.4) - 4, 4, WIDTH, 1)
display.set_font("sans")
display.set_pen(0)
TEXT_SIZE = 0.62
y = 20 + int(LINE_HEIGHT / 2)
display.set_font("sans")
display.text("Up/Down - Change page", 0, y, WIDTH, TEXT_SIZE)
y += LINE_HEIGHT
display.text("a, b or c - Launch app", 0, y, WIDTH, TEXT_SIZE)
y += LINE_HEIGHT
display.text("a & c - Exit app", 0, y, WIDTH, TEXT_SIZE)
y += LINE_HEIGHT
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.halt()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,119 @@
import os
import sys
import time
import badger2040w
from badger2040w 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 = badger2040w.Badger2040W()
display.led(128)
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:
IMAGES = [f for f in os.listdir("/images") if f.endswith(".jpg")]
TOTAL_IMAGES = len(IMAGES)
except OSError:
pass
state = {
"current_image": 0,
"show_info": True
}
def show_image(n):
file = IMAGES[n]
name = file.split(".")[0]
jpeg.open_file("/images/{}".format(file))
jpeg.decode()
if state["show_info"]:
name_length = display.measure_text(name, 0.5)
display.set_pen(0)
display.rectangle(0, HEIGHT - 21, name_length + 11, 21)
display.set_pen(15)
display.rectangle(0, HEIGHT - 20, name_length + 10, 20)
display.set_pen(0)
display.text(name, 5, HEIGHT - 10, WIDTH, 0.5)
for i in range(TOTAL_IMAGES):
x = 286
y = int((128 / 2) - (TOTAL_IMAGES * 10 / 2) + (i * 10))
display.set_pen(0)
display.rectangle(x, y, 8, 8)
if state["current_image"] != i:
display.set_pen(15)
display.rectangle(x + 1, y + 1, 6, 6)
display.update()
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()
badger_os.state_load("image", state)
changed = not badger2040w.woken_by_button()
while True:
if display.pressed(badger2040w.BUTTON_UP):
if state["current_image"] > 0:
state["current_image"] -= 1
changed = True
if display.pressed(badger2040w.BUTTON_DOWN):
if state["current_image"] < TOTAL_IMAGES - 1:
state["current_image"] += 1
changed = True
if display.pressed(badger2040w.BUTTON_A):
state["show_info"] = not state["show_info"]
changed = True
if display.pressed(badger2040w.BUTTON_B) or display.pressed(badger2040w.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"])
changed = False
# Halt the Badger to save power, it will wake up if any of the front buttons are pressed
display.halt()

View File

@ -0,0 +1,44 @@
import badger2040w
from badger2040w import WIDTH
TEXT_SIZE = 1
LINE_HEIGHT = 15
display = badger2040w.Badger2040W()
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/badger2040w", 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.halt()

View File

@ -0,0 +1,312 @@
import binascii
import badger2040w
import badger_os
# **** Put your list title here *****
list_title = "Checklist"
list_file = "checklist.txt"
# Global Constantsu
WIDTH = badger2040w.WIDTH
HEIGHT = badger2040w.HEIGHT
ARROW_THICKNESS = 3
ARROW_WIDTH = 18
ARROW_HEIGHT = 14
ARROW_PADDING = 2
MAX_ITEM_CHARS = 26
TITLE_TEXT_SIZE = 0.7
ITEM_TEXT_SIZE = 0.6
ITEM_SPACING = 20
LIST_START = 40
LIST_PADDING = 2
LIST_WIDTH = WIDTH - LIST_PADDING - LIST_PADDING - ARROW_WIDTH
LIST_HEIGHT = HEIGHT - LIST_START - LIST_PADDING - ARROW_HEIGHT
# Default list items - change the list items by editing checklist.txt
list_items = ["Badger", "Badger", "Badger", "Badger", "Badger", "Mushroom", "Mushroom", "Snake"]
save_checklist = False
try:
with open("checklist.txt", "r") as f:
raw_list_items = f.read()
if raw_list_items.find(" X\n") != -1:
# Have old style checklist, preserve state and note we should resave the list to remove the Xs
list_items = []
state = {
"current_item": 0,
"checked": []
}
for item in raw_list_items.strip().split("\n"):
if item.endswith(" X"):
state["checked"].append(True)
item = item[:-2]
else:
state["checked"].append(False)
list_items.append(item)
state["items_hash"] = binascii.crc32("\n".join(list_items))
badger_os.state_save("list", state)
save_checklist = True
else:
list_items = [item.strip() for item in raw_list_items.strip().split("\n")]
except OSError:
save_checklist = True
if save_checklist:
with open("checklist.txt", "w") as f:
for item in list_items:
f.write(item + "\n")
# ------------------------------
# Drawing functions
# ------------------------------
# Draw the list of items
def draw_list(items, item_states, start_item, highlighted_item, x, y, width, height, item_height, columns):
item_x = 0
item_y = 0
current_col = 0
for i in range(start_item, len(items)):
if i == highlighted_item:
display.set_pen(12)
display.rectangle(item_x, item_y + y - (item_height // 2), width // columns, item_height)
display.set_pen(0)
display.text(items[i], item_x + x + item_height, item_y + y, WIDTH, ITEM_TEXT_SIZE)
draw_checkbox(item_x, item_y + y - (item_height // 2), item_height, 15, 0, 2, item_states[i], 2)
item_y += item_height
if item_y >= height - (item_height // 2):
item_x += width // columns
item_y = 0
current_col += 1
if current_col >= columns:
return
# Draw a upward arrow
def draw_up(x, y, width, height, thickness, padding):
border = (thickness // 4) + padding
display.line(x + border, y + height - border,
x + (width // 2), y + border)
display.line(x + (width // 2), y + border,
x + width - border, y + height - border)
# Draw a downward arrow
def draw_down(x, y, width, height, thickness, padding):
border = (thickness // 2) + padding
display.line(x + border, y + border,
x + (width // 2), y + height - border)
display.line(x + (width // 2), y + height - border,
x + width - border, y + border)
# Draw a left arrow
def draw_left(x, y, width, height, thickness, padding):
border = (thickness // 2) + padding
display.line(x + width - border, y + border,
x + border, y + (height // 2))
display.line(x + border, y + (height // 2),
x + width - border, y + height - border)
# Draw a right arrow
def draw_right(x, y, width, height, thickness, padding):
border = (thickness // 2) + padding
display.line(x + border, y + border,
x + width - border, y + (height // 2))
display.line(x + width - border, y + (height // 2),
x + border, y + height - border)
# Draw a tick
def draw_tick(x, y, width, height, thickness, padding):
border = (thickness // 2) + padding
display.line(x + border, y + ((height * 2) // 3),
x + (width // 2), y + height - border)
display.line(x + (width // 2), y + height - border,
x + width - border, y + border)
# Draw a cross
def draw_cross(x, y, width, height, thickness, padding):
border = (thickness // 2) + padding
display.line(x + border, y + border, x + width - border, y + height - border)
display.line(x + width - border, y + border, x + border, y + height - border)
# Draw a checkbox with or without a tick
def draw_checkbox(x, y, size, background, foreground, thickness, tick, padding):
border = (thickness // 2) + padding
display.set_pen(background)
display.rectangle(x + border, y + border, size - (border * 2), size - (border * 2))
display.set_pen(foreground)
display.line(x + border, y + border, x + size - border, y + border)
display.line(x + border, y + border, x + border, y + size - border)
display.line(x + size - border, y + border, x + size - border, y + size - border)
display.line(x + border, y + size - border, x + size - border, y + size - border)
if tick:
draw_tick(x, y, size, size, thickness, 2 + border)
# ------------------------------
# Program setup
# ------------------------------
changed = not badger2040w.woken_by_button()
state = {
"current_item": 0,
}
badger_os.state_load("list", state)
items_hash = binascii.crc32("\n".join(list_items))
if "items_hash" not in state or state["items_hash"] != items_hash:
# Item list changed, or not yet written reset the list
state["current_item"] = 0
state["items_hash"] = items_hash
state["checked"] = [False] * len(list_items)
changed = True
# Global variables
items_per_page = 0
# Create a new Badger and set it to update FAST
display = badger2040w.Badger2040W()
display.led(128)
display.set_font("sans")
display.set_thickness(2)
if changed:
display.set_update_speed(badger2040w.UPDATE_FAST)
else:
display.set_update_speed(badger2040w.UPDATE_TURBO)
# Find out what the longest item is
longest_item = 0
for i in range(len(list_items)):
while True:
item = list_items[i]
item_length = display.measure_text(item, ITEM_TEXT_SIZE)
if item_length > 0 and item_length > LIST_WIDTH - ITEM_SPACING:
list_items[i] = item[:-1]
else:
break
longest_item = max(longest_item, display.measure_text(list_items[i], ITEM_TEXT_SIZE))
# And use that to calculate the number of columns we can fit onscreen and how many items that would give
list_columns = 1
while longest_item + ITEM_SPACING < (LIST_WIDTH // (list_columns + 1)):
list_columns += 1
items_per_page = ((LIST_HEIGHT // ITEM_SPACING) + 1) * list_columns
# ------------------------------
# Main program loop
# ------------------------------
while True:
if len(list_items) > 0:
if display.pressed(badger2040w.BUTTON_A):
if state["current_item"] > 0:
page = state["current_item"] // items_per_page
state["current_item"] = max(state["current_item"] - (items_per_page) // list_columns, 0)
if page != state["current_item"] // items_per_page:
display.update_speed(badger2040w.UPDATE_FAST)
changed = True
if display.pressed(badger2040w.BUTTON_B):
state["checked"][state["current_item"]] = not state["checked"][state["current_item"]]
changed = True
if display.pressed(badger2040w.BUTTON_C):
if state["current_item"] < len(list_items) - 1:
page = state["current_item"] // items_per_page
state["current_item"] = min(state["current_item"] + (items_per_page) // list_columns, len(list_items) - 1)
if page != state["current_item"] // items_per_page:
display.update_speed(badger2040w.UPDATE_FAST)
changed = True
if display.pressed(badger2040w.BUTTON_UP):
if state["current_item"] > 0:
state["current_item"] -= 1
changed = True
if display.pressed(badger2040w.BUTTON_DOWN):
if state["current_item"] < len(list_items) - 1:
state["current_item"] += 1
changed = True
if changed:
badger_os.state_save("list", state)
display.set_pen(15)
display.clear()
display.set_pen(12)
display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT)
display.rectangle(0, HEIGHT - ARROW_HEIGHT, WIDTH, ARROW_HEIGHT)
y = LIST_PADDING + 12
display.set_pen(0)
display.text(list_title, LIST_PADDING, y, WIDTH, TITLE_TEXT_SIZE)
y += 12
display.set_pen(0)
display.line(LIST_PADDING, y, WIDTH - LIST_PADDING - ARROW_WIDTH, y)
if len(list_items) > 0:
page_item = 0
if items_per_page > 0:
page_item = (state["current_item"] // items_per_page) * items_per_page
# Draw the list
display.set_pen(0)
draw_list(list_items, state["checked"], page_item, state["current_item"], LIST_PADDING, LIST_START,
LIST_WIDTH, LIST_HEIGHT, ITEM_SPACING, list_columns)
# Draw the interaction button icons
display.set_pen(0)
# Previous item
if state["current_item"] > 0:
draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2),
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
# Next item
if state["current_item"] < (len(list_items) - 1):
draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2),
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
# Previous column
if state["current_item"] > 0:
draw_left((WIDTH // 7) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT,
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
# Next column
if state["current_item"] < (len(list_items) - 1):
draw_right(((WIDTH * 6) // 7) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT,
ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
if state["checked"][state["current_item"]]:
# Tick off item
draw_cross((WIDTH // 2) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT,
ARROW_HEIGHT, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
else:
# Untick item
draw_tick((WIDTH // 2) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT,
ARROW_HEIGHT, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING)
else:
# Say that the list is empty
empty_text = "Nothing Here"
text_length = display.measure_text(empty_text, ITEM_TEXT_SIZE)
display.text(empty_text, ((LIST_PADDING + LIST_WIDTH) - text_length) // 2, (LIST_HEIGHT // 2) + LIST_START - (ITEM_SPACING // 4), WIDTH, ITEM_TEXT_SIZE)
display.update()
display.set_update_speed(badger2040w.UPDATE_TURBO)
changed = False
display.halt()

View File

@ -0,0 +1,48 @@
import badger2040w as badger2040
from badger2040w import WIDTH
import network
TEXT_SIZE = 1
LINE_HEIGHT = 16
# Display Setup
display = badger2040.Badger2040W()
display.led(128)
# Connects to the wireless network. Ensure you have entered your details in WIFI_CONFIG.py :).
display.connect()
net = network.WLAN(network.STA_IF).ifconfig()
# Page Header
display.set_pen(15)
display.clear()
display.set_pen(0)
display.set_pen(0)
display.rectangle(0, 0, WIDTH, 20)
display.set_pen(15)
display.text("badgerOS", 3, 4)
display.text("Network Details", WIDTH - display.measure_text("Network Details") - 4, 4)
display.set_pen(0)
y = 35 + int(LINE_HEIGHT / 2)
if net:
display.text("> LOCAL IP: {}".format(net[0]), 0, y, WIDTH)
y += LINE_HEIGHT
display.text("> Subnet: {}".format(net[1]), 0, y, WIDTH)
y += LINE_HEIGHT
display.text("> Gateway: {}".format(net[2]), 0, y, WIDTH)
y += LINE_HEIGHT
display.text("> DNS: {}".format(net[3]), 0, y, WIDTH)
else:
display.text("> No network connection!", 0, y, WIDTH)
y += LINE_HEIGHT
display.text("> Check details in WIFI_CONFIG.py", 0, y, WIDTH)
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.halt()

View File

@ -0,0 +1,215 @@
import badger2040w as badger2040
from badger2040w import WIDTH
import machine
from urllib import urequest
import gc
import qrcode
import badger_os
# URLS to use (Entertainment, Science and Technology)
URL = ["http://feeds.bbci.co.uk/news/entertainment_and_arts/rss.xml",
"http://feeds.bbci.co.uk/news/science_and_environment/rss.xml",
"http://feeds.bbci.co.uk/news/technology/rss.xml"]
code = qrcode.QRCode()
state = {
"current_page": 0,
"feed": 2
}
badger_os.state_load("news", state)
# Display Setup
display = badger2040.Badger2040W()
display.led(128)
display.set_update_speed(2)
# Setup buttons
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN)
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)
button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN)
button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN)
def read_until(stream, char):
result = b""
while True:
c = stream.read(1)
if c == char:
return result
result += c
def discard_until(stream, c):
while stream.read(1) != c:
pass
def parse_xml_stream(s, accept_tags, group_by, max_items=3):
tag = []
text = b""
count = 0
current = {}
while True:
char = s.read(1)
if len(char) == 0:
break
if char == b"<":
next_char = s.read(1)
# Discard stuff like <?xml vers...
if next_char == b"?":
discard_until(s, b">")
continue
# Detect <![CDATA
elif next_char == b"!":
s.read(1) # Discard [
discard_until(s, b"[") # Discard CDATA[
text = read_until(s, b"]")
discard_until(s, b">") # Discard ]>
gc.collect()
elif next_char == b"/":
current_tag = read_until(s, b">")
top_tag = tag[-1]
# Populate our result dict
if top_tag in accept_tags:
current[top_tag.decode("utf-8")] = text.decode("utf-8")
# If we've found a group of items, yield the dict
elif top_tag == group_by:
yield current
current = {}
count += 1
if count == max_items:
return
tag.pop()
text = b""
gc.collect()
continue
else:
current_tag = read_until(s, b">")
tag += [next_char + current_tag.split(b" ")[0]]
text = b""
gc.collect()
else:
text += char
def measure_qr_code(size, code):
w, h = code.get_size()
module_size = int(size / w)
return module_size * w, module_size
def draw_qr_code(ox, oy, size, code):
size, module_size = measure_qr_code(size, code)
display.set_pen(15)
display.rectangle(ox, oy, size, size)
display.set_pen(0)
for x in range(size):
for y in range(size):
if code.get_module(x, y):
display.rectangle(ox + x * module_size, oy + y * module_size, module_size, module_size)
# A function to get the data from an RSS Feed, this in case BBC News.
def get_rss(url):
try:
stream = urequest.urlopen(url)
output = list(parse_xml_stream(stream, [b"title", b"description", b"guid", b"pubDate"], b"item"))
return output
except OSError as e:
print(e)
return False
# Connects to the wireless network. Ensure you have entered your details in WIFI_CONFIG.py :).
display.connect()
print(state["feed"])
feed = get_rss(URL[state["feed"]])
def draw_page():
# Clear the display
display.set_pen(15)
display.clear()
display.set_pen(0)
# Draw the page header
display.set_font("bitmap6")
display.set_pen(0)
display.rectangle(0, 0, WIDTH, 20)
display.set_pen(15)
display.text("News", 3, 4)
display.text("Page: " + str(state["current_page"] + 1), WIDTH - display.measure_text("Page: ") - 4, 4)
display.set_pen(0)
display.set_font("bitmap8")
# Draw articles from the feed if they're available.
if feed:
page = state["current_page"]
display.set_pen(0)
display.text(feed[page]["title"], 2, 30, WIDTH - 130, 2)
code.set_text(feed[page]["guid"])
draw_qr_code(WIDTH - 100, 25, 100, code)
else:
display.set_pen(0)
display.rectangle(0, 60, WIDTH, 25)
display.set_pen(15)
display.text("Unable to display news! Check your network settings in WIFI_CONFIG.py", 5, 65, WIDTH, 1)
display.update()
draw_page()
while 1:
changed = False
if button_down.value():
if state["current_page"] < 2:
state["current_page"] += 1
changed = True
if button_up.value():
if state["current_page"] > 0:
state["current_page"] -= 1
changed = True
if button_a.value():
state["feed"] = 0
state["current_page"] = 0
feed = get_rss(URL[state["feed"]])
badger_os.state_save("news", state)
changed = True
if button_b.value():
state["feed"] = 1
state["current_page"] = 0
feed = get_rss(URL[state["feed"]])
badger_os.state_save("news", state)
changed = True
if button_c.value():
state["feed"] = 2
state["current_page"] = 0
feed = get_rss(URL[state["feed"]])
badger_os.state_save("news", state)
changed = True
if changed:
draw_page()

View File

@ -0,0 +1,139 @@
import badger2040w
import qrcode
import time
import os
import badger_os
# Check that the qrcodes directory exists, if not, make it
try:
os.mkdir("/qrcodes")
except OSError:
pass
# Check that there is a qrcode.txt, if not preload
try:
text = open("/qrcodes/qrcode.txt", "r")
except OSError:
text = open("/qrcodes/qrcode.txt", "w")
text.write("""https://pimoroni.com/badger2040w
Badger 2040 W
* 296x128 1-bit e-ink
* 2.4GHz wireless
* five user buttons
* user LED
* 2MB QSPI flash
Scan this code to learn
more about Badger 2040 W.
""")
text.flush()
text.seek(0)
# Load all available QR Code Files
try:
CODES = [f for f in os.listdir("/qrcodes") if f.endswith(".txt")]
TOTAL_CODES = len(CODES)
except OSError:
pass
print(f'There are {TOTAL_CODES} QR Codes available:')
for codename in CODES:
print(f'File: {codename}')
display = badger2040w.Badger2040W()
code = qrcode.QRCode()
state = {
"current_qr": 0
}
def measure_qr_code(size, code):
w, h = code.get_size()
module_size = int(size / w)
return module_size * w, module_size
def draw_qr_code(ox, oy, size, code):
size, module_size = measure_qr_code(size, code)
display.set_pen(15)
display.rectangle(ox, oy, size, size)
display.set_pen(0)
for x in range(size):
for y in range(size):
if code.get_module(x, y):
display.rectangle(ox + x * module_size, oy + y * module_size, module_size, module_size)
def draw_qr_file(n):
display.led(128)
file = CODES[n]
codetext = open("/qrcodes/{}".format(file), "r")
lines = codetext.read().strip().split("\n")
code_text = lines.pop(0)
title_text = lines.pop(0)
detail_text = lines
# Clear the Display
display.set_pen(15) # Change this to 0 if a white background is used
display.clear()
display.set_pen(0)
code.set_text(code_text)
size, _ = measure_qr_code(128, code)
left = top = int((badger2040w.HEIGHT / 2) - (size / 2))
draw_qr_code(left, top, 128, code)
left = 128 + 5
display.text(title_text, left, 20, badger2040w.WIDTH, 2)
top = 40
for line in detail_text:
display.text(line, left, top, badger2040w.WIDTH, 1)
top += 10
if TOTAL_CODES > 1:
for i in range(TOTAL_CODES):
x = 286
y = int((128 / 2) - (TOTAL_CODES * 10 / 2) + (i * 10))
display.set_pen(0)
display.rectangle(x, y, 8, 8)
if state["current_qr"] != i:
display.set_pen(15)
display.rectangle(x + 1, y + 1, 6, 6)
display.update()
badger_os.state_load("qrcodes", state)
changed = not badger2040w.woken_by_button()
while True:
if TOTAL_CODES > 1:
if display.pressed(badger2040w.BUTTON_UP):
if state["current_qr"] > 0:
state["current_qr"] -= 1
changed = True
if display.pressed(badger2040w.BUTTON_DOWN):
if state["current_qr"] < TOTAL_CODES - 1:
state["current_qr"] += 1
changed = True
if display.pressed(badger2040w.BUTTON_B) or display.pressed(badger2040w.BUTTON_C):
display.set_pen(15)
display.clear()
badger_os.warning(display, "To add QR codes, connect Badger 2040 W to a PC, load up Thonny, and add files to /qrcodes directory.")
time.sleep(4)
changed = True
if changed:
draw_qr_file(state["current_qr"])
badger_os.state_save("qrcodes", state)
changed = False
# Halt the Badger to save power, it will wake up if any of the front buttons are pressed
display.halt()

View File

@ -0,0 +1,106 @@
# This example grabs current weather details from Open Meteo and displays them on Badger 2040 W.
# Find out more about the Open Meteo API at https://open-meteo.com
import badger2040w as badger2040
from badger2040w import WIDTH
import urequests
import jpegdec
# Set your latitude/longitude here (find yours by right clicking in Google Maps!)
LAT = 53.38609085276884
LNG = -1.4239983439328177
TIMEZONE = "auto" # determines time zone from lat/long
URL = "http://api.open-meteo.com/v1/forecast?latitude=" + str(LAT) + "&longitude=" + str(LNG) + "&current_weather=true&timezone=" + TIMEZONE
# Display Setup
display = badger2040.Badger2040W()
display.led(128)
display.set_update_speed(2)
jpeg = jpegdec.JPEG(display.display)
# Connects to the wireless network. Ensure you have entered your details in WIFI_CONFIG.py :).
display.connect()
def get_data():
global weathercode, temperature, windspeed, winddirection, date, time
print(f"Requesting URL: {URL}")
r = urequests.get(URL)
# open the json data
j = r.json()
print("Data obtained!")
print(j)
# parse relevant data from JSON
current = j["current_weather"]
temperature = current["temperature"]
windspeed = current["windspeed"]
winddirection = calculate_bearing(current["winddirection"])
weathercode = current["weathercode"]
date, time = current["time"].split("T")
r.close()
def calculate_bearing(d):
# calculates a compass direction from the wind direction in degrees
dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
ix = round(d / (360. / len(dirs)))
return dirs[ix % len(dirs)]
def draw_page():
# Clear the display
display.set_pen(15)
display.clear()
display.set_pen(0)
# Draw the page header
display.set_font("bitmap6")
display.set_pen(0)
display.rectangle(0, 0, WIDTH, 20)
display.set_pen(15)
display.text("Weather", 3, 4)
display.set_pen(0)
display.set_font("bitmap8")
if temperature is not None:
# Choose an appropriate icon based on the weather code
# Weather codes from https://open-meteo.com/en/docs
# Weather icons from https://fontawesome.com/
if weathercode in [71, 73, 75, 77, 85, 86]: # codes for snow
jpeg.open_file("/icons/icon-snow.jpg")
elif weathercode in [51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82]: # codes for rain
jpeg.open_file("/icons/icon-rain.jpg")
elif weathercode in [1, 2, 3, 45, 48]: # codes for cloud
jpeg.open_file("/icons/icon-cloud.jpg")
elif weathercode in [0]: # codes for sun
jpeg.open_file("/icons/icon-sun.jpg")
elif weathercode in [95, 96, 99]: # codes for storm
jpeg.open_file("/icons/icon-storm.jpg")
jpeg.decode(13, 40, jpegdec.JPEG_SCALE_FULL)
display.set_pen(0)
display.text(f"Temperature: {temperature}°C", int(WIDTH / 3), 28, WIDTH - 105, 2)
display.text(f"Wind Speed: {windspeed}kmph", int(WIDTH / 3), 48, WIDTH - 105, 2)
display.text(f"Wind Direction: {winddirection}", int(WIDTH / 3), 68, WIDTH - 105, 2)
display.text(f"Last update: {date}, {time}", int(WIDTH / 3), 88, WIDTH - 105, 2)
else:
display.set_pen(0)
display.rectangle(0, 60, WIDTH, 25)
display.set_pen(15)
display.text("Unable to display weather! Check your network settings in WIFI_CONFIG.py", 5, 65, WIDTH, 1)
display.update()
get_data()
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.halt()

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,3 @@
Images must be 296x128 pixel JPEGs
Create a new "images" directory via Thonny, and upload your .jpg files there.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,183 @@
import gc
import os
import time
import math
import badger2040w as badger2040
import badger_os
import jpegdec
APP_DIR = "/examples"
FONT_SIZE = 2
changed = False
exited_to_launcher = False
woken_by_button = badger2040.woken_by_button() # Must be done before we clear_pressed_to_wake
if badger2040.pressed_to_wake(badger2040.BUTTON_A) and badger2040.pressed_to_wake(badger2040.BUTTON_C):
# Pressing A and C together at start quits app
exited_to_launcher = badger_os.state_clear_running()
badger2040.reset_pressed_to_wake()
else:
# Otherwise restore previously running app
badger_os.state_launch()
display = badger2040.Badger2040W()
display.set_font("bitmap8")
display.led(128)
jpeg = jpegdec.JPEG(display.display)
state = {
"page": 0,
"running": "launcher"
}
badger_os.state_load("launcher", state)
examples = [x[:-3] for x in os.listdir("/examples") if x.endswith(".py")]
# Approximate center lines for buttons A, B and C
centers = (41, 147, 253)
MAX_PAGE = math.ceil(len(examples) / 3)
WIDTH = 296
def map_value(input, in_min, in_max, out_min, out_max):
return (((input - in_min) * (out_max - out_min)) / (in_max - in_min)) + out_min
def draw_disk_usage(x):
_, f_used, _ = badger_os.get_disk_usage()
display.set_pen(15)
display.image(
bytearray(
(
0b00000000,
0b00111100,
0b00111100,
0b00111100,
0b00111000,
0b00000000,
0b00000000,
0b00000001,
)
),
8,
8,
x,
4,
)
display.rectangle(x + 10, 3, 80, 10)
display.set_pen(0)
display.rectangle(x + 11, 4, 78, 8)
display.set_pen(15)
display.rectangle(x + 12, 5, int(76 / 100.0 * f_used), 6)
display.text("{:.2f}%".format(f_used), x + 91, 4, WIDTH, 1.0)
def render():
display.set_pen(15)
display.clear()
display.set_pen(0)
max_icons = min(3, len(examples[(state["page"] * 3):]))
for i in range(max_icons):
x = centers[i]
label = examples[i + (state["page"] * 3)]
icon_label = label.replace("_", "-")
icon = f"{APP_DIR}/icon-{icon_label}.jpg"
label = label.replace("_", " ")
jpeg.open_file(icon)
jpeg.decode(x - 26, 30)
display.set_pen(0)
w = display.measure_text(label, FONT_SIZE)
display.text(label, int(x - (w / 2)), 16 + 80, WIDTH, FONT_SIZE)
for i in range(MAX_PAGE):
x = 286
y = int((128 / 2) - (MAX_PAGE * 10 / 2) + (i * 10))
display.set_pen(0)
display.rectangle(x, y, 8, 8)
if state["page"] != i:
display.set_pen(15)
display.rectangle(x + 1, y + 1, 6, 6)
display.set_pen(0)
display.rectangle(0, 0, WIDTH, 16)
draw_disk_usage(90)
display.set_pen(15)
display.text("badgerOS", 4, 4, WIDTH, 1.0)
display.update()
def wait_for_user_to_release_buttons():
while display.pressed_any():
time.sleep(0.01)
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)
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()
if exited_to_launcher or not woken_by_button:
wait_for_user_to_release_buttons()
display.set_update_speed(badger2040.UPDATE_MEDIUM)
render()
display.set_update_speed(badger2040.UPDATE_FAST)
while True:
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()

View File

@ -0,0 +1,181 @@
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
BUTTON_DOWN = 11
BUTTON_A = 12
BUTTON_B = 13
BUTTON_C = 14
BUTTON_UP = 15
BUTTON_USER = None # User button not available on W
BUTTON_MASK = 0b11111 << 11
SYSTEM_VERY_SLOW = 0
SYSTEM_SLOW = 1
SYSTEM_NORMAL = 2
SYSTEM_FAST = 3
SYSTEM_TURBO = 4
UPDATE_NORMAL = 0
UPDATE_MEDIUM = 1
UPDATE_FAST = 2
UPDATE_TURBO = 3
LED = 22
ENABLE_3V3 = 10
BUSY = 26
WIDTH = 296
HEIGHT = 128
SYSTEM_FREQS = [
4000000,
12000000,
48000000,
133000000,
250000000
]
BUTTONS = {
BUTTON_DOWN: machine.Pin(BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN),
BUTTON_A: machine.Pin(BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN),
BUTTON_B: machine.Pin(BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN),
BUTTON_C: machine.Pin(BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN),
BUTTON_UP: machine.Pin(BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN),
}
WAKEUP_MASK = 0
def woken_by_button():
return wakeup.get_gpio_state() & BUTTON_MASK > 0
def pressed_to_wake(button):
return wakeup.get_gpio_state() & (1 << button) > 0
def reset_pressed_to_wake():
wakeup.reset_gpio_state()
def pressed_to_wake_get_once(button):
global WAKEUP_MASK
result = (wakeup.get_gpio_state() & ~WAKEUP_MASK & (1 << button)) > 0
WAKEUP_MASK |= (1 << button)
return result
def system_speed(speed):
try:
machine.freq(SYSTEM_FREQS[speed])
except IndexError:
pass
class Badger2040W():
def __init__(self):
self.display = PicoGraphics(DISPLAY_INKY_PACK)
self._led = machine.PWM(machine.Pin(LED))
self._led.freq(1000)
self._led.duty_u16(0)
self._update_speed = 0
def __getattr__(self, item):
# Glue to redirect calls to PicoGraphics
return getattr(self.display, item)
def update(self):
t_start = time.ticks_ms()
self.display.update()
t_elapsed = time.ticks_ms() - t_start
delay_ms = [4700, 2600, 900, 250][self._update_speed]
if t_elapsed < delay_ms:
time.sleep((delay_ms - t_elapsed) / 1000)
def set_update_speed(self, speed):
self.display.set_update_speed(speed)
self._update_speed = speed
def led(self, brightness):
brightness = max(0, min(255, brightness))
self._led.duty_u16(int(brightness * 256))
def invert(self, invert):
raise RuntimeError("Display invert not supported in PicoGraphics.")
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
def pressed(self, button):
return BUTTONS[button].value() == 1 or pressed_to_wake_get_once(button)
def pressed_any(self):
for button in BUTTONS.values():
if button.value():
return True
return False
@micropython.native
def icon(self, data, index, data_w, icon_size, x, y):
s_x = (index * icon_size) % data_w
s_y = int((index * icon_size) / data_w)
for o_y in range(icon_size):
for o_x in range(icon_size):
o = ((o_y + s_y) * data_w) + (o_x + s_x)
bm = 0b10000000 >> (o & 0b111)
if data[o >> 3] & bm:
self.display.pixel(x + o_x, y + o_y)
def image(self, data, w, h, x, y):
for oy in range(h):
row = data[oy]
for ox in range(w):
if row & 0b1 == 0:
self.display.pixel(x + ox, y + oy)
row >>= 1
def status_handler(self, mode, status, ip):
print(mode, status, ip)
self.display.set_pen(15)
self.display.clear()
self.display.set_pen(0)
if status:
self.display.text("Connected!", 10, 10, 300, 0.5)
self.display.text(ip, 10, 30, 300, 0.5)
else:
self.display.text("Connecting...", 10, 10, 300, 0.5)
self.update()
def isconnected(self):
return network.WLAN(network.STA_IF).isconnected()
def ip_address(self):
return network.WLAN(network.STA_IF).ifconfig()[0]
def connect(self):
if WIFI_CONFIG.COUNTRY == "":
raise RuntimeError("You must populate WIFI_CONFIG.py for networking.")
self.display.set_update_speed(2)
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=self.status_handler)
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
gc.collect()

View File

@ -0,0 +1,188 @@
"""Keep track of app state in persistent flash storage."""
import os
import gc
import time
import json
import machine
import badger2040w as badger2040
def get_battery_level():
return 0
# Battery measurement
vbat_adc = machine.ADC(badger2040.PIN_BATTERY)
vref_adc = machine.ADC(badger2040.PIN_1V2_REF)
vref_en = machine.Pin(badger2040.PIN_VREF_POWER)
vref_en.init(machine.Pin.OUT)
vref_en.value(0)
# Enable the onboard voltage reference
vref_en.value(1)
# Calculate the logic supply voltage, as will be lower that the usual 3.3V when running off low batteries
vdd = 1.24 * (65535 / vref_adc.read_u16())
vbat = (
(vbat_adc.read_u16() / 65535) * 3 * vdd
) # 3 in this is a gain, not rounding of 3.3V
# Disable the onboard voltage reference
vref_en.value(0)
# Convert the voltage to a level to display onscreen
return vbat
def get_disk_usage():
# f_bfree and f_bavail should be the same?
# f_files, f_ffree, f_favail and f_flag are unsupported.
f_bsize, f_frsize, f_blocks, f_bfree, _, _, _, _, _, f_namemax = os.statvfs("/")
f_total_size = f_frsize * f_blocks
f_total_free = f_bsize * f_bfree
f_total_used = f_total_size - f_total_free
f_used = 100 / f_total_size * f_total_used
f_free = 100 / f_total_size * f_total_free
return f_total_size, f_used, f_free
def state_running():
state = {"running": "launcher"}
state_load("launcher", state)
return state["running"]
def state_clear_running():
running = state_running()
state_modify("launcher", {"running": "launcher"})
return running != "launcher"
def state_set_running(app):
state_modify("launcher", {"running": app})
def state_launch():
app = state_running()
if app is not None and app != "launcher":
launch(app)
def state_delete(app):
try:
os.remove("/state/{}.json".format(app))
except OSError:
pass
def state_save(app, data):
try:
with open("/state/{}.json".format(app), "w") as f:
f.write(json.dumps(data))
f.flush()
except OSError:
import os
try:
os.stat("/state")
except OSError:
os.mkdir("/state")
state_save(app, data)
def state_modify(app, data):
state = {}
state_load(app, state)
state.update(data)
state_save(app, state)
def state_load(app, defaults):
try:
data = json.loads(open("/state/{}.json".format(app), "r").read())
if type(data) is dict:
defaults.update(data)
return True
except (OSError, ValueError):
pass
state_save(app, defaults)
return False
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
# Draw an overlay box with a given message within it
def warning(display, message, width=badger2040.WIDTH - 20, height=badger2040.HEIGHT - 20, line_spacing=20, text_size=0.6):
print(message)
if display is None:
display = badger2040.Badger2040W()
display.led(128)
# Draw a light grey background
display.set_pen(12)
display.rectangle((badger2040.WIDTH - width) // 2, (badger2040.HEIGHT - height) // 2, width, height)
width -= 20
height -= 20
display.set_pen(15)
display.rectangle((badger2040.WIDTH - width) // 2, (badger2040.HEIGHT - height) // 2, width, height)
# Take the provided message and split it up into
# lines that fit within the specified width
words = message.split(" ")
lines = []
current_line = ""
for word in words:
if display.measure_text(current_line + word + " ", text_size) < width:
current_line += word + " "
else:
lines.append(current_line.strip())
current_line = word + " "
lines.append(current_line.strip())
display.set_pen(0)
# Display each line of text from the message, centre-aligned
num_lines = len(lines)
for i in range(num_lines):
length = display.measure_text(lines[i], text_size)
current_line = (i * line_spacing) - ((num_lines - 1) * line_spacing) // 2
display.text(lines[i], (badger2040.WIDTH - length) // 2, (badger2040.HEIGHT // 2) + current_line, badger2040.WIDTH, text_size)
display.update()

View File

@ -0,0 +1,108 @@
import rp2
import network
import machine
import uasyncio
class NetworkManager:
_ifname = ("Client", "Access Point")
def __init__(self, country="GB", client_timeout=30, 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)
self._mode = network.STA_IF
self._client_timeout = client_timeout
self._access_point_timeout = access_point_timeout
self._status_handler = status_handler
self._error_handler = error_handler
self.UID = ("{:02X}" * 8).format(*machine.unique_id())
def isconnected(self):
return self._sta_if.isconnected() or self._ap_if.isconnected()
def config(self, var):
if self._sta_if.active():
return self._sta_if.config(var)
else:
if var == "password":
return self.UID
return self._ap_if.config(var)
def mode(self):
if self._sta_if.isconnected():
return self._ifname[0]
if self._ap_if.isconnected():
return self._ifname[1]
return None
def ifaddress(self):
if self._sta_if.isconnected():
return self._sta_if.ifconfig()[0]
if self._ap_if.isconnected():
return self._ap_if.ifconfig()[0]
return '0.0.0.0'
def disconnect(self):
if self._sta_if.isconnected():
self._sta_if.disconnect()
if self._ap_if.isconnected():
self._ap_if.disconnect()
async def wait(self, mode):
while not self.isconnected():
self._handle_status(mode, None)
await uasyncio.sleep_ms(1000)
def _handle_status(self, mode, status):
if callable(self._status_handler):
self._status_handler(self._ifname[mode], status, self.ifaddress())
def _handle_error(self, mode, msg):
if callable(self._error_handler):
if self._error_handler(self._ifname[mode], msg):
return
raise RuntimeError(msg)
async def client(self, ssid, psk):
if self._sta_if.isconnected():
self._handle_status(network.STA_IF, True)
return
self._ap_if.disconnect()
self._ap_if.active(False)
self._sta_if.active(True)
self._sta_if.connect(ssid, psk)
self._sta_if.config(pm=0xa11140)
try:
await uasyncio.wait_for(self.wait(network.STA_IF), self._client_timeout)
self._handle_status(network.STA_IF, True)
except uasyncio.TimeoutError:
self._sta_if.active(False)
self._handle_status(network.STA_IF, False)
self._handle_error(network.STA_IF, "WIFI Client Failed")
async def access_point(self):
if self._ap_if.isconnected():
self._handle_status(network.AP_IF, True)
return
self._sta_if.disconnect()
self._sta_if.active(False)
self._ap_if.ifconfig(("10.10.1.1", "255.255.255.0", "10.10.1.1", "10.10.1.1"))
self._ap_if.config(password=self.UID)
self._ap_if.active(True)
try:
await uasyncio.wait_for(self.wait(network.AP_IF), self._access_point_timeout)
self._handle_status(network.AP_IF, True)
except uasyncio.TimeoutError:
self._sta_if.active(False)
self._handle_status(network.AP_IF, False)
self._handle_error(network.AP_IF, "WIFI Client Failed")

View File

@ -0,0 +1 @@
import launcher # noqa F401

View File

@ -0,0 +1,10 @@
*.py
lib/*.py
examples/*.jpg
examples/*.py
images/*.jpg
images/*.txt
badges/*.txt
badges/*.jpg
books/*.txt
icons/*.jpg

View File

@ -67,7 +67,7 @@ mp_obj_t Badger2040___del__(mp_obj_t self_in) {
_Badger2040_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Badger2040_obj_t);
// Try to ensure power is cut off when soft reset (IE: "Stop" in Thonny)
self->badger2040->power_off();
delete self->badger2040;
//delete self->badger2040;
return mp_const_none;
}
@ -101,7 +101,7 @@ mp_obj_t Badger2040_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_
badger2040_obj = m_new_obj_with_finaliser(_Badger2040_obj_t);
badger2040_obj->base.type = &Badger2040_type;
badger2040_obj->buf = buffer;
badger2040_obj->badger2040 = new pimoroni::Badger2040(buffer);
badger2040_obj->badger2040 = m_new_class(pimoroni::Badger2040, buffer);
badger2040_obj->badger2040->init();
return MP_OBJ_FROM_PTR(badger2040_obj);

View File

@ -0,0 +1,260 @@
# Badger 2040 W <!-- omit in toc -->
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 switches from the Badger-specific drawing library of Badger 2040, to our generic PicoGraphics library.
PicoGraphics brings some great improvements, such as JPEG support with dithering and cross-compatibility between all of our other display products.
We've tried to make the transition as simple as possible, but there are a few breaking changes you'll need to be aware of:
* `pen()` is now `set_pen()`
* `update_speed()` is now `set_update_speed()`
* `thickness()` is now `set_thickness()` and *only* applies to Hershey fonts
* `image()` and `icon()` are deprecated, use JPEGs instead.
* `invert()` is not supported.
See the [PicoGraphics function reference](../picographics/README.md) for more information on how to draw to the display.
Additionally 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
:warning: If you're using the examples-included firmware you're good to go, otherwise you'll need to copy `examples/badger2040w/lib/badger2040w.py` and `examples/badger2040w/lib/network_manager.py` over to your Badger 2040 W.
To start coding your Badger 2040 W, you will need to add the following lines of code to the start of your code file.
```python
import badger2040w
badger = badger2040w.Badger2040W()
```
This will create a `Badger2040W` 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 W is based upon PicoGraphics you should read the [PicoGraphics function reference](../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 W 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
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 W 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 badger2040w
import jpegdec
badger = badger2040w.Badger2040W()
jpeg = jpegdec.JPEG(badger.display)
```
`badger.display` points to the PicoGraphics instance that the Badger2040W 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 W 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
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 W features 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.
## Waking From Sleep
### Button Presses
When running on battery, pressing a button on Badger 2040 W 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 W has been woken by a button, and figure out which one:
* `badger2040w.woken_by_button()` - determine if any button was pressed during power-on.
* `badger2040w.pressed_to_wake(button)` - determine if the given button was pressed during power-on.
* `badger2040w.reset_pressed_to_wake()` - clear the wakeup GPIO state.
* `badger2040w.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 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.
## Update Speed
The E Ink display on Badger 2040 W 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 `badger2040w.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 `badger2040w` 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.

View File

@ -95,9 +95,10 @@ MICROPY_EVENT_POLL_HOOK
uint8_t *pixel = (uint8_t *)pDraw->pPixels;
for(int y = 0; y < pDraw->iHeight; y++) {
for(int x = 0; x < pDraw->iWidth; x++) {
if(x >= pDraw->iWidthUsed) break; // Clip to the used width
current_graphics->set_pen((uint8_t)(*pixel >> 4));
current_graphics->set_pixel({pDraw->x + x, pDraw->y + y});
if(x < pDraw->iWidthUsed) { // Clip to the used width
current_graphics->set_pen((uint8_t)(*pixel >> 4));
current_graphics->pixel({pDraw->x + x, pDraw->y + y});
}
pixel++;
}
}

View File

@ -0,0 +1,59 @@
include_directories(${CMAKE_CURRENT_LIST_DIR}/../../)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../")
# Essential
include(pimoroni_i2c/micropython)
include(pimoroni_bus/micropython)
# Pico Graphics Essential
include(hershey_fonts/micropython)
include(bitmap_fonts/micropython)
include(picographics/micropython)
# Pico Graphics Extra
include(jpegdec/micropython)
include(qrcode/micropython/micropython)
# Sensors & Breakouts
include(breakout_dotmatrix/micropython)
include(breakout_encoder/micropython)
include(breakout_ioexpander/micropython)
include(breakout_ltr559/micropython)
include(breakout_as7262/micropython)
include(breakout_rgbmatrix5x5/micropython)
include(breakout_matrix11x7/micropython)
include(breakout_msa301/micropython)
include(breakout_pmw3901/micropython)
include(breakout_mics6814/micropython)
include(breakout_potentiometer/micropython)
include(breakout_rtc/micropython)
include(breakout_trackball/micropython)
include(breakout_sgp30/micropython)
include(breakout_bh1745/micropython)
include(breakout_bme68x/micropython)
include(breakout_bme280/micropython)
include(breakout_bmp280/micropython)
include(breakout_icp10125/micropython)
include(breakout_scd41/micropython)
include(breakout_vl53l5cx/micropython)
include(pcf85063a/micropython)
# Utility
include(adcfft/micropython)
include(wakeup/micropython)
# LEDs & Matrices
include(plasma/micropython)
# Servos & Motors
include(pwm/micropython)
include(servo/micropython)
include(encoder/micropython)
include(motor/micropython)
# include(micropython-common)
include(modules_py/modules_py)

View File

@ -22,6 +22,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_set_palette_obj, 2, ModPicoGraphics_s
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_pen_obj, ModPicoGraphics_set_pen);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_create_pen_obj, 4, 4, ModPicoGraphics_create_pen);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_create_pen_hsv_obj, 4, 4, ModPicoGraphics_create_pen_hsv);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_thickness_obj, ModPicoGraphics_set_thickness);
// Primitives
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_set_clip_obj, 5, 5, ModPicoGraphics_set_clip);
@ -56,6 +57,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics__del__obj, ModPicoGraphics__del__);
STATIC const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_pixel), MP_ROM_PTR(&ModPicoGraphics_pixel_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_pen), MP_ROM_PTR(&ModPicoGraphics_set_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_thickness), MP_ROM_PTR(&ModPicoGraphics_set_thickness_obj) },
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&ModPicoGraphics_clear_obj) },
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&ModPicoGraphics_update_obj) },

View File

@ -711,6 +711,18 @@ mp_obj_t ModPicoGraphics_create_pen_hsv(size_t n_args, const mp_obj_t *args) {
return mp_obj_new_int(result);
}
mp_obj_t ModPicoGraphics_set_thickness(mp_obj_t self_in, mp_obj_t pen) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
if(self->graphics->pen_type != PicoGraphics::PEN_1BIT) {
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Thickness not supported!"));
}
self->graphics->set_thickness(mp_obj_get_int(pen));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_set_palette(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
size_t num_tuples = n_args - 1;
const mp_obj_t *tuples = pos_args + 1;

View File

@ -72,6 +72,7 @@ extern mp_obj_t ModPicoGraphics_hsv_to_rgb(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_set_pen(mp_obj_t self_in, mp_obj_t pen);
extern mp_obj_t ModPicoGraphics_create_pen(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_create_pen_hsv(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_set_thickness(mp_obj_t self_in, mp_obj_t thickness);
// Primitives
extern mp_obj_t ModPicoGraphics_set_clip(size_t n_args, const mp_obj_t *args);

View File

@ -1,12 +1,14 @@
#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 MP_DEFINE_CONST_FUN_OBJ_0(Wakeup_get_shift_state_obj, Wakeup_get_shift_state);
STATIC MP_DEFINE_CONST_FUN_OBJ_0(Wakeup_reset_shift_state_obj, Wakeup_reset_shift_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) },
{ MP_ROM_QSTR(MP_QSTR_get_shift_state), MP_ROM_PTR(&Wakeup_get_shift_state_obj) },
{ MP_ROM_QSTR(MP_QSTR_reset_shift_state), MP_ROM_PTR(&Wakeup_reset_shift_state_obj) }
};

View File

@ -62,6 +62,11 @@ mp_obj_t Wakeup_get_gpio_state() {
return mp_obj_new_int(runtime_wakeup_gpio_state);
}
mp_obj_t Wakeup_reset_gpio_state() {
runtime_wakeup_gpio_state = 0;
return mp_const_none;
}
void err_no_sr() {
mp_raise_msg(&mp_type_RuntimeError, "Wakeup_get_shift_state: board does not have a shift register.");
}

View File

@ -2,5 +2,6 @@
#include "py/objstr.h"
extern mp_obj_t Wakeup_get_gpio_state();
extern mp_obj_t Wakeup_reset_gpio_state();
extern mp_obj_t Wakeup_get_shift_state();
extern mp_obj_t Wakeup_reset_shift_state();