Make hardware configurable
Some checks failed
Build RPi Pico firmware image / Build-Firmware (push) Successful in 3m21s
Check code formatting / Check-C-Format (push) Successful in 7s
Check code formatting / Check-Python-Flake8 (push) Failing after 9s
Check code formatting / Check-Bash-Shellcheck (push) Successful in 4s
Run unit tests on host / Run-Unit-Tests (push) Successful in 8s
Run pytests / Check-Pytest (push) Successful in 10s
Some checks failed
Build RPi Pico firmware image / Build-Firmware (push) Successful in 3m21s
Check code formatting / Check-C-Format (push) Successful in 7s
Check code formatting / Check-Python-Flake8 (push) Failing after 9s
Check code formatting / Check-Bash-Shellcheck (push) Successful in 4s
Run unit tests on host / Run-Unit-Tests (push) Successful in 8s
Run pytests / Check-Pytest (push) Successful in 10s
Move hardware-specifics (pin assignments, power management) to hwconfig_*.py. The build system will build a firmware image firmware-filesystem-$variant.uf2 for all variants for which a hwconfig_$variant.py file exits. Inside the filesystem image, the selected variants hwconfig_$variant.py file will always be named hwconfig.py. At runtime, main.py will attempt to import hwconfig which will load the configuration for the correct variant. Currently, the hwconfig_* modules are expected to define the pin mapping and implement a board_init method. Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: Build RPi Pico firmware image
|
name: Build RPi Pico firmware image
|
||||||
on:
|
"on":
|
||||||
push:
|
push:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -22,4 +22,4 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: firmware-RPi-Pico-W-with-fs
|
name: firmware-RPi-Pico-W-with-fs
|
||||||
path: software/lib/micropython/ports/rp2/build-TONBERRY_RPI_PICO_W/firmware-filesystem.uf2
|
path: software/lib/micropython/ports/rp2/build-TONBERRY_RPI_PICO_W/firmware-filesystem-*.uf2
|
||||||
|
|||||||
@@ -27,12 +27,19 @@ fi
|
|||||||
BUILDDIR=lib/micropython/ports/rp2/build-TONBERRY_RPI_PICO_W/
|
BUILDDIR=lib/micropython/ports/rp2/build-TONBERRY_RPI_PICO_W/
|
||||||
FS_STAGE_DIR=$(mktemp -d)
|
FS_STAGE_DIR=$(mktemp -d)
|
||||||
trap 'rm -rf $FS_STAGE_DIR' EXIT
|
trap 'rm -rf $FS_STAGE_DIR' EXIT
|
||||||
find src/ -iname '*.py' | cpio -pdm "$FS_STAGE_DIR"
|
for hwconfig in src/hwconfig_*.py; do
|
||||||
tools/mklittlefs/mklittlefs -p 256 -s 868352 -c "$FS_STAGE_DIR"/src $BUILDDIR/filesystem.bin
|
hwconfig_base=$(basename "$hwconfig")
|
||||||
truncate -s 2M $BUILDDIR/firmware-filesystem.bin
|
hwname=${hwconfig_base##hwconfig_}
|
||||||
dd if=$BUILDDIR/firmware.bin of=$BUILDDIR/firmware-filesystem.bin bs=1k
|
hwname=${hwname%%.py}
|
||||||
dd if=$BUILDDIR/filesystem.bin of=$BUILDDIR/firmware-filesystem.bin bs=1k seek=1200
|
find src/ -iname '*.py' \! -iname 'hwconfig_*.py' | cpio -pdm "$FS_STAGE_DIR"
|
||||||
$PICOTOOL uf2 convert $BUILDDIR/firmware-filesystem.bin $BUILDDIR/firmware-filesystem.uf2
|
cp "$hwconfig" "$FS_STAGE_DIR"/src/hwconfig.py
|
||||||
|
tools/mklittlefs/mklittlefs -p 256 -s 868352 -c "$FS_STAGE_DIR"/src $BUILDDIR/filesystem.bin
|
||||||
|
truncate -s 2M $BUILDDIR/firmware-filesystem.bin
|
||||||
|
dd if=$BUILDDIR/firmware.bin of=$BUILDDIR/firmware-filesystem.bin bs=1k
|
||||||
|
dd if=$BUILDDIR/filesystem.bin of=$BUILDDIR/firmware-filesystem.bin bs=1k seek=1200
|
||||||
|
$PICOTOOL uf2 convert $BUILDDIR/firmware-filesystem.bin $BUILDDIR/firmware-filesystem-"$hwname".uf2
|
||||||
|
rm -r "${FS_STAGE_DIR:?}"/*
|
||||||
|
done
|
||||||
|
|
||||||
echo "Output in $BUILDDIR/firmware.uf2"
|
echo "Output in $BUILDDIR/firmware.uf2"
|
||||||
echo "Image with filesystem in $BUILDDIR/firmware-filesystem.uf2"
|
echo "Images with filesystem in" $BUILDDIR/firmware-filesystem-*.uf2
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
import _audiocore
|
import _audiocore
|
||||||
from asyncio import ThreadSafeFlag
|
from asyncio import ThreadSafeFlag
|
||||||
|
from utils import get_pin_index
|
||||||
|
|
||||||
|
|
||||||
class Audiocore:
|
class Audiocore:
|
||||||
def __init__(self, pin, sideset):
|
def __init__(self, din, dclk, lrclk):
|
||||||
|
assert get_pin_index(lrclk) == get_pin_index(dclk)+1 # TODO: Support different pin arrangements
|
||||||
self.notify = ThreadSafeFlag()
|
self.notify = ThreadSafeFlag()
|
||||||
self.pin = pin
|
self.pin = din
|
||||||
self.sideset = sideset
|
self.sideset = dclk
|
||||||
self._audiocore = _audiocore.Audiocore(self.pin, self.sideset, self._interrupt)
|
self._audiocore = _audiocore.Audiocore(self.pin, self.sideset, self._interrupt)
|
||||||
|
|
||||||
def deinit(self):
|
def deinit(self):
|
||||||
@@ -40,12 +45,13 @@ class Audiocore:
|
|||||||
|
|
||||||
|
|
||||||
class AudioContext:
|
class AudioContext:
|
||||||
def __init__(self, pin, sideset):
|
def __init__(self, din, dclk, lrclk):
|
||||||
self.pin = pin
|
self.din = din
|
||||||
self.sideset = sideset
|
self.dclk = dclk
|
||||||
|
self.lrclk = lrclk
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self._audiocore = Audiocore(self.pin, self.sideset)
|
self._audiocore = Audiocore(self.din, self.dclk, self.lrclk)
|
||||||
return self._audiocore
|
return self._audiocore
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
|||||||
63
software/src/hwconfig_Rev1.py
Normal file
63
software/src/hwconfig_Rev1.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
|
import machine
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
# SD Card SPI
|
||||||
|
SD_DI = Pin.board.GP3
|
||||||
|
SD_DO = Pin.board.GP4
|
||||||
|
SD_SCK = Pin.board.GP2
|
||||||
|
SD_CS = Pin.board.GP5
|
||||||
|
|
||||||
|
# MAX98357
|
||||||
|
I2S_LRCLK = Pin.board.GP6
|
||||||
|
I2S_DCLK = Pin.board.GP7
|
||||||
|
I2S_DIN = Pin.board.GP8
|
||||||
|
I2S_SD = Pin.board.GP9
|
||||||
|
|
||||||
|
# RC522
|
||||||
|
RC522_SPIID = 1
|
||||||
|
RC522_RST = Pin.board.GP14
|
||||||
|
RC522_IRQ = Pin.board.GP15
|
||||||
|
RC522_MOSI = Pin.board.GP11
|
||||||
|
RC522_MISO = Pin.board.GP12
|
||||||
|
RC522_SCK = Pin.board.GP10
|
||||||
|
RC522_SS = Pin.board.GP13
|
||||||
|
|
||||||
|
# WS2812
|
||||||
|
LED_DIN = Pin.board.GP16
|
||||||
|
LED_COUNT = 1
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
BUTTON_VOLUP = Pin.board.GP17
|
||||||
|
BUTTON_VOLDOWN = Pin.board.GP19
|
||||||
|
BUTTON_NEXT = Pin.board.GP18
|
||||||
|
BUTTON_POWER = Pin.board.GP21
|
||||||
|
|
||||||
|
# Power
|
||||||
|
POWER_EN = Pin.board.GP22
|
||||||
|
VBAT_ADC = Pin.board.GP26
|
||||||
|
|
||||||
|
|
||||||
|
def board_init():
|
||||||
|
# Keep power turned on
|
||||||
|
# TODO: Implement soft power off
|
||||||
|
POWER_EN.init(mode=Pin.OUTPUT)
|
||||||
|
POWER_EN.value(1)
|
||||||
|
|
||||||
|
# Set 8 mA drive strength and fast slew rate for SD SPI
|
||||||
|
machine.mem32[0x4001c004 + 6*4] = 0x67
|
||||||
|
machine.mem32[0x4001c004 + 7*4] = 0x67
|
||||||
|
machine.mem32[0x4001c004 + 8*4] = 0x67
|
||||||
|
|
||||||
|
# Permanently enable amplifier
|
||||||
|
# TODO: Implement amplifier power management
|
||||||
|
I2S_SD.init(mode=Pin.OPEN_DRAIN)
|
||||||
|
I2S_SD.value(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_battery_voltage():
|
||||||
|
adc = machine.ADC(VBAT_ADC) # create ADC object on ADC pin
|
||||||
|
battv = adc.read_u16()/65535.0*3.3*2
|
||||||
|
return battv
|
||||||
51
software/src/hwconfig_breadboard.py
Normal file
51
software/src/hwconfig_breadboard.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
|
import machine
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
# SD Card SPI
|
||||||
|
SD_DI = Pin.board.GP3
|
||||||
|
SD_DO = Pin.board.GP4
|
||||||
|
SD_SCK = Pin.board.GP2
|
||||||
|
SD_CS = Pin.board.GP5
|
||||||
|
|
||||||
|
# MAX98357
|
||||||
|
I2S_LRCLK = Pin.board.GP7
|
||||||
|
I2S_DCLK = Pin.board.GP6
|
||||||
|
I2S_DIN = Pin.board.GP8
|
||||||
|
I2S_SD = None
|
||||||
|
|
||||||
|
# RC522
|
||||||
|
RC522_SPIID = 1
|
||||||
|
RC522_RST = Pin.board.GP9
|
||||||
|
RC522_IRQ = Pin.board.GP14
|
||||||
|
RC522_MOSI = Pin.board.GP11
|
||||||
|
RC522_MISO = Pin.board.GP12
|
||||||
|
RC522_SCK = Pin.board.GP10
|
||||||
|
RC522_SS = Pin.board.GP13
|
||||||
|
|
||||||
|
# WS2812
|
||||||
|
LED_DIN = Pin.board.GP16
|
||||||
|
LED_COUNT = 1
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
BUTTON_VOLUP = Pin.board.GP17
|
||||||
|
BUTTON_VOLDOWN = Pin.board.GP19
|
||||||
|
BUTTON_NEXT = Pin.board.GP18
|
||||||
|
BUTTON_POWER = None
|
||||||
|
|
||||||
|
# Power
|
||||||
|
POWER_EN = None
|
||||||
|
VBAT_ADC = Pin.board.GP26
|
||||||
|
|
||||||
|
|
||||||
|
def board_init():
|
||||||
|
# Set 8 mA drive strength and fast slew rate for SD SPI
|
||||||
|
machine.mem32[0x4001c004 + 6*4] = 0x67
|
||||||
|
machine.mem32[0x4001c004 + 7*4] = 0x67
|
||||||
|
machine.mem32[0x4001c004 + 8*4] = 0x67
|
||||||
|
|
||||||
|
def get_battery_voltage():
|
||||||
|
# Not supported on breadboard
|
||||||
|
return None
|
||||||
@@ -18,14 +18,23 @@ from nfc import Nfc
|
|||||||
from rp2_neopixel import NeoPixel
|
from rp2_neopixel import NeoPixel
|
||||||
from utils import Buttons, SDContext, TimerManager
|
from utils import Buttons, SDContext, TimerManager
|
||||||
|
|
||||||
|
try:
|
||||||
|
import hwconfig
|
||||||
|
except ImportError:
|
||||||
|
print("Fatal: No hwconfig.py found")
|
||||||
|
raise
|
||||||
|
|
||||||
micropython.alloc_emergency_exception_buf(100)
|
micropython.alloc_emergency_exception_buf(100)
|
||||||
|
|
||||||
|
# Machine setup
|
||||||
|
hwconfig.board_init()
|
||||||
|
|
||||||
|
|
||||||
async def rainbow(np, period=10):
|
async def rainbow(np, period=10):
|
||||||
def gamma(value, X=2.2):
|
def gamma(value, X=2.2):
|
||||||
return min(max(int(brightness * pow(value / 255.0, X) * 255.0 + 0.5), 0), 255)
|
return min(max(int(brightness * pow(value / 255.0, X) * 255.0 + 0.5), 0), 255)
|
||||||
|
|
||||||
brightness = 0.5
|
brightness = 0.05
|
||||||
count = 0.0
|
count = 0.0
|
||||||
leds = len(np)
|
leds = len(np)
|
||||||
while True:
|
while True:
|
||||||
@@ -34,7 +43,7 @@ async def rainbow(np, period=10):
|
|||||||
np[i] = (gamma((sin(ofs / leds * 2 * pi) + 1) * 127),
|
np[i] = (gamma((sin(ofs / leds * 2 * pi) + 1) * 127),
|
||||||
gamma((sin(ofs / leds * 2 * pi + 2/3*pi) + 1) * 127),
|
gamma((sin(ofs / leds * 2 * pi + 2/3*pi) + 1) * 127),
|
||||||
gamma((sin(ofs / leds * 2 * pi + 4/3*pi) + 1) * 127))
|
gamma((sin(ofs / leds * 2 * pi + 4/3*pi) + 1) * 127))
|
||||||
count += 0.2
|
count += 0.02 * leds
|
||||||
before = time.ticks_ms()
|
before = time.ticks_ms()
|
||||||
await np.async_write()
|
await np.async_write()
|
||||||
now = time.ticks_ms()
|
now = time.ticks_ms()
|
||||||
@@ -42,12 +51,6 @@ async def rainbow(np, period=10):
|
|||||||
await asyncio.sleep_ms(20 - (now - before))
|
await asyncio.sleep_ms(20 - (now - before))
|
||||||
|
|
||||||
|
|
||||||
# Machine setup
|
|
||||||
|
|
||||||
# Set 8 mA drive strength and fast slew rate
|
|
||||||
machine.mem32[0x4001c004 + 6*4] = 0x67
|
|
||||||
machine.mem32[0x4001c004 + 7*4] = 0x67
|
|
||||||
machine.mem32[0x4001c004 + 8*4] = 0x67
|
|
||||||
# high prio for proc 1
|
# high prio for proc 1
|
||||||
machine.mem32[0x40030000 + 0x00] = 0x10
|
machine.mem32[0x40030000 + 0x00] = 0x10
|
||||||
|
|
||||||
@@ -55,21 +58,24 @@ machine.mem32[0x40030000 + 0x00] = 0x10
|
|||||||
def run():
|
def run():
|
||||||
asyncio.new_event_loop()
|
asyncio.new_event_loop()
|
||||||
# Setup LEDs
|
# Setup LEDs
|
||||||
pin = Pin.board.GP16
|
np = NeoPixel(hwconfig.LED_DIN, hwconfig.LED_COUNT, sm=1)
|
||||||
np = NeoPixel(pin, 10, sm=1)
|
|
||||||
asyncio.create_task(rainbow(np))
|
asyncio.create_task(rainbow(np))
|
||||||
|
|
||||||
# Setup MP3 player
|
# Setup MP3 player
|
||||||
with SDContext(mosi=Pin(3), miso=Pin(4), sck=Pin(2), ss=Pin(5), baudrate=15000000), \
|
with SDContext(mosi=hwconfig.SD_DI, miso=hwconfig.SD_DO, sck=hwconfig.SD_SCK, ss=hwconfig.SD_CS,
|
||||||
AudioContext(Pin(8), Pin(6)) as audioctx:
|
baudrate=15000000), \
|
||||||
|
AudioContext(hwconfig.I2S_DIN, hwconfig.I2S_DCLK, hwconfig.I2S_LRCLK) as audioctx:
|
||||||
|
|
||||||
# Setup NFC
|
# Setup NFC
|
||||||
reader = MFRC522(spi_id=1, sck=10, miso=12, mosi=11, cs=13, rst=9, tocard_retries=20)
|
reader = MFRC522(spi_id=hwconfig.RC522_SPIID, sck=hwconfig.RC522_SCK, miso=hwconfig.RC522_MISO,
|
||||||
|
mosi=hwconfig.RC522_MOSI, cs=hwconfig.RC522_SS, rst=hwconfig.RC522_RST, tocard_retries=20)
|
||||||
|
|
||||||
# Setup app
|
# Setup app
|
||||||
deps = app.Dependencies(mp3player=lambda the_app: MP3Player(audioctx, the_app),
|
deps = app.Dependencies(mp3player=lambda the_app: MP3Player(audioctx, the_app),
|
||||||
nfcreader=lambda the_app: Nfc(reader, the_app),
|
nfcreader=lambda the_app: Nfc(reader, the_app),
|
||||||
buttons=lambda the_app: Buttons(the_app))
|
buttons=lambda the_app: Buttons(the_app, pin_volup=hwconfig.BUTTON_VOLUP,
|
||||||
|
pin_voldown=hwconfig.BUTTON_VOLDOWN,
|
||||||
|
pin_next=hwconfig.BUTTON_NEXT))
|
||||||
the_app = app.PlayerApp(deps)
|
the_app = app.PlayerApp(deps)
|
||||||
|
|
||||||
# Start
|
# Start
|
||||||
@@ -80,5 +86,5 @@ def run():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_UP).value() != 0:
|
if machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_UP).value() != 0:
|
||||||
time.sleep(5)
|
time.sleep(1)
|
||||||
run()
|
run()
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
from utils.buttons import Buttons
|
from utils.buttons import Buttons
|
||||||
from utils.mbrpartition import MBRPartition
|
from utils.mbrpartition import MBRPartition
|
||||||
|
from utils.pinindex import get_pin_index
|
||||||
from utils.sdcontext import SDContext
|
from utils.sdcontext import SDContext
|
||||||
from utils.timer import TimerManager
|
from utils.timer import TimerManager
|
||||||
|
|
||||||
__all__ = ["Buttons", "MBRPartition", "SDContext", "TimerManager"]
|
__all__ = ["Buttons", "get_pin_index", "MBRPartition", "SDContext", "TimerManager"]
|
||||||
|
|||||||
43
software/src/utils/pinindex.py
Normal file
43
software/src/utils/pinindex.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
pins = [Pin.board.GP0,
|
||||||
|
Pin.board.GP1,
|
||||||
|
Pin.board.GP2,
|
||||||
|
Pin.board.GP3,
|
||||||
|
Pin.board.GP4,
|
||||||
|
Pin.board.GP5,
|
||||||
|
Pin.board.GP6,
|
||||||
|
Pin.board.GP7,
|
||||||
|
Pin.board.GP8,
|
||||||
|
Pin.board.GP9,
|
||||||
|
Pin.board.GP10,
|
||||||
|
Pin.board.GP11,
|
||||||
|
Pin.board.GP12,
|
||||||
|
Pin.board.GP13,
|
||||||
|
Pin.board.GP14,
|
||||||
|
Pin.board.GP15,
|
||||||
|
Pin.board.GP16,
|
||||||
|
Pin.board.GP17,
|
||||||
|
Pin.board.GP18,
|
||||||
|
Pin.board.GP19,
|
||||||
|
Pin.board.GP20,
|
||||||
|
Pin.board.GP21,
|
||||||
|
Pin.board.GP22,
|
||||||
|
None, # 23
|
||||||
|
None, # 24
|
||||||
|
None, # 25
|
||||||
|
Pin.board.GP26,
|
||||||
|
Pin.board.GP27,
|
||||||
|
Pin.board.GP28,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_pin_index(pin: Pin) -> int:
|
||||||
|
"""
|
||||||
|
Get the pin index back from a pin object.
|
||||||
|
Unfortunately, micropython has no built-in function for this.
|
||||||
|
"""
|
||||||
|
return pins.index(pin)
|
||||||
@@ -1,2 +1,40 @@
|
|||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
|
class Pin:
|
||||||
|
def __init__(self, idx):
|
||||||
|
self.idx = idx
|
||||||
|
|
||||||
|
board = None
|
||||||
|
|
||||||
|
|
||||||
|
class Board:
|
||||||
|
GP0 = Pin(0)
|
||||||
|
GP1 = Pin(1)
|
||||||
|
GP2 = Pin(2)
|
||||||
|
GP3 = Pin(3)
|
||||||
|
GP4 = Pin(4)
|
||||||
|
GP5 = Pin(5)
|
||||||
|
GP6 = Pin(6)
|
||||||
|
GP7 = Pin(7)
|
||||||
|
GP8 = Pin(8)
|
||||||
|
GP9 = Pin(9)
|
||||||
|
GP10 = Pin(10)
|
||||||
|
GP11 = Pin(11)
|
||||||
|
GP12 = Pin(12)
|
||||||
|
GP13 = Pin(13)
|
||||||
|
GP14 = Pin(14)
|
||||||
|
GP15 = Pin(15)
|
||||||
|
GP16 = Pin(16)
|
||||||
|
GP17 = Pin(17)
|
||||||
|
GP18 = Pin(18)
|
||||||
|
GP19 = Pin(19)
|
||||||
|
GP20 = Pin(20)
|
||||||
|
GP21 = Pin(21)
|
||||||
|
GP22 = Pin(22)
|
||||||
|
GP26 = Pin(26)
|
||||||
|
GP27 = Pin(27)
|
||||||
|
GP28 = Pin(28)
|
||||||
|
|
||||||
|
|
||||||
|
Pin.board = Board
|
||||||
|
|||||||
Reference in New Issue
Block a user