Agent skill
safety-critical-embedded
Safety-critical embedded system design patterns. Use when building dual-core safety monitors, failsafe systems, watchdog implementations, or any system where failure could cause harm. Covers isolation, fault detection, and emergency procedures.
Install this agent skill to your Project
npx add-skill https://github.com/mojoatomic/triton/tree/main/.claude/skills/safety-critical-embedded
SKILL.md
Safety-Critical Embedded Systems
Design patterns for embedded systems where failure modes must be managed and safety cannot be compromised.
Core Principle: Defense in Depth
No single point of failure. Every safety mechanism has a backup.
┌─────────────────────────────────────────────────────────────┐
│ SAFETY LAYERS │
├─────────────────────────────────────────────────────────────┤
│ Layer 1: Software assertions (P10_ASSERT) │
│ Layer 2: Runtime fault detection (safety monitor) │
│ Layer 3: Hardware watchdog (independent timer) │
│ Layer 4: Physical failsafe (mechanical, electrical) │
└─────────────────────────────────────────────────────────────┘
Dual-Core Architecture
Isolate safety-critical code from complex logic.
┌─────────────────────────────────────────────────────────────┐
│ CORE 0 (Safety) │ CORE 1 (Application) │
│ ───────────────── │ ──────────────────── │
│ • Cannot be blocked │ • Can be complex │
│ • Minimal code │ • Runs control algorithms │
│ • Feeds watchdog │ • Reads sensors │
│ • Monitors faults │ • Updates actuators │
│ • Triggers emergency │ • State machines │
│ • 100 Hz fixed loop │ • 50 Hz or variable │
│ │ │
│ If Core 1 dies, Core 0 │ Core 1 checks │
│ still triggers failsafe │ is_emergency_active() │
└─────────────────────────────────────────────────────────────┘
↕ Shared State (volatile, atomic) ↕
Core 0 Requirements
- Maximum 200 lines total
- No blocking calls
- No dynamic memory
- No complex logic
- Fixed execution time
- Always feeds watchdog
// Core 0 main loop - MINIMAL
void core0_main(void) {
safety_init();
const uint32_t period_us = 10000; // 100 Hz
uint32_t next_us = time_us_32();
while (1) {
watchdog_update();
safety_monitor_run();
if (g_emergency_active) {
emergency_maintain();
}
// Fixed timing
next_us += period_us;
while (time_us_32() < next_us) {
tight_loop_contents();
}
}
}
Cross-Core Communication
Use volatile variables with atomic semantics:
// Shared state - declared volatile
static volatile uint32_t g_core1_heartbeat = 0;
static volatile bool g_emergency_active = false;
static volatile FaultFlags_t g_faults = {0};
// Core 1 increments heartbeat each loop
void core1_loop(void) {
g_core1_heartbeat++; // Atomic on ARM Cortex-M
// ... rest of loop
}
// Core 0 detects stall
void check_core1_health(void) {
static uint32_t last_seen = 0;
static uint32_t stall_count = 0;
if (g_core1_heartbeat == last_seen) {
stall_count++;
if (stall_count > STALL_THRESHOLD) {
trigger_emergency(FAULT_CORE1_STALL);
}
} else {
stall_count = 0;
}
last_seen = g_core1_heartbeat;
}
Fault Detection
Fault Categories
typedef union {
struct {
uint16_t signal_lost : 1; // No RC signal
uint16_t low_battery : 1; // Below threshold
uint16_t leak_detected : 1; // Water ingress
uint16_t over_temp : 1; // Thermal limit
uint16_t depth_exceeded : 1; // Beyond max depth
uint16_t pitch_exceeded : 1; // Beyond safe angle
uint16_t core1_stall : 1; // Application hung
uint16_t watchdog_warn : 1; // Near timeout
uint16_t sensor_fault : 1; // Invalid readings
uint16_t reserved : 7;
} bits;
uint16_t all;
} FaultFlags_t;
Detection Patterns
// Signal loss - timeout based
void check_signal(uint32_t now_ms) {
if ((now_ms - g_last_valid_signal_ms) > SIGNAL_TIMEOUT_MS) {
set_fault(FAULT_SIGNAL_LOST);
}
}
// Threshold - hysteresis to prevent flapping
void check_battery(void) {
uint16_t mv = adc_read_battery();
if (mv < BATT_CRITICAL_MV) {
set_fault(FAULT_LOW_BATTERY);
} else if (mv > BATT_RECOVERY_MV) {
clear_fault(FAULT_LOW_BATTERY); // Only if recoverable
}
}
// Rate of change - detect sudden anomalies
void check_depth_rate(int32_t depth_cm, float dt) {
static int32_t prev_depth = 0;
int32_t rate = (depth_cm - prev_depth) / dt;
if (abs(rate) > MAX_DESCENT_RATE_CM_S) {
set_fault(FAULT_DESCENT_RATE);
}
prev_depth = depth_cm;
}
Emergency Procedures
Atomic Emergency State
Once triggered, emergency cannot be cancelled except by power cycle:
static volatile bool g_emergency_active = false;
static volatile uint8_t g_emergency_reason = 0;
void trigger_emergency(uint8_t reason) {
// Atomic set - cannot be undone
g_emergency_active = true;
g_emergency_reason = reason;
// Immediate safe state
actuators_safe_state();
// Log for post-mortem
log_event(EVT_EMERGENCY, reason);
}
bool is_emergency_active(void) {
return g_emergency_active;
}
Safe State Definition
Define what "safe" means for each actuator:
void actuators_safe_state(void) {
// Motors: stop
motor_set_speed(MOTOR_PUMP, 0);
motor_set_speed(MOTOR_PROP, 0);
// Valves: open (vent pressure)
valve_open(VALVE_VENT);
// Servos: neutral or safe position
servo_set(SERVO_RUDDER, SERVO_NEUTRAL);
servo_set(SERVO_ELEVATOR, SERVO_FULL_UP);
// Outputs: disable high-power
gpio_put(PIN_ENABLE_POWER, 0);
}
Emergency Maintenance Loop
Continuously enforce safe state in case something tries to override:
void emergency_maintain(void) {
// Re-assert safe state every cycle
actuators_safe_state();
// Blink LED fast to indicate emergency
static uint32_t blink_timer = 0;
if (++blink_timer % 10 == 0) {
gpio_xor_mask(1 << PIN_LED);
}
// Sound alarm if equipped
buzzer_pattern(PATTERN_SOS);
}
Watchdog Implementation
Hardware Watchdog
Use the MCU's hardware watchdog, not a software timer:
void safety_init(void) {
// Enable hardware watchdog - 1 second timeout
watchdog_enable(1000, true);
}
void safety_monitor_run(void) {
// Only feed if no faults - deliberate reset on fault
if (g_faults.all == 0) {
watchdog_update();
}
// Check all conditions...
}
Watchdog Starvation
If Core 0 detects an unrecoverable fault, stop feeding the watchdog:
void check_critical_faults(void) {
uint16_t critical_mask = FAULT_LEAK | FAULT_CORE1_STALL | FAULT_SENSOR;
if (g_faults.all & critical_mask) {
// Stop feeding watchdog - force reset
// Log will survive if in persistent memory
log_event(EVT_WATCHDOG_STARVATION, g_faults.all);
while (1) {
// Wait for hardware reset
tight_loop_contents();
}
}
}
Startup Sequence
Power-On Self-Test
bool power_on_self_test(void) {
bool pass = true;
// Check each subsystem
pass &= test_memory_integrity();
pass &= test_watchdog_functional();
pass &= test_sensors_responding();
pass &= test_actuators_range();
pass &= test_communication_link();
if (!pass) {
// Do not proceed - stay in safe state
log_event(EVT_POST_FAILED, get_post_failures());
actuators_safe_state();
}
return pass;
}
Core Launch Handshake
Verify Core 1 actually started:
#define CORE1_READY_MAGIC 0xC0DE1001
// In main()
int main(void) {
core0_init();
multicore_launch_core1(core1_main);
// Wait for handshake with timeout
uint32_t timeout = 0;
while (!multicore_fifo_rvalid() && timeout < 1000) {
sleep_ms(1);
timeout++;
}
if (timeout >= 1000) {
trigger_emergency(FAULT_CORE1_LAUNCH);
}
uint32_t magic = multicore_fifo_pop_blocking();
if (magic != CORE1_READY_MAGIC) {
trigger_emergency(FAULT_CORE1_LAUNCH);
}
// Core 1 is running, start safety monitor
core0_main();
}
// In core1_main()
void core1_main(void) {
core1_init();
// Signal ready
multicore_fifo_push_blocking(CORE1_READY_MAGIC);
// Run application
application_loop();
}
Testing Safety Systems
Fault Injection
#ifdef TEST_MODE
void inject_fault(FaultType_t fault) {
switch (fault) {
case FAULT_SIGNAL_LOST:
g_last_valid_signal_ms = 0; // Force timeout
break;
case FAULT_LOW_BATTERY:
g_test_battery_mv = 5000; // Force low reading
break;
case FAULT_CORE1_STALL:
// Core 1 test: stop incrementing heartbeat
break;
}
}
#endif
Safety Monitor Tests
Every fault condition must have a test proving it triggers emergency:
void test_signal_loss_triggers_emergency(void) {
// Setup
g_emergency_active = false;
g_last_valid_signal_ms = time_ms() - SIGNAL_TIMEOUT_MS - 100;
// Act
safety_monitor_run();
// Assert
TEST_ASSERT(g_faults.bits.signal_lost == 1);
TEST_ASSERT(g_emergency_active == true);
}
Summary
Safety-critical systems require:
- Isolation - Safety code runs independently of application
- Simplicity - Safety code is minimal and verifiable
- Redundancy - Multiple layers detect the same faults
- Fail-safe - Default state is always safe
- Testability - Every fault path is tested
- Auditability - All events are logged for post-mortem
Never compromise safety for features. If in doubt, trigger emergency and surface.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
documentation-standards
Apply professional documentation standards. Use when writing README files, commit messages, code comments, technical docs, or any user-facing text. Enforces evidence-based claims, no marketing language.
power-of-10
NASA/JPL Power of 10 coding rules for safety-critical C code. Use when writing embedded C firmware, safety-critical systems, aerospace code, or any project requiring high reliability. Enforces static analysis, bounded loops, no dynamic memory.
github-hygiene
Enforces strict GitHub workflow hygiene and best practices. Use this skill for ALL git and GitHub operations including commits, branches, pull requests, and repository management. Core rules - NEVER commit directly to main/master, ALWAYS work on feature branches, use GitHub MCP for all GitHub API operations (issues, PRs, reviews), require PRs for all merges. Triggers on any git command, GitHub operation, or when starting new feature work.
handoff
Compact the current conversation into a handoff document for another agent to pick up.
setup-pre-commit
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.
obsidian-vault
Search, create, and manage notes in the Obsidian vault with wikilinks and index notes. Use when user wants to find, create, or organize notes in Obsidian.
Didn't find tool you were looking for?