From e2f1af6eda9c4267476ee542c156f30fbb6e91fd Mon Sep 17 00:00:00 2001 From: sukuwc Date: Wed, 11 Nov 2026 17:02:15 +0100 Subject: [PATCH 1/3] Add RP2350 port with the Intech Studio Grid RP2350 board Port for the RP2350 (Arm secure) built on pico-sdk, started by the bootrom from an IMAGE_DEF in the first sector of flash: - mass storage DFU with 256 byte UF2 payloads in family 0xe48bff59; writes are limited to the application area and a sector is only erased when needed - bootloader in the first 32 KB, application linked at 0x10008000 - double tap of reset through POWMAN's DOUBLE_TAP flag: RAM and the POWMAN scratch registers do not survive a RUN pin reset - self-update that invalidates the IMAGE_DEF first and rewrites it last, so an interrupted update leaves the bootrom's USB bootloader as recovery - WS2812 indicator on PIO, serial number from the bootrom chip info - blinky and update-tinyuf2 applications pico-sdk's own TinyUSB support is switched off because its glue redefines the family_support.cmake functions, and pico_override_flash_size() is not used because the PICO_FLASH_SIZE_BYTES it defines makes every application write hard_assert in flash_range_erase/program. family_add_app() also forwards LD_FLASH_BOOT_SIZE and the pico-sdk variables to the applications. Co-Authored-By: Claude Sonnet 5.5 --- ports/family_support.cmake | 2 +- ports/rp2350/CMakeLists.txt | 51 ++++ ports/rp2350/README.md | 87 ++++++ ports/rp2350/app.cmake | 20 ++ ports/rp2350/board_flash.c | 259 ++++++++++++++++++ ports/rp2350/boards.c | 244 +++++++++++++++++ ports/rp2350/boards.h | 53 ++++ .../intechstudio_grid_rp2350/board.cmake | 14 + .../boards/intechstudio_grid_rp2350/board.h | 32 +++ ports/rp2350/family.cmake | 71 +++++ .../linker/pico_flash_region_app.template.ld | 1 + ports/rp2350/tusb_config.h | 103 +++++++ ports/rp2350/ws2812.pio | 17 ++ 13 files changed, 953 insertions(+), 1 deletion(-) create mode 100644 ports/rp2350/CMakeLists.txt create mode 100644 ports/rp2350/README.md create mode 100644 ports/rp2350/app.cmake create mode 100644 ports/rp2350/board_flash.c create mode 100644 ports/rp2350/boards.c create mode 100644 ports/rp2350/boards.h create mode 100644 ports/rp2350/boards/intechstudio_grid_rp2350/board.cmake create mode 100644 ports/rp2350/boards/intechstudio_grid_rp2350/board.h create mode 100644 ports/rp2350/family.cmake create mode 100644 ports/rp2350/linker/pico_flash_region_app.template.ld create mode 100644 ports/rp2350/tusb_config.h create mode 100644 ports/rp2350/ws2812.pio diff --git a/ports/family_support.cmake b/ports/family_support.cmake index 484cba7e9..05d623cb4 100644 --- a/ports/family_support.cmake +++ b/ports/family_support.cmake @@ -126,7 +126,7 @@ function(family_add_app NAME SOURCE_DIR) -DARTIFACT_PATH=${ARTIFACT_PATH} -DPython_EXECUTABLE=${Python_EXECUTABLE} ) - foreach (VAR LOG LOGGER CFLAGS_CLI TRACE_ETM LTO_ON) + foreach (VAR LOG LOGGER CFLAGS_CLI TRACE_ETM LTO_ON PICO_SDK_PATH PICO_PLATFORM LD_FLASH_BOOT_SIZE) if (DEFINED ${VAR}) # LIST_SEPARATOR below turns | back into ; so a list value stays one argument string(REPLACE ";" "|" VALUE "${${VAR}}") diff --git a/ports/rp2350/CMakeLists.txt b/ports/rp2350/CMakeLists.txt new file mode 100644 index 000000000..1854af72c --- /dev/null +++ b/ports/rp2350/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.20) + +# family.cmake (included by family_support.cmake) pulls in pico_sdk_init.cmake, which must happen before project() +include(${CMAKE_CURRENT_LIST_DIR}/../family_support.cmake) + +project(tinyuf2 C CXX ASM) +set(CMAKE_EXECUTABLE_SUFFIX .elf) + +# pico-sdk's toolchain file does not set CMAKE_SIZE, which family_configure_common runs after the build +if (NOT CMAKE_SIZE) + string(REGEX REPLACE "gcc$" "size" CMAKE_SIZE "${CMAKE_C_COMPILER}") +endif () + +pico_sdk_init() + +#------------------------------------ +# TinyUF2 +#------------------------------------ +add_executable(tinyuf2 + board_flash.c + boards.c + ${TOP}/lib/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c + ${TOP}/lib/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c + ) + +# the WS2812 PIO program +pico_generate_pio_header(tinyuf2 ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio) + +# The bootloader links into the first LD_FLASH_BOOT_SIZE bytes of flash only. pico_override_flash_size() cannot be used +# for that: it also defines PICO_FLASH_SIZE_BYTES, which makes flash_range_erase/program hard_assert() on every offset +# beyond the bootloader, i.e. on every application write. So only the linker region is overridden. +set(PICO_FLASH_SIZE_BYTES_STRING "${LD_FLASH_BOOT_SIZE}") +configure_file(${PICO_SDK_PATH}/src/rp2_common/pico_standard_link/pico_flash_region.template.ld + ${CMAKE_CURRENT_BINARY_DIR}/tinyuf2/pico_flash_region.ld) +pico_add_linker_script_override_path(tinyuf2 ${CMAKE_CURRENT_BINARY_DIR}/tinyuf2 FILES pico_flash_region.ld) + +# drop the SDK runtime parts the bootloader does not use (default alarm pool, printf, float/double, panic, ...) +pico_minimize_runtime(tinyuf2) + +# see __wrap_irq_add_shared_handler() in boards.c +target_link_options(tinyuf2 PUBLIC "LINKER:--wrap=irq_add_shared_handler") + +# the RP2040 and RP2350 share one TinyUSB device driver (OPT_MCU_RP2040) +family_configure_tinyuf2(tinyuf2 OPT_MCU_RP2040) +family_gen_uf2(tinyuf2 ${UF2_FAMILY_ID}) + +#------------------------------------ +# Application +#------------------------------------ +family_add_app(blinky ${TOP}/apps/blinky) +family_add_app(update-tinyuf2 ${TOP}/apps/self_update SELF_UPDATE) diff --git a/ports/rp2350/README.md b/ports/rp2350/README.md new file mode 100644 index 000000000..09da70f2d --- /dev/null +++ b/ports/rp2350/README.md @@ -0,0 +1,87 @@ +# TinyUF2 for RP2350 + +Port for the RP2350 (Arm Cortex-M33, secure) built on [pico-sdk](https://github.com/raspberrypi/pico-sdk). The bootloader runs +behind the RP2350 bootrom, which finds its `IMAGE_DEF` in flash and starts it: the bootrom USB bootloader (BOOTSEL) stays +available as recovery whatever is in flash. + +Boards: + +| Board | Notes | +| --- | --- | +| `intechstudio_grid_rp2350` | 16 WS2812 on GPIO6 (PIO0), volume `GRIDRP2350` | + +## Build + +pico-sdk is not a submodule: `tools/get_deps.py` fetches the pinned version into `lib/mcu/raspberrypi/pico-sdk`. + +``` +python3 tools/get_deps.py rp2350 +cmake -B build -G Ninja -DBOARD=intechstudio_grid_rp2350 ports/rp2350 +cmake --build build +``` + +Use another pico-sdk with `-DPICO_SDK_PATH=` or the `PICO_SDK_PATH` environment variable. The build also needs a host +compiler, for the SDK's `pioasm`. + +## Flash layout + +| Address | Content | +| --- | --- | +| `0x10000000` | TinyUF2, 32 KB region (`LD_FLASH_BOOT_SIZE` in `board.cmake`) | +| `0x10008000` | Application | + +The application must be linked at `0x10008000`, which `app.cmake` does for the applications built here. A UF2 file of an +application uses the family ID `0xe48bff59` (`RP2350_ARM_S`) and absolute flash addresses. To create one from a `.bin`: + +``` +uf2conv.py -c -b 0x10008000 -f 0xe48bff59 firmware.bin +``` + +The bootloader refuses UF2 blocks below the application start or past the end of the flash, and erases a 4 KB sector only +when a block to write does not fit in blank flash, writing back the blocks of that sector it knows to be good. + +The bootloader image is about 17 KB, so the region could shrink: 16 KB was not reached with pico-sdk, TinyUSB's mass storage +class and the flash driver. What was tried: `pico_minimize_runtime()` and replacing `irq_add_shared_handler()` (both used, +about 3.4 KB saved) and LTO (does not link with the Arm GNU 13.3 and Debian 14.2 toolchains). + +## Flashing the bootloader + +Hold BOOTSEL while plugging in, then copy `_bin//tinyuf2.uf2` to the BOOTSEL drive. Updating a running bootloader is done with +the self-update below. SWD flash targets are not set up. + +## Entering the bootloader + +- Double tap reset: the LEDs show purple for 0.5 s after reset, a second reset within that time stays in the bootloader. +- The bootloader also stays when the application is missing or invalid: the stack pointer in the first vector must point into + SRAM and the reset vector must be a Thumb address inside the application area. +- From an application: `board_reboot_to_bootloader()`. + +The double tap uses the `DOUBLE_TAP` flag of POWMAN, armed from the start of the bootloader until it starts the application or +enters DFU mode. A RUN pin reset powers down RAM and also resets the POWMAN scratch registers, which are only kept over watchdog +and software resets: the application request therefore uses `powman_hw->scratch[0]`, the reset button the flag. + +## Self-update + +`update-tinyuf2.uf2` is built next to the bootloader in `_bin//apps`. Copy it to the bootloader drive: it runs from the +application area, rewrites the bootloader and erases itself, then the new bootloader starts. The application area is +overwritten, so copy the application again afterwards. + +The bootloader's first sector holds the `IMAGE_DEF` the bootrom looks for. The update erases it first and writes it last, so an +interrupted update leaves no bootable mix of old and new code: the bootrom then starts its USB bootloader, from where +`tinyuf2.uf2` can be copied again. An image is only written when its vector table is valid, it fits the boot area and holds an +`IMAGE_DEF` start marker in its first 4 KB. + +## Not implemented + +- Write protection: `board_flash_protect_bootloader()` does nothing. UF2 writes cannot reach the bootloader, but the flash + itself is not protected against an application. It needs the block protect bits of the flash chip, whose type is not known yet. +- `BOARD_FLASH_SIZE` in `board.cmake` is the assumed 2 MB, to be confirmed for the board. +- Applications for the RISC-V cores and the non-secure Arm family are not supported. + +## Notes + +- pico-sdk's own TinyUSB support is switched off in `family.cmake`: its glue defines functions with the names of this repo's + `family_support.cmake` and replaces them. TinyUF2 builds `lib/tinyusb`, and CMake prints a warning that SDK USB support is unavailable. +- `pico_override_flash_size()` must not be used for the boot region: it also defines `PICO_FLASH_SIZE_BYTES`, and the SDK's + `flash_range_erase()`/`flash_range_program()` assert on every offset beyond it. +- Flash writes keep interrupts off, as the flash leaves XIP mode meanwhile. diff --git a/ports/rp2350/app.cmake b/ports/rp2350/app.cmake new file mode 100644 index 000000000..b3fde453d --- /dev/null +++ b/ports/rp2350/app.cmake @@ -0,0 +1,20 @@ +include_guard(GLOBAL) + +# applications are standalone pico-sdk projects: pico_sdk_init() must come after their project() and before their targets +enable_language(CXX) +if (NOT CMAKE_SIZE) + string(REGEX REPLACE "gcc$" "size" CMAKE_SIZE "${CMAKE_C_COMPILER}") +endif () +pico_sdk_init() + +function(family_configure_app TARGET) + family_configure_common(${TARGET}) + + # boards.c is compiled into the application too, it drives the WS2812 + pico_generate_pio_header(${TARGET} ${FAMILY_PATH}/ws2812.pio) + + # link behind the bootloader: the flash region starts at the application start + configure_file(${FAMILY_PATH}/linker/pico_flash_region_app.template.ld + ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}/pico_flash_region.ld @ONLY) + pico_add_linker_script_override_path(${TARGET} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET} FILES pico_flash_region.ld) +endfunction() diff --git a/ports/rp2350/board_flash.c b/ports/rp2350/board_flash.c new file mode 100644 index 000000000..058b21519 --- /dev/null +++ b/ports/rp2350/board_flash.c @@ -0,0 +1,259 @@ +// RP2350 flash access through the bootrom flash API (pico-sdk hardware_flash). +// +// TinyUF2 hands over 256 byte UF2 payloads in any order and neither erases nor range checks them, so this file: +// - refuses everything below the application start (the bootloader) and above the flash, +// - erases a 4 KB sector only when a row to write is not blank, and writes back the rows of that sector known to be good. +#include "board_api.h" + +#include "hardware/flash.h" +#include "hardware/sync.h" +#include "pico/bootrom.h" +#include "boot/picobin.h" +#include "boot/picoboot_constants.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM +//--------------------------------------------------------------------+ + +// flash is erased in sectors and programmed in pages, a UF2 payload is one page +#define ERASE_SIZE FLASH_SECTOR_SIZE +#define PROGRAM_SIZE FLASH_PAGE_SIZE + +enum { + BLOCK_COUNT = BOARD_FLASH_SIZE / ERASE_SIZE, + ROWS_PER_BLOCK = ERASE_SIZE / PROGRAM_SIZE +}; +_Static_assert(ROWS_PER_BLOCK <= 16, "good_rows is a 16 bit mask"); + +// rows of each block known to hold their data, written or found identical since the flash was initialized. +// They are written back when the block has to be erased. +static uint16_t good_rows[BLOCK_COUNT]; +// holds the rows to keep while their block is erased +static uint8_t block_cache[ERASE_SIZE] __attribute__((aligned(4))); + +//--------------------------------------------------------------------+ +// Internal Helper +//--------------------------------------------------------------------+ +static bool is_blank(uint32_t addr, uint32_t size) { + const volatile uint32_t* flash = (const volatile uint32_t*) addr; + for (uint32_t i = 0; i < size / sizeof(uint32_t); i++) { + if (flash[i] != 0xffffffff) { + return false; + } + } + return true; +} + +static bool is_equal(uint32_t addr, const uint8_t* data, uint32_t size) { + return memcmp((const void*) addr, data, size) == 0; +} + +static bool is_valid_row(uint32_t addr, uint32_t len) { + return len == PROGRAM_SIZE && (addr % PROGRAM_SIZE) == 0 && addr >= BOARD_FLASH_APP_START && + addr <= BOARD_FLASH_ADDR_ZERO + BOARD_FLASH_SIZE - len; +} + +// The bootrom flash functions take the flash out of XIP mode, so no code or interrupt handler may run from flash +// meanwhile: keep interrupts off for the duration. +static void flash_erase(uint32_t block) { + const uint32_t ints = save_and_disable_interrupts(); + flash_range_erase(block * ERASE_SIZE, ERASE_SIZE); + restore_interrupts(ints); +} + +static void flash_program(uint32_t offset, const uint8_t* src, uint32_t len) { + const uint32_t ints = save_and_disable_interrupts(); + flash_range_program(offset, src, len); + restore_interrupts(ints); +} + +// Erase a block, writing back the rows to keep +static bool flash_erase_block_keep_rows(uint32_t block, uint32_t keep) { + const uint32_t base = BOARD_FLASH_ADDR_ZERO + block * ERASE_SIZE; + + for (uint32_t row = 0; row < ROWS_PER_BLOCK; row++) { + if (keep & (1u << row)) { + memcpy(block_cache + row * PROGRAM_SIZE, (const void*) (base + row * PROGRAM_SIZE), PROGRAM_SIZE); + } + } + + flash_erase(block); + TUF2_VERIFY(is_blank(base, ERASE_SIZE)); + + for (uint32_t row = 0; row < ROWS_PER_BLOCK; row++) { + if (keep & (1u << row)) { + flash_program(block * ERASE_SIZE + row * PROGRAM_SIZE, block_cache + row * PROGRAM_SIZE, PROGRAM_SIZE); + } + } + good_rows[block] = (uint16_t) keep; + return true; +} + +//--------------------------------------------------------------------+ +// Board API +//--------------------------------------------------------------------+ +void board_flash_init(void) { +} + +uint32_t board_flash_size(void) { + return BOARD_FLASH_SIZE; +} + +void board_flash_read(uint32_t addr, void* buffer, uint32_t len) { + memcpy(buffer, (const void*) addr, len); +} + +void board_flash_flush(void) { + // rows are programmed as they arrive +} + +bool board_flash_write(uint32_t addr, void const* data, uint32_t len) { + TUF2_VERIFY(is_valid_row(addr, len)); + + const uint32_t offset = addr - BOARD_FLASH_ADDR_ZERO; + const uint32_t block = offset / ERASE_SIZE; + const uint16_t row_bit = (uint16_t) (1u << ((offset % ERASE_SIZE) / PROGRAM_SIZE)); + + // a UF2 block written twice, or the flash already holding the data + if (is_equal(addr, data, len)) { + good_rows[block] |= row_bit; + return true; + } + + if (!is_blank(addr, len)) { + TUF2_VERIFY(flash_erase_block_keep_rows(block, good_rows[block] & ~row_bit)); + } + + flash_program(offset, data, len); + TUF2_VERIFY(is_equal(addr, data, len)); + good_rows[block] |= row_bit; + return true; +} + +void board_flash_erase_app(void) { + // erasing the first block of the app is enough to invalidate it + const uint32_t block = (BOARD_FLASH_APP_START - BOARD_FLASH_ADDR_ZERO) / ERASE_SIZE; + flash_erase(block); + good_rows[block] = 0; +} + +bool board_flash_protect_bootloader(bool protect) { + // M6: flash chip block protect. Until then writes below BOARD_FLASH_APP_START are refused by board_flash_write() + (void) protect; + return false; +} + +#ifdef TINYUF2_SELF_UPDATE + +static bool is_all_ff(const uint8_t* data, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + if (data[i] != 0xff) { + return false; + } + } + return true; +} + +static bool is_new_bootloader_valid(const uint8_t* bootloader_bin, uint32_t bootloader_len) { + // at least larger than the vector table, and must leave the application untouched + if (bootloader_len < 1024 || bootloader_len > BOARD_FLASH_BOOT_SIZE) { + return false; + } + + const uint32_t* word = (const uint32_t*) (uintptr_t) bootloader_bin; + const uint32_t sp = word[0]; + const uint32_t entry = word[1]; + + // 1st word is the stack pointer (must be in SRAM), the 2nd the reset handler: a Thumb address inside the image + if ((sp & 0xff000003u) != 0x20000000u) { + return false; + } + if ((entry & 1u) == 0 || entry < BOARD_FLASH_ADDR_ZERO || entry >= BOARD_FLASH_ADDR_ZERO + bootloader_len) { + return false; + } + + // the bootrom only boots an image with an IMAGE_DEF block, which it looks for in the first 4 KB + const uint32_t search_words = (bootloader_len < 4096 ? bootloader_len : 4096) / sizeof(uint32_t); + for (uint32_t i = 0; i < search_words; i++) { + if (word[i] == PICOBIN_BLOCK_MARKER_START) { + return true; + } + } + return false; +} + +// Fill block_cache with what a block of the bootloader area must hold: its part of the new bootloader, then blank +static uint32_t bootloader_block_fill(uint32_t block, const uint8_t* bootloader_bin, uint32_t bootloader_len) { + const uint32_t offset = block * ERASE_SIZE; + uint32_t size = 0; + if (offset < bootloader_len) { + size = ((bootloader_len - offset) < ERASE_SIZE) ? (bootloader_len - offset) : ERASE_SIZE; + } + + memset(block_cache, 0xff, sizeof(block_cache)); + memcpy(block_cache, bootloader_bin + offset, size); + return BOARD_FLASH_ADDR_ZERO + offset; +} + +// Make a block of the bootloader area hold its part of the new bootloader and nothing else +static bool bootloader_block_update(uint32_t block, const uint8_t* bootloader_bin, uint32_t bootloader_len) { + const uint32_t base = bootloader_block_fill(block, bootloader_bin, bootloader_len); + if (is_equal(base, block_cache, ERASE_SIZE)) { + return true; + } + + if (!is_blank(base, ERASE_SIZE)) { + flash_erase(block); + TUF2_VERIFY(is_blank(base, ERASE_SIZE)); + } + + for (uint32_t row = 0; row < ROWS_PER_BLOCK; row++) { + if (!is_all_ff(block_cache + row * PROGRAM_SIZE, PROGRAM_SIZE)) { + flash_program(block * ERASE_SIZE + row * PROGRAM_SIZE, block_cache + row * PROGRAM_SIZE, PROGRAM_SIZE); + } + } + return is_equal(base, block_cache, ERASE_SIZE); +} + +// Runs from RAM: erases the first block of the application, which is the code that is running, then reboots. +// Interrupts are off, and nothing in flash may be called once the erase has started. +static void __no_inline_not_in_flash_func(erase_app_start_and_reboot)(uint32_t offset) { + flash_range_erase(offset, ERASE_SIZE); + rom_reboot(REBOOT2_FLAG_REBOOT_TYPE_NORMAL, 10, 0, 0); + while (1) {} +} + +void board_self_update(const uint8_t* bootloader_bin, uint32_t bootloader_len) { + // On failure this application stays in place so the update can be tried again + if (!is_new_bootloader_valid(bootloader_bin, bootloader_len)) { + TUF2_LOG1("Invalid bootloader image\r\n"); + return; + } + + // The IMAGE_DEF the bootrom boots from sits in block 0. Invalidate it first and rewrite it last, so that a power + // loss halfway never leaves a bootable mix of old and new code: the bootrom then falls back to its USB drive. + const uint32_t base0 = bootloader_block_fill(0, bootloader_bin, bootloader_len); + if (!is_equal(base0, block_cache, ERASE_SIZE)) { + flash_erase(0); + } + + // keep writing until flash contents matches the new bootloader data, the rest of the bootloader area blank + const uint32_t blocks = BOARD_FLASH_BOOT_SIZE / ERASE_SIZE; + bool updated = false; + for (uint32_t retry = 0; retry < 3 && !updated; retry++) { + updated = true; + for (uint32_t i = blocks; i > 0 && updated; i--) { + updated = bootloader_block_update(i - 1, bootloader_bin, bootloader_len); + } + } + + if (!updated) { + TUF2_LOG1("Bootloader update failed\r\n"); + return; + } + + // self-destruct so the new bootloader does not run this application again + save_and_disable_interrupts(); + erase_app_start_and_reboot(BOARD_FLASH_APP_START - BOARD_FLASH_ADDR_ZERO); +} +#endif diff --git a/ports/rp2350/boards.c b/ports/rp2350/boards.c new file mode 100644 index 000000000..e63a8c213 --- /dev/null +++ b/ports/rp2350/boards.c @@ -0,0 +1,244 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "board_api.h" + +#include "pico/bootrom.h" +#include "boot/picoboot_constants.h" + +#include "hardware/clocks.h" +#include "hardware/irq.h" +#include "hardware/resets.h" +#include "hardware/structs/scb.h" +#include "hardware/pio.h" +#include "hardware/structs/systick.h" +#include "hardware/timer.h" + +#include "ws2812.pio.h" + +//--------------------------------------------------------------------+ +// Neopixel (WS2812 driven by PIO) +//--------------------------------------------------------------------+ +#define NEOPIXEL_PIO pio0 +#define NEOPIXEL_SM 0 +#define NEOPIXEL_BIT_HZ 800000u +#define NEOPIXEL_BIT_PIO_CYCLES (ws2812_T1 + ws2812_T2 + ws2812_T3) +// the line must stay low this long after a frame to latch it (datasheet minimum is 280 us) +#define NEOPIXEL_RESET_US 300u + +static void neopixel_init(void) { + // the only program in this PIO: load it at offset 0 directly instead of through pio_add_program() (saves ~300 bytes) + const uint offset = 0; + for (uint i = 0; i < count_of(ws2812_program_instructions); i++) { + NEOPIXEL_PIO->instr_mem[offset + i] = ws2812_program_instructions[i]; + } + pio_sm_config c = ws2812_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, NEOPIXEL_PIN); + // shift left, autopull at 24 bits: one GRB pixel per FIFO word + sm_config_set_out_shift(&c, false, true, 24); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + + // integer clock divider: sys_clk / (bit rate * PIO cycles per bit), 8 bit fraction + const uint32_t target = NEOPIXEL_BIT_HZ * NEOPIXEL_BIT_PIO_CYCLES; + const uint32_t sys_hz = clock_get_hz(clk_sys); + sm_config_set_clkdiv_int_frac8(&c, (uint16_t) (sys_hz / target), (uint8_t) (((sys_hz % target) * 256u) / target)); + + pio_gpio_init(NEOPIXEL_PIO, NEOPIXEL_PIN); + pio_sm_set_consecutive_pindirs(NEOPIXEL_PIO, NEOPIXEL_SM, NEOPIXEL_PIN, 1, true); + pio_sm_init(NEOPIXEL_PIO, NEOPIXEL_SM, offset, &c); + pio_sm_set_enabled(NEOPIXEL_PIO, NEOPIXEL_SM, true); +} + +static inline uint8_t apply_brightness(uint8_t value) { + return (uint8_t) (((uint32_t) value * NEOPIXEL_BRIGHTNESS) / 255u); +} + +void board_rgb_write(uint8_t const rgb[]) { + static uint32_t last_end = 0; + + // neopixel color order is GRB, left aligned in the 24 bit shift + const uint32_t grb = ((uint32_t) apply_brightness(rgb[1]) << 24) | ((uint32_t) apply_brightness(rgb[0]) << 16) | + ((uint32_t) apply_brightness(rgb[2]) << 8); + + // the previous frame is latched once the line has been low for the reset time + while ((time_us_32() - last_end) < NEOPIXEL_RESET_US) {} + + for (uint i = 0; i < NEOPIXEL_NUMBER; i++) { + pio_sm_put_blocking(NEOPIXEL_PIO, NEOPIXEL_SM, grb); + } + + // wait until the last word has left the FIFO; the reset time above covers the word still being shifted out + while (!pio_sm_is_tx_fifo_empty(NEOPIXEL_PIO, NEOPIXEL_SM)) {} + last_end = time_us_32(); +} + +// single color LED is not present on this board +void board_led_write(uint32_t state) { + (void) state; +} + +//--------------------------------------------------------------------+ +// Board +//--------------------------------------------------------------------+ +#ifndef BUILD_APPLICATION +// POWMAN's DOUBLE_TAP flag is kept across a RUN pin reset. It is set from the start of the bootloader until the +// application is started or DFU mode is entered, so a second reset in that window is seen at the next boot. +static inline void double_tap_arm(void) { + hw_set_bits(&powman_hw->chip_reset, POWMAN_CHIP_RESET_DOUBLE_TAP_BITS | POWMAN_PASSWORD_BITS); +} + +static inline void double_tap_disarm(void) { + hw_clear_bits(&powman_hw->chip_reset, POWMAN_CHIP_RESET_DOUBLE_TAP_BITS | POWMAN_PASSWORD_BITS); +} +#endif + +void board_init(void) { + neopixel_init(); + +#ifndef BUILD_APPLICATION + // reset while the previous boot was waiting for a second tap: ask main() for DFU mode + if (powman_hw->chip_reset & POWMAN_CHIP_RESET_DOUBLE_TAP_BITS) { + TINYUF2_DBL_TAP_REG = DBL_TAP_MAGIC; + } + double_tap_arm(); +#endif +} + +void board_teardown(void) { +#ifndef BUILD_APPLICATION + double_tap_disarm(); +#endif + board_timer_stop(); + // leave the application a clean start: no pending SysTick, no enabled or pending interrupts, PIO0 in reset state + scb_hw->icsr = M33_ICSR_PENDSTCLR_BITS; + for (uint i = 0; i < count_of(nvic_hw->icer); i++) { + nvic_hw->icer[i] = 0xffffffffu; + nvic_hw->icpr[i] = 0xffffffffu; + } + reset_block_mask(RESETS_RESET_PIO0_BITS); + unreset_block_mask_wait_blocking(RESETS_RESET_PIO0_BITS); +} + +void board_dfu_init(void) { +#ifndef BUILD_APPLICATION + double_tap_disarm(); +#endif + // clk_usb is set up by the SDK runtime init; TinyUSB's dcd_init() resets and configures the USB controller +} + +void board_reset(void) { + // let the bootrom do a normal reboot; a bare watchdog trigger does not reset anything unless PSM_WDSEL is set up + rom_reboot(REBOOT2_FLAG_REBOOT_TYPE_NORMAL, 10, 0, 0); + while (1) {} +} + +void board_dfu_complete(void) { + board_reset(); +} + +// The application is valid when its vector table looks sane: the initial stack pointer is in SRAM and the reset +// handler is a Thumb address inside the application area. Erased flash (0xffffffff) fails the stack pointer check. +bool board_app_valid(void) { + const uint32_t* vector = (const uint32_t*) BOARD_FLASH_APP_START; + const uint32_t sp = vector[0]; + const uint32_t entry = vector[1]; + + return (sp & 0xff000003u) == 0x20000000u && (entry & 1u) != 0 && entry > BOARD_FLASH_APP_START && + entry < BOARD_FLASH_ADDR_ZERO + BOARD_FLASH_SIZE; +} + +void board_app_jump(void) { + const uint32_t* vector = (const uint32_t*) BOARD_FLASH_APP_START; + + // exceptions before the application installs its own vector table must reach the application's handlers + scb_hw->vtor = BOARD_FLASH_APP_START; + __dsb(); + __isb(); + + // same as a reset: stack pointer from the first vector, then the reset handler from the second + __asm volatile("msr msp, %0" : : "r"(vector[0]) : "memory"); + ((void (*)(void)) vector[1])(); + while (1) {} +} + +// for applications: come back in the bootloader instead of running the application again +void board_reboot_to_bootloader(void) { + TINYUF2_DBL_TAP_REG = DBL_TAP_MAGIC; + board_reset(); +} + +// The serial number is the chip's 64 bit device id, as given by the bootrom (same bytes as pico_get_unique_board_id()) +uint8_t board_usb_get_serial(uint8_t serial_id[16]) { + const rom_get_sys_info_fn get_sys_info = (rom_get_sys_info_fn) rom_func_lookup(ROM_FUNC_GET_SYS_INFO); + uint32_t words[9]; + if (get_sys_info == NULL || get_sys_info(words, 9, SYS_INFO_CHIP_INFO) != 4) { + return 0; + } + + // words[0] flags the returned items, then package_sel, then the device id in words[2] and words[3] + const uint8_t* bytes = (const uint8_t*) words; + for (uint i = 0; i < 8; i++) { + serial_id[i] = bytes[15 - i]; + } + return 8; +} + +//--------------------------------------------------------------------+ +// Timer +//--------------------------------------------------------------------+ +void board_timer_start(uint32_t ms) { + systick_hw->rvr = (clock_get_hz(clk_sys) / 1000u) * ms - 1u; + systick_hw->cvr = 0; + // enable, interrupt, processor clock + systick_hw->csr = 0x7u; +} + +void board_timer_stop(void) { + systick_hw->csr = 0; +} + +// pico-sdk vector table name for SysTick +void isr_systick(void); +void isr_systick(void) { + board_timer_handler(); +} + +int board_uart_write(void const* buf, int len) { + (void) buf; + (void) len; + return 0; +} + +//--------------------------------------------------------------------+ +// USB Interrupt Handler +//--------------------------------------------------------------------+ + +// TinyUSB's RP2040/RP2350 device driver registers its USB IRQ with irq_add_shared_handler(), which links in the SDK's +// shared handler list (about 700 bytes). Nothing else shares USBCTRL_IRQ in the bootloader, so install it as the +// exclusive handler instead (see --wrap in CMakeLists.txt). +void __wrap_irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_priority); +void __wrap_irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_priority) { + (void) order_priority; + irq_set_exclusive_handler(num, handler); +} diff --git a/ports/rp2350/boards.h b/ports/rp2350/boards.h new file mode 100644 index 000000000..1e2374481 --- /dev/null +++ b/ports/rp2350/boards.h @@ -0,0 +1,53 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ha Thach (tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BOARDS_H_ +#define BOARDS_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "board.h" + +// Double tap of reset enters the bootloader. TinyUF2 keeps a 32 bit token in TINYUF2_DBL_TAP_REG, here a POWMAN +// scratch register (the same one in the bootloader and in applications, so an application can request the bootloader, +// see board_reboot_to_bootloader()). POWMAN scratch survives watchdog and software resets, but NOT the RUN pin: +// a RUN reset also resets POWMAN, and RAM is powered down by it. Only POWMAN's DOUBLE_TAP flag survives RUN, so +// boards.c arms that flag while the bootloader waits for the second tap and turns it into the token at the next boot. +#include "hardware/structs/powman.h" +#define TINYUF2_DBL_TAP_DFU 1 +#define TINYUF2_DBL_TAP_REG (powman_hw->scratch[0]) + +// Flash is memory mapped here, and UF2 files for this family use these absolute addresses +#define BOARD_FLASH_ADDR_ZERO 0x10000000 + +// The application starts right after the bootloader region (BOARD_FLASH_BOOT_SIZE, from board.cmake) +#define BOARD_FLASH_APP_START (BOARD_FLASH_ADDR_ZERO + BOARD_FLASH_BOOT_SIZE) + +#ifdef __cplusplus + } +#endif + +#endif /* BOARDS_H_ */ diff --git a/ports/rp2350/boards/intechstudio_grid_rp2350/board.cmake b/ports/rp2350/boards/intechstudio_grid_rp2350/board.cmake new file mode 100644 index 000000000..45e7e0fb5 --- /dev/null +++ b/ports/rp2350/boards/intechstudio_grid_rp2350/board.cmake @@ -0,0 +1,14 @@ +# flash size of the board in bytes. TODO: confirm +set(LD_FLASH_SIZE 2097152) + +# flash reserved for the bootloader, in bytes (32 KB for now: 16 KB does not fit the full feature set) +if (NOT DEFINED LD_FLASH_BOOT_SIZE) + set(LD_FLASH_BOOT_SIZE 32768) +endif () + +function(update_board TARGET) + target_compile_definitions(${TARGET} INTERFACE + BOARD_FLASH_SIZE=${LD_FLASH_SIZE} + BOARD_FLASH_BOOT_SIZE=${LD_FLASH_BOOT_SIZE} + ) +endfunction() diff --git a/ports/rp2350/boards/intechstudio_grid_rp2350/board.h b/ports/rp2350/boards/intechstudio_grid_rp2350/board.h new file mode 100644 index 000000000..110240428 --- /dev/null +++ b/ports/rp2350/boards/intechstudio_grid_rp2350/board.h @@ -0,0 +1,32 @@ +#ifndef BOARD_H_ +#define BOARD_H_ + +//--------------------------------------------------------------------+ +// Neopixel +//--------------------------------------------------------------------+ + +// Number of neopixels +#define NEOPIXEL_NUMBER 16 +// Brightness from 1 to 255 +#define NEOPIXEL_BRIGHTNESS 0x08 + +// WS2812 data pin (GPIO number), no supply enable pin on this board +#define NEOPIXEL_PIN 6 + +// BOARD_FLASH_SIZE and BOARD_FLASH_BOOT_SIZE come from board.cmake + +//--------------------------------------------------------------------+ +// USB UF2 +//--------------------------------------------------------------------+ + +#define USB_VID 0x2e8a +#define USB_PID 0x1145 +#define USB_MANUFACTURER "Intech Studio" +#define USB_PRODUCT "Grid RP2350 Bootloader" + +#define UF2_PRODUCT_NAME USB_PRODUCT +#define UF2_BOARD_ID "RP2350-GRID-v0" +#define UF2_VOLUME_LABEL "GRIDRP2350" +#define UF2_INDEX_URL "https://github.com/intechstudio/grid-fw/" + +#endif diff --git a/ports/rp2350/family.cmake b/ports/rp2350/family.cmake new file mode 100644 index 000000000..268f2edf0 --- /dev/null +++ b/ports/rp2350/family.cmake @@ -0,0 +1,71 @@ +include_guard(GLOBAL) + +#------------------------------------ +# Config +#------------------------------------ + +# RP2350_ARM_S_FAMILY_ID in pico-sdk's boot/uf2.h +set(UF2_FAMILY_ID 0xe48bff59) + +set(PICO_PLATFORM rp2350) +# uf2 is made by family_gen_uf2, not by the SDK +set(PICO_NO_UF2 1) +# nothing here post-processes with picotool, and without an installed one the SDK would fetch and build it on every build +set(PICO_NO_PICOTOOL 1) +# the bootloader has no secure gateway functions, so build without -mcmse (as the RP2350 bootrom itself does) +set(PICO_NO_CMSE 1) + +# pico-sdk: -DPICO_SDK_PATH, else the environment, else the copy tools/get_deps.py fetches +if (NOT PICO_SDK_PATH AND DEFINED ENV{PICO_SDK_PATH}) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) +endif () +if (NOT PICO_SDK_PATH) + set(PICO_SDK_PATH ${TOP}/lib/mcu/raspberrypi/pico-sdk) +endif () +if (NOT EXISTS ${PICO_SDK_PATH}/pico_sdk_init.cmake) + message(FATAL_ERROR "pico-sdk not found at ${PICO_SDK_PATH}: run python3 tools/get_deps.py rp2350, " + "or pass -DPICO_SDK_PATH=") +endif () + +# pico-sdk's tinyusb glue includes TinyUSB's own family_support.cmake, whose functions (family_configure_common ...) +# would replace TinyUF2's. TinyUF2 builds lib/tinyusb itself, so point the SDK at a path without hw/bsp/rp2040: +# it then skips its TinyUSB support (and prints a warning saying so). +set(PICO_TINYUSB_PATH ${CMAKE_BINARY_DIR}/no-sdk-tinyusb) + +include(${CMAKE_CURRENT_LIST_DIR}/boards/${BOARD}/board.cmake) + +# pico-sdk selects the toolchain, no CMAKE_TOOLCHAIN_FILE here +include(${PICO_SDK_PATH}/pico_sdk_init.cmake) + +#------------------------------------ +# BOARD_TARGET +#------------------------------------ +function(family_add_board_target BOARD_TARGET) + if (TARGET ${BOARD_TARGET}) + return() + endif () + + add_library(${BOARD_TARGET} INTERFACE) + target_include_directories(${BOARD_TARGET} INTERFACE + ${CMAKE_CURRENT_FUNCTION_LIST_DIR} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/boards/${BOARD} + ) + target_link_libraries(${BOARD_TARGET} INTERFACE + pico_runtime + pico_standard_link + hardware_clocks + hardware_flash + hardware_gpio + hardware_irq + hardware_pio + hardware_resets + hardware_structs + pico_sync + ) + + update_board(${BOARD_TARGET}) + + target_compile_definitions(${BOARD_TARGET} INTERFACE + BOARD_UF2_FAMILY_ID=${UF2_FAMILY_ID} + ) +endfunction() diff --git a/ports/rp2350/linker/pico_flash_region_app.template.ld b/ports/rp2350/linker/pico_flash_region_app.template.ld new file mode 100644 index 000000000..86eede63b --- /dev/null +++ b/ports/rp2350/linker/pico_flash_region_app.template.ld @@ -0,0 +1 @@ +FLASH(rx) : ORIGIN = 0x10000000 + @LD_FLASH_BOOT_SIZE@, LENGTH = @LD_FLASH_SIZE@ - @LD_FLASH_BOOT_SIZE@ diff --git a/ports/rp2350/tusb_config.h b/ports/rp2350/tusb_config.h new file mode 100644 index 000000000..a3eb0b464 --- /dev/null +++ b/ports/rp2350/tusb_config.h @@ -0,0 +1,103 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +// TinyUSB's RP2040/RP2350 support uses pico-sdk macros such as __not_in_flash() +#include "pico.h" + +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif + +#define CFG_TUSB_OS OPT_OS_NONE + +// can be defined by compiler in DEBUG build +#ifndef CFG_TUSB_DEBUG + #define CFG_TUSB_DEBUG 0 +#endif + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 1 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_BUFSIZE 4096 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_BUFSIZE 64 + +// Vendor FIFO size of TX and RX +// If not configured vendor endpoints will not be buffered +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/ports/rp2350/ws2812.pio b/ports/rp2350/ws2812.pio new file mode 100644 index 000000000..e5c795004 --- /dev/null +++ b/ports/rp2350/ws2812.pio @@ -0,0 +1,17 @@ +; WS2812 data output, 800 kHz bit rate: one bit is T1 + T2 + T3 PIO cycles +.program ws2812 +.side_set 1 + +.define public T1 2 +.define public T2 5 +.define public T3 3 + +.wrap_target +bitloop: + out x, 1 side 0 [T3 - 1] ; shift one bit out, drive low + jmp !x do_zero side 1 [T1 - 1] ; drive high, branch on the bit +do_one: + jmp bitloop side 1 [T2 - 1] ; a one stays high longer +do_zero: + nop side 0 [T2 - 1] ; a zero goes low +.wrap From 82833be4f9725cf40b44d787e5aee981c9f28299 Mon Sep 17 00:00:00 2001 From: sukuwc Date: Wed, 11 Nov 2026 17:02:15 +0100 Subject: [PATCH 2/3] Register the RP2350 port in dependencies, CI, build checks and docs - tools/get_deps.py: pin pico-sdk 2.3.1 for the rp2350 family - build.yml and check_build.py: build intechstudio_grid_rp2350 with the ARM jobs - test_build.py: rp2350 also compiles apps/blinky - README.md and supported_boards.md: list the RP2350 port. The generated supported_boards.md also gains the two Espressif Intech boards that were missing from it on master. Co-Authored-By: Claude Sonnet 5.5 --- .claude/skills/build/scripts/check_build.py | 1 + .claude/test/test_build.py | 4 ++-- .github/workflows/build.yml | 1 + README.md | 1 + supported_boards.md | 8 ++++++++ tools/get_deps.py | 3 +++ 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.claude/skills/build/scripts/check_build.py b/.claude/skills/build/scripts/check_build.py index eb0670298..98d49c38a 100755 --- a/.claude/skills/build/scripts/check_build.py +++ b/.claude/skills/build/scripts/check_build.py @@ -51,6 +51,7 @@ 'lpc55': ['lpcxpresso55s69'], 'maxim': ['max32666fthr'], 'mimxrt10xx': ['imxrt1064_evk', 'imxrt1170_evk'], + 'rp2350': ['intechstudio_grid_rp2350'], 'samd51': ['intech_grid'], 'stm32f3': ['stm32f303disco'], 'stm32f4': ['stm32f411ve_discovery', 'feather_stm32f405_express'], diff --git a/.claude/test/test_build.py b/.claude/test/test_build.py index 4e84c2da6..fa62672cb 100644 --- a/.claude/test/test_build.py +++ b/.claude/test/test_build.py @@ -375,8 +375,8 @@ def test_app_nothing_builds_is_uncovered(self): def test_top_level_app_selects_ports_whose_backend_compiles_it(self): ports = {cb.port_of(b) for b, _ in classify('apps/blinky/src/blinky.c')[2]} - self.assertEqual(ports, {'espressif', 'lpc55', 'maxim', 'mimxrt10xx', 'samd51', 'stm32f3', 'stm32f4', 'stm32h5', - 'stm32h7'}) + self.assertEqual(ports, {'espressif', 'lpc55', 'maxim', 'mimxrt10xx', 'rp2350', 'samd51', 'stm32f3', 'stm32f4', + 'stm32h5', 'stm32h7'}) def test_a_ports_cmake_files_select_its_cmake_build(self): for port in ('kinetis_k32l2', 'stm32h7', 'stm32l4'): diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd73a93db..22ec721f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,6 +76,7 @@ jobs: - 'lpc55' - 'maxim' - 'mimxrt10xx' + - 'rp2350' - 'samd51' - 'stm32f3' - 'stm32f4' diff --git a/README.md b/README.md index 2a5d78048..412eb1c1b 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Not all features are implemented for all MCUs, following is supported MCUs and i | LPC55 | ✔ | ✔ | | | ✔ | | | MAX32 | ✔ | ✔ | ✔ | | | | | iMXRT | ✔ | ✔ | ✔ | | ✔ | | +| RP2350 | ✔ | ✔ | ✔ | | ✔ | | | SAMD51 | ✔ | | ✔ | ✔ | ✔ | | | STM32F3 | ✔ | ✔ | ✔ | ✔ | ✔ | | | STM32F4 | ✔ | ✔ | ✔ | ✔ | ✔ | | diff --git a/supported_boards.md b/supported_boards.md index 0705e5d63..49dabe1be 100644 --- a/supported_boards.md +++ b/supported_boards.md @@ -57,6 +57,8 @@ | heltec_wireless_tracker | Heltec Wireless Tracker | 0x303A:0x81A1 | https://heltec.org/project/wireless-tracker | | hexky_s2 | FutureKeys HexKy-S2 | 0x303A:0x80DA | https://futurekeys.net/index.html | | hiibot_iots2 | HIIBOT HiiBot IoTs2 | 0x80E7:0x8111 | https://www.tindie.com/products/bradchan/hiibot-iots2/ | +| intechstudio_grid | Intech Studio Grid | 0x303A:0x8122 | https://github.com/intechstudio/grid-fw | +| intechstudio_knot | Intech Studio Knot | 0x303A:0x8124 | https://github.com/intechstudio/knot | | lilygo_tdeck | LILYGO T-Deck | 0x303A:0x81B7 | https://github.com/Xinyuan-LilyGO/T-Deck | | lilygo_tdisplay_s3 | LILYGO T-Display S3 | 0x303A:0x8140 | https://github.com/Xinyuan-LilyGO/T-Display-S3 | | lilygo_tdongle_s3 | LILYGO T-Dongle S3 | 0x303A:0x82C3 | https://github.com/Xinyuan-LilyGO/T-Dongle-S3 | @@ -169,6 +171,12 @@ | teensy40 | PJRC Teensy 4.0 | 0x239A:0x0085 | https://www.pjrc.com/store/teensy40.html | | teensy41 | PJRC Teensy 4.1 | 0x239A:0x00AD | https://www.pjrc.com/store/teensy41.html | +## rp2350 + +| Board | Name | VID PID | URL | +| --- | --- | --- | --- | +| intechstudio_grid_rp2350 | Intech Studio Grid RP2350 Bootloader | 0x2E8A:0x1145 | https://github.com/intechstudio/grid-fw/ | + ## samd51 | Board | Name | VID PID | URL | diff --git a/tools/get_deps.py b/tools/get_deps.py index 87f1aedc5..e8a7c732c 100644 --- a/tools/get_deps.py +++ b/tools/get_deps.py @@ -20,6 +20,9 @@ 'lib/mcu/nxp/mcux-sdk': ['https://github.com/nxp-mcuxpresso/mcux-sdk.git', '9990f264f98430f6d885041ab0f24224d68f4958', 'kinetis_k kinetis_k32l2 kinetis_kl lpc51 lpc54 lpc55 mcx mimxrt10xx'], + 'lib/mcu/raspberrypi/pico-sdk': ['https://github.com/raspberrypi/pico-sdk.git', + '079c6f39023649b154152db30f1d781e884879bc', + 'rp2350'], 'lib/mcu/st/cmsis_device_f3': ['https://github.com/STMicroelectronics/cmsis_device_f3.git', '5e4ee5ed7a7b6c85176bb70a9fd3c72d6eb99f1b', 'stm32f3'], From 6e2d337b2cbf2bbc5381bf3079559e19546c8ea6 Mon Sep 17 00:00:00 2001 From: sukuwc Date: Wed, 11 Nov 2026 17:15:47 +0100 Subject: [PATCH 3/3] Add the intechstudio_wrid_rp2350 board Same hardware and USB IDs as intechstudio_grid_rp2350, with its own product string, board ID and volume label (WRIDRP2350) to tell the two bootloaders apart, like intech_grid and intech_wrid on the SAMD51. Co-Authored-By: Claude Sonnet 5.5 --- ports/rp2350/README.md | 1 + .../intechstudio_wrid_rp2350/board.cmake | 14 ++++++++ .../boards/intechstudio_wrid_rp2350/board.h | 32 +++++++++++++++++++ supported_boards.md | 1 + 4 files changed, 48 insertions(+) create mode 100644 ports/rp2350/boards/intechstudio_wrid_rp2350/board.cmake create mode 100644 ports/rp2350/boards/intechstudio_wrid_rp2350/board.h diff --git a/ports/rp2350/README.md b/ports/rp2350/README.md index 09da70f2d..ce14898b2 100644 --- a/ports/rp2350/README.md +++ b/ports/rp2350/README.md @@ -9,6 +9,7 @@ Boards: | Board | Notes | | --- | --- | | `intechstudio_grid_rp2350` | 16 WS2812 on GPIO6 (PIO0), volume `GRIDRP2350` | +| `intechstudio_wrid_rp2350` | Same hardware and USB IDs as `intechstudio_grid_rp2350`, volume `WRIDRP2350`, to tell the two bootloaders apart | ## Build diff --git a/ports/rp2350/boards/intechstudio_wrid_rp2350/board.cmake b/ports/rp2350/boards/intechstudio_wrid_rp2350/board.cmake new file mode 100644 index 000000000..45e7e0fb5 --- /dev/null +++ b/ports/rp2350/boards/intechstudio_wrid_rp2350/board.cmake @@ -0,0 +1,14 @@ +# flash size of the board in bytes. TODO: confirm +set(LD_FLASH_SIZE 2097152) + +# flash reserved for the bootloader, in bytes (32 KB for now: 16 KB does not fit the full feature set) +if (NOT DEFINED LD_FLASH_BOOT_SIZE) + set(LD_FLASH_BOOT_SIZE 32768) +endif () + +function(update_board TARGET) + target_compile_definitions(${TARGET} INTERFACE + BOARD_FLASH_SIZE=${LD_FLASH_SIZE} + BOARD_FLASH_BOOT_SIZE=${LD_FLASH_BOOT_SIZE} + ) +endfunction() diff --git a/ports/rp2350/boards/intechstudio_wrid_rp2350/board.h b/ports/rp2350/boards/intechstudio_wrid_rp2350/board.h new file mode 100644 index 000000000..1e97ff159 --- /dev/null +++ b/ports/rp2350/boards/intechstudio_wrid_rp2350/board.h @@ -0,0 +1,32 @@ +#ifndef BOARD_H_ +#define BOARD_H_ + +//--------------------------------------------------------------------+ +// Neopixel +//--------------------------------------------------------------------+ + +// Number of neopixels +#define NEOPIXEL_NUMBER 16 +// Brightness from 1 to 255 +#define NEOPIXEL_BRIGHTNESS 0x08 + +// WS2812 data pin (GPIO number), no supply enable pin on this board +#define NEOPIXEL_PIN 6 + +// BOARD_FLASH_SIZE and BOARD_FLASH_BOOT_SIZE come from board.cmake + +//--------------------------------------------------------------------+ +// USB UF2 +//--------------------------------------------------------------------+ + +#define USB_VID 0x2e8a +#define USB_PID 0x1145 +#define USB_MANUFACTURER "Intech Studio" +#define USB_PRODUCT "Wrid RP2350 Bootloader" + +#define UF2_PRODUCT_NAME USB_PRODUCT +#define UF2_BOARD_ID "RP2350-WRID-v0" +#define UF2_VOLUME_LABEL "WRIDRP2350" +#define UF2_INDEX_URL "https://github.com/intechstudio/grid-fw/" + +#endif diff --git a/supported_boards.md b/supported_boards.md index 49dabe1be..ffc2404b1 100644 --- a/supported_boards.md +++ b/supported_boards.md @@ -176,6 +176,7 @@ | Board | Name | VID PID | URL | | --- | --- | --- | --- | | intechstudio_grid_rp2350 | Intech Studio Grid RP2350 Bootloader | 0x2E8A:0x1145 | https://github.com/intechstudio/grid-fw/ | +| intechstudio_wrid_rp2350 | Intech Studio Wrid RP2350 Bootloader | 0x2E8A:0x1145 | https://github.com/intechstudio/grid-fw/ | ## samd51