Tuesday, May 20, 2014

Show People Your Emotions using the Arduino Lillypad!

  In my eternal quest for finding possible projects I thought there would be no better way to get into the Lilly-pad (and this is meant to be slightly ironic), than to mess with  a slightly finicky hear sensor and even harder to time Adafruit Neopixel rings,

    Like the emotionally unstable crazy person that I am, I decided I should find a way to show people what emotion I'm most likely experiencing. I had access to a hear rate sensor and since you can actually mount those as an ear ring, it serves my needs perfectly.



    







     




    So first of all lets talk about how the pulse sensor works. If we look at the diagram above we an see that we simply have an LED going through a small circuit to a Photo-resistor. The Photo-resistor takes the amount of light present and turns it into an Analog Voltage. When blood is pumped through your finger or ear there is a difference in the amount of light. Through code we can actually get this simple sensor to act as a fairly sensitive heartbeat monitor. Plus, who doesn't want their ears to glow green?!

  
   So next we need to see how we can actually show our emotions from our heart beat. And of course the first thing that comes to mind are individually addressable LED strips, the Adafruit Neopixel rings are perfect for the job!


Important Things to Know About NeoPixels in General

  • Not all addressable LEDs are NeoPixels. “NeoPixel” is Adafruit’s brand for individually-addressable RGB color pixels and strips based on the WS2812 and WS2811 LED/drivers, using a single-wire control protocol. Other LED products we carry — WS2801 pixels, LPD8806 and “analog” strips — use different methodologies (and have their own tutorials). When seeking technical support in the forums, a solution can be found more quickly if the correct LED type is mentioned.
  • NeoPixels don’t just light up on their own; they require a microcontroller (such as Arduino) and some programming. (Just giving them power will not make them turn on)
  • NeoPixels aren’t the answer for every project. The control signal has very strict timing requirements, and some development boards (such as Netduino or Raspberry Pi) can’t reliably achieve this. This is why we continue to offer other LED types; some are more adaptable to certain situations.

The approximate peak power use (all LEDs on at maximum brightness) per meter is:
  • 30 LEDs: 9.5 Watts (just under 2 Amps at 5 Volts).
  • 60 LEDs: 18 Watts (about 3.6 Amps at 5 Volts).
  • 144 LEDs : 35 watts (7 Amps at 5 Volts).
Mixed colors and lower brightness settings will use proportionally less power.


So the plan here is to have the Neopixel Rings light up to the rhythm of  your heart. The plan is also to have it change colors if its within certain BPM (beats per minutes) ranges. A possible addition would be to have more and more Neopixels light up as your heart beats faster but this would results in the LEDs

The code will be something similar to this, timing needs to be adjusted however:


/*
 >>> Pulse Sensor purple wire goes to Analog Pin 0 by default <<<
 Pulse Sensor Power and GND wires go to a Digital Pin set High and to the Ground Plane of the PCB.

 Pulse Sensor sample aquisition and processing happens in the background via Timer 2 interrupt. 2mS sample rate.
 PWM on pins 3 and 11 will not work when using this code, because we are using Timer 2!
 The following variables are automatically updated:
 Signal :    int that holds the analog signal data straight from the sensor. updated every 2mS.
 IBI  :      int that holds the time interval between beats. 2mS resolution.
 BPM  :      int that holds the heart rate value, derived every beat, from averaging previous 10 IBI values.
 QS  :       boolean that is made true whenever Pulse is found and BPM is updated. User must reset.
 Pulse :     boolean that is true when a heartbeat is sensed then false in time with pin13 LED going out.


NeoPixel Ring goggles sketch -- Andrei Aldea 2014
Welding goggles using 50mm round lenses can be outfitted with
a pair of Adafruit NeoPixel Rings

By default, pixel #0 (the first LED) on both rings should be at the TOP of
the goggles.  Looking at the BACK of the board, pixel #0 is immediately
clockwise from the OUT connection.  If a different pixel is at the top,
that's OK, the code can compensate (TOP_LED_FIRST and TOP_LED_SECOND below).

IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across
pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input
and minimize distance between Arduino and first pixel.  Avoid connecting
on a live circuit...if you must, connect GND first.

 */

#include <Adafruit_NeoPixel.h>
#ifdef __AVR_ATtiny85__ // Trinket, Gemma or other ATtiny type devices
#include <avr/power.h>
#endif

#define PIN            0

#define TOP_LED_FIRST  0 // Change these if the first pixel is not
#define TOP_LED_SECOND 0 // at the top of the first and/or second ring.

#define EFFECT         RAINBOW // Choose a visual effect from the names below

#define RAINBOW        0
#define ECTO           1

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(32, PIN, NEO_GRB + NEO_KHZ800);

const int8_t PROGMEM
yCoord[] = { // Vertical coordinate of each pixel.  First pixel is at top.
  127,117,90,49,0,-49,-90,-117,-127,-117,-90,-49,0,49,90,117 }
,
sine[] = { // Brightness table for ecto effect
  0, 28, 96, 164, 192, 164, 96, 28, 0, 28, 96, 164, 192, 164, 96, 28 };

// Eyelid vertical coordinates.  Eyes shut slightly below center.
#define upperLidTop     130
#define upperLidBottom  -45
#define lowerLidTop     -40
#define lowerLidBottom -130

const uint8_t PROGMEM gamma8[] = {
  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,
  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,  2,  3,  3,  3,  3,
  3,  3,  4,  4,  4,  4,  5,  5,  5,  5,  5,  6,  6,  6,  6,  7,
  7,  7,  8,  8,  8,  9,  9,  9, 10, 10, 10, 11, 11, 11, 12, 12,
  13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20,
  20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29,
  30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42,
  42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
  58, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 75,
  76, 77, 78, 80, 81, 82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96,
  97, 99,100,102,103,105,106,108,109,111,112,114,115,117,119,120,
  122,124,125,127,129,130,132,134,136,137,139,141,143,145,146,148,
  150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,
  182,184,186,188,191,193,195,197,199,202,204,206,209,211,213,215,
  218,220,223,225,227,230,232,235,237,240,242,245,247,250,252,255
};

uint32_t
iColor[16][3];      // Background colors for eyes
int16_t
hue          =   0; // Initial hue around perimeter (0-1535)
uint8_t
iBrightness[16],    // Brightness map -- eye colors get scaled by these
brightness   = 220, // Global brightness (0-255)
blinkFrames  =   5, // Speed of current blink
blinkCounter =  30, // Countdown to end of next blink
eyePos       = 192, // Current 'resting' eye (pupil) position
newEyePos    = 192, // Next eye position when in motion
gazeCounter  =  75, // Countdown to next eye movement
gazeFrames   =  50; // Duration of eye movement (smaller = faster)
int8_t
eyeMotion    =   0; // Distance from prior to new position

//  VARIABLES
int pulsePin = 0;                 // Pulse Sensor purple wire connected to analog pin 0
int blinkPin = 13;                // pin to blink led at each beat
int fadePin = 5;                  // pin to do fancy classy fading blink at each beat
int fadeRate = 0;                 // used to fade LED on with PWM on fadePin


// these variables are volatile because they are used during the interrupt service routine!
volatile int BPM;                   // used to hold the pulse rate
volatile int Signal;                // holds the incoming raw data
volatile int IBI = 600;             // holds the time between beats, must be seeded!
volatile boolean Pulse = false;     // true when pulse wave is high, false when it's low
volatile boolean QS = false;        // becomes true when Arduoino finds a beat.

//====================================START OF VOID SETUP==============================================

void setup(){
  pinMode(blinkPin,OUTPUT);         // pin that will blink to your heartbeat!
  pinMode(fadePin,OUTPUT);          // pin that will fade to your heartbeat!
  Serial.begin(115200);             // we agree to talk fast!
  interruptSetup();                 // sets up to read Pulse Sensor signal every 2mS
  // UN-COMMENT THE NEXT LINE IF YOU ARE POWERING The Pulse Sensor AT LOW VOLTAGE,
  // AND APPLY THAT VOLTAGE TO THE A-REF PIN
  //analogReference(EXTERNAL);

#ifdef __AVR_ATtiny85__ // Trinket, Gemma, etc.
  if(F_CPU == 16000000) clock_prescale_set(clock_div_1);
  // Seed random number generator from an unused analog input:
  randomSeed(analogRead(2));
#else
  randomSeed(analogRead(A0));
#endif

  pixels.begin();
}

//====================================START OF VOID LOOP===============================================

void loop(){
  if (QS == true){                       // Quantified Self flag is true when arduino finds a heartbeat
    fadeRate = 255;                  // Set 'fadeRate' Variable to 255 to fade LED with pulse
    //WRITE YOUR OTHER FUNCTIONS HERE FOR THE GOGGLES! You can use BPM or BPS as a variable here
    QS = false;                      // reset the Quantified Self flag for next time  
  }

//=================================Start of Neopixel Effects===============================================

uint8_t i, r, g, b, a, c, inner, outer, ep;
  int     y1, y2, y3, y4, h;
  int8_t  y;

  // Draw eye background colors

#if EFFECT == RAINBOW

  // This renders a glotating rainbow...a WAY overdone LED effect but
  // does show the color gamut nicely.

  for(h=hue, i=0; i<16; i++, h += 96) {
    a = h;
    switch((h >> 8) % 6) {
     case 0: iColor[i][0] = 255; iColor[i][1] =   a; iColor[i][2] =   0; break;
     case 1: iColor[i][0] =  ~a; iColor[i][1] = 255; iColor[i][2] =   0; break;
     case 2: iColor[i][0] =   0; iColor[i][1] = 255; iColor[i][2] =   a; break;
     case 3: iColor[i][0] =   0; iColor[i][1] =  ~a; iColor[i][2] = 255; break;
     case 4: iColor[i][0] =   a; iColor[i][1] =   0; iColor[i][2] = 255; break;
     case 5: iColor[i][0] = 255; iColor[i][1] =   0; iColor[i][2] =  ~a; break;
    }
  }
  hue += 7;
  if(hue >= 1536) hue -= 1536;

#elif EFFECT == ECTO

  // A steampunk aesthetic might fare better with this more subdued effect.
  // Etherial green glow with just a little animation for visual spice.

  a = (hue >> 4) & 15;
  c =  hue       & 15;
  for(i=0; i<16; i++) {
    b = (a + 1) & 15;
    iColor[i][1] = 255; // Predominantly green
    iColor[i][0] = (pgm_read_byte(&sine[a]) * (16 - c) +
                    pgm_read_byte(&sine[b]) *       c  ) >> 4;
    iColor[i][2] = iColor[i][0] >> 1;
    a = b;
  }
  hue -= 3;

#endif

  // Render current blink (if any) into brightness map
  if(blinkCounter <= blinkFrames * 2) { // In mid-blink?
    if(blinkCounter > blinkFrames) {    // Eye closing
      outer = blinkFrames * 2 - blinkCounter;
      inner = outer + 1;
    } else {                            // Eye opening
      inner = blinkCounter;
      outer = inner - 1;
    }
    y1 = upperLidTop    - (upperLidTop - upperLidBottom) * outer / blinkFrames;
    y2 = upperLidTop    - (upperLidTop - upperLidBottom) * inner / blinkFrames;
    y3 = lowerLidBottom + (lowerLidTop - lowerLidBottom) * inner / blinkFrames;
    y4 = lowerLidBottom + (lowerLidTop - lowerLidBottom) * outer / blinkFrames;
    for(i=0; i<16; i++) {
      y = pgm_read_byte(&yCoord[i]);
      if(y > y1) {        // Above top lid
        iBrightness[i] = 0;
      } else if(y > y2) { // Blur edge of top lid in motion
        iBrightness[i] = brightness * (y1 - y) / (y1 - y2);
      } else if(y > y3) { // In eye
        iBrightness[i] = brightness;
      } else if(y > y4) { // Blur edge of bottom lid in motion
        iBrightness[i] = brightness * (y - y4) / (y3 - y4);
      } else {            // Below bottom lid
        iBrightness[i] = 0;
      }
    }
  } else { // Not in blink -- set all 'on'
    memset(iBrightness, brightness, sizeof(iBrightness));
  }

  if(--blinkCounter == 0) { // Init next blink?
    blinkFrames  = random(4, 8);
    blinkCounter = blinkFrames * 2 + random(5, 180);
  }

  // Calculate current eye movement, possibly init next one
  if(--gazeCounter <= gazeFrames) { // Is pupil in motion?
    ep = newEyePos - eyeMotion * gazeCounter / gazeFrames; // Current pos.
    if(gazeCounter == 0) {                   // Last frame?
      eyePos      = newEyePos;               // Current position = new pos
      newEyePos   = random(16) * 16;         // New pos. (always pixel center)
      eyeMotion   = newEyePos - eyePos;      // Distance to move
      gazeFrames  = random(10, 20);          // Duration of movement
      gazeCounter = random(gazeFrames, 130); // Count to END of next movement
    }
  } else ep = eyePos; // Not moving -- fixed gaze

  // Draw pupil -- 2 pixels wide, but sup-pixel positioning may span 3.
  a = ep >> 4;         // First candidate
  b = (a + 1)  & 0x0F; // 1 pixel CCW of a
  c = (a + 2)  & 0x0F; // 2 pixels CCW of a
  i = ep & 0x0F;       // Fraction of 'c' covered (0-15)
  iBrightness[a] = (iBrightness[a] *       i ) >> 4;
  iBrightness[b] = 0;
  iBrightness[c] = (iBrightness[c] * (16 - i)) >> 4;

  // Merge iColor with iBrightness, issue to NeoPixels
  for(i=0; i<16; i++) {
    a = iBrightness[i] + 1;
    // First eye
    r = iColor[i][0];            // Initial background RGB color
    g = iColor[i][1];
    b = iColor[i][2];
    if(a) {
      r = (r * a) >> 8;          // Scale by brightness map
      g = (g * a) >> 8;
      b = (b * a) >> 8;
    }
    pixels.setPixelColor(((i + TOP_LED_FIRST) & 15),
      pgm_read_byte(&gamma8[r]), // Gamma correct and set pixel
      pgm_read_byte(&gamma8[g]),
      pgm_read_byte(&gamma8[b]));

    // Second eye uses the same colors, but reflected horizontally.
    // The same brightness map is used, but not reflected (same left/right)
    r = iColor[15 - i][0];
    g = iColor[15 - i][1];
    b = iColor[15 - i][2];
    if(a) {
      r = (r * a) >> 8;
      g = (g * a) >> 8;
      b = (b * a) >> 8;
    }
    pixels.setPixelColor(16 + ((i + TOP_LED_SECOND) & 15),
      pgm_read_byte(&gamma8[r]),
      pgm_read_byte(&gamma8[g]),
      pgm_read_byte(&gamma8[b]));
  }
  pixels.show();

  delay(15);
}

//The interrups need to be set different for different AVR Microcontrollers, see code bellow

/*

 The register settings above tell Timer2 to go into CTC mode, and to count up to 124 (0x7C) over and over and over again.
 A prescaler of 256 is used to get the timing right so that it takes 2 milliseconds to count to 124.
 An interrupt flag is set every time Timer2 reaches 124, and a special function called an Interrupt Service Routine (ISR) that we wrote is run at the very next possible moment, no matter what the rest of the program is doing. sei() ensures that global interrupts are enabled.
 Timing is important! If you are using a different Arduino or Arduino compatible device, you will need to change this function. (please see different interrupts bellow)

 This code works with Arduino UNO or Arduino PRO or Arduino Pro Mini 5V or any Arduino running with an ATmega328 and 16MHz clock.

 void interruptSetup(){
 TCCR2A = 0x02;
 TCCR2B = 0x06;
 OCR2A = 0x7C;
 TIMSK2 = 0x02;
 sei();
 }

 If you are using a FIO or LillyPad Arduino or Arduino Pro Mini 3V or Arduino SimpleSnap or other Arduino that has ATmega168 or ATmega328 with 8MHz oscillator, change the line TCCR2B = 0x06 to TCCR2B = 0x05.

 If you are using Arduino Leonardo or Adafruit's Flora  or Arduino Micro or other Arduino that has ATmega32u4 running at 16MHz

 void interruptSetup(){
 TCCR0A = 0x02;
 TCCR0B = 0x04;
 OCR0A = 0x7C;
 TIMSK0 = 0x02;
 sei();
 }

 The LilyPad Arduino USB runs at 8MHz, likely some other ATmega32u4 based devices out there, so to correct the timing, change TCCR0B = 0x04;  to TCCR0B = 0x03; Then change OCR0A = 0x7C; to OCR0A = 0xF9;

 The only other thing you will need is the correct ISR vector in the next step. ATmega32u4 devices use ISR(TIMER0_COMPA_vect)

 */

volatile int rate[10];                    // array to hold last ten IBI values
volatile unsigned long sampleCounter = 0;          // used to determine pulse timing
volatile unsigned long lastBeatTime = 0;           // used to find IBI
volatile int P =512;                      // used to find peak in pulse wave, seeded
volatile int T = 512;                     // used to find trough in pulse wave, seeded
volatile int thresh = 512;                // used to find instant moment of heart beat, seeded
volatile int amp = 100;                   // used to hold amplitude of pulse waveform, seeded
volatile boolean firstBeat = true;        // used to seed rate array so we startup with reasonable BPM
volatile boolean secondBeat = false;      // used to seed rate array so we startup with reasonable BPM


void interruptSetup(){  
  // Initializes Timer2 to throw an interrupt every 2mS.
  TCCR2A = 0x02;     // DISABLE PWM ON DIGITAL PINS 3 AND 11, AND GO INTO CTC MODE
  TCCR2B = 0x05;     // DON'T FORCE COMPARE, 256 PRESCALER
  OCR2A = 0X7C;      // SET THE TOP OF THE COUNT TO 124 FOR 500Hz SAMPLE RATE
  TIMSK2 = 0x02;     // ENABLE INTERRUPT ON MATCH BETWEEN TIMER2 AND OCR2A
  sei();             // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED    
}


// THIS IS THE TIMER 2 INTERRUPT SERVICE ROUTINE.
// Timer 2 makes sure that we take a reading every 2 miliseconds
ISR(TIMER2_COMPA_vect){                         // triggered when Timer2 counts to 124
  cli();                                      // disable interrupts while we do this
  Signal = analogRead(pulsePin);              // read the Pulse Sensor
  sampleCounter += 2;                         // keep track of the time in mS with this variable
  int N = sampleCounter - lastBeatTime;       // monitor the time since the last beat to avoid noise

    //  find the peak and trough of the pulse wave
  if(Signal < thresh && N > (IBI/5)*3){       // avoid dichrotic noise by waiting 3/5 of last IBI
    if (Signal < T){                        // T is the trough
      T = Signal;                         // keep track of lowest point in pulse wave
    }
  }

  if(Signal > thresh && Signal > P){          // thresh condition helps avoid noise
    P = Signal;                             // P is the peak
  }                                        // keep track of highest point in pulse wave

  //  NOW IT'S TIME TO LOOK FOR THE HEART BEAT
  // signal surges up in value every time there is a pulse

  if (N > 250){                                   // avoid high frequency noise
    if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ){
     
      Pulse = true;                               // set the Pulse flag when we think there is a pulse
      digitalWrite(blinkPin,HIGH);                // turn on pin 13 LED
      IBI = sampleCounter - lastBeatTime;         // measure time between beats in mS
      lastBeatTime = sampleCounter;               // keep track of time for next pulse

      if(secondBeat){                        // if this is the second beat, if secondBeat == TRUE
        secondBeat = false;                  // clear secondBeat flag
        for(int i=0; i<=9; i++){             // seed the running total to get a realisitic BPM at startup
          rate[i] = IBI;                    
        }
      }

      if(firstBeat){                         // if it's the first time we found a beat, if firstBeat == TRUE
        firstBeat = false;                   // clear firstBeat flag
        secondBeat = true;                   // set the second beat flag
        sei();                               // enable interrupts again
        return;                              // IBI value is unreliable so discard it
      }


      // keep a running total of the last 10 IBI values
      word runningTotal = 0;                  // clear the runningTotal variable  

      for(int i=0; i<=8; i++){                // shift data in the rate array
        rate[i] = rate[i+1];                  // and drop the oldest IBI value
        runningTotal += rate[i];              // add up the 9 oldest IBI values
      }

      rate[9] = IBI;                          // add the latest IBI to the rate array
      runningTotal += rate[9];                // add the latest IBI to runningTotal
      runningTotal /= 10;                     // average the last 10 IBI values
      BPM = 60000/runningTotal;               // how many beats can fit into a minute? that's BPM!
      QS = true;                              // set Quantified Self flag
      // QS FLAG IS NOT CLEARED INSIDE THIS ISR
    }                    
  }

  if (Signal < thresh && Pulse == true){   // when the values are going down, the beat is over
    digitalWrite(blinkPin,LOW);            // turn off pin 13 LED
    Pulse = false;                         // reset the Pulse flag so we can do it again
    amp = P - T;                           // get amplitude of the pulse wave
    thresh = amp/2 + T;                    // set thresh at 50% of the amplitude
    P = thresh;                            // reset these for next time
    T = thresh;
  }

  if (N > 2500){                           // if 2.5 seconds go by without a beat
    thresh = 512;                          // set thresh default
    P = 512;                               // set P default
    T = 512;                               // set T default
    lastBeatTime = sampleCounter;          // bring the lastBeatTime up to date      
    firstBeat = true;                      // set these to avoid noise
    secondBeat = false;                    // when we get the heartbeat back
  }

  sei();                                   // enable interrupts when you're done!
}// end isr


This project will be continued over the summer, however, due to time and material restraints, I am not able to work on it further at this time. Please stand by for a full tutorial sometime this summer.

Friday, March 14, 2014

Turnigy 9X Based FPV Base Station

For the drone I needed a base station. I need it not only for FPV, but I also need to be able to use the APM Mission planner to assign and monitor the status of the drone. This base station here I have made on top of the 9X 6ch RC Transmitter allows me to do just that, as the monitor can take 2 sources and switch between them. This means that I could add a Raspberry Pi or preferably a more powerful ARM device. (An extremely low power X86 board would be amazing, but I'm not betting on it.)

The FPV station is a relatively simple concept. You take a monitor  and then add some video equipment of your preference (900mhz in my case), some batteries and you're almost good. More to be added...






Tuesday, February 11, 2014

Sensor Package Code V0.01 Pre-Alpha

/*
  Implemented as of now: 4 Gas sensors being read by analog values (They are not being calibrated, values are pure analog).
 GPS should be read and written to SD card, pins most probably need changing due to mega problem, check documentation.
 SD card string writing needs changes in terms of formatting. DRASTICALLY

 To be implemented: Barometer, Pressure, Xbee, Dust Sensor, Etc.

 NOTE: This code runs serial at the speed of 115200, so set the reading to that

 NOTE2: THE CURRENT CODE DOES NO WRITE THE OPTICAL DUST SENSOR TO THE SD CARD AND POSSIBLY NOT TO SERIAL EITHER! Xbee still not implemented

 Sharp pin 1 (V-LED)   => 5V (connected to 150ohm resister)
 Sharp pin 2 (LED-GND) => Arduino GND pin
 Sharp pin 3 (LED)     => Arduino pin 2
 Sharp pin 4 (S-GND)   => Arduino GND pin
 Sharp pin 5 (Vo)      => Arduino A0 pin
 Sharp pin 6 (Vcc)     => 5V

 Connections for the HTU21D Humidity Sensor

 -VCC = 3.3V
 -GND = GND
 -SDA = A4 (use inline 10k resistor if your board is 5V)
 -SCL = A5 (use inline 10k resistor if your board is 5V)

 Errors 998 if not sensor is detected. Error 999 if CRC is bad. (HTU21D Humidity Library)

 */
#include <SPI.h> //Include SD reader and writer libraries
#include <SD.h>

//The following are for the HTU21D humidity sensor

#include "HTU21D.h" //The following is the librar for the HTU21D.h humidty sensor
HTU21D myHumidity;

//End of HTU21D humidity sensor

//THE FOLLOWING LIBRARIES ARE USED FOR THE TMP102 Digital Temperature Sensor
#define TMP102_I2C_ADDRESS 72

/* This is the I2C address for our chip.
This value is correct if you tie the ADD0 pin to ground. See the datasheet for some other values. */
//END OF TMP102

#include <SPI.h>> //THESE 2 LIBRARIES ARE FROM THE SHARP SENSOR SOURCECODE http://www.howmuchsnow.com/arduino/airquality/dust.ino
#include <stdlib.h> //WHAT LIBRARY IS THIS?

#include <Adafruit_GPS.h> //Include the Adafruti GPS library
#include <SoftwareSerial.h> // Include the SoftWare serial library (softSerial)

#include <avr/sleep.h> //May be taken out later depending on how the project advances

//The avr/sleep.h function could be used possibly, according to the shield_sdlog example to
//allow the arduino to read the GPS during an interrupt and thus allows the program more 'fredom'. It parses the new NMEA sentence when available and allows to acces the data when desired

SoftwareSerial mySerial(8, 7); //These pins need to be changed for the MEGA, not to mention you need to rewire the pins if using the Ultimate GPS shield. It works with UNO and Duemilanove
Adafruit_GPS GPS(&mySerial); //Uses the Adafruit GPS library to do most of the 'heavy lifting' of the GPS data.

// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences
#define GPSECHO  false

// set to true to only log to SD when GPS has a fix, for debugging, keep it false
#define LOG_FIXONLY true

#define ledPin 13 //Will maybe take this pin out later

//THE FOLLOWING SECTION IS FOR THE MLP3115A2 ALTITUDE/PRESSURE SENSOR ONLY

#include <Wire.h> // for IIC communication

#define STATUS     0x00
#define OUT_P_MSB  0x01
#define OUT_P_CSB  0x02
#define OUT_P_LSB  0x03
#define OUT_T_MSB  0x04
#define OUT_T_LSB  0x05
#define DR_STATUS  0x06
#define OUT_P_DELTA_MSB  0x07
#define OUT_P_DELTA_CSB  0x08
#define OUT_P_DELTA_LSB  0x09
#define OUT_T_DELTA_MSB  0x0A
#define OUT_T_DELTA_LSB  0x0B
#define WHO_AM_I   0x0C
#define F_STATUS   0x0D
#define F_DATA     0x0E
#define F_SETUP    0x0F
#define TIME_DLY   0x10
#define SYSMOD     0x11
#define INT_SOURCE 0x12
#define PT_DATA_CFG 0x13
#define BAR_IN_MSB 0x14
#define BAR_IN_LSB 0x15
#define P_TGT_MSB  0x16
#define P_TGT_LSB  0x17
#define T_TGT      0x18
#define P_WND_MSB  0x19
#define P_WND_LSB  0x1A
#define T_WND      0x1B
#define P_MIN_MSB  0x1C
#define P_MIN_CSB  0x1D
#define P_MIN_LSB  0x1E
#define T_MIN_MSB  0x1F
#define T_MIN_LSB  0x20
#define P_MAX_MSB  0x21
#define P_MAX_CSB  0x22
#define P_MAX_LSB  0x23
#define T_MAX_MSB  0x24
#define T_MAX_LSB  0x25
#define CTRL_REG1  0x26
#define CTRL_REG2  0x27
#define CTRL_REG3  0x28
#define CTRL_REG4  0x29
#define CTRL_REG5  0x2A
#define OFF_P      0x2B
#define OFF_T      0x2C
#define OFF_H      0x2D

#define MPL3115A2_ADDRESS 0x60 // 7-bit I2C address

long startTime;

//END OF MLP3115A2 Altitude/Pressure Sensor


// These constants won't change.  They're used to give names
// to the pins used:
const int analogInPin1 = A0;  // Analog input pin that the potentiometer is attached to
const int analogInPin2 = A1;
const int analogInPin3 = A2;
const int analogInPin4 = A3;

const int chipSelect = 53; //This is the SD Pin, may need changing, on Mega its normally pin 53, must be left as output

int SensorValue1 = 0;        // value read from the sensor
int SensorValue2 = 1;
int SensorValue3 = 2;
int SensorValue4 = 3;

int sensor1 = analogRead(analogInPin1); //This line needs to be edited as the analog sensors values and readings need to go in here, not at the bottom
int sensor2 = analogRead(analogInPin2);
int sensor3 = analogRead(analogInPin3);
int sensor4 = analogRead(analogInPin4);

//Begin of Dust Sensor Variables===============

int dustPin=0;
int ledPower=2;
int delayTime=280;
int delayTime2=40;
float offTime=9680;

int dustVal=0;
int i=0;
float ppm=0;
char s[32];
float voltage = 0;
float dustdensity = 0;
float ppmpercf = 0;

//End of dust sensor variables=================

File dataFile; //Creates the file from the library and gives it name, changed later in code

// read a Hex value and return the decimal equivalent
uint8_t parseHex(char c) {
  if (c < '0')
    return 0;
  if (c <= '9')
    return c - '0';
  if (c < 'A')
    return 0;
  if (c <= 'F')
    return (c - 'A')+10;
}

// blink out an error code
void error(uint8_t errno) {
  /*
  if (SD.errorCode()) {
   putstring("SD error: ");
   Serial.print(card.errorCode(), HEX);
   Serial.print(',');
   Serial.println(card.errorData(), HEX);
   }
   */
  while(1) {
    uint8_t i;
    for (i=0; i<errno; i++) {
      digitalWrite(ledPin, HIGH);
      delay(100);
      digitalWrite(ledPin, LOW);
      delay(100);
    }
    for (i=errno; i<10; i++) {
      delay(200);
    }
  }
}

//=====================================STAR OF VOID SETUP=====================================================================

void setup() {
 
  //The following is for the HTU21D Humidity sensor
 
    Serial.println("HTU21D Initiated!!");
  myHumidity.begin();
 
  //End of HTU21D Humididty Sensor

  //The following is for TMP102 temperature sensor
 
 
  //The following is for TMP102 temperature sensor
 
 
 //The Following is for the MPL3115A2 Altitude/Pressure Sensor

  Wire.begin();        // join i2c bus
 // Serial.begin(57600);  // start serial for output THIS HAS TO BE SEEN OVER! MIGHT AFFECT CODE SPEED, Its the speed used in the example only

 if(IIC_Read(WHO_AM_I) == 196)
    Serial.println("MPL3115A2 online!");
  else
    Serial.println("No response - check connections");


  // Configure the sensor
  setModeAltimeter(); // Measure altitude above sea level in meters
  //setModeBarometer(); // Measure pressure in Pascals from 20 to 110 kPa

  setOversampleRate(7); // Set Oversample to the recommended 128
  enableEventFlags(); // Enable all three pressure and temp event flags

//This concludes the MPL3115A2 Altitude/Pressure Sensor Section
 
  // initialize serial communications at 9600 bps:
  Serial.begin(115200); //It needs to be so fast in order to read GPS without having to miss values

  pinMode(ledPower,OUTPUT); //The LED for Optical Dust sensor activity
 
    i=0; //Sets Sharp optical dust sensor values to 0
  ppm =0;//Sets Sharp optical dust sensor values to 0

  Serial.println("\r\nUltimate GPSlogger Shield");
  pinMode(ledPin, OUTPUT);

  Serial.print("Initializing SD card...");
  pinMode(SS, OUTPUT); //Make sure that the default chip select pin is set to output, even if you don't use it, it looks weird as SS...

  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);

  //WHATS UP WITH THE TWO LINES ABOVE?!?!?!?! I MIGHT NOT NEED THEM. WHY PIN SS AND PIN 10?!?!!?

  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect, 11, 12, 13)) {
    Serial.println("Card initialization failed, or not present");
    error(2);
    // don't do anything more:
    while (1) ;
  }
  Serial.println("card initialized.");

  char filename[15];
  strcpy(filename, "GPSLOG00.TXT");
  for (uint8_t i = 0; i < 100; i++) {
    filename[6] = '0' + i/10;
    filename[7] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! SD.exists(filename)) {
      break;
    }
  }

  // Open up the file we're going to log to!
  dataFile = SD.open("DatalLog.txt", FILE_WRITE);
  if (! dataFile) {
    Serial.println("error opening datalog.txt");
    // Wait forever since we can't write data
    while (1) ;
  }

  // connect to the GPS at the desired rate
  GPS.begin(9600);

  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  // uncomment this line to turn on only the "minimum recommended" data
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // For logging data, we don't suggest using anything but either RMC only or RMC+GGA
  // to keep the log files at a reasonable size
  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);   // 1 or 5 Hz update rate

  // Turn off updates on antenna status, if the firmware permits it
  GPS.sendCommand(PGCMD_NOANTENNA);

  Serial.println("Ready!");
}

//=====================================STAR OF VOID LOOP=====================================================================

void loop() {

  //This is for the HTU21D Humidty Sensor
 
  float humd = myHumidity.readHumidity();
  float temp = myHumidity.readTemperature();
 
   Serial.print("Time:");
  Serial.print(millis());
  Serial.print(" Temperature:");
  Serial.print(temp, 1);
  Serial.print("C");
  Serial.print(" Humidity:");
  Serial.print(humd, 1);
  Serial.print("%");

  Serial.println();
  delay(1000);
 
  //This si the end for the HTU21D Humidity Sensor
 

 
  //This is for the TMP102 Tempreature Sensor
 
  getTemp102();
  delay(5000); //wait 5 seconds before printing our next set of readings.

 //End of TMP102 Temperature sensor
 
  //This section is dedicated to the Sharp optical dust sensor
 
    i=i+1;
  digitalWrite(ledPower,LOW); // power on the LED
  delayMicroseconds(delayTime);
  dustVal=analogRead(dustPin); // read the dust value
  ppm = ppm+dustVal;
  delayMicroseconds(delayTime2);
  digitalWrite(ledPower,HIGH); // turn the LED off
  delayMicroseconds(offTime);

  // if you're not connected, and ten seconds have passed since
  // your last connection, then connect again and send data:
    voltage = ppm/i*0.0049;
    dustdensity = 0.17*voltage-0.1;
    ppmpercf = (voltage-0.0256)*120000;
    if (ppmpercf < 0)
      ppmpercf = 0;
    if (dustdensity < 0 )
      dustdensity = 0;
    if (dustdensity > 0.5)
      dustdensity = 0.5;
    String dataString = ""; //CREATES A STRING FOR THE OPTICAL DUST SENSOR
    dataString += dtostrf(voltage, 9, 4, s);
    dataString += ",";
    dataString += dtostrf(dustdensity, 5, 2, s);
    dataString += ",";
    dataString += dtostrf(ppmpercf, 8, 0, s);
    i=0;
    ppm=0;
    Serial.println(dataString);
    delay(1000);
 
  //End of sharp optical dust sensor section
 
  char c = GPS.read();
  if (GPSECHO)
    if (c)   Serial.print(c);

  // if a sentence is received, we can check the checksum, parse it...
  if (GPS.newNMEAreceived()) {
    // a tricky thing here is if we print the NMEA sentence, or data
    // we end up not listening and catching other sentences!
    // so be very wary if using OUTPUT_ALLDATA and trying to print out data
    //Serial.println(GPS.lastNMEA());   // this also sets the newNMEAreceived() flag to false

    if (!GPS.parse(GPS.lastNMEA()))   // this also sets the newNMEAreceived() flag to false
      return;  // we can fail to parse a sentence in which case we should just wait for another

    // Sentence parsed!
    Serial.println("OK");
    if (LOG_FIXONLY && !GPS.fix) {
      Serial.print("No Fix");
      return;
    }

    // Rad. lets log it!
    Serial.println("Log");

    char *stringptr = GPS.lastNMEA();
    uint8_t stringsize = strlen(stringptr);
    if (stringsize != dataFile.write((uint8_t *)stringptr, stringsize))    //write the string to the SD file
        error(4);
    if (strstr(stringptr, "RMC"))   dataFile.flush();
    Serial.println();

    // make a string for assembling the data to log:
    String dataString = "";

    dataFile.println(dataString);

    // print to the serial port too:
    Serial.println(dataString);

    // read the analog in value:
    SensorValue1 = analogRead(analogInPin1);  
    dataString += String(sensor1);
    delay(10); //Wiat 10 milliseconds before reading next value so the analog-to-digital converter can settle after reading

    SensorValue2 = analogRead(analogInPin2);
    dataString += String(sensor2);
    delay(10); //Wiat 10 milliseconds before reading next value so the analog-to-digital converter can settle after reading

    SensorValue3 = analogRead(analogInPin3);
    dataString += String(sensor3);
    delay(10); //Wiat 10 milliseconds before reading next value so the analog-to-digital converter can settle after reading

    SensorValue4 = analogRead(analogInPin4);
    dataString += String(sensor4);
    delay(10); //Wiat 10 milliseconds before reading next value so the analog-to-digital converter can settle after reading

    // print the results to the serial monitor:
    Serial.print("sensor1 = " );                      
    Serial.println(SensorValue1);          

    Serial.print("sensor2 = " );                      
    Serial.println(SensorValue2);

    Serial.print("sensor3 = " );                      
    Serial.println(SensorValue3);

    Serial.print("sensor4 = " );                      
    Serial.println(SensorValue4);    


    // The following line will 'save' the file to the SD card after every
    // line of data - this will use more power and slow down how much data
    // you can read but it's safer!
    // If you want to speed up the system, remove the call to flush() and it
    // will save the file only every 512 bytes - every time a sector on the
    // SD card is filled with data.
    dataFile.flush();

    // Take 1 measurement every 500 milliseconds
    delay(500);
  }
 
  //THIS IS THE PART OF THE CODE FOR THE MPL3115A2 Pressure sensor
 
    startTime = millis();

  float altitude = readAltitude();
  Serial.print("Altitude(m):");
  Serial.print(altitude, 2);

  //altitude = readAltitudeFt();
  //Serial.print(" Altitude(ft):");
  //Serial.print(altitude, 2);

  /*float pressure = readPressure();
   Serial.print(" Pressure(Pa):");
   Serial.println(pressure, 2);*/

  //float temperature = readTemp();
  //Serial.print(" Temp(c):");
  //Serial.print(temperature, 2);

  //float temperature = readTempF();
  //Serial.print(" Temp(f):");
  //Serial.print(temperature, 2);
 
  Serial.print(" time diff:");
  Serial.print(millis() - startTime);

  Serial.println();

  //delay(1);
}

//Returns the number of meters above sea level
float readAltitude()
{
  toggleOneShot(); //Toggle the OST bit causing the sensor to immediately take another reading

  //Wait for PDR bit, indicates we have new pressure data
  int counter = 0;
  while( (IIC_Read(STATUS) & (1<<1)) == 0)
  {
      if(++counter > 100) return(-999); //Error out
      delay(1);
  }
 
  // Read pressure registers
  Wire.beginTransmission(MPL3115A2_ADDRESS);
  Wire.write(OUT_P_MSB);  // Address of data to get
  Wire.endTransmission(false); // Send data to I2C dev with option for a repeated start. THIS IS NECESSARY and not supported before Arduino V1.0.1!
  Wire.requestFrom(MPL3115A2_ADDRESS, 3); // Request three bytes

  //Wait for data to become available
  counter = 0;
  while(Wire.available() < 3)
  {
    if(counter++ > 100) return(-999); //Error out
    delay(1);
  }

  byte msb, csb, lsb;
  msb = Wire.read();
  csb = Wire.read();
  lsb = Wire.read();

  toggleOneShot(); //Toggle the OST bit causing the sensor to immediately take another reading

  // The least significant bytes l_altitude and l_temp are 4-bit,
  // fractional values, so you must cast the calulation in (float),
  // shift the value over 4 spots to the right and divide by 16 (since
  // there are 16 values in 4-bits).
  float tempcsb = (lsb>>4)/16.0;

  float altitude = (float)( (msb << 8) | csb) + tempcsb;

  return(altitude);
}

//Returns the number of feet above sea level
float readAltitudeFt()
{
  return(readAltitude() * 3.28084);
}

//Reads the current pressure in Pa
//Unit must be set in barometric pressure mode
float readPressure()
{
  toggleOneShot(); //Toggle the OST bit causing the sensor to immediately take another reading

  //Wait for PDR bit, indicates we have new pressure data
  int counter = 0;
  while( (IIC_Read(STATUS) & (1<<2)) == 0)
  {
      if(++counter > 100) return(-999); //Error out
      delay(1);
  }

  // Read pressure registers
  Wire.beginTransmission(MPL3115A2_ADDRESS);
  Wire.write(OUT_P_MSB);  // Address of data to get
  Wire.endTransmission(false); // Send data to I2C dev with option for a repeated start. THIS IS NECESSARY and not supported before Arduino V1.0.1!
  Wire.requestFrom(MPL3115A2_ADDRESS, 3); // Request three bytes

  //Wait for data to become available
  counter = 0;
  while(Wire.available() < 3)
  {
    if(counter++ > 100) return(-999); //Error out
    delay(1);
  }

  byte msb, csb, lsb;
  msb = Wire.read();
  csb = Wire.read();
  lsb = Wire.read();

  toggleOneShot(); //Toggle the OST bit causing the sensor to immediately take another reading

  // Pressure comes back as a left shifted 20 bit number
  long pressure_whole = (long)msb<<16 | (long)csb<<8 | (long)lsb;
  pressure_whole >>= 6; //Pressure is an 18 bit number with 2 bits of decimal. Get rid of decimal portion.

  lsb &= 0b00110000; //Bits 5/4 represent the fractional component
  lsb >>= 4; //Get it right aligned
  float pressure_decimal = (float)lsb/4.0; //Turn it into fraction

  float pressure = (float)pressure_whole + pressure_decimal;

  return(pressure);
}

float readTemp()
{
  toggleOneShot(); //Toggle the OST bit causing the sensor to immediately take another reading

  //Wait for TDR bit, indicates we have new temp data
  int counter = 0;
  while( (IIC_Read(STATUS) & (1<<1)) == 0)
  {
      if(++counter > 100) return(-999); //Error out
      delay(1);
  }
 
  // Read temperature registers
  Wire.beginTransmission(MPL3115A2_ADDRESS);
  Wire.write(OUT_T_MSB);  // Address of data to get
  Wire.endTransmission(false); // Send data to I2C dev with option for a repeated start. THIS IS NECESSARY and not supported before Arduino V1.0.1!
  Wire.requestFrom(MPL3115A2_ADDRESS, 2); // Request two bytes

  //Wait for data to become available
  counter = 0;
  while(Wire.available() < 2)
  {
    if(++counter > 100) return(-999); //Error out
    delay(1);
  }

  byte msb, lsb;
  msb = Wire.read();
  lsb = Wire.read();
 
  // The least significant bytes l_altitude and l_temp are 4-bit,
  // fractional values, so you must cast the calulation in (float),
  // shift the value over 4 spots to the right and divide by 16 (since
  // there are 16 values in 4-bits).
  float templsb = (lsb>>4)/16.0; //temp, fraction of a degree

  float temperature = (float)(msb + templsb);

  return(temperature);
}

//Give me temperature in fahrenheit!
float readTempF()
{
  return((readTemp() * 9.0)/ 5.0 + 32.0); // Convert celsius to fahrenheit
}

//Sets the mode to Barometer
//CTRL_REG1, ALT bit
void setModeBarometer()
{
  byte tempSetting = IIC_Read(CTRL_REG1); //Read current settings
  tempSetting &= ~(1<<7); //Clear ALT bit
  IIC_Write(CTRL_REG1, tempSetting);
}

//Sets the mode to Altimeter
//CTRL_REG1, ALT bit
void setModeAltimeter()
{
  byte tempSetting = IIC_Read(CTRL_REG1); //Read current settings
  tempSetting |= (1<<7); //Set ALT bit
  IIC_Write(CTRL_REG1, tempSetting);
}

//Puts the sensor in standby mode
//This is needed so that we can modify the major control registers
void setModeStandby()
{
  byte tempSetting = IIC_Read(CTRL_REG1); //Read current settings
  tempSetting &= ~(1<<0); //Clear SBYB bit for Standby mode
  IIC_Write(CTRL_REG1, tempSetting);
}

//Puts the sensor in active mode
//This is needed so that we can modify the major control registers
void setModeActive()
{
  byte tempSetting = IIC_Read(CTRL_REG1); //Read current settings
  tempSetting |= (1<<0); //Set SBYB bit for Active mode
  IIC_Write(CTRL_REG1, tempSetting);
}

//Setup FIFO mode to one of three modes. See page 26, table 31
//From user jr4284
void setFIFOMode(byte f_Mode)
{
  if (f_Mode > 3) f_Mode = 3; // FIFO value cannot exceed 3.
  f_Mode <<= 6; // Shift FIFO byte left 6 to put it in bits 6, 7.

  byte tempSetting = IIC_Read(F_SETUP); //Read current settings
  tempSetting &= ~(3<<6); // clear bits 6, 7
  tempSetting |= f_Mode; //Mask in new FIFO bits
  IIC_Write(F_SETUP, tempSetting);
}

//Call with a rate from 0 to 7. See page 33 for table of ratios.
//Sets the over sample rate. Datasheet calls for 128 but you can set it
//from 1 to 128 samples. The higher the oversample rate the greater
//the time between data samples.
void setOversampleRate(byte sampleRate)
{
  if(sampleRate > 7) sampleRate = 7; //OS cannot be larger than 0b.0111
  sampleRate <<= 3; //Align it for the CTRL_REG1 register

  byte tempSetting = IIC_Read(CTRL_REG1); //Read current settings
  tempSetting &= 0b11000111; //Clear out old OS bits
  tempSetting |= sampleRate; //Mask in new OS bits
  IIC_Write(CTRL_REG1, tempSetting);
}

//Clears then sets the OST bit which causes the sensor to immediately take another reading
//Needed to sample faster than 1Hz
void toggleOneShot(void)
{
  byte tempSetting = IIC_Read(CTRL_REG1); //Read current settings
  tempSetting &= ~(1<<1); //Clear OST bit
  IIC_Write(CTRL_REG1, tempSetting);

  tempSetting = IIC_Read(CTRL_REG1); //Read current settings to be safe
  tempSetting |= (1<<1); //Set OST bit
  IIC_Write(CTRL_REG1, tempSetting);
}

//Enables the pressure and temp measurement event flags so that we can
//test against them. This is recommended in datasheet during setup.
void enableEventFlags()
{
  IIC_Write(PT_DATA_CFG, 0x07); // Enable all three pressure and temp event flags
}

// These are the two I2C functions in this sketch.
byte IIC_Read(byte regAddr)
{
  // This function reads one byte over IIC
  Wire.beginTransmission(MPL3115A2_ADDRESS);
  Wire.write(regAddr);  // Address of CTRL_REG1
  Wire.endTransmission(false); // Send data to I2C dev with option for a repeated start. THIS IS NECESSARY and not supported before Arduino V1.0.1!
  Wire.requestFrom(MPL3115A2_ADDRESS, 1); // Request the data...
  return Wire.read();
}

void IIC_Write(byte regAddr, byte value)
{
  // This function writes one byto over IIC
  Wire.beginTransmission(MPL3115A2_ADDRESS);
  Wire.write(regAddr);
  Wire.write(value);
  Wire.endTransmission(true);
}

//This is for the TMP102 Digital Thermometer

void getTemp102(){
  byte firstbyte, secondbyte; //these are the bytes we read from the TMP102 temperature registers
  int val; /* an int is capable of storing two bytes, this is where we "chuck" the two bytes together. */
  float convertedtemp; /* We then need to multiply our two bytes by a scaling factor, mentioned in the datasheet. */
  float correctedtemp;
  // The sensor overreads (?)


  /* Reset the register pointer (by default it is ready to read temperatures)
You can alter it to a writeable register and alter some of the configuration -
the sensor is capable of alerting you if the temperature is above or below a specified threshold. */

  Wire.beginTransmission(TMP102_I2C_ADDRESS); //Say hi to the sensor.
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(TMP102_I2C_ADDRESS, 2);
  Wire.endTransmission();


  firstbyte      = (Wire.read());
/*read the TMP102 datasheet - here we read one byte from
 each of the temperature registers on the TMP102*/
  secondbyte     = (Wire.read());
/*The first byte contains the most significant bits, and
 the second the less significant */
    val = ((firstbyte) << 4);
 /* MSB */
    val |= (secondbyte >> 4);  
/* LSB is ORed into the second 4 bits of our byte.
Bitwise maths is a bit funky, but there's a good tutorial on the playground*/
    convertedtemp = val*0.0625;
    correctedtemp = convertedtemp - 5;
    /* See the above note on overreading */


  Serial.print("firstbyte is ");
  Serial.print("\t");
  Serial.println(firstbyte, BIN);
  Serial.print("secondbyte is ");
  Serial.print("\t");
  Serial.println(secondbyte, BIN);
  Serial.print("Concatenated byte is ");
  Serial.print("\t");
  Serial.println(val, BIN);
  Serial.print("Converted temp is ");
  Serial.print("\t");
  Serial.println(val*0.0625);
  Serial.print("Corrected temp is ");
  Serial.print("\t");
  Serial.println(correctedtemp);
  Serial.println();
}

/* End code */

Wednesday, January 8, 2014

The Quest for Lift

There are many kinds of propellers available for multi rotor applications, and while I started with the basic APC props shown bellow, they are not enough for the speed and weight of the hexacopter.


Thus I not only needed to go for larger propellers, but also for stronger ones. Carbon fiber propellers were almost the obvious choice.


The third and forth option is Graupner props, these are professional propellers, and since they are only made in Germany, they are very expensive. 



So there are still plenty of options. Wood was not used because its the heaviest of the three. 




Tuesday, January 7, 2014

Flashing Custom Firmware: OH THE HORROR!

   Well I knew this would have to be done sooner or later but what I had to realize is that pretty much every micro-controller needs to run on custom firmware. The 6 Atmega 8P based ESCs run on SimonK firmware, the ATMega2560 on the APM 2.5 runs on custom hexacopter firmware as well as the camera boards I am planning to add. This means a lot of manual flashing for me, so here is some details on how that was done.

Come to think about it, the haxacopter is like an Octopus in that it has multiple brains, one central main brain and several smaller brains in the arms.  So yeah, a cute little analogy here.

This is the main section of 1 of the ESCs, yu can see the voltage regulators as well as the Atmega 8P micro controller, a crystal oscillator, etc. Its really a small, full featured micro controller.


This is the back of the ESC, it reveals a large discharge capacitor as well as an array of 12  high current MOSFETs for a total peak continuous AC driving power of 30Amps with a peak power of 40Amps for 10 seconds on average cooling

To note here is that what the ESCs do is primarily convert DC voltage from the battery into AC voltage for the brushless motor. Secondly, they regulate a steady 5V @ 2A power supply for the APM and the rest of the system, assuring operation of the device. Since they are used as drivers for the motors, they will obviously also get throttle input from the APM. 

Normal ESCs will take the average PWM control single from the Rx and average the values to get a good overall speed for something like a plane, on a multi rotor on the other hand you need to have rapid and accurate throttle response. The SimonK and similar ESC firmwares bypass some of the features on the ESCs such as voltage protections and other unnecessary add-ons, favoring throttle response and thus allowing Multirotors to "Lock" in a direction, thus removing the whole that comes from the flight controller trying to stabilize the multi rotor.

First of all all flashing require a USB ASP or similar device .

The first method of flashing is smothering 6 wires to incredibly small and fragile pads on the controller, just for reference, the picture above is using 16 gauge if not 24 servo wire.

This method is slow and annoying. I've decided to not go for it.



The good folks over at Hobbyking have come up with this idea. Instead of having to solder to the actual board, you can simply touch the pins of the ATmega 8 and program it that way. Its fast and works wonders. 'Nuff said. Its about 20$ for the tool but it will save you a lot of defective ESCs, talking first hand here :)


On the software side I used the KK Flash tool, it works great and its kept up to date, I have registered some errors but overall its fine. 

A USB ASP device

I have made a tutorial on flashing using the KK Multicopter Flash Tool on actual KK 2.0 boards, although the software is compatible with many more boards and ESCs. 




Problem with the Xbee Adapters


The APM takes data over a protocol called Mavlink and Xbees are one of the supported (and cheaper) communication methods. Mavlink was designed to be a very fast data protocol as to allow for actual live feed of data from fast moving vehicles or sensor arrays, thus it requires a high nitrate of about a 11000 baud rate. The Xbees are capable of that but are set to 6200 baud by default. Thus we need to change the baud rate in the XCTU utility through the terminal. This implies reflashing the firmware to the Xbee (I'm using Pro modules but its the same for standard series 1 modules).



    The Xbee pro modules run over  900mhz radio waves. Thus they do not interfere with the main 2.4Ghz control line of the Hexacopter. On the other hand they might disturb the 860Mhz radio signal coming from the Video receiver. I'm going to need to mess around with cloverleaf antennas and the channels used on the two devices later.

     Comming back to the main problem, in order to connect the Xbee to an X86 PC, an adapter board must be used, I chose the once form Paralax as it was good quality and a proven board.


   What becomes immediately obvious upon examining the USB adapter is that there is no reset button present. This is a major problem as the Xbee modules will be in a soft-brick after flshing, meaning that I will have to reset the xbee while powered. To do this, I have to "short" the RST pin to one of the ground pins. For some reason, the Xbees are not posting at all after doing this, possibly being Hard-bricked. I will have to look further into this but I am not making it a priority as it is more of a side/safety feature that can be added anytime later. 

Once functional it will enable me to see live telemetry from a laptop as well as control the APM 2.5 through something like a joystick or a gamepad, thus making it far more accessible for beginners, and funner with something like the Wii mote ;)


Battery Changes

     Today I realized that two 2200mah 3S batteries are not going to be enough for any susteined flight.  Thus I am moving towards bigger batteries, much bigger.

This is also going to address the weight problem as the hexacopter is currently too powerful for the amount of power its producing. Thus the batteries will get it above the current 17% hover throttle to at least 30+%, an ideal value being between 40-60%.

I am thinking of the following batteries:


Or possibly something like this:



So these options should be fine in series with XT60 connectors, will probably have to use XT90 at the endings as XT60 is only good up to 90Amps and I could be drawing 180Amps between the two batteries. XT90s are generally good for 220Amps each. They are huge though. 

I'm still looking at a better power delivery. There are different options

Ardupilot based Hexacopter Workflow

Andrei Aldea
DeadCat MK1 Project Documentation


These are the project notes for my Quadcopter build. I will try to include as much information regarding everything that I have done as well as provide the best and most pictures that I possibly can.


The Frame: I decided to go for a frame that had not become very popular yet, inspired by the Orvillecopter.

What makes this type of frame unique is the angle at which the arms are spaced. In the Dead Cat type configuration (the origin of the name is obvious), the front arms of the quadcopter are pushed back, allowing for easy mounting of camcorders since the propellers won’t get in the shot. The rear arms are at the same angle as a standard X configuration quadcopter.


The frame I used is a modified XK450 frame, similar to the DJI Flamewheel. It is shown below with the optional extended legs, which I used. Its a standard X configuration, but I wanted more.



So this is the SK450 with the Dead Cat modification. The Long legs above are used on the arms.



The Motors:


For Motors I decided to go with Turnigy NTM Prop Drive 28-26 1350KV Motors. These are AC Motors, so they are very efficient, they will pull a max of 30Amps each, they also have a very good thrust to weight ratio with 8 inch props. According to the website, their specs are as follows:


Poles: 3
Motor Wind: 14T
Max current: 18A
Max Power: 227W @ 11.1V (3S) / 302W @ 15V (4S)
Shaft: 3mm
Weight: 56g
ESC: 25~30A
Cell count: 3s~4s Lipoly
Bolt holes: 16mm & 19mm
Bolt thread: M3
Connection: 3.5mm Bullet-connector
Prop Test Data:
8x3.8 - 11.1V / 158W / 14.2A / 0.736kg thrust
8x3.8 - 14.8V / 310W / 21A / 1.1kg thrust
.

This means that the motors will spin at 100% throttle 16200 RPM and 22005 RPM depending on the battery voltage



The Batteries:



Specifications:
Capacity: 2200mAh
Voltage: 3S1P / 3 Cell / 11.1V
Discharge: 35C Constant / 70C Burst
Weight: 199g (including wire, plug & case)
Dimensions: 115x35x27mm
Balance Plug: JST-XH
Discharge Plug: XT60



The Flight Controller:



Other Components:
How They Work:
High strand-count silicone insulated average Amperage tolerance:


  • 8AWG 200 amps
  • 10AWG 140 amps
  • 12AWG 90 amps
  • 14AWG 60 amps
  • 16AWG 35 amps
  • 18AWG 20 amps
  • 20AWG 12 amps
  • 22AWG 10 amps


Update October 17 2013: The Xbee Pro modules are refusing to change baud rate and have been soft bricked, I’m on the phone with Digi and Adafruit to see what can be done. Until I get them working, telemetry will not be active.
Update October 19 2013: A shipment from Hong Kong will arrive at JFK with the parts I need to finally assemble the quadcopter, it includes some video equipment as well as 10-14AWG wire and high diameter heat shrink tubing.


This is how my frame was destroyed… proving it was not a very good frame:


Now I’m waiting as of October 31 2013 for new parts from Hong Kong. Its extremely frustrating…


Update November 11 2013: I’m encountering some technical problems. I am going into deeper issues.


I have done some calculations on the motors, assuming Linear results rather than curves. They should provide a fairly close match to real life results:


Model: NTM Prop Drive Series 2826 1350kv
Kv: 1350rpm/v
Poles: 3
Motor Wind: 14T
Max current: 18A
Max Power: 227W @ 11.1V (3S) / 302W @ 15V (4S)
Shaft: 3mm
Weight: 56g
ESC: 25~30A
Cell count: 3s~4s Lipoly
Bolt holes: 16mm & 19mm
Bolt thread: M3
Connection: 3.5mm Bullet-connector



Prop Test Data:
8x3.8 - 11.1V / 158W / 14.2A / 0.736kg thrust
8x3.8 - 14.8V / 310W / 21A   / 1.1kg thrust


This results in the following assuming linear curve (might differ slightly):


9x4.5 - 11.1V / 211W / 18.9A  / 0.980 kg thrust
10x4.5 - 11.1v / 234W /   21A   / 1.098 kg thrust
11x4.5 - 11.1V / 258W / 23.1A / 1.198 kg thrust
12x4.5 - 11.1V / 281W / 25.2A / 1.307 kg thrust


Note: We see that 4S 8x3.8 is equivalent in thrust with 3S 10x4.5 in amperage.


This is all assuming linear thrust, real data might be different as the motor has in reality a curved thrust.


Max Current for the motor is rated at 18A so it might be pushing it...


For the Sake of it 4S (14.8V) configurations. They are not faisable!


9x4.5 - 14.8V / 413W / 18.9A / 0.980 kg thrust  //The calculations are not made yet. Results are 3S!!!!
10x4.5 - 14.8v / 234W /   21A / 1.098 kg thrust
11x4.5 - 14.8V / 258W / 23.1A / 1.198 kg thrust
12x4.5 - 14.8V / 281W / 25.2A / 1.307 kg thrust


Now to compare to predicted weight of the hexacopter:


Lift capacity = (Number of motors * thrust/motor) / 2 (50% capacity or less is ideal)


Overall theoretical lift by 3S:


 9x4.5 -11.1V  / 5.88Kg Overall - 2.94 Kg Ideal
10x4.5 -11.1V  / 6.588Kg Overall - 3.295 Ideal
11x4.5 -11.1V  / 7.188Kg Overall - 3.594 Ideal
12x4.5 -11.1V / 7.842Kg Overall - 3.921 Ideal


10x4.5- 11x.4.5 Ideal Overall