Skip to content

RP2040/2350 FOTA Bootloader allows you to perform Firmware Over The Air updates with the Raspberry Pi Pico W board. It contains all required linker scripts that will adapt your application to the new application memory layout.

License

Notifications You must be signed in to change notification settings

Galzai/pico_fota_bootloader

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Raspberry Pi Pico FOTA Bootloader

This bootloader allows you to perform secure Firmware Over The Air (FOTA) OTA updates on RP2XXX Boards. It contains all required linker scripts that will adapt your application to the new application memory layout. This fork of the bootloader supports both RP2040 (Pico W) and RP2350 (Pico 2W) chips.

The memory layout is as follows:

+-------------------------------------------+  <-- __FLASH_START (0x10000000)
|              Bootloader (36k)             |
+-------------------------------------------+  <-- __FLASH_INFO_APP_HEADER
|             App Header (4 bytes)          |
+-------------------------------------------+  <-- __FLASH_INFO_DOWNLOAD_HEADER
|         Download Header (4 bytes)         |
+-------------------------------------------+  <-- __FLASH_INFO_IS_DOWNLOAD_SLOT_VALID
|      Is Download Slot Valid (4 bytes)     |
+-------------------------------------------+  <-- __FLASH_INFO_IS_FIRMWARE_SWAPPED
|       Is Firmware Swapped (4 bytes)       |
+-------------------------------------------+  <-- __FLASH_INFO_IS_AFTER_ROLLBACK
|        Is After Rollback (4 bytes)        |
+-------------------------------------------+  <-- __FLASH_INFO_SHOULD_ROLLBACK
|         Should Rollback (4 bytes)         |
+-------------------------------------------+
|            Padding (4072 bytes)           |
+-------------------------------------------+  <-- __FLASH_APP_START
|    Flash Application Slot (FLASH_SIZE/2)  |
+-------------------------------------------+  <-- __FLASH_DOWNLOAD_SLOT_START
|     Flash Download Slot (FLASH_SIZE/2)    |
+-------------------------------------------+

Basic usage

Basic usage can be found here.

Features

pico_fota_bootloader supports the following features:

  • SHA256 calculation- application binary FOTA image is appended with a SHA256 value

    • as a result, the <app_name>_fota_image.bin binary file will be appended with 256 bytes from which last 32 bytes will contain SHA256 of the image

    • after downloading a binary file, the user can use the pfb_firmware_sha256_check function to check if the calculated SHA256 matches the expected one

    • this option can be disabled using -DPFB_WITH_SHA256_HASHING=OFF CMake option

    • see the example for more information

  • image encryption - application binary FOTA image is encrypted using AES ECB algorithm

    • encryption/decryption key should be set using -DPFB_AES_KEY=<value> CMake option

      • both the bootloader and the future FOTA images should be compiled using the same key value, otherwise the bootloader won't be able to properly decrypt the FOTA image
    • as a result, the <app_name>_fota_image_encrypted.bin file will be created

      • if PFB_WITH_SHA256_HASHING has been enabled, a SHA256 will also be encrypted with the firmware image
    • this option can be disabled using -DPFB_WITH_IMAGE_ENCRYPTION=OFF CMake option

  • rollback mechanism - if the freshly downloaded firmware won't be committed before the very next reboot, the bootloader will perform the rollback (the firmware will be swapped back to the previous working version)

  • basic debug logging - enabled by default, can be turned off using -DPFB_WITH_BOOTLOADER_LOGS=OFF CMake option

    • debug logs can be redirected from USB to UART using -DPFB_REDIRECT_BOOTLOADER_LOGS_TO_UART=ON CMake option

Prerequisites

  • pico-sdk version >= 1.5.1

    • required for the pico_mbedtls library
  • Python 3 with the following packages: argparse, hashlib, os, Cryptodome

    • required for the SHA256 calculation and AES ECB image encryption

Example

your_project/CMakeLists.txt

This fork of the bootloader was only tested using FetchContent.

cmake_minimum_required(VERSION 3.13)

# initialize the SDK based on PICO_SDK_PATH
# note: this must happen before project()
include(pico_sdk_import.cmake)

include(FetchContent)
FetchContent_Declare(
  pico_fota_bootloader
  GIT_REPOSITORY https://github.com/galzai/pico_fota_bootloader.git
  GIT_TAG main  # or specific tag/commit
)
FetchContent_MakeAvailable(pico_fota_bootloader)

...
pico_sdk_init()

add_subdirectory(pico_fota_bootloader)

add_executable(your_app
               main.c)
target_link_libraries(your_app
                      pico_stdlib
                      pico_fota_bootloader_lib)
pfb_compile_with_bootloader(your_app)
pico_add_extra_outputs(your_app)
# rest of the file if needed...

Custom Bootloader Event Handlers

You can optionally define the following functions in your application to hook into the bootloader process. Include pico_fota_bootloader_handlers.h in your project.

If you're consuming this repo via FetchContent and building the bootloader as a separate binary, provide your handler implementations to the bootloader at configure time by passing your source file(s) via the PFB_BOOTLOADER_HANDLERS_SRCS cache variable (semicolon-separated list):

-DPFB_BOOTLOADER_HANDLERS_SRCS="/abs/path/my_bootloader_handlers.c"

These source files will be compiled into the bootloader, overriding the default weak handlers.

#include <pico_fota_bootloader_handlers.h>

// Called at the very start of the bootloader
void on_bootloader_started(void);

// Called at the end of the bootloader
// status is a boot_status_t enum:
//   BOOT_STATUS_OK        - Normal boot, nothing swapped
//   BOOT_STATUS_SWAP      - Firmware swapped
//   BOOT_STATUS_ROLLBACK  - Rollback performed
void on_boot_completed(boot_status_t status);

// Called during flash swap operation
// current_sector - Current sector being swapped (0 to total_sectors-1)
// total_sectors  - Total number of sectors to swap
void on_flash_operation_progress(uint32_t current_sector, uint32_t total_sectors);

If you do not provide custom sources, the bootloader will use default empty handlers.

your_project/main.c

#include <pico_fota_bootloader.h>
...
int main() {
    ...
    if (pfb_is_after_firmware_update()) {
        // handle new firmare info if needed
    }
    if (pfb_is_after_rollback()) {
        // handle performed rollback if needed
    }
    ...

    // commit the new firmware - otherwise after the next reboot the rollback
    // will be performed
    pfb_firmware_commit();
    ...

    // initialize download slot before writing into it
    pfb_initialize_download_slot();
    ...

    // acquire the data (e.g. from the web) and write it into the download slot
    // using chunks of N*256 bytes
    for (int i = 0; i < size; i++) {
        if (pfb_write_to_flash_aligned_256_bytes(src, offset_bytes, len_bytes)) {
            // handle error if needed
            while (1);
        }
    }
    ...

    size_t firmware_size = offset_bytes;
    if (pfb_firmware_sha256_check(firmware_size)) {
        // handle the SHA256 error/mismatch if needed
        while (1);
    }

    // once the binary file has been successfully downloaded, mark the download
    // slot as valid - the firmware will be swapped after a reboot
    pfb_mark_download_slot_as_valid();
    ...

    // when you're ready - reboot and perform the upgrade
    pfb_perform_update();

    /* code unreachable */
}

Note that while testing it seems that using FreeRTOS SMP may not fully work as expected. As such it is reccomended to pin the update code to it's own core without sharing it.

Compiling and running

Compiling

Create the build directory and build the project within it.

# these commands may vary depending on the OS
mkdir build/
cd build
cmake -DPFB_AES_KEY="<your_key_value>" .. && make -j

You should have output similar to:

build/
├── _deps\pico_fota_bootloader-build
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   ├── libpico_fota_bootloader_lib.a
│   ├── Makefile
│   ├── pico_fota_bootloader.bin
│   ├── pico_fota_bootloader.dis
│   ├── pico_fota_bootloader.elf
│   ├── pico_fota_bootloader.elf.map
│   ├── pico_fota_bootloader.hex
│   └── pico_fota_bootloader.uf2
└── your_app
    ├── CMakeFiles
    ├── cmake_install.cmake
    ├── Makefile
    ├── your_app.bin
    ├── your_app.dis
    ├── your_app.elf
    ├── your_app.elf.map
    ├── your_app_fota_image.bin
    ├── your_app_fota_image_encrypted.bin
    ├── your_app.hex
    └── your_app.uf2

Running

Set Pico W to the BOOTSEL state (by powering it up with the BOOTSEL button or using picotool reboot -u -f. pressed) and copy the pico_fota_bootloader.uf2 file into it. Right now the Pico W is flashed with the bootloader but does not have proper application in the application FLASH memory slot. Then, set Pico W to the BOOTSEL state again and copy the your_app.uf2 file. The board should reboot and start your_app application.

NOTE: you can also look at the serial output logs to monitor the application state.

Performing the firmware update (OTA)

To perform a firmware update (over the air), a your_app_fota_image_encrypted.bin file (or your_app_fota_image.bin file in case of setting the -DPFB_WITH_IMAGE_ENCRYPTION=OFF CMake option) should be sent to or downloaded by the Pico W. Note that while rebuilding the application, the linker scripts' contents should not be changed or should be changed carefully to maintain the memory layout backward compatibility.

About

RP2040/2350 FOTA Bootloader allows you to perform Firmware Over The Air updates with the Raspberry Pi Pico W board. It contains all required linker scripts that will adapt your application to the new application memory layout.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Linker Script 45.1%
  • C 35.4%
  • CMake 13.1%
  • Python 6.4%