Make hardware configurable
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
|
||||
on:
|
||||
"on":
|
||||
push:
|
||||
|
||||
jobs:
|
||||
@@ -22,4 +22,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
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/
|
||||
FS_STAGE_DIR=$(mktemp -d)
|
||||
trap 'rm -rf $FS_STAGE_DIR' EXIT
|
||||
find src/ -iname '*.py' | cpio -pdm "$FS_STAGE_DIR"
|
||||
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.uf2
|
||||
for hwconfig in src/hwconfig_*.py; do
|
||||
hwconfig_base=$(basename "$hwconfig")
|
||||
hwname=${hwconfig_base##hwconfig_}
|
||||
hwname=${hwname%%.py}
|
||||
find src/ -iname '*.py' \! -iname 'hwconfig_*.py' | cpio -pdm "$FS_STAGE_DIR"
|
||||
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 "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
|
||||
from asyncio import ThreadSafeFlag
|
||||
from utils import get_pin_index
|
||||
|
||||
|
||||
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.pin = pin
|
||||
self.sideset = sideset
|
||||
self.pin = din
|
||||
self.sideset = dclk
|
||||
self._audiocore = _audiocore.Audiocore(self.pin, self.sideset, self._interrupt)
|
||||
|
||||
def deinit(self):
|
||||
@@ -40,12 +45,13 @@ class Audiocore:
|
||||
|
||||
|
||||
class AudioContext:
|
||||
def __init__(self, pin, sideset):
|
||||
self.pin = pin
|
||||
self.sideset = sideset
|
||||
def __init__(self, din, dclk, lrclk):
|
||||
self.din = din
|
||||
self.dclk = dclk
|
||||
self.lrclk = lrclk
|
||||
|
||||
def __enter__(self):
|
||||
self._audiocore = Audiocore(self.pin, self.sideset)
|
||||
self._audiocore = Audiocore(self.din, self.dclk, self.lrclk)
|
||||
return self._audiocore
|
||||
|
||||
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.OUT)
|
||||
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
|
||||
52
software/src/hwconfig_breadboard.py
Normal file
52
software/src/hwconfig_breadboard.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# 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
|
||||
@@ -6,7 +6,6 @@ import asyncio
|
||||
import machine
|
||||
import micropython
|
||||
import time
|
||||
from machine import Pin
|
||||
from math import pi, sin, pow
|
||||
|
||||
# Own modules
|
||||
@@ -18,14 +17,23 @@ from nfc import Nfc
|
||||
from rp2_neopixel import NeoPixel
|
||||
from utils import Buttons, SDContext, TimerManager
|
||||
|
||||
try:
|
||||
import hwconfig
|
||||
except ImportError:
|
||||
print("Fatal: No hwconfig.py found")
|
||||
raise
|
||||
|
||||
micropython.alloc_emergency_exception_buf(100)
|
||||
|
||||
# Machine setup
|
||||
hwconfig.board_init()
|
||||
|
||||
|
||||
async def rainbow(np, period=10):
|
||||
def gamma(value, X=2.2):
|
||||
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
|
||||
leds = len(np)
|
||||
while True:
|
||||
@@ -34,7 +42,7 @@ async def rainbow(np, period=10):
|
||||
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 + 4/3*pi) + 1) * 127))
|
||||
count += 0.2
|
||||
count += 0.02 * leds
|
||||
before = time.ticks_ms()
|
||||
await np.async_write()
|
||||
now = time.ticks_ms()
|
||||
@@ -42,12 +50,6 @@ async def rainbow(np, period=10):
|
||||
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
|
||||
machine.mem32[0x40030000 + 0x00] = 0x10
|
||||
|
||||
@@ -55,21 +57,24 @@ machine.mem32[0x40030000 + 0x00] = 0x10
|
||||
def run():
|
||||
asyncio.new_event_loop()
|
||||
# Setup LEDs
|
||||
pin = Pin.board.GP16
|
||||
np = NeoPixel(pin, 10, sm=1)
|
||||
np = NeoPixel(hwconfig.LED_DIN, hwconfig.LED_COUNT, sm=1)
|
||||
asyncio.create_task(rainbow(np))
|
||||
|
||||
# Setup MP3 player
|
||||
with SDContext(mosi=Pin(3), miso=Pin(4), sck=Pin(2), ss=Pin(5), baudrate=15000000), \
|
||||
AudioContext(Pin(8), Pin(6)) as audioctx:
|
||||
with SDContext(mosi=hwconfig.SD_DI, miso=hwconfig.SD_DO, sck=hwconfig.SD_SCK, ss=hwconfig.SD_CS,
|
||||
baudrate=15000000), \
|
||||
AudioContext(hwconfig.I2S_DIN, hwconfig.I2S_DCLK, hwconfig.I2S_LRCLK) as audioctx:
|
||||
|
||||
# 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
|
||||
deps = app.Dependencies(mp3player=lambda the_app: MP3Player(audioctx, 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)
|
||||
|
||||
# Start
|
||||
@@ -80,5 +85,5 @@ def run():
|
||||
|
||||
if __name__ == '__main__':
|
||||
if machine.Pin(17, machine.Pin.IN, machine.Pin.PULL_UP).value() != 0:
|
||||
time.sleep(5)
|
||||
time.sleep(1)
|
||||
run()
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
from utils.buttons import Buttons
|
||||
from utils.mbrpartition import MBRPartition
|
||||
from utils.pinindex import get_pin_index
|
||||
from utils.sdcontext import SDContext
|
||||
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
|
||||
# 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