Validate configuration through its owning schemas and keep board declarations separate from shared F722 resource ownership. Bound USB transitions, isolate transport failures and retain restart-only activation. Migrate navigation and flight consumers with the sensor and schema contracts. Require explicit installation data and synchronous output authority; board motor outputs remain inhibited. Validation: assembled-prefix host tests, strict Clippy, platform/USB tests and both ARM release builds. Final-source coverage and scoped Miri/Kani evidence are retained separately from hardware qualification.
10 KiB
Architecture
Kaptan separates immutable board configuration from platform-owned runtime resources and portable flight logic.
board declaration
│
▼
F722 platform / RTIC application ──► data channel ──► MAVLink telemetry
│
├── physical sensor drivers
└── startup motor-pin inhibition
Portable flight libraries (not connected to physical actuators)
FlightEndpoint ──► Navigation / control / phases / safety
│
└── caller-owned synchronous output sink
Each F722 board combines F722UserConfig with an immutable hardware manifest.
The manifest selects the IMU route, driver settings, mount, serial inventory,
status LED, optional I²C sensor, motor pins and service frequency. User settings
select UART roles, baud and MAVLink settings. Motor pins are inhibited at startup.
F722ImmutableConfig<R> retains the IMU route type through initialization and
interrupt acknowledgement. The sealed Mpu6000Spi1 and Icm42688Spi3 routes
select IMU wiring and backend independently of serial inventory and LED wiring.
Each board declares one PortCatalog<N>::DESCRIPTORS array. Both
Ports<Catalog, N> and SerialInventory::new(&Catalog::DESCRIPTORS) use it for
stable UART/field identities, supported roles, baud allowlists and resource
claims. Validation produces a boot-only PortPlan<N>. The same const N sizes
I/O and protocol storage: five UARTs on each current board. Absent UARTs
have no slots. A declared UART set to Disabled retains its slot but initializes
no peripheral or pins. USB is fixed normal MAVLink outside that inventory,
separate from ROM DFU.
Each MAVLink Link has its own scheduler, encoder and receive framing, with a
separate transport queue. A shared collector snapshots sensor mailboxes for all
links. See MAVLink for scheduling and transport behavior.
kaptan-flight-runtime::FlightEndpoint runs Quad-X control and
kaptan-navigation::Navigation without allocation. Callers supply
observations, independently captured receipt/processing time, clock ticks and a
synchronous output sink. Transport and wire encoding stay outside the core.
The board images do not yet construct a flight endpoint.
See Safety boundary for
epochs, request sequencing and the output commit contract.
FlightEndpoint::supervise checks actual time, cached health and phase/stream
deadlines without advancing navigation, integrating control or consuming a
control release. It can inhibit and publish outcomes, but cannot renew enabled
output. Callers must service the endpoint with actual time even when input
stops. The core owns control release and deadline decisions.
Within FlightEndpoint, fusion, current prediction and initialization completion
run only when the 100-Hz control service is due. Early tick calls supervise
cached evidence without advancing navigation. Ingress admission and its
diagnostics remain immediate, but receiving or queuing fresh observations alone
does not renew capabilities. The actual control gap is checked before a due
navigation update can refresh evidence.
Navigation initializes from a stationary window of IMU, fixed-reference local
position/velocity, magnetic, pressure and range observations. It derives attitude
and a fixed launch origin from sensors, with explicit calibration/reference
identities. Its shared 16-state error covariance covers position, velocity,
attitude error, gyro bias, accelerometer bias and barometric datum. Bounded
delayed fusion and current prediction preserve observation timing and capability
evidence.
The navigation sensor-snapshots feature enables driver snapshot conversion.
The same flight estimator is available on host and embedded targets.
Navigation owns the 25-m/s² free-flight acceleration bound checked by complete-profile validation. See Configuration for required installation data and the supported vehicle model.
Configuration and diagnostics use separate paths:
FILE_CONTROL / FILE_DATA
│
▼
kaptan-file-mavlink ──► virtual FileProvider
│
KCFG importer/exporter
│
ConfigManager ──► private store
kaptan-log / kaptan-err-reportable
│
▼
kaptan-diagnostics-mavlink ──► per-link encoder / bounded TX
The Kaptan-native file dialect owns bounded transfers and retries across links. Its provider maps saved, active and factory-reset endpoints to streaming KCFG v3 and the generic configuration lifecycle.
ConfigManager owns boot-active values, known committed values or unknown durable
state, and the factory candidate. F722 supplies its UART-disabled setup fallback
through recover_with. Readable invalid settings remain inspectable for USB
repair. Saving or factory reset never changes active consumers until reboot.
Target-owned stores use private persistence formats. F722 alternates compact
KCSP snapshots between two erase sectors, retaining only the old and replacement
records across the commit boundary. That format is not exposed as KCFG. Motor
allocation/mixer topology remains compiled vehicle capability data.
I/O ownership
Sensor handoff uses owned LatestMailbox<T> storage split into exactly one
non-cloneable producer and one non-cloneable consumer. Publishing replaces an
unread value, and taking removes it at most once. Each operation copies or takes
an Option<T> inside the platform critical section. No callback, allocation,
retry or wait occurs inside that section. Storage stays borrowed while endpoints
are in use, and moving, dropping or forgetting an endpoint holds no lock. The
backend must exclude every participating context, using Cortex-M single-core exclusion on
F722 and the critical-section/std backend for host threads.
UART TX and RX are interrupt-driven, without UART DMA. Each I/O owner retains
the peripheral, TX queue and a 512-byte RX buffer with a typed Timestamp per
byte captured at IRQ receipt. The worker takes at most 16 bytes per UART per
turn under a short lock. Protocol parsing, scheduling and persistence run
outside UART locks. Overflow or a hardware RX fault discards the damaged backlog
and resets the decoder before later bytes. These bounds do not establish a
measured latency guarantee.
kaptan-tx::TransmitQueue owns bounded admission and one logically active frame.
Pending admission cannot replace that active frame. The queue itself is movable
and does not promise DMA address stability. UART byte writes and USB endpoint
writes borrow or copy its bytes synchronously. MAVLink owns encoding, while
platform I/O owners handle transmission.
USB uses the repository-patched Synopsys driver. Runtime register waits allow higher-priority interrupts and stop after 5 ms or 1,080,000 polls per wait. These are fault limits, not measured execution times. A fault holds USB in peripheral reset until restart and discards its queued data. Brief critical sections protect USB ownership and endpoint buffers. Boot initialization still runs under RTIC's startup interrupt mask.
SPI1 and SPI3 DMA each use a once-claimed static pair of 32-byte TX/RX buffers.
start_transfer copies in, so hardware retains no caller slice. Completion
copies out only after checking stream disable, SPI quiescence, transfer counts
and faults. Failure to establish shutdown or drain RX quarantines the storage
and prevents reuse. Moving, dropping or forgetting the owner never releases
that claim. The F722 implementation relies on DMA-accessible SRAM, disabled
D-cache and device barriers. Physical SPI shutdown, timing and fault behavior
still require hardware validation.
Monotonic time
TIM2 is the canonical F722 clock. The configured 108-MHz timer input and PSC=107
produce a free-running 32-bit counter at 1 MHz with ARR=u32::MAX. All readers
and the CC1 service IRQ extend one shared u64 microsecond epoch inside the same
critical section. A consumer may remain silent across wraps if other readers
or IRQ service keep extending that epoch. Combined observations must be strictly
less than 2^32 microseconds apart, about 71.58 minutes, including interrupt
blackouts and debugger halts. NMI and fault handlers must not access this clock.
CC1 schedules the current 100-Hz service at epoch-aligned 10-ms deadlines. Delayed service skips missed periods in constant work and emits at most one notification per IRQ. Rearming uses two bounded attempts before pending a recovery invocation. Callbacks and notifications run after clock exclusion ends. TIM2 is not reset for each service tick, and no update IRQ or timer DMA is used. Its IRQ is enabled only at the successful end of initialization.
Ordinary WFI retains the timer clock. Deep sleep, STOP/Standby, clock-tree changes,
counter reset/regression and debugger halts spanning a full wrap are unsupported
and require platform reset. Uptime must leave one service period before u64
microsecond overflow. DWT is used only for the CPU-running SRAM flash-poll timeout
at 216 MHz, not sensor timestamps or service deadlines. These contracts do not
establish measured oscillator accuracy, IRQ latency, jitter or worst-case timing.
Diagnostics
kaptan-log has no MAVLink, platform, time, allocation or filesystem dependency.
Its configuration coupling is compile-time enum metadata for LevelFilter.
kaptan-err-reportable independently owns explicit error identities and a bounded
nonblocking ingress, without logging, transport or clock dependencies.
Event creation is independent from sink filtering. The worker drains reportable
errors before logs and offers each diagnostic to all MAVLink links. Heartbeat
and sensor publications use separate scheduling.
See Configuration for KCFG and the file-transfer contract, Logs for the receiver contract, and Errors for derived-error and failure-reporting scope.
Ownership rules
- A peripheral owner is local to the platform runtime.
- Portable crates use typed units and validated values rather than raw wire or timer representations.
SafetySupervisor::evaluate_and_commitchecks current evidence at the synchronous enabled-output boundary. No transferable permit escapes it.- Rejected numerical updates do not partially commit computational candidates. Fault latches, inhibition and ingress replay watermarks follow their explicit failure contracts rather than rolling back authority or replay history.