PROJECTOCCUPYMARS
Published © Apache-2.0

Agni Flight Computer V2

Aerospace-grade flight computer with real-time telemetry, sensor fusion, data logging, and GUI control.

AdvancedFull instructions provided2 hours227
Agni Flight Computer V2

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
Grove - Barometer Sensor (BMP280)
Seeed Studio Grove - Barometer Sensor (BMP280)
×1
Grove - Barometer Sensor (BMP280)
Seeed Studio Grove - Barometer Sensor (BMP280)
×1
SparkFun Transceiver Breakout - nRF24L01+ (RP-SMA)
SparkFun Transceiver Breakout - nRF24L01+ (RP-SMA)
×1
Buzzer
Buzzer
×1
Inertial Measurement Unit (IMU) (6 deg of freedom)
Inertial Measurement Unit (IMU) (6 deg of freedom)
×1

Software apps and online services

Arduino IDE
Arduino IDE
PlatformIO IDE
PlatformIO IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
3D Printer (generic)
3D Printer (generic)
Rework Station Kit, SMD
Rework Station Kit, SMD

Story

Read more

Custom parts and enclosures

Agni Flight Computer V2 CAD

This is the 3D CAD model of Agni Flight Computer V2. It includes mounting points, connector placements, and dimensions for integration into rocket avionics systems.

Sketchfab still processing.

Agni Flight Computer V2 CAD

Agni Flight Computer V2 CAD

Schematics

Agni Flight Computer V2 Schematic

Code

Agni Flight Computer V2 Software

C/C++
/**************************************************************************
 * 
 * Author     : [PIE.SPACE]
 * Start Date : [10-1-2025]
 * End Date   : [22-1-2025]
 * 
 * MPU6050 Check 

 Hi, this is Agni Flight Computer Version 2. This flight computer is designed only for Ground Control Station (GCS) purposes and is not used in the rocket because it runs on an Arduino Nano, which has limited processing power. However, if you want to use this flight computer for your rocket project, feel free to use it πŸš€
**************************************************************************/

#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include <EEPROM.h>
#include <math.h>
#include <Arduino.h>
#include <Adafruit_NeoPixel.h> // For WS2812B LED stripe

/**************************************************************************
  Function Prototypes
**************************************************************************/
void calibrateAccelerometer();
void calibrateGyroscope();
void sensorSelfTest();
void saveCalibrationData();
void loadCalibrationData();
void resetDisplacement();
void MadgwickAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az, float dt);

/**************************************************************************
  EEPROM Calibration Storage Definitions
**************************************************************************/
#define EEPROM_CALIB_SIGNATURE 0xA55A55A5UL
#define EEPROM_SIGNATURE_ADDR  0
#define EEPROM_CALIB_ADDR      4

struct CalibrationData {
  float accXOffset;
  float accYOffset;
  float accZOffset;
  float gyroXOffset;
  float gyroYOffset;
  float gyroZOffset;
};

CalibrationData calibData;
bool calibrationLoaded = false;

/**************************************************************************
  Global Objects and Variables
**************************************************************************/
Adafruit_MPU6050 mpu;

// Timing for sensor updates
unsigned long previousTime = 0;

// Displacement calculation (via double integration)
float velocityX = 0.0f, velocityY = 0.0f, velocityZ = 0.0f;
float displacementX = 0.0f, displacementY = 0.0f, displacementZ = 0.0f;

// Shock detection
#define SHOCK_THRESHOLD 15.0f  // m/s^2 threshold
bool shockDetected = false;

// Vibration sensing (rolling standard deviation)
#define VIBRATION_WINDOW_SIZE  50
float vibrationBuffer[VIBRATION_WINDOW_SIZE];
int vibrationIndex = 0;
bool bufferFilled = false;
float vibrationLevel = 0.0f;

// Min/Max tracking for angular velocity and linear acceleration
float minAngularVelocity = 3.4e38f;
float maxAngularVelocity = -3.4e38f;
float minLinearAcceleration = 3.4e38f;
float maxLinearAcceleration = -3.4e38f;

// Noise threshold for acceleration (to mitigate drift in integration)
#define ACC_NOISE_THRESHOLD 0.1f

// Madgwick variables
volatile float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f;
const float beta = 0.1f; // Madgwick algorithm gain

/**************************************************************************
  WS2812B LED Stripe Definitions and Variables
**************************************************************************/
#define LED_PIN 3       // Pin connected to the WS2812B stripe 
#define NUM_LEDS 1      // Number of LEDs in the stripe
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

// For continuous rainbow glow (non-blocking)
unsigned long previousRainbowMillis = 0;
uint8_t rainbowPos = 0;
const uint16_t RAINBOW_INTERVAL = 20; // Update interval in milliseconds

/**************************************************************************
  Musical Note Definitions
**************************************************************************/
#define Buzzer_Pin 9

#define NOTE_B0   31
#define NOTE_C1   33
#define NOTE_CS1  35
#define NOTE_D1   37
#define NOTE_DS1  39
#define NOTE_E1   41
#define NOTE_F1   44
#define NOTE_FS1  46
#define NOTE_G1   49
#define NOTE_GS1  52
#define NOTE_A1   55
#define NOTE_AS1  58
#define NOTE_B1   62
#define NOTE_C2   65
#define NOTE_CS2  69
#define NOTE_D2   73
#define NOTE_DS2  78
#define NOTE_E2   82
#define NOTE_F2   87
#define NOTE_FS2  93
#define NOTE_G2   98
#define NOTE_GS2  104
#define NOTE_A2   110
#define NOTE_AS2  117
#define NOTE_B2   123
#define NOTE_C3   131
#define NOTE_CS3  139
#define NOTE_D3   147
#define NOTE_DS3  156
#define NOTE_E3   165
#define NOTE_F3   175
#define NOTE_FS3  185
#define NOTE_G3   196
#define NOTE_GS3  208
#define NOTE_A3   220
#define NOTE_AS3  233
#define NOTE_B3   247
#define NOTE_C4   262
#define NOTE_CS4  277
#define NOTE_D4   294
#define NOTE_DS4  311
#define NOTE_E4   330
#define NOTE_F4   349
#define NOTE_FS4  370
#define NOTE_G4   392
#define NOTE_GS4  415
#define NOTE_A4   440
#define NOTE_AS4  466
#define NOTE_B4   494
#define NOTE_C5   523
#define NOTE_CS5  554
#define NOTE_D5   587
#define NOTE_DS5  622
#define NOTE_E5   659
#define NOTE_F5   698
#define NOTE_FS5  740
#define NOTE_G5   784
#define NOTE_GS5  831
#define NOTE_A5   880
#define NOTE_AS5  932
#define NOTE_B5   988
#define NOTE_C6   1047
#define NOTE_CS6  1109
#define NOTE_D6   1175
#define NOTE_DS6  1245
#define NOTE_E6   1319
#define NOTE_F6   1397
#define NOTE_FS6  1480
#define NOTE_G6   1568
#define NOTE_GS6  1661
#define NOTE_A6   1760
#define NOTE_AS6  1865
#define NOTE_B6   1976
#define NOTE_C7   2093
#define NOTE_CS7  2217
#define NOTE_D7   2349
#define NOTE_DS7  2489
#define NOTE_E7   2637
#define NOTE_F7   2794
#define NOTE_FS7  2960
#define NOTE_G7   3136
#define NOTE_GS7  3322
#define NOTE_A7   3520
#define NOTE_AS7  3729
#define NOTE_B7   3951
#define NOTE_C8   4186
#define NOTE_CS8  4435
#define NOTE_D8   4699
#define NOTE_DS8  4978

/**************************************************************************
  WS2812B LED Helper Functions
**************************************************************************/
// The Wheel function maps a number between 0 and 255 to a color value.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  } else {
    WheelPos -= 170;
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  }
}

// Startup LED flash with rainbow for 5 seconds.
void StartUpFlashLed() {
  Serial.println("Showing rainbow for 5 seconds...");
  unsigned long startTime = millis();
  int iteration = 0;
  while (millis() - startTime < 5000) {
    // Use an unsigned type for the LED index to match numPixels() return type.
    for(uint16_t i = 0; i < strip.numPixels(); i++) {
      int colorIndex = (i + iteration) & 255;
      strip.setPixelColor(i, Wheel(colorIndex));
    }
    strip.show();
    iteration++;
    delay(10);
  }
  strip.clear();
  strip.show();
}

/**************************************************************************
  Buzzer Melody Patterns
**************************************************************************/
static int droneStartupMelody[][2] = {
  {NOTE_G4,  200},
  {NOTE_A4,  200},
  {NOTE_B4,  250},
  {NOTE_G4,  200},
  {NOTE_A4,  200},
  {NOTE_B4,  300},
  {0,        150},
  {NOTE_D5,  200},
  {NOTE_FS5, 200},
  {NOTE_A5,  250},
  {NOTE_D5,  200},
  {NOTE_FS5, 200},
  {NOTE_A5,  300},
};
int startupLength = sizeof(droneStartupMelody) / sizeof(droneStartupMelody[0]);

#define ASC_SWEEP_START_FREQ   300
#define ASC_SWEEP_END_FREQ    1500
#define ASC_SWEEP_STEP         50
#define ASC_SWEEP_DELAY        30

#define DESC_SWEEP_START_FREQ 1500
#define DESC_SWEEP_END_FREQ    300
#define DESC_SWEEP_STEP         50
#define DESC_SWEEP_DELAY        30

#define URGENT_BEEP_REPEATS    4
#define URGENT_BEEP_ON_MS    150
#define URGENT_BEEP_OFF_MS   150

static int victoryMelody[][2] = {
  {NOTE_C4, 200}, {NOTE_E4, 200}, {NOTE_G4, 200},
  {NOTE_C5, 250},
  {0, 100},
  {NOTE_G4, 200}, {NOTE_E4, 200}, {NOTE_C4, 300}
};
int victoryLength = sizeof(victoryMelody) / sizeof(victoryMelody[0]);

static int errorMelody[][2] = {
  {NOTE_G3, 200}, {NOTE_E3, 200}, {NOTE_C3, 300},
  {0, 100},
  {NOTE_C3, 150}, {NOTE_C3, 150}
};
int errorLength = sizeof(errorMelody) / sizeof(errorMelody[0]);

static int futuristicArp[][2] = {
  {NOTE_C4,  100},
  {NOTE_E4,  100},
  {NOTE_G4,  100},
  {NOTE_C5,  100},
  {NOTE_G4,  100},
  {NOTE_E4,  100},
  {NOTE_C4,  100},
  {0,        100}
};
int futuristicArpLength = sizeof(futuristicArp) / sizeof(futuristicArp[0]);

// Helper function to play a melody array.
void playMelody(const int melody[][2], int length) {
  for (int i = 0; i < length; i++) {
    int freq = melody[i][0];
    int dur  = melody[i][1];
    if (freq > 0) {
      tone(Buzzer_Pin, freq, dur);
    } else {
      noTone(Buzzer_Pin);
    }
    delay(dur + 20);
    noTone(Buzzer_Pin);
  }
}

void playStartupMelody() {
  Serial.println("Playing Drone Startup Melody...");
  playMelody(droneStartupMelody, startupLength);
}
void ascendingSweep() {
  Serial.println("Ascending Sweep...");
  for (int freq = ASC_SWEEP_START_FREQ; freq <= ASC_SWEEP_END_FREQ; freq += ASC_SWEEP_STEP) {
    tone(Buzzer_Pin, freq);
    delay(ASC_SWEEP_DELAY);
  }
  noTone(Buzzer_Pin);
}
void descendingSweep() {
  Serial.println("Descending Sweep...");
  for (int freq = DESC_SWEEP_START_FREQ; freq >= DESC_SWEEP_END_FREQ; freq -= DESC_SWEEP_STEP) {
    tone(Buzzer_Pin, freq);
    delay(DESC_SWEEP_DELAY);
  }
  noTone(Buzzer_Pin);
}
void urgentBeep() {
  Serial.println("Urgent Beep Pattern...");
  for (int i = 0; i < URGENT_BEEP_REPEATS; i++) {
    digitalWrite(Buzzer_Pin, HIGH);
    delay(URGENT_BEEP_ON_MS);
    digitalWrite(Buzzer_Pin, LOW);
    delay(URGENT_BEEP_OFF_MS);
  }
  noTone(Buzzer_Pin);
}
void playVictoryMelody() {
  Serial.println("Playing Victory Melody...");
  playMelody(victoryMelody, victoryLength);
}
void playErrorMelody() {
  Serial.println("Playing Error Melody...");
  playMelody(errorMelody, errorLength);
}
void playFuturisticArpeggio() {
  Serial.println("Playing Futuristic Arpeggio...");
  for (int i = 0; i < 3; i++) {
    playMelody(futuristicArp, futuristicArpLength);
  }
}
void randomFuturisticBeep(int beepCount) {
  Serial.println("Playing Random Futuristic Beeps...");
  for (int i = 0; i < beepCount; i++) {
    int randFreq = random(200, 2000);
    int randDur  = random(50, 300);
    tone(Buzzer_Pin, randFreq, randDur);
    delay(randDur + 30);
    noTone(Buzzer_Pin);
  }
}

// Renamed original buzzer setup & loop (not used):
void unusedBuzzerSetup() {
  // Original buzzer code remains here but is not called.
}
void unusedBuzzerLoop() {
  // Empty in the original example.
}

/**************************************************************************
  Arduino Setup (Primary)
**************************************************************************/
void setup() {
  // 1) Set up buzzer pin
  pinMode(Buzzer_Pin, OUTPUT);

  // 2) Initialize LED stripe for WS2812B
  strip.begin();
  strip.show(); // Ensure all LEDs are off initially

  // 3) Start serial communication at 230400 baud
  Serial.begin(230400); 
  while (!Serial) { delay(1); }
  Serial.println("Starting. Buzzer Tone first...");

  // 4) Play buzzer melodies and sweeps
  playStartupMelody();    // Drone-style startup
  ascendingSweep();       // Ascending sweep
  descendingSweep();      // Descending sweep
  urgentBeep();           // Urgent beep pattern

  // 5) Flash the LED stripe with a rainbow pattern for 5 seconds
  StartUpFlashLed();
  urgentBeep();

  Serial.println("Buzzer sequences done. Now initializing MPU6050...");

  // 6) Initialize the MPU6050
  if (!mpu.begin()) {
    Serial.println(F("Failed to find MPU6050 chip. Stopping."));
    while (1) { delay(10); }
  }
  Serial.println(F("MPU6050 Found."));

  // Set accelerometer range
  mpu.setAccelerometerRange(MPU6050_RANGE_16_G);
  Serial.print(F("Accelerometer range set to: "));
  switch (mpu.getAccelerometerRange()) {
    case MPU6050_RANGE_2_G:  Serial.println(F("Β±2G"));  break;
    case MPU6050_RANGE_4_G:  Serial.println(F("Β±4G"));  break;
    case MPU6050_RANGE_8_G:  Serial.println(F("Β±8G"));  break;
    case MPU6050_RANGE_16_G: Serial.println(F("Β±16G")); break;
  }

  // Set gyroscope range
  mpu.setGyroRange(MPU6050_RANGE_2000_DEG);
  Serial.print(F("Gyro range set to: "));
  switch (mpu.getGyroRange()) {
    case MPU6050_RANGE_250_DEG:  Serial.println(F("Β±250 deg/s"));  break;
    case MPU6050_RANGE_500_DEG:  Serial.println(F("Β±500 deg/s"));  break;
    case MPU6050_RANGE_1000_DEG: Serial.println(F("Β±1000 deg/s")); break;
    case MPU6050_RANGE_2000_DEG: Serial.println(F("Β±2000 deg/s")); break;
  }

  // Set digital low-pass filter
  mpu.setFilterBandwidth(MPU6050_BAND_260_HZ);
  Serial.print(F("Filter bandwidth set to: "));
  switch (mpu.getFilterBandwidth()) {
    case MPU6050_BAND_260_HZ: Serial.println(F("260 Hz")); break;
    case MPU6050_BAND_184_HZ: Serial.println(F("184 Hz")); break;
    case MPU6050_BAND_94_HZ:  Serial.println(F("94 Hz"));  break;
    case MPU6050_BAND_44_HZ:  Serial.println(F("44 Hz"));  break;
    case MPU6050_BAND_21_HZ:  Serial.println(F("21 Hz"));  break;
    case MPU6050_BAND_10_HZ:  Serial.println(F("10 Hz"));  break;
    case MPU6050_BAND_5_HZ:   Serial.println(F("5 Hz"));   break;
  }

  // Sensor self-test
  sensorSelfTest();

  // Load calibration data; if unavailable, perform calibration
  loadCalibrationData();
  if (!calibrationLoaded) {
    calibrateAccelerometer();
    calibrateGyroscope();
  }

  previousTime = millis();
  Serial.println("Setup complete.\n");
}

/**************************************************************************
  Arduino Loop (Primary)
**************************************************************************/
void loop() {
  // 1) Get MPU sensor events
  sensors_event_t accelEvent, gyroEvent, tempEvent;
  mpu.getEvent(&accelEvent, &gyroEvent, &tempEvent);

  // 2) Apply calibration offsets
  float ax = accelEvent.acceleration.x - calibData.accXOffset;
  float ay = accelEvent.acceleration.y - calibData.accYOffset;
  float az = accelEvent.acceleration.z - calibData.accZOffset;
  float gx = gyroEvent.gyro.x - calibData.gyroXOffset;
  float gy = gyroEvent.gyro.y - calibData.gyroYOffset;
  float gz = gyroEvent.gyro.z - calibData.gyroZOffset;

  // 3) Calculate time difference
  unsigned long currentTime = millis();
  float dt = (currentTime - previousTime) / 1000.0f;
  previousTime = currentTime;

  // 4) Madgwick sensor fusion update
  MadgwickAHRSupdateIMU(gx, gy, gz, ax, ay, az, dt);

  // 5) Convert quaternion to Euler angles (in degrees)
  float rollRad  = atan2(2.0f*(q0*q1 + q2*q3), 1 - 2*(q1*q1 + q2*q2));
  float pitchRad = asin(2.0f*(q0*q2 - q3*q1));
  float yawRad   = atan2(2.0f*(q0*q3 + q1*q2), 1 - 2*(q2*q2 + q3*q3));
  float roll  = rollRad  * (180.0f / PI);
  float pitch = pitchRad * (180.0f / PI);
  float yaw   = yawRad   * (180.0f / PI);

  // 6) Gravity removal for displacement
  float gravityX = sin(pitchRad) * 9.81f;
  float gravityY = -sin(rollRad) * 9.81f;
  float gravityZ = cos(pitchRad)*cos(rollRad) * 9.81f;
  float linAccX = ax - gravityX;
  float linAccY = ay - gravityY;
  float linAccZ = az - gravityZ;

  // 7) Apply noise threshold
  if (fabs(linAccX) < ACC_NOISE_THRESHOLD) linAccX = 0;
  if (fabs(linAccY) < ACC_NOISE_THRESHOLD) linAccY = 0;
  if (fabs(linAccZ) < ACC_NOISE_THRESHOLD) linAccZ = 0;

  // 8) Double integration for displacement
  velocityX += linAccX * dt;
  velocityY += linAccY * dt;
  velocityZ += linAccZ * dt;
  displacementX += velocityX * dt;
  displacementY += velocityY * dt;
  displacementZ += velocityZ * dt;

  // 9) Convert gyro readings to degrees per second
  float gxDeg = gx * (180.0f / PI);
  float gyDeg = gy * (180.0f / PI);
  float gzDeg = gz * (180.0f / PI);

  // 10) Track min/max angular velocity and linear acceleration
  float angularVelMag = sqrt(gxDeg*gxDeg + gyDeg*gyDeg + gzDeg*gzDeg);
  if (angularVelMag < minAngularVelocity) minAngularVelocity = angularVelMag;
  if (angularVelMag > maxAngularVelocity) maxAngularVelocity = angularVelMag;

  float linearAccMag = sqrt(ax*ax + ay*ay + az*az);
  if (linearAccMag < minLinearAcceleration) minLinearAcceleration = linearAccMag;
  if (linearAccMag > maxLinearAcceleration) maxLinearAcceleration = linearAccMag;

  // 11) Shock detection
  shockDetected = (linearAccMag > SHOCK_THRESHOLD);

  // 12) Vibration (rolling standard deviation)
  vibrationBuffer[vibrationIndex] = linearAccMag;
  vibrationIndex = (vibrationIndex + 1) % VIBRATION_WINDOW_SIZE;
  if (vibrationIndex == 0) {
    bufferFilled = true;
  }
  if (bufferFilled) {
    float mean = 0.0f;
    for (int i = 0; i < VIBRATION_WINDOW_SIZE; i++) {
      mean += vibrationBuffer[i];
    }
    mean /= (float)VIBRATION_WINDOW_SIZE;
    float sumSq = 0.0f;
    for (int i = 0; i < VIBRATION_WINDOW_SIZE; i++) {
      float diff = (vibrationBuffer[i] - mean);
      sumSq += diff * diff;
    }
    vibrationLevel = sqrt(sumSq / (float)VIBRATION_WINDOW_SIZE);
  }

  // 13) Print data in CSV format to Serial Monitor
  Serial.print(roll, 2);                  Serial.print(",");
  Serial.print(pitch, 2);                 Serial.print(",");
  Serial.print(yaw, 2);                   Serial.print(",");
  Serial.print(q0, 4);                    Serial.print(",");
  Serial.print(q1, 4);                    Serial.print(",");
  Serial.print(q2, 4);                    Serial.print(",");
  Serial.print(q3, 4);                    Serial.print(",");
  Serial.print(ax, 2);                    Serial.print(",");
  Serial.print(ay, 2);                    Serial.print(",");
  Serial.print(az, 2);                    Serial.print(",");
  Serial.print(gxDeg, 2);                 Serial.print(",");
  Serial.print(gyDeg, 2);                 Serial.print(",");
  Serial.print(gzDeg, 2);                 Serial.print(",");
  Serial.print(tempEvent.temperature, 1); Serial.print(",");
  Serial.print(minAngularVelocity, 2);    Serial.print(",");
  Serial.print(maxAngularVelocity, 2);    Serial.print(",");
  Serial.print(minLinearAcceleration, 2); Serial.print(",");
  Serial.print(maxLinearAcceleration, 2); Serial.print(",");
  Serial.print(displacementX, 2);         Serial.print(",");
  Serial.print(displacementY, 2);         Serial.print(",");
  Serial.print(displacementZ, 2);         Serial.print(",");
  Serial.print(shockDetected ? 1 : 0);    Serial.print(",");
  Serial.println(vibrationLevel, 2);

  // 14) Serial command processing (if any command is sent)
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');
    command.trim();
    if (command.equalsIgnoreCase("calibrate accelerometer")) {
      calibrateAccelerometer();
    } else if (command.equalsIgnoreCase("calibrate gyroscope")) {
      calibrateGyroscope();
    } else if (command.equalsIgnoreCase("save calibration")) {
      saveCalibrationData();
    } else if (command.equalsIgnoreCase("load calibration")) {
      loadCalibrationData();
    } else if (command.equalsIgnoreCase("reset displacement")) {
      resetDisplacement();
    } else if (command.equalsIgnoreCase("self test")) {
      sensorSelfTest();
    }
  }

  // 15) Optionally, a small beep in each loop iteration (currently commented out)
  // tone(Buzzer_Pin, 1000, 100);

  // 16) Continuous non-blocking rainbow glow update for WS2812B LED stripe
  if (millis() - previousRainbowMillis >= RAINBOW_INTERVAL) {
    previousRainbowMillis = millis();
    rainbowPos++;  // Increment color position for smooth transition
    // Since only one LED is used, update pixel 0 with a rainbow color
    strip.setPixelColor(0, Wheel(rainbowPos));
    strip.show();
  }
}

/**************************************************************************
  Accelerometer Calibration
**************************************************************************/
void calibrateAccelerometer() {
  Serial.println(F("\n-- Accelerometer Calibration --"));
  Serial.println(F("Keep the sensor flat and still..."));

  const int numSamples = 100;
  float sumX = 0.0f, sumY = 0.0f, sumZ = 0.0f;
  for (int i = 0; i < numSamples; i++) {
    sensors_event_t a, g, temp;
    mpu.getEvent(&a, &g, &temp);
    sumX += a.acceleration.x;
    sumY += a.acceleration.y;
    sumZ += a.acceleration.z;
    delay(10);
  }
  float avgX = sumX / numSamples;
  float avgY = sumY / numSamples;
  float avgZ = sumZ / numSamples;

  calibData.accXOffset = avgX;
  calibData.accYOffset = avgY;
  // Subtract 9.81 so the offset yields ~9.81 when stationary
  calibData.accZOffset = avgZ - 9.81f;

  Serial.print(F("Accelerometer Offsets: X="));
  Serial.print(calibData.accXOffset, 3);
  Serial.print(F(", Y="));
  Serial.print(calibData.accYOffset, 3);
  Serial.print(F(", Z="));
  Serial.print(calibData.accZOffset, 3);
  Serial.println(F(" (Z includes gravity)"));
  Serial.println(F("Accelerometer calibration complete.\n"));
}

/**************************************************************************
  Gyroscope Calibration
**************************************************************************/
void calibrateGyroscope() {
  Serial.println(F("\n-- Gyroscope Calibration --"));
  Serial.println(F("Keep the sensor still (no rotation)..."));

  const int numSamples = 100;
  float sumX = 0.0f, sumY = 0.0f, sumZ = 0.0f;
  for (int i = 0; i < numSamples; i++) {
    sensors_event_t a, g, temp;
    mpu.getEvent(&a, &g, &temp);
    sumX += g.gyro.x;
    sumY += g.gyro.y;
    sumZ += g.gyro.z;
    delay(10);
  }
  calibData.gyroXOffset = sumX / numSamples;
  calibData.gyroYOffset = sumY / numSamples;
  calibData.gyroZOffset = sumZ / numSamples;

  Serial.print(F("Gyroscope Offsets: X="));
  Serial.print(calibData.gyroXOffset, 5);
  Serial.print(F(", Y="));
  Serial.print(calibData.gyroYOffset, 5);
  Serial.print(F(", Z="));
  Serial.print(calibData.gyroZOffset, 5);
  Serial.println(F("\nGyroscope calibration complete.\n"));
}

/**************************************************************************
  Sensor Self-Test Routine
**************************************************************************/
void sensorSelfTest() {
  Serial.println(F("\n-- Sensor Self-Test --"));
  const int numSamples = 50;
  float sumAccX = 0.0f, sumAccY = 0.0f, sumAccZ = 0.0f;
  float sumGyroX = 0.0f, sumGyroY = 0.0f, sumGyroZ = 0.0f;

  for (int i = 0; i < numSamples; i++) {
    sensors_event_t a, g, temp;
    mpu.getEvent(&a, &g, &temp);
    sumAccX += a.acceleration.x;
    sumAccY += a.acceleration.y;
    sumAccZ += a.acceleration.z;
    sumGyroX += g.gyro.x;
    sumGyroY += g.gyro.y;
    sumGyroZ += g.gyro.z;
    delay(5);
  }

  float avgAccX = sumAccX / numSamples;
  float avgAccY = sumAccY / numSamples;
  float avgAccZ = sumAccZ / numSamples;
  float avgGyroX = sumGyroX / numSamples;
  float avgGyroY = sumGyroY / numSamples;
  float avgGyroZ = sumGyroZ / numSamples;

  Serial.print(F("Average Acceleration: X="));
  Serial.print(avgAccX, 3);
  Serial.print(F(", Y="));
  Serial.print(avgAccY, 3);
  Serial.print(F(", Z="));
  Serial.print(avgAccZ, 3);
  Serial.println();

  Serial.print(F("Average Gyro: X="));
  Serial.print(avgGyroX, 5);
  Serial.print(F(", Y="));
  Serial.print(avgGyroY, 5);
  Serial.print(F(", Z="));
  Serial.print(avgGyroZ, 5);
  Serial.println();

  if (fabs(avgAccX) > 1.0 || fabs(avgAccY) > 1.0 || fabs(avgAccZ - 9.81) > 1.0) {
    Serial.println(F("Warning: Accelerometer readings out of expected range."));
  } else {
    Serial.println(F("Accelerometer readings are within expected range."));
  }

  if (fabs(avgGyroX) > 0.1 || fabs(avgGyroY) > 0.1 || fabs(avgGyroZ) > 0.1) {
    Serial.println(F("Warning: Gyroscope readings out of expected range."));
  } else {
    Serial.println(F("Gyroscope readings are within expected range."));
  }
  Serial.println(F("Self-test complete.\n"));
}

/**************************************************************************
  Save Calibration Data to EEPROM
**************************************************************************/
void saveCalibrationData() {
  Serial.println(F("\nSaving calibration data to EEPROM..."));
  uint32_t signature = EEPROM_CALIB_SIGNATURE;
  EEPROM.put(EEPROM_SIGNATURE_ADDR, signature);
  EEPROM.put(EEPROM_CALIB_ADDR, calibData);
  Serial.println(F("Calibration data saved.\n"));
}

/**************************************************************************
  Load Calibration Data from EEPROM
**************************************************************************/
void loadCalibrationData() {
  uint32_t signature;
  EEPROM.get(EEPROM_SIGNATURE_ADDR, signature);
  if (signature == EEPROM_CALIB_SIGNATURE) {
    EEPROM.get(EEPROM_CALIB_ADDR, calibData);
    calibrationLoaded = true;
    Serial.println(F("\nCalibration data loaded from EEPROM."));
    Serial.print(F("ACC Offsets: X="));
    Serial.print(calibData.accXOffset, 3);
    Serial.print(F(", Y="));
    Serial.print(calibData.accYOffset, 3);
    Serial.print(F(", Z="));
    Serial.println(calibData.accZOffset, 3);
    Serial.print(F("GYRO Offsets: X="));
    Serial.print(calibData.gyroXOffset, 5);
    Serial.print(F(", Y="));
    Serial.print(calibData.gyroYOffset, 5);
    Serial.print(F(", Z="));
    Serial.println(calibData.gyroZOffset, 5);
  } else {
    calibrationLoaded = false;
    Serial.println(F("\nNo valid calibration data found in EEPROM."));
  }
}

/**************************************************************************
  Reset Displacement and Velocity
**************************************************************************/
void resetDisplacement() {
  velocityX = velocityY = velocityZ = 0.0f;
  displacementX = displacementY = displacementZ = 0.0f;
  Serial.println(F("\nDisplacement and velocity reset.\n"));
}

/**************************************************************************
  MadgwickAHRSupdateIMU
**************************************************************************/
void MadgwickAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az, float dt) {
  float recipNorm;
  float s0, s1, s2, s3;
  float qDot1, qDot2, qDot3, qDot4;
  float _2q0, _2q1, _2q2, _2q3;
  float _4q0, _4q1, _4q2;
  float q0q0, q1q1, q2q2, q3q3;

  // Rate of change of quaternion from gyro
  qDot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz);
  qDot2 = 0.5f * (q0 * gx + q2 * gz - q3 * gy);
  qDot3 = 0.5f * (q0 * gy - q1 * gz + q3 * gx);
  qDot4 = 0.5f * (q0 * gz + q1 * gy - q2 * gx);

  // Normalize accelerometer measurement
  recipNorm = sqrt(ax * ax + ay * ay + az * az);
  if (recipNorm == 0.0f) return; // avoid division by zero
  recipNorm = 1.0f / recipNorm;
  ax *= recipNorm;
  ay *= recipNorm;
  az *= recipNorm;

  // Auxiliary variables
  _2q0 = 2.0f * q0;
  _2q1 = 2.0f * q1;
  _2q2 = 2.0f * q2;
  _2q3 = 2.0f * q3;
  _4q0 = 4.0f * q0;
  _4q1 = 4.0f * q1;
  _4q2 = 4.0f * q2;
  q0q0 = q0 * q0;
  q1q1 = q1 * q1;
  q2q2 = q2 * q2;
  q3q3 = q3 * q3;

  // Gradient descent algorithm corrective step
  float f1 = _2q1 * q3 - _2q0 * q2 - ax;
  float f2 = _2q0 * q1 + _2q2 * q3 - ay;
  float f3 = 1.0f - _2q1 * q1 - _2q2 * q2 - az;
  s0 = -_2q2 * f1 + _2q1 * f2;
  s1 =  _2q3 * f1 + _2q0 * f2 - 4.0f * q1 * f3;
  s2 = -_2q0 * f1 + _2q3 * f2 - 4.0f * q2 * f3;
  s3 =  _2q1 * f1 + _2q2 * f2;

  recipNorm = sqrt(s0*s0 + s1*s1 + s2*s2 + s3*s3);
  if (recipNorm == 0.0f) return;
  recipNorm = 1.0f / recipNorm;
  s0 *= recipNorm;
  s1 *= recipNorm;
  s2 *= recipNorm;
  s3 *= recipNorm;

  // Apply feedback step
  qDot1 -= beta * s0;
  qDot2 -= beta * s1;
  qDot3 -= beta * s2;
  qDot4 -= beta * s3;

  // Integrate rate of change of quaternion to yield new quaternion
  q0 += qDot1 * dt;
  q1 += qDot2 * dt;
  q2 += qDot3 * dt;
  q3 += qDot4 * dt;

  // Normalize quaternion
  recipNorm = sqrt(q0*q0 + q1*q1 + q2*q2 + q3*q3);
  if (recipNorm == 0.0f) return;
  recipNorm = 1.0f / recipNorm;
  q0 *= recipNorm;
  q1 *= recipNorm;
  q2 *= recipNorm;
  q3 *= recipNorm;
}

Agni Flight Computer V2 Firmware

This repository contains the embedded C++ code for the Agni Flight Computer V2. It handles real-time sensor data acquisition, Kalman/Madgwick filtering, shock detection, and telemetry output for aerospace-grade flight control.

Credits

PROJECTOCCUPYMARS
1 project β€’ 2 followers
Building a future where everyone can explore space. Mars, here we come! Let's make humanity a multiplanetary species.

Comments