ESP-32 Flight Datalogger – Field Validation

ESP-32 Flight Datalogger – Field Validation

With all of the final code validated on the workbench, it was time to test it in the field. The project deadline was 3 weeks away and the field testing was a formality to ensure nothing was missing before assembly. With all of the bench testing done, it seemed that this would take a single day to complete and the next phase would begin.

Field testing involves taking the hardware outside and testing it in a state that closely matches its final form. The hardware is rather raw and not complete, so care must be taken to not disturb or mishandle it. This was the case with this project, the hardware and wiring were fastened down and secure. The process for activating and controlling the hardware was well rehearsed. It behaved with high reliability on the bench, so it was the best it would be for field testing.

When the tests began, it became apparent early on that something wasn’t working. The GPS module was failing to get a reliable lock or it was taking a longer than expected time to get a lock. The first thing to suspect were loose wiring. But all of these checked fine. The field testing took a sour turn, the unreliable GPS lock issue had to be identified before clearing the firmware for the hardware build phase.

Out came the test equipment. The GPS was connected to a serial monitor, however it operated without issue. The GPS unit by itself acquired a lock quickly and consistently. The GPS mockup was tested again and the system operated as expected. However, the GPS lock issue continued to plague the project when the GPS was connected to the ESP32-Cam module.

This went on for a couple of days, the pressure was on to get a fix, pun intended. Identifying the problem was the first hurdle, the next would be correcting it, while the last would be validated field testing. This meant going back to the firmware development stage and bench hardware. Each interface of the ESP32-Cam module was scrutinized. The project had originally used hardware UART pins as serial debug for development. The GPS serial was received on a software serial pin that was assigned to another GPIO pin. The mockup worked without issue in this configuration. However, the GPS module had issues with that setup. The GPIO pin assigned as a software serial input exhibited a noticeable voltage drop, changing the logic recognized by the ESP32-Cam module. No voltage drop occurred when the GPS was connected to the hardware serial pin of the ESP32-Cam module. This meant that the firmware would need to be reworked to accommodate this requirement.

See notes “Supported Interfaces_Ver5.txt”

ESP-32 Cam
GPIO0 CSI_MCLK, I2C SLC, this can't be grounded at startup, it's the program pin
GPIO1 U0TXD, WRITE INT, used for DEBUG console during DEV
GPIO3 U0RXD, GPS RXD
GPIO16 U2RXD, I2C SDA

GPS GP-20U7
UART Transmit - This will connect to ESP-32 GPIO3 (U0RXD)


Volt/Amp Sensor - INA219
This uses I2C, a bit of a challenge, requires 2 data pins
SLC - This will connect to ESP-32 GPIO0
SDA - This will connect to ESP-32 GPIO16


I also need a file write interupt to prevent data corruption at power down.
GPIO1 - Set Pin HIGH for data write, LOW to stop data write
    Set this option when all debuging is done


Notes

Field tests were spotty using software serial or U2RXD (GPIO16) for input from the GPS.
Only U0RXD (GPIO3) reliably recieved and processed GPS data promptly and consistantly.

Earlier dev noted that the GPS logic levels dropped from 3.3v to 1.5v when connected to any GPIO other than GPIO3.

Also noted that the bootup message on DEBUG had system memory errors, but cleared on subsequent reboots.

Write interupt works fine using the U0TXD pin if the following are followed.

When hardware is initialized,
Start both GPIO1 and GPIO3 as hardware serial ports
Then set GPIO1 pin as an input, followed by defining its initial state

Back to the bench, the code changes were made. The revision involved a global change to all of the GPIOs used on the ESP32-Cam module to all of the sensors. Fortunately, the code was organized in a way that the time involved for a new version was minimal. The I2C data bus, write interrupt, GPS input, and serial debug were tested one by one and passed. The hardware was once again ready for field testing.

See code set “FlightData_Final_Ver9.ino”

/** 

FlightData_Final_Ver9.ino
Pressure and Temperature Sensor Merge
July 22nd, 2021 - 04:21

**/


// Libraries
#include "FS.h" 
#include "SD_MMC.h"
#include <TinyGPS++.h>
#include <Wire.h>
#include <Adafruit_INA219.h>
#include "WiFi.h"
#include <Adafruit_BMP085.h>



// Variables and Constants

// Set these based on your needs
// GPS Home, https://www.google.com/maps
static const double SixtyAcres_LAT = 47.702875, SixtyAcres_LON = -122.137535;
// Current baromentric pressure, https://tides4fishing.com/
uint32_t inHg = 30.03; 


TinyGPSPlus gps;	// The TinyGPS++ object
unsigned long last = 0UL;	// For stats that happen every 5 seconds
int PadSeconds;
int PadMinutes;
int PadDays;
int PadMonths;

Adafruit_INA219 ina219(0x40);
    // Initializes I2C communication with the Adafruit_INA219 device address 0x40
uint32_t currentFrequency;
float shuntvoltage = 0;
float busvoltage = 0;
float current_mA = 0;
float loadvoltage = 0;
float power_mW = 0;

int WifiNetworks = 0;
int NetworkCount = 0;

Adafruit_BMP085 bmp; // can this be addressed?
uint32_t mbar = inHg / .02953; // convert inHg to mbar
uint32_t Pa = mbar / .01; // convert mbar to Pa

int InteruptState = 1;
int FaultState = 0;
int WarmUpState = 1;

const char* FileName = "/FLTDATA.CSV";

#define FlashLED 4 
#define WriteInt 1
#define StautsLED 33 // REMEMBER - The ESP32-CAM has Reverse logic, HIGH is off, LOW is on.
#define SCL 0 
#define SDA 16


// Routines and Subroutines


// Hardware Initialization Routine
void HardwareInit()
{
  Serial.begin(9600);
  Wire.begin(SDA, SCL);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  pinMode(StautsLED, OUTPUT);
  pinMode(FlashLED, OUTPUT);
  pinMode(WriteInt, INPUT);
  digitalWrite(FlashLED, LOW);
  digitalWrite(StautsLED, HIGH); // The ESP32-Cam Module uses reverse logic, so HIGH = off and LOW = on
  delay(5000);  // give GPS time to settle 
} 

    
void StartupMicrSD()
{
   if(!SD_MMC.begin()){
        SystemHalt();
        return;
    }
    uint8_t cardType = SD_MMC.cardType();

    if(cardType == CARD_NONE){
        SystemHalt();
        return;
    }

}  

//Append to the end of file in SD card
void appendFile(fs::FS &fs, const char * path, const char * message){

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        return;
    }
    if(file.print(message)){
    } else {
    }
}


//Sample Data Write for Main Loop
void WriteInterupt()
{
     InteruptState = digitalRead(WriteInt);
     if (InteruptState == 0)
     {
     	while (InteruptState == 0){
     	        InteruptState = digitalRead(WriteInt);
     		digitalWrite(StautsLED, LOW);
     		delay(50);
     		digitalWrite(StautsLED, HIGH);
     		delay(950);
       }
     }
}


//Sample Data Write for Main Loop
void SystemHalt()
{
     FaultState = 1;
     while (FaultState == 1){
     	digitalWrite(StautsLED, LOW);
     	delay(50);
     	digitalWrite(StautsLED, HIGH);
     	delay(50);
     }
}
  
  
// Fault Detetection and Alerting Routine
void FaultDetect()
{
   if (! bmp.begin ()) {
    digitalWrite(StautsLED, LOW);
   while (1) { delay(10); }
  }
  
  if (! ina219.begin()) {
    digitalWrite(StautsLED, LOW);
    while (1) { delay(10); }
  }
} 


// Clear Fault Alert Routine
void ClearFaultAlert()
{
  digitalWrite(StautsLED, HIGH);
}

  
// Sensor Calibraion Scaling Routine
void SensorCalibration()
{
  ina219.setCalibration_16V_400mA();
}


// Get Voltage Sensor Value Routine
void GetVoltSensorValues()
{
  shuntvoltage = ina219.getShuntVoltage_mV();
  busvoltage = ina219.getBusVoltage_V();
  current_mA = ina219.getCurrent_mA();
  power_mW = ina219.getPower_mW();
  loadvoltage = busvoltage + (shuntvoltage / 1000);
}


// WiFi Scanning Routine
void WifiScanning()
{
    // WiFi.scanNetworks will return the number of networks found
    WifiNetworks = WiFi.scanNetworks();
    
    if (WifiNetworks == 0) 
       {
    
       } 
    
    else 
       {
        String StringWifiNetworks = String(WifiNetworks);
        appendFile(SD_MMC, FileName, StringWifiNetworks.c_str()); 
        appendFile(SD_MMC, FileName, ",");
        
        for (NetworkCount = 0; NetworkCount < 3; ++NetworkCount) 
           {
           
            String StringSSID = String(WiFi.SSID(NetworkCount));
            appendFile(SD_MMC, FileName, StringSSID.c_str()); 
            appendFile(SD_MMC, FileName, ",");
           
            String StringRSSI = String(WiFi.RSSI(NetworkCount));
            appendFile(SD_MMC, FileName, StringRSSI.c_str()); 
            appendFile(SD_MMC, FileName, ",");
           
            String StringEncryptionType = String((WiFi.encryptionType(NetworkCount) == WIFI_AUTH_OPEN)?" ":"*");
            appendFile(SD_MMC, FileName, StringEncryptionType.c_str()); 
            appendFile(SD_MMC, FileName, ",");
            
           }
       }
} 


// Printout Header Routine
void PrintHeader()
{
    appendFile(SD_MMC, FileName, "Lat,Long,Date,Time,MPH,Course,Altitude,Home,Bearing,Cardinal,Bus V,Shunt mV,Load V,mA,mW,Temp C,Temp F,Def Alt M,Cal Alt M, Cal Alt F,Networks,SSID-1,RSSID-1,Enc-1,SSID-2,RSSID-2,Enc-2,SSID-3,RSSID-3,Enc-3\n");
}


// Printout Location Routine
void PrintOutLocation()
{
    String StringLat = String(gps.location.lat(), 6);
    String StringLong = String(gps.location.lng(), 6);
    String Location = String(StringLat + "," + StringLong + ",");
    appendFile(SD_MMC, FileName, Location.c_str());
}


// Printout Date Routine
void PrintOutDate()
{
    PadMonths = gps.date.month();
      if (PadMonths < 10)
        {        
         appendFile(SD_MMC, FileName, "0");
        }        
    String StringMonth = String(gps.date.month());
    appendFile(SD_MMC, FileName, StringMonth.c_str());
    appendFile(SD_MMC, FileName, "/");
    PadDays = gps.date.day();
      if (PadDays < 10)
        {        
          appendFile(SD_MMC, FileName, "0");
        }        
    String StringDay = String(gps.date.day());
    appendFile(SD_MMC, FileName, StringDay.c_str());     
    appendFile(SD_MMC, FileName, "/");    
    String StringYear = String(gps.date.year());
    appendFile(SD_MMC, FileName, StringYear.c_str());   
    appendFile(SD_MMC, FileName, ",");
}


// Printout Time Routine
void PrintOutTime()
{
    String StringHour = String(gps.time.hour());
    appendFile(SD_MMC, FileName, StringHour.c_str());    
    appendFile(SD_MMC, FileName, ":");      
    PadMinutes = gps.time.minute();
      if (PadMinutes < 10)
        {
          appendFile(SD_MMC, FileName, "0");
        }
    String StringMinutes = String(gps.time.minute());
    appendFile(SD_MMC, FileName, StringMinutes.c_str());    
    appendFile(SD_MMC, FileName, ":");    
    PadSeconds = gps.time.second();
      if (PadSeconds < 10)
        {
          appendFile(SD_MMC, FileName, "0");
        }
    String StringSeconds = String(gps.time.second());
    appendFile(SD_MMC, FileName, StringSeconds.c_str());       
    appendFile(SD_MMC, FileName, ",");    
}


// Printout Speed Routine
void PrintOutSpeed()
{
    String StringMph = String(gps.speed.mph());
    appendFile(SD_MMC, FileName, StringMph.c_str()); 
    appendFile(SD_MMC, FileName, ","); 
}


// Printout Course Routine
void PrintOutCourse()
{
    String StringCourse = String(gps.course.deg());
    appendFile(SD_MMC, FileName, StringCourse.c_str()); 
    appendFile(SD_MMC, FileName, ","); 
}


// Printout Altitude Routine
void PrintOutAltitude()
{
    String StringAltitude = String(gps.altitude.feet());
    appendFile(SD_MMC, FileName, StringAltitude.c_str()); 
    appendFile(SD_MMC, FileName, ","); 
}


// Reference Bearing Routine
void BearingReference()
{
      
      double distanceToSixtyAcres =
        TinyGPSPlus::distanceBetween(
          gps.location.lat(),
          gps.location.lng(),
          SixtyAcres_LAT, 
          SixtyAcres_LON);
      double courseToSixtyAcres =
        TinyGPSPlus::courseTo(
          gps.location.lat(),
          gps.location.lng(),
          SixtyAcres_LAT, 
          SixtyAcres_LON);
          
      String StringDistToHome = String(distanceToSixtyAcres/1609, 6);
      appendFile(SD_MMC, FileName, StringDistToHome.c_str()); 
      appendFile(SD_MMC, FileName, ","); 
      String StringCourseToHome = String(courseToSixtyAcres, 6);
      appendFile(SD_MMC, FileName, StringCourseToHome.c_str()); 
      appendFile(SD_MMC, FileName, ","); 
      String StringCardinal = String(TinyGPSPlus::cardinal(courseToSixtyAcres));
      appendFile(SD_MMC, FileName, StringCardinal.c_str()); 
      appendFile(SD_MMC, FileName, ","); 
}



// Printout Results Routine
void PrintOutINA219Readings()
{
  String StringBusVoltage = String(busvoltage);
  appendFile(SD_MMC, FileName, StringBusVoltage.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
  String StringShuntVoltage = String(shuntvoltage);
  appendFile(SD_MMC, FileName, StringShuntVoltage.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
  String StringLoadVoltage = String(loadvoltage);
  appendFile(SD_MMC, FileName, StringLoadVoltage.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
  String StringCurrentmA = String(current_mA);
  appendFile(SD_MMC, FileName, StringCurrentmA.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
  String StringPowermA = String(power_mW);
  appendFile(SD_MMC, FileName, StringPowermA.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
}



// Printout Results Routine
void PrintOutGY65Readings()
{
  String StringTemperatureC = String(bmp.readTemperature ());
  appendFile(SD_MMC, FileName, StringTemperatureC.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
  String StringTemperatureF = String(bmp.readTemperature () * 9 / 5 + 32);
  appendFile(SD_MMC, FileName, StringTemperatureF.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
  String StringDefAltitudeM = String(bmp.readAltitude ());
  appendFile(SD_MMC, FileName, StringDefAltitudeM.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
  String StringCalAltitudeM = String(bmp.readAltitude (Pa));
  appendFile(SD_MMC, FileName, StringCalAltitudeM.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
  String StringCalAltitudeF = String(bmp.readAltitude (Pa) * 3.2808);
  appendFile(SD_MMC, FileName, StringCalAltitudeF.c_str()); 
  appendFile(SD_MMC, FileName, ","); 
}


// Inbound Serial Data Processing
void InboundSerialData()
{
  while (Serial.available() > 0)
    gps.encode(Serial.read());

  if (gps.altitude.isUpdated())
  {
    PrintOutGlobalReadings();
  }

  else if (millis() - last > 5000)
  {
    if (gps.location.isValid())
    last = millis();
  }
}


// Printout Global Results Routine
void PrintOutGlobalReadings()
{
    PrintOutLocation();
    PrintOutDate();
    PrintOutTime();
    PrintOutSpeed();
    PrintOutCourse();
    PrintOutAltitude();
    BearingReference();
    GetVoltSensorValues();
    PrintOutINA219Readings();
    PrintOutGY65Readings();
    WifiScanning();
    appendFile(SD_MMC, FileName, "\n"); 
}


void setup()
{
  HardwareInit();  
  StartupMicrSD();
  FaultDetect();
  SensorCalibration();
  ClearFaultAlert();
  PrintHeader();
}


void loop()
{
  WriteInterupt();
  InboundSerialData();
}

Now the deadline was 2 weeks away, so the pressure for a passing result was at its highest. The first test had the GPS lock take within 30 seconds. The following day, three additional tests were done in varying manners with all passing results. The firmware was ready for the hardware build phase.

Now with the project ready for building, hardware used in the development would need to be sourced for the final builds. The next section will cover a supply chain challenge and late phase changes to firmware.

Comments are closed.