Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release-firmware.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ on:
default: false
type: boolean
pico2:
description: Build the Raspberry Pi Pico 2 too (off until a board is on the bench)
description: Build the Raspberry Pi Pico 2 and the Pimoroni Pico Plus 2 W too (off until a board is on the bench)
default: false
type: boolean
lvgl-circuitpython-ref:
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ one command:
--flash 16MB --modules displayif,pygraphics,/home/you/earful
```

Boards are upstream's own generic ones; the
Boards are upstream's own, with a variant of ours where a board needs one; the
[newcomer's guide](docs/newcomers.md#boards-we-build-for) has the command
for each board we use. You name the modules you want: ours
by short name, anything else by its path, or `all`. Leave out `--port` and it
Expand Down Expand Up @@ -95,7 +95,10 @@ New here? The [newcomer's guide](docs/newcomers.md) explains the model; the
module and a Fetch-backed `requests`), and `esp32/`: the sdkconfig
fragments and partition table every esp32 or every chip gets, and the few
board variants of ours (`ESP32_GENERIC_S3` `LCD_4_3`, `LCD_7` and `T_EMBED`,
`ESP32_GENERIC_P4` `WIFI6_DEV_KIT` and `WIFI6_DEV_KIT_H2`).
`ESP32_GENERIC_P4` `WIFI6_DEV_KIT` and `WIFI6_DEV_KIT_H2`); and `rp2/`
(`RPI_PICO2_W` `PIMORONI_PICO_PLUS2_W`, the Pimoroni Pico Plus 2 W with its
16 MB flash, 8 MB PSRAM and 48 GPIOs:
`--port rp2 --board RPI_PICO2_W --variant PIMORONI_PICO_PLUS2_W`).
- [`scripts/exercise_build.sh`](scripts/exercise_build.sh) — builds every desktop port and two esp32
boards from a bare clone, checks the failure paths, and writes a report of
what should match on any machine. Needs no board. For a report on builds you
Expand Down
55 changes: 48 additions & 7 deletions build_mp.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,19 +740,23 @@ def has_coredump_partition(table):
return any(re.match(r"\s*[^#,\s][^,]*,\s*data\s*,\s*coredump\s*,", line) for line in table.read_text().splitlines())


def link_board(gen, base, generated):
"""Link everything upstream's board holds into gen, except the files the
caller generates there: ${MICROPY_BOARD_DIR} is an include directory and
board cmake files name files in it."""
for entry in sorted(base.iterdir()):
if entry.name not in generated:
(gen / entry.name).symlink_to(entry.resolve())


def esp32_board_dir(gen, base, board, variant, fragment_path, module_dirs, components=()):
"""Upstream's board, unchanged, plus our fragment, as BOARD_DIR= (the way
cmods' build_mp.sh did it). Everything else the board holds is linked
through, because ${MICROPY_BOARD_DIR} is an include directory and board
cmake files name files in it."""
cmods' build_mp.sh did it)."""
if gen.exists():
shutil.rmtree(gen)
gen.mkdir(parents=True)
vfile = f"mpconfigvariant_{variant}.cmake" if variant else "mpconfigvariant.cmake"
generated = {"mpconfigboard.cmake", "mpconfigboard.h", vfile}
for entry in sorted(base.iterdir()):
if entry.name not in generated:
(gen / entry.name).symlink_to(entry.resolve())
link_board(gen, base, {"mpconfigboard.cmake", "mpconfigboard.h", vfile})
lines = ["# Generated by build_mp.py: the board below, unchanged.", f"include({base.as_posix()}/mpconfigboard.cmake)"]
for d in module_dirs:
comps = d / "components"
Expand Down Expand Up @@ -787,6 +791,39 @@ def esp32_board_dir(gen, base, board, variant, fragment_path, module_dirs, compo
(gen / vfile).write_text("\n".join(vlines) + "\n")


def rp2_board_dir(gen, base, board, variant):
"""Upstream's board, unchanged, plus our variant's files, as BOARD_DIR=.
The port takes BOARD_VARIANT= only when the board holds an
mpconfigvariant_<V>.cmake, and builds the pin names from the board's
pins.csv, so the generated directory holds ours: a variant file that
includes upstream's default variant and then ours, our pins.csv when we
have one (it replaces upstream's whole), and the board's header followed by
ours (upstream's defines its name and hostname without #ifndef, so ours
has to come after to #undef them)."""
ours = board_variant_dir("rp2", board, variant)
if not ours:
die(f"{board} has no variant {variant}")
vfile = f"mpconfigvariant_{variant}.cmake"
if (base / vfile).exists():
die(f"{board} has a variant {variant} of its own")
if gen.exists():
shutil.rmtree(gen)
gen.mkdir(parents=True)
own = {n for n in ("mpconfigboard.h", "pins.csv") if (ours / n).exists()}
link_board(gen, base, {vfile} | own)
if "pins.csv" in own:
(gen / "pins.csv").symlink_to((ours / "pins.csv").resolve())
if "mpconfigboard.h" in own:
(gen / "mpconfigboard.h").write_text(
"// Generated by build_mp.py: the board's own header, then the variant's.\n"
f'#include "{(base / "mpconfigboard.h").as_posix()}"\n'
f'#include "{(ours / "mpconfigboard.h").as_posix()}"\n')
(gen / vfile).write_text(
"# Generated by build_mp.py: the board's default variant, then ours.\n"
f"include({base.as_posix()}/mpconfigvariant.cmake OPTIONAL)\n"
f"include({(ours / 'mpconfigvariant.cmake').as_posix()})\n")


# TinyUSB, for the esp32 targets that build it. The port takes it from the
# IDF component manager, which fetches it into managed_components/ at
# configure time, so the overlay can't patch it. Instead a copy at the same
Expand Down Expand Up @@ -1744,6 +1781,10 @@ def shell(cmd, log=None):
if args.coredump:
say("This is a troubleshooting build: a crash writes a core dump to the 'coredump' partition.")
elif port == "rp2":
if board_variant_dir(port, board, variant):
gen = build / board
rp2_board_dir(gen, board_dir(port, board), board, variant)
make += [f"BOARD_DIR={gen}"]
shell(make + ["submodules"])
storage = None
for attempt in (1, 2):
Expand Down
20 changes: 20 additions & 0 deletions docs/newcomers.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ on top. Add `--modules` to any of these:
| WT32-SC01 Plus | `--port esp32 --board ESP32_GENERIC_S3 --flash 16MB` |
| Raspberry Pi Pico (RP2040) | `--port rp2 --board RPI_PICO` (not `--modules all`: see [The model](#the-model)) |
| Raspberry Pi Pico 2 (RP2350) | `--port rp2 --board RPI_PICO2` |
| Pimoroni Pico Plus 2 W (16 MB flash, 8 MB PSRAM, 48 GPIOs) | `--port rp2 --board RPI_PICO2_W --variant PIMORONI_PICO_PLUS2_W` |

A new board usually needs no file at all: pick the upstream board and
variant that match its chip and PSRAM, and give `--flash` its flash size. Make
Expand Down Expand Up @@ -317,6 +318,25 @@ The partition table is found the same way, the most specific
coredump partition on the S3, which a standard build leaves unused);
without one, the board's own is used. Either way autosize grows the app partition when the image doesn't fit.

## rp2 settings

An rp2 board of ours is a variant of one of upstream's, in
`variants/rp2/<BOARD>/<VARIANT>/`. The port only knows variants that sit in
the board's own folder, so `build_mp.py` makes a folder of its own for the
build, `builds/rp2/<BOARD>/<VARIANT>/<BOARD>/`, links upstream's board into it
unchanged and adds what the variant holds:

- `mpconfigvariant.cmake` (required): cmake settings, included after the
board's own default variant. The board's `PICO_BOARD`, the number of GPIOs,
PSRAM and the size of the filesystem go here.
- `mpconfigboard.h`: C defines, included after the board's own header, so it
can `#undef` what the board sets, such as its name.
- `pins.csv`: the pin names, in place of the board's.

`PIMORONI_PICO_PLUS2_W` is the one so far. It builds the Pico 2 W for the
Pimoroni board's 16 MB flash, 8 MB PSRAM, 48 GPIOs and its own pin names, and
its files say why each setting is there.

## Where things are

| Path | What it is |
Expand Down
5 changes: 4 additions & 1 deletion scripts/release_firmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

release_firmware.py matrix [--pico2]
Print the build matrix as JSON for the workflow: one row per cell.
The Pico 2's row is left out unless --pico2 is given.
The Pico 2's and the Pico Plus 2 W's rows are left out unless --pico2 is given.

release_firmware.py build <cell> <out-dir> [--ws DIR]
Build one cell with build_mp.py and put its files, a sha256 of each and
Expand Down Expand Up @@ -96,6 +96,9 @@
dict(id="mp-pico2", label="Raspberry Pi Pico 2", interpreter="micropython", port="rp2",
board="RPI_PICO2", variant=None, flash="4MB", modules="all",
chip="rp2350", offset=None, kind="rp2", deps="none", optional=True),
dict(id="mp-pico-plus2-w", label="Pimoroni Pico Plus 2 W", interpreter="micropython", port="rp2",
board="RPI_PICO2_W", variant="PIMORONI_PICO_PLUS2_W", flash="16MB", modules="all",
chip="rp2350", offset=None, kind="rp2", deps="none", optional=True),
]

# What each kind of cell must hand over, as (role, file in the build dir, required).
Expand Down
54 changes: 54 additions & 0 deletions test_build_mp.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,60 @@ def test_variant_manifests(self):
build_mp.VARIANTS_DIR, build_mp.MODULES_DIR = saved


class Rp2BoardDir(unittest.TestCase):
"""A variant of ours on rp2 gets a board directory: upstream's, linked
through unchanged, with our files added where the port looks for them."""

def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
root = Path(self.tmp.name)
self.base = root / "mp" / "BOARD"
self.base.mkdir(parents=True)
for name, text in (("mpconfigboard.cmake", "set(A 1)"), ("mpconfigboard.h", "#define NAME 1"),
("mpconfigvariant.cmake", "set(P 1)"), ("pins.csv", "GP0,GPIO0"), ("board.json", "{}")):
(self.base / name).write_text(text)
self.ours = root / "variants" / "rp2" / "BOARD" / "OURS"
self.ours.mkdir(parents=True)
(self.ours / "mpconfigvariant.cmake").write_text("set(Q 2)")
self.gen = root / "gen"
self.saved = build_mp.VARIANTS_DIR
build_mp.VARIANTS_DIR = root / "variants"

def tearDown(self):
build_mp.VARIANTS_DIR = self.saved
self.tmp.cleanup()

def test_variant_file_only(self):
build_mp.rp2_board_dir(self.gen, self.base, "BOARD", "OURS")
for name in ("mpconfigboard.cmake", "mpconfigboard.h", "mpconfigvariant.cmake", "pins.csv", "board.json"):
self.assertEqual(self.gen.joinpath(name).resolve(), self.base.joinpath(name).resolve(), name)
self.assertTrue(self.gen.joinpath(name).is_symlink(), name)
text = (self.gen / "mpconfigvariant_OURS.cmake").read_text()
self.assertIn(f"include({self.base.as_posix()}/mpconfigvariant.cmake OPTIONAL)", text)
self.assertLess(text.index("mpconfigvariant.cmake OPTIONAL"), text.index(self.ours.as_posix()))
self.assertIn(f"include({self.ours.as_posix()}/mpconfigvariant.cmake)", text)

def test_our_pins_and_header_replace_and_follow_upstreams(self):
(self.ours / "pins.csv").write_text("GP47,GPIO47")
(self.ours / "mpconfigboard.h").write_text("#undef NAME")
build_mp.rp2_board_dir(self.gen, self.base, "BOARD", "OURS")
self.assertEqual((self.gen / "pins.csv").resolve(), (self.ours / "pins.csv").resolve())
header = (self.gen / "mpconfigboard.h").read_text()
self.assertFalse((self.gen / "mpconfigboard.h").is_symlink())
self.assertLess(header.index(self.base.as_posix()), header.index(self.ours.as_posix()))
self.assertEqual((self.base / "pins.csv").read_text(), "GP0,GPIO0") # upstream untouched

def test_regenerates_over_an_old_directory(self):
build_mp.rp2_board_dir(self.gen, self.base, "BOARD", "OURS")
(self.ours / "pins.csv").write_text("GP47,GPIO47")
build_mp.rp2_board_dir(self.gen, self.base, "BOARD", "OURS")
self.assertEqual((self.gen / "pins.csv").resolve(), (self.ours / "pins.csv").resolve())

def test_a_variant_that_is_not_ours_is_refused(self):
with self.assertRaises(SystemExit):
build_mp.rp2_board_dir(self.gen, self.base, "BOARD", "NOPE")


class CircuitPythonFrozenLibraries(unittest.TestCase):
"""A CircuitPython build that freezes our Python freezes asyncio and
adafruit_ticks too, unless the board already does."""
Expand Down
4 changes: 3 additions & 1 deletion test_release_firmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ class Table(unittest.TestCase):
def test_ten_cells_by_default_and_the_pico2_only_when_asked(self):
self.assertEqual(len(rf.matrix(False)["include"]), 10)
ids = [c["id"] for c in rf.matrix(True)["include"]]
self.assertEqual(len(ids), 11)
self.assertEqual(len(ids), 12)
self.assertIn("mp-pico2", ids)
self.assertIn("mp-pico-plus2-w", ids)
self.assertNotIn("mp-pico2", [c["id"] for c in rf.matrix(False)["include"]])
self.assertNotIn("mp-pico-plus2-w", [c["id"] for c in rf.matrix(False)["include"]])
self.assertEqual(len(set(ids)), len(ids))

def test_five_micropython_and_five_circuitpython(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Pimoroni Pico Plus 2 W, after upstream's RPI_PICO2_W header (which defines
// these without #ifndef, hence the #undef).

#undef MICROPY_HW_BOARD_NAME
#define MICROPY_HW_BOARD_NAME "Pimoroni Pico Plus 2 W"

// What the board calls itself on a network, as the stock Pico 2 W does with its own name.
#undef MICROPY_PY_NETWORK_HOSTNAME_DEFAULT
#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "PicoPlus2W"
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Pimoroni Pico Plus 2 W (PIM726) on top of upstream's RPI_PICO2_W: an RP2350B
# (QFN-80, 48 GPIOs) with 16 MB of flash, 8 MB of PSRAM and an RM2 radio.
# build_mp.py includes this after upstream's own default variant, which sets
# PICO_PLATFORM for the RP2350.

# The SDK's own header for this board. It is what puts the 16 MB flash size,
# the 8 MB PSRAM and its chip select (GP47), the user switch (GP45), the
# Qw/ST I2C bus (GP4/GP5) and the RM2's pins (REG_ON GP23, data and host wake
# GP24, CS GP25, clock GP29, LED on the radio's GPIO 0) into the build. These
# are the same pins CircuitPython's pimoroni_pico_plus2w sets by hand.
set(PICO_BOARD "pimoroni_pico_plus2_w_rp2350")

# The QFN-80 package has 48 GPIOs where the Pico 2 W's RP2350A has 30. This also
# picks the RP2350B alternate-function table for the pin names.
set(PICO_NUM_GPIOS 48)

# 8 MB of PSRAM on GP47. The port brings it up at boot and adds it to the
# heap beside the on-chip RAM. The SDK header gives the size; the chip select
# must agree with its PICO_PSRAM_CS_PIN or main.c refuses to build.
set(MICROPY_HW_ENABLE_PSRAM 1)
set(MICROPY_HW_PSRAM_CS_PIN 47)

# 13 MB of the 16 MB flash is the filesystem, and the firmware gets the other
# 3 MB: the image with every module in it is 2.9 MB on the Pico 2 W. A
# smaller build can ask for more with MICROPY_HW_FLASH_STORAGE_BYTES; one
# that doesn't fit is shrunk by build_mp.py. Upstream's board sets this
# for 4 MB of flash before this file runs, so only a value given on the
# command line (a cache entry) may keep it.
if(NOT DEFINED CACHE{MICROPY_HW_FLASH_STORAGE_BYTES})
set(MICROPY_HW_FLASH_STORAGE_BYTES 13631488) # 13 * 1024 * 1024
endif()

# The RM2 talks to the RP2350 over PIO at the system clock divided by 2, the
# divider CircuitPython uses on this board (CYW43_PIO_CLOCK_DIV_INT=2) and the
# SDK's default. It is written out so a change in the SDK can't move it; raise
# it if the radio turns unreliable at a higher system clock. The pins and the
# radio's three GPIOs (CYW43_WL_GPIO_COUNT, the LED on 0) come from the header
# above, so CircuitPython's -DCYW43_DEFAULT_PIN_WL_* flags have nothing to add.
list(APPEND MICROPY_DEF_BOARD
CYW43_PIO_CLOCK_DIV_INT=2
CYW43_PIO_CLOCK_DIV_FRAC8=0
)
84 changes: 84 additions & 0 deletions variants/rp2/RPI_PICO2_W/PIMORONI_PICO_PLUS2_W/pins.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Pin names for the Pimoroni Pico Plus 2 W. This file replaces the Pico 2 W's,
# which names 26 of the 30 pins of an RP2350A, so every GPIO of the RP2350B is here.
# machine.Pin(n) takes any of them by number whether or not it has a name.
# GP23, GP24, GP25 and GP29 are wired to the RM2 radio (below): leave them to it.
# GP30 to GP39, GP44 and GP46 are not connected to anything on the board.
# GP47 is the PSRAM chip select.
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
GP23,GPIO23
GP24,GPIO24
GP25,GPIO25
GP26,GPIO26
GP27,GPIO27
GP28,GPIO28
GP29,GPIO29
GP30,GPIO30
GP31,GPIO31
GP32,GPIO32
GP33,GPIO33
GP34,GPIO34
GP35,GPIO35
GP36,GPIO36
GP37,GPIO37
GP38,GPIO38
GP39,GPIO39
GP40,GPIO40
GP41,GPIO41
GP42,GPIO42
GP43,GPIO43
GP44,GPIO44
GP45,GPIO45
GP46,GPIO46
GP47,GPIO47

# The user switch, named as CircuitPython names it. It is on GP45.
USER_SW,GPIO45
BUTTON,GPIO45

# GP40 to GP43 have analog input; GP43 also reads VSYS through a divider (CircuitPython's names).
A0,GPIO40
A1,GPIO41
A2,GPIO42
A3,GPIO43
VOLTAGE_MONITOR,GPIO43

# The Qw/ST connector's I2C bus, I2C0. CircuitPython names the bus itself (I2C,
# STEMMA_I2C) rather than its pins, which a pin table can't do: these two name the pins,
# and machine.I2C(0) already defaults to them.
I2C_SDA,GPIO4
I2C_SCL,GPIO5

# The RM2's gSPI lines. GP24 is both data and host wake.
WL_REG_ON,GPIO23
WL_DATA,GPIO24
WL_CS,GPIO25
WL_CLK,GPIO29

# The RM2's own GPIOs. The user LED is on the first; the third senses VBUS (CircuitPython's VBUS_SENSE).
WL_GPIO0,EXT_GPIO0
WL_GPIO1,EXT_GPIO1
WL_GPIO2,EXT_GPIO2
LED,EXT_GPIO0
VBUS_SENSE,EXT_GPIO2
Loading