Embedded Systems Expert
Load Order
Read shared-kernel/SKILL.md first.
Core Competencies
Memory Layout
.text (flash, read-only code)
.rodata (flash, read-only data)
.data (flash-initialized, copied to RAM on boot)
.bss (RAM, zero-initialized)
.stack (RAM, grows down)
.heap (RAM, grows up — if used at all)
- Understand the linker script. Read it before trusting it.
Interrupt Discipline
- ISRs are short, deterministic, and non-blocking
- No
malloc, no printf, no floating-point unless FPU lazy stacking is configured
- No blocking on mutexes — use semaphores given from ISR or defer to task
- Priority assignment: higher priority = lower numeric value on Cortex-M
- Understand NVIC priority grouping (preempt priority vs subpriority)
- Nested interrupts are allowed but require stack budgeting
DMA
- Cache maintenance is mandatory on Cortex-M7 with D-cache enabled:
- Invalidate D-cache before DMA reads into buffer
- Clean D-cache after CPU writes to DMA source buffer
- Align DMA buffers to cache line boundaries (32 bytes on Cortex-M7)
- Use non-cacheable MPU region for DMA buffers if cache maintenance is impractical
RTOS Primitives
- Task priorities: static, assigned at creation, higher number = higher priority in FreeRTOS
- Mutex vs semaphore: mutex has priority inheritance, semaphore does not
- Priority inversion: use priority inheritance (mutex) or immediate ceiling
- Queue vs message buffer: queue is fixed-size items, message buffer is variable-length
- Task notifications are faster than semaphores for 1:1 signaling
Power Management
- Sleep modes per MCU family — know the exit latency of each
- Wake sources: configure before entering sleep, verify with loopback test
- Clock tree: PLL, prescalers, peripheral clocks — changing one affects all
- Measure actual current draw with a
µCurrent or Power Profiler Kit, not the datasheet
Peripheral Bringup
Order matters:
- Clock enable for the peripheral
- GPIO alternate function configuration
- Peripheral register configuration
- NVIC enable (if using interrupts)
- Peripheral enable
Verify with:
- Logic analyzer on SPI/I2C/UART lines
- Oscilloscope for timing-critical signals
- GPIO toggle at ISR entry/exit to measure ISR execution time
Verification Gates
Before Merging Firmware Changes
.map file reviewed: flash usage, RAM usage, stack sizes
- Stack usage measured:
- Static: GCC
-fstack-usage + callgraph analysis
- Runtime: stack watermarking (FreeRTOS
uxTaskGetStackHighWaterMark)
- ISR execution time measured with GPIO toggle + oscilloscope
- No new warnings under
-Wall -Wextra -Werror
- MISRA C rules respected if target is safety-critical
Before Shipping
- Watchdog configured and fed from main loop, not from ISR
- Brownout detection enabled at appropriate threshold
- Bootloader verified: can recover from bricked app image
- OTA update path tested end-to-end if applicable
- Flash wear leveling if using internal flash for data storage
Non-Negotiables
- No
printf inside ISRs — ever
- No dynamic allocation after init in safety-critical paths
- All peripheral init functions return status codes, and callers check them
- Watchdog is configured before entering main loop
- No floating-point in ISRs unless FPU context save is explicitly configured
- All global state accessed from both ISR and task is
volatile AND protected by critical section or lock-free primitive
- Interrupt-safe ring buffers use atomic head/tail pointers, not mutexes
Common Failure Modes
| Symptom |
Root Cause |
| HardFault on first peripheral use |
Forgot to enable peripheral clock |
| HardFault with PC = garbage |
Stack overflow — grow stack or reduce locals |
| HardFault with unaligned access |
ARM requires alignment; cast through memcpy |
| DMA reads stale data |
D-cache not invalidated before DMA read |
| DMA sees stale data from CPU |
D-cache not cleaned after CPU write |
| Intermittent I2C NACK |
Missing pull-ups or wrong timing at higher clock |
| UART garbled at high baud |
Baud rate error > 2% — check clock tree |
| Task starvation |
Lower-priority task never runs — check priority assignment |
| Priority inversion hang |
Blocking on semaphore instead of priority-inheriting mutex |
| Current draw 10× datasheet |
Peripheral left enabled in sleep, or floating GPIO |
Deliverables
Minimal Cortex-M Startup (conceptual — verify against your chip's startup)
extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss;
extern int main(void);
void Reset_Handler(void) {
// Copy .data from flash to RAM
uint32_t *src = &_sidata, *dst = &_sdata;
while (dst < &_edata) *dst++ = *src++;
// Zero .bss
for (dst = &_sbss; dst < &_ebss; *dst++ = 0);
// Enable FPU (Cortex-M4F/M7) — set CP10, CP11 full access
SCB->CPACR |= (0xF << 20);
// SystemInit() from CMSIS configures clock tree
SystemInit();
main();
while (1); // main must not return
}
FreeRTOS Task Shape
static void comms_task(void *arg) {
const TickType_t period = pdMS_TO_TICKS(100);
TickType_t last = xTaskGetTickCount();
for (;;) {
// do periodic work
if (process_inbox() != COMMS_OK) {
log_error("comms inbox processing failed");
}
vTaskDelayUntil(&last, period); // absolute, drift-free
}
}
// creation
BaseType_t ok = xTaskCreate(
comms_task,
"comms",
512, // stack size in words (4 bytes each on Cortex-M) — measure, do not guess
NULL,
tskIDLE_PRIORITY + 2,
NULL);
configASSERT(ok == pdPASS);
Interrupt-Safe Ring Buffer (lock-free, single producer / single consumer)
typedef struct {
volatile uint32_t head;
volatile uint32_t tail;
uint8_t buf[256]; // size must be power of 2
} rb_t;
// producer (ISR)
bool rb_push_isr(rb_t *rb, uint8_t b) {
uint32_t next = (rb->head + 1) & 0xFF;
if (next == rb->tail) return false; // full
rb->buf[rb->head] = b;
__DMB(); // memory barrier — ensure data visible before head advance
rb->head = next;
return true;
}
// consumer (task)
bool rb_pop(rb_t *rb, uint8_t *out) {
if (rb->tail == rb->head) return false; // empty
*out = rb->buf[rb->tail];
__DMB();
rb->tail = (rb->tail + 1) & 0xFF;
return true;
}
Debugging Toolkit
- SWD/JTAG probe: J-Link, ST-Link, Black Magic Probe, CMSIS-DAP
- Debugger: GDB + OpenOCD / pyOCD, or vendor IDE
- Logic analyzer: Saleae, DSLogic for protocol decode
- Oscilloscope: for timing, signal integrity, current measurement
- RTT (Real-Time Transfer): SEGGER RTT for log output without UART bandwidth
- SystemView / Tracealyzer: RTOS task timing visualization
Reference Links to Verify
- Chip reference manual (primary source — not the datasheet summary)
- Errata sheet (critical — half of "chip bugs" are documented)
- CMSIS documentation for the Cortex-M core in use
- FreeRTOS or Zephyr official docs for the RTOS version in use
1---2name: embedded-systems3description: Use for firmware, microcontroller, and real-time systems work — C, C++, Rust embedded, ARM Cortex-M (M0/M0+/M3/M4/M7/M33), ESP32, ESP32-S3, RP2040, RP2350, STM32, nRF52/53, FreeRTOS, Zephyr RTOS, bare-metal, interrupt handlers, DMA, memory-mapped I/O, linker scripts, bootloaders, JTAG/SWD debugging, I2C, SPI, UART, CAN, USB, BLE, LoRa, and hardware bringup. Triggers on mentions of firmware, MCU, microcontroller, RTOS, ISR, DMA, bare-metal, HAL, or specific chips (STM32, ESP32, nRF, RP2040, SAMD, PIC32).4---56# Embedded Systems Expert78## Load Order9Read `shared-kernel/SKILL.md` first.1011## Core Competencies1213### Memory Layout14- `.text` (flash, read-only code)15- `.rodata` (flash, read-only data)16- `.data` (flash-initialized, copied to RAM on boot)17- `.bss` (RAM, zero-initialized)18- `.stack` (RAM, grows down)19- `.heap` (RAM, grows up — if used at all)20- Understand the linker script. Read it before trusting it.2122### Interrupt Discipline23- ISRs are short, deterministic, and non-blocking24- No `malloc`, no `printf`, no floating-point unless FPU lazy stacking is configured25- No blocking on mutexes — use semaphores given from ISR or defer to task26- Priority assignment: higher priority = lower numeric value on Cortex-M27- Understand NVIC priority grouping (preempt priority vs subpriority)28- Nested interrupts are allowed but require stack budgeting2930### DMA31- Cache maintenance is mandatory on Cortex-M7 with D-cache enabled:32 - Invalidate D-cache before DMA reads into buffer33 - Clean D-cache after CPU writes to DMA source buffer34- Align DMA buffers to cache line boundaries (32 bytes on Cortex-M7)35- Use non-cacheable MPU region for DMA buffers if cache maintenance is impractical3637### RTOS Primitives38- **Task priorities**: static, assigned at creation, higher number = higher priority in FreeRTOS39- **Mutex vs semaphore**: mutex has priority inheritance, semaphore does not40- **Priority inversion**: use priority inheritance (mutex) or immediate ceiling41- **Queue vs message buffer**: queue is fixed-size items, message buffer is variable-length42- **Task notifications** are faster than semaphores for 1:1 signaling4344### Power Management45- Sleep modes per MCU family — know the exit latency of each46- Wake sources: configure before entering sleep, verify with loopback test47- Clock tree: PLL, prescalers, peripheral clocks — changing one affects all48- Measure actual current draw with a `µCurrent` or Power Profiler Kit, not the datasheet4950### Peripheral Bringup51Order matters:521. Clock enable for the peripheral532. GPIO alternate function configuration543. Peripheral register configuration554. NVIC enable (if using interrupts)565. Peripheral enable5758Verify with:59- Logic analyzer on SPI/I2C/UART lines60- Oscilloscope for timing-critical signals61- GPIO toggle at ISR entry/exit to measure ISR execution time6263## Verification Gates6465### Before Merging Firmware Changes66- `.map` file reviewed: flash usage, RAM usage, stack sizes67- Stack usage measured:68 - Static: GCC `-fstack-usage` + callgraph analysis69 - Runtime: stack watermarking (FreeRTOS `uxTaskGetStackHighWaterMark`)70- ISR execution time measured with GPIO toggle + oscilloscope71- No new warnings under `-Wall -Wextra -Werror`72- MISRA C rules respected if target is safety-critical7374### Before Shipping75- Watchdog configured and fed from main loop, not from ISR76- Brownout detection enabled at appropriate threshold77- Bootloader verified: can recover from bricked app image78- OTA update path tested end-to-end if applicable79- Flash wear leveling if using internal flash for data storage8081## Non-Negotiables8283- No `printf` inside ISRs — ever84- No dynamic allocation after init in safety-critical paths85- All peripheral init functions return status codes, and callers check them86- Watchdog is configured before entering main loop87- No floating-point in ISRs unless FPU context save is explicitly configured88- All global state accessed from both ISR and task is `volatile` AND protected by critical section or lock-free primitive89- Interrupt-safe ring buffers use atomic head/tail pointers, not mutexes9091## Common Failure Modes9293| Symptom | Root Cause |94|---|---|95| HardFault on first peripheral use | Forgot to enable peripheral clock |96| HardFault with PC = garbage | Stack overflow — grow stack or reduce locals |97| HardFault with unaligned access | ARM requires alignment; cast through memcpy |98| DMA reads stale data | D-cache not invalidated before DMA read |99| DMA sees stale data from CPU | D-cache not cleaned after CPU write |100| Intermittent I2C NACK | Missing pull-ups or wrong timing at higher clock |101| UART garbled at high baud | Baud rate error > 2% — check clock tree |102| Task starvation | Lower-priority task never runs — check priority assignment |103| Priority inversion hang | Blocking on semaphore instead of priority-inheriting mutex |104| Current draw 10× datasheet | Peripheral left enabled in sleep, or floating GPIO |105106## Deliverables107108### Minimal Cortex-M Startup (conceptual — verify against your chip's startup)109110```c111extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss;112extern int main(void);113114void Reset_Handler(void) {115 // Copy .data from flash to RAM116 uint32_t *src = &_sidata, *dst = &_sdata;117 while (dst < &_edata) *dst++ = *src++;118119 // Zero .bss120 for (dst = &_sbss; dst < &_ebss; *dst++ = 0);121122 // Enable FPU (Cortex-M4F/M7) — set CP10, CP11 full access123 SCB->CPACR |= (0xF << 20);124125 // SystemInit() from CMSIS configures clock tree126 SystemInit();127128 main();129 while (1); // main must not return130}131```132133### FreeRTOS Task Shape134135```c136static void comms_task(void *arg) {137 const TickType_t period = pdMS_TO_TICKS(100);138 TickType_t last = xTaskGetTickCount();139140 for (;;) {141 // do periodic work142 if (process_inbox() != COMMS_OK) {143 log_error("comms inbox processing failed");144 }145 vTaskDelayUntil(&last, period); // absolute, drift-free146 }147}148149// creation150BaseType_t ok = xTaskCreate(151 comms_task,152 "comms",153 512, // stack size in words (4 bytes each on Cortex-M) — measure, do not guess154 NULL,155 tskIDLE_PRIORITY + 2,156 NULL);157configASSERT(ok == pdPASS);158```159160### Interrupt-Safe Ring Buffer (lock-free, single producer / single consumer)161162```c163typedef struct {164 volatile uint32_t head;165 volatile uint32_t tail;166 uint8_t buf[256]; // size must be power of 2167} rb_t;168169// producer (ISR)170bool rb_push_isr(rb_t *rb, uint8_t b) {171 uint32_t next = (rb->head + 1) & 0xFF;172 if (next == rb->tail) return false; // full173 rb->buf[rb->head] = b;174 __DMB(); // memory barrier — ensure data visible before head advance175 rb->head = next;176 return true;177}178179// consumer (task)180bool rb_pop(rb_t *rb, uint8_t *out) {181 if (rb->tail == rb->head) return false; // empty182 *out = rb->buf[rb->tail];183 __DMB();184 rb->tail = (rb->tail + 1) & 0xFF;185 return true;186}187```188189## Debugging Toolkit190- **SWD/JTAG probe**: J-Link, ST-Link, Black Magic Probe, CMSIS-DAP191- **Debugger**: GDB + OpenOCD / pyOCD, or vendor IDE192- **Logic analyzer**: Saleae, DSLogic for protocol decode193- **Oscilloscope**: for timing, signal integrity, current measurement194- **RTT (Real-Time Transfer)**: SEGGER RTT for log output without UART bandwidth195- **SystemView / Tracealyzer**: RTOS task timing visualization196197## Reference Links to Verify198- Chip reference manual (primary source — not the datasheet summary)199- Errata sheet (critical — half of "chip bugs" are documented)200- CMSIS documentation for the Cortex-M core in use201- FreeRTOS or Zephyr official docs for the RTOS version in use