Small Air-Pressure Sensor

I put together a small air-pressure sensor that could fit inside a Junior Model rocket nose cone. The components used were:-

  • 3.3v Mini Trinket
  • BMP180 GY-68 sensor
  • 50mAH Lithium Ion Battery
  • A lot of hardcore programming to get it to fit in 5k of space!

Some pictures of this are shown below.

 

Everything together.
Everything together.
Glued string to interior of the Nose cone. This string then is thread through cap and tied to it. This keeps everything together.
Glued string to interior of the Nose cone. This string then is thread through cap and tied to it. This keeps everything together.

NOTE: We had a very small hole drilled into the side of the Nose Cone to allow air pressure to equalise.

Top view of sensor unit. Battery is glued to the Veraboard pcb. Wires that you see connect to the Air pressure sensor on top
Top view of sensor unit. Battery is glued to the Veraboard pcb. Wires that you see connect to the Air pressure sensor on top
Underside of sensor device. On/Off switch is at top. Three pin header at bottom of for Serial Connection.
Underside of sensor device. On/Off switch is at top. Three pin header at bottom of for Serial Connection.

 

The code had to be able to detect a reduction in Air Pressure, to sense that the rocket flight had started. Then it had to record the last 10 measurements plus another 40 measurements inside the EPROM of the Arduino. We had a measurement taking place every 0.2 seconds, so we got 10 seconds of measurements.

And it worked quite well! Below is a plot of points for maiden flight.

 

Air Pressure vs time
Air Pressure vs time

Below is the code I used. Please note, that I have not included a cut-down version of SoftwareSerial. Essentially the Trinket never needs to receive data on its software Serial link, so I commented out code in the library. This allows the program to JUST fit inside the 5310 Bytes.

 

#include <TinyWireM.h>
#include <SoftwareSerial.h>
#include <EEPROM.h>

// Uncomment following line, recompile and upload to
// read results. This is because we don't have enough
// space to have READ and WRITE functionality co-existing.
// #define READ

SoftwareSerial mySerial(4, 3); // RX, TX

int addr = 0; // EEPROM memory

#define BMP180_ADDRESS 0x77 // I2C address of BMP180 
// define calibration data for temperature:
int ac1;
int ac2; 
int ac3; 
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1; 
int b2;
int mb;
int mc;
int md;
long b5; 
//define variables for pressure and temperature calculation
long x1,x2;
//define variables for pressure calculation
long x3,b3,b6,p;
unsigned long b4,b7;
//define variables for temperature and pressure reading

long p_avg = 0;
byte iter = 0;
int p_threshold = 200;
int p_threshold_count_required = 3;
int p_threshold_count = 0;
boolean launch_detected = false;
boolean information_saved = false;
long rawpressure;


long hist_measurements[10];
long measurements[40];
short hist_measurement_index = 0;
short measurement_index = 0;


const unsigned char OSS = 3; // Oversampling Setting
/* blz 12 Datasheet
OSS=0 ultra Low Power Setting, 1 sample, 4.5 ms 3uA
OSS=1 Standard Power Setting, 2 samples, 7.5 ms 5uA
OSS=2 High Resolution, 4 samples, 13.5 ms 7uA
OSS=3 Ultra High Resolution, 2 samples, 25.5 ms 12uA
*/


void setup() {

 pinMode(1, OUTPUT);
 digitalWrite(1, HIGH);
 
 
 TinyWireM.begin();
 delay(5000);
 
 // First read calibration data from EEPROM
 ac1 = bmp180ReadInt(0xAA);
 ac2 = bmp180ReadInt(0xAC);
 ac3 = bmp180ReadInt(0xAE);
 ac4 = bmp180ReadInt(0xB0);
 ac5 = bmp180ReadInt(0xB2);
 ac6 = bmp180ReadInt(0xB4);
 b1 = bmp180ReadInt(0xB6);
 b2 = bmp180ReadInt(0xB8);
 mb = bmp180ReadInt(0xBA);
 mc = bmp180ReadInt(0xBC);
 md = bmp180ReadInt(0xBE);


 delay(5000);
 mySerial.begin(9600);
 // mySerial.println("S");
 digitalWrite(1, LOW);

#ifdef READ
 // uncomment this and comment out the writeResults below when wanting to read values.
 readResults();
#endif 
 
}

void loop() {
 // first, read uncompensated temperature
 //temperature = bmp180ReadUT();
 //and then calculate calibrated temperature
 unsigned int rawtemp = bmp180ReadUT();
 b5 = computeB5 (rawtemp);
 // temperature = bmp180CorrectTemperature(bmp180ReadUT());
 // then , read uncompensated pressure
 rawpressure = bmp180ReadUP();

 b6 = b5 - 4000;
 // Calculate B3
 x1 = (b2 * (b6 * b6)>>12)>>11;
 x2 = (ac2 * b6)>>11;
 x3 = x1 + x2;
 b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;
 
 // Calculate B4
 x1 = (ac3 * b6)>>13;
 x2 = (b1 * ((b6 * b6)>>12))>>16;
 x3 = ((x1 + x2) + 2)>>2;
 b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;
 
 b7 = ((unsigned long)(rawpressure - b3) * (50000>>OSS));
 if (b7 < 0x80000000) {
 p = (b7<<1)/b4;
 } else {
 p = (b7/b4)<<1; 
 }
 x1 = (p>>8) * (p>>8);
 x1 = (x1 * 3038)>>16;
 x2 = (-7357 * p)>>16;
 p += (x1 + x2 + 3791)>>4;
 

// Debugging 
// mySerial.println(p);

 if (iter < 10) {
 p_avg = (iter * p_avg + p)/(iter+1);
 iter++; 


// Debugging
/*
 if (iter == 10) {
 mySerial.print("a"); 
 mySerial.println(p_avg);

 mySerial.println(((p_avg >> 32) & 0xff), HEX);
 mySerial.println(((p_avg >> 16) & 0xff), HEX);
 mySerial.println(((p_avg >> 8) & 0xff), HEX);
 mySerial.println((p_avg & 0xff), HEX);

 delay(120);
 }
*/
 
 }

 if ( p_avg - p > p_threshold) {
 p_threshold_count++;
 } else {
 p_threshold_count = 0;
 }

 if (p_threshold_count >= p_threshold_count_required) {
 launch_detected = true;
 }

 
#ifndef READ
 if (launch_detected) {
 digitalWrite(1, HIGH);
 measurements[measurement_index] = p;
 measurement_index++;
 if (measurement_index > 40) {
 measurement_index = 0; 
 if (! information_saved) {
 information_saved = true;
 writeResults (p_avg, hist_measurement_index);
 }
 }
 
 } else {
 hist_measurements[hist_measurement_index] = p;
 hist_measurement_index++;
 if (hist_measurement_index > 9) hist_measurement_index = 0;
 }
#endif
 
 // mySerial.println(rawtemp);
 // mySerial.println(temperature);
 // mySerial.println(b5);
 delay(200);
}



int bmp180ReadInt(unsigned char address)
{
 unsigned char msb, lsb;
 TinyWireM.beginTransmission(BMP180_ADDRESS);
 TinyWireM.send(address);
 TinyWireM.endTransmission();
 TinyWireM.requestFrom(BMP180_ADDRESS, 2);
 while(TinyWireM.available()<2);
 msb = TinyWireM.receive();
 lsb = TinyWireM.receive();
 return (int) msb<<8 | lsb;
}

unsigned int bmp180ReadUT()
{
 unsigned int ut;
 
 // Write 0x2E into Register 0xF4 and wait at least 4.5mS
 // This requests a temperature reading 
 // with results in 0xF6 and 0xF7
 TinyWireM.beginTransmission(BMP180_ADDRESS);
 TinyWireM.send(0xF4);
 TinyWireM.send(0x2E);
 TinyWireM.endTransmission();
 
 // Wait at least 4.5ms
 delay(10);
 
 // Then read two bytes from registers 0xF6 (MSB) and 0xF7 (LSB)
 // and combine as unsigned integer
 ut = bmp180ReadInt(0xF6);
 return ut;
}

/*
double bmp180CorrectTemperature(unsigned int ut)
{
 x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
 x2 = ((long)mc << 11)/(x1 + md); 
 b5 = x1 + x2; 
 return (((b5 + 8)>>4)); 
}
*/


// Read the uncompensated pressure value
unsigned long bmp180ReadUP()
{
 unsigned char msb, lsb, xlsb;
 unsigned long up = 0;
 
 // Write 0x34+(OSS<<6) into register 0xF4
 // Request a pressure reading w/ oversampling setting
 TinyWireM.beginTransmission(BMP180_ADDRESS);
 TinyWireM.send(0xF4);
 TinyWireM.send(0x34 + (OSS<<6));
 TinyWireM.endTransmission();
 
 // Wait for conversion, delay time dependent on OSS
 // delay(5 + (5*OSS));
 delay(26);
 
 // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
 TinyWireM.beginTransmission(BMP180_ADDRESS);
 TinyWireM.send(0xF6);
 TinyWireM.endTransmission();
 TinyWireM.requestFrom(BMP180_ADDRESS, 3);
 
 // Wait for data to become available
 while(TinyWireM.available() < 3)
 ;
 msb = TinyWireM.receive();
 lsb = TinyWireM.receive();
 xlsb = TinyWireM.receive();
 
 up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
 
 return up;
}


double bmp180CorrectPressure(unsigned long up)
{ 
 b6 = b5 - 4000;
 // Calculate B3
 x1 = (b2 * (b6 * b6)>>12)>>11;
 x2 = (ac2 * b6)>>11;
 x3 = x1 + x2;
 b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;
 
 // Calculate B4
 x1 = (ac3 * b6)>>13;
 x2 = (b1 * ((b6 * b6)>>12))>>16;
 x3 = ((x1 + x2) + 2)>>2;
 b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;
 
 b7 = ((unsigned long)(up - b3) * (50000>>OSS));
 if (b7 < 0x80000000) {
 p = (b7<<1)/b4;
 } else {
 p = (b7/b4)<<1; 
 }
 x1 = (p>>8) * (p>>8);
 x1 = (x1 * 3038)>>16;
 x2 = (-7357 * p)>>16;
 p += (x1 + x2 + 3791)>>4;
 
 return p;
}


int32_t computeB5(int32_t UT) {
 int32_t X1 = (UT - (int32_t)ac6) * ((int32_t)ac5) >> 15;
 int32_t X2 = ((int32_t)mc << 11) / (X1+(int32_t)md);
 return X1 + X2;
}


// avg_pressure = 32-bit measurement of air pressure - AVERAGE
// hist_index = where we are up to in the histortical measurements...the place of the last value
// This helps us know where the start/end numbers are in the historical data...when we go to 
// put all the data back together again.
// historical_measurements = pointer to where the historical measurements are
// measurements = pointer to post launch test results are stored.
void writeResults(long avg_pressure, short hist_index)
{
 // Save the Average value
 EEPROM.write(addr, ((avg_pressure >> 16) & 0xff));
 addr++;
 EEPROM.write(addr, ((avg_pressure >> 8) & 0xff));
 addr++;
 EEPROM.write(addr, (avg_pressure & 0xff));
 addr++;

 EEPROM.write(addr, hist_index);
 addr++;
 for (int i = 0; i < 10; i++) {
 EEPROM.write(addr, ((hist_measurements[i] >> 16) & 0xff));
 addr++;
 EEPROM.write(addr, ((hist_measurements[i] >> 8) & 0xff));
 addr++;
 EEPROM.write(addr, (hist_measurements[i] & 0xff)); 
 addr++;
 }

 for (int i = 0; i < 40; i++) {
 EEPROM.write(addr, ((measurements[i] >> 16) & 0xff));
 addr++;
 EEPROM.write(addr, ((measurements[i] >> 8) & 0xff));
 addr++;
 EEPROM.write(addr, (measurements[i] & 0xff)); 
 addr++;
 }

 int i = 0;
 while (i < 100) {
 digitalWrite(1, HIGH);
 delay(50);
 digitalWrite(1, LOW);
 delay(150);
 i++;
 }
}


void readResults()
{
 byte val;
 for (int i = 0; i < 154; i++) {
 val = EEPROM.read(i);
 mySerial.println(val, HEX);
 }

 while(1);
}



 

Then I needed to write a PERL script to interrogate these data from the Serial Port and put in a CSV File.

 

#!/usr/bin/perl

$pressure_file = "data.txt";
my $data_index = 1;

open(my $fh, "<", $pressure_file)
 or die "cannot open < " . $pressure_file . ": $!";

my $line_number = 1;
while ($line = <$fh>)
{
 #print "Processing: " . $line;
 if ($line_number <= 3 ){
 $val = hex($line);
 $avg = $avg + 256**(3 - $line_number) * $val;
# print "Line: " . $line;
# print "Line Number: " . $line_number . "\n";
# print "AVG: " . $avg . "\n";
 }
 
 if ($line_number == 3) {
 print "Avg Pressure: " . $avg . "\n";
 }

 # Find Starting point in historical data
 if ($line_number == 4) {
 $start_point = $line + 1;
 if ($start_point == 11) {
 $start_point = 1;
 }
 print "Starting Point: " . $start_point . "\n";
 }

 if ($line_number > 4) {
 $v_data_idx_mod = ($line_number - 5) % 3;
# print "MOD: " . $v_data_idx_mod . "\n";
 $val = hex($line);
 $v_data = $v_data + 256 ** (2 - $v_data_idx_mod) * $val;
# print $val . " - " . $v_data_idx_mod . "\n";

 if ($v_data_idx_mod == 2) {
 # print "Reading: " . $v_data . "\n";
 if ($data_index <= 10) {
 $historical_data[$data_index] = $v_data;
 } else {
 $measurement_index = $data_index - 10;
 $measurement[$measurement_index] = $v_data;
 }
# print $v_data . "\n";
 $v_data = "";
 $data_index++; 
 }
 



 }

 $line_number++;
}

close $fh;


$historical_index = $start_point;
print "STARTING POINT: $historical_index \n";
$i = 1;

while ($i <= 10) {
$v_final[$i] = $historical_data[$historical_index];

$historical_index = $historical_index + 1;
if ($historical_index > 10) {
 $historical_index = 1;
}
$i++;
}

$i = 1;
$measurement_index = 11;
while ($i <= 40) {
$v_final[$measurement_index] = $measurement[$i];

$measurement_index++;
$i++;
}



# Cycle through data and print it out
$i = 1;
while ($i <= 50) {
$time = 0.2 * $i;
print "$time," . $v_final[$i] . "\n";
$i++;
}

 

It all seems quite desperate, but this is what you need to do if you want to make it small and fit inside the Nose Cone!

 

The total weight of this unit was 5 grams.