Firmware & RTOS
Hard real-time embedded flight systems demand sub-microsecond timing determinism, strict memory allocation limits, and robust fieldbus communication protocols.
NuttX vs. bare-metal vs. FreeRTOS — how do you choose for a flight controller?
Choosing an OS architecture for flight control requires evaluating real-time determinism against software abstraction convenience.
| Dimension | Bare-Metal Super-Loop | FreeRTOS | NuttX (PX4 Architecture) |
|---|---|---|---|
| Scheduling Model | Single non-preemptive loop / ISR interrupts | Preemptive priority-based microkernel | POSIX-compliant RTOS microkernel |
| Memory Footprint | Minimal ($<16\text{KB}$ Flash, $<4\text{KB}$ RAM) | Small ($<32\text{KB}$ Flash, $<8\text{KB}$ RAM) | Large ($>512\text{KB}$ Flash, $>128\text{KB}$ RAM) |
| POSIX Compatibility | None | None (Minimal POSIX wrapper) | Full POSIX VFS (`open`, `read`, `pthread`) |
| Inter-Thread Overhead | Global variables / volatile flags | Direct Task Notifications / Queue pointers | uORB publish-subscribe bus queues |
| Worst-Case Jitter | Lowest ($<100\text{ns}$) | Low ($<1\mu\text{s}$) | Moderate ($5-15\mu\text{s}$) |
For high-frequency actuator loops ($200\text{kHz}$ motor control), bare-metal or FreeRTOS with static memory allocation (`xTaskCreateStatic`) is mandatory. For full-featured flight stacks with filesystem logging and dynamic driver loading, NuttX provides POSIX APIs at the cost of higher interrupt latency and scheduling jitter.
Explain your 200kHz EtherCAT/CAN control loop — what makes that hard?
At a $200\text{kHz}$ sampling rate, the total timing budget per cycle is precisely **$5.0\mu\text{s}$**. Any CPU stall, cache miss, or dynamic allocation instantly causes deadline overrun.
1. 5.0 Microsecond Cycle Budget Breakdown
- $0.0 - 1.0\mu\text{s}$: SPI / DMA Sensor Readout: Read raw gyro/encoder registers over high-speed SPI ($40\text{MHz}$) directly into MCU Tightly-Coupled Memory (ITCM/DTCM).
- $1.0 - 3.0\mu\text{s}$: Control Math Execution: Execute floating-point PI/PD loop. Uses Cortex-M7 hardware single-cycle FPU instructions. Trigonometric calculations use lookup tables. Zero `malloc` calls.
- $3.0 - 4.2\mu\text{s}$: Actuator Wrench / Motor Command Assembly: Format EtherCAT Process Data Object (PDO) or CAN-FD frame bytes. Write directly to peripheral registers.
- $4.2 - 5.0\mu\text{s}$: Timing Headroom / Margin: Safety margin reserved for interrupt context switching.
2. Hardware Management: DTCM & Cache Invalidation
Standard MCU SRAM passes through L1 Data Cache, introducing non-deterministic cache-miss latencies ($20-50$ CPU cycles). We map the hot control loop memory directly into **Data Tightly-Coupled Memory (DTCM)** at address `0x20000000`, guaranteeing single-cycle zero-wait-state memory access bypass of cache controllers.
What is priority inversion, and how do you prevent it?
Priority Inversion occurs when a High-Priority Task ($T_H$) is indirectly blocked by a Medium-Priority Task ($T_M$) because $T_H$ is waiting on a mutex held by a Low-Priority Task ($T_L$). $T_M$ preempts $T_L$ (since $T_M > T_L$), extending $T_H$'s wait time indefinitely.
Priority Inheritance Protocol (PIP) Mechanism
Under PIP, when $T_H$ attempts to acquire a mutex currently held by $T_L$, the RTOS scheduler temporarily elevates the priority of $T_L$ to match $T_H$ ($\text{Priority}(T_L) \leftarrow \text{Priority}(T_H)$). $T_L$ executes without preemption by $T_M$, releases the mutex, and instantly reverts to its original low priority, allowing $T_H$ to acquire the mutex immediately.
// FreeRTOS Mutex Creation with Priority Inheritance Enabled
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
// Note: FreeRTOS Mutexes (xSemaphoreCreateMutex) automatically implement PIP.
// Binary Semaphores (xSemaphoreCreateBinary) DO NOT implement PIP and must never be used for resource locking!
CAN bus arbitration vs. EtherCAT synchronization — how do they differ?
CAN and EtherCAT solve fieldbus communication using fundamentally different physical and data-link layer protocols.
1. CAN 2.0 / CAN-FD CSMA/CR Arbitration
CAN is a multi-master broadcast bus using **Carrier Sense Multiple Access with Collision Resolution (CSMA/CR)**. Arbitration occurs bit-by-bit using dominant (`0`, driven low voltage) and recessive (`1`, high impedance) bus states. If Node A transmits CAN ID `0x100` (`00100000000`) and Node B transmits `0x104` (`00100000100`), at the 9th bit Node B sees dominant `0` while outputting `1`, loses arbitration silently, and stops transmitting, allowing Node A's frame to proceed without collision.
2. EtherCAT Distributed Clocks (DC) Synchronization
EtherCAT uses a Master-Slave ring topology with **Processing-on-The-Fly**. An Ethernet frame passes through hardware EtherCAT Slave Controller (ESC) ASICs, which extract and insert data within nanoseconds while the frame flows physically through the chip. **Distributed Clocks (DC)** synchronize all slave hardware timers to a master reference clock by measuring propagation delay $t_{\text{prop}}$ across nodes:
This allows EtherCAT slaves to trigger PWM updates simultaneously with sub-microsecond jitter ($<100\text{ns}$).
How did you bring up ArduPilot on custom hardware (Strix H7 via DFU)?
Bringing up ArduPilot on a custom STM32H7 flight board follows a rigorous hardware validation pipeline:
- Hardware Definition (`hwdef.dat`): Define MCU pin assignments, SPI/I2C peripheral clock mapping, DMA channels, and timer PWM pin assignments for ChibiOS compilation.
- DFU Bootloader Flashing: Pull `BOOT0` high, connect USB, and flash the compiled `bootloader.bin` via `dfu-util` or `STM32CubeProgrammer`:
dfu-util -a 0 -s 0x08000000:leave -D ArduPilot_bootloader.bin - Peripheral Communication Verification: Connect MAVLink GCS console via USB VCP. Query raw SPI sensor registers (e.g., verifying `WHO_AM_I` register `0x75` for MPU6000 / ICM20689 IMUs over SPI).
- Sensor & Actuator Calibration Gates: Perform 6-position accelerometer calibration, 3D magnetometer sphere fitting, and verify DShot motor ESC signals on an oscilloscope before mounting propellers.
Hard vs. soft real-time — where's the line in your stack?
The distinction between hard and soft real-time is defined by the utility function $U(t)$ when a task deadline $T_{\text{deadline}}$ is missed:
- Hard Real-Time ($U(t) \to -\infty$ for $t > T_{\text{deadline}}$): Missing a deadline constitutes complete system failure (e.g., $200\text{kHz}$ motor commutation loop, EtherCAT DC frame synchronization, EKF IMU prediction step). Handled via dedicated RTOS tasks with high priorities and zero memory allocation.
- Soft Real-Time ($U(t)$ degrades gracefully for $t > T_{\text{deadline}}$): Missing a deadline degrades system performance but causes no safety risk (e.g., GCS telemetry packet streaming, H.264 video bitrate encoding, UI rendering). Handled on Linux compute nodes using standard POSIX threads (`SCHED_OTHER`).