Device and desktop split
The firmware runs diagnostics locally on the device, with its own screen and rotary knob for standalone operation. EVCore Studio connects over USB-C and controls the same firmware functions through the same typed control API that the local UI uses. Studio never computes a diagnostic result that the device did not produce.Firmware layers
The firmware is organized into four layers. A module may include headers from its own layer or a lower one, and same-layer includes must not form a cycle.node scripts/check-layers.mjs enforces this and runs as part of scripts/verify.ps1.
Hardware boundary
Nothing infirmware/ touches a register, pin, or peripheral. Every hardware action goes through callbacks: EV_Board (in evcore_app.h), EV_ApplyOutputs (in evcore_safety.h), and EV_RouteApply (in evcore_routing.h). The simulator in simulation/ implements these callbacks now; an STM32 board layer will implement them later. Firmware never includes simulator or test headers.
Safety as a foundation
The safety manager is a leaf module that services consult. Routing and the test runner ask it for permission before energizing or moving relays. Nothing energizes without safety approval, and safety depends on nothing it could be blocked by. All outputs default OFF, and every failure path ends de-energized.Ownership and scheduling
All state lives in caller-owned structs.EV_App owns one EV_Safety, EV_Routing, a copied EV_TestProfile, a report, and a log. There is no heap and no global mutable state in firmware modules. The APIs are single-owner and non-reentrant. A cooperative time-triggered scheduler feeds them from one owner context.
Scheduler
The firmware uses a cooperative, time-triggered main loop (decision 0002). A hardware timer sets the tick; the loop runs the safety and app tick every period, then drains USB and console work within a time budget. Interrupts only capture inputs and move bytes. This choice was accepted on 2026-09-24 because it is the simplest to analyze: worst-case loop time is measurable, and no RTOS code needs to be qualified. The rule is that exactly one task context ownsEV_App. Interrupts capture timestamped inputs and move bytes into bounded queues (EV_ByteQueue, EV_Can).
FreeRTOS or ThreadX may be revisited only when a measured time-budget overrun appears, for example from display redraws or flash writes. Independent hardware must still remove outputs if firmware stops, regardless of the scheduler choice.
EVCore Studio structure
Studio is an Electron application. The main process (desktop/main.cjs) owns files, backups, USB permission, PDF rendering, and the firmware simulator bridge. The sandboxed page reaches the main process only through the fixed window.evcore API in desktop/preload.cjs. Every IPC request is checked to come from the app’s own page.
The page (public/) is split into core modules (storage, record database, device connection) and one module per screen. Studio connects to the device in three modes: in-app demo (synthetic signals), firmware simulator (real firmware with simulated hardware), and USB device (Web Serial with Studio’s own port picker). Simulated data is labeled as simulated everywhere, including on service reports.