| name | firmware-engineering |
| description | Ingénierie du firmware embarqué — bare-metal, HAL, boot sequence, linker, debugging, optimisation |
| category | edge-ai |
| author | E.V.A |
| version | 1.0.0 |
Ingénierie du Firmware Embarqué
Vue d'ensemble
Développement professionnel de firmware pour systèmes embarqués : architecture bare-metal, HAL, séquence de boot, linkers, optimisation mémoire/temps, tests, et déploiement.
Architecture Firmware
Couches logicielles
┌──────────────────────────────────────┐
│ Application Layer │ main(), tasks, state machines
├──────────────────────────────────────┤
│ Service Layer │ CLI, logging, update OTA, crypto
├──────────────────────────────────────┤
│ HAL (Hardware Abstraction) │ API portable (init, read, write)
├──────────────────────────────────────┤
│ MCU Abstraction │ CMSIS, STM32 HAL, ESP-IDF, Zephyr
├──────────────────────────────────────┤
│ Hardware │ CPU, GPIO, timers, DMA, periph
└──────────────────────────────────────┘
Patterns architecturaux
void main(void) {
init();
while(1) {
process_uart();
process_sensor();
process_led();
}
}
typedef enum { STATE_IDLE, STATE_MEASURE, STATE_TX, STATE_ERROR } system_state_t;
system_state_t state = STATE_IDLE;
void system_tick(void) {
switch(state) {
case STATE_IDLE:
if (measure_ready()) state = STATE_MEASURE;
break;
case STATE_MEASURE:
start_adc();
state = STATE_TX;
break;
case STATE_TX:
if (tx_complete()) state = STATE_IDLE;
break;
case STATE_ERROR:
reset_system();
break;
}
}
Séquence de Boot
Cortex-M Boot Sequence
Reset Vector → Reset_Handler
│
├── 1. Set SP (Stack Pointer) from vector[0]
├── 2. Set PC = Reset_Handler from vector[1]
│
├── 3. SystemInit() — Horloges, PLL, prescalers
│ └── HSE → PLL → SYSCLK → AHB/APB dividers
│
├── 4. .data copy (FLASH → RAM)
│ └── Load LMA from _sidata → VMA at _sdata
│
├── 5. .bss zero (RAM)
│ └── Fill _sbss.._ebss with 0
│
├── 6. Constructors (.init_array) — C++ global objects
│ └── For each entry in __init_array_start..__init_array_end
│
├── 7. main()
│ ├── Hardware init (GPIO, UART, clocks, ADC)
│ ├── RTOS init (xTaskCreate, vTaskStartScheduler)
│ └── while(1) app loop
│
└── 8. HardFault si main() retourne
Implementation Startup
void SystemInit(void) {
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY));
RCC->PLLCFGR = (8 << 0) |
(336 << 6) |
(0 << 16) |
(7 << 24) |
RCC_PLLCFGR_PLLSRC_HSE;
RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY));
RCC->CFGR = RCC_CFGR_HPRE_DIV1 |
RCC_CFGR_PPRE1_DIV4 |
RCC_CFGR_PPRE2_DIV2 |
RCC_CFGR_SW_PLL;
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
SCB->CPACR |= (3 << 20) | (3 << 22);
}
HAL Design Patterns
HAL Portable
#include <stdint.h>
#include <stdbool.h>
typedef enum { MY_OK = 0, MY_ERR_TIMEOUT, MY_ERR_BUSY, MY_ERR_INVAL } my_status_t;
typedef void (*my_callback_t)(void *arg);
void my_gpio_set(uint8_t pin);
void my_gpio_clear(uint8_t pin);
bool my_gpio_read(uint8_t pin);
my_status_t my_gpio_irq_enable(uint8_t pin, my_callback_t cb);
my_status_t my_timer_start(uint32_t us, my_callback_t cb);
void my_timer_stop(void);
uint32_t my_timer_now_us(void);
my_status_t my_uart_init(uint32_t baud);
void ;
;
;
;
;
Implémentation STM32 (exemple)
#include "my_hal.h"
void my_gpio_set(uint8_t pin) {
GPIO_TypeDef *gpio = gpio_port(pin >> 4);
uint8_t bit = pin & 0x0F;
gpio->BSRR = (1 << bit);
}
void my_gpio_clear(uint8_t pin) {
GPIO_TypeDef *gpio = gpio_port(pin >> 4);
uint8_t bit = pin & 0x0F;
gpio->BSRR = (1 << (bit + 16));
}
Gestion Mémoire
Linker Script Avancé
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
BKPSRAM(rw) : ORIGIN = 0x40024000, LENGTH = 4K /* Backup SRAM */
}
SECTIONS {
/* ITCMRAM — code critique temps réel */
.itcm : ALIGN(32) {
*(.isr_vector)
. = ALIGN(256);
_itim_start = .;
*(.ramfunc) /* Code temps réel en RAM */
. = ALIGN(4);
_itim_end = .;
} > RAM AT > FLASH
/* DMA buffers — alignés 32 bytes pour cache coherence */
.dma_buffers (NOLOAD) : ALIGN(32) {
*(.dma_buffer)
} > CCMRAM
/* Backup SRAM — données persistantes après reset */
.backup (NOLOAD) : {
*(.backup_data)
} > BKPSRAM
}
Memory Pool (statique, sans fragmentation)
#define POOL_SIZE 16
#define BLOCK_SIZE 64
static uint8_t pool[POOL_SIZE][BLOCK_SIZE];
static uint32_t pool_mask = 0;
void *pool_alloc(void) {
for (int i = 0; i < POOL_SIZE; i++) {
if (!(pool_mask & (1 << i))) {
pool_mask |= (1 << i);
memset(pool[i], 0, BLOCK_SIZE);
return &pool[i];
}
}
return NULL;
}
void pool_free(void *ptr) {
int idx = ((uint8_t*)ptr - (uint8_t*)pool) / BLOCK_SIZE;
pool_mask &= ~(1 << idx);
}
Ring Buffer (Circular Buffer)
#define RBUF_SIZE 256
struct ringbuf {
uint8_t buf[RBUF_SIZE];
volatile uint32_t head;
volatile uint32_t tail;
};
bool ringbuf_put(struct ringbuf *rb, uint8_t byte) {
uint32_t next = (rb->head + 1) & (RBUF_SIZE - 1);
if (next == rb->tail) return false;
rb->buf[rb->head] = byte;
__DMB();
rb->head = next;
return true;
}
bool ringbuf_get(struct ringbuf *rb, uint8_t *byte) {
if (rb->head == rb->tail) return false;
*byte = rb->buf[rb->tail];
__DMB();
rb->tail = (rb->tail + 1) & (RBUF_SIZE - 1);
return true;
}
Watchdog et Supervision
void iwdg_init(uint32_t timeout_ms) {
IWDG->KR = 0x5555;
IWDG->PR = IWDG_PR_PR_4;
IWDG->RLR = timeout_ms * (LSI_FREQ / 64) / 1000;
IWDG->KR = 0xCCCC;
}
void iwdg_refresh(void) {
IWDG->KR = 0xAAAA;
}
void wwdg_init(uint32_t window_ms) {
WWDG->CFR = WWDG_CFR_WDGTB_1 |
(window_count << 0);
WWDG->CR = WWDG_CR_WDGA | 0x7F;
}
void supervisor_check(void) {
static uint32_t last_tick = 0;
uint32_t now = HAL_GetTick();
if (task1_last_run < now - 100) error_handler();
if (task2_last_run < now - 10) error_handler();
if (!main_loop_flag) error_handler();
iwdg_refresh();
main_loop_flag = ;
}
Cli Embarqué
void cli_process(uint8_t byte) {
static char line[128];
static int pos = 0;
if (byte == '\r') {
line[pos] = '\0';
cli_execute(line);
pos = 0;
} else if (byte == '\b' && pos > 0) {
pos--;
} else if (pos < sizeof(line) - 1) {
line[pos++] = byte;
}
}
void cli_execute(const char *cmd) {
if (strcmp(cmd, "help") == 0) {
uart_puts("Commands: help, info, reset, stats, echo <text>\n");
} else if (strcmp(cmd, "info") == 0) {
char buf[64];
snprintf(buf, sizeof(buf), "CPU: %lu MHz, Free heap: %u\n",
SystemCoreClock / 1000000, (unsigned)xPortGetFreeHeapSize());
uart_puts(buf);
} else if (strcmp(cmd, "reset") == 0) {
NVIC_SystemReset();
} ((cmd, , ) == ) {
uart_puts(cmd + ); uart_puts();
}
}
FOTA (Firmware Over-The-Air)
typedef struct {
uint32_t magic;
uint32_t version;
uint32_t size;
uint32_t crc32;
uint32_t reserved[3];
uint8_t payload[];
} __attribute__((packed)) firmware_header_t;
bool fota_validate(firmware_header_t *fh) {
if (fh->magic != 0xDEADBEEF) return false;
if (fh->size > MAX_FIRMWARE_SIZE) return false;
uint32_t calc_crc = crc32_le(0, (uint8_t*)fh->payload, fh->size);
return calc_crc == fh->crc32;
}
void fota_switch_bank(void) {
SCB->VTOR = VECTOR_TABLE_BANK_B;
__DSB();
__ISB();
NVIC_SystemReset();
}
Optimisation Code / Taille
Flags GCC
CFLAGS += -Os -ffunction-sections -fdata-sections -Wl,--gc-sections
CFLAGS += -O2 -fomit-frame-pointer -funroll-loops --param max-unroll-times=2
CFLAGS += -g3 -gdwarf-4
LDFLAGS += -Wl,--print-memory-usage
Techniques de compaction
const uint8_t lookup_table[256] = { ... };
typedef struct {
uint8_t flag_a : 1;
uint8_t flag_b : 1;
uint8_t flag_c : 1;
uint8_t spare : 5;
} status_flags_t;
typedef union {
struct {
uint32_t timestamp;
uint16_t value;
} sensor_data;
uint8_t raw[6];
} packet_t;
static inline uint32_t min_u32(uint32_t a, uint32_t b) {
return (a < b) ? a : b;
}
Tests de Firmware
Tests unitaires (host)
#include <assert.h>
#include <string.h>
#define HOST_TEST
#include "ringbuf.c"
void test_basic() {
struct ringbuf rb;
memset(&rb, 0, sizeof(rb));
assert(ringbuf_put(&rb, 0x42) == true);
assert(ringbuf_put(&rb, 0x43) == true);
uint8_t byte;
assert(ringbuf_get(&rb, &byte) == true);
assert(byte == 0x42);
assert(ringbuf_get(&rb, &byte) == true);
assert(byte == 0x43);
assert(ringbuf_get(&rb, &byte) == false);
}
void test_full() {
struct ringbuf rb;
memset(&rb, 0, sizeof(rb));
for (int i = 0; i < RBUF_SIZE - 1; i++)
assert(ringbuf_put(&rb, i));
assert(ringbuf_put(&rb, ) == );
}
Pitfalls
- Volatile : Variables partagées ISR ↔ main doivent être
volatile
- Stack overflow : Toujours configurer un stack guard (MPU) ou au moins un pattern de remplissage
- Watchdog : Toujours superviser les tâches (pas juste un toggle GPIO)
- .bss initialisation : Sur MCU, .bss n'est PAS initialisé par le linker — c'est la startup qui le fait
- Endianness : ARM Cortex-M est little-endian par défaut (configurable pour M3+)
- FPU stacking : Sur M4/M7, lazy stacking peut causer des latences d'interruption
- Timing : Ne pas utiliser HAL_Delay() dans les ISR — utiliser un timestamp tick
- CRC : Toujours valider le firmware avant de le copier en flash (FOTA)
- Bootloader : Le bootloader doit vérifier la validité du firmware avant de sauter
Ressources