Friday, July 11, 2014

HPR L1 - part two - The Payload

As I stated in my last post, I can explore and learn with the Torrent kit in a number of ways, but my immediate goal is Level One HPR certification. That said, however, I can't just leave an empty seat in a payload bay when a rocket is going up with a $30 motor.

Aside from rocketry, the other things I've been playing around with a lot lately are electronics and microcontrollers. Some time ago, I'd picked up a GPS component on clearance at Radio Shack for pretty cheap. After testing it out with an Arduino, I decided that I could build a data logger to ride along on my L1 flight. In addition, I've added a couple of very inexpensive sensors I got from China on eBay - a barometric pressure sensor to detect altitude, and a 3-axis accelerometer.

Here's the parts list:
  • Arduino 3.3V Pro Mini - $4 (Arduino Day sale price - thanks SparFun!)
  • Parallax PMB-648 GPS module - I don't remember how much it was.
  • BMP-180 pressure sensor - $2 (from eBay)
  • ADXL345 accelerometer - $3 (from eBay)
  • SD card reader breakout board - $3 (from eBay)
  • 9V batter holder
  • RGB LED
  • 3 resistors
  • Adafruit half-size Perma-Proto board
  • a small slide switch
  • some 22 gauge wire
  • female headers
  • solder
  • some M2 and M3 screws, nuts, and standoffs for mounting everything
Overall, under $40.

It's not the most compact or elegant package, but mainly with this version I want to see if it works, how everything holds up under launch G-forces and recovery conditions, and what kind of data I get. If it works well, I plan to build a new version, using a 10 DOF board, a micro-SD card, and maybe a better/smaller GPS and a LiPo battery.

Here's a diagram of how I wired it. I mounted the Arduino, the pressure sensor, and the accelerometer on the proto-board, but in this diagram I just included the headers to show where they would be placed, since there are some wires running underneath the breakout boards. The specific Arduino pin connections are detailed in the comments of the sketch.




The ADXL345 board I have has two rows of 5 pins, and is mounted in rows I6-10 and A6-10. The BMP180 is in E12-15. The Arduino has pin 9 at the top left. Note also that the Arduino Micro has A4 and A5 hole that are not on the outer edge, so the green and yellow wires shown at D14 and D15 are actually wires that are soldered directly to the Pro Micro. Also, I chose a 470 Ω resistor for the green portion of the LED because otherwise it looked a whole lot brighter than the red and blue components.

The pressure sensor and the accelerometer communicate with the Arduino over I2C, and I use the Adafruit libraries to interface with them. The SD card connects over SPI. In initial testing, I tried reading the GPS with SoftwareSerial, but found that it was a lot faster connected to the hardware UART TX/RX lines. Note that in my final configuration, I did not solder the GPS line that connects to RX1, but instead connect it with a jumper, because this needs to be disconnected whenever I re-program the Arduino.

I didn't bother making any sort of power switch that I could access when this is set up inside the payload bay for two reasons: First, since this device doesn't arm any sort of ejection charge, there is no safety consideration for doing it. Second, in testing I found that it will easily run for over two hours on a rechargeable 9V, so it is no problem to just power it up as I prep the rocket for launch, even if it means that it runs and collects ground data while I wait for a launch spot.

When running, the LED flashes each time a write is done to the SD card,  with one color indicating if each of the three sensors is working. Typically it will  flash with red and green immediately on power-up, then after 30s or so, when the GPS gets a fix, the blue will kick in. I've found that I can see these colors by peaking into the vent hole of the bay when it is all loaded up for a final check.

The data is written to a CSV file on the SD card. I get about 12 lines per second of altimeter and accelerometer data, and a new GPS read about once or twice a second (this seems to come in at random times). With an old 2GB SD card in there, I have more space than I'll ever need. I'd considered using EEPROM modules to store the data to save some physical space and power, but it would limit the memory space too much and make it harder to download the data. Plus power use doesn't seem to be an issue, so it didn't seem worth pursuing. Maybe in a future version.

Partly because of its bulk, and partly because I want to save the sled that came with the Torrent kit for a "real" dual-deployment altimeter, I made my own sled for mounting of the electronics. I epoxied four 1.5" lengths of aluminum tubing to a piece of 1/8" thick craft plywood cut to fit all the components, then mounted everything up with the screws. Except the GPS, which has no mounting holes - that is just held on with double-stick foam tape. I'm crossing my fingers on that one (though I may do something else to reinforce it - zip tie holes?).

Overall weight of the electronics, battery, and sled is 148g.

All completed, here is what it looks like, before and after mounting it on the sled:




And finally, here is the Arduino sketch I put together:

/* Torrent datalogger payload
   
 OUTPUT FILE FORMAT:
   millis, altitude (m), x accel (m/s^2), y accel, z accel,
   lat, long, GPS altitude, course, speed, satellites, date, time
 
 * SD card attached to SPI bus as follows:
 ** MOSI - pin 11 - green
 ** MISO - pin 12 - blue
 ** CLK - pin 13  - yellow
 ** CS - pin 4    - orange
 
 Altimeter, accelerometer (I2C):
   Connect SCL to analog 5
   Connect SDA to analog 4
   Connect VDD to 3.3V DC
   Connect GROUND to common ground
   
 GPS
   Yellow wire to Arduino RX

 Status LED
  red - 7 green - 8  blue - 9
   
 */
 
#include <SD.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085_U.h>
#include <Adafruit_ADXL345_U.h>
#include <TinyGPS.h>

int led = 13;
int red = 7;
int green = 8;
int blue = 9;
boolean red_status = LOW;
boolean green_status = LOW;
boolean blue_status = LOW;
unsigned long last_fix = 1;

File myFile;
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10085);
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);
float adxl[3];
TinyGPS gps;

void setup()
{
  Serial.begin(4800);  // necessary for GPS read
  pinMode(led, OUTPUT);
  pinMode(red, OUTPUT);    
  pinMode(green, OUTPUT);
  pinMode(blue, OUTPUT);
  
  /* Initialise the altimeter */
  if(!bmp.begin())
  {
    //Serial.print("Ooops, no BMP085 detected ... Check your wiring or I2C ADDR!");
    ;
  }
  
  /* Initialise the accelerometer */
  if(!accel.begin())
  {
    /* There was a problem detecting the ADXL345 ... check your connections */
    //Serial.println("Ooops, no ADXL345 detected ... Check your wiring!");
    ;
  }
  accel.setRange(ADXL345_RANGE_16_G);

  // On the Ethernet Shield, CS is pin 4. It's set as an output by default.
  // Note that even if it's not used as the CS pin, the hardware SS pin 
  // (10 on most Arduino boards, 53 on the Mega) must be left as an output 
  // or the SD library functions will not work. 
   pinMode(10, OUTPUT);
   
  if (!SD.begin(4)) {
    //Serial.println("initialization failed!");
    return;
  }
  //Serial.println("initialization done.");
    
}  // setup

float get_altitude() {
  sensors_event_t event;
  bmp.getEvent(&event);
  if (event.pressure) {
    float temperature;
    bmp.getTemperature(&temperature);
    float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
    //Serial.println(seaLevelPressure);
    float my_altitude;
    my_altitude = bmp.pressureToAltitude(seaLevelPressure,
                                        event.pressure,
                                        temperature);
    if ( my_altitude < 0 ) {
      red_status = LOW; 
      return -1;
    }
    else {
      red_status = HIGH; 
      return my_altitude;
    }
    
  }  
  else { 
    red_status = LOW; 
    return 0; }
}

void get_accel(float *pdata) {
  sensors_event_t event; 
  accel.getEvent(&event);
  if (event.acceleration.x) {
    pdata[0] = event.acceleration.x;
    pdata[1] = event.acceleration.y;
    pdata[2] = event.acceleration.z;
    green_status = HIGH;
  }
  else {
    green_status = LOW; 
    pdata[0] = 999; 
    pdata[1] = 999;
    pdata[2] = 999;
  }
}

bool feedgps() {
  while (Serial.available()) {
    if (gps.encode(Serial.read()))
      return true;
  }
  return false;
}


void gpsdump(TinyGPS &gps) {

  float latitude, longitude;
  unsigned long fix_age;
  gps.f_get_position(&latitude, &longitude, &fix_age);
  
  if (fix_age == TinyGPS::GPS_INVALID_AGE )
    blue_status = LOW;
  else if (fix_age > 2000)
    blue_status = LOW;
  else
    blue_status = HIGH;
  
  int year;
  byte month, day, hour, minute, second, hundredths;
  gps.crack_datetime(&year,&month,&day,&hour,&minute,&second,&hundredths);
  
  
  myFile.print(",");
  myFile.print(latitude,5);
  myFile.print(",");
  myFile.print(longitude,5);
  myFile.print(",");
  myFile.print(gps.f_altitude());
  myFile.print(",");
  myFile.print(gps.f_course());
  myFile.print(",");
  myFile.print(gps.f_speed_kmph());
  myFile.print(",");
  myFile.print(gps.satellites());
  myFile.print(",");
  myFile.print(month, DEC); myFile.print("/"); myFile.print(day, DEC); myFile.print("/"); myFile.print(year);
  myFile.print(",");
  myFile.print(hour, DEC); myFile.print(":"); myFile.print(minute, DEC); myFile.print(":"); myFile.print(second, DEC);

}


void led_on () {
  digitalWrite(red, red_status);
  digitalWrite(green, green_status);
  digitalWrite(blue, blue_status);
}

void led_off () {
  digitalWrite(red, LOW);
  digitalWrite(green, LOW);
  digitalWrite(blue, LOW);
}


void loop()
{
  myFile = SD.open("torr06.csv", FILE_WRITE);
  if (myFile) {
    //digitalWrite(led, HIGH);
    if ( millis() - last_fix > 4000 ) {
      blue_status = LOW;
    }
    led_on();
    //Serial.println("writing");
    myFile.print(millis());
    myFile.print(',');
    myFile.print(get_altitude());
    get_accel(&adxl[0]);
    myFile.print(',');
    myFile.print(adxl[0]);
    myFile.print(',');
    myFile.print(adxl[1]);
    myFile.print(',');
    myFile.print(adxl[2]);
    if (feedgps()) {
      last_fix = millis();
      gpsdump(gps);
    }
    myFile.println(' ');
    myFile.close();
    //digitalWrite(led, LOW);
    led_off();
  }

}