# Internals How the firmware works inside. You don't need this unless you're modifying the firmware itself. ## Threads Each thread runs at a fixed rate on a specific CPU core. Higher priority threads can interrupt lower priority ones. | Thread | Frequency | Priority | CPU | Description | Status | |--------|-----------|----------|-----|-------------|--------| | `estop` | 100 Hz | 99 | 7 | Safety watchdog, heartbeat check | Enabled | | `can_rx` | 1000 Hz | 95 | 1 | CAN RX drain for all buses | Enabled | | `can0` | 100 Hz | 94 | 6 | CAN bus 0 motor I/O (L_Leg) | Enabled | | `can1` | 100 Hz | 94 | 5 | CAN bus 1 motor I/O (R_Leg) | Enabled | | `imu` | 500 Hz | 92 | 4 | BNO085 read (built-in fusion) | Enabled | | `policy` | 50 Hz | 90 | 3 | ONNX neural network inference | Enabled | | `can2` | 100 Hz | 86 | 5 | CAN bus 2 motor I/O (L_Arm) | Disabled | | `can3` | 100 Hz | 85 | 5 | CAN bus 3 motor I/O (R_Arm) | Disabled | | `can4` | 100 Hz | 84 | 6 | CAN bus 4 motor I/O (Torso) | Disabled | | `can5` | 100 Hz | 83 | 6 | CAN bus 5 motor I/O | Disabled | | `aggregator` | 100 Hz | 80 | 2 | Merge CAN data into state snapshot | Enabled | | `netrx` | 100 Hz | 50 | 2 | UDP command reception (port 8888) | Enabled | | `nettx` | 100 Hz | 45 | 2 | Protobuf telemetry streaming | Enabled | | `power` | 10 Hz | 30 | 3 | Battery management | Disabled | Priority is Linux `SCHED_FIFO` (1-99). Higher number = more important = can interrupt lower numbers. ### CPU Layout ``` CPU 1: can_rx (High-frequency CAN receive) CPU 2: aggregator, netrx, nettx (Data aggregation + Network) CPU 3: policy, power (Control) CPU 4: imu (IMU sensor) CPU 5: can1, can2, can3 (CAN buses 1-3) CPU 6: can0, can4, can5 (CAN buses 0, 4-5) CPU 7: estop (Safety-critical) ``` Why this layout: - **CPU 7**: E-stop gets its own core so nothing can starve safety - **CPU 1**: CAN RX at 1kHz needs a dedicated core for consistent timing - **CPU 4**: IMU at 500Hz has its own core for low-latency sensor reads - **CPU 5-6**: CAN motor threads spread across two cores - **CPU 2-3**: Network, aggregator, and policy share cores (lower frequency) ### What Each Thread Does **estop** - Watches for problems. If it doesn't see commands for 100ms, or a motor overheats, or the IMU dies, it kills power to the motors. **can_rx** - Runs at 1kHz to drain incoming CAN frames from all buses. This dedicated high-frequency thread ensures motor feedback is captured promptly, preventing packet loss in userspace. Without this, the 100Hz CAN threads might miss feedback frames that arrive between their polling intervals. **imu** - Reads the BNO085 at 500Hz, writes orientation to `g_app.imu`. **can0-5** - Each one talks to motors on one CAN bus. Sends commands, reads back position/velocity/current/temperature. **aggregator** - Combines motor data from all CAN threads into one snapshot. **policy** - Runs the ONNX neural network. Reads sensor state, outputs motor commands. **netrx** - Listens on UDP 8888 for your commands. Parses the protobuf, puts it in a queue for the policy. **nettx** - Sends telemetry back to you at 100Hz. ### Real-Time Stuff At startup, the firmware locks all memory (`mlockall`) so there are no page faults during operation. Each thread is pinned to a specific CPU core so it doesn't get migrated around. ### Thread Lifecycle ```{mermaid} flowchart TD init["SCHEDULER_init()"] --> create subgraph create["SCHEDULER_create_thread()"] c1["Allocate thread slot"] c2["Configure timer with frequency"] c3["Set priority"] end create --> start subgraph start["SCHEDULER_start_thread()"] s1["Start periodic timer"] s2["Set CPU affinity"] s3["Thread enters RUNNING state"] end start --> running running["Thread Running
(periodic loop)"] --> callback["callback()"] callback --> running running --> stop["SCHEDULER_stop_thread()"] stop --> stopped["Thread Stopped"] style create fill:#e3f2fd style start fill:#e8f5e9 style running fill:#fff3e0 ``` --- ## Shared State (g_app) All threads share data through one struct called `g_app`. It has everything: joint positions, IMU data, commands, error flags. ```c typedef struct { /* System Flags */ volatile bool running; volatile bool estop_active; ControlMode_E current_mode; /* Joint State */ struct { float pos[30], vel[30], current[30], temp[30]; } joints; /* IMU State (atomic) */ struct { _Atomic uint32_t seq; float quat[4], ang_vel[3], gravity[3]; } imu; /* Gait & Control */ float gait_phase; GaitMode_E gait_mode; /* Inter-Thread Communication */ struct { ... } comms; /* Thread Management */ struct { ... } threads; } AppContext_S; extern AppContext_S g_app; /* Global instance */ ``` ### How Threads Share Data **Seqlocks** - When one thread writes a snapshot and multiple threads read it. The reader retries if it catches the writer mid-update. | Structure | Writer | Reader(s) | Data | |-----------|--------|-----------|------| | `cmd_snapshot` | policy_thread | CAN threads | `IoCmd_S` motor commands | | `state_snapshot` | aggregator_thread | policy, nettx | `IoState_S` sensor state | **Queues** - When one thread produces data and another consumes it. Lock-free, no blocking. | Queue | Producer | Consumer | Data | |-------|----------|----------|------| | `netcmd_to_policy` | netrx_thread | policy_thread | `NetCmd_S` | | `can_to_aggregator[6]` | CAN threads | aggregator | `IoState_S` | ### Performance | Operation | Time | |-----------|------| | Seqlock read | ~50 ns | | Queue push/pop | ~100 ns | | Atomic load | ~10 ns | No locks, no blocking, no surprises. --- ## Data Flow ```{mermaid} flowchart TD subgraph inputs["Input Threads"] imu["IMU thread
500 Hz
Reads sensor"] netrx["netrx_thread
100 Hz
Receives UDP"] can_in["CAN threads
100 Hz
Talks to motors"] end imu --> imu_data["g_app.imu
(atomic seqlock)"] netrx --> netcmd_queue["netcmd_to_policy
(SPSC queue)"] can_in --> can_queue["can_to_aggregator
(SPSC queues)"] imu_data --> policy netcmd_queue --> policy["policy_thread
50 Hz
Runs ONNX model"] policy --> cmd_snap["cmd_snapshot
(seqlock)
IoCmd_S: motor targets"] cmd_snap --> can_out["CAN threads
(read cmds, write state)"] can_queue --> aggregator can_out --> aggregator["aggregator_thread
Combines state"] aggregator --> state_snap["state_snapshot
(seqlock)
IoState_S: joints, IMU, errors"] state_snap --> nettx["nettx_thread
100 Hz
Sends telemetry"] style inputs fill:#e3f2fd style policy fill:#fff3e0 style aggregator fill:#e8f5e9 ``` --- ## Internal Structs ### IoState_S Sensor data going to the policy. Written by aggregator, read by policy. | Field | Type | Unit | Description | |-------|------|------|-------------| | `joint_pos[30]` | float | rad | Joint positions | | `joint_vel[30]` | float | rad/s | Joint velocities | | `joint_current[30]` | float | A | Motor phase currents | | `joint_temp[30]` | float | °C | Motor temperatures | | `base_quat[4]` | float | - | Orientation quaternion [w,x,y,z] | | `base_omega[3]` | float | rad/s | Angular velocity in body frame | | `base_gravity[3]` | float | - | Gravity vector (normalized) | | `gait_phase` | float | - | Locomotion phase [0-1] | | `battery_voltage` | float | V | Pack voltage | | `motors_enabled` | bool | - | Motor power state | | `emergency_stop` | bool | - | E-stop active | | `timestamp_us` | uint64 | μs | Capture timestamp | ### IoCmd_S Motor commands from policy to CAN threads. | Field | Type | Unit | Description | |-------|------|------|-------------| | `target_pos[30]` | float | rad | Target positions | | `target_vel[30]` | float | rad/s | Target velocities (feedforward) | | `target_torque[30]` | float | Nm | Target torques (feedforward) | | `kp[30]` | float | - | Position gains | | `kd[30]` | float | - | Velocity gains | | `enable_motors` | bool | - | Enable motor power | | `emergency_stop` | bool | - | Trigger E-stop | ### NetCmd_S Commands from network to policy (parsed from your RobotCommand). | Field | Type | Unit | Description | |-------|------|------|-------------| | `body_vel_cmd[3]` | float | m/s, rad/s | Velocity command [vx, vy, vyaw] | | `control_mode` | ControlMode_E | enum | Requested control mode | | `enable` | bool | - | Enable motion | | `emergency_stop` | bool | - | Remote E-stop | | `timestamp_us` | uint64 | μs | Command timestamp | --- ## Important Constants | Name | Value | What it means | |------|-------|---------------| | `DEADMAN_TIMEOUT_US` | 100000 | 100ms without commands = E-stop | | `NETWORK_RX_PORT` | 8888 | Where to send commands | | `NUM_CAN_BUSES` | 6 | Number of CAN interfaces | ## Source Files | File | What it does | |------|--------------| | `source/main.c` | Thread setup in `init_thread_config()` | | `source/app_context/app_context.h` | Global state struct | | `source/lib/io_types.h` | IoState_S, IoCmd_S, NetCmd_S |