Shourya's Project
Project IGNIS: In-Depth Rocket Avionics Architecture

Project IGNIS: In-Depth Rocket Avionics Architecture

Shourya Shourya
August 26, 2026
13 min read
Table of Contents

“In rocketry, software bugs don’t throw exceptions—they create craters. If your state estimation fails at high speeds, gravity wins.”
— Debugging telemetry at 3 AM.


1. Motivation & Mission Profile

Commercial off-the-shelf (COTS) altimeters and flight computers are standard in high-power model rocketry, but they operate primarily as “black boxes.” Most rely on naive barometric pressure differentiation or fixed pyro delay timers, offering little room for custom sensor fusion, low-latency telemetry downlinks, or active control hooks (such as Thrust Vector Control).

Project IGNIS is an ongoing, fully open-source aerospace development platform designed to bridge the gap between hobbyist rocketry and aerospace avionics. The core mission profile demands:

  1. Deterministic State Estimation: Running a high-rate physics loop (100 Hz to 250 Hz) executing an onboard Kalman Filter to accurately estimate altitude, vertical velocity, and acceleration.
  2. Mach Dip & Shockwave Immunity: Preventing false apogee deployment caused by aerodynamic compressibility effects and dynamic pressure spikes.
  3. Fail-Safe Dual Deployment: Independently driving drogue and main parachute deployment channels with hardware-level brownout isolation and continuity monitoring.
  4. Long-Range Real-Time Telemetry: Downlinking serialized binary telemetry packets over a robust LoRa PHY link while logging raw sensor arrays to onboard non-volatile SPI flash memory.

2. Hardware Architecture & Electrical Design

Avionics systems in rocketry experience severe physical and electrical environments: continuous motor vibration, high-G mechanical shocks, thermal dissipation constraints within sealed fiberglass airframes, and electrical noise from inductive pyrotechnic actuators.

2.1 Avionics Subsystem Breakdown

SubsystemComponent / ICInterfaceSpecifications & Role
Main Processing Unit32-Bit ARM Cortex-M4 / ESP32-S3SPI / I2C / UARTRuns the 100 Hz RTOS control loop, Kalman filter matrix math, and FSM
High-Precision BarometerMS5611 / BMP280SPI (Up to 10 MHz)24-bit ΔΣ\Delta\Sigma ADC; altitude resolution down to 10 cm for apogee tracking
High-G IMUBMI088 / MPU6050SPI / I2C16-bit 6-DOF sensor; ±24g\pm 24\text{g} accelerometer and ±2000/s\pm 2000^\circ/\text{s} gyroscope
Telemetry TransceiverSemtech SX1276 / SX1278SPILoRa modulation at 433/868/915 MHz; downlinks live attitude, altitude, and state
Blackbox FlashWinbond W25Q128 (16 MB)Quad-SPI / SPILogs raw, uncompressed 100 Hz sensor frames for post-flight reconstruction
Pyro DriversN-Channel Power MOSFETs + OptoisolatorsGPIO (Logic High)Dual channels with 30V/15A30\text{V} / 15\text{A} rating; fires nichrome e-matches
Power ManagementTPS62160 Step-Down + LP5907 LDOPower RailDual isolated domains: 3.3V3.3\text{V} ultra-low-noise logic and 7.4V7.4\text{V} pyro rail

2.2 Power Distribution & Noise Isolation

A major failure mode in custom flight computers is microcontroller brownout during pyro actuation. An electronic match (e-match) or nichrome wire acts nearly as a direct short when ignited, pulling 2A2\text{A} to 4A4\text{A} instantaneously.

To guarantee zero voltage dip on the digital rails:

  1. Physical Rail Separation: The primary power source (2S LiPo, 7.4V7.4\text{V} nominal) branches immediately into two isolated paths:
    • Actuator Path: Feeds directly to the pyro terminal blocks through high-current traces.
    • Logic Path: Passes through a reverse-protection Schottky diode and an array of low-ESR bulk decoupling capacitors (470μF470\,\mu\text{F} tantalum parallel with 10μF10\,\mu\text{F} ceramic), feeding a high-frequency synchronous step-down buck converter.
  2. Galvanic & Optical Isolation: The gate of each low-side N-channel MOSFET is driven through an optocoupler (e.g., PC817 or dedicated gate driver) with pull-down resistors (10kΩ10\,\text{k}\Omega) ensuring pins remain strictly low during microcontroller bootup and reset cycles.
  3. Continuity Detection: A small sensing resistor network (100kΩ100\,\text{k}\Omega) passes a safe sub-milliamp current (1mA\le 1\,\text{mA}) through the e-match to an ADC pin, allowing the firmware to verify loop continuity before arming the launch pad.

3. Mathematical State Estimation: 1D Linear Kalman Filter

Relying on raw barometric pressure to compute velocity via discrete differentiation (vk=hkhk1Δtv_k = \frac{h_k - h_{k-1}}{\Delta t}) amplifies high-frequency noise, creating wild velocity oscillations that trigger premature parachute deployments.

IGNIS integrates accelerometer readings with barometric pressure using a 1D Linear Kalman Filter (LKF).


3.1 Kinematic State Vector & Process Model

The continuous-time kinematic model of the rocket moving along its vertical axis is governed by Newton’s equations of motion:

x˙(t)=v(t),v˙(t)=a(t),a˙(t)=w(t)\dot{x}(t) = v(t), \quad \dot{v}(t) = a(t), \quad \dot{a}(t) = w(t)

Where w(t)w(t) represents process disturbance (jerk/noise) modeled as zero-mean white Gaussian noise with variance σw2\sigma_w^2.

We define the discrete state vector xk\mathbf{x}_k at sample step kk with interval Δt\Delta t:

xk=[xkvkak]=[Altitude (m)Vertical Velocity (m/s)Vertical Acceleration (m/s2)]\mathbf{x}_k = \begin{bmatrix} x_k \\ v_k \\ a_k \end{bmatrix} = \begin{bmatrix} \text{Altitude } (m) \\ \text{Vertical Velocity } (m/s) \\ \text{Vertical Acceleration } (m/s^2) \end{bmatrix}

The discrete-time state transition matrix F\mathbf{F} is:

F=[1Δt12Δt201Δt001]\mathbf{F} = \begin{bmatrix} 1 & \Delta t & \frac{1}{2}\Delta t^2 \\ 0 & 1 & \Delta t \\ 0 & 0 & 1 \end{bmatrix}

The discrete Process Noise Covariance matrix Q\mathbf{Q} derived from continuous white noise spectral density q=σw2q = \sigma_w^2 is:

Q=q[Δt520Δt48Δt36Δt48Δt33Δt22Δt36Δt22Δt]\mathbf{Q} = q \cdot \begin{bmatrix} \frac{\Delta t^5}{20} & \frac{\Delta t^4}{8} & \frac{\Delta t^3}{6} \\ \frac{\Delta t^4}{8} & \frac{\Delta t^3}{3} & \frac{\Delta t^2}{2} \\ \frac{\Delta t^3}{6} & \frac{\Delta t^2}{2} & \Delta t \end{bmatrix}

3.2 Measurement Model

The sensor payload provides two direct observations:

  1. Barometric altitude: zbaro=44330(1(PP0)15.255)z_{\text{baro}} = 44330 \cdot \left(1 - \left(\frac{P}{P_0}\right)^{\frac{1}{5.255}}\right)
  2. Inertial acceleration: zaccel=aIMU1gz_{\text{accel}} = a_{\text{IMU}} - 1g

The measurement vector zk\mathbf{z}_k and observation matrix H\mathbf{H} are:

zk=[zbarozaccel],H=[100001]\mathbf{z}_k = \begin{bmatrix} z_{\text{baro}} \\ z_{\text{accel}} \end{bmatrix}, \quad \mathbf{H} = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 0 & 1 \end{bmatrix}

The Measurement Covariance matrix R\mathbf{R} incorporates sensor noise variances:

R=[σbaro200σaccel2]\mathbf{R} = \begin{bmatrix} \sigma_{\text{baro}}^2 & 0 \\ 0 & \sigma_{\text{accel}}^2 \end{bmatrix}

3.3 The Recursive Filter Loop

At every sample interval (Δt=10ms\Delta t = 10\,\text{ms} for 100Hz100\,\text{Hz}):

1. Prediction Step:

x^kk1=Fx^k1k1\hat{\mathbf{x}}_{k|k-1} = \mathbf{F} \hat{\mathbf{x}}_{k-1|k-1} Pkk1=FPk1k1FT+Q\mathbf{P}_{k|k-1} = \mathbf{F} \mathbf{P}_{k-1|k-1} \mathbf{F}^T + \mathbf{Q}

2. Innovation & Kalman Gain Calculation:

yk=zkHx^kk1\mathbf{y}_k = \mathbf{z}_k - \mathbf{H}\hat{\mathbf{x}}_{k|k-1} Sk=HPkk1HT+R\mathbf{S}_k = \mathbf{H} \mathbf{P}_{k|k-1} \mathbf{H}^T + \mathbf{R} Kk=Pkk1HTSk1\mathbf{K}_k = \mathbf{P}_{k|k-1} \mathbf{H}^T \mathbf{S}_k^{-1}

3. State & Covariance Update:

x^kk=x^kk1+Kkyk\hat{\mathbf{x}}_{k|k} = \hat{\mathbf{x}}_{k|k-1} + \mathbf{K}_k \mathbf{y}_k Pkk=(IKkH)Pkk1\mathbf{P}_{k|k} = (\mathbf{I} - \mathbf{K}_k \mathbf{H}) \mathbf{P}_{k|k-1}

Because Sk\mathbf{S}_k is a 2×22 \times 2 matrix, its inversion is computed analytically without numerical linear algebra overhead:

Sk1=1S11S22S12S21[S22S12S21S11]\mathbf{S}_k^{-1} = \frac{1}{S_{11}S_{22} - S_{12}S_{21}} \begin{bmatrix} S_{22} & -S_{12} \\ -S_{21} & S_{11} \end{bmatrix}

4. Deterministic Flight State Machine (FSM)

The flight computer software architecture uses a strict finite-state machine. State transitions require cross-validation across multiple sensor thresholds and timing lockouts.

[ PAD_IDLE ]

     │  Launch Detect: (a_z > 25 m/s² for 100 ms) AND (Altitude > 10 m)

[ BOOST ]

     │  Motor Burnout: (a_z <= 0 m/s²) AND (Flight Time > Min Burn Time)

[ COAST (Mach Lockout Active) ]

     │  Apogee Detect: (v_z <= 0 m/s) AND (Baro Altitude Decreasing)

[ APOGEE -> EJECT DROGUE ]

     │  Drogue Descent: Terminal Velocity ≈ 25 m/s

[ DROGUE_DESCENT ]

     │  Main Altitude Threshold: (Altitude <= 150 m AGL)

[ MAIN -> EJECT MAIN ]

     │  Main Descent: Terminal Velocity ≈ 5 m/s

[ MAIN_DESCENT ]

     │  Landing Detect: (|v_z| < 0.5 m/s) AND (|a_z| < 1.5 m/s²) for 5 seconds

[ LANDED (LoRa Beacon Active) ]

Transition Validation Rules

  1. PAD_IDLE \rightarrow BOOST: Requires sustained vertical acceleration >2.5g> 2.5\text{g} (24.5m/s224.5\,\text{m/s}^2) for 1010 consecutive sample cycles (100ms100\,\text{ms}) combined with a barometric altitude gain >10m> 10\,\text{m} above pad level.
  2. BOOST \rightarrow COAST: Triggered when motor thrust ceases and vertical acceleration drops below zero (az0m/s2a_z \le 0\,\text{m/s}^2).
  3. COAST \rightarrow APOGEE: Filtered vertical velocity vz0.0m/sv_z \le 0.0\,\text{m/s} verified across 55 consecutive cycles, provided the flight duration exceeds the motor’s minimum burnout timer.
  4. APOGEE \rightarrow DROGUE_DESCENT: Drogue pyro channel is energized for 1000ms1000\,\text{ms}.
  5. DROGUE_DESCENT \rightarrow MAIN_DESCENT: Filtered altitude drops below the preset recovery ceiling (150m150\,\text{m} AGL). Main pyro channel is energized.
  6. MAIN_DESCENT \rightarrow LANDED: Filtered velocity falls within ±0.5m/s\pm 0.5\,\text{m/s} and altitude variation remains within ±1m\pm 1\,\text{m} over 5seconds5\,\text{seconds}.

5. C++ Firmware Implementation

Below is a production-grade implementation of the non-blocking state machine and analytical Kalman Filter:

#include <stdint.h>
#include <stdbool.h>
#include <math.h>
 
// Flight States
typedef enum {
    STATE_PAD_IDLE = 0,
    STATE_BOOST,
    STATE_COAST,
    STATE_APOGEE,
    STATE_DROGUE_DESCENT,
    STATE_MAIN_DESCENT,
    STATE_LANDED
} FlightState_t;
 
// Kalman Filter Structure
typedef struct {
    float x;      // Altitude (m)
    float v;      // Velocity (m/s)
    float a;      // Acceleration (m/s^2)
    float P[3][3];
    float Q_var;
    float R_baro;
    float R_accel;
} KalmanFilter1D_t;
 
// Global Flight Data
FlightState_t current_state = STATE_PAD_IDLE;
KalmanFilter1D_t kf;
uint32_t launch_timestamp = 0;
uint32_t state_entry_time = 0;
float ground_pressure_pa = 101325.0f;
 
void kalman_init(KalmanFilter1D_t *filter, float q_noise, float r_baro, float r_acc) {
    filter->x = 0.0f;
    filter->v = 0.0f;
    filter->a = 0.0f;
    filter->Q_var = q_noise;
    filter->R_baro = r_baro;
    filter->R_accel = r_acc;
 
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            filter->P[i][j] = (i == j) ? 1.0f : 0.0f;
        }
    }
}
 
void kalman_predict_and_update(KalmanFilter1D_t *f, float z_baro, float z_acc, float dt) {
    // 1. Predict State
    float x_pred = f->x + f->v * dt + 0.5f * f->a * dt * dt;
    float v_pred = f->v + f->a * dt;
    float a_pred = f->a;
 
    // 2. Predict Covariance (P = F * P * F^T + Q)
    float dt2 = dt * dt;
    float dt3 = dt2 * dt;
    float dt4 = dt3 * dt;
 
    float p00 = f->P[0][0] + dt * (f->P[1][0] + f->P[0][1]) + dt2 * (f->P[2][0] + f->P[0][2] + f->P[1][1]) + f->Q_var * (dt4 / 4.0f);
    float p01 = f->P[0][1] + dt * (f->P[1][1] + f->P[0][2]) + dt2 * f->P[2][1] + f->Q_var * (dt3 / 2.0f);
    float p02 = f->P[0][2] + dt * f->P[1][2] + f->Q_var * (dt2 / 2.0f);
    float p11 = f->P[1][1] + dt * (f->P[2][1] + f->P[1][2]) + f->Q_var * dt2;
    float p12 = f->P[1][2] + dt * f->P[2][2] + f->Q_var * dt;
    float p22 = f->P[2][2] + f->Q_var;
 
    // 3. Compute Innovation Matrix S = H * P * H^T + R
    float S00 = p00 + f->R_baro;
    float S01 = p02;
    float S10 = p02;
    float S11 = p22 + f->R_accel;
 
    // 4. Invert 2x2 Matrix S
    float det = (S00 * S11) - (S01 * S10);
    if (fabsf(det) < 1e-7f) return;
    float invDet = 1.0f / det;
    float iS00 =  S11 * invDet;
    float iS01 = -S01 * invDet;
    float iS10 = -S10 * invDet;
    float iS11 =  S00 * invDet;
 
    // 5. Compute Kalman Gain K = P * H^T * inv(S)
    float K[3][2];
    K[0][0] = p00 * iS00 + p02 * iS10;
    K[0][1] = p00 * iS01 + p02 * iS11;
    K[1][0] = p01 * iS00 + p12 * iS10;
    K[1][1] = p01 * iS01 + p12 * iS11;
    K[2][0] = p02 * iS00 + p22 * iS10;
    K[2][1] = p02 * iS01 + p22 * iS11;
 
    // 6. Update State with Measurement Residuals
    float y_baro  = z_baro - x_pred;
    float y_accel = z_acc  - a_pred;
 
    f->x = x_pred + K[0][0] * y_baro + K[0][1] * y_accel;
    f->v = v_pred + K[1][0] * y_baro + K[1][1] * y_accel;
    f->a = a_pred + K[2][0] * y_baro + K[2][1] * y_accel;
 
    // 7. Update Covariance Matrix P = (I - K * H) * P
    f->P[0][0] = (1.0f - K[0][0]) * p00 - K[0][1] * p02;
    f->P[0][1] = (1.0f - K[0][0]) * p01 - K[0][1] * p12;
    f->P[0][2] = (1.0f - K[0][0]) * p02 - K[0][1] * p22;
    f->P[1][0] = -K[1][0] * p00 + p01 - K[1][1] * p02;
    f->P[1][1] = -K[1][0] * p01 + p11 - K[1][1] * p12;
    f->P[1][2] = -K[1][0] * p02 + p12 - K[1][1] * p22;
    f->P[2][0] = -K[2][0] * p00 + (1.0f - K[2][1]) * p02;
    f->P[2][1] = -K[2][0] * p01 + (1.0f - K[2][1]) * p12;
    f->P[2][2] = -K[2][0] * p02 + (1.0f - K[2][1]) * p22;
}
 
void process_flight_fsm(float raw_accel_z, float raw_pressure_pa, uint32_t now_ms) {
    float dt = 0.01f; // 100 Hz fixed loop
    float raw_altitude = 44330.0f * (1.0f - powf(raw_pressure_pa / ground_pressure_pa, 0.190295f));
    float net_accel = raw_accel_z - 9.80665f;
 
    kalman_predict_and_update(&kf, raw_altitude, net_accel, dt);
 
    switch (current_state) {
        case STATE_PAD_IDLE:
            if (kf.a > 24.5f && kf.x > 8.0f) {
                current_state = STATE_BOOST;
                launch_timestamp = now_ms;
            }
            break;
 
        case STATE_BOOST:
            if (kf.a <= 0.0f && (now_ms - launch_timestamp) > 500) {
                current_state = STATE_COAST;
            }
            break;
 
        case STATE_COAST:
            // Apogee Condition: Zero vertical velocity crossing
            if (kf.v <= 0.0f && (now_ms - launch_timestamp) > 1500) {
                fire_pyro_channel(0); // Fire Drogue
                current_state = STATE_DROGUE_DESCENT;
                state_entry_time = now_ms;
            }
            break;
 
        case STATE_DROGUE_DESCENT:
            if (kf.x <= 150.0f && (now_ms - state_entry_time) > 2000) {
                fire_pyro_channel(1); // Fire Main
                current_state = STATE_MAIN_DESCENT;
            }
            break;
 
        case STATE_MAIN_DESCENT:
            if (fabsf(kf.v) < 0.4f && (now_ms - launch_timestamp) > 10000) {
                current_state = STATE_LANDED;
            }
            break;
 
        case STATE_LANDED:
            enable_locator_beacon();
            break;
            
        default:
            break;
    }
}

To maximize range and link reliability under high Doppler shifts and aerodynamic flutter, telemetry packets are bit-packed into compact binary structs rather than ASCII/JSON strings.

#pragma pack(push, 1)
typedef struct {
    uint8_t  sync_byte;      // 0xAA Framing Marker
    uint32_t timestamp_ms;   // Elapsed flight time
    uint8_t  state;          // Current FSM state ID
    int32_t  altitude_cm;    // Filtered altitude in centimeters
    int16_t  velocity_cms;   // Filtered velocity in cm/s
    int16_t  accel_mg;       // Filtered vertical acceleration in milli-g
    int16_t  gyro_roll;      // Roll rate in 0.1 deg/s
    int16_t  gyro_pitch;     // Pitch rate in 0.1 deg/s
    uint16_t battery_mv;     // Battery rail voltage in mV
    uint8_t  pyro_status;    // Bitmask of continuity & firing status
    uint16_t crc16;          // CCITT-16 Checksum
} TelemetryPacket_t;
#pragma pack(pop)

With a 2424-byte payload at Spreading Factor 7 (SF7) and 125kHz125\,\text{kHz} Bandwidth, transmission time on air is 38ms\approx 38\,\text{ms}, allowing a reliable 10Hz10\,\text{Hz} downlink rate while leaving the radio in low-power receive mode for ground commands.


7. Field Challenges & Hard-Earned Engineering Lessons

💥 Challenge 1: Pyro Transient Brownout & Ground Bounce

  • Root Cause: Firing e-matches pulled a instantaneous 3.5A3.5\text{A} current spike through the ground trace. The parasitic inductance of the PCB trace caused a ground voltage bounce, raising the digital ground plane and tripping the MCU’s internal Brown-Out Reset (BOR).
  • Solution: Redesigned the PCB with a true Star Ground topology. The high-current pyro return path bypasses the digital ground plane entirely and connects directly to the negative battery terminal tab. Added a high-speed 15V15\text{V} TVS diode across the e-match terminals to clamp inductive spikes.

🌪️ Challenge 2: Transonic Shockwaves & Mach Dip Artifacts

  • Root Cause: As high-power rockets approach transonic velocities (M0.81.1M \approx 0.8\text{--}1.1), localized expansion fans and shockwave boundary layers create pressure drops over the airframe sampling holes. The barometer registers a sudden fictitious altitude drop and subsequent rise, falsely indicating apogee.
  • Solution: Implemented dual-stage gating:
    1. Dynamic Noise Scaling: During high-acceleration phases, the measurement covariance RbaroR_{\text{baro}} is dynamically increased by 10×10\times, forcing the Kalman filter to trust inertial integration over barometric data.
    2. Minimum Flight Duration Lockout: The FSM strictly disables apogee deployment until after the motor’s known minimum burn time has elapsed.

📡 Challenge 3: Flash Logging Latency Spikes

  • Root Cause: Erasing and writing pages to SPI NOR flash memory introduces non-deterministic delays (5ms5\,\text{ms} to 40ms40\,\text{ms} per sector erase), stalling the main control loop.
  • Solution: Implemented a Double-Buffered Ping-Pong DMA Queue. The main sensor loop writes fixed-size records into RAM Buffer A. When full, DMA drains Buffer A to SPI flash in the background while the core seamlessly fills Buffer B.

8. Roadmap & Next Milestones

  • Thrust Vector Control (TVC): Integration of a dual-servo gimbal ring driven by a quaternion-based PID controller for active ascent stabilization.
  • Hardware-in-the-Loop (HIL) Test Chamber: Real-time simulation of flight pressure profiles inside a software-controlled pneumatic vacuum chamber.
  • Web-Based Ground Station: WebGL-powered 3D rocket orientation display, trajectory mapping, and live telemetry graphs.

9. Open Source Repository

All hardware schematics (KiCad), PCB Gerber files, and embedded C++ source code are freely available:

👉 GitHub: Shourya-x/Project-ignis