Files
ruvnet--RuView/firmware/esp32-gamma-stim/main/stim_core.h
T
Claude 9744d367a2 feat(firmware): ESP32 gamma stimulation actuator (ADR-250 M2 device harness)
The hardware side of ruview-gamma: an ESP32 driving an LED + audio flicker at
a commanded 36-44 Hz envelope with a hardware emergency stop. This is the
actuator the hil::verify_hil contract grades.

Split for testability:
- main/stim_core.{h,c}: pure C safety core, no ESP-IDF deps. Envelope
  validation mirroring SafetyEnvelope::conservative(), a latched
  START/STOP/e-stop state machine (a session can never silently resume after
  an e-stop), exact integer timing in millihertz (the +/-0.1 Hz HIL target is
  exact: 40.0 Hz = 40000 mHz -> 12500 us half-period), and a fail-closed line
  parser. 15 host tests pass under gcc, no hardware needed.
- main/main.c: ESP-IDF binding. GPTimer ISR at 1 MHz crystal ticks, LEDC PWM
  for LED (19.5 kHz carrier) and audio tone, sync-out GPIO for logic-analyzer
  capture, e-stop GPIO ISR that turns outputs off in the ISR (microseconds,
  vs the 100 ms HIL budget) then latches, USB-CDC line console.

Defense in depth: the device re-enforces the safety envelope independently of
the Rust host, so a buggy or compromised host cannot command an
out-of-envelope output. Emits a canonical integer SESSION {...} record per run
for witness-hash reproduction (HIL 100% reproducibility target).

Kconfig pins, 4 MB single-app, radio-off deterministic actuator profile.
Maps 1:1 to the five hil::verify_hil targets.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH
2026-06-10 04:39:24 +00:00

163 lines
5.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* stim_core.h — pure, host-testable core of the gamma stimulation firmware.
*
* Everything safety-critical lives here, with NO ESP-IDF dependencies, so the
* exact code that ships on the device is unit-tested on the host (gcc) and in
* CI. main.c is a thin hardware binding (timers, LEDC, GPIO, UART).
*
* Mirrors the ruview-gamma crate's SafetyEnvelope::conservative() (ADR-250
* §5/§12): the firmware enforces the same hard caps *independently*, so even a
* compromised or buggy host cannot command an out-of-envelope stimulus.
* Defense in depth: host gate (Rust) AND device gate (this file).
*
* Units: frequency in millihertz (exact integer math — the ±0.1 Hz HIL target
* is ±100 mHz), intensity in percent (0100), duration in seconds.
*/
#ifndef STIM_CORE_H
#define STIM_CORE_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ---- Hard safety envelope (device-side; never widened at runtime) ------- */
typedef struct {
uint32_t min_freq_mhz; /* 36000 = 36.0 Hz */
uint32_t max_freq_mhz; /* 44000 = 44.0 Hz */
uint8_t max_brightness_pct;/* 40 = SafetyEnvelope::conservative cap 0.40 */
uint8_t max_volume_pct; /* 40 */
uint32_t max_duration_s; /* 900 = 15 min */
} stim_envelope_t;
/* The compiled-in conservative envelope (ADR-250 §5). Kconfig may narrow it,
* never widen it (enforced by range limits in Kconfig.projbuild). */
stim_envelope_t stim_envelope_conservative(void);
/* ---- Session state machine ---------------------------------------------- */
typedef enum {
STIM_IDLE = 0, /* outputs off, ready for START */
STIM_RUNNING, /* stimulation active */
STIM_LOCKED, /* emergency-stopped; START refused until UNLOCK */
} stim_state_t;
typedef enum {
STIM_STOP_NONE = 0,
STIM_STOP_COMPLETED, /* duration elapsed (not a safety stop) */
STIM_STOP_HOST, /* host STOP command */
STIM_STOP_BUTTON, /* hardware e-stop button */
STIM_STOP_FAULT, /* internal fault (watchdog, bad state) */
} stim_stop_reason_t;
typedef struct {
uint32_t freq_mhz; /* commanded envelope frequency */
uint8_t brightness_pct; /* LED intensity during ON half-period */
uint8_t volume_pct; /* tone intensity during ON half-period */
uint32_t duration_s; /* session length */
} stim_params_t;
typedef struct {
stim_envelope_t envelope;
stim_state_t state;
stim_params_t active; /* valid when state == RUNNING */
stim_stop_reason_t last_stop;
uint32_t session_seq; /* increments on each START */
uint32_t elapsed_half_periods; /* advanced by the timer ISR */
bool envelope_on; /* current half-period phase */
} stim_ctx_t;
/* Initialize a context with the given envelope, in IDLE. */
void stim_init(stim_ctx_t *ctx, stim_envelope_t envelope);
/* ---- Validation (fail closed) ------------------------------------------- */
typedef enum {
STIM_OK = 0,
STIM_ERR_FREQ_RANGE, /* outside [min,max] mHz */
STIM_ERR_BRIGHTNESS_CAP,
STIM_ERR_VOLUME_CAP,
STIM_ERR_DURATION_CAP,
STIM_ERR_ZERO_DURATION,
STIM_ERR_BUSY, /* START while RUNNING */
STIM_ERR_LOCKED, /* START while LOCKED (e-stop latched) */
STIM_ERR_PARSE, /* malformed command line */
STIM_ERR_UNKNOWN_CMD,
} stim_rc_t;
/* Validate params against the context envelope. Pure; no state change. */
stim_rc_t stim_validate(const stim_ctx_t *ctx, const stim_params_t *p);
/* ---- Transitions (the only mutators) ------------------------------------ */
/* START: validate + transition IDLE->RUNNING. Fails closed on any violation,
* on BUSY, and on LOCKED. */
stim_rc_t stim_start(stim_ctx_t *ctx, const stim_params_t *p);
/* STOP from the host: RUNNING->IDLE (graceful; not latched). */
stim_rc_t stim_stop_host(stim_ctx_t *ctx);
/* Emergency stop (button ISR or fault): any state -> LOCKED. Latched —
* further STARTs are refused until stim_unlock(). Mirrors the Rust
* SafetyMonitor latch (a session must never silently resume). */
void stim_estop(stim_ctx_t *ctx, stim_stop_reason_t why);
/* Operator unlock after an e-stop: LOCKED -> IDLE. */
stim_rc_t stim_unlock(stim_ctx_t *ctx);
/* Timer ISR tick: advance one half-period. Returns true while RUNNING; when
* the session's duration is reached it transitions to IDLE (COMPLETED) and
* returns false. Pure integer math, ISR-safe. */
bool stim_tick(stim_ctx_t *ctx);
/* Half-period length in microseconds for a commanded frequency:
* 500'000'000'000 / freq_mhz / 1000 — exact for the supported range.
* (40.0 Hz = 40000 mHz -> 12'500 us.) */
uint32_t stim_half_period_us(uint32_t freq_mhz);
/* Total half-periods in a session of duration_s at freq_mhz (rounded down). */
uint32_t stim_session_half_periods(uint32_t freq_mhz, uint32_t duration_s);
/* ---- Host command protocol (line-based, UART) ---------------------------
*
* START <freq_mhz> <brightness_pct> <volume_pct> <duration_s>
* STOP
* STATUS
* UNLOCK
* VERSION
*
* stim_parse_line() parses one trimmed line into a command. Pure.
*/
typedef enum {
STIM_CMD_NONE = 0,
STIM_CMD_START,
STIM_CMD_STOP,
STIM_CMD_STATUS,
STIM_CMD_UNLOCK,
STIM_CMD_VERSION,
} stim_cmd_kind_t;
typedef struct {
stim_cmd_kind_t kind;
stim_params_t params; /* valid when kind == STIM_CMD_START */
} stim_cmd_t;
stim_rc_t stim_parse_line(const char *line, stim_cmd_t *out);
/* Human-readable tag for a return code (for "ERR <tag>" replies). */
const char *stim_rc_str(stim_rc_t rc);
/* Human-readable tag for a stop reason (for the session log). */
const char *stim_stop_str(stim_stop_reason_t r);
#ifdef __cplusplus
}
#endif
#endif /* STIM_CORE_H */