Bare Metal C/C++ Embedded Development

Register-level peripheral control, deterministic real-time performance, and zero-overhead firmware for the applications where every cycle and every byte matters.

Platform engineering
Platform Capabilities

What We Build on This Platform

Register-Level Peripheral Drivers

Clock-gating, peripheral initialisation, and data transfer directly from hardware registers without vendor library overhead.

Startup Code and Linker Script

Vector table, .data/.bss initialisation, memory map, and section placement for the specific MCU's physical memory layout.

Interrupt Architecture

NVIC configuration, ISR handler, critical section, and interrupt latency analysis for hard real-time requirements.

DMA Programming

DMA channel/stream configuration, circular buffer, and double-buffer for zero-CPU data transfer on Cortex-M and other architectures.

Timing and Cycle Counting

Hardware timer configuration, DWT cycle counter, precision delay, and sub-microsecond timestamping.

Low-Power Without RTOS

State machine with hardware-timed wake, peripheral power gating, and deep sleep entry without RTOS scheduler overhead.

MISRA C and Static Analysis

MISRA C:2012, CERT C, PC-Lint, Polyspace, and Coverity for safety-critical and regulatory compliance applications.

C++ for Embedded

constexpr, RAII for peripheral ownership, and template metaprogramming for zero-cost abstraction in embedded C++.

Platform Integration

How Ankh Uses This Platform

01

Architecture Decision — Bare Metal vs RTOS

Bare metal C chosen when: the application has a single dominant control loop with simple interrupt-driven peripherals; the timing requirement is so strict that RTOS context-switch overhead is unacceptable; the memory constraint is too tight for RTOS kernel objects; or the certification requirement makes the RTOS a more complex certification target than the application merits.

02

Peripheral Driver Development

Each peripheral driven from the hardware reference manual: clock enable in the RCC register, peripheral reset, GPIO alternate function configuration, peripheral parameter configuration, and data transfer (polled for slow peripherals, interrupt-driven for medium-throughput, DMA for high-throughput). Every driver function documented with the specific register writes it makes.

03

Interrupt and Timing Architecture

Interrupt priority assigned for every ISR source. Critical section implemented using PRIMASK or BASEPRI disable. ISR handler executes the minimum required operation in ISR context and defers processing to main loop. Hardware timer configured for precision timing and timestamping. DWT cycle counter used for sub-microsecond elapsed time measurement.

04

State Machine Design

Application control logic structured as a deterministic state machine: explicit state variable, state transition table from events and conditions, one-entry-per-cycle main loop processing events in priority order. Hardware timer-based non-blocking delays using elapsed-time comparison rather than busy-wait loops that burn CPU cycles.

05

Static Analysis and Code Quality

MISRA C:2012 compliance for safety-critical applications: mandatory rule compliance, advisory rule disposition for each violation. PC-Lint, Polyspace, or Coverity for runtime error detection: null pointer dereference, array out-of-bounds, integer overflow. Code review against the project's coding standard before every merge.

Engineering Depth

Deep Technical Capability

01

Linker Script as the Firmware's Physical Architecture

The GNU linker script defines the physical memory map of the firmware — where the interrupt vector table is placed in flash, where .text code follows, where .rodata constant data is placed, where .data initialisation values are stored in flash at the load address and copied to RAM at the run address, and where .bss uninitialised data is placed and zeroed. A linker script error — a section placed at the wrong address, a missing KEEP() directive that allows the linker to discard the interrupt vector table as unused, or an output section conflicting with a reserved memory region — produces a firmware that compiles without warnings and fails at runtime in a way indistinguishable from a hardware fault. Ankh writes linker scripts from the target MCU's memory map datasheet, verifying section placement against the hardware reference manual and the resulting binary with the linker's map file output before the firmware is loaded on hardware.

02

ISR Latency Budget for Hard Real-Time Requirements

The Cortex-M's interrupt latency from assertion of an interrupt request to the first instruction of the ISR handler is 12 cycles at zero wait states from zero interrupt nesting — the hardware interrupt entry sequence of pushing registers, fetching the vector, and branching. For a Cortex-M4 at 168MHz this is 71ns. A firmware with other ISRs executing that preempt the new interrupt based on NVIC priority adds the preempting ISR's execution time to this base latency. Ankh calculates the worst-case interrupt latency for every safety-critical interrupt by summing the base latency, tail-chaining latency for back-to-back interrupts, and the maximum execution time of every ISR that can preempt the target interrupt — and validates the calculation by measuring the interrupt latency on prototype hardware using a logic analyser with the worst-case preemption scenario artificially stimulated.

03

MISRA C:2012 in Production Embedded Firmware

MISRA C:2012 is the automotive-derived C coding standard for safety-related embedded systems — 143 rules categorised as mandatory, required, and advisory. The MISRA rules most frequently violated in embedded firmware are: casting between pointer and integer types without explicit size specification (Rule 11.1–11.4); using bitfield types without explicit signed/unsigned specification (Rule 6.1); performing arithmetic on a pointer type without a bound check (Rule 18.1–18.4); and omitting a default clause in a switch statement (Rule 16.4). A MISRA-compliant codebase is not simply a codebase with no MISRA violations — it is a codebase with a deviation record for every advisory rule violation that justifies why the violation is acceptable for the specific case, reviewed and approved by the safety team. Ankh maintains MISRA compliance throughout development using PC-Lint or LDRA in the CI/CD pipeline, with deviation records for every rule exception as part of the formal software development record.

01

Bare metal is not simpler than RTOS — it is more demanding

The RTOS handles task scheduling, stack management, and inter-task communication that bare metal firmware must implement explicitly. A complex bare metal application with five concurrent activities managed in a single main loop requires explicit state machine design, explicit event queuing, explicit priority management, and explicit protection of shared data — all of which the RTOS provides implicitly. Ankh chooses bare metal C when the application genuinely benefits from its advantages — determinism, minimum overhead, simplest certification path — not as a default for projects that would benefit from RTOS structure.

02

Volatile is not a substitute for memory barriers

A volatile-qualified global variable shared between an ISR and the main loop is read and written as individual atomic accesses — but on a processor with a write buffer (Cortex-M3 and above), a sequence of writes to non-volatile memory may be reordered such that the ISR reads a value reflecting a partial update of a multi-byte variable. The correct protection for a multi-byte shared variable between an ISR and the main loop is a critical section that disables the interrupt at the entry and exit of the main loop's read-modify-write sequence — not a volatile qualifier that the programmer believes provides atomicity but does not.

03

The startup code is the most safety-critical code in the firmware

The startup code that runs before main() initialises the environment that every subsequent line of firmware assumes is correctly initialised. Startup code that zeroes .bss before copying .data produces a .data section overwritten with zeros — placing every initialised global at zero regardless of the value specified in the C source. Startup code that does not initialise the heap before the first malloc call produces a heap with undefined internal state. Ankh writes startup code from the ARM Cortex-M startup code specification and the target MCU's memory map — verifying the .data copy and .bss zero with the linker map file before the first application function is executed on hardware.

Quote your project

Need deterministic bare metal firmware?Let's build it from the startup code up.