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.
NOTE: We had a very small hole drilled into the side of the Nose Cone to allow air pressure to equalise.
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.
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.