Firmware Driver Boundary
이 문서는 실제 PCB/sensor driver 팀이 dev-guide의 C/Firmware 예제를 참고할 때 봐야 하는 driver boundary 기준입니다. 현재 예제는 실제 board driver를 포함하지 않고 deterministic synthetic sensor를 사용합니다. 목적은 hardware 없이도 PatientSignalSample, packet quality, firmware artifact 흐름을 검증할 수 있게 하는 것입니다.
1. 목적
Firmware driver는 clinical risk를 판단하지 않습니다. Driver는 sensor에서 관찰한 값을 잃지 않고 C core가 이해할 수 있는 sample과 quality evidence로 변환합니다.
Driver boundary가 지켜야 하는 질문은 아래입니다.
- sample timestamp와 sequence가 단조롭게 증가하는가
- raw channel, vital estimate, motion, contact quality가 같은 sample 시점의 evidence인가
- sensor dropout, clipping, saturation, excessive motion을 backend가 해석할 수 있는 flag로 보존하는가
- low SpO2처럼 유효한 생체신호와 sensor disconnected처럼 무효한 측정을 구분하는가
- firmware version, hardware revision, calibration id가 packet에 남는가
2. Core contract
Board driver가 최종적으로 채워야 하는 core input은 PatientSignalSample입니다.
typedef struct PatientSignalSample {
unsigned long long timestamp_ms;
unsigned int sequence_number;
int ppg_raw;
int ecg_raw;
unsigned int spo2_permille;
unsigned int heart_rate_bpm;
unsigned int motion_mg;
unsigned int contact_quality;
} PatientSignalSample;
이 struct는 hardware register shape가 아닙니다. Driver adapter가 sensor register, DMA buffer, BLE packet, AFE output을 이 domain sample로 변환합니다. patient-signal-c-core는 ADC bus, RTOS queue, DMA, BLE stack을 몰라야 합니다.
2-1. Field mapping
| Field | Driver responsibility |
|---|---|
timestamp_ms |
publish time이 아니라 sample acquisition time을 기록합니다. Wall-clock이 없으면 firmware monotonic tick을 millisecond로 변환하고 기준 epoch를 release note나 driver 문서에 남깁니다. |
sequence_number |
sample source별로 단조 증가합니다. wrap이 가능하면 wrap width와 wrap 이후 gap 계산 정책을 문서화합니다. Gap을 adapter에서 조용히 메우지 않습니다. |
ppg_raw, ecg_raw |
clipped/saturated 판단이 가능하도록 ADC/AFE native scale을 보존합니다. scale 변환이 필요하면 gain, offset, unit, rail 값을 calibration metadata나 driver 문서에 남깁니다. |
spo2_permille |
0.1% 단위 SpO2 estimate, 예: 97.2% -> 972 |
heart_rate_bpm |
현재 sample/window에 대응하는 heart rate estimate |
motion_mg |
motion artifact 판단에 사용할 acceleration magnitude |
contact_quality |
0-100 범위의 contact/sensor confidence |
Driver가 값을 smoothing하거나 보정할 수는 있지만, 품질 판단에 필요한 원인을 숨기면 안 됩니다. 예를 들어 contact loss를 단순히 마지막 정상 값으로 채우면 backend는 missingness와 artifact를 구분할 수 없습니다.
Driver가 여러 sensor clock을 합쳐 하나의 sample을 만들 때는 timestamp_ms가 어떤 channel 기준인지 명시합니다. PPG, ECG, motion sample rate가 다르면 adapter는 resampling rule을 소유할 수 있지만, resampling으로 생긴 missing value와 stale value를 quality evidence에서 숨기지 않습니다.
2-2. Packet quality mapping
C core는 sample과 sequence를 보고 아래 packet quality를 계산합니다.
typedef struct PatientSignalPacketQuality {
unsigned int dropped_samples;
bool sensor_disconnected;
bool clipped;
bool saturated;
bool excessive_motion;
} PatientSignalPacketQuality;
Driver가 지켜야 하는 mapping 기준은 아래입니다.
| Evidence | Expected output direction |
|---|---|
| sequence gap or expected count gap | dropped_samples > 0 |
| contact quality near zero or all vital/raw values zero | sensor_disconnected = true |
| negative raw channel or raw channel above ADC range | clipped = true |
| SpO2 above physiological max or raw near upper rail | saturated = true |
| high motion magnitude | excessive_motion = true |
Low SpO2는 자동으로 invalid가 아닙니다. Low SpO2가 유효한 measurement라면 backend risk workflow가 clinical risk를 판단해야 합니다. Driver와 C core는 측정이 유효한지, 품질 근거가 무엇인지 제공하는 데 집중합니다.
2-3. Invalid, degraded, clinical concern 구분
Driver boundary에서 가장 중요한 구분은 clinical concern과 measurement integrity입니다.
| State | Meaning | Driver behavior |
|---|---|---|
| valid clinical concern | 측정은 유효하지만 값이 임상적으로 우려됨 | 값을 보존하고 quality flag를 과장하지 않습니다. 예: low SpO2, high heart rate |
| degraded measurement | 측정은 가능하지만 artifact 가능성이 큼 | sample은 전달하되 excessive_motion, low contact_quality, clipping evidence를 보존합니다. |
| invalid measurement | 측정 자체를 신뢰할 수 없음 | disconnected/dropout evidence를 남기고 stale value나 last-known-good value로 조용히 대체하지 않습니다. |
이 구분이 깨지면 downstream 문제가 생깁니다. Low SpO2를 sensor_disconnected처럼 처리하면 risk workflow가 실제 임상 우려를 놓칠 수 있고, sensor dropout을 정상 SpO2로 채우면 dashboard가 오래되거나 무효한 신호를 현재 상태처럼 보여줄 수 있습니다.
3. Adapter boundary
현재 bare-metal style app은 아래 위치에 synthetic driver를 둡니다.
apps/wearable-firmware/src/adapters/outbound/mock_sensor.c
apps/wearable-zephyr-firmware/src/adapters/outbound/mock_sensor.c
실제 board driver를 붙일 때는 같은 boundary에 board-specific adapter를 추가합니다.
apps/wearable-firmware/src/adapters/outbound/afe_sensor.c
apps/wearable-firmware/src/adapters/outbound/afe_sensor.h
권장 adapter 흐름은 아래입니다.
sensor register / DMA / BLE payload
-> board-specific driver
-> PatientSignalSample
-> patient_signal_build_packet_from_sample
-> PatientSignalPacket
-> gateway/transport adapter
Transport는 별도 adapter입니다. MQTT, BLE, UART, file dump, gateway HTTP는 packet을 전달하는 방법이지, sample quality rule을 소유하지 않습니다.
Board-specific adapter는 아래 정보를 최소한으로 드러내야 합니다.
- sensor/AFE model과 driver version
- sample rate와 channel alignment policy
- raw channel scale, gain, offset, rail range
- timestamp source와 clock drift 가정
- sequence wrap width와 overflow policy
- calibration id와 calibration 적용 위치
이 정보는 C core API로 모두 밀어 넣지 않습니다. Core API에는 sample과 metadata만 전달하고, driver-specific 설명은 README, release manifest, calibration metadata, test evidence에 남깁니다.
4. Timing and buffering
1단계 예제는 fixed-size window와 single-sample packet build로 boundary를 보여줍니다. 실제 board에서는 아래 항목을 board adapter에서 다룹니다.
- ISR-safe ring buffer
- DMA half/full transfer callback
- monotonic clock source
- sample period drift
- sequence wrap policy
- packet flush interval
- backpressure or queue overflow
이 항목은 C core 안으로 들어가지 않습니다. Core는 이미 만들어진 PatientSignalWindow와 PatientSignalSample을 받아 summary와 packet quality를 계산합니다.
4-1. Overflow and backpressure
실제 board에서는 queue overflow가 발생할 수 있습니다. 이때 driver는 아래 원칙을 따릅니다.
- overflow count를 내부 metric이나 event로 남깁니다.
- dropped sample은 sequence gap 또는
expected_sample_countgap으로 packet quality에 드러나야 합니다. - ISR이나 DMA callback에서 clinical 판단을 하지 않습니다.
- backpressure 때문에 sampling을 멈췄다면 packet flush 시점과 skipped sample 범위를 review evidence에 남깁니다.
- stale sample을 새 timestamp로 다시 발행하지 않습니다.
Host simulation과 native_sim은 실제 ISR timing을 완전히 재현하지 못합니다. 대신 overflow와 gap을 deterministic scenario로 만들어 packet quality가 변하는지 검증합니다.
5. Test evidence
Driver boundary를 바꾸는 PR은 아래 evidence를 남겨야 합니다.
| Evidence | Purpose |
|---|---|
| host unit test | sample validity, quality flag, sequence gap rule |
| host integration test | synthetic scenario -> expected packet quality |
| bare-metal host simulation | app bootstrap과 packet build wiring |
| Zephyr native_sim smoke | RTOS queue/thread composition |
| firmware release manifest | .elf/.hex/.bin/.map checksum and source revision |
실제 PCB/HIL이 필요해지면 별도 runner와 workflow로 분리합니다. HIL 실패가 host C package, Python API, TypeScript UI CI를 막지 않게 하기 위해서입니다.
Review description에는 아래 evidence block을 남깁니다.
Firmware driver evidence
- timestamp source:
- sequence policy:
- sample rate/channel alignment:
- raw scale/calibration:
- quality scenarios tested:
- host tests:
- native_sim result:
- artifact manifest:
- known limitations:
이 block은 regulatory 문서가 아니라 engineering review index입니다. Reviewer가 driver change와 packet quality change를 빠르게 연결해 볼 수 있게 하는 목적입니다.
6. Review checklist
Firmware driver review는 아래를 확인합니다.
- driver가
PatientSignalSample외부로 clinical risk 용어를 만들지 않습니다. - timestamp와 sequence policy가 문서화되어 있습니다.
- dropout, clipping, saturation, excessive motion을 구분할 수 있는 evidence가 보존됩니다.
- calibration id, firmware version, hardware revision이
PatientSignalDeviceMetadata로 전달됩니다. - packet quality 변화가 Rust/Python preprocessing fixture와 충돌하지 않습니다.
- board-specific code가
patient-signal-c-core에 들어가지 않습니다. - queue overflow, stale sample, sequence wrap, clock drift에 대한 판단이 adapter 문서나 test evidence에 남아 있습니다.
- low SpO2 같은 valid clinical concern을 invalid measurement로 바꾸지 않습니다.
- sensor disconnected 같은 invalid measurement를 last-known-good value로 숨기지 않습니다.
이 기준을 지키면 실제 PCB driver가 붙어도 backend contract, Rust preprocessing, Python risk workflow, TypeScript monitoring UI가 같은 signal quality language를 유지할 수 있습니다.