The UPOD firmware is written in the Arduino style C programming language. The Arduino IDE is used to compile and upload the code. The firmware is available in the Firmware folder of our github repository. The introductory video in the firmware folder will provide the basic information necessary to configure and upload the firmware code to the UPOD.
For those wishing to understand the code in-depth, read on…


UPOD firmware: in depth

Lines 40-52 give the configuration criteria for the UPOD firmware. Boolean flags determine which features are to be included in the code. A ’1′ will include the flag, a ’0′ will omit it.

#define GPS 0
#define SERIAL_STREAM 1
#define SD_WRITE 1
#define AUX_10bit 0
#define ON_BOARD 0 //MCP3424 on-board “address: H,F”
#define ALPHA_ARRAY 1 //auxillary 4-stat array, uses 2 MCP3424s
//SAMPLING CONFIGURATION
#define SAMPLE_RATE 1 //sample rate in Hz
#define FLUSH_RATE 1 //flush rate in Hz
//IMPORTANT: MODEL MUST HAVE A 2-digit ID number#define
model”UPOD88″ //UPOD model indicator
  •  ”GPS” will log GPS coordinates to the UPOD in NMEA standard format. Note: The UPOD firmware converts North/East=1 and South/West=-1
  • “SERIAL_STREAM” will stream the data real-time to the UART Tx/Rx.  Note: The UART is at 5V, not 3.3V
  • “SD_WRITE” will enable logging to the onboard microSD card.  If SD write is enables, no datalogging will take place if SD card is not present or broken.  (The UPOD instead displays a flashing LED error message)
  • “AUX_10bit” enables datalogging from the auxillary 8 channel, 10bit analog-to-digital converter breakout board
  • “ON_BOARD” refers to the 4 channel, 18bit differential analog-to-digital converter located on the rear-side of the board below the CO2 sensor
  • “ALPHA_ARRAY” refers to to quad-stat potentiostat board.

One might think it advantageous to enable all functions all the time.  Consider, however, that “GPS”, “ON_BOARD” and “ALPHA_ARRAY” all require additional sample time (eg. the 18bit ADC may only sample at 5SPS).  These latencies are in addition to the designated sample time.  For example, with the board fully-enabled and a defined sample time of 1 sec, the actual sample rate will be closer to 7sec.  This may be of concern for individuals wishing to sample at a higher rate.  Recall, however, that most of the sensors (metal-oxide type, electrolytic) have a physical hysteresis with characteristic time far greater than 7 sec.

When “GPS”, “AUX_10bit”,”ON_BOARD” or “ALPHA_ARRAY” are disabled, -999 will be logged in their place.

  • “SAMPLE_RATE” defines the desired sample-rate in Hz (more precisely, the pause between sampling events).  The UPOD green LED will flash for every sampling event.
  • “FLUSH_RATE” defines the rate at which data is flushed from the RAM buffer to the SD card.  This is only important is SD_WRITE is enabled.  For most purposes, FLUSH_RATE should be set to “SAMPLE_RATE.  The UPOD red LED will flash for every flush to the SD card.

 


Lines 55-63 establish our ‘mcp3424′ objets. Each object is associated with an MCP3424 chip. Each chip is identified by a unique address (see pg. 20 of the datasheet). The UPOD may accomodate up to 9 mcp3424 chips. The address is set physically using solderpad jumpers (see ‘Quadstat‘)

//MCP3424 declarations
mcp3424 on_board;
float on_board_value[4];#if ALPHA_ARRAY
mcp3424 alpha_one;
mcp3424 alpha_two;
float alpha_value[8];
#endif

Lines 210-217 set the mcp3424 object’s address within the void setup() {…} loop.

#if ON_BOARD
on_board.GetAddress(‘H’,'F’); //user defined address for on-board 18bit ADC
#endif#if ALPHA_ARRAY
alpha_one.GetAddress(‘G’,'F’); //user defined address for the alphasense pstat array (4-stat)
alpha_two.GetAddress(‘H’,'H’) ;
#endif

And each channel of the mcp3424 can be accessed through it’s object as in lines 456-461 of the void sample(…) {…} function.

#if ON_BOARD
on_board_value[0]=on_board.GetValue(1);
on_board_value[1]=on_board.GetValue(2);
on_board_value[2]=on_board.GetValue(3);
on_board_value[3]=on_board.GetValue(4);
#endif

 


Lines 67-129 define most of the strings used in the UPOD firmware. Here we use the ‘progmem’ library to store these lines in the program memory. Doing so prevents overflowing the RAM stack memory when running the code. here we have a list of strings of type prog_char and qualifier PROGMEM. A table of pointers *string_table[] points to each of these strings stored in program memory.

//TABLE of STRINGS STORED IN PROGRAM MEMORY
//—————————————-
char progmem_buffer[50];
prog_char string_0[] PROGMEM = “sampling sensors…”;
prog_char string_1[] PROGMEM = “flushing SD card buffer”;
prog_char string_2[] PROGMEM = “*Card initialization failed*”;
prog_char string_3[] PROGMEM = “*RTC is not working*”;
prog_char string_4[] PROGMEM = “Current file: “;
prog_char string_5[] PROGMEM = “*File cannot be found or opened on card*”;
prog_char string_6[] PROGMEM = “,”;
prog_char string_7[] PROGMEM = “/”;
prog_char string_8[] PROGMEM = “:”;
prog_char string_9[] PROGMEM = “\n”;
prog_char string_10[] PROGMEM = “”;
prog_char string_11[] PROGMEM = “”;
prog_char string_12[] PROGMEM = “Could not open calibration file. Rebooting…”;
prog_char string_13[] PROGMEM = “”;
prog_char string_14[] PROGMEM = “Model,YYYY/MM/DD,HH:MM:SS,UnixTime,”;
prog_char string_15[] PROGMEM = “Baseline,CO2,A0,Fig1,Fig2,e2v03,e2vNO2,e2v1,e2v2,”;
prog_char string_16[] PROGMEM = “e2v3,e2v4,Temp,Rh,BaroT,BaroP,Lat,N/S,Lon,”;
prog_char string_17[] PROGMEM = “E/W,C0,C1,C2,C3,C4,C5,C6,C7,Ba1,Ba2,Ba3,Ba4″;
prog_char string_18[] PROGMEM = “Bb1,Bb2,Bb3,Bb4,Bc1,Bc2,Bc3,Bc4″;
prog_char string_19[] PROGMEM = “Closing and Reopening file”;
prog_char string_20[] PROGMEM = “NaN”;
prog_char string_21[] PROGMEM = “Free RAM (bytes)= “;
prog_char string_22[] PROGMEM = “-999,-999,-999,-999″;
prog_char string_23[] PROGMEM = “”;
prog_char string_24[] PROGMEM = “”;PROGMEM const char *string_table[]=
{
string_0,
string_1,
string_2,
string_3,
string_4,
string_5,
string_6,
string_7,
string_8,
string_9,
string_10,
string_11,
string_12,
string_13,
string_14,
string_15,
string_16,
string_17,
string_18,
string_19,
string_20,
string_21,
string_22,
string_23,
string_24
};char tab[]=”,”;
char slash[]=”.”;
char colon[]=”:”;
char nan[]=”NaN”;

The string can be accessed using, for example, the commands in lines 283 to 284 which copy string 14 in program memory to a character buffer char progmem_buffer[50]in the stack and then print it to the SD card. The buffer now contains the contents of string 14.

strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[14]))); //print header section
logfile.print(progmem_buffer);

 


Lines 166-172 define the pin number for some of our hardware peripherals. These definitions are very important.

#define CS_Uno 10 //chip select for SD card SPI protocol
#define CS_ADC_1 9 // 10bit for MOx array default pull-up resistor to 5v
#define CS_ADC_2 2 // 10bit breakout board; attaches to header under CO2 sensor
#define RHT03_PIN A3 //RHT internal

#define red_led 8
#define green_led 7

The following table lists the pin assignments on the UPOD. Those with (selectable) are only associated with their hardware when the corresponding hardware flag is enabled. eg. pin 2 is the chip-select pin for the AUX_10_bit adc. Pin 2 is free to use for other I/O if AUX_10_bit is ‘off’ i.e. set to AUX_10_bit 0. It is important to disable the pin functions in the setup() {} funtion if you wish to modify the code and commandeer a pin for another use.
atmega_pinout


Lines 131-153 initialize variables and objects used to capture the GPS coordinate data. The GPS code is handled within the main body of the firmware rather than in an external library. The GPS module streams coordinate data every second which is captures and parsed by the UPOD. The atmega chip makes use of the ‘NewSoftSerial’ library which emulates a UART connection on a digital pin. Here, the virtual ‘Rx’ line is set to digital pin 4, and the Tx line is set to a fantom pin 254 (b/c the GPS module only transmits data). Notice that pin 4 is explicitly declared here rather than defined in lines 166-172 (above).

#if GPS
NewSoftSerial gpsSerial(4,254); //set gps-RX/atmega-TX to phony address
#define _GPGGA_TERM “$GPGGA,” //term to compare the string beginning with
#define _MAX_LENGTH 15 //max length of recursive string catching
#define _GPS_WAIT_TIME 3000 //time in mS to wait for a gps reading before aborting read
int length=0;
int wait=1;
int n=0;
char gpsIN;
char sentence_[_MAX_LENGTH];
int desired_string=0; //integer to hold the string comparision
int term_=0;
char time[11];
char lat[13];
char hem1[2];
char lon[13];
char hem2[2];
char one[]=”1″;
char minusone[]=”-1″;

unsigned long gps_time;
char* terms_[]={time, lat, hem1, lon, hem2};
#endif


The setup() {} function executes once when the chip first boots up. The commands called within the function initiate various features of the code.
Lines 195-204 set the pin mode for the pin numbers defined in the previous section.

pinMode(CS_Uno, OUTPUT);
pinMode(CS_ADC_1, OUTPUT);
digitalWrite(CS_ADC_1,HIGH); //deselect ADC initially
#if AUX_10bit
pinMode(CS_ADC_2, OUTPUT);
digitalWrite(CS_ADC_2,HIGH); //deselect ADC initially
#endif

pinMode(red_led,OUTPUT);
pinMode(green_led,OUTPUT);

Lines 206-228 perform hardware and communication initializations. The baud rate for the UART serial output is set to 9600. The GPS module’s baud rate is set to 4800. Initializations take place for the real time clock (I2C protocol) and BMP085 pressure/temp sensor. The settings are established for the SPI protocol.

Wire.begin();
RTC.begin();
Serial.begin(9600);

#if ON_BOARD
on_board.GetAddress(‘H’,'F’); //user defined address for on-board 18bit ADC
#endif

#if ALPHA_ARRAY
alpha_one.GetAddress(‘G’,'F’); //user defined address for the alphasense pstat array (4-stat)
alpha_two.GetAddress(‘H’,'H’) ;
#endif

#if GPS
gpsSerial.begin(4800);
#endif

SPI.begin();
SPI.setDataMode(0);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV16);

BMP085.Calibrate(); //get calibration constants from BMP085

Lines 230 to 259 within the setup() {} function check for the proper operation/connection to the real time clock and to the SD card. If the SD card is not present or broken, the red and green LED will flash in sync (note: this only applies if ‘SD_WRITE’ is enabled). If the real time clock is not operational, the red and green LED will flash in an alternating pattern. The soft_reset() function is called every 10 seconds. soft_reset() is defined in lines 438-441. It does nothing more than reboot the chip, restarting the setup() {} process until and SD card is present or the RTC chip is operational.

#if SD_WRITE
if (! SD.begin(CS_Uno))
{

//while(1) //lock into loop with red/green sync flashing at 1Hz for 10 sec, then reboot system
for(int i=1;i<=10;i++)
{
digitalWrite(green_led, HIGH);
digitalWrite(red_led,HIGH);
delay(500);
digitalWrite(green_led,LOW);
digitalWrite(red_led,LOW);
delay(500);
}
soft_reset();
}
#endif

if (! RTC.isrunning())
{
while(1) //Flash red/green in alternate at 1Hz if RTC not working
{
digitalWrite(green_led,HIGH);
digitalWrite(red_led,LOW);
delay(500);
digitalWrite(green_led,LOW);
digitalWrite(red_led,HIGH);
delay(500);
}
}

If an SD card is present and the RTC is operational, lines 260-313 will execute. Here, a filename is created using the current date (‘MMDDYYYY’) and the UPOD model number (‘XXX’). The code checks to see if a file already exists with the filename (‘XXXMMDDYYYY.csv’). If a file exists (logging already took place that day), then the file is opened for appending. The file is created if it does not already exist. If the code cannot open the file (eg. card is full), then it enters the same blinking pattern as described above.

DateTime now=RTC.now();
//fill string ‘current_file’ with MMDDYYYY
int month=now.month();
int day=now.day();
int year=now.year();

#if SD_WRITE
current_file[first_date]=month/10+’0′;
current_file[first_date+1]=month%10+’0′;
current_file[first_date+2]=day/10+’0′;
current_file[first_date+3]=day%10+’0′;
current_file[first_date+4]=((year/10)%100)%10+’0′;
current_file[first_date+5]=((year%1000)%100)%10+’0′;

//fill ‘current_file’ with SS of UPOD model number
current_file[0]=model[4]; //get the UPOD model number from defined string at header
current_file[1]=model[5];
current_file[2]=model[6];

if(! SD.exists(current_file))
{
logfile=SD.open(current_file, FILE_WRITE);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[14]))); //print header section
logfile.print(progmem_buffer);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[15])));
logfile.print(progmem_buffer);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[16])));
logfile.print(progmem_buffer);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[17])));
logfile.print(progmem_buffer);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[18])));
logfile.println(progmem_buffer);
logfile.flush();
}
else
{
logfile=SD.open(current_file, FILE_WRITE);
}

if(!logfile)
{
for(int i=1;i<=10;i++)
{
digitalWrite(green_led, HIGH);
digitalWrite(red_led,HIGH);
delay(500);
digitalWrite(green_led,LOW);
digitalWrite(red_led,LOW);
delay(500);
}
soft_reset();
}
#endif


Lines 317-406 define the main loop() {} of the program. The loop begins by flashing the green LED, signaling a data measurement event. The GPS portion waits until a line of data is received from the GPS module, and then parses it to extract the latitude and longitude coordinates. Once the GPS has captured the coordinates, the loop executes the void sample(int,int*) function. This is followed by a delay time between sampling events.

void loop()
{

digitalWrite(green_led,HIGH); //flash green light when sampling
delay(50);
digitalWrite(green_led,LOW);

#if GPS
gps_time=millis();
wait=1;
length=0;
n=0;
desired_string=0;

beginning:
while(wait && ( (millis()-gps_time)<_GPS_WAIT_TIME) && ( (millis()-_GPS_WAIT_TIME)>0) ) //this enters the GPS reading state until all info is returned
{
readin:
if(gpsSerial.available())
{
gpsIN=gpsSerial.read();

if (gpsIN == ‘$’) //if we’re at the beginning ofa sentence
{
n=1; //set string indexer to ’1′
term_=0; //re-index to first term
desired_string=0; //set string comparing term to ’0′
goto readin;
}

if(n>0 && n<7) //if we are still reading in the first term of the new string
{
if(gpsIN == _GPGGA_TERM[n])
{
desired_string=desired_string++; //should == 6 if correct string
}
n++;
} //end loop through string comparison

if(desired_string >=6)
{
if(desired_string==6)
{
desired_string++;
goto readin; //don’t look at the first ‘,’ since we don’t want the first term
}

//we sill continue to enter this statement until ‘desired_string’ is reset
switch (gpsIN)
{
case ‘,’: //when we have reached the end of a term (not including first term)
terms_[term_][length]=’\0′; //terminate output string
// Serial.println(terms_[term]);
term_++; //increment to the next term
length=0;
if(term_>=5)
{ wait=0;
desired_string=0;
goto beginning;
}
goto readin;
break;

case ‘*’: //we have reached the end of the line and want to exit everything
wait=0;
goto beginning;
break;
}

terms_[term_][length]=gpsIN; //add input character to proper term
length++;
}
}
}

if(wait == 1) //if we exited because the time limit has elapsed, NOT because we have observed a good reading
{
char none[]=”0 \0″;
terms_[0]=none;
terms_[1]=none;
terms_[2]=none;
terms_[3]=none;
terms_[4]=none;
}
#endif

sample(cycles_b4_flush, &flush_crit);

delay(1000./SAMPLE_RATE-100.); //delay between normal samples minus the flash time of 50ms and a little extra for sensor polling
}


Lines 411-436 define the void ADCcall(int[],int) function. This function is used to acquire the 8 channels of the 10bit analog-to-digital values from the MCP3008. It takes as an argument an array of 8 integers and the ‘chip select’ or ‘slave select’ pin (see SPI). The MCP3008 is used for signals from the 8 metal-oxide type sensors (‘e2v’ and ‘Figaro’) as well as the ADC breakout board.

void ADCcall(int Figaro[], int pin)
{
const byte ADC_ch[8]={B1000,B1001,B1010,B1011,B1100,B1101,B1110,B1111}; //channel designations
const byte start_byte= B00000001; //initial byte to send to start ADC transmission

digitalWrite(pin,LOW);
delay(10);
digitalWrite(pin,HIGH); //cycle ADC to reset if it was in LOW initially
delay(10);

for (int i=0; i<8; i++)
{
digitalWrite(pin,LOW); //activate ADC chip

byte channel_byte=ADC_ch[i]<<4; //determine channel to read from

SPI.transfer(start_byte);
byte byte1=SPI.transfer(channel_byte);
byte byte2=SPI.transfer(B00000000);

Figaro[i]= int(((byte1 & B00000011)<<8 | byte2) ); //mask first 6 bits of 'byte1' and concatenate w/ 'byte2'

digitalWrite(pin,HIGH); //deselect ADC
}
}

Lines 444-752 make up the void sample(int, int*) function. This function defines all actions taking place during a sampling event. Lines 446-474 request the current data from various sensors. This includes the current time from the RTC, the barometic pressure and temperature from the BMP085 sensor, the analog voltages from the metal-oxide type sensor array (8 sensors), the 8 analog voltages from the ADC breakout board, the analog voltages from the 4 channels of the onboard 18bit ADC, the voltages from the Quad-stat, and the relative humidity and temperature from the RHT03 sensor.

DateTime now=RTC.now();
BMP085.refresh();

//Sample sensors and write to buffer
ADCcall(ADC1, CS_ADC_1);

#if AUX_10bit
ADCcall(ADC2,CS_ADC_2);
#endif

#if ON_BOARD
on_board_value[0]=on_board.GetValue(1);
on_board_value[1]=on_board.GetValue(2);
on_board_value[2]=on_board.GetValue(3);
on_board_value[3]=on_board.GetValue(4);
#endif

#if ALPHA_ARRAY
alpha_value[0]=alpha_one.GetValue(1);
alpha_value[1]=alpha_one.GetValue(2);
alpha_value[2]=alpha_one.GetValue(3);
alpha_value[3]=alpha_one.GetValue(4);
alpha_value[4]=alpha_two.GetValue(1);
alpha_value[5]=alpha_two.GetValue(2);
alpha_value[6]=alpha_two.GetValue(3);
alpha_value[7]=alpha_two.GetValue(4);
#endif

myRHT03.readData(); //read the RHT03 T and Rh

Lines 476-597 write the data to the SD card buffer. The GPS ‘North’/'South’ and ‘East’/'West’ tags are converted to ’1′/’-1′ respectively. Values written to the buffer are comma delimited and consistent with the header titles.

#if SD_WRITE
//add values to comma delimited data line
logfile.print(model); //model name
logfile.print(tab);
logfile.print((int)now.year()); //current year
logfile.print(slash);
logfile.print((int)now.month()); //current month
logfile.print(slash);
logfile.print((int)now.day()); //current day
logfile.print(tab);
logfile.print((int)now.hour()); //curent hr
logfile.print(colon);
logfile.print((int)now.minute()); //current minute
logfile.print(colon);
logfile.print((int)now.second()); //current second
logfile.print(tab);
logfile.print(now.unixtime()); //current unixtime; time since midnight 1/1/1970 (seconds)
logfile.print(tab);
logfile.print(analogRead(A1)); //Baseline VOC sensor
logfile.print(tab);
logfile.print(analogRead(A2)); //CO2 sensor
logfile.print(tab);
logfile.print(analogRead(A0)); //analog channel 0
logfile.print(tab);
logfile.print(ADC1[0]); //Figaro 1
logfile.print(tab);
logfile.print(ADC1[1]); //Figaro 2
logfile.print(tab);
logfile.print(ADC1[2]); //e2v_O3
logfile.print(tab);
logfile.print(ADC1[3]); //e2v_NO2
logfile.print(tab);
logfile.print(ADC1[4]); //e2v_1
logfile.print(tab);
logfile.print(ADC1[5]); //e2v_2
logfile.print(tab);
logfile.print(ADC1[6]); //e2v_3
logfile.print(tab);
logfile.print(ADC1[7]); //e2v_4
logfile.print(tab);
dtostrf(myRHT03.getTemperatureC(),6,2,RHT03_buffer); //Temp internal
logfile.print(RHT03_buffer);
logfile.print(tab);
dtostrf(myRHT03.getHumidity(),6,2,RHT03_buffer); //Rh internal
logfile.print(RHT03_buffer);
logfile.print(tab);
logfile.print(BMP085.temperature);
logfile.print(tab);
logfile.print(BMP085.pressure);
#if GPS
logfile.print(tab);
logfile.print(terms_[1]); //lat
logfile.print(tab);
if (strcmp(hem1,”N”)==0)
{
logfile.print(one);
}
if (strcmp(hem1,”S”)==0)
{
logfile.print(minusone);
}
logfile.print(tab);
logfile.print(terms_[3]); //lon
logfile.print(tab);
if (strcmp(hem2,”E”)==0)
{
logfile.print(one);
}
if (strcmp(hem2,”W”)==0)
{
logfile.print(minusone);
}
#else
logfile.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
logfile.print(progmem_buffer);
#endif

#if AUX_10bit
for(int p=0;p<8;p++)
{
logfile.print(tab);
logfile.print(ADC2[p]); //analog0
}

#else
logfile.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
logfile.print(progmem_buffer);
logfile.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
logfile.print(progmem_buffer);
#endif

#if ON_BOARD
for(int p=0;p<4;p++)
{
logfile.print(tab);
logfile.print(on_board_value[p]);
}
#else
logfile.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
logfile.print(progmem_buffer);
#endif

#if ALPHA_ARRAY
for(int p=0;p<8;p++)
{
logfile.print(tab);
logfile.print(alpha_value[p]);
}
#else
logfile.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
logfile.print(progmem_buffer);
logfile.print(tab);
logfile.print(progmem_buffer);
#endif

logfile.println();
#endif //SD_WRITE

Lines 599-717 perform the same actions as above, accept streaming data to the UART instead of writting it to the SD card bufer.

#if SERIAL_STREAM
Serial.print(model); //model name
Serial.print(tab);
Serial.print((int)now.year()); //current year
Serial.print(slash);
Serial.print((int)now.month()); //current month
Serial.print(slash);
Serial.print((int)now.day()); //current day
Serial.print(tab);
Serial.print((int)now.hour()); //curent hr
Serial.print(colon);
Serial.print((int)now.minute()); //current minute
Serial.print(colon);
Serial.print((int)now.second()); //current second
Serial.print(tab);
Serial.print(now.unixtime()); //current unixtime; time since midnight 1/1/1970 (seconds)
Serial.print(tab);
Serial.print(analogRead(A1)); //Baseline VOC sensor
Serial.print(tab);
Serial.print(analogRead(A2)); //CO2 sensor
Serial.print(tab);
Serial.print(analogRead(A0)); //analog channel 0
Serial.print(tab);
Serial.print(ADC1[0]); //Figaro 1
Serial.print(tab);
Serial.print(ADC1[1]); //Figaro 2
Serial.print(tab);
Serial.print(ADC1[2]); //e2v_O3
Serial.print(tab);
Serial.print(ADC1[3]); //e2v_NO2
Serial.print(tab);
Serial.print(ADC1[4]); //e2v_1
Serial.print(tab);
Serial.print(ADC1[5]); //e2v_2
Serial.print(tab);
Serial.print(ADC1[6]); //e2v_3
Serial.print(tab);
Serial.print(ADC1[7]); //e2v_4
Serial.print(tab);
dtostrf(myRHT03.getTemperatureC(),6,2,RHT03_buffer); //Temp internal
Serial.print(RHT03_buffer);
Serial.print(tab);
dtostrf(myRHT03.getHumidity(),6,2,RHT03_buffer); //Rh internal
Serial.print(RHT03_buffer);
Serial.print(tab);
Serial.print(BMP085.temperature);
Serial.print(tab);
Serial.print(BMP085.pressure);
#if GPS
Serial.print(tab);
Serial.print(lat); //lat
Serial.print(tab);
if (strcmp(hem1,”N”)==0)
{
Serial.print(one);
}
if (strcmp(hem1,”S”)==0)
{
Serial.print(minusone);
}
Serial.print(tab);
Serial.print(lon); //lon
Serial.print(tab);
if (strcmp(hem2,”E”)==0)
{
Serial.print(one);
}
if (strcmp(hem2,”W”)==0)
{
Serial.print(minusone);
}
#else
Serial.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
Serial.print(progmem_buffer);
#endif

#if AUX_10bit
for(int p=0;p<8;p++)
{
Serial.print(tab);
Serial.print(ADC2[p]);
}
#else
Serial.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
Serial.print(progmem_buffer);
Serial.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
Serial.print(progmem_buffer);
#endif

#if ON_BOARD
for(int p=0;p<4;p++)
{
Serial.print(tab);
Serial.print(on_board_value[p]);
}
#else
Serial.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
Serial.print(progmem_buffer);
#endif

#if ALPHA_ARRAY
for(int p=0;p<8;p++)
{
Serial.print(tab);
Serial.print(alpha_value[p]);
}
#else
Serial.print(tab);
strcpy_P(progmem_buffer, (char*)pgm_read_word(&(string_table[22]))); // -1,-1,-1,-1
Serial.print(progmem_buffer);
Serial.print(tab);
Serial.print(progmem_buffer);
#endif
Serial.println();
#endif //SERIAL_STREAM

Lastly, lines 720-751 flush the SD card buffer, writing the data to the card itself. This occurs at the flush rate frequency specified in the header definitions of the firmware (see the first section of this page). The red LED will flash when this process takes place. When the data is flushed, the code will also close and reopen the logfile. The current date is used in reopening the file, thus is the day has changed between consecutive data flushes, the code will create a new logfile.

#if SD_WRITE
*flush_crit=*flush_crit+1; //index counter up for flus criteria
if(*flush_crit>=cycles )
{
digitalWrite(red_led,HIGH);

logfile.flush();

*flush_crit=0;
delay(100);
digitalWrite(red_led,LOW);

logfile.close(); //close and reopen logfile to check SD card presence
current_file[(first_date+2)]=now.day()/10+’0′;
current_file[(first_date+3)]=now.day()%10+’0′; //edit current file

if(! SD.exists(current_file))
{
soft_reset();
}

else
{
logfile=SD.open(current_file, FILE_WRITE);
}

if(!logfile)
{
soft_reset();
}
}
#endif