How it is built
Architecture overview
Medical device firmware drives a therapy: an insulin pump moves a plunger, a ventilator moves air. A software fault can hurt the patient, and a regulator will ask for evidence that you looked for it. The design therefore does not trust the dosing code to be right. A smaller, independent safety channel checks every actuator command against its own limits and can cut the drive, and every unsafe default (reset, power loss, bad update, lost link) must land in the same place: therapy stopped, patient alerted. The lifecycle rules (IEC 62304 for the software, ISO 14971 for risk management) are named only; they shape the process, the parts below shape the product. The running example is an insulin pump. No commercial pump firmware is open, so the open pieces named below are the RTOS, the bootloader and the phone-side dosing controllers.
Parts:
- Kernel and tasks — fixed-priority tasks — Preemptive priority scheduling,
rate-monotonic-analysis(no page yet) - Isolation — a memory region per task — Memory protection unit
- Therapy logic — modes, dose arithmetic, optional closed loop — Hierarchical state machine, Deterministic fixed-point math, Model predictive control
- Safety monitor — independent checker with a cutoff —
safety-monitor-architecture(no page yet),redundancy-voting(no page yet) - Supervision — task and hardware watchdogs — Watchdog timer
- Self-test and integrity — RAM/ROM tests, record checksums —
memory-self-test-march(no page yet), CRC32C checksums - Persistent state — dose ledger, settings, log in flash —
wear-leveling(no page yet), CRC32C checksums - Phone link — authenticated commands over Bluetooth LE —
ble-gatt(no page yet), ECDH key exchange, AEAD - Boot and update — signed code only, bad updates revert — Secure boot chain, plus secure updates and PKI (sibling systems)
- Design-time evidence — hazard analysis, proofs, rules — Fault tree analysis, Model checking and formal proof, Safety-critical coding standards
phone ──BLE, AEAD──┐
▼
┌─ application MCU (RTOS, MPU) ─────────────────┐ requests ┌─ safety MCU ─────────┐
│ comms ─► therapy state machine ─► dose plan ├──────────►│ own clock + watchdog │
│ alarms ◄───────────┘ │ │◄──────────┤ limits · dose ledger │
│ │ │ verdicts │ cutoff decision │
└───────────────────────────────────────┬───────┘ └───────────┬──────────┘
▼ │ enable
┌──────────────┐ ▼
│ motor driver │◄─── cutoff gate ─────AND
└──────┬───────┘
▼
motor ─► plunger ─► reservoir ─► infusion set ─► patient
└── encoder counts ─► both MCUs
Two processors are one way to get independence; an isolated second core or a hardware limiter serves the same purpose. What matters is that the checker shares no clock, firmware image or bug with the doer.
How the parts fit together
Kernel and tasks
The tasks are few and fixed at build time: a motor task (the only hard deadline), a sensor task, comms, UI and
alarms, a logger. A preemptive priority scheduler runs the highest-priority ready
task, so the motor task preempts the rest and its worst-case response time is computable. Whether the set fits is a
schedulability question that rate-monotonic-analysis (no page yet) answers: for n periodic tasks the Liu and
Layland sufficient bound is a CPU load of n(2^(1/n) − 1) (source: Liu and Layland 1973), about 0.78 for three tasks
(arithmetic). Above the bound you run exact response-time analysis, not hope. An RTOS beats a bare loop here because
a slow radio call cannot delay a motor step; the sibling RTOS system covers the kernel itself.
Contract to neighbours: tasks exchange small fixed-size messages through bounded queues, not shared variables, and only the motor task touches the driver registers.
Isolation
On a microcontroller without an MMU, a memory protection unit gives each task a few regions with permissions, so an overrun in the comms task faults instead of overwriting the dose schedule. Zephyr's user mode is built on it and is designed for devices with an MPU; it treats user threads as untrusted and the kernel and supervisor threads as trusted (source: Zephyr user-mode documentation). Two limits matter for a safety case. The MPU checks CPU accesses only, so a DMA engine needs its own restrictions. And isolation shows freedom from interference between functions; it does not replace the checker.
Therapy logic
The modes (idle, priming, delivering, suspended, alarm, fault) form a hierarchical state machine: a substate inherits its parent's reactions, so "any fault, from any state, stops delivery" is written once on the root, and leaving "delivering" always disables the driver in its exit action.
Dose arithmetic runs in integers, in a fixed-point format chosen per quantity (units per step, steps per unit). Float would agree on most builds, but "most" is not a claim a reviewer accepts, and the checker must reproduce the number bit-exactly to compare it.
A closed loop adds a model-predictive controller that optimises a short predicted
glucose horizon under constraints and applies only the first step; a PID controller is the simpler
alternative but handles constraints less directly. Either way the controller proposes and the state machine
disposes: output is clipped by limits the controller cannot change. The open OpenAPS controller shows the pattern.
It never sets a temporary basal above the lowest of the pump's own maximum basal rate, 3 times the highest
programmed hourly basal (default max_daily_safety_multiplier) and 4 times the current hourly basal (default
current_basal_safety_multiplier), and max_iob caps insulin on board (source: OpenAPS documentation, preferences
and safety settings).
Safety monitor
The safety-monitor-architecture (no page yet) idea is doer and checker. The application MCU plans a delivery and
sends a request; the safety MCU holds its own copy of the limits, recomputes the step count, counts encoder pulses
itself and keeps a ledger of what was delivered. A request over a single-bolus, hourly or daily limit is refused.
The motor driver's enable line is a hardware AND of both channels' permissions, so "drive on" needs two positive
signals and either side can stop it.
Where a value must not be wrong, redundancy-voting (no page yet) applies in small form: a second copy stored
inverted, or the dose arithmetic run twice by different code. Full 2oo3 voting needs a third channel. Hot-standby
takeover (Hot-standby redundancy) fits systems that must keep running; a pump's safe
response to a fault is to stop.
Supervision
A watchdog timer resets the chip when software stops proving it is healthy. The proof must be about the application, not about interrupts: one supervisor kicks the hardware watchdog only after every task has reported in. Zephyr's task watchdog gives one software channel per task on a kernel timer, with the hardware watchdog as an optional fallback if the task watchdog or the scheduler fails (source: Zephyr task watchdog documentation). A reset is not a safe state by itself: the driver comes up disabled and the next boot starts in "delivery stopped" until the checker and the ledger agree on what was delivered.
Self-test and integrity
Safety standards ask the device to test itself: memory-self-test-march (no page yet) covers March RAM tests at
start-up, ROM checksums and CPU self-tests, sliced to run in idle time. Every record that matters to dosing carries
a CRC32C: settings, basal profile, ledger. A mismatch is a fault state, never a silent default.
Checksums find accidental corruption only; authenticity comes from signatures and AEAD.
Persistent state
The ledger and settings live in flash, which wears out and can lose power mid-write. wear-leveling (no page yet)
spreads erases; a log-structured store does it as a side effect. Zephyr's NVS appends id-data pairs in a ring of
sectors, writes the data first and the metadata (with a CRC) second so an interrupted write is ignored at start-up,
and keeps one sector free (source: Zephyr NVS documentation). Its lifetime formula is
SECTOR_COUNT × SECTOR_SIZE × PAGE_ERASES / (NS × (DS + 8)) minutes. Arithmetic example with assumed values: 4
sectors of 1,024 bytes, the 20,000-erase page the NVS page cites for an nRF51822, one 8-byte ledger record every 3
minutes gives 4 × 1,024 × 20,000 / ((1/3) × 16) ≈ 15.4 million minutes, about 29 years. A record every second gives
about two months, so the ledger writes at delivery milestones and keeps fine counts in RAM.
Phone link
The pump is controlled from a phone over Bluetooth LE (ble-gatt, no page yet: link layer, attribute protocol,
connection intervals, pairing). Do not rely on link-layer security alone. The application layer runs its own key
agreement (ECDH, with peer-key validation and a KDF) and protects every command with an
AEAD that includes a monotonic counter, so a captured command cannot be replayed. The pump
enforces its limits on every command regardless of sender; an authenticated app can be wrong or compromised.
Device certificates and revocation come from the sibling PKI system.
Boot and update
Secure boot makes the ROM verify the bootloader, and the bootloader verify the application, with a version or security counter that rejects old signed images. MCUboot, an open bootloader, has two image slots and a "test" swap: the new image runs once, and unless it marks itself OK after its own self-test, the next reset swaps the old image back (source: MCUboot design documentation). That rollback keeps a bad update from bricking the device. Downgrade prevention compares a security counter in the signed image with a counter held in non-volatile trusted storage (same source). Updates wait for a safe moment, not during a bolus; the slot swap and signed manifest follow A/B partition updates and Signed update metadata.
Design-time evidence
Before the code exists, fault tree analysis takes the top event ("insulin delivered exceeds the commanded dose") and lists the failure combinations behind it. A one-element cut set is a single point of failure and a design bug; the tree also shows which channels above earn their cost. Model checking covers the small, high-value state machines (therapy modes, the update confirm protocol) where exhaustive search is feasible. The code follows a safety-critical coding standard: bounded loops, no heap after start-up, checked returns, enforced by a tool in CI. Ferrocene, a Rust toolchain, states it is qualified for IEC 62304 Class C among other standards (source: ferrocene.dev, vendor statement).
Data and control flow
A bolus request, end to end
- The phone sends "deliver 2.5 U". The comms task authenticates and decrypts it (AEAD, counter check); on failure nothing reaches therapy logic.
- The state machine accepts it only in "idle" or "delivering basal" and converts units to motor steps in fixed point. With an assumed 0.05 U per step (arithmetic example), 2.5 U is 50 steps.
- It sends (steps, rate, sequence number) to the safety MCU. The checker validates against its own limits, recomputes the steps and records the intent in the ledger with a CRC.
- On "go", the motor task steps the driver from a timer interrupt, a fixed-rate loop with absolute deadlines so the rate does not drift. Both MCUs count the encoder pulses.
- The checker compares counts with the plan every few steps. A mismatch beyond tolerance (stall, occlusion, runaway) drops the enable line and the application raises an alarm.
- At completion the ledger entry is closed, the log record written and the phone gets an authenticated status. Time goes into step 4 (order of seconds to minutes, set by dose and rate); the checks are small by comparison.
Boot after a reset
- ROM verifies the bootloader, the bootloader verifies the application and picks the slot.
- The application reads the reset cause, runs start-up self-tests and brings the driver up disabled.
- It loads settings and ledger, verifying every CRC, and asks the checker whether both agree on the last delivery. If an entry was open (intent recorded, completion not), it compares encoder counts with the plan; if it cannot reconcile them, it stays stopped and alarms until the user confirms.
Scaling
The first limits are not throughput, in the order you would pull the levers:
- Worst-case response time of the motor task. Measure on hardware, then rerun the analysis after every change to a higher-priority task.
- Flash endurance. The ledger's wear budget is a product-life limit; run the NVS formula first.
- Power. Radio duty cycle, motor drive and always-on self-tests share one small battery; measure each and run the tests in idle slots.
- Verification effort. Every feature must be in the fault tree and the test plan. The checker must stay simpler than the doer; a checker that grows into a second doer shares its bugs.
- Fleet. Each device has its own keys and logs; roll updates out in stages with the revert path as the brake, and feed field logs to the sibling error-tracking and audit-log systems.
Failure modes
| Failure | Symptom | Cause | Fix |
|---|---|---|---|
| Over-delivery after a bit flip | Dose larger than ordered, no fault raised | Critical value held once, unchecked, in RAM or flash | CRC32C on every critical record, inverted second copy, checker recomputes from its own limits |
| Runaway motor | Drive keeps stepping after the plan ended | Driver stuck on, or the motor task in a bad state | Checker counts encoder pulses and drops the AND gate; driver defaults to disabled on reset |
| Watchdog fed by the wrong code | Device hung but never resets | Kick in a timer interrupt or a dedicated kick task | One supervisor kicks after every task checked in; task watchdog channels with hardware fallback |
| Boot loop after update | Pump resets forever, no therapy | Signed image that crashes at start-up | Test swap with confirm-after-self-test; reset counter drops to safe mode and alarms |
| Stack overflow in a low task | Random faults in neighbouring tasks | One flat address space | MPU regions and a no-access guard region below each stack |
| Torn write at power loss | Ledger or settings unreadable | Battery removed during a flash write | Data before metadata, CRC per record, spare sector (log-structured store) |
| Command replay or forgery | Unwanted bolus from a nearby radio | Link-layer security only | AEAD with a monotonic counter above the link; pump-side limits on every command |
| Silent alarm | Fault, patient not told | The alarm output was never tested | Annunciator self-test at boot, alarm priorities and escalation (Alarm management) |
Real implementations
| name | what it is | repo | licence | notes |
|---|---|---|---|---|
| Zephyr | RTOS with user mode (MPU), task watchdog, NVS, MCUboot integration | zephyrproject-rtos/zephyr | Apache-2.0 | Safety group aims at IEC 61508 SIL 3 for a limited source scope: a stated goal, not a granted certificate (safety overview) |
| FreeRTOS kernel | Small preemptive RTOS; MPU-enabled ports for Cortex-M | FreeRTOS/FreeRTOS-Kernel | MIT | MPU ports under portable/GCC/ARM_CM4_MPU and portable/ARMv8M |
| MCUboot | Secure bootloader: signed images, swap/test/revert, downgrade prevention | mcu-tools/mcuboot | Apache-2.0 | Needs a port to the part's flash and a security-counter store |
| SAFERTOS | Commercial safety RTOS derived from the FreeRTOS functional model | highintegritysystems.com/safertos | — | Vendor states pre-certification to IEC 61508 SIL 3, ISO 26262 ASIL D and IEC 62304 |
| Ferrocene | Qualified Rust toolchain | ferrocene/ferrocene | MIT OR Apache-2.0 | Vendor lists IEC 62304 Class C among its qualifications |
| oref0 (OpenAPS) | Open closed-loop dosing algorithm that runs outside the pump | openaps/oref0 | MIT | Source of the safety-limit pattern above |
| Loop | iOS closed-loop app for supported pumps and CGMs | LoopKit/Loop | MIT (with noted exceptions for frameworks and graphics) | Community project, not an approved medical product by itself |
| AndroidAPS | Android closed-loop app | nightscout/AndroidAPS | AGPL-3.0 | Community project |
Sources
- IEC 62304 (medical device software life cycle), publication page: IEC 62304:2006 + AMD1:2015
- Zephyr documentation: Safety overview, User mode, Task watchdog, Non-volatile storage (NVS), Bluetooth
- MCUboot: design documentation
- OpenAPS: preferences and safety settings
- Liu and Layland, Scheduling Algorithms for Multiprogramming in a Hard-Real-Time Environment, Journal of the ACM, 1973
- Leveson and Turner, An investigation of the Therac-25 accidents, IEEE Computer, 1993 (an investigation of accidents with a radiation-therapy machine)
- Vendor pages: SAFERTOS, Ferrocene