Add shared emulator services, waveform generation and STM32 USB boot ports
This commit is contained in:
68
c/set-protocol/ports/stm32f407-usb-boot/README.md
Normal file
68
c/set-protocol/ports/stm32f407-usb-boot/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# STM32F407VET6 USB boot port
|
||||
|
||||
SET v2 single-slot updater: `include/set_boot.h`, `src/set_boot.c` and the existing
|
||||
`set_firmware` codec. Byte transport is supplied by the application, independently
|
||||
of the Flash callbacks. The emulator project supplies the ST USB CDC stream.
|
||||
|
||||
## Integration in another F407 project
|
||||
|
||||
1. Copy `boot_config.template.h` to the project's include directory as
|
||||
`boot_config.h`: set crystal frequency, SET node and product model. Match
|
||||
the crystal frequency with HAL `HSE_VALUE`.
|
||||
2. Compile `src/set_protocol.c`, `src/set_firmware.c`, `src/set_boot.c` and this
|
||||
port's `boot_main.c`, `boot_flash.c`, `boot_request.c`, `boot_usb_stream.c`.
|
||||
3. Provide STM32F4 CMSIS/HAL, startup, the ST USB device CDC middleware and the
|
||||
board USB transport (`usb_cdc.h`, `usb_setp_stream.h` and their implementation).
|
||||
The transport calls `usb_setp_feed`/`usb_setp_reset` from this port instead of
|
||||
an application command dispatcher. Enable SET v2 for the bootloader build.
|
||||
4. Link boot code below `0x0800C000`; link the application at `0x08010000`.
|
||||
Add `boot_request.c` to the application and periodically call
|
||||
`f407_reset_poll`; its update command calls `f407_request_boot`.
|
||||
|
||||
The emulator's `Bootloader/Src/*.c` files are include-only build wrappers. Neither
|
||||
the portable updater nor this port depends on the emulator models or its
|
||||
`board_config.h`. The USB transport and HAL configuration remain board supplied.
|
||||
|
||||
Flash layout (512 KiB device): boot sectors 0..2 at `0x08000000` (48 KiB),
|
||||
commit record in sector 3 at `0x0800C000`, application sectors 4..7 at
|
||||
`0x08010000` (448 KiB). All addresses and sector operations are in this port.
|
||||
The sector programming/jump approach is adapted from climate's
|
||||
`climate_control_f407vet6_f4/Bootloader/bootloader_f407.c`.
|
||||
|
||||
BEGIN requires slot 0, base `0x08010000`, size 8..458752, block size 1..256,
|
||||
ERASE_SLOT flag only, zero SHA/key/signature. DATA must be sequential; its CRC,
|
||||
length and bounds are checked. END requires complete receipt and matching size
|
||||
and CRC. The port verifies the actual Flash CRC and vector table before writing
|
||||
the commit marker last. SHA, signatures, automatic activation and resume are
|
||||
not supported and are rejected explicitly. CRC is an integrity check.
|
||||
|
||||
STATUS returns the standard 16-byte SET firmware status. ACTIVATE accepts an
|
||||
empty payload after END; the transport adapter defers reset to let the USB
|
||||
reply finish. ABORT clears the RAM session; it does not restore erased firmware.
|
||||
On reset, SP and reset PC are checked like in climate. USB metadata is not
|
||||
required, so Keil/SWD installs and alternating SWD/USB updates work even with
|
||||
absent or stale USB metadata. Preserve the bootloader using sector erase rather
|
||||
than full-chip erase. Reflash the updated bootloader once to adopt this behavior.
|
||||
USB END/ACTIVATE still require complete receipt and CRC verification.
|
||||
|
||||
Like climate, the reset vector check cannot detect incomplete images once valid
|
||||
vectors are written: after an interrupted update and reset such an image may run.
|
||||
To recover an unresponsive app, erase sector 4 at `0x08010000` via SWD, reset, and
|
||||
upload the full image again. Erasing metadata sector 3 no longer forces bootloader
|
||||
entry. An interrupted update requires a fresh BEGIN and full retransmission.
|
||||
`test_boot_image.c` is a host-only regression test; do not add it to firmware targets.
|
||||
|
||||
`boot_request.c` is shared by the application and bootloader. Product command
|
||||
`0x1003` (empty payload) in the application sets RTC backup register 0 and
|
||||
schedules reset after 500 ms. The bootloader consumes that flag before deciding
|
||||
whether to jump. Reserve BKP0R for this port. The jump must run early after reset,
|
||||
before enabling peripheral interrupts, USB or DMA. Both images must set VTOR
|
||||
to their own link address in SystemInit.
|
||||
|
||||
Board: PA11 D-/PA12 D+, existing self-powered OTG_FS CDC port, 25 MHz HSE;
|
||||
no VBUS host drive, PA9/PA10 are not used. Flash erase uses x32 at nominal 3.3 V,
|
||||
programming uses bytes to support unaligned protocol blocks. Hardware acceptance
|
||||
must cover disconnect/power loss during erase, programming and commit.
|
||||
|
||||
The SET USB command dispatcher is shared with G474 in
|
||||
`../stm32-usb-boot/boot_usb_protocol.inc`; keep that directory when importing this port.
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef F407_BOOT_CONFIG_H
|
||||
#define F407_BOOT_CONFIG_H
|
||||
/* Copy to the board include directory as boot_config.h.
|
||||
* HSE must be an integer MHz frequency; PLL input is 1 MHz.
|
||||
* The Flash layout in boot_port.h targets the 512 KiB STM32F407VE. */
|
||||
#define F407_BOOT_HSE_HZ 25000000UL
|
||||
#define F407_BOOT_NODE 16U
|
||||
#define F407_BOOT_MODEL "STM32F407-USB-BOOT"
|
||||
#endif
|
||||
88
c/set-protocol/ports/stm32f407-usb-boot/boot_flash.c
Normal file
88
c/set-protocol/ports/stm32f407-usb-boot/boot_flash.c
Normal file
@@ -0,0 +1,88 @@
|
||||
/* Flash geometry and jump sequence derived from climate's F407 bootloader.
|
||||
* Sector 3 is a durable commit record; sectors 4..7 hold the application.
|
||||
* Reset checks vectors like climate; USB END always verifies Flash. */
|
||||
#include "boot_port.h"
|
||||
#include "boot_image.h"
|
||||
#include "stm32f4xx_hal.h"
|
||||
#include <string.h>
|
||||
#define META 0x0800C000UL
|
||||
#define COMMIT 0x53455442UL
|
||||
#define ERRORS (FLASH_SR_PGSERR|FLASH_SR_PGPERR|FLASH_SR_PGAERR|FLASH_SR_WRPERR|FLASH_SR_SOP)
|
||||
static void unlock(void) {
|
||||
if(FLASH->CR & FLASH_CR_LOCK) { FLASH->KEYR=0x45670123; FLASH->KEYR=0xCDEF89AB; }
|
||||
FLASH->SR=ERRORS;
|
||||
}
|
||||
static bool finish(void) {
|
||||
while(FLASH->SR & FLASH_SR_BSY) {}
|
||||
return !(FLASH->SR & ERRORS);
|
||||
}
|
||||
static void cache_reset(void) {
|
||||
FLASH->ACR &= ~(FLASH_ACR_ICEN|FLASH_ACR_DCEN);
|
||||
FLASH->ACR |= FLASH_ACR_ICRST|FLASH_ACR_DCRST;
|
||||
FLASH->ACR &= ~(FLASH_ACR_ICRST|FLASH_ACR_DCRST);
|
||||
FLASH->ACR |= FLASH_ACR_ICEN|FLASH_ACR_DCEN;
|
||||
__DSB(); __ISB();
|
||||
}
|
||||
static bool erase_sector(unsigned sector) {
|
||||
bool ok; unlock();
|
||||
FLASH->CR=(FLASH->CR & ~(FLASH_CR_SNB|FLASH_CR_PSIZE)) |
|
||||
FLASH_CR_SER | (2UL<<FLASH_CR_PSIZE_Pos) | (sector<<FLASH_CR_SNB_Pos);
|
||||
FLASH->CR|=FLASH_CR_STRT; ok=finish();
|
||||
FLASH->CR &= ~FLASH_CR_SER; FLASH->CR |= FLASH_CR_LOCK;
|
||||
cache_reset(); return ok;
|
||||
}
|
||||
static bool program(uint32_t address, const uint8_t *p, uint32_t n) {
|
||||
bool ok=true; unlock();
|
||||
/* Byte programming supports arbitrary SET block boundaries and final tails. */
|
||||
FLASH->CR=(FLASH->CR & ~FLASH_CR_PSIZE)|FLASH_CR_PG;
|
||||
while(n--) {
|
||||
*(volatile uint8_t *)address=*p;
|
||||
if(!finish()) { ok=false; break; }
|
||||
++address; ++p;
|
||||
}
|
||||
FLASH->CR &= ~FLASH_CR_PG; FLASH->CR |= FLASH_CR_LOCK;
|
||||
cache_reset(); return ok;
|
||||
}
|
||||
static bool invalidate(void *u) { (void)u; return erase_sector(3); }
|
||||
static bool erase(void *u, uint32_t size) {
|
||||
unsigned s; (void)u;
|
||||
if(size<8 || size>F407_APP_SIZE) return false;
|
||||
for(s=4;s<=7;++s) {
|
||||
uint32_t start=s==4 ? F407_APP_BASE : 0x08020000UL+(s-5)*0x20000UL;
|
||||
if(start>=F407_APP_BASE+size) break;
|
||||
if(!erase_sector(s)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool write(void *u,uint32_t off,const uint8_t *p,uint16_t n) {
|
||||
(void)u;
|
||||
if(!n || off>F407_APP_SIZE || n>F407_APP_SIZE-off) return false;
|
||||
return program(F407_APP_BASE+off,p,n) && !memcmp((const void *)(F407_APP_BASE+off),p,n);
|
||||
}
|
||||
static bool verify(void *u,uint32_t size,uint32_t crc) {
|
||||
(void)u;
|
||||
return f407_vectors_valid((const uint8_t *)F407_APP_BASE,size) &&
|
||||
setp_crc32((const uint8_t *)F407_APP_BASE,size)==crc;
|
||||
}
|
||||
static bool commit(void *u,uint32_t size,uint32_t crc) {
|
||||
uint32_t record[4]={size,crc,~size,COMMIT}; (void)u;
|
||||
return program(META,(const uint8_t *)record,sizeof(record)) &&
|
||||
!memcmp((const void *)META,record,sizeof(record));
|
||||
}
|
||||
bool f407_app_valid(void) {
|
||||
/* Like climate: direct Keil/SWD writes need no USB completion record. */
|
||||
return f407_vectors_valid((const uint8_t *)F407_APP_BASE,F407_APP_SIZE);
|
||||
}
|
||||
static void activate(void *u) { (void)u; /* main waits for USB response before reset */ }
|
||||
const setp_boot_ops f407_boot_ops={invalidate,erase,write,verify,commit,activate};
|
||||
__attribute__((naked,noreturn)) static void enter(uint32_t sp,uint32_t pc) {
|
||||
__asm volatile("msr msp, r0\nmovs r2, #0\nmsr control, r2\ndsb\nisb\ncpsie i\nbx r1\n");
|
||||
}
|
||||
void f407_jump_app(void) {
|
||||
unsigned i; uint32_t sp=*(const uint32_t *)F407_APP_BASE;
|
||||
uint32_t pc=*(const uint32_t *)(F407_APP_BASE+4);
|
||||
__disable_irq(); SysTick->CTRL=0;
|
||||
for(i=0;i<8;++i) { NVIC->ICER[i]=0xffffffff; NVIC->ICPR[i]=0xffffffff; }
|
||||
SCB->ICSR=SCB_ICSR_PENDSTCLR_Msk|SCB_ICSR_PENDSVCLR_Msk;
|
||||
SCB->VTOR=F407_APP_BASE; enter(sp,pc);
|
||||
}
|
||||
19
c/set-protocol/ports/stm32f407-usb-boot/boot_image.h
Normal file
19
c/set-protocol/ports/stm32f407-usb-boot/boot_image.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef F407_BOOT_IMAGE_H
|
||||
#define F407_BOOT_IMAGE_H
|
||||
#include "boot_port.h"
|
||||
#include <string.h>
|
||||
|
||||
static inline bool f407_vectors_valid(const uint8_t *image, uint32_t size)
|
||||
{
|
||||
uint32_t sp, pc;
|
||||
if (size < 8 || size > F407_APP_SIZE) return false;
|
||||
memcpy(&sp, image, 4);
|
||||
memcpy(&pc, image + 4, 4);
|
||||
return !(sp & 7) &&
|
||||
((sp > 0x20000000UL && sp <= 0x20020000UL) ||
|
||||
(sp > 0x10000000UL && sp <= 0x10010000UL)) &&
|
||||
(pc & 1) && (pc & ~1UL) >= F407_APP_BASE &&
|
||||
(pc & ~1UL) < F407_APP_BASE + size;
|
||||
}
|
||||
|
||||
#endif
|
||||
63
c/set-protocol/ports/stm32f407-usb-boot/boot_main.c
Normal file
63
c/set-protocol/ports/stm32f407-usb-boot/boot_main.c
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "stm32f4xx_hal.h"
|
||||
#include "boot_config.h"
|
||||
#include "usb_cdc.h"
|
||||
#include "boot_port.h"
|
||||
#if F407_BOOT_HSE_HZ != HSE_VALUE
|
||||
#error boot_config.h and HAL HSE_VALUE must describe the same crystal
|
||||
#endif
|
||||
#if (F407_BOOT_HSE_HZ % 1000000UL) || F407_BOOT_HSE_HZ < 2000000UL || F407_BOOT_HSE_HZ > 50000000UL
|
||||
#error HSE must provide a valid integer PLLM divider (2..50 MHz)
|
||||
#endif
|
||||
uint32_t SystemCoreClock=16000000;
|
||||
const uint8_t AHBPrescTable[16]={0,0,0,0,0,0,0,0,1,2,3,4,6,7,8,9};
|
||||
const uint8_t APBPrescTable[8]={0,0,0,0,1,2,3,4};
|
||||
static uint8_t usb_clock_ready;
|
||||
static volatile uint32_t clock_fault;
|
||||
void SystemInit(void) { SCB->CPACR|=0xFu<<20; SCB->VTOR=0x08000000; }
|
||||
void SysTick_Handler(void) { HAL_IncTick(); }
|
||||
static void board_fatal(uint32_t code) { clock_fault=code; for(;;) {} }
|
||||
static void peripheral_clock(void)
|
||||
{
|
||||
RCC_OscInitTypeDef osc={0};
|
||||
RCC_ClkInitTypeDef clk={0};
|
||||
/* Разрешаем доступ к PWR и выбираем диапазон питания ядра перед повышением частоты. */
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
|
||||
osc.OscillatorType=RCC_OSCILLATORTYPE_HSE;
|
||||
osc.HSEState=RCC_HSE_ON;
|
||||
/* Готовность HSE проверяется HAL с ограниченным временем ожидания, а не бесконечным циклом. */
|
||||
if (HAL_RCC_OscConfig(&osc)==HAL_OK) usb_clock_ready=1;
|
||||
else {
|
||||
clock_fault=1;
|
||||
osc.HSEState=RCC_HSE_OFF;
|
||||
if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3);
|
||||
}
|
||||
/* CPU 72 MHz, APB1 36 MHz, USB 48 MHz; USB requires the HSE crystal. */
|
||||
osc.OscillatorType=RCC_OSCILLATORTYPE_HSI;
|
||||
osc.HSIState=RCC_HSI_ON;
|
||||
osc.HSICalibrationValue=RCC_HSICALIBRATION_DEFAULT;
|
||||
osc.PLL.PLLState=RCC_PLL_ON;
|
||||
osc.PLL.PLLSource=usb_clock_ready ? RCC_PLLSOURCE_HSE : RCC_PLLSOURCE_HSI;
|
||||
osc.PLL.PLLM=usb_clock_ready ? F407_BOOT_HSE_HZ/1000000UL : 16;
|
||||
osc.PLL.PLLN=144; osc.PLL.PLLP=RCC_PLLP_DIV2; osc.PLL.PLLQ=3;
|
||||
if (HAL_RCC_OscConfig(&osc)!=HAL_OK) board_fatal(3);
|
||||
clk.ClockType=RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
|
||||
clk.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;
|
||||
clk.AHBCLKDivider=RCC_SYSCLK_DIV1;
|
||||
clk.APB1CLKDivider=RCC_HCLK_DIV2; clk.APB2CLKDivider=RCC_HCLK_DIV1;
|
||||
/* HAL задаёт два такта ожидания Flash и перестраивает SysTick для новой частоты CPU. */
|
||||
if (HAL_RCC_ClockConfig(&clk,FLASH_LATENCY_2)!=HAL_OK) board_fatal(3);
|
||||
}
|
||||
|
||||
|
||||
void boot_protocol_init(void);
|
||||
void boot_protocol_poll(uint32_t now);
|
||||
int main(void) {
|
||||
/* Jump from reset, before initializing USB, DMA, clocks or interrupts. */
|
||||
bool requested=f407_boot_requested();
|
||||
if(!requested && f407_app_valid()) f407_jump_app();
|
||||
if(HAL_Init()!=HAL_OK) board_fatal(3);
|
||||
peripheral_clock(); boot_protocol_init();
|
||||
if(usb_clock_ready) usb_cdc_init();
|
||||
for(;;) { uint32_t now=HAL_GetTick(); usb_cdc_poll(now); boot_protocol_poll(now); }
|
||||
}
|
||||
13
c/set-protocol/ports/stm32f407-usb-boot/boot_port.h
Normal file
13
c/set-protocol/ports/stm32f407-usb-boot/boot_port.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef F407_BOOT_PORT_H
|
||||
#define F407_BOOT_PORT_H
|
||||
#include "set_boot.h"
|
||||
#define F407_APP_BASE 0x08010000UL
|
||||
#define F407_APP_SIZE 0x00070000UL
|
||||
#define F407_BOOT_REQUEST 0x1003U
|
||||
extern const setp_boot_ops f407_boot_ops;
|
||||
bool f407_app_valid(void);
|
||||
bool f407_boot_requested(void);
|
||||
void f407_request_boot(uint32_t now);
|
||||
void f407_reset_poll(uint32_t now);
|
||||
void f407_jump_app(void);
|
||||
#endif
|
||||
26
c/set-protocol/ports/stm32f407-usb-boot/boot_request.c
Normal file
26
c/set-protocol/ports/stm32f407-usb-boot/boot_request.c
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "boot_port.h"
|
||||
#include "stm32f4xx_hal.h"
|
||||
#define REQUEST_MAGIC 0x42555342UL
|
||||
static uint32_t reset_at;
|
||||
static bool reset_pending;
|
||||
static void backup_access(void)
|
||||
{
|
||||
__HAL_RCC_PWR_CLK_ENABLE();
|
||||
PWR->CR |= PWR_CR_DBP;
|
||||
__HAL_RCC_RTC_ENABLE();
|
||||
}
|
||||
bool f407_boot_requested(void)
|
||||
{
|
||||
bool requested;
|
||||
backup_access(); requested=RTC->BKP0R==REQUEST_MAGIC;
|
||||
RTC->BKP0R=0; return requested;
|
||||
}
|
||||
void f407_request_boot(uint32_t now)
|
||||
{
|
||||
backup_access(); RTC->BKP0R=REQUEST_MAGIC;
|
||||
reset_at=now+500; reset_pending=true;
|
||||
}
|
||||
void f407_reset_poll(uint32_t now)
|
||||
{
|
||||
if(reset_pending && (int32_t)(now-reset_at)>=0) NVIC_SystemReset();
|
||||
}
|
||||
10
c/set-protocol/ports/stm32f407-usb-boot/boot_usb_stream.c
Normal file
10
c/set-protocol/ports/stm32f407-usb-boot/boot_usb_stream.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "stm32f4xx_hal.h"
|
||||
#include "boot_port.h"
|
||||
#include "boot_config.h"
|
||||
#define BOOT_OPS f407_boot_ops
|
||||
#define BOOT_APP_BASE F407_APP_BASE
|
||||
#define BOOT_APP_SIZE F407_APP_SIZE
|
||||
#define BOOT_NODE F407_BOOT_NODE
|
||||
#define BOOT_MODEL F407_BOOT_MODEL
|
||||
#define BOOT_RESET() NVIC_SystemReset()
|
||||
#include "../stm32-usb-boot/boot_usb_protocol.inc"
|
||||
34
c/set-protocol/ports/stm32f407-usb-boot/test_boot_image.c
Normal file
34
c/set-protocol/ports/stm32f407-usb-boot/test_boot_image.c
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "boot_image.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint32_t words[32] = {0x20020000, F407_APP_BASE + 9};
|
||||
uint8_t *image = (uint8_t *)words;
|
||||
/* A Keil image needs no metadata. Replacing its body must still boot. */
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[10] ^= 1;
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[0] = words[1] = 0xffffffff;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[0] = 0x20020000; words[1] = 0x08000101;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[1] = F407_APP_BASE + 8;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[1] = F407_APP_BASE + 9; words[0] = 0x20020001;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[0] = 0x10010000;
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
assert(!f407_vectors_valid(image, 8));
|
||||
assert(!f407_vectors_valid(image, 7));
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE + 1));
|
||||
words[1] = F407_APP_BASE + F407_APP_SIZE + 1;
|
||||
assert(!f407_vectors_valid(image, F407_APP_SIZE));
|
||||
words[1] = F407_APP_BASE + 9;
|
||||
/* Like climate, vectors alone cannot establish image completeness. */
|
||||
memset(image + 8, 0xff, sizeof(words) - 8);
|
||||
assert(f407_vectors_valid(image, F407_APP_SIZE));
|
||||
puts("F407 boot: Keil image replacement and vector checks passed");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user