Merge pull request #633 from pimoroni/feature/badger2040w
Badger2040w support
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
include("../manifest.py")
|
||||
|
||||
require("mip")
|
||||
require("ntptime")
|
||||
require("urequests")
|
||||
require("urllib.urequest")
|
||||
require("umqtt.simple")
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
SSID = ""
|
||||
PSK = ""
|
||||
COUNTRY = ""
|
After Width: | Height: | Size: 7.7 KiB |
|
@ -0,0 +1,7 @@
|
|||
mustelid inc
|
||||
H. Badger
|
||||
RP2040
|
||||
2MB Flash
|
||||
E ink
|
||||
296x128px
|
||||
/badges/badge.jpg
|
|
@ -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()
|
|
@ -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)
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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) + "¤t_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()
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,3 @@
|
|||
Images must be 296x128 pixel JPEGs
|
||||
|
||||
Create a new "images" directory via Thonny, and upload your .jpg files there.
|
After Width: | Height: | Size: 9.0 KiB |
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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")
|
|
@ -0,0 +1 @@
|
|||
import launcher # noqa F401
|
|
@ -0,0 +1,10 @@
|
|||
*.py
|
||||
lib/*.py
|
||||
examples/*.jpg
|
||||
examples/*.py
|
||||
images/*.jpg
|
||||
images/*.txt
|
||||
badges/*.txt
|
||||
badges/*.jpg
|
||||
books/*.txt
|
||||
icons/*.jpg
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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) },
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) }
|
||||
};
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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();
|