Kompletter OTA Flash-Prozess mit Server (hab zwischencommits vergessen ups)

This commit is contained in:
2026-05-15 23:33:10 +02:00
parent 5d9d3e205b
commit ad05cef1bf
16 changed files with 1797 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(app C CXX ASM)
pico_sdk_init()
# Zwei Varianten aus derselben Quelle: schnell- und langsam-blinkend.
# Beide werden ueber LED_DELAY_MS parametrisiert.
function(make_app_variant target_name delay_ms)
add_executable(${target_name} app.c)
target_compile_definitions(${target_name} PRIVATE LED_DELAY_MS=${delay_ms})
pico_set_linker_script(${target_name} ${CMAKE_CURRENT_LIST_DIR}/memmap_app.ld)
target_link_libraries(${target_name}
pico_stdlib
pico_cyw43_arch_none
)
pico_add_extra_outputs(${target_name})
endfunction()
make_app_variant(app_fast 80)
make_app_variant(app_slow 600)
+18
View File
@@ -0,0 +1,18 @@
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#ifndef LED_DELAY_MS
#define LED_DELAY_MS 80
#endif
int main(void) {
if (cyw43_arch_init()) {
return -1;
}
while (true) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
sleep_ms(LED_DELAY_MS);
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
sleep_ms(LED_DELAY_MS);
}
}
+303
View File
@@ -0,0 +1,303 @@
/* Based on GCC ARM embedded samples.
Defines the following symbols for use by code:
__exidx_start
__exidx_end
__etext
__data_start__
__preinit_array_start
__preinit_array_end
__init_array_start
__init_array_end
__fini_array_start
__fini_array_end
__data_end__
__bss_start__
__bss_end__
__end__
end
__HeapLimit
__StackLimit
__StackTop
__stack (== StackTop)
*/
MEMORY
{
/* App liegt hinter dem 512 KB grossen Bootloader-Slot. */
FLASH(rx) : ORIGIN = 0x10080000, LENGTH = 4M - 512k
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k
SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k
SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k
}
ENTRY(_entry_point)
SECTIONS
{
.flash_begin : {
__flash_binary_start = .;
} > FLASH
/* The bootrom will enter the image at the point indicated in your
IMAGE_DEF, which is usually the reset handler of your vector table.
The debugger will use the ELF entry point, which is the _entry_point
symbol, and in our case is *different from the bootrom's entry point.*
This is used to go back through the bootrom on debugger launches only,
to perform the same initial flash setup that would be performed on a
cold boot.
*/
.text : {
__logical_binary_start = .;
KEEP (*(.vectors))
KEEP (*(.binary_info_header))
__binary_info_header_end = .;
KEEP (*(.embedded_block))
__embedded_block_end = .;
KEEP (*(.reset))
/* TODO revisit this now memset/memcpy/float in ROM */
/* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from
* FLASH ... we will include any thing excluded here in .data below by default */
*(.init)
*libgcc.a:cmse_nonsecure_call.o
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)
*(.fini)
/* Pull all c'tors into .text */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* Followed by destructors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(SORT(.preinit_array.*)))
KEEP(*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
*(SORT(.fini_array.*))
*(.fini_array)
PROVIDE_HIDDEN (__fini_array_end = .);
*(.eh_frame*)
. = ALIGN(4);
} > FLASH
/* Note the boot2 section is optional, and should be discarded if there is
no reference to it *inside* the binary, as it is not called by the
bootrom. (The bootrom performs a simple best-effort XIP setup and
leaves it to the binary to do anything more sophisticated.) However
there is still a size limit of 256 bytes, to ensure the boot2 can be
stored in boot RAM.
Really this is a "XIP setup function" -- the name boot2 is historic and
refers to its dual-purpose on RP2040, where it also handled vectoring
from the bootrom into the user image.
*/
.boot2 : {
__boot2_start__ = .;
*(.boot2)
__boot2_end__ = .;
} > FLASH
ASSERT(__boot2_end__ - __boot2_start__ <= 256,
"ERROR: Pico second stage bootloader must be no more than 256 bytes in size")
.rodata : {
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*)
*(.srodata*)
. = ALIGN(4);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
. = ALIGN(4);
} > FLASH
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > FLASH
__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > FLASH
__exidx_end = .;
/* Machine inspectable binary information */
. = ALIGN(4);
__binary_info_start = .;
.binary_info :
{
KEEP(*(.binary_info.keep.*))
*(.binary_info.*)
} > FLASH
__binary_info_end = .;
. = ALIGN(4);
.ram_vector_table (NOLOAD): {
*(.ram_vector_table)
} > RAM
.uninitialized_data (NOLOAD): {
. = ALIGN(4);
*(.uninitialized_data*)
} > RAM
.data : {
__data_start__ = .;
*(vtable)
*(.time_critical*)
/* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */
*(.text*)
. = ALIGN(4);
*(.rodata*)
. = ALIGN(4);
*(.data*)
*(.sdata*)
. = ALIGN(4);
*(.after_data.*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__mutex_array_start = .);
KEEP(*(SORT(.mutex_array.*)))
KEEP(*(.mutex_array))
PROVIDE_HIDDEN (__mutex_array_end = .);
*(.jcr)
. = ALIGN(4);
} > RAM AT> FLASH
.tdata : {
. = ALIGN(4);
*(.tdata .tdata.* .gnu.linkonce.td.*)
/* All data end */
__tdata_end = .;
} > RAM AT> FLASH
PROVIDE(__data_end__ = .);
/* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */
__etext = LOADADDR(.data);
.tbss (NOLOAD) : {
. = ALIGN(4);
__bss_start__ = .;
__tls_base = .;
*(.tbss .tbss.* .gnu.linkonce.tb.*)
*(.tcommon)
__tls_end = .;
} > RAM
.bss (NOLOAD) : {
. = ALIGN(4);
__tbss_end = .;
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
*(COMMON)
PROVIDE(__global_pointer$ = . + 2K);
*(.sbss*)
. = ALIGN(4);
__bss_end__ = .;
} > RAM
.heap (NOLOAD):
{
__end__ = .;
end = __end__;
KEEP(*(.heap*))
} > RAM
/* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however
to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */
__HeapLimit = ORIGIN(RAM) + LENGTH(RAM);
/* Start and end symbols must be word-aligned */
.scratch_x : {
__scratch_x_start__ = .;
*(.scratch_x.*)
. = ALIGN(4);
__scratch_x_end__ = .;
} > SCRATCH_X AT > FLASH
__scratch_x_source__ = LOADADDR(.scratch_x);
.scratch_y : {
__scratch_y_start__ = .;
*(.scratch_y.*)
. = ALIGN(4);
__scratch_y_end__ = .;
} > SCRATCH_Y AT > FLASH
__scratch_y_source__ = LOADADDR(.scratch_y);
/* .stack*_dummy section doesn't contains any symbols. It is only
* used for linker to calculate size of stack sections, and assign
* values to stack symbols later
*
* stack1 section may be empty/missing if platform_launch_core1 is not used */
/* by default we put core 0 stack at the end of scratch Y, so that if core 1
* stack is not used then all of SCRATCH_X is free.
*/
.stack1_dummy (NOLOAD):
{
*(.stack1*)
} > SCRATCH_X
.stack_dummy (NOLOAD):
{
KEEP(*(.stack*))
} > SCRATCH_Y
.flash_end : {
KEEP(*(.embedded_end_block*))
PROVIDE(__flash_binary_end = .);
} > FLASH =0xaa
/* stack limit is poorly named, but historically is maximum heap ptr */
__StackLimit = ORIGIN(RAM) + LENGTH(RAM);
__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
__StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
__StackBottom = __StackTop - SIZEOF(.stack_dummy);
PROVIDE(__stack = __StackTop);
/* picolibc and LLVM */
PROVIDE (__heap_start = __end__);
PROVIDE (__heap_end = __HeapLimit);
PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) );
PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1));
PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) );
/* llvm-libc */
PROVIDE (_end = __end__);
PROVIDE (__llvm_libc_heap_limit = __HeapLimit);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")
ASSERT( __binary_info_header_end - __logical_binary_start <= 1024, "Binary info must be in first 1024 bytes of the binary")
ASSERT( __embedded_block_end - __logical_binary_start <= 4096, "Embedded block must be in first 4096 bytes of the binary")
/* todo assert on extra code */
}
+73
View File
@@ -0,0 +1,73 @@
# 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()
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 ()
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")
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 ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
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})
+41
View File
@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(bootloader C CXX ASM)
pico_sdk_init()
if (NOT WIFI_SSID OR NOT WIFI_PASSWORD)
message(FATAL_ERROR "Bitte WIFI_SSID und WIFI_PASSWORD per -D setzen")
endif()
if (NOT PICO_ID)
message(FATAL_ERROR "Bitte PICO_ID per -DPICO_ID=<n> setzen")
endif()
add_executable(bootloader
bootloader.c
)
target_compile_definitions(bootloader PRIVATE
WIFI_SSID=\"${WIFI_SSID}\"
WIFI_PASSWORD=\"${WIFI_PASSWORD}\"
PICO_ID=${PICO_ID}
)
target_include_directories(bootloader PRIVATE ${CMAKE_CURRENT_LIST_DIR})
pico_set_linker_script(bootloader ${CMAKE_CURRENT_LIST_DIR}/memmap_bootloader.ld)
target_link_libraries(bootloader
pico_stdlib
pico_cyw43_arch_lwip_threadsafe_background
pico_sha256
hardware_flash
hardware_sync
)
pico_enable_stdio_usb(bootloader 1)
pico_enable_stdio_uart(bootloader 0)
pico_add_extra_outputs(bootloader)
+467
View File
@@ -0,0 +1,467 @@
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "pico/sha256.h"
#include "hardware/flash.h"
#include "hardware/sync.h"
#include "hardware/structs/scb.h"
#include "lwip/pbuf.h"
#include "lwip/tcp.h"
// ---- OTA-Wire-Protokoll (siehe server/server.py) ----
#define OTA_MAGIC 0xC0FFEE01u
#define OTA_STATUS_NONE 0x00u
#define OTA_STATUS_UPDATE 0x01u
#define SERVER_IP "192.168.161.25"
#define SERVER_PORT 9000
// ---- Flash-Layout ----
#define APP_BASE 0x10080000u
#define APP_FLASH_OFFSET (APP_BASE - XIP_BASE) // = 0x80000, fuer flash_range_*
#define APP_SLOT_SIZE (3584u * 1024u) // 3.5 MB, muss zum Server passen
#define SHA256_BYTES 32
#ifndef PICO_ID
#error "Bitte PICO_ID per -DPICO_ID=<n> setzen"
#endif
// ---------- Hilfen ----------
static void print_hash(const char *label, const uint8_t *hash) {
printf("%s ", label);
for (int i = 0; i < SHA256_BYTES; i++) {
printf("%02x", hash[i]);
}
printf("\n");
}
static bool app_present(uint32_t base) {
uint32_t sp = *(volatile uint32_t *)base;
return sp > 0x20000000u && sp <= 0x20082000u;
}
__attribute__((noreturn))
static void jump_to_app(uint32_t base) {
uint32_t sp = *(volatile uint32_t *)base;
uint32_t reset = *(volatile uint32_t *)(base + 4u);
cyw43_arch_deinit();
__asm volatile ("cpsid i" ::: "memory");
scb_hw->vtor = base;
__asm volatile ("dsb 0xf" ::: "memory");
__asm volatile ("isb 0xf" ::: "memory");
__asm volatile (
"msr msp, %0\n\t"
"bx %1\n"
:: "r" (sp), "r" (reset)
: "memory"
);
__builtin_unreachable();
}
__attribute__((noreturn))
static void halt_with_led(void) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
while (true) {
tight_loop_contents();
}
}
// ---------- Flash-Operationen mit IRQ-Maskierung ----------
static void flash_erase_sector(uint32_t flash_offset) {
uint32_t irq = save_and_disable_interrupts();
flash_range_erase(flash_offset, FLASH_SECTOR_SIZE);
restore_interrupts(irq);
}
static void flash_program_page(uint32_t flash_offset, const uint8_t *data) {
uint32_t irq = save_and_disable_interrupts();
flash_range_program(flash_offset, data, FLASH_PAGE_SIZE);
restore_interrupts(irq);
}
// ---------- App-Slot hashen ----------
static bool hash_app_slot(uint8_t out[SHA256_BYTES]) {
pico_sha256_state_t st;
if (pico_sha256_start_blocking(&st, SHA256_BIG_ENDIAN, true) != PICO_OK) {
printf("pico_sha256_start fehlgeschlagen\n");
return false;
}
pico_sha256_update_blocking(&st, (const uint8_t *)APP_BASE, APP_SLOT_SIZE);
sha256_result_t result;
pico_sha256_finish(&st, &result);
memcpy(out, result.bytes, SHA256_BYTES);
return true;
}
// ---------- OTA-Client ----------
typedef enum {
OTA_PHASE_REQUEST, // Request raus, warte auf Status-Byte
OTA_PHASE_HEADER, // Status=Update gelesen, sammle size + new_hash (36 Byte)
OTA_PHASE_PAYLOAD, // sammle payload (size Byte) — und schreibe ins Flash
OTA_PHASE_DONE,
OTA_PHASE_ERROR,
} ota_phase_t;
typedef struct {
struct tcp_pcb *pcb;
ip_addr_t remote;
ota_phase_t phase;
uint8_t request[40];
// Update-Header
uint32_t expected_size;
uint8_t new_hash[SHA256_BYTES];
uint32_t header_have;
uint8_t header_buf[4 + SHA256_BYTES];
// Payload + Flash
uint32_t payload_have;
uint8_t page_buf[FLASH_PAGE_SIZE];
uint32_t page_filled;
volatile bool done;
bool update_pending;
} ota_t;
static void ota_finish(ota_t *o) {
if (o->pcb) {
tcp_arg(o->pcb, NULL);
tcp_recv(o->pcb, NULL);
tcp_err(o->pcb, NULL);
tcp_close(o->pcb);
o->pcb = NULL;
}
o->done = true;
}
// Schreibt eine voll gefuellte page_buf an den Slot. Erase passiert genau dann,
// wenn das die erste Page eines neuen Sektors ist.
static void flush_page_to_flash(ota_t *o, uint32_t offset_in_slot) {
uint32_t flash_off = APP_FLASH_OFFSET + offset_in_slot;
if ((offset_in_slot % FLASH_SECTOR_SIZE) == 0) {
flash_erase_sector(flash_off);
}
flash_program_page(flash_off, o->page_buf);
}
static void ota_handle_bytes(ota_t *o, const uint8_t *data, uint16_t len) {
uint16_t i = 0;
while (i < len && o->phase != OTA_PHASE_DONE && o->phase != OTA_PHASE_ERROR) {
if (o->phase == OTA_PHASE_REQUEST) {
uint8_t status = data[i++];
printf("<- status=0x%02x\n", status);
if (status == OTA_STATUS_NONE) {
o->phase = OTA_PHASE_DONE;
break;
}
if (status != OTA_STATUS_UPDATE) {
printf("unerwartetes Statusbyte\n");
o->phase = OTA_PHASE_ERROR;
break;
}
o->update_pending = true;
o->phase = OTA_PHASE_HEADER;
o->header_have = 0;
} else if (o->phase == OTA_PHASE_HEADER) {
uint16_t take = (uint16_t)(sizeof(o->header_buf) - o->header_have);
if (take > (uint16_t)(len - i)) take = (uint16_t)(len - i);
memcpy(&o->header_buf[o->header_have], &data[i], take);
o->header_have += take;
i += take;
if (o->header_have == sizeof(o->header_buf)) {
memcpy(&o->expected_size, &o->header_buf[0], 4);
memcpy(o->new_hash, &o->header_buf[4], SHA256_BYTES);
printf("Update-Header: size=%u\n", (unsigned)o->expected_size);
print_hash("new_hash:", o->new_hash);
if (o->expected_size == 0 || o->expected_size > APP_SLOT_SIZE) {
printf("Size ausserhalb gueltigem Bereich\n");
o->phase = OTA_PHASE_ERROR;
break;
}
o->phase = OTA_PHASE_PAYLOAD;
o->payload_have = 0;
o->page_filled = 0;
}
} else if (o->phase == OTA_PHASE_PAYLOAD) {
uint32_t rest_total = o->expected_size - o->payload_have;
uint32_t rest_chunk = (uint32_t)(len - i);
uint32_t free_in_page = FLASH_PAGE_SIZE - o->page_filled;
uint32_t take = rest_chunk;
if (take > rest_total) take = rest_total;
if (take > free_in_page) take = free_in_page;
memcpy(&o->page_buf[o->page_filled], &data[i], take);
o->page_filled += take;
o->payload_have += take;
i += (uint16_t)take;
bool page_done = (o->page_filled == FLASH_PAGE_SIZE);
bool last_byte = (o->payload_have == o->expected_size);
if (page_done || last_byte) {
uint32_t offset_in_slot = o->payload_have - o->page_filled;
if (last_byte && o->page_filled < FLASH_PAGE_SIZE) {
// Letzte Page: mit 0xFF auffuellen, damit der Slot-Hash passt
memset(&o->page_buf[o->page_filled], 0xFF,
FLASH_PAGE_SIZE - o->page_filled);
}
flush_page_to_flash(o, offset_in_slot);
// Fortschritt alle 64 KB als Lebenszeichen
if (((o->payload_have & 0xFFFF) == 0) || last_byte) {
printf(" geschrieben: %u/%u Byte\n",
(unsigned)o->payload_have, (unsigned)o->expected_size);
}
o->page_filled = 0;
if (last_byte) {
o->phase = OTA_PHASE_DONE;
break;
}
}
}
}
}
static err_t on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
ota_t *o = (ota_t *)arg;
if (!p) {
if (o->phase == OTA_PHASE_PAYLOAD && o->payload_have < o->expected_size) {
printf("Verbindung zu frueh geschlossen (%u/%u)\n",
(unsigned)o->payload_have, (unsigned)o->expected_size);
o->phase = OTA_PHASE_ERROR;
} else if (o->phase != OTA_PHASE_DONE) {
o->phase = OTA_PHASE_DONE;
}
ota_finish(o);
return ERR_OK;
}
struct pbuf *q = p;
while (q) {
ota_handle_bytes(o, (const uint8_t *)q->payload, q->len);
q = q->next;
}
tcp_recved(pcb, p->tot_len);
pbuf_free(p);
if (o->phase == OTA_PHASE_DONE || o->phase == OTA_PHASE_ERROR) {
ota_finish(o);
}
return ERR_OK;
}
static void on_err(void *arg, err_t err) {
ota_t *o = (ota_t *)arg;
printf("TCP-Fehler: %d\n", err);
o->pcb = NULL;
o->phase = OTA_PHASE_ERROR;
o->done = true;
}
static err_t on_connected(void *arg, struct tcp_pcb *pcb, err_t err) {
ota_t *o = (ota_t *)arg;
if (err != ERR_OK) {
printf("connect-callback err=%d\n", err);
o->phase = OTA_PHASE_ERROR;
ota_finish(o);
return err;
}
err_t w = tcp_write(pcb, o->request, sizeof(o->request), TCP_WRITE_FLAG_COPY);
if (w != ERR_OK) {
printf("tcp_write err=%d\n", w);
o->phase = OTA_PHASE_ERROR;
ota_finish(o);
return w;
}
tcp_output(pcb);
return ERR_OK;
}
// Statisch, damit das halbe KB nicht jedesmal auf dem Stack steht.
static ota_t g_ota;
static void run_ota_request(const uint8_t current_hash[SHA256_BYTES],
bool *update_was_received,
uint32_t *received_size,
uint8_t out_new_hash[SHA256_BYTES],
bool *failed) {
*update_was_received = false;
*received_size = 0;
*failed = false;
ota_t *o = &g_ota;
memset(o, 0, sizeof(*o));
if (!ip4addr_aton(SERVER_IP, &o->remote)) {
printf("ungueltige Server-IP\n");
*failed = true;
return;
}
uint32_t magic = OTA_MAGIC;
uint32_t pid = (uint32_t)PICO_ID;
memcpy(&o->request[0], &magic, 4);
memcpy(&o->request[4], &pid, 4);
memcpy(&o->request[8], current_hash, SHA256_BYTES);
o->pcb = tcp_new_ip_type(IP_GET_TYPE(&o->remote));
if (!o->pcb) {
printf("tcp_new fehlgeschlagen\n");
*failed = true;
return;
}
tcp_arg(o->pcb, o);
tcp_recv(o->pcb, on_recv);
tcp_err(o->pcb, on_err);
o->phase = OTA_PHASE_REQUEST;
printf("Verbinde TCP %s:%d\n", SERVER_IP, SERVER_PORT);
cyw43_arch_lwip_begin();
err_t err = tcp_connect(o->pcb, &o->remote, SERVER_PORT, on_connected);
cyw43_arch_lwip_end();
if (err != ERR_OK) {
printf("tcp_connect err=%d\n", err);
ota_finish(o);
*failed = true;
return;
}
// 60 s Gesamttimeout. Reicht fuer 260 KB Download inkl. ein paar Erase-Pausen.
const uint32_t TIMEOUT_MS = 60000;
uint32_t waited = 0;
while (!o->done && waited < TIMEOUT_MS) {
sleep_ms(50);
waited += 50;
}
if (!o->done) {
printf("OTA-Timeout, breche ab\n");
ota_finish(o);
*failed = true;
return;
}
if (o->phase == OTA_PHASE_ERROR) {
*failed = true;
return;
}
*update_was_received = o->update_pending;
if (o->update_pending) {
*received_size = o->expected_size;
memcpy(out_new_hash, o->new_hash, SHA256_BYTES);
}
}
// ---------- Nach-Download: Tail-Erase + Hash-Verify ----------
// Erased alle Sektoren ab `start_offset` (im Slot, relativ) bis Slot-Ende.
static void erase_tail(uint32_t start_offset) {
// Auf Sektor-Grenze hoch runden.
uint32_t aligned = (start_offset + FLASH_SECTOR_SIZE - 1) & ~(FLASH_SECTOR_SIZE - 1);
uint32_t total_sectors = APP_SLOT_SIZE / FLASH_SECTOR_SIZE;
uint32_t first_sector = aligned / FLASH_SECTOR_SIZE;
printf("Tail-Erase: Sektoren %u..%u (= %u KB)\n",
(unsigned)first_sector, (unsigned)(total_sectors - 1),
(unsigned)((total_sectors - first_sector) * FLASH_SECTOR_SIZE / 1024));
for (uint32_t s = first_sector; s < total_sectors; s++) {
flash_erase_sector(APP_FLASH_OFFSET + s * FLASH_SECTOR_SIZE);
// Optisches Feedback: LED toggeln alle 32 Sektoren (~ alle 1.4 s).
if ((s & 0x1F) == 0) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, (s & 0x20) != 0);
}
}
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
printf("Tail-Erase fertig\n");
}
// ---------- main ----------
int main(void) {
stdio_init_all();
sleep_ms(2500);
printf("\n=== Bootloader Etappe C (pico_id=%d) ===\n", (int)PICO_ID);
if (cyw43_arch_init()) {
printf("cyw43_arch_init fehlgeschlagen\n");
halt_with_led();
}
cyw43_arch_enable_sta_mode();
uint8_t current_hash[SHA256_BYTES];
printf("Hashe App-Slot (%u Byte)...\n", (unsigned)APP_SLOT_SIZE);
absolute_time_t t0 = get_absolute_time();
if (!hash_app_slot(current_hash)) {
halt_with_led();
}
int64_t took = absolute_time_diff_us(t0, get_absolute_time()) / 1000;
printf("SHA256 fertig in %lld ms\n", took);
print_hash("current:", current_hash);
printf("Verbinde mit WLAN \"%s\"...\n", WIFI_SSID);
if (cyw43_arch_wifi_connect_timeout_ms(
WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 15000) != 0) {
printf("WLAN-Verbindung fehlgeschlagen — boote alte App\n");
goto boot;
}
printf("WLAN OK\n");
bool update_received = false;
bool fail = false;
uint32_t received_size = 0;
uint8_t expected_new_hash[SHA256_BYTES];
run_ota_request(current_hash, &update_received, &received_size,
expected_new_hash, &fail);
if (fail) {
printf("OTA-Request fehlgeschlagen — boote alte App\n");
goto boot;
}
if (!update_received) {
printf("Server: kein Update\n");
goto boot;
}
// Update wurde komplett ins Flash geschrieben. Jetzt Tail erasen
// und den ganzen Slot neu hashen.
erase_tail(received_size);
uint8_t post_hash[SHA256_BYTES];
printf("Verifiziere durch erneutes Hashen des Slots...\n");
t0 = get_absolute_time();
if (!hash_app_slot(post_hash)) {
printf("Post-Hash fehlgeschlagen — bleibe stehen\n");
halt_with_led();
}
took = absolute_time_diff_us(t0, get_absolute_time()) / 1000;
printf("Post-Hash fertig in %lld ms\n", took);
print_hash("got: ", post_hash);
print_hash("expected:", expected_new_hash);
if (memcmp(post_hash, expected_new_hash, SHA256_BYTES) != 0) {
printf("HASH-MISMATCH nach Flash-Write — bleibe stehen, springe nicht.\n");
halt_with_led();
}
printf("Update verifiziert. \\o/\n");
boot:
if (!app_present(APP_BASE)) {
printf("Kein gueltiges App-Image bei 0x%08x — bleibe stehen\n",
(unsigned)APP_BASE);
halt_with_led();
}
printf("Springe in die App...\n");
sleep_ms(100);
jump_to_app(APP_BASE);
}
+46
View File
@@ -0,0 +1,46 @@
#ifndef _LWIPOPTS_H
#define _LWIPOPTS_H
// lwIP-Konfig fuer pico_cyw43_arch_lwip_threadsafe_background.
// Werte uebernommen aus pico-examples/pico_w/wifi/lwipopts_examples_common.h.
#define NO_SYS 1
#define LWIP_SOCKET 0
#define MEM_LIBC_MALLOC 0
#define MEM_ALIGNMENT 4
#define MEM_SIZE 4000
#define MEMP_NUM_TCP_SEG 32
#define MEMP_NUM_ARP_QUEUE 10
#define PBUF_POOL_SIZE 24
#define LWIP_ARP 1
#define LWIP_ETHERNET 1
#define LWIP_ICMP 1
#define LWIP_RAW 1
#define LWIP_DHCP 1
#define LWIP_IPV4 1
#define LWIP_TCP 1
#define LWIP_UDP 1
#define LWIP_DNS 1
#define LWIP_TCP_KEEPALIVE 1
#define LWIP_NETCONN 0
#define TCP_MSS 1460
#define TCP_WND (8 * TCP_MSS)
#define TCP_SND_BUF (8 * TCP_MSS)
#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS))
#define LWIP_NETIF_STATUS_CALLBACK 1
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETIF_TX_SINGLE_PBUF 1
#define DHCP_DOES_ARP_CHECK 0
#define LWIP_DHCP_DOES_ACD_CHECK 0
#define LWIP_CHKSUM_ALGORITHM 3
#define MEM_STATS 0
#define SYS_STATS 0
#define MEMP_STATS 0
#define LINK_STATS 0
#endif
+304
View File
@@ -0,0 +1,304 @@
/* Based on GCC ARM embedded samples.
Defines the following symbols for use by code:
__exidx_start
__exidx_end
__etext
__data_start__
__preinit_array_start
__preinit_array_end
__init_array_start
__init_array_end
__fini_array_start
__fini_array_end
__data_end__
__bss_start__
__bss_end__
__end__
end
__HeapLimit
__StackLimit
__StackTop
__stack (== StackTop)
*/
MEMORY
{
/* Bootloader sitzt am Flash-Anfang und bekommt fix 512 KB.
Alles ab 0x10080000 gehoert der App. */
FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 512k
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k
SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k
SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k
}
ENTRY(_entry_point)
SECTIONS
{
.flash_begin : {
__flash_binary_start = .;
} > FLASH
/* The bootrom will enter the image at the point indicated in your
IMAGE_DEF, which is usually the reset handler of your vector table.
The debugger will use the ELF entry point, which is the _entry_point
symbol, and in our case is *different from the bootrom's entry point.*
This is used to go back through the bootrom on debugger launches only,
to perform the same initial flash setup that would be performed on a
cold boot.
*/
.text : {
__logical_binary_start = .;
KEEP (*(.vectors))
KEEP (*(.binary_info_header))
__binary_info_header_end = .;
KEEP (*(.embedded_block))
__embedded_block_end = .;
KEEP (*(.reset))
/* TODO revisit this now memset/memcpy/float in ROM */
/* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from
* FLASH ... we will include any thing excluded here in .data below by default */
*(.init)
*libgcc.a:cmse_nonsecure_call.o
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)
*(.fini)
/* Pull all c'tors into .text */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* Followed by destructors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(SORT(.preinit_array.*)))
KEEP(*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
*(SORT(.fini_array.*))
*(.fini_array)
PROVIDE_HIDDEN (__fini_array_end = .);
*(.eh_frame*)
. = ALIGN(4);
} > FLASH
/* Note the boot2 section is optional, and should be discarded if there is
no reference to it *inside* the binary, as it is not called by the
bootrom. (The bootrom performs a simple best-effort XIP setup and
leaves it to the binary to do anything more sophisticated.) However
there is still a size limit of 256 bytes, to ensure the boot2 can be
stored in boot RAM.
Really this is a "XIP setup function" -- the name boot2 is historic and
refers to its dual-purpose on RP2040, where it also handled vectoring
from the bootrom into the user image.
*/
.boot2 : {
__boot2_start__ = .;
*(.boot2)
__boot2_end__ = .;
} > FLASH
ASSERT(__boot2_end__ - __boot2_start__ <= 256,
"ERROR: Pico second stage bootloader must be no more than 256 bytes in size")
.rodata : {
*(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*)
*(.srodata*)
. = ALIGN(4);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
. = ALIGN(4);
} > FLASH
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > FLASH
__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > FLASH
__exidx_end = .;
/* Machine inspectable binary information */
. = ALIGN(4);
__binary_info_start = .;
.binary_info :
{
KEEP(*(.binary_info.keep.*))
*(.binary_info.*)
} > FLASH
__binary_info_end = .;
. = ALIGN(4);
.ram_vector_table (NOLOAD): {
*(.ram_vector_table)
} > RAM
.uninitialized_data (NOLOAD): {
. = ALIGN(4);
*(.uninitialized_data*)
} > RAM
.data : {
__data_start__ = .;
*(vtable)
*(.time_critical*)
/* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */
*(.text*)
. = ALIGN(4);
*(.rodata*)
. = ALIGN(4);
*(.data*)
*(.sdata*)
. = ALIGN(4);
*(.after_data.*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__mutex_array_start = .);
KEEP(*(SORT(.mutex_array.*)))
KEEP(*(.mutex_array))
PROVIDE_HIDDEN (__mutex_array_end = .);
*(.jcr)
. = ALIGN(4);
} > RAM AT> FLASH
.tdata : {
. = ALIGN(4);
*(.tdata .tdata.* .gnu.linkonce.td.*)
/* All data end */
__tdata_end = .;
} > RAM AT> FLASH
PROVIDE(__data_end__ = .);
/* __etext is (for backwards compatibility) the name of the .data init source pointer (...) */
__etext = LOADADDR(.data);
.tbss (NOLOAD) : {
. = ALIGN(4);
__bss_start__ = .;
__tls_base = .;
*(.tbss .tbss.* .gnu.linkonce.tb.*)
*(.tcommon)
__tls_end = .;
} > RAM
.bss (NOLOAD) : {
. = ALIGN(4);
__tbss_end = .;
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
*(COMMON)
PROVIDE(__global_pointer$ = . + 2K);
*(.sbss*)
. = ALIGN(4);
__bss_end__ = .;
} > RAM
.heap (NOLOAD):
{
__end__ = .;
end = __end__;
KEEP(*(.heap*))
} > RAM
/* historically on GCC sbrk was growing past __HeapLimit to __StackLimit, however
to be more compatible, we now set __HeapLimit explicitly to where the end of the heap is */
__HeapLimit = ORIGIN(RAM) + LENGTH(RAM);
/* Start and end symbols must be word-aligned */
.scratch_x : {
__scratch_x_start__ = .;
*(.scratch_x.*)
. = ALIGN(4);
__scratch_x_end__ = .;
} > SCRATCH_X AT > FLASH
__scratch_x_source__ = LOADADDR(.scratch_x);
.scratch_y : {
__scratch_y_start__ = .;
*(.scratch_y.*)
. = ALIGN(4);
__scratch_y_end__ = .;
} > SCRATCH_Y AT > FLASH
__scratch_y_source__ = LOADADDR(.scratch_y);
/* .stack*_dummy section doesn't contains any symbols. It is only
* used for linker to calculate size of stack sections, and assign
* values to stack symbols later
*
* stack1 section may be empty/missing if platform_launch_core1 is not used */
/* by default we put core 0 stack at the end of scratch Y, so that if core 1
* stack is not used then all of SCRATCH_X is free.
*/
.stack1_dummy (NOLOAD):
{
*(.stack1*)
} > SCRATCH_X
.stack_dummy (NOLOAD):
{
KEEP(*(.stack*))
} > SCRATCH_Y
.flash_end : {
KEEP(*(.embedded_end_block*))
PROVIDE(__flash_binary_end = .);
} > FLASH =0xaa
/* stack limit is poorly named, but historically is maximum heap ptr */
__StackLimit = ORIGIN(RAM) + LENGTH(RAM);
__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
__StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
__StackBottom = __StackTop - SIZEOF(.stack_dummy);
PROVIDE(__stack = __StackTop);
/* picolibc and LLVM */
PROVIDE (__heap_start = __end__);
PROVIDE (__heap_end = __HeapLimit);
PROVIDE( __tls_align = MAX(ALIGNOF(.tdata), ALIGNOF(.tbss)) );
PROVIDE( __tls_size_align = (__tls_size + __tls_align - 1) & ~(__tls_align - 1));
PROVIDE( __arm32_tls_tcb_offset = MAX(8, __tls_align) );
/* llvm-libc */
PROVIDE (_end = __end__);
PROVIDE (__llvm_libc_heap_limit = __HeapLimit);
/* Check if data + heap + stack exceeds RAM limit */
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")
ASSERT( __binary_info_header_end - __logical_binary_start <= 1024, "Binary info must be in first 1024 bytes of the binary")
ASSERT( __embedded_block_end - __logical_binary_start <= 4096, "Embedded block must be in first 4096 bytes of the binary")
/* todo assert on extra code */
}
+73
View File
@@ -0,0 +1,73 @@
# 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()
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 ()
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")
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 ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
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})
+23
View File
@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(flashwrite C CXX ASM)
pico_sdk_init()
add_executable(flashwrite
flashwrite.c
)
target_link_libraries(flashwrite
pico_stdlib
pico_cyw43_arch_none
hardware_flash
hardware_sync
)
pico_enable_stdio_usb(flashwrite 1)
pico_enable_stdio_uart(flashwrite 0)
pico_add_extra_outputs(flashwrite)
+96
View File
@@ -0,0 +1,96 @@
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "hardware/flash.h"
#include "hardware/sync.h"
// Test-Sektor: weit weg von allem anderen. Pico 2W hat 4 MB Flash.
// 0xE0000 = 896 KB Offset. Wuerde im OTA-Layout knapp hinter der App liegen,
// hier aber unbenutzt.
#define TEST_FLASH_OFFSET 0xE0000u
#define TEST_FLASH_ADDR (XIP_BASE + TEST_FLASH_OFFSET)
static void blink_forever(uint32_t on_ms, uint32_t off_ms) {
while (true) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
sleep_ms(on_ms);
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
sleep_ms(off_ms);
}
}
static void hexdump16(const uint8_t *p) {
for (int i = 0; i < 16; i++) {
printf("%02x ", p[i]);
}
printf("\n");
}
int main(void) {
stdio_init_all();
sleep_ms(3000); // Host-Terminal Zeit zum Verbinden
if (cyw43_arch_init()) {
while (true) {
tight_loop_contents();
}
}
printf("\n=== flashwrite sanity test ===\n");
printf("Test-Sektor: XIP-Adresse 0x%08x (Flash-Offset 0x%05x)\n",
(unsigned)TEST_FLASH_ADDR, (unsigned)TEST_FLASH_OFFSET);
printf("FLASH_SECTOR_SIZE=%u FLASH_PAGE_SIZE=%u\n",
(unsigned)FLASH_SECTOR_SIZE, (unsigned)FLASH_PAGE_SIZE);
const uint8_t *flash = (const uint8_t *)TEST_FLASH_ADDR;
printf("Vor erase: ");
hexdump16(flash);
printf("Erase 4 KB...\n");
uint32_t irq = save_and_disable_interrupts();
flash_range_erase(TEST_FLASH_OFFSET, FLASH_SECTOR_SIZE);
restore_interrupts(irq);
printf("Nach erase: ");
hexdump16(flash);
for (uint32_t i = 0; i < FLASH_SECTOR_SIZE; i++) {
if (flash[i] != 0xFF) {
printf("FEHLER: Erase unvollstaendig bei +%lu: 0x%02x\n", i, flash[i]);
blink_forever(50, 50);
}
}
printf("Erase verifiziert (alle 4096 Byte = 0xFF).\n");
uint8_t pattern[FLASH_PAGE_SIZE];
for (uint32_t i = 0; i < FLASH_PAGE_SIZE; i++) {
pattern[i] = (uint8_t)(i ^ 0x5A);
}
printf("Program 256 Byte...\n");
irq = save_and_disable_interrupts();
flash_range_program(TEST_FLASH_OFFSET, pattern, FLASH_PAGE_SIZE);
restore_interrupts(irq);
printf("Nach program: ");
hexdump16(flash);
if (memcmp(flash, pattern, FLASH_PAGE_SIZE) == 0) {
printf("ERFOLG: Pattern korrekt im Flash.\n");
printf("LED: schnelles Blinky 200/200.\n");
blink_forever(200, 200);
} else {
printf("FEHLER: Pattern-Mismatch.\n");
for (uint32_t i = 0; i < FLASH_PAGE_SIZE; i++) {
if (flash[i] != pattern[i]) {
printf(" Diff bei +%lu: got 0x%02x, expected 0x%02x\n",
i, flash[i], pattern[i]);
break;
}
}
blink_forever(50, 50);
}
}
+73
View File
@@ -0,0 +1,73 @@
# 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()
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 ()
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")
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 ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
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})
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
OTA-Update-Server fuer den Pico-Bootloader.
Wire-Protokoll:
Pico -> Server (40 bytes):
[0:4] magic 0xC0FFEE01 (LE)
[4:8] pico_id (LE u32)
[8:40] SHA256 des aktuellen App-Slots (3.5 MB, 0xFF-gepaddet)
Server -> Pico:
[0:1] status: 0x00 = no update, 0x01 = update follows
falls status == 0x01:
[1:5] size der Binary in bytes (LE u32)
[5:37] SHA256 des neuen App-Slots (auch 0xFF-gepaddet auf 3.5 MB)
[37:] Binary-Payload (size Bytes)
"""
import hashlib
import socket
import struct
import sys
from pathlib import Path
MAGIC = 0xC0FFEE01
APP_SLOT_SIZE = 3584 * 1024 # 3.5 MB
SHA256_SIZE = 32
REQUEST_SIZE = 4 + 4 + SHA256_SIZE # 40
STATUS_NO_UPDATE = 0x00
STATUS_UPDATE = 0x01
LISTEN_HOST = "0.0.0.0"
LISTEN_PORT = 9000
RECV_TIMEOUT_S = 5.0
FIRMWARE_DIR = Path(__file__).parent / "firmware"
# pico_id -> binary path. Picos, die hier nicht gelistet sind, bekommen
# immer status=no_update (server kennt das Geraet nicht).
MANIFEST = {
1: FIRMWARE_DIR / "pico_2.bin",
2: FIRMWARE_DIR / "pico_1.bin",
}
def slot_hash(binary_path: Path) -> bytes:
"""Hash der Datei, gepaddet mit 0xFF bis 3.5 MB — exakt das,
was der Pico auf seinem App-Slot berechnet."""
data = binary_path.read_bytes()
if len(data) > APP_SLOT_SIZE:
raise ValueError(
f"{binary_path}: {len(data)} bytes > app slot ({APP_SLOT_SIZE})"
)
padded = data + b"\xff" * (APP_SLOT_SIZE - len(data))
return hashlib.sha256(padded).digest()
def recv_exact(conn: socket.socket, n: int) -> bytes:
buf = b""
while len(buf) < n:
chunk = conn.recv(n - len(buf))
if not chunk:
raise ConnectionError(
f"Verbindung geschlossen nach {len(buf)}/{n} Bytes"
)
buf += chunk
return buf
def handle_client(conn: socket.socket, addr) -> None:
conn.settimeout(RECV_TIMEOUT_S)
try:
request = recv_exact(conn, REQUEST_SIZE)
except (ConnectionError, socket.timeout) as exc:
print(f"[{addr}] Request-Abbruch: {exc}")
return
magic, pico_id = struct.unpack("<II", request[:8])
current_hash = request[8:]
print(
f"[{addr}] pico_id={pico_id} hash={current_hash.hex()[:16]}... magic=0x{magic:08x}"
)
if magic != MAGIC:
print(f"[{addr}] FEHLER: ungueltiges Magic")
return
binary_path = MANIFEST.get(pico_id)
if binary_path is None:
print(f"[{addr}] pico_id {pico_id} nicht im Manifest -> no_update")
conn.sendall(bytes([STATUS_NO_UPDATE]))
return
if not binary_path.exists():
print(f"[{addr}] Manifest-Eintrag fehlt: {binary_path} -> no_update")
conn.sendall(bytes([STATUS_NO_UPDATE]))
return
target_hash = slot_hash(binary_path)
if current_hash == target_hash:
print(f"[{addr}] aktuell -> no_update")
conn.sendall(bytes([STATUS_NO_UPDATE]))
return
binary = binary_path.read_bytes()
print(
f"[{addr}] update -> {binary_path.name} ({len(binary)} bytes, "
f"hash {target_hash.hex()[:16]}...)"
)
conn.sendall(bytes([STATUS_UPDATE]))
conn.sendall(struct.pack("<I", len(binary)))
conn.sendall(target_hash)
conn.sendall(binary)
def main() -> int:
if not FIRMWARE_DIR.exists():
FIRMWARE_DIR.mkdir(parents=True)
print(f"firmware/ angelegt: {FIRMWARE_DIR}")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as srv:
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((LISTEN_HOST, LISTEN_PORT))
srv.listen(4)
print(f"OTA-Server hoert auf {LISTEN_HOST}:{LISTEN_PORT}")
print(f"Manifest: {[(k, str(v.name)) for k, v in MANIFEST.items()]}")
try:
while True:
conn, addr = srv.accept()
try:
handle_client(conn, addr)
except Exception as exc:
print(f"[{addr}] Exception: {exc}")
finally:
conn.close()
except KeyboardInterrupt:
print("\nbeendet")
return 0
if __name__ == "__main__":
sys.exit(main())
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""
Simuliert einen Pico, der den OTA-Server anfragt. Damit testen wir das
Protokoll ohne echtes Geraet.
Usage:
test_client.py <pico_id> [--current-binary PATH] [--host HOST] [--port PORT]
Beispiele:
# so tun, als laege auf dem Pico ein leerer Slot (alles 0xFF):
test_client.py 1
# so tun, als laege auf dem Pico schon pico_1.bin:
test_client.py 1 --current-binary firmware/pico_1.bin
# unbekannte Pico-ID:
test_client.py 99
"""
import argparse
import hashlib
import socket
import struct
import sys
from pathlib import Path
MAGIC = 0xC0FFEE01
APP_SLOT_SIZE = 3584 * 1024
SHA256_SIZE = 32
def slot_hash(binary_path: Path | None) -> bytes:
if binary_path is None:
# Leerer Slot: 3.5 MB 0xFF.
padded = b"\xff" * APP_SLOT_SIZE
else:
data = binary_path.read_bytes()
if len(data) > APP_SLOT_SIZE:
raise ValueError(f"{binary_path}: zu gross ({len(data)})")
padded = data + b"\xff" * (APP_SLOT_SIZE - len(data))
return hashlib.sha256(padded).digest()
def recv_exact(sock: socket.socket, n: int) -> bytes:
buf = b""
while len(buf) < n:
chunk = sock.recv(n - len(buf))
if not chunk:
raise ConnectionError(f"Server-Disconnect nach {len(buf)}/{n}")
buf += chunk
return buf
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("pico_id", type=int)
ap.add_argument("--current-binary", type=Path, default=None,
help="Pfad einer Binary, die wir 'als bereits installiert' simulieren")
ap.add_argument("--host", default="127.0.0.1")
ap.add_argument("--port", type=int, default=9000)
ap.add_argument("--save-update", type=Path, default=None,
help="Falls Server ein Update sendet, hier speichern")
args = ap.parse_args()
current_hash = slot_hash(args.current_binary)
print(f"-> pico_id={args.pico_id} hash={current_hash.hex()[:16]}...")
req = struct.pack("<II", MAGIC, args.pico_id) + current_hash
assert len(req) == 40
with socket.create_connection((args.host, args.port), timeout=5.0) as sock:
sock.sendall(req)
status_byte = recv_exact(sock, 1)
status = status_byte[0]
print(f"<- status=0x{status:02x}")
if status == 0x00:
print(" no update — boot the existing app")
return 0
if status != 0x01:
print(f" FEHLER: unerwartetes Status-Byte 0x{status:02x}")
return 1
size_bytes = recv_exact(sock, 4)
size = struct.unpack("<I", size_bytes)[0]
new_hash = recv_exact(sock, SHA256_SIZE)
print(f" size={size} new_hash={new_hash.hex()[:16]}...")
payload = recv_exact(sock, size)
actual_slot_hash = hashlib.sha256(
payload + b"\xff" * (APP_SLOT_SIZE - len(payload))
).digest()
match = actual_slot_hash == new_hash
print(f" payload empfangen ({len(payload)} bytes) "
f"hash-match={match}")
if not match:
print(" FEHLER: gepaddeter Payload-Hash passt nicht zum angekuendigten Slot-Hash")
return 2
if args.save_update:
args.save_update.write_bytes(payload)
print(f" gespeichert: {args.save_update}")
return 0
if __name__ == "__main__":
sys.exit(main())