Preston Furulie
Published © MIT

Not A UFO

PC NASA ASCEND SPRING 2026 — ATMOSPHERIC RESEARCH Spring 2026 launch successfully completed. www.pcnasaascend.com

AdvancedProtip7 days13
Not A UFO

Things used in this project

Story

Read more

Custom parts and enclosures

BODY_MODULE_PROTOTYPE

One of MANY parts
Body modular design for sensors and hardware

Schematics

BURST

PEAK ELEVATION

BOTTOM BOARD

LIMITED INFORMATION REGARDING THE INTERNAL DESIGN OF THE PAYLOAD

Code

Arduino C++

C/C++
Latest debugged and improved ide
/*   (        )     )           )  (        )              )   (     (                       
   )\ )  ( /(  ( /(        ( /(  )\ )  ( /(       (   ( /(   )\ )  )\ )       (            
  (()/(  )\()) )\())  (    )\())(()/(  )\())      )\  )\()) (()/( (()/(  (    )\ )    (    
   /(_))((_)\ ((_)\   )\  ((_)\  /(_))((_)\     (((_)((_)\   /(_)) /(_)) )\  (()/(    )\   
  (_))   _((_)  ((_) ((_)  _((_)(_))  __((_)    )\___  ((_) (_))  (_))  ((_)  /(_))_ ((_)  
  | _ \ | || | / _ \ | __|| \| ||_ _| \ \/ /   ((/ __|/ _ \ | |   | |   | __|(_)) __|| __| 
  |  _/ | __ || (_) || _| | .` | | |   >  <     | (__| (_) || |__ | |__ | _|   | (_ || _|  
  |_|   |_||_| \___/ |___||_|\_||___| /_/\_\     \___|\___/ |____||____||___|   \___||___|
          _____                    _____                    _____                    _____          
         /\    \                  /\    \                  /\    \                  /\    \         
        /::\____\                /::\    \                /::\    \                /::\    \        
       /::::|   |               /::::\    \              /::::\    \              /::::\    \       
      /:::::|   |              /::::::\    \            /::::::\    \            /::::::\    \      
     /::::::|   |             /:::/\:::\    \          /:::/\:::\    \          /:::/\:::\    \     
    /:::/|::|   |            /:::/__\:::\    \        /:::/__\:::\    \        /:::/__\:::\    \    
   /:::/ |::|   |           /::::\   \:::\    \       \:::\   \:::\    \      /::::\   \:::\    \   
  /:::/  |::|   | _____    /::::::\   \:::\    \    ___\:::\   \:::\    \    /::::::\   \:::\    \  
 /:::/   |::|   |/\    \  /:::/\:::\   \:::\    \  /\   \:::\   \:::\    \  /:::/\:::\   \:::\    \ 
/:: /    |::|   /::\____\/:::/  \:::\   \:::\____\/::\   \:::\   \:::\____\/:::/  \:::\   \:::\____\
\::/    /|::|  /:::/    /\::/    \:::\  /:::/    /\:::\   \:::\   \::/    /\::/    \:::\  /:::/    /
 \/____/ |::| /:::/    /  \/____/ \:::\/:::/    /  \:::\   \:::\   \/____/  \/____/ \:::\/:::/    / 
         |::|/:::/    /            \::::::/    /    \:::\   \:::\    \               \::::::/    /  
         |::::::/    /              \::::/    /      \:::\   \:::\____\               \::::/    /   
         |:::::/    /               /:::/    /        \:::\  /:::/    /               /:::/    /    
         |::::/    /               /:::/    /          \:::\/:::/    /               /:::/    /     
         /:::/    /               /:::/    /            \::::::/    /               /:::/    /      
        /:::/    /               /:::/    /              \::::/    /               /:::/    /       
        \::/    /                \::/    /                \::/    /                \::/    /        
         \/____/                  \/____/                  \/____/                  \/____/                                                                                                                                                                                                                                                                        
_____/\\\\\\\\\________/\\\\\\\\\\\__________/\\\\\\\\\__/\\\\\\\\\\\\\\\__/\\\\\_____/\\\__/\\\\\\\\\\\\____        
 ___/\\\\\\\\\\\\\____/\\\/////////\\\_____/\\\////////__\/\\\///////////__\/\\\\\\___\/\\\_\/\\\////////\\\__       
  __/\\\/////////\\\__\//\\\______\///____/\\\/___________\/\\\_____________\/\\\/\\\__\/\\\_\/\\\______\//\\\_      
   _\/\\\_______\/\\\___\////\\\__________/\\\_____________\/\\\\\\\\\\\_____\/\\\//\\\_\/\\\_\/\\\_______\/\\\_     
    _\/\\\\\\\\\\\\\\\______\////\\\______\/\\\_____________\/\\\///////______\/\\\\//\\\\/\\\_\/\\\_______\/\\\_    
     _\/\\\/////////\\\_________\////\\\___\//\\\____________\/\\\_____________\/\\\_\//\\\/\\\_\/\\\_______\/\\\_   
      _\/\\\_______\/\\\__/\\\______\//\\\___\///\\\__________\/\\\_____________\/\\\__\//\\\\\\_\/\\\_______/\\\__  
       _\/\\\_______\/\\\_\///\\\\\\\\\\\/______\////\\\\\\\\\_\/\\\\\\\\\\\\\\\_\/\\\___\//\\\\\_\/\\\\\\\\\\\\/___ 
        _\///________\///____\///////////___________\/////////__\///////////////__\///_____\/////__\////////////_____
HailMaryV1f
2026-04-08
PC NASA ASCEND
Contributors: Angela Trainor, Marquis Muza, Ethan Pierson, Emma Landis; Preston Furulie (V1f)

CHANGELOG V1f (from V1e)
=========================
BUG FIXES (Spring 2026 flight CSV root-cause analysis):
  1) CSV ROW CORRUPTION  println(magZ) split rows; GPS time landed on next row.
  2) DOUBLE-WRITE  logData buffered AND wrote; every point duplicated.
  3) BUFFER FLUSH TIMING  exact-millis match never fired. Elapsed-based now.
  4) MAG VALIDATION  0..100 range discarded negative field vectors (-39 uT).
  5) GPS LOCATION  re-enabled with int32_t scaled integers (no String alloc).
  6) HEADER DUPLICATION  checks file size, only writes if empty.
  7) SD RECOVERY  marks card offline on open() fail, retries periodically.
  8) BNO055 INIT  fixed "} else ;" fall-through to success message.
  9) BNO055 MAG ZERO  was setting accelSuccess=false instead of magSuccess.
  10) F() MACRO  all Serial strings in PROGMEM to save SRAM.

ADVANCED ADDITIONS (V1f post-flight engineering):
  11) GPS AIRBORNE MODE  UBX-CFG-NAV5 dynamic model 6 (Airborne <1g)
      sent to VK2828U7G5LF (u-blox G7020) on boot.
      Raises COCOM ceiling from ~12km to 50km. Without this, GPS
      silently stops reporting position above ~12km altitude.
  12) IMPACT DETECTION  BNO055 accel magnitude monitored during DESCENT
      phase only. Threshold 15G (147 m/s^2), above the 9G descent forces
      but catches hard ground strikes. Emergency SD flush on trigger.
  13) STALE DATA TRACKING  per-row staleness counter for BMP and BNO.
      stale=0 means fresh sensor read. stale>0 means repeated value.
      Researchers can filter out unreliable data in post-processing.
  14) FLIGHT PHASE DETECTION  altitude-based state machine:
      GROUND(0) -> ASCENT(1) -> FLOAT(2) -> DESCENT(3) -> LANDED(4)
      Logged per-row for automatic data segmentation.
  15) GPS QUALITY COLUMNS  satellites, HDOP, GPS altitude logged per-row.
  16) BNO055 CALIBRATION  packed cal status logged (sys|gyro|accel|mag).
  17) SENSOR HEALTH BITMASK  8-bit per row: UV1|UV2|UV3|UV4|BMP|BNO|SD|GPS
  18) BMP COLD SHUTDOWN  marks sensor cold-dead after 10 consecutive fails
      when temp < -35C. Recovery attempted every 30s instead of every read.
  19) WATCHDOG TIMER  AVR WDT at 8s. If I2C bus hangs (BNO055/AS7331 pulling
      SDA low in cold/vibration) or any code path blocks, the Mega resets
      automatically and logging resumes. #1 cause of "it stopped recording."
  20) SERIAL1 RX BUFFER 256  default 64-byte buffer overflows when SD writes
      stall for 100ms+. At 9600 baud GPS fills 64 bytes in 67ms. Now 256.
  21) EEPROM LANDING BACKUP  last valid GPS lat/lng/alt written to EEPROM
      every 30s. Survives SD corruption, power loss, hard reset. 100k write
      cycles = 34 days continuous at 30s interval. Printed on boot.
  22) VERTICAL VELOCITY  (alt - prevAlt) / dt per row, in m/s.
      Instant ascent/descent rate without post-processing 180k rows.
  23) ACCEL MAGNITUDE  sqrt(ax^2+ay^2+az^2) per row, in m/s^2.
      Full G-force profile: turbulence, burst, parachute load, landing.
  24) FREE SRAM MONITORING  available bytes logged per row. If memory leaks
      or stack collision, the number drops visibly before a crash.
  25) MILLIS ROLLOVER SAFE  all timing uses unsigned subtraction
      (currentTime - lastTime >= interval) which wraps correctly at 49.7 days.
      No special handling needed; noted for documentation.

CSV COLUMN REFERENCE (37 columns):
  elapsed(ms), UV1A-C, UV2A-C, UV3A-C, UV4A-C,
  pressure(Pa), temp(C), alt(m),
  gyroX/Y/Z(deg/s), accelX/Y/Z(m/s2), magX/Y/Z(uT),
  time_utc, lat, lng, gps_sats, gps_hdop, gps_alt(m),
  stale_bmp, stale_bno, bno_cal, health, phase,
  vert_vel(m/s), accel_mag(m/s2), free_ram(bytes)

DECODING PACKED FIELDS:
  bno_cal (0-255): sys=(val>>6)&3, gyro=(val>>4)&3, accel=(val>>2)&3, mag=val&3
  health  (0-255): b7=UV1, b6=UV2, b5=UV3, b4=UV4, b3=BMP, b2=BNO, b1=SD, b0=GPS
  phase   (0-4):   0=ground, 1=ascent, 2=float, 3=descent, 4=landed

HARDWARE:
  Board:  Arduino Mega 2560
  GPS:    VK2828U7G5LF (u-blox G7020) on Serial1 @ 9600 baud (pins 18/19)
  Baro:   BMP388/BMP390 via software SPI (CS=10, SCK=13, MISO=12, MOSI=11)
  IMU:    BNO055 via I2C @ 0x28 (NDOF 9-axis fusion mode)
  UV:     4x SparkFun AS7331 via I2C @ 0x74, 0x75, 0x76, 0x77
  SD:     CS pin 53, file "asusux.csv"
  EEPROM: Mega internal 4KB (addresses 0-11 for GPS backup)

BAUD RATE: 115200 (Serial monitor) / 9600 (GPS Serial1)

// END README /////////////////////////////////////////////////////*/

/////////////////////////////////////////////////////////////////////
// GLOBAL ///////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

// Enlarge Serial1 RX buffer BEFORE Arduino.h pulls in HardwareSerial.
// Default is 64 bytes. At 9600 baud the GPS fills that in 67ms.
// SD FAT writes can stall for 100-200ms, causing NMEA sentence loss.
#define SERIAL_RX_BUFFER_SIZE 256

#include <Arduino.h>
#include <Wire.h>
#include <SparkFun_AS7331.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BMP3XX.h"
#include <SD.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>
#include <TinyGPS++.h>
#include <avr/wdt.h>
#include <EEPROM.h>

// Pin definitions
#define BMP_SCK 13
#define BMP_MISO 12
#define BMP_MOSI 11
#define BMP_CS 10
#define SD_CHIP_SELECT 53

#define SEALEVELPRESSURE_HPA (1013.25)
#define BUFFER_SIZE 10

#define BNO055_ADDR 0x28
#define BNO055_SAMPLERATE_DELAY_MS 100

// Impact detection threshold: ~15g = 147 m/s^2
// Spring 2026 descent was ~9G normal, so threshold must be above that.
// A hard ground strike (corrupted video on landing) is 20-50G+.
// BNO055 16g range maxes at ~156 m/s^2. 15G gives clear separation.
// Only armed during DESCENT phase to prevent false triggers.
#define IMPACT_THRESHOLD_MS2 147.0

// Flight phase constants
#define PHASE_GROUND  0
#define PHASE_ASCENT  1
#define PHASE_FLOAT   2
#define PHASE_DESCENT 3
#define PHASE_LANDED  4

// Altitude thresholds for phase detection (meters, BMP-based)
#define ASCENT_TRIGGER_M    100.0   // 100m above launch = definitely ascending
#define FLOAT_RATE_MS       2.0     // <2 m per sample = floating
#define DESCENT_DROP_M      50.0    // dropped 50m from peak = descending
#define LANDED_ALT_M        200.0   // below 200m after descent = landed
#define LANDED_RATE_MS      1.0     // <1 m/s vertical = stopped

// BMP cold shutdown thresholds
#define BMP_COLD_TEMP_C     (-55.0) // below this, BMP390 is unreliable
#define BMP_FAIL_LIMIT      10      // consecutive fails before marking cold-dead
#define BMP_COLD_RECOVERY_MS 30000  // try to revive frozen BMP every 30s

static const char DATA_FILE_NAME[] = "asusux.csv";

// Sensor objects
SfeAS7331ArdI2C myUVSensor1;
SfeAS7331ArdI2C myUVSensor2;
SfeAS7331ArdI2C myUVSensor3;
SfeAS7331ArdI2C myUVSensor4;
Adafruit_BMP3XX bmp;
Adafruit_BNO055 bno = Adafruit_BNO055(1, BNO055_ADDR);
File dataFile;

// UV data
float UV1A, UV1B, UV1C;
float UV2A, UV2B, UV2C;
float UV3A, UV3B, UV3C;
float UV4A, UV4B, UV4C;

// BNO055 data
float gyroX, gyroY, gyroZ;
float accelX, accelY, accelZ;
float magX, magY, magZ;

// Previous values for validation fallback
float prevUV1A, prevUV1B, prevUV1C;
float prevUV2A, prevUV2B, prevUV2C;
float prevUV3A, prevUV3B, prevUV3C;
float prevUV4A, prevUV4B, prevUV4C;
uint32_t prevPressure;
float prevTemperature;
float prevAltitude;
float prevGyroX, prevGyroY, prevGyroZ;
float prevAccelX, prevAccelY, prevAccelZ;
float prevMagX, prevMagY, prevMagZ;

// Timing
unsigned long lastWriteTime = 0;
const unsigned long writeInterval = 500;
unsigned long lastRecoveryAttempt = 0;
const unsigned long recoveryInterval = 5000;
unsigned long lastBufferFlushTime = 0;
const unsigned long bufferFlushInterval = 5000;
unsigned long lastSDRecoveryAttempt = 0;
const unsigned long sdRecoveryInterval = 5000;

// Stale data counters
uint16_t staleBmpCount = 0;
uint16_t staleBnoCount = 0;
unsigned long lastBmpColdRecovery = 0;
bool bmpColdDead = false;   // true when BMP is frozen below operating temp

// Flight phase state
uint8_t flightPhase = PHASE_GROUND;
float launchAltitude = 0;    // recorded at first valid BMP read
float peakAltitude = 0;      // highest BMP altitude seen
float prevPhaseAltitude = 0; // for rate-of-climb calculation
bool launchAltitudeSet = false;

// Impact detection
bool impactDetected = false;
unsigned long impactTime = 0;

// EEPROM GPS backup
#define EEPROM_GPS_ADDR 0        // start address for 12 bytes: lat(4)+lng(4)+alt(4)
unsigned long lastEepromWrite = 0;
const unsigned long eepromWriteInterval = 30000;  // 30 seconds

// Vertical velocity tracking
float prevVelocityAlt = 0;
unsigned long prevVelocityTime = 0;

// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //
// REDACTED //

Credits

Preston Furulie
1 project • 0 followers

Comments