Safety-critical C · DAL-C style
Fifteen reusable C99 components, each held to a Design Assurance Level C process: written requirements, requirements-based tests, 100% MC/DC, saturating arithmetic, zero dynamic memory.
/* bounded PID, fixed-point, anti-windup */ sc_pid_config_t cfg = { .kp=3*ONE, .ki=ONE/8, .kd=ONE/2, .d_filter=ONE/3, .out_min=0, .out_max=100*ONE }; if (sc_pid_config_valid(&cfg) != SC_OK) return; sc_pid_init(&pid, /*out*/0, /*meas*/ y); sc_q16_t u = sc_pid_update(&cfg, &pid, setpoint, y);
§01Components
Each is one header
and one source file, dependent only on <stdint.h> /
<stdbool.h>, holding no static state.
| Ref | Module | Function | LLR | Cov |
|---|---|---|---|---|
| SC‑SAT | sc_sat | Saturating 32-bit add / subtract — pins at the integer limits, never wraps. | 6 | |
| SC‑FIX | sc_fixed | Q16.16 fixed-point: saturating add / sub / mul (rounded) / div, clamp, abs. | 21 | |
| SC‑CRC | sc_crc | Bitwise CRC-8/SMBUS, CRC-16/CCITT-FALSE, CRC-32/ISO-HDLC. No tables. | 8 | |
| SC‑HYS | sc_hysteresis | Schmitt-trigger comparator with independent assert / clear thresholds. | 12 | |
| SC‑DBN | sc_debounce | Integrator debounce for a noisy digital input — rejects bursts shorter than a sample-count threshold. | 9 | |
| SC‑MED | sc_median | Sliding-window median filter — rejects isolated spikes an averaging filter would smear. Odd window, in-state storage, bounded insertion sort. | 11 | |
| SC‑COBS | sc_cobs | Consistent Overhead Byte Stuffing — frame a byte stream so 0x00 can delimit it. Encode / decode, exact round-trip. | 13 | |
| SC‑RB | sc_ringbuf | Fixed-capacity byte FIFO over caller storage. No malloc, unambiguous full / empty. | 12 | |
| SC‑RL | sc_ratelimit | Slew-rate limiter with output clamp, on saturating arithmetic. | 18 | |
| SC‑LUT | sc_lut | Piecewise-linear lookup table with clamped extrapolation — sensor linearisation, command shaping. | 10 | |
| SC‑SM | sc_sm | Table-driven finite state machine engine — transition table of (from, event, to, action), bounded scan, first match wins. | 11 | |
| SC‑SCH | sc_sched | Cooperative cyclic scheduler — task table with per-task period and phase, deterministic tick-driven dispatch, no preemption. | 9 | |
| SC‑PID | sc_pid | Positional PID: derivative-on-measurement + low-pass, output clamp, conditional-integration anti-windup. | 16 | |
| SC‑VOTE | sc_vote | M-of-N redundancy voter — clusters replicated channel readings by tolerance, returns the majority value, agreeing count, dissenting-channel mask and a consensus verdict. | 14 | |
| SC‑WDG | sc_watchdog | Latching deadline supervisor — a supervised activity must kick once per timeout ticks; a missed deadline trips and stays tripped until re-armed. Bounded up-counter, fail-safe on NULL. | 12 |
§02Architecture
Ten components
are leaf primitives; sc_sat is the saturating-arithmetic base
three others build on, and sc_fixed in turn underpins the PID.
Nothing reaches outside the standard headers.
The worked example wires six of them into one loop: a buffered, averaged sensor reading drives a demand lamp and a PID whose command is slew-limited before it reaches the plant.
Full architecture — the sc_sm state diagram, per-component size ↗
§03Verification
$ make report
sc_sat 8 tests 20 checks PASS
sc_fixed 7 tests 37 checks PASS
sc_crc 5 tests 16 checks PASS
sc_hysteresis 11 tests 35 checks PASS
sc_ringbuf 9 tests 74 checks PASS
sc_ratelimit 10 tests 55 checks PASS
sc_lut 6 tests 20 checks PASS
sc_debounce 6 tests 29 checks PASS
sc_sm 7 tests 38 checks PASS
sc_sched 4 tests 24 checks PASS
sc_cobs 6 tests 1081 checks PASS
sc_pid 8 tests 835 checks PASS
sc_median 9 tests 93 checks PASS
sc_vote 9 tests 69 checks PASS
sc_watchdog 9 tests 53 checks PASS
2479 checks, 0 failure(s)
Lines executed:100.00% of 543
Condition outcomes covered:100.00% of 374
Traceability: complete (every HLR has LLRs and tests;
every LLR is implemented and traced).
§04Traceability
Every low-level
requirement names its parent and its implementing function; every source block
carries an /* LLR-... */ tag; the matrix generator fails the build
on any break.
Architecture — dependency graph, the state-machine framework, the example's signal flow ↗
§05How it's built
requirements/<module>.md: HLRs, then LLRs that each trace up and name their function./* LLR-... */ tag.tests/test_<module>.c: each test names its requirement; compound decisions get MC/DC cases.make coverage instruments with GCC condition coverage; CI fails under 100% line and 100% branch.tools/gen_rtm.py rebuilds the matrix and fails on any orphan requirement.-m32.§06Use it
$ make lib # -> build/libdal_c.a $ make example # builds + runs a composed control loop $ make install PREFIX=/opt/dal-c // then #include <dal_c/dal_c.h> // or one component header
examples/control_loop.c
composes sc_pid + sc_ratelimit + sc_hysteresis +
sc_ringbuf + sc_fixed + sc_crc into a simulated
room-temperature loop.