Scaffold bootloader project with TODO checklist

New project under bootloader/ with the same structure as
color_switcher: Dockerfile, docker-compose, modular CMake, and
APP_BOOT + SYS_ECU stub components. Reuses common/ submodules for
MCU_UART, MCU_USB, HAL_COM, STD_TYPES.

TODO.md lists all open design decisions (interface, protocol, flash
layout, entry trigger, integrity checks, flash strategy) and the
implementation tasks that depend on them.
This commit is contained in:
Mohamed Salem 2026-04-18 23:37:33 +02:00
parent b62a86bdc7
commit 4f1199a3e6
17 changed files with 506 additions and 0 deletions

View File

@ -19,6 +19,7 @@ git submodule update --init --recursive
| Project | Description | Target Board | | Project | Description | Target Board |
|---|---|---| |---|---|---|
| [color_switcher](color_switcher/) | Interactive color-switching firmware with USB-CDC serial commands and WS2812B LED control via PIO | Waveshare RP2040-Zero | | [color_switcher](color_switcher/) | Interactive color-switching firmware with USB-CDC serial commands and WS2812B LED control via PIO | Waveshare RP2040-Zero |
| [bootloader](bootloader/) | Custom firmware bootloader — receives updates over UART/USB, writes to flash, jumps to app | RP2040 (any board) |
## Shared Components (git submodules) ## Shared Components (git submodules)
@ -63,6 +64,7 @@ pico/
│ ├── MCU_PIO/ # generic PIO driver + ws2812.pio │ ├── MCU_PIO/ # generic PIO driver + ws2812.pio
│ ├── HAL_COM/ # communication abstraction │ ├── HAL_COM/ # communication abstraction
│ └── HAL_LED/ # LED strip abstraction │ └── HAL_LED/ # LED strip abstraction
├── bootloader/ # project: custom firmware bootloader
├── color_switcher/ # project: WS2812B color switcher ├── color_switcher/ # project: WS2812B color switcher
│ ├── cmake/ # modular CMake build system │ ├── cmake/ # modular CMake build system
│ ├── src/ # project-specific components (APP_CLSW, SYS_ECU) │ ├── src/ # project-specific components (APP_CLSW, SYS_ECU)

24
bootloader/CLAUDE.md Normal file
View File

@ -0,0 +1,24 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project
Custom bootloader for the Raspberry Pi Pico (RP2040). Receives firmware over a communication interface, writes to flash, and jumps to the main application.
## Build System
All builds run inside Docker — no local toolchain required.
- `docker compose run --rm pico-build bash /scripts/build.sh` — compile the firmware
- `./flash.sh bootloader` — flash to Pico (from repo root)
## Architecture
- `src/APP_BOOT/{inc,prg,cfg}/` — bootloader application logic (firmware receive, flash write, app jump)
- `src/SYS_ECU/{inc,prg,cfg}/` — system orchestrator (init sequence, main loop)
- Shared components from `common/` (STD_TYPES, MCU_UART, MCU_USB, HAL_COM, etc.)
## Conventions
Same as the repo-level CLAUDE.md — MISRA-C style, STD_TYPES, Hungarian naming, descriptive comments, no magic numbers.

23
bootloader/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
# Base image: Ubuntu 24.04 for ARM cross-compilation toolchain
FROM ubuntu:24.04
# Install cross-compilation dependencies + clangd for dev container intellisense
RUN apt-get update && apt-get install -y \
cmake \
gcc-arm-none-eabi \
libnewlib-arm-none-eabi \
build-essential \
git \
python3 \
clangd \
&& rm -rf /var/lib/apt/lists/*
# Clone the Pico SDK with submodules (TinyUSB, etc.)
RUN git clone https://github.com/raspberrypi/pico-sdk.git /opt/pico-sdk \
&& cd /opt/pico-sdk \
&& git submodule update --init
# Tell CMake where to find the Pico SDK
ENV PICO_SDK_PATH=/opt/pico-sdk
WORKDIR /project

26
bootloader/README.md Normal file
View File

@ -0,0 +1,26 @@
# Bootloader
A custom bootloader for the Raspberry Pi Pico (RP2040). Receives firmware updates over a communication interface, writes to flash, and jumps to the main application.
## Status
**Scaffolded — not yet implemented.** See `TODO.md` for the design decisions and implementation tasks.
## Prerequisites
- [Docker](https://docs.docker.com/get-docker/) and Docker Compose
## Building
```bash
cd bootloader/
docker compose run --rm pico-build bash /scripts/build.sh
```
## Flashing
From the repo root:
```bash
./flash.sh bootloader
```

61
bootloader/TODO.md Normal file
View File

@ -0,0 +1,61 @@
# Bootloader — Design Decisions & TODO
## Open Design Decisions
These must be resolved before implementation begins:
### 1. Firmware update interface
- [ ] UART only
- [ ] USB-CDC only
- [ ] Both (selectable via config or auto-detect)
- **Notes:** We have MCU_UART and MCU_USB in common/ ready to use via HAL_COM.
### 2. Transfer protocol
- [ ] Custom simple protocol (length + CRC32 + raw binary)
- [ ] XMODEM / YMODEM (standard, many terminal tools support it)
- [ ] Custom framed protocol (start byte, length, payload, CRC, end byte)
- **Notes:** Custom is simplest to implement. XMODEM is widely supported by serial tools.
### 3. Flash memory layout
- [ ] Define bootloader size (e.g., first 16 KB, 32 KB, or 64 KB of flash)
- [ ] Define application start address
- [ ] Define metadata region (app version, CRC, valid flag)
- **Notes:** RP2040 has 2 MB flash. Pico SDK linker script needs modification to place the bootloader and app at different addresses.
### 4. Bootloader entry trigger
- [ ] Always enter bootloader, wait N seconds for firmware, then jump to app
- [ ] Check a GPIO pin (button held = stay in bootloader)
- [ ] Check a magic value in flash/RAM (set by the application to request update)
- [ ] Combination: check button first, then magic value, then jump to app
- **Notes:** "Button + timeout" is the most user-friendly approach.
### 5. Application jump mechanism
- [ ] Read the app's vector table at the app start address
- [ ] Set the MSP (main stack pointer) from the vector table
- [ ] Jump to the reset handler from the vector table
- **Notes:** Standard Cortex-M0+ boot sequence. Need to disable interrupts and peripherals before jumping.
### 6. Integrity verification
- [ ] CRC32 check on the received firmware before writing to flash
- [ ] CRC32 check on the stored application before jumping (boot-time validation)
- [ ] Signature verification (stretch goal — not needed for initial version)
### 7. Flash write strategy
- [ ] Erase + write as chunks arrive (streaming)
- [ ] Buffer entire firmware in RAM, then erase + write at once (limited by RAM size)
- [ ] Dual-bank: write to alternate region, swap on success (A/B update)
- **Notes:** Streaming is most memory-efficient. A/B is safest (rollback on failure) but uses 2x flash.
## Implementation Tasks
Once design decisions are made:
- [ ] Create linker scripts (bootloader.ld, application.ld) with correct flash regions
- [ ] Implement APP_BOOT_enuInit — check entry trigger, decide boot vs update mode
- [ ] Implement firmware receive via HAL_COM (protocol parsing, CRC validation)
- [ ] Implement flash write using Pico SDK hardware_flash API
- [ ] Implement application jump (disable interrupts, set MSP, branch to reset handler)
- [ ] Implement boot-time app validation (CRC check before jumping)
- [ ] Modify color_switcher's linker script to start at the app address (not 0x10000000)
- [ ] Test: flash bootloader, then send color_switcher firmware over UART/USB
- [ ] Test: corrupt firmware scenario — bootloader should stay in update mode

View File

@ -0,0 +1,28 @@
# ============================================================================
# Top-level CMakeLists.txt for the bootloader project
# ============================================================================
# Same phase-ordered structure as color_switcher.
cmake_minimum_required(VERSION 3.13)
get_filename_component(PROJECT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_config)
include(project_config)
include(mcu_config)
mcu_init()
project(${PROJECT_NAME}
VERSION ${PROJECT_VERSION}
LANGUAGES ${PROJECT_LANGUAGES})
mcu_sdk_config()
include(sources_config)
add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_INCLUDE_DIRS})
mcu_link_target(${PROJECT_NAME})

View File

@ -0,0 +1,34 @@
# ============================================================================
# mcu_config.cmake RP2040 / Pico SDK configuration for the bootloader
# ============================================================================
# Step 1/3: called BEFORE project() bootstrap the Pico SDK toolchain
macro(mcu_init)
include(${CMAKE_SOURCE_DIR}/pico_sdk_import.cmake)
endmacro()
# Step 2/3: called AFTER project() register SDK targets
macro(mcu_sdk_config)
set(CMAKE_C_STANDARD ${PROJECT_C_STANDARD})
set(CMAKE_C_STANDARD_REQUIRED ON)
pico_sdk_init()
endmacro()
# Step 3/3: called AFTER add_executable() link SDK libs + configure target
function(mcu_link_target target)
# Libraries needed by the bootloader.
# hardware_flash is critical the bootloader writes firmware to flash.
target_link_libraries(${target} PRIVATE
pico_stdlib
hardware_uart
hardware_dma
hardware_flash
)
# Route stdio over USB-CDC for bootloader status messages
pico_enable_stdio_usb(${target} 1)
pico_enable_stdio_uart(${target} 0)
# Generate .uf2 for initial bootloader flashing via BOOTSEL
pico_add_extra_outputs(${target})
endfunction()

View File

@ -0,0 +1,11 @@
# ============================================================================
# project_config.cmake Bootloader project identity and language settings
# ============================================================================
set(PROJECT_NAME Bootloader_PICO)
set(PROJECT_VERSION 0.1.0)
set(PROJECT_LANGUAGES C CXX ASM)
set(PROJECT_C_STANDARD 11)
# Generate compile_commands.json for clangd intellisense
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

View File

@ -0,0 +1,37 @@
# ============================================================================
# sources_config.cmake Source files and include paths for the bootloader
# ============================================================================
set(COMMON_DIR "${PROJECT_ROOT_DIR}/../common")
# Collect .c files from project src/ and common submodules
file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS
"${PROJECT_ROOT_DIR}/src/*.c"
"${COMMON_DIR}/STD_TYPES/*.c"
"${COMMON_DIR}/MCU_UART/*.c"
"${COMMON_DIR}/MCU_USB/*.c"
"${COMMON_DIR}/HAL_COM/*.c")
set(PROJECT_INCLUDE_DIRS
# Common components (git submodules)
${COMMON_DIR}/STD_TYPES/inc
${COMMON_DIR}/MCU_UART/inc
${COMMON_DIR}/MCU_UART/prg
${COMMON_DIR}/MCU_UART/cfg
${COMMON_DIR}/MCU_USB/inc
${COMMON_DIR}/MCU_USB/prg
${COMMON_DIR}/MCU_USB/cfg
${COMMON_DIR}/HAL_COM/inc
${COMMON_DIR}/HAL_COM/prg
${COMMON_DIR}/HAL_COM/cfg
# Project-specific components
${PROJECT_ROOT_DIR}/src/APP_BOOT/inc
${PROJECT_ROOT_DIR}/src/APP_BOOT/prg
${PROJECT_ROOT_DIR}/src/APP_BOOT/cfg
${PROJECT_ROOT_DIR}/src/SYS_ECU/prg
)

View File

@ -0,0 +1,121 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

View File

@ -0,0 +1,13 @@
# Docker Compose for the bootloader project.
# Same structure as color_switcher — shared build script + common submodules.
services:
pico-build:
build: .
volumes:
- .:/project
- ../common:/common
- ../build.sh:/scripts/build.sh
command: sleep infinity

View File

@ -0,0 +1,9 @@
/******************************************************************************
* File: APP_BOOT_cfg.c
* Component: APP_BOOT
* Description: Configuration definitions for the bootloader application.
*
* Layer: Application - configuration
*****************************************************************************/
#include "APP_BOOT_cfg.h"

View File

@ -0,0 +1,18 @@
/******************************************************************************
* File: APP_BOOT_cfg.h
* Component: APP_BOOT
* Description: Configuration header for the bootloader application.
* Will hold flash layout addresses, timeout values,
* entry trigger config, etc.
*
* Layer: Application - configuration
*****************************************************************************/
#ifndef APP_BOOT_CFG_H
#define APP_BOOT_CFG_H
#include "STD_TYPES.h"
/* TODO: define flash layout, timeouts, entry trigger config */
#endif /* APP_BOOT_CFG_H */

View File

@ -0,0 +1,20 @@
/******************************************************************************
* File: APP_BOOT.h
* Component: APP_BOOT
* Description: Public interface for the bootloader application.
* Handles firmware receive, flash write, integrity check,
* and application jump.
*
* Layer: Application
*****************************************************************************/
#ifndef APP_BOOT_H
#define APP_BOOT_H
#include "STD_TYPES.h"
STD_tenuResult APP_BOOT_enuInit(void);
void APP_BOOT_vRunnable(void);
#endif /* APP_BOOT_H */

View File

@ -0,0 +1,27 @@
/******************************************************************************
* File: APP_BOOT_prg.c
* Component: APP_BOOT
* Description: Bootloader application logic stub implementation.
* See TODO.md for the design decisions needed before
* implementing the actual bootloader behavior.
*
* Layer: Application
*****************************************************************************/
#include "APP_BOOT.h"
#include "APP_BOOT_priv.h"
#include "APP_BOOT_cfg.h"
STD_tenuResult APP_BOOT_enuInit(void)
{
STD_tenuResult enuResultLoc = STD_OK;
/* TODO: check bootloader entry trigger (button, magic value, etc.) */
return enuResultLoc;
}
void APP_BOOT_vRunnable(void)
{
/* TODO: firmware receive state machine, flash write, app jump */
}

View File

@ -0,0 +1,15 @@
/******************************************************************************
* File: APP_BOOT_priv.h
* Component: APP_BOOT
* Description: Private header for the bootloader application.
*
* Layer: Application - internal use only
*****************************************************************************/
#ifndef APP_BOOT_PRIV_H
#define APP_BOOT_PRIV_H
#include "APP_BOOT.h"
#include "APP_BOOT_cfg.h"
#endif /* APP_BOOT_PRIV_H */

View File

@ -0,0 +1,37 @@
/******************************************************************************
* File: SYS_ECU.c
* Component: SYS_ECU
* Description: Bootloader system orchestrator. Init sequence + main loop.
*
* Layer: System (top of the stack)
*****************************************************************************/
#include "STD_TYPES.h"
#include "pico/stdlib.h"
#include "MCU_USB.h"
#include "HAL_COM.h"
#include "APP_BOOT.h"
static void vInitAll(void)
{
/* MCU layer */
MCU_USB_enuInit();
/* HAL layer */
HAL_COM_enuInit();
/* Application layer */
APP_BOOT_enuInit();
}
int main(void)
{
vInitAll();
while (1)
{
APP_BOOT_vRunnable();
sleep_ms(10);
}
}