Flight Computer Project Guide

Hello everyone, I'm weather balloon enthusiast and I always wanted to build my own Flight Computer for Weather Balloons.

Here is my current setup:

  • Teensy 4.1 with Build In SD Card
  • GY87 - MPU6050 + HMC5883L + BMP085
  • uBlox GPS MAX M8Q
  • 3 LED's - Red / Blue / Green
  • Push Button - To switch between modes
  • Buzzer - To make sound

The idea behind this simple flight computer is to save all the data from the GY87 & GPS to the SD card, on later stage I will add LoRa RF95X so I can transmit the data.

It has 3 different modes, that we can control with the Push Button, 1 single push will trigger the main mode or modeOne(). This mode is saving all the info to the SD card and also printing the available data pver Serial. Sevond mode or modeTwo() is triggered via 2 pushes and it will test the wings of my flying vehicle. Third mode is the modeOff() triggered via one long push.

Here is my current software, that I mostly put together from different examples from each library. I understand that this may not be the best way but it works. Any suggestions for modifications are welcome. Also please feel free to use my code in Your own project!

#include <SD.h> // include SD library 
#include <SPI.h> //include SPI library
#include <Wire.h> // include Wire library
#include <Servo.h> // include Servo library
#include <I2Cdev.h> // include I2CDev library
#include "BMP085.h" // include BMP085 library
#include "MPU6050.h" // include MPU library 
#include "HMC5883L.h" // include HMC5883 library
#include <PinButton.h> // include PushButton library
#include <TinyGPSPlus.h> // include GPS library
#include <ServoEasing.hpp> // include Servo Easing library
#include <SoftwareSerial.h> // include Software Serial library

#define GREEN_LED 12 // define GREEN LED
#define RED_LED 26 //define  RED LED 
#define BLUE_LED 31 // define BLUE LED 
#define LEFT_WING_PIN 41 // define Left WING
#define RIGHT_WING_PIN 40 //define  Right WING

const int buzzer = 27; // Buzzer
const int chipSelect = BUILTIN_SDCARD; // Teensy 4.1 BUILDIN_SDCARD
static const int RXPin = 0, TXPin = 1; // GPS PIN connections
static const uint32_t GPSBaud = 9600; // GPS Baud Rate 
static const float ACCEL_SENS = 16384.0; // Accel Sensitivity with default +/- 2g scale
static const float GYRO_SENS = 131.0; // Gyro Sensitivity with default +/- 250 deg/s scale

TinyGPSPlus gps;  // The TinyGPSPlus object
SoftwareSerial ss(RXPin, TXPin); // The serial connection to the GPS device
ServoEasing Left_Wing; // Left Wing Servo Object
ServoEasing Right_Wing; // Right Wing Servo Object
PinButton myButton(10); // Mode Switching Button Object
File myFile; // SD card Object


// Magnetometer class default I2C address is 0x1E
// specific I2C addresses may be passed as a parameter here
// this device only supports one I2C address (0x1E)
HMC5883L mag;
int16_t mx, my, mz;

// Accel/Gyro class default I2C address is 0x68 (can be 0x69 if AD0 is high)
// specific I2C addresses may be passed as a parameter here
MPU6050 accelgyro;

int16_t ax, ay, az;
int16_t gx, gy, gz;
int scale = 16384;
// Barometer class default I2C address is 0x77
// specific I2C addresses may be passed as a parameter here
// (though the BMP085 supports only one address)
BMP085 barometer;

float temperature;
float pressure;
int32_t lastMicros;

void setup() {
  
  bool state = false;
  unsigned int count = 0;

  pinMode(RED_LED, OUTPUT); // Set Red LED pin output
  pinMode(BLUE_LED, OUTPUT); // Set Blue LED pin output
  pinMode(GREEN_LED, OUTPUT); // Set GREEN LED pin output
  pinMode(buzzer, OUTPUT); // Set BUZZER - pin 9 as an output
  
  // Attach servo to pin and set servo to start position.
  Left_Wing.attach(LEFT_WING_PIN, 4);
  Right_Wing.attach(RIGHT_WING_PIN, 3);
  Left_Wing.setSpeed(140); // This speed is taken if no further speed argument is given.
  Right_Wing.setSpeed(140); // This speed is taken if no further speed argument is given.
  delay(500); // Wait for servo to reach start position.
  
  
  Serial.begin(9600);
  while (!Serial && (count < 30)) {
    delay(200); // Wait for serial port to connect with timeout. Needed for native USB
    //Blink GREEN LED
    digitalWrite(GREEN_LED, state);
    state = !state;
    count++;
  }
  digitalWrite(GREEN_LED, HIGH);
 // join I2C bus (I2Cdev library doesn't do this automatically)
  Wire.begin();
  
  // ==================== MPU6050 ============================
  accelgyro.initialize();
  Serial.print("Testing Accel/Gyro... ");
  Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");
  // Starts up with accel +/- 2 g and gyro +/- 250 deg/s scale
  accelgyro.setI2CBypassEnabled(true); // set bypass mode
  // Now we can talk to the HMC5883l

  // ==================== HMC5883L ============================
  mag.initialize();
  Serial.print("Testing Mag...  ");
  Serial.println(mag.testConnection() ? "HMC5883L connection successful" : "HMC5883L connection failed");

  // ==================== BMP085 ============================
  barometer.initialize();
  Serial.print("Testing Pressure...  ");
  Serial.println(barometer.testConnection() ? "BMP085 connection successful" : "BMP085 connection failed");
 // ===================== SD CARD =========================
  Serial.print("Initializing SD card...");
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    Serial.println("Try Again in 5 seconds...");
    digitalWrite(RED_LED, HIGH); // RED LED ON
    digitalWrite(GREEN_LED, LOW); // GREEN LED OFF
    tone(buzzer, 200); // Make Sound 
    delay(750);
    noTone(buzzer); // Stop sound... 
    while (1) {
       // Here put the code were we will try again in 5 seconds
     }
  }
  Serial.println("SD Card Initialized.");
  myFile = SD.open("FlightLog.txt", FILE_WRITE);
  // if the file is available, write to it:
  if (myFile) {
    myFile.println("=== Time ===  |=== Accel === | === Gyro === | ======= Mag ======= |===== GPS DATA ===== |  ==== GPS DATA ==== | === Barometer === |");
    myFile.println("   H:M:s      |  X   Y   Z   |  X   Y   Z   |  X   Y   Z  Heading |    LAT      LNG     |    SPEED    ALT     |  Temp   Pressure  |");
    myFile.close();
  }
  // if the file isn't open, pop up an error:
  else {
    Serial.println("Error opening FlighLog.txt");
    digitalWrite(RED_LED, HIGH); // RED LED ON
    digitalWrite(GREEN_LED, LOW); // GREEN LED OFF
    tone(buzzer, 200); // Make Sound
    delay(750);
    noTone(buzzer); // Stop sound... 
  }
   // ==================== GPS initialising ============================
  ss.begin(GPSBaud);

  Serial.println("====// Setup Complete //====");
  
  //CLOSE BOTH WINGS
  Left_Wing.write(95); // Left Wing CLOSED
  Right_Wing.write(0); // Right Wing CLOSED
}

void modeOne() {
  //CLOSE BOTH WINGS
  Left_Wing.write(0); // Left Wing OPEN
  Right_Wing.write(105); // Right Wing OPEN
  
  static unsigned long ms = 0;
  static boolean state = !state;

  // Serial Output Format
  // === Time ===  |=== Accel === | === Gyro === | ======= Mag ======= |===== GPS DATA ===== |  ==== GPS DATA ==== | === Barometer === |
  //    H:M:s      |  X   Y   Z   |  X   Y   Z   |  X   Y   Z  Heading |    LAT      LNG     |    SPEED    ALT     |  Temp   Pressure  |

  if (millis() - ms > 100) {
    printTime(gps.time); // Print GPS TIME
    Serial.print(": ");
    accelgyro.getMotion6( & ax, & ay, & az, & gx, & gy, & gz); // read raw accel/gyro measurements
    // display tab-separated accel/gyro x/y/z values
    Serial.print(ax / ACCEL_SENS);
    Serial.print(" / ");
    Serial.print(ay / ACCEL_SENS);
    Serial.print(" / ");
    Serial.print(az / ACCEL_SENS);
    Serial.print(" , ");
    Serial.print(gx / GYRO_SENS);
    Serial.print(" / ");
    Serial.print(gy / GYRO_SENS);
    Serial.print(" / ");
    Serial.print(gz / GYRO_SENS);
    Serial.print(" , ");
    
    // read raw heading measurements
    mag.getHeading( & mx, & my, & mz);

    // display tab-separated mag x/y/z values
    Serial.print(mx);
    Serial.print(" / ");
    Serial.print(my);
    Serial.print(" / ");
    Serial.print(mz);
    Serial.print(" , ");

    // To calculate heading in degrees. 0 degree indicates North
    float heading = atan2(my, mx);
    if (heading < 0) heading += 2 * M_PI;
    Serial.print(heading * 180 / M_PI);
    Serial.print(" , ");

    //Display LON & LAT
    printFloat(gps.location.lat(), gps.location.isValid(), 11, 6);
    Serial.print(" , "); 
    printFloat(gps.location.lng(), gps.location.isValid(), 12, 6);
    Serial.print(", ");
    
    //Display SPEED & ALTITUDE
    printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2); //Speed
    Serial.print(" , ");    
    printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2); //Altitude    
    Serial.print(", ");
    // request temperature
    barometer.setControl(BMP085_MODE_TEMPERATURE);

    // wait appropriate time for conversion (4.5ms delay)
    lastMicros = micros();
    while (micros() - lastMicros < barometer.getMeasureDelayMicroseconds());

    // read calibrated temperature value in degrees Celsius
    temperature = barometer.getTemperatureC();
    
    // request pressure (3x oversampling mode, high detail, 23.5ms delay)
    barometer.setControl(BMP085_MODE_PRESSURE_3);
    while (micros() - lastMicros < barometer.getMeasureDelayMicroseconds());

    // read calibrated pressure value in Pascals (Pa)
    pressure = barometer.getPressure();

    // display measured values if appropriate
    Serial.print(temperature);
    Serial.print(" , ");   
    Serial.print(pressure / 100);
    Serial.println("\t");
    ms = millis();

   
    // write the GY87 values to SD card
      myFile = SD.open("FlightLog.txt", FILE_WRITE);

      if (myFile) {
      myFile.print((float) gps.time.hour(), 0); myFile.print(":");
      myFile.print((float) gps.time.minute(), 0); myFile.print(":"); 
      myFile.print((float) gps.time.second(), 0); myFile.print(" | ");     
      myFile.print((float) ax / scale); 
      myFile.print((float) ay / scale); 
      myFile.print((float) az / scale);
      myFile.print(" , ");
      myFile.print((float) gx / scale); 
      myFile.print((float) gy / scale); 
      myFile.print((float) gz / scale); 
      myFile.print(" , ");
      myFile.print((float) mx / scale); 
      myFile.print((float) my / scale); 
      myFile.print((float) mz / scale); 
      myFile.print((float) heading * 180 / M_PI);
      myFile.print(" , "); 
      myFile.print((float) gps.location.lat(), 6);
      myFile.print((float) gps.location.lng(), 6); 
      myFile.print(" , ");
      myFile.print((float) gps.speed.kmph()); 
      myFile.print(" , ");
      myFile.print((float) gps.altitude.meters()); 
      myFile.print(" , ");
      myFile.print((float) temperature);
      myFile.print(" , ");
      myFile.println((float) pressure); 
      
      myFile.close();
    }
    // blink LED to indicate activity
    digitalWrite(BLUE_LED, state);
    state = !state;
    }

  // Keep waiting until the fix is valid
   while( !gps.location.isValid() )
   {
    smartDelay(500);

    if (millis() > 500 && gps.charsProcessed() < 10)
      // blink LED to indicate activity
      digitalWrite(RED_LED, state);
      state = !state;
      Serial.println(F("No GPS data received!"));
   }
}

// This custom version of delay() ensures that the gps object is being "fed".
static void smartDelay(unsigned long ms)
{
  unsigned long start = millis();
  do 
  {
    while (ss.available())
    gps.encode(ss.read());
  } while (millis() - start < ms);
}

static void printFloat(float val, bool valid, int len, int prec)
{
  if (!valid)
  {
    while (len-- > 1)
      Serial.print('*');
    Serial.print(' ');
  }
  else
  {
    Serial.print(val, prec);
    int vi = abs((int)val);
    int flen = prec + (val < 0.0 ? 2 : 1); // . and -
    flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
    for (int i=flen; i<len; ++i)
      Serial.print(' ');
  }
  smartDelay(0);
}

static void printTime(TinyGPSTime &t)
{
  if (!t.isValid())
  {
    Serial.print(F("******** "));
  }
  else
  {
    char sz[32];
    sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second());
    Serial.print(sz);
  }

  smartDelay(0);
}
/*static void printInt(unsigned long val, bool valid, int len)
{
  char sz[32] = "*****************";
  if (valid)
    sprintf(sz, "%ld", val);
  sz[len] = 0;
  for (int i=strlen(sz); i<len; ++i)
    sz[i] = ' ';
  if (len > 0) 
    sz[len-1] = ' ';
  Serial.print(sz);
  smartDelay(0);
}

static void printStr(const char *str, int len)
{
  int slen = strlen(str);
  for (int i=0; i<len; ++i)
    Serial.print(i<slen ? str[i] : ' ');
  smartDelay(0);
} */


void modeTwo() {

  // START TESTING MODE:
  Serial.println("--== TEST MODE ==-- ");
  digitalWrite(BLUE_LED, HIGH);
  digitalWrite(RED_LED, LOW);
  digitalWrite(GREEN_LED, LOW);

  tone(buzzer, 1000); // 
  delay(200);
  tone(buzzer, 500); // 
  delay(250);
  tone(buzzer, 1000); // 
  delay(10);
  noTone(buzzer); // Stop sound...

  //OPEN BOTH WINGS
  Left_Wing.write(0); // Left Wing OPEN
  Right_Wing.write(105); // Right Wing OPEN

  delay(2000);

  //TEST LEFT WING
  Left_Wing.write(0); // Left Wing OPEN
  delay(500);
  Left_Wing.write(95); // Left Wing CLOSED
  delay(500);
  Left_Wing.write(0); // Left Wing OPEN
  delay(1000);

  //TEST RIGHT WING
  Right_Wing.write(105); // Right Wing OPEN
  delay(500); 
  Right_Wing.write(0); // Right  Wing CLOSED
  delay(500);
  Right_Wing.write(105); // Right Wing OPEN
  delay(500);

  //CLOSE BOTH WINGS
  Left_Wing.write(95); // Left Wing CLOSED
  Right_Wing.write(0); // Right Wing CLOSED
  
  tone(buzzer, 1000); // 
  delay(10);
  tone(buzzer, 500); // 
  delay(250);
  tone(buzzer, 1000); // 
  delay(250);
  noTone(buzzer); // Stop sound...
  Serial.println("--== TEST MODE FINISHED SUCESSFULLY ==--");
  digitalWrite(BLUE_LED, LOW);
 
}
void modeOff() {
  Left_Wing.write(95); // Left Wing CLOSED
  Right_Wing.write(0); // Right Wing CLOSED
  digitalWrite(GREEN_LED, LOW);
  digitalWrite(BLUE_LED, LOW);
  digitalWrite(RED_LED, HIGH);
  tone(buzzer, 200); // 
  delay(750);
  noTone(buzzer); // Stop sound... 
  digitalWrite(RED_LED, LOW); 
}


void loop() {
  
  digitalWrite(GREEN_LED, HIGH);
  // remember the selected mode; 255 indicates no mode is active
  static uint8_t mode = 255;
  
  myButton.update();
  if (myButton.isSingleClick()) {

   mode = 1;

  }

  if (myButton.isDoubleClick()) {
   
    mode = 2;
  
  }

  if (myButton.isLongClick()) {

    mode = 0;

  }
// honour the mode
  switch (mode)
  {
    case 0:
      modeOff();
      mode = 255;  // if you want to prevent modeOff to be repeated, set mode to 255
      break;
    case 1:
      modeOne();
      break;
    case 2:
      modeTwo();
      mode = 255; // Preventing Test Mode from repeating - 255
      break;     
  }

}
// close void loop

Thanks For Looking :slight_smile:

Will You accompany the balloon and press the button?
How much is working?

No the idea behind a push button is when the balloon is on the ground so you can switch from mode to mode.

One reply to two questions. Is it too hard to reply to two questions in one reply?

Didn't not realise you have a second question, its working fine, saves the IMU and GPs data to SD card, moves the servos (wing) so it's working enough for now.

Okey.
You are not alone. Many people only read the first question and reply. It adds some posting...

No. Any code that works is a good code at an early stage. Intricate code that only experts can read that have issues is far worse. Remember that maintaining, altering the code later, can be required.
Make lots of comments in the code! You will not remember all some year, years later! Add valuable comments now!

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.