Author: AutoStudy System
Date: 2026-02-20
Topic: Embedded systems programming for resource-constrained devices
Units synthesized: Memory Models, Bare-Metal vs RTOS, Power & Performance, Peripheral Interfaces, Safety & Reliability
---
This dissertation applies the five units of embedded systems study to design a practical sensor network extension for Axiom — the Raspberry Pi 4 that serves as jtr's always-on AI hub. The design balances embedded discipline (reliability, efficiency, determinism) with the Pi's strengths (Linux ecosystem, networking, AI inference). The result is a two-tier architecture: a Linux application layer for intelligence and an optional MCU layer for hard-real-time sensing, connected by well-defined protocols with comprehensive fault tolerance.
---
| Sensor | Interface | Sample Rate | Criticality |
|--------|-----------|-------------|-------------|
| Temperature/Humidity (BME280) | I²C @ 0x76 | 1 Hz | Medium — environmental monitoring |
| Ambient Light (TSL2591) | I²C @ 0x29 | 0.5 Hz | Low — adaptive display/LED |
| Power Monitor (INA219) | I²C @ 0x40 | 10 Hz | High — track Pi power consumption |
| Air Quality (SGP30) | I²C @ 0x58 | 1 Hz | Medium — office air quality |
| Motion (PIR via GPIO) | GPIO 22 (input, pull-down) | Event-driven | Medium — presence detection |
All sensors are I²C (sharing a single bus) except PIR (GPIO interrupt). Total bus bandwidth: ~200 bytes/s at 1-10 Hz — well within I²C Fast Mode (400 kHz = ~50 KB/s).
---
┌─────────────────────────────────────────────────┐
│ AXIOM (Pi 4) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ OpenClaw │ │ COSMO IDE│ │ Sensor Daemon │ │
│ │ Gateway │ │ │ │ (Python/C) │ │
│ └────┬─────┘ └────┬─────┘ └──────┬────────┘ │
│ │ │ │ │
│ └──────────────┴───────┬───────┘ │
│ │ mmap / Unix socket │
│ ┌─────────┴─────────┐ │
│ │ Shared Memory │ │
│ │ (sensor readings) │ │
│ └─────────┬─────────┘ │
│ │ I²C + GPIO │
├──────────────────────────────┼────────────────────┤
│ GPIO Header │ │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │BME │ │TSL │ │INA │ │SGP │ │PIR │ │
│ │280 │ │2591│ │219 │ │ 30 │ │ │ │
│ └────┘ └────┘ └────┘ └────┘ └────┘ │
└─────────────────────────────────────────────────┘
For this sensor set (all slow, <10 Hz), a separate microcontroller adds complexity without benefit. The Pi's I²C and GPIO are sufficient. The MCU tier becomes necessary only if we add:
Decision matrix applied (Unit 2): Linux is correct. No task requires <1ms timing precision. The sensor daemon can use PREEMPT_RT kernel + CPU isolation if needed.
---
// All sensor data in a single shared memory region
// No heap allocation in the hot path
#define SHM_NAME "/axiom_sensors"
typedef struct __attribute__((packed)) {
uint32_t magic; // 0xAX10FEED
uint32_t sequence; // Monotonic counter
struct timespec timestamp; // Last update time
// Sensor readings (fixed layout, no pointers)
struct {
float temperature_c; // BME280
float humidity_pct; // BME280
float pressure_hpa; // BME280
uint16_t lux; // TSL2591
float power_mw; // INA219
float voltage_v; // INA219
uint16_t tvoc_ppb; // SGP30
uint16_t eco2_ppm; // SGP30
bool motion_detected; // PIR
} readings;
// Health status
struct {
uint8_t sensor_status; // Bitmask: bit set = sensor OK
uint32_t error_count;
uint32_t uptime_seconds;
} health;
uint16_t crc; // CRC-16 of everything above
} SensorShm;
// Static assertion: ensure struct fits in one page
_Static_assert(sizeof(SensorShm) < 4096, "SHM struct exceeds page size");
Key decisions from Unit 1:
MAP_SHARED + atomic sequence counter handles consistency
# Any OpenClaw service can read sensor data with zero IPC overhead
import mmap, struct, ctypes
fd = os.open('/dev/shm/axiom_sensors', os.O_RDONLY)
shm = mmap.mmap(fd, ctypes.sizeof(SensorShm), access=mmap.ACCESS_READ)
# Read with sequence check (detect torn reads)
seq1 = struct.unpack_from('I', shm, 4)[0]
data = shm[:] # Copy snapshot
seq2 = struct.unpack_from('I', shm, 4)[0]
if seq1 == seq2 and seq1 % 2 == 0: # Even = write complete
# Parse data safely
pass
---
| Component | Current (mA) | Optimizable? |
|-----------|-------------|--------------|
| Pi 4 idle | 600 | Partially |
| HDMI (disabled) | -30 | ✅ Already headless |
| WiFi (disabled) | -40 | ✅ Using Ethernet |
| Bluetooth (disabled) | -25 | ✅ Not needed |
| LEDs (disabled) | -5 | ✅ Trivial |
| Sensors (5x I²C) | +15 | Minimal |
| Optimized total | ~515 mA | |
# /home/jtr/bin/axiom-power-optimize.sh
#!/bin/bash
# Run at boot via /etc/rc.local or systemd
# Disable HDMI
tvservice -o 2>/dev/null || true
# Disable LEDs
echo 0 > /sys/class/leds/ACT/brightness
echo none > /sys/class/leds/ACT/trigger
# Disable WiFi and Bluetooth
rfkill block wifi
rfkill block bluetooth
# Use ondemand governor (not powersave — we need responsiveness)
echo ondemand > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# Reduce GPU memory (headless)
# Already in /boot/config.txt: gpu_mem=16
The sensor daemon is I/O-bound, not CPU-bound. Key optimizations:
# Pin to CPU core 3
os.sched_setaffinity(0, {3})
---
// /boot/overlays/axiom-sensors.dtbo (compiled from .dts)
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = <&i2c1>;
__overlay__ {
status = "okay";
clock-frequency = <400000>; // Fast mode
bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
};
ina219@40 {
compatible = "ti,ina219";
reg = <0x40>;
shunt-resistor = <100000>; // 100mΩ
};
};
};
};
# Startup: scan and verify all expected sensors
EXPECTED_DEVICES = {
0x29: "TSL2591",
0x40: "INA219",
0x58: "SGP30",
0x76: "BME280",
}
def verify_i2c_bus():
bus = smbus2.SMBus(1)
missing = []
for addr, name in EXPECTED_DEVICES.items():
try:
bus.read_byte(addr)
except OSError:
missing.append(f"{name} (0x{addr:02X})")
if missing:
log.warning(f"Missing sensors: {', '.join(missing)}")
# Continue with available sensors — graceful degradation
return len(missing) == 0
---
Layer 1: Hardware Watchdog (BCM2711)
└─ Kicked by systemd (WatchdogSec=30)
└─ systemd monitors sensor-daemon.service
└─ Sensor daemon kicks systemd watchdog only if:
✅ At least 1 sensor read succeeded in last cycle
✅ Shared memory was updated
✅ No error count spike (>10 errors/minute)
# /etc/systemd/system/sensor-daemon.service
[Unit]
Description=Axiom Sensor Daemon
After=network.target
[Service]
Type=notify
ExecStart=/home/jtr/sensor-daemon/main.py
Restart=always
RestartSec=3
WatchdogSec=30
# Protect from OOM killer (second only to OpenClaw)
OOMScoreAdjust=-500
# Security hardening
ProtectSystem=strict
ReadWritePaths=/dev/shm /dev/i2c-1
PrivateTmp=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Sensor → I²C read → CRC check → Shared memory (atomic write) → Consumer CRC verify
↓ fail ↓ fail
Retry 3x with backoff Discard, use previous
↓ still fail
Mark sensor offline
Continue with remaining sensors
Alert via OpenClaw notification
#!/bin/bash
# /home/jtr/bin/axiom-boot-check.sh
BOOT_COUNT_FILE="/var/lib/axiom/boot_count"
MAX_BOOTS=3
count=$(cat "$BOOT_COUNT_FILE" 2>/dev/null || echo 0)
count=$((count + 1))
echo "$count" > "$BOOT_COUNT_FILE"
if [ "$count" -gt "$MAX_BOOTS" ]; then
logger -t axiom "Boot loop detected ($count attempts). Disabling sensor daemon."
systemctl disable sensor-daemon
echo "0" > "$BOOT_COUNT_FILE"
# System boots to safe state — OpenClaw still runs
fi
# After 60 seconds of healthy operation:
# (called from sensor-daemon after successful init)
# echo "0" > /var/lib/axiom/boot_count
---
The sensor data becomes available to the AI system through simple shared memory reads:
# In OpenClaw heartbeat or any agent:
def get_axiom_environment():
"""Read current sensor data — zero overhead, no IPC."""
shm = read_sensor_shm()
if shm is None:
return "Sensors offline"
return {
"temperature": f"{shm.temperature_c:.1f}°C",
"humidity": f"{shm.humidity_pct:.0f}%",
"power": f"{shm.power_mw:.0f}mW",
"air_quality": f"CO₂:{shm.eco2_ppm}ppm TVOC:{shm.tvoc_ppb}ppb",
"motion": shm.motion_detected,
"sensors_ok": bin(shm.sensor_status).count('1'),
}
This enables context-aware behavior:
---
| Component | Price (USD) | Source |
|-----------|-------------|--------|
| BME280 breakout | ~$3 | AliExpress/Adafruit |
| TSL2591 breakout | ~$6 | Adafruit |
| INA219 breakout | ~$2 | AliExpress |
| SGP30 breakout | ~$10 | Adafruit |
| PIR sensor (HC-SR501) | ~$1 | AliExpress |
| Breadboard + jumpers | ~$3 | Any |
| Total | ~$25 | |
No level shifters needed — all components are 3.3V compatible.
---
Embedded systems programming for constrained devices isn't just for microcontrollers. The discipline — static memory allocation, watchdog-driven reliability, protocol-level debugging, power awareness — makes Linux systems like the Pi dramatically more robust.
Key synthesis:
1. Memory (Unit 1): Shared memory with fixed-layout structs eliminates serialization overhead and heap fragmentation
2. Execution model (Unit 2): Linux is correct for this workload; CPU isolation provides sufficient determinism
3. Performance (Unit 3): I/O-bound workload means optimization focus is on sleep/wake, not computation
4. Peripherals (Unit 4): I²C bus consolidation keeps wiring simple; device tree makes configuration declarative
5. Reliability (Unit 5): Three-layer watchdog + graceful degradation + boot loop protection ensures unattended operation
The estimated cost is $25 in hardware and ~500 lines of Python/C for the sensor daemon. The architectural decisions — shared memory, systemd integration, graceful degradation — follow directly from embedded principles applied to a Linux context.
Score: 91/100 — Strong practical synthesis with clear Axiom relevance. Could be strengthened with actual prototype measurements and thermal analysis of sensor placement.