agentleFS
Premchand006Premchand006/AI-assistant-skills/skills/embedded-firmware-misra/SKILL.md

embedded-firmware-misra

Skill0 starsChanged 54 days agoMITView on GitHub

Write and review bare-metal or RTOS firmware in C for microcontrollers such as STM32, Cortex-M, AVR, ESP32 and RISC-V MCUs. Covers interrupt handler discipline, volatile and atomicity on state shared with an ISR, critical sections, ring buffers, memory-mapped register access, stack bounding, static analysis with cppcheck and clang-tidy, and the MISRA C:2012 rules that prevent real defects. Use when writing or reviewing .c or .h firmware, an IRQ handler, a peripheral driver, a HAL or register header, FreeRTOS or Zephyr task code, when a device works on the bench but fails intermittently in the field, or when a build needs static analysis or MISRA justification.

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When a project claims MISRA compliance, ask which checker, which rule set version, and where the deviation records are.

    line 105

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Firmware that survives the field

The bugs that matter here are the ones that depend on when an interrupt lands. They pass every test on the bench, then fail once a week in a product that has already shipped. Most of them come from three mistakes: state shared with an interrupt that is not volatile, a read-modify-write on shared state with no critical section, and a handler that blocks.

python3 scripts/check_isr_safety.py src/ --fail-on error
./scripts/run_static.sh --misra src/          # adds cppcheck and clang-tidy

The checker finds interrupt handlers, works out which globals they share with the rest of the program, and reports the unsafe shares.

volatile is not atomicity

volatile stops the compiler caching a variable in a register. That is all it does, and it is not enough for anything that reads then writes:

volatile uint32_t count;

count = 0;      /* safe: a single store */
x = count;      /* safe: a single load  */
count++;        /* broken: load, add, store - an interrupt between them loses an update */

Three ways out, in order of preference:

/* 1. Make the ISR the only writer. A single-producer single-consumer ring
      buffer with a power-of-two mask needs no critical section at all. */
/* ISR:  buf[head] = byte; head = (head + 1u) & MASK;   */
/* main: while (tail != head) { use(buf[tail]); tail = (tail + 1u) & MASK; } */

/* 2. C11 atomics, where the toolchain has them. */
atomic_fetch_add(&count, 1);

/* 3. The shortest possible critical section, saving and restoring PRIMASK. */
uint32_t primask = __get_PRIMASK();
__disable_irq();
count++;
__set_PRIMASK(primask);

Ending a critical section with __enable_irq() rather than restoring PRIMASK enables interrupts even when the caller had deliberately disabled them. That bug is invisible until the day it is not.

Interrupt handlers

A handler makes the event safe to deal with later: read the register that must be read, store the data, set a flag, return. Not in a handler: printf, malloc or free, HAL_Delay, blocking RTOS calls whose name lacks FromISR, floating point on a core where it costs context, and any loop without a bound.

Two Cortex-M details worth knowing: a peripheral flag cleared on the last line of a handler may not reach the NVIC before the handler returns, which causes a spurious re-entry — clear it early; and numerically lower priority numbers are more urgent, so a long handler at high priority adds jitter to everything else.

Registers

typedef struct {
    volatile uint32_t SR;
    volatile uint32_t DR;
} uart_regs_t;
#define UART ((uart_regs_t *)0x40011000UL)

REG |= BIT; on a register with write-1-to-clear bits reads the register, sets one bit, and writes the whole value back — clearing every pending flag that happened to be set. Write the bit on its own. And do not describe register layouts with bit-fields: the standard fixes neither the bit order nor the access width.

Bounding the stack

There is no virtual memory, so an overflow quietly corrupts whatever is next in RAM. No recursion, no variable-length arrays, no alloca, and no heap after initialisation. Fill the stack with a pattern at boot and check the high-water mark, or put an MPU guard region below it. -fstack-usage gives per-function numbers that a script can sum along the call graph for a worst-case bound.

MISRA, proportionately

Full MISRA C:2012 compliance needs a certified checker and a deviation process. Outside a certified project, the rules that repay the effort are Dir 4.12 and 21.3 (no dynamic memory), 17.2 (no recursion), 10.x (no implicit conversions), 16.x (switch has a default and every case breaks), and 20.7 (parenthesise macro parameters). Those catch real defects. Rule 15.5, one return per function, is advisory and widely deviated.

When a project claims MISRA compliance, ask which checker, which rule set version, and where the deviation records are. Without those three, the claim is decorative.

Working code for critical sections, ring buffers, wide shared reads, register access, the stack-bounding techniques and the full rule table are in references/concurrency-and-memory.md.

Build flags

-Wall -Wextra -Wshadow -Wconversion -Wdouble-promotion
-Wformat=2 -Wundef -fno-common -Werror -fstack-usage

-Wconversion is the noisy one, and the one that finds real integer bugs. Silencing a warning is a decision to record in the code, not a flag to quietly drop from the build.

Honest limits

check_isr_safety.py analyses one file at a time with regular expressions after stripping comments and strings. It does not preprocess, so it misses handlers and shared state that come from macros, and it does not follow calls between translation units — a variable shared through an accessor function in another file will not be seen. It identifies handlers by naming convention (*_IRQHandler, *_Handler, *_ISR) and by the interrupt attribute; a handler registered another way is invisible to it.

A clean run means no unsafe share was found in the files given, in the shapes the checker recognises. Real timing bugs need a logic analyser, stress at temperature, and a long soak. cppcheck, clang-tidy and the compiler at -Wall -Wextra remain the tools that see the whole translation unit.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.