Teensy 3.0-based device for measuring photosynthesis

Status
Not open for further replies.

gbathree

Active member
Hello! Me and some other folks are working making a hand-held, open source device which can measure plant photosynthesis. This device will ultimately connect to a cell phone to allow for easy display of results and to transfer the data into a global database of plant information sharing. The idea combines big data (combined, global, plant health data in the field), citizen science and education (engaging the public to take part in real research), and genuinely useful research in a new and unique way. There's a number of awesome new citizen science projects happening which follow the same idea (see American Gut project and the guys at Public Laboratories are two great examples). Our project is called Photosynq and is located at www.photosynq.org, and we keep files and more info at the project management page at Open Design Engine here.

Anyway, shameless promo out of the way, we're using the Teensy 3.0 to control the LEDs and to take data from the detectors. You can find a pretty good description of the types of measurements we're interested in taking at a commercial version of similar (but pretty expensive and not very open source but they have a great explanation page) equipment here. The short explanation is:

1. We pulse LED light at a plant leaf.
2. The light is processed by the leaf in 1 or 3 ways - 1) the energy is dissipated in the leaf (heat), 2) the energy is used by the plant for useful stuff (making sugars and growing), or 3) the energy is absorbed and re-released as infra-red light in the 700 - 800nm range. This 3rd process is called "fluorescence" - if you don't believe me, see this picture using an IR filter. The leaves are so bright because they are fluorescing - aka emitting IR!
3. We use a detector to measure the leaf's fluorescence response.
4. Based on that response, we can calculate how much light the plant is able to absorb and use, how much it's using right now, and lots of other fundamental and truly valuable stuff.

There's other kinds of measurements we'd like to take with this device besides this, but this is a good start.

Here's the basic requirements for using the Teensy:
- In order to effectively take these measurements, we need to flash the LED and sample the detector pretty quickly (on the 10s of microseconds timescale), and we need to be able to ensure the timing of those two activities on the 1s of microseconds timescale.
- We want this to be a project that anyone can take part in, so ideally we'd like the code to be fairly "standard arduino" stuff. If we have more complex code, we'd like to make a library to simplify the front-end

Ok - that's a good intro post. More posts to come!
 
That sounds awesome!

I might be able to contribute to the project. And my son will absolutely love this project.

I think you'll really need to use interrupts with a timer to get your sampling. You'll also want a a class-10 SDHC card, and look at the other threads on this.

I've been doing this and writing directly to an SD card, and then transmitting the resulting file over USB to the host PC. When I get my bluetooth modules in, I'll be transmitting real-time down-sampled data over bluetooth. And when done sampling, it will send the whole file (with all the samples) over bluetooth (ultimately to an android device). I also have a base sample rate, but can sample at lower sample rates for some signals (ie for a base sample rate of 100Hz, I often sample at 100Hz,50Hz,25Hz,20Hz,10Hz,5Hz,2Hz,1Hz)

If this is a measure of photosynthesis efficiency, this would be very cool for plant breeding or genetics.
 
Phew - sorry it took a few days to respond, been trying to work on this to get some worthwhile numbers to share.

Thanks linuxgeek! In our group we have some good expertise but not a lot on the arduino coding side of things, so I'd really love help and ideas on how to make things faster/cleaner/easier.

Here's what I came up with (see code below), based on comments from the thread and my own ability (or lack thereof). Basically, it's just a for loop to define how many OFF/ON cycles light cycles there are, and then I stuff as many analogReads as possible on the ON cycle.

I am pretty sure the right way to do this is with interrupts and timers, like the PITimer here https://github.com/loglow/PITimer, for all the reasons previously suggested but I can't even get his loglow's example to work. Also, linuxgeek your totally right about the sd card - I've found I can store about 1000 analogreads before the arduino just locks up (presumably not enough memory?). Plus, the transfer rates (read write) for the SD cards on the 48mhz teensy are in the 1 - 5us range which is pretty darn fast.

So possible improvements: try a timer-based interrupt to free up clock cycles and add precision, decrease analongRead time to get more reads in a single ON cycle, decrease the time it takes to save the data somewhere (sd card?).

Ideal: It we could get the program to turn on an led, do at least one analogread, then turn off the led all in 1us then turn it off for 1us and repeat that cycle a few hundred times AND store the read data AND make it relatively easy for standard arduino users (not super-arduino users) to code perhaps by making a library or something - now THAT would be awesome! ... any suggestions?

** To measure the actual analogRead times, you'll need an oscilliscope. See the full file here https://opendesignengine.net/documents/15 for all the gory details.

Code:
/*
DESCRIPTION:
This file is used to measure how quickly and with how much error the analogRead() can be measured at the top (ON cycle) of a digitalWrite() call.
*/

unsigned long pulsecycles = 1; // Number of times the "pulselengthon" and "pulselengthoff" cycle (on/off is 1 cycle) (maximum = 1000 until we can store the read data in an SD card or address the interrupts issue)
unsigned long pulselengthon = 50; // Pulse LED on length in uS (minimum = 5us based on a single ~4us analogRead - +5us for each additional analogRead measurement in the pulse).
unsigned long pulselengthoff = 9975; // Pulse LED off length in uS (minimum = 20us + any additional operations which you may want to call during that time).
int measuringlight1 = 5;
int detector1 = A0;
unsigned long start1,start1orig,end1;
unsigned long pulselengthoncheck, pulselengthoffcheck, pulsecyclescheck, totaltimecheck;
int data1, data2, data3, data4;
float data1f, data2f, data3f, data4f;
int i = 0; // This detects the number of analogReads() per pulse

void setup() {
  
Serial.begin(38400); // set baud rate for serial communication
pinMode(measuringlight1, OUTPUT); // set pin to output
analogReadAveraging(1); // set analog averaging to 0 (ie ADC takes only one signal, takes ~3us)]
start1 = 0;
start1orig = 0;
end1 = 0;
data1 = 0;
data2 = 0;
data3 = 0;
data4 = 0;
}
  
void loop() {

// METHOD 1: 
// Using a for loop to define ON/OFF cycles, and using an empty while loop to do the timing

// NOTES:
// interrupts (in the form of cli() and sei() OR the standard arduino interrupts / noInterrupts) seems to make no difference in the result... though the Interrupts call itself was better than sei()
// based on looking at the actual oscilliscope but not much
// Adding to a String variable using the =+ or the string.concat() version of the string function takes about 8us!
// BUT, setting a simple int data variable equal to our analog read, that takes no time at all.
// when concatenating a string, this is slowest -   data = data + data1; .  It's better to call the concat function once and concatenate multiple things at a time within it, rather than making multiple calls.
// Even for the very quick pulses when using WASP method or other really short methods, we should be able to use the ADC to get 1us timed flashes and then just run analogRead as fast as possible (~3us).  
// Then, we can drop any of the values which are clearly too low (this should be obvious on a graph)... or perhaps we can speed up our analogRead?

// IMPROVEMENTS:
// Find a better way to store data than the String function - it's really slow and causes our OFF time to be really slow also.
// Even just getting rid of second string concatenation to add the comma (data += ",") would allow us to shorten the OFF time minimum down to 10us.
// We need a better place to store the data (SD card, for example) data storage limitation is causing the maximum number of cycles to be ~1000.  SD card should take care of that problem.

// SPECS USING THIS METHOD: 
// Peak to Peak variability, ON and OFF length variability all is <1us.  Total measurement variability = 0 to +3us (regardless of how many cycles)

digitalWriteFast(measuringlight1, HIGH); // OPTIONAL: Used with the RC circuit to measuring analogRead() timing (also, switch LOW and HIGH on the digitalWrite's in the program)
delay(3000); // give operator time to turn on the serial monitor and the capacitor to charge
noInterrupts(); // turn off interrupts to reduce interference from other calls

// Flash the LED in a cycle with defined ON and OFF times, and take analogRead measurements as fast as possible on the ON cycle
start1orig = micros();
start1 = micros();
for (i=0;i<pulsecycles;i++) {
  digitalWriteFast(measuringlight1, LOW); // During the ON phase, keep things as fast as possible - so simple data1 and data2 - no strings or anything confusing!
  data1 = analogRead(detector1); // user needs to set the number of analogReads() per ON cycle - add more for more analogReads()
  data2 = analogRead(detector1);
  data3 = analogRead(detector1);
  data4 = analogRead(detector1);
  start1=start1+pulselengthon;
  while (micros()<start1) {}
//  digitalWriteFast(measuringlight1, LOW); // Now, during OFF phase, we can average that data and save it as a string!
  start1=start1+pulselengthoff;
  while (micros()<start1) {} 
}
end1 = micros();

interrupts();

// Measure the total time of the run, the time of one ON cycle, and the time of one OFF cycle 
totaltimecheck = end1 - start1orig;

data1f = data1*3.3/1023; // convert the analog read into a voltage reading to correspond to the oscilliscope
data2f = data2*3.3/1023; // convert the analog read into a voltage reading to correspond to the oscilliscope
data3f = data3*3.3/1023; // convert the analog read into a voltage reading to correspond to the oscilliscope
data4f = data4*3.3/1023; // convert the analog read into a voltage reading to correspond to the oscilliscope

// Print values:
delay(50);
Serial.println("check the length of one pulse (one on/off), and the total number of pulses in the run: expected versus actual:");
Serial.println("expected values");

Serial.print("# of pulse cycles:  ");
Serial.println(pulsecycles);

Serial.print("total run length:  ");
Serial.println(totaltimecheck);

Serial.print("value of i:  ");
Serial.println(i);

// You can try to add start and end variables to measure the pulse on/off time, but it's better and more accurate to use an oscilliscope
//Serial.print("pulse length on actual:  ");
//Serial.println(pulselengthoncheck);
//Serial.print("pulse length off actual:  ");
//Serial.println(pulselengthoffcheck);

Serial.println("averaged data points from each ON measurement cycle");
Serial.println(data1f);
Serial.println(data2f);
Serial.println(data3f);
Serial.println(data4f);

delay(50);

while (1) {};

}
 
I'd like to understand the physics of the phenomena, so I could help more.

Why exactly do you pulse a light? It seemed like you are trying to remove the impact of ambient lighting.
But isn't your baseline measure, without an LED turned on, a good measure of ambient light?

How long does it take for a plant to flouresce after exposure to light?

I just recently did a test on mine, and mine was capturing and putting into a buffer in 6us.

And I just did a test to see if I could capture at 1000Hz and 500Hz simultaneously. It worked.
But I also suspect I'm doing something wrong with interrupts, so I'm stress testing it, and trying to simplify the code as much as possible.

I think there's several problems with your code, but I can't look at it too much right now. I'm pretty slow at reading/writing C/C++ still.

You should take a look at ByteBuffer, and use that for a buffer. It needs to be rewritten for teensy3. But you can use putInt, but pass it a short, and use getInt(). lol, I just automatically finished that sentence with a semicolon.

I think I mostly rewrote ByteBuffer for t3, but I think before finishing it, I moved on to completely rewriting it for what i needed. I'll post it if I did.

I think you should not be doing any floating point math. Just store the ints. You can copy/paste it into libreoffice and do those calculations.

I think you should also look at sdfat's examples for writing audio to an sdcard.
 
I'm trying to get Dave, the professor here, to effectively describe what we're doing on the website, he'll do it much better than me of explaining it (hopefully that'll be the next blogpost on photosynq) - maybe he'll even just jump on this forum and respond directly. In the mean time, these links give you a good sense of pulse modulated fluorescence, which is the first type of measurement we'd like to get up and running - http://www.hansatech-instruments.com/modulated_overview.htm . The wiki article on plant fluoresence (called chlorophyll fluoresence) is also a good start - http://en.wikipedia.org/wiki/Chlorophyll_fluorescence .

For the "baseline as a measure of ambient light" question - we are using a detector which picks up only 700 - 1200nm light from Hamamatsu, so normal non IR light is not picked up (well, that's 99.999% true at least). In addition, we are using an AC filter on the output of the detector to effectively "get rid" of the baseline value. So, if you're in a room with some ambient IR light, the detector reading is 0. But if you have a pulsed IR source which is pulsed at sufficient frequency to get through the AC filter, then you will see a reading. Since we are pulsing the LED, the IR response from the plant is also pulsed, and therefore the signal is visible from the detector.

Again, go to the hansa-tech website and wikipedia for a clearer explanation if that didn't help :)

I was thinking about putting the code on github or something also so people can mess with it, but it seems a bit overkill for simple arduino code.

Thanks for the suggestions and if I'd love to get feedback on optimizing/improving the code. I'll work on suggestions so far and report back - let me know if you make any more progress on interrupts!
 
Ok - we made some progress. Now I've got code which:

1) takes analogread()s pretty fast (by setting analogreadaveraging to 1 as suggested) - it takes only a few us per read.
2) can save data to the SD card - each write or append takes about 20ish us (actually, when you do file.print(O_WRITE) it's pretty slow to set up, but after that any file.print(O_APPEND)'s that you do are around 20us).
3) create a folder structure on the SD card and auto-create new folders so the user can effectively append existing experiments or create new ones.
4) has a standard calibration routine (calibration not currently enabled, but once we reduce noise on the analogread it will be) and user options for running the protocol and saving the data.

I know it's not optimized - I'm really barely an Arduino programmer, let alone C or C++, so I managed to get through it but I'd love any feedback. The entire code is posted here, but I hope to get it up on Github soon to make collaboration easier. Also, posted on our website with a quick update here.

The SD card is the standard PJRC version which you buy through their store, and it's hooked up as per this post - http://forum.pjrc.com/threads/16758-Teensy-3-MicroSD-guide .

In the long term, the right solution is to create a library for each of the subroutines I think... but I'm not sure. I'd love to get any thoughts/feedback/suggestions, especially as it relates to improving speed, performance, simplifying the code, or removing parts of the code which may result in lockups or other issues.

Code:
/*
DESCRIPTION:
 This file is used to measure pulse modulated fluorescence (PMF) using a saturating pulse and a measuring pulse.  The measuring pulse LED (for example, Rebel Luxeon Orange) can itself produce some infra-red
 fluorescence which is detected by the detector.  This signal causes the detector response to be higher than expected which causes bias in the results.  So, in order to account for this added infra-red, we 
 perform a calibration.  The calibration is a short set of pulses which occur at the beginning of the program from an infra-red LED (810nm).  The detector response from these flashes indicates how "reflective"
 the sample is to infra-red light in the range produced by our measuring LED.  We can create a calibration curve by measuring a very reflective sample (like tin foil) and a very non-reflective sample (like
 black electrical tape) with both the calibrating LED and the measuring LED.  This curve tells us when we see detector response X for the calibrating LED, that correspond to a baseline response of Y for the
 measuring LED.  Once we have this calibration curve, when we measure an actual sample (like a plant leaf) we automatically calculate and remove the reflected IR produced by the measuring pulse.  Vioala!
 Using Arduino 1.0.3 w/ Teensyduino installed downloaded from http://www.pjrc.com/teensy/td_download.html .   
 */

// NOTES FOR USER
// When changing protocols, makes sure to change the name of the saved file (ie data-I.csv) so you know what it is.
// Max file name size is 12 TOTAL characters (8 characters + .csv extension).  Program does not distinguish between upper and lower case
// Files have a basename (like ALGAE), and then for each subroutine an extension is added (like ALGAE-I) to indicate from which subroutine the data came.  
// When you create a new experiment, you save the data (ALGAE-I) in a folder.  If you want stop and restart but save data to the same file, you may append that file.
// Otherwise, you can create a new data file (like ALGAE-I) file an a different folder, which will be named 01GAE, 02GAE, 03GAE and so on.
// Calibration is performed to create an accurate reflectance in the sample using a 850nm LED.  You can find a full explanation of the calibration at https://opendesignengine.net/documents/14
// A new calibration should be performed when sample conditions have changed (a new sample is used, the same sample is used in a different position)
// The previous calibration value is saved in the SD card, so if no calibration is performed the most recently saved calibration value will be used!
// See KEY VARIABLES USED IN THE PROTOCOL below to change the measurement pulse cycles, pulse lengths, saturation pulses, etc.
// Note that the output values minimum and maximum are dependent on the analog resolution.  From them, you can calculate the actual current through the detector.
// ... So - at 10 bit resolution, min = 0, max = 10^2 = 1023; 16 bit resolution, min = 0, max = 16^2 = 65535; etc.
// ... To calculate actual voltage on the analog pin: 3.3*((measured value) / (max value based on analog resolution)).
// If you want to dig in and change values in this file, search for "NOTE!".  These areas provide key information that you will need to make changes.


// SPECS USING THIS METHOD: 
// Peak to Peak variability, ON and OFF length variability all is <1us.  Total measurement variability = 0 to +3us (regardless of how many cycles)

// SD CARD ENABLE AND SET
#include <SdFat.h>
#include <SdFatUtil.h> // use functions to print strings from flash memory

const uint8_t SD_CHIP_SELECT = SS;

SdFat sd;
#define error(s) sd.errorHalt_P(PSTR(s))  // store error strings in flash to save RAM

// KEY VARIABLES USED IN THE PROTOCOL
unsigned long calpulsecycles = 50; // Number of times the "pulselengthon" and "pulselengthoff" cycle during calibration (on/off is 1 cycle)
// data for measuring and saturating pulses --> to calculate total time=pulsecycles*(pulselengthon + pulselengthoff)
int pulsecycles = 100; // Number of times the "pulselengthon" and "pulselengthoff" cycle (on/off is 1 cycle) (maximum = 1000 until we can store the read data in an SD card or address the interrupts issue)
int saturatingcycleon = 20; //The cycle number in which the saturating light turns on (set = 0 for no saturating light)
int saturatingcycleoff = 40; //The cycle number in which the saturating light turns off
unsigned long pulselengthon = 25; // Pulse LED on length in uS (minimum = 5us based on a single ~4us analogRead - +5us for each additional analogRead measurement in the pulse).
unsigned long pulselengthoff = 49975; // Pulse LED off length in uS (minimum = 20us + any additional operations which you may want to call during that time).
char filename[13] = "ALGAE"; // Base filename used for data files and directories on the SD Card
float reference = 3; // The reference (AREF) supplied to the ADC
int analogresolution = 16; // Set the resolution of the analog to digital converter (max 16 bit, 13 bit usable)  
int measuringlight1 = 3; // Teensy pin for measuring light
int saturatinglight1 = 4; // Teensy pin for saturating light
int calibratinglight1 = 2; // Teensy pin for calibrating light
int detector1 = A10; // Teensy analog pin for detector

// INTERNAL VARIABLES, COUNTERS, ETC.
unsigned long start1,start1orig,end1, calstart1orig, calend1, start5, start6, start7, end5;
unsigned long pulselengthoncheck, pulselengthoffcheck, pulsecyclescheck, totaltimecheck, caltotaltimecheck;
float data1f, data2f, data3f, data4f, irtinvalue, irtapevalue, rebeltapevalue, rebeltinvalue, irsamplevalue, rebelsamplevalue, baselineir, dataaverage, caldataaverage1, caldataaverage2, rebelslope, irslope, baseline = 0;
char filenamedir[13];
char filenamelocal[13];
int data1, data2, data3, data4, caldata1, caldata2, caldata3, caldata4, analogresolutionvalue;
int i = 0; // Used as a counter
int j = 0; // Used as a counter
int k = 0; // Used as a counter
int* caldatatape;
int* caldatatin;
int* caldatasample;
int* datasample;
int val = 0;
int cal = 0;
int cal2 = 0;
int cal3 = 0;
int val2 = 0;
int flag = 0;
int flag2 = 0;
int keypress = 0;


void setup() {

  delay(3000); // give operator time to turn on the serial monitor and the capacitor to charge

  Serial.begin(38400); // set baud rate for serial communication
  pinMode(measuringlight1, OUTPUT); // set pin to output
  pinMode(saturatinglight1, OUTPUT); // set pin to output
  pinMode(calibratinglight1, OUTPUT); // set pin to output  
  analogReadAveraging(1); // set analog averaging to 1 (ie ADC takes only one signal, takes ~3u
  pinMode(detector1, EXTERNAL);
  analogReadRes(analogresolution);
  analogresolutionvalue = pow(2,analogresolution); // calculate the max analogread value of the resolution setting
//  Serial.print(analogresolutionvalue);
  

  if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) sd.initErrorHalt(); // Set SD Card to full speed ahead!

  //PRINT SD CARD MEMORY INFORMATION
  PgmPrint("Free RAM: ");
  Serial.println(FreeRam());  

  PgmPrint("Volume is FAT");
  Serial.println(sd.vol()->fatType(), DEC);
  Serial.println();

  // list file in root with date and size
  PgmPrintln("Files found in root:");
  sd.ls(LS_DATE | LS_SIZE);
  Serial.println();

  // Recursive list of all directories
  PgmPrintln("Files found in all dirs:");
  sd.ls(LS_R);

  // PRINT OTHER INFORMATION
  printout();

  //CREATE NEW DIRECTORY OR DATA FILE, OR APPEND EXISTING  
  //If the primary file name does not yet exist as a file or folder, then create the folder and store all subsequent files (ie file-m.csv, file.c.csv...) in that folder
  //... if the primary file name does already exist as a file or folder, then create a new folder with that name and store subsequent files in it.

  strcpy(filenamedir, filename);
  Serial.println(sd.exists(filenamedir));

  if (sd.exists(filenamedir) == 1) {
    Serial.print("Press 1 to append the existing data files in directory ");
    Serial.print(filenamedir);
    Serial.println(" or press 2 to create a new directory.");    

    while (cal3 != 1 && cal3 !=2) {
      cal3 = Serial.read()-48; // for some crazy reason, inputting 0 in the Serial Monitor yields 48 on the Teensy itself, so this just shifts everything by 48 so that when you input X, it's saved in Teensy as X.
      if (cal3 == 2) {
        for (j = 0; j<100; j++) {
          filenamedir[0] = j/10 + '0';
          filenamedir[1] = j%10 + '0';
          if (sd.exists(filenamedir) == 0) {
            break;
          }
        }
        Serial.print("OK - creating a new directory called:  ");
        Serial.println(filenamedir);
        SdFile sub1;
        sub1.makeDir(sd.vwd(), filenamedir); // Creating new folder
        Serial.print(".  Data files are saved in that directory as ");
        Serial.print(filename);
        Serial.println(" plus -(subroutine extension letter).CSV for each subroutine.");
      }
      else if (cal3 == 1) {
        for (j = 0; j<100; j++) {
          filenamedir[0] = j/10 + '0';
          filenamedir[1] = j%10 + '0';
          if (sd.exists(filenamedir) == 0) {
            filenamedir[0] = (j-1)/10 + '0';
            filenamedir[1] = (j-1)%10 + '0';
            break;
          }
        }
        Serial.print("Ok - appending most recently used files located in directory ");
        Serial.println(filenamedir);
        break;
      }
    }
  }
  else if (sd.exists(filenamedir) == 0) {

    Serial.print("OK - creating a new directory called ");
    Serial.print(filenamedir);
    Serial.print(".  Data files are saved in that directory as ");
    Serial.print(filenamedir);
    Serial.println(" plus -(subroutine extension letter).CSV for each subroutine.");
    SdFile sub1;
    sub1.makeDir(sd.vwd(), filenamedir); // Creating new folder

  }



  // MAIN PROGRAM
  Serial.println("Would you like to run a calibration? -- type 1 for yes or 2 for no");
  while (cal == 0 | flag == 0) {
    cal = Serial.read()-48; //
    if (cal == 1) {
      delay(50);
      Serial.println("Ok - calibrating and then running normal protocal");
      Serial.println("Place the shiny side of the calibration piece face up in the photosynq, and close the lid.");
      Serial.println("When you're done, press any key to continue");
      Serial.flush();
      while (Serial.read() <= 0) {
      }

      Serial.println("Thanks - calibrating...");
      basicfluor(); // order matters here - make sure that basicfluor() is BEFORE calibrationtin()!
      calibrationtin();

      Serial.println("Now place the black side of the calibration piece face up in the photosynq, and close the lid.");
      Serial.println("When you're done, press any key to continue");
      Serial.flush();
      while (Serial.read() <= 0) {
      }

      Serial.println("Thanks - calibrating...");
      basicfluor(); // order matters here - make sure that basicfluor() is BEFORE calibrationtape()!
      calibrationtape();
      Serial.println("Calibration finished!");
      cal = 2;
    }
    if (cal == 2) {
      delay(50);
      Serial.println("Proceeding with normal measurement protocol");
      Serial.println("Place the sample in front of the light guide and press any key to continue");
      Serial.flush();
      while (Serial.read() <= 0) {
      }
      Serial.println("Thanks - running protocol...");
      while (1) {
        calibrationsample(); // order matters here - calibrationsample() must be BEFORE basicflour()
        basicfluor();
        calculations();
        Serial.println("");        
        Serial.println("Press any key to take another measurement, or press reset button to calibrate or start a new file");
        while (Serial.read() <= 0) {
        }
      }      
    }

    else if (cal > 2) {
      delay(50);
      Serial.println("That's not a 1 or a 2!  Please enter either 1 for yes, or 2 for no");
      cal = 0;
    }
  }
}

void calibrationtin() {

  // CALIBRATION TIN
  // Flash calibrating light to determine how reflective the sample is to ~850nm light with the tin foil as the sample (low reflectance).  This has been tested with Luxeon Rebel Orange as measuring pulse.

  caldatatin = (int*)malloc(calpulsecycles*sizeof(int)); // create the array of proper size to save one value for all each ON/OFF cycle
  noInterrupts(); // turn off interrupts to reduce interference from other calls

    start1 = micros();
  for (i=0;i<calpulsecycles;i++) {
    digitalWriteFast(calibratinglight1, HIGH);
    caldata1 = analogRead(detector1); // NOTE! user needs to set the number of analogReads() per ON cycle - add more for more analogReads()
    caldata2 = analogRead(detector1);
    caldata3 = analogRead(detector1);
    caldata4 = analogRead(detector1);
    start1=start1+pulselengthon;
    while (micros()<start1) {
    }
    start1=start1+pulselengthoff;
    digitalWriteFast(calibratinglight1, LOW); 
    caldatatin[i] = (caldata1+caldata2+caldata3+caldata4); // data is stored as an array, size = calpulsecycles.  Each point in the array is the average of the data points from an ON cycle.  Averaging is done later.
    //    file.print(caldatatin[i]); // (~10us) store data point on the SD Card
    //    file.println(""); // move to next line in .csv (creates new column in spreadsheet) 
    while (micros()<start1) {
    } 
  }

  interrupts();
  //  file.print(","); // move to next line in .csv (creates new column in spreadsheet) 
  //  free(caldatatin); // release the memory allocated for the data

  for (i=0;i<calpulsecycles;i++) {
    irtinvalue += caldatatin[i]; // totals all of the analogReads taken
  }
  Serial.println(irtinvalue);  
  irtinvalue = (float) irtinvalue;
  irtinvalue = (irtinvalue / (calpulsecycles*4)); //  Divide by the total number of samples to get the average reading during the calibration - NOTE! The divisor here needs to be equal to the number of analogReads performed above!!!! 
  rebeltinvalue = rebelsamplevalue;
  rebelsamplevalue = (int) rebelsamplevalue; // reset rebelsamplevalue to integer for future operations
  for (i=0;i<calpulsecycles;i++) { // Print the results!
    Serial.print(caldatatin[i]);
    Serial.print(", ");
    Serial.print(" ");  
  }
  Serial.println(" ");    
  Serial.print("the baseline high reflectace value from calibration: ");
  Serial.println(irtinvalue, 7);
  Serial.print("The last 4 data points from the calibration: ");  
  Serial.print(caldata1);
  Serial.print(", ");
  Serial.print(caldata2);
  Serial.print(", ");
  Serial.print(caldata3);
  Serial.print(", ");
  Serial.println(caldata4);
}

void calibrationtape() {

  // CALIBRATION TAPE
  // Flash calibrating light to determine how reflective the sample is to ~850nm light with the black tape as the sample (low reflectance).  This has been tested with Luxeon Rebel Orange as measuring pulse.


  caldatatape = (int*)malloc(calpulsecycles*sizeof(int)); // create the array of proper size to save one value for all each ON/OFF cycle
  noInterrupts(); // turn off interrupts to reduce interference from other calls

    start1 = micros();
  for (i=0;i<calpulsecycles;i++) {
    digitalWriteFast(calibratinglight1, HIGH);
    caldata1 = analogRead(detector1); // NOTE! user needs to set the number of analogReads() per ON cycle - add more for more analogReads()
    caldata2 = analogRead(detector1);
    caldata3 = analogRead(detector1);
    caldata4 = analogRead(detector1);
    start1=start1+pulselengthon;
    while (micros()<start1) {
    }
    start1=start1+pulselengthoff;
    digitalWriteFast(calibratinglight1, LOW);
    caldatatape[i] = (caldata1+caldata2+caldata3+caldata4); // data is stored as an array, size = calpulsecycles.  Each point in the array is the average of the data points from an ON cycle.  Averaging is done later.
    //    file.print(caldatatape[i]); // (~10us) store data point on the SD Card
    //    file.println(""); // move to next line in .csv (creates new column in spreadsheet) 
    while (micros()<start1) {
    } 
  }

  interrupts();
  //  file.print(","); // move to next line in .csv (creates new column in spreadsheet) 
  //  free(caldatatape); // release the memory allocated for the data

  for (i=0;i<calpulsecycles;i++) {
    irtapevalue += caldatatape[i]; // totals all of the analogReads taken
  }
  Serial.println(irtapevalue);
  irtapevalue = (float) irtapevalue;
  irtapevalue = (irtapevalue / (calpulsecycles*4)); //  Divide by the total number of samples to get the average reading during the calibration - NOTE! The divisor here needs to be equal to the number of analogReads performed above!!!! 
  rebeltapevalue = rebelsamplevalue;
  rebelsamplevalue = (int) rebelsamplevalue; // reset rebelsamplevalue to integer for future operations 
  for (i=0;i<calpulsecycles;i++) { // Print the results!
    Serial.print(caldatatape[i]);
    Serial.print(", ");
    Serial.print(" ");  
  }
  Serial.println(" ");    
  Serial.print("the baseline low reflectace value from calibration:  ");
  Serial.println(irtapevalue, 7);
  Serial.print("The last 4 data points from the calibration: ");  
  Serial.print(caldata1);
  Serial.print(", ");
  Serial.print(caldata2);
  Serial.print(", ");
  Serial.print(caldata3);
  Serial.print(", ");
  Serial.println(caldata4);


  //CALCULATE AND SAVE CALIBRATION DATA TO SD CARD
  rebelslope = rebeltinvalue - rebeltapevalue;
  irslope = irtinvalue - irtapevalue;

  SdFile file;

  file.open("RTAPE.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(rebeltapevalue, 8);
  file.close();

  file.open("RTIN.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(rebeltinvalue, 8);
  file.close();

  file.open("ITAPE.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(irtapevalue, 8);
  file.close();

  file.open("ITIN.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(irtinvalue, 8);
  file.close();

  file.open("RSLOP.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(rebelslope, 8);
  file.close();

  file.open("ISLOP.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(irslope, 8);
  file.close();

  Serial.print("Baseline value: ");  
  Serial.println(baseline, 8);
  Serial.print("IR slope: ");  
  Serial.println(irslope, 8);
  Serial.print("rebel slope: ");  
  Serial.println(rebelslope, 8);
  Serial.print("ir tin value: ");  
  Serial.println(irtinvalue, 8);
  Serial.print("ir tape value: ");  
  Serial.println(irtapevalue, 8);
  Serial.print("rebel tin value: ");  
  Serial.println(rebeltinvalue, 8);
  Serial.print("rebel tape value: ");  
  Serial.println(rebeltapevalue, 8);

}

void calibrationsample() {

  // CALIBRATION SAMPLE
  // Flash calibrating light to determine how reflective the sample is to ~850nm light with the actual sample in place.  This has been tested with Luxeon Rebel Orange as measuring pulse.

  caldatasample = (int*)malloc(calpulsecycles*sizeof(int)); // create the array of proper size to save one value for all each ON/OFF cycle
  noInterrupts(); // turn off interrupts to reduce interference from other calls

    calstart1orig = millis();
  start1 = micros();
  for (i=0;i<calpulsecycles;i++) {
    digitalWriteFast(calibratinglight1, HIGH);
    caldata1 = analogRead(detector1); // NOTE! user needs to set the number of analogReads() per ON cycle - add more for more analogReads()
    caldata2 = analogRead(detector1);
    caldata3 = analogRead(detector1);
    caldata4 = analogRead(detector1);
    start1=start1+pulselengthon;
    while (micros()<start1) {
    }
    start1=start1+pulselengthoff;
    digitalWriteFast(calibratinglight1, LOW); 
    caldatasample[i] = (caldata1+caldata2+caldata3+caldata4); // data is stored as an array, size = calpulsecycles.  Each point in the array is the average of the data points from an ON cycle.  Averaging is done later.
    //    file.print(caldatasample[i]); // (~10us) store data point on the SD Card
    //    file.print(","); // (~10us) store data point on the SD Card   
    while (micros()<start1) {
    } 
  }
  calend1 = micros();

  interrupts();
  //  file.println(""); // move to next line in .csv (creates new column in spreadsheet) 
  //  free(caldatasample); // release the memory allocated for the data

  for (i=0;i<calpulsecycles;i++) {
    irsamplevalue += caldatasample[i]; // totals all of the analogReads taken
  }
  //  Serial.println(irsamplevalue);
  irsamplevalue = (float) irsamplevalue;
  irsamplevalue = (irsamplevalue / (calpulsecycles*4)); //  Divide by the total number of samples to get the average reading during the calibration - NOTE! The divisor here needs to be equal to the number of analogReads performed above!!!! 
  for (i=0;i<calpulsecycles;i++) { // Print the results!
    Serial.print(caldatasample[i]);
    Serial.print(", ");
    Serial.print(" ");  
  }
  Serial.println(" ");    
  Serial.print("the baseline sample reflectace value from calibration:  ");
  Serial.println(irsamplevalue, 7);

  // RETRIEVE STORED BASELINE VALUES AND CALCULATE BASELINE VALUE

  SdFile file;
  int c;
  char local[12];

  Serial.println("Calibration values pulled from SD Card");

  file.open("ISLOP.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  irslope = strtod(local, NULL);
  Serial.println(irslope, 8);
  file.close();

  file.open("RSLOP.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  rebelslope = strtod(local, NULL);
  Serial.println(rebelslope, 8);
  file.close();

  file.open("ITIN.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  irtinvalue = strtod(local, NULL);
  Serial.println(irtinvalue, 8);
  file.close();

  file.open("ITAPE.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  irtapevalue = strtod(local, NULL);
  Serial.println(irtapevalue, 8);
  file.close();

  file.open("RTIN.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  rebeltinvalue = strtod(local, NULL);
  Serial.println(rebeltinvalue, 8);
  file.close();

  file.open("RTAPE.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  rebeltapevalue = strtod(local, NULL);
  Serial.println(rebeltapevalue, 8);
  file.close();

  // CALCULATE BASELINE VALUE
  baseline = (rebelslope*(irsamplevalue-irtinvalue)/(irslope-rebeltinvalue));

}

void basicfluor() {
  // Flash the LED in a cycle with defined ON and OFF times, and take analogRead measurements as fast as possible on the ON cycle

  strcpy(filenamelocal,filename); // set the local filename variable with the extension for this subroutine.
  strcat(filenamelocal, "-I.CSV"); // NOTE! "filename" + calibration name extension (CAN ONLY BE A DASH (-) AND 1 LETTER!).  This is the name of file that this subroutine data is stored in
  SdFile sub1;
  sub1.open(filenamedir, O_READ);
  SdBaseFile* dir = &sub1;
  SdFile file;
  file.open(dir, filenamelocal, O_CREAT | O_WRITE | O_APPEND);
  strcpy(filenamelocal,filename); // reset the localfilename variable;

  datasample = (int*)malloc(pulsecycles*sizeof(int)); // create the array of proper size to save one value for all each ON/OFF cycle
  noInterrupts();

  start1orig = micros();
  file.print(start1orig); // first row shows the run time in milliseconds
  start1 = micros();
  for (i=0;i<pulsecycles;i++) {
    digitalWriteFast(measuringlight1, HIGH); 
    if (saturatingcycleon == i+1) {
      digitalWriteFast(saturatinglight1, HIGH);  // Turn saturating light on   
    }
    data1 = analogRead(detector1); // NOTE! user needs to set the number of analogReads() per ON cycle - add more for more analogReads()
    data2 = analogRead(detector1);
    data3 = analogRead(detector1);
    data4 = analogRead(detector1);
    start1=start1+pulselengthon;
    while (micros()<start1) {
    }
    start1=start1+pulselengthoff;
    digitalWriteFast(measuringlight1, LOW); // 
    if (saturatingcycleoff == i+1) {
    digitalWriteFast(saturatinglight1, LOW); // Turn saturating light off
    }
    datasample[i] = (data1+data2+data3+data4); // NOTE! ADJUST BASELINE MULTIPLIER BASED ON NUMBER OF DATA SAMPLED DURING MEASURING PULSE!  data is stored as an array, size = calpulsecycles.  Each point in the array is the average of the data points from an ON cycle.  Averaging is done later.
    //    start5 = micros();
    file.print(datasample[i]); // (~10us) store data point on the SD Card
    //    start6 = micros();
    file.print(","); // move to next line in .csv (creates new column in spreadsheet) 
    //    end5 = micros();
    while (micros()<start1) {
    } 
  }
  end1 = micros();
  interrupts();

  file.println(""); // move to next column
  file.close();

  free(datasample); // release the memory allocated for the data

  // TEST SD CARD WRITE SPEED
  //  Serial.print("Time required for 2 write's (sample, comma) to the SD Card: ");
  //  Serial.print(end5 - start5);
  //  Serial.print(", ");
  //  Serial.println(end5 - start6);

  for (i=0;i<pulsecycles;i++) {
    rebelsamplevalue += datasample[i]; // totals all of the analogReads taken
  }
  delay(50);
  Serial.print("THIS IS THE BASELINE: ");
  Serial.println(baseline);  
  rebelsamplevalue = (float) rebelsamplevalue; // create a float for rebelsamplevalue so it can be saved later
  rebelsamplevalue = (rebelsamplevalue / (pulsecycles*4)); //  NOTE!  Divide by the total number of samples to get the average reading during the calibration - NOTE! The divisor here needs to be equal to the number of analogReads performed above!!!! 
  Serial.print("Rebel sample value:  ");
  Serial.println(rebelsamplevalue);
  Serial.println("");  
  for (i=0;i<pulsecycles;i++) { // Print the results!
    Serial.print(datasample[i]);
    Serial.print(", ");
    Serial.print(" ");  
  }
  Serial.println("");

}

// USER PRINTOUT OF MAIN PARAMETERS
void printout() {

  Serial.println("");
  Serial.print("Pulse cycles during calibration                    ");
  Serial.println(calpulsecycles); // Number of times the "pulselengthon" and "pulselengthoff" cycle (on/off is 1 cycle) (maximum = 1000 until we can store the read data in an SD card or address the interrupts issue)
  Serial.print("Pulse cycles during fluorescence                   ");
  Serial.println(pulsecycles); // Number of times the "pulselengthon" and "pulselengthoff" cycle (on/off is 1 cycle) (maximum = 1000 until we can store the read data in an SD card or address the interrupts issue)
  Serial.print("Pulse cycle # when saturation pulse turns on       ");
  Serial.println(saturatingcycleon); //The cycle number in which the saturating light turns on (set = 0 for no saturating light)
  Serial.print("Pulse cycle # when saturation pulse turns off      ");
  Serial.println(saturatingcycleoff); //The cycle number in which the saturating light turns off
  Serial.print("Length of a single measuring pulse in us           ");
  Serial.println(pulselengthon); // Pulse LED on length in uS (minimum = 5us based on a single ~4us analogRead - +5us for each additional analogRead measurement in the pulse).
  Serial.print("Length of the wait time between pulses pulse in us ");
  Serial.println(pulselengthoff); // Pulse LED on length in uS (minimum = 5us based on a single ~4us analogRead - +5us for each additional analogRead measurement in the pulse).
  Serial.print("Base data file name                                ");
  Serial.println(filename);
  Serial.println("");
  Serial.print("Teensy pin for measuring light      ");
  Serial.println(measuringlight1);
  Serial.print("Teensy pin for saturating light     ");
  Serial.println(saturatinglight1);
  Serial.print("Teensy pin for calibration light    ");
  Serial.println(calibratinglight1);
  Serial.print("Teensy pin for detector             ");
  Serial.println(detector1);
  Serial.println("");

}

// USER PRINTOUT OF TEST RESULTS
void calculations() {

  Serial.println("the following two values");
  Serial.println(datasample[5]);
  Serial.println(analogresolutionvalue);
  Serial.println("");
  Serial.println("DATA - RAW ANALOG VALUES (divide by number of samples taken during measurement pulse to get actual value");
  for (i=0;i<pulsecycles;i++) { // Average each of the data points from the sample
    datasample[i] = 1000000*((reference*datasample[i])/(analogresolutionvalue*4)); // NOTE! The divisor here needs to be equal to the number of analogReads performed above, and the other number is the voltage reference (INTERNAL = 1.1, normal = 3.3)
  }
  for (i=0;i<pulsecycles;i++) { // Print the results!
    Serial.print(datasample[i], 8);
    Serial.println("");
  }
  Serial.println("");

  Serial.println("");
  Serial.println("ALL DATA IN CURRENT DIRECTORY - BASELINE ADJUSTED VALUES");
  int16_t c;
  SdFile sub1;
  sub1.open(filenamedir, O_READ);
  SdBaseFile* dir = &sub1;
  SdFile file;
  strcpy(filenamelocal,filename); // set the local filename variable with the extension for this subroutine.
  strcat(filenamelocal, "-I.CSV"); // NOTE! Make sure this matches the local filename of the output data you'd like to display through Serial.
  file.open(dir, filenamelocal, O_READ);
  while ((c = file.read()) > 0) Serial.write((char)c); // copy data to the serial port
  strcpy(filenamelocal,filename); // reset the localfilename variable;

  totaltimecheck = end1 - start1orig;
  caltotaltimecheck = calend1 - calstart1orig;

  Serial.println("");
  Serial.print("Size of the baseline:  ");
  Serial.println(baseline, 8);

  totaltimecheck = end1 - start1orig;
  caltotaltimecheck = calend1 - calstart1orig;

  Serial.println("");
  Serial.println("GENERAL INFORMATION");
  Serial.println("");

  Serial.print("# of pulse cycles:  ");
  Serial.println(pulsecycles);

  Serial.print("total run length (measuring pulses):  ");
  Serial.println(totaltimecheck);

  Serial.print("total run length (calibration pulses):  ");
  Serial.println(caltotaltimecheck);

  Serial.print("value of i:  ");
  Serial.println(i);

  Serial.println("");
  Serial.println("CALIBRATION DATA");
  Serial.println("");


  Serial.print("The baseline from the sample is:  ");
  Serial.println(baseline);
  Serial.print("The calibration value using the reflective side for the calibration LED and measuring LED are:  ");
  Serial.print(rebeltinvalue);
  Serial.println(irtinvalue);
  Serial.print("The calibration value using the black side for the calibration LED and measuring LED are:  ");
  Serial.print(rebeltapevalue);
  Serial.println(irtapevalue);

  delay(50);

}

void loop(){
}





// GREG'S NOTES

// TROUBLESHOOTING:
// WHY!!!!! when I try to refer to another char when naming a file to O_APPEND, O_CREAT, or O_WRITE the program file.open() fails to return anything
// program views upper and lower case the same in the filename
// you can't do two separate O_CREAT followed by another file.open O_WRITE - you have to have them in the same line like this   file.open(name, O_CREAT | O_WRITE);
// you have to have the char name = "" in void Setup() - you can't declare it globally... why not?  You can't declare another global char and set it equal to the local version... annoying
// Char name itself cannot exceed 12 characters total for some reason, even if you declare it to be longer using [value].  If it is >12 char - it simply will not save to the SD Card
// I can't use variables created in void setup() in any other void sections... I have to create them as global variables... however, if I create a char as a global variable, it cannot be used
// ... as the name of a file.

// TESTING
// Try to write 80k file sizes (the number 42 40k times) and save the files.  Check the files and make sure all of the data is there

// NOTES:
// interrupts (in the form of cli() and sei() OR the standard arduino interrupts / noInterrupts) seems to make no difference in the result... though the Interrupts call itself was better than sei()
// based on looking at the actual oscilliscope but not much
// Adding to a String variable using the =+ or the string.concat() version of the string function takes about 8us!
// BUT, setting a simple int data variable equal to our analog read, that takes no time at all.
// when concatenating a string, this is slowest -   data = data + data1; .  It's better to call the concat function once and concatenate multiple things at a time within it, rather than making multiple calls.
// Even for the very quick pulses when using WASP method or other really short methods, we should be able to use the ADC to get 1us timed flashes and then just run analogRead as fast as possible (~3us).  
// Then, we can drop any of the values which are clearly too low (this should be obvious on a graph)... or perhaps we can speed up our analogRead?

// IMPROVEMENTS:
// Even just getting rid of second string concatenation to add the comma (data += ",") would allow us to shorten the OFF time minimum down to 10us.
// IF you can search saved files and find a calibration value which has the same filename AND same resistor value AND it's been more than X time since last calibration
// THEN ask user to calibrate (via Serial port for now)
 
Have you looked at any of the RTOS's? I use FreeRTOS_ARM and it has some simple examples that help with the analog reads and sd card saves that might be of some use.
 
Nice progress.

If I read it correctly, you could do a lot more tests and write each result to a buffer. Then write the buffer to the sdcard after they are all done. For that you can read SdFat library examples. Particularly RawWrite.ino.

I have timer interrupts working, but I'm afraid my code is not very generalizable. I get about 100kHz. I'm going to pull the 2 or 3 functions that I think are re-usable and generalize them a bit and post those in the next day or two.

Are you using an external voltage reference?
 
Sorry it's taken me a while to respond, I must have missed the last few comments in my inbox:

duff - an RTOS is probably the most efficient solution for high speed, but I think it takes a lot away from the usability and modability of the system by "regular" users. Ideally, we would like anyone who's a decent arduino programmer to be able to take this unit, create a new protocol, upload it and run it relatively quickly. That's my assumption... but I'll look into it more, perhaps it's easier than I'm thinking it would be.

linuxgeek - I managed to get the timer working as well (using loglow's PITimer), and it definitely reduced jitter which is great and simplified the code. I was having some issues sending data to the SD card during a timer call... it seemed to lock up the program, but I think I got it figured out (I'm going to work on it again this week). I'll reconfirm that it's all working and repost the code here - I'd love to see your code to compare as well.

We do have an external voltage reference, but it's not a good one - just a DC power supply with a resistor at 1V. I know we need a better one, but Robert is away for a couple weeks and will work on it when he gets back. Because of the voltage reference quality, and the quality of the power supply via the USB, and the fact that there's all kinds of wires running into the teensy which adds lots of noise our signal isn't nearly as clean as I know it could be. We're working on making a well designed breakout board to minimize the noise, but it'll take a few weeks.

This week we're hoping to run our first experiment using algae bioreactors - hopefully that'll have some interesting data.
 
Have you tried?:
analogReference(INTERNAL);

It cleaned up my ADC quite a bit. I mostly don't need an external reference after using that.

I just started altering my code & it's not useable right now. If I don't get it back up by tomorrow, I'll extract the meaningful bits.
 
Yes, I've done that but it only helped some. I have comparisons between the benchtop unit (called the Ideaspec) and the Teensy-based unit, and it's a very big different quality-wise even with the internal analog reference. Some of the noise I can get rid of, for example, by just turning off the Serial Monitor (which indicates it's coming from messy power, which travels along the USB cord or so I assume), but I think the majority comes from the analog reference.
 
Ok, try this.

ByteBuffer32bit goes in your libraries. And then run the sketch & open the Serial Monitor. Data will be written to the SD card.
Let me know if something isn't clear.

View attachment samplerUpload.zip

Also, you might want to try running off batteries, and avoid the USB electrical connection.
 
Another thing that might be good since you don't really need to be sampling all the time, just when you are giving pulses...

Stop the interrupt sampler when you are writing to the sdcard, then resume once it's done. That might minimize power draws and inconsistent latencies.
You could also reject samples that have an inconsistent elapsed microseconds from pulse to ADC capture.

In other places, people have suggested using batteries with a fast and a slow capacitor to steady the power.
 
So I was having some issues recreating the signal processing board that Robert had made, so it was taking me a few days just to get the unit back up and running. But I finally did, and the signal looks great! Actually, as you suggested, one of the biggest improvements came from moving to a battery power supply (+-9V) - that really smoothed out the signal. We were getting big oscillations in the signal when the LED switched on (see https://lh3.googleusercontent.com/-...dcIf_Uxw/w913-h685-no/IMG_20130521_114733.jpg for example), and that was reduced significantly when I switched to the +-9V battery - see below for an image which compares a pulse width modulated fluorescence from the same sample, before and after.

Selection_020.png

There's a little more hardware stuff to do to optimize, and then we should have a truly cordless unit, which is exciting.

This morning I'm going to work a little more on software and I'll take a look at your code, linuxgeek - thanks!
 
Awesome - just got a chance to look at it... and I'm confused about one thing - loglow produced two different libraries for timers, one is PITimer and the other is IntervalTimer... is there a difference? IntervalTimer says it's based on PITimer but I can't tell that it added or changed functionality... any ideas here?

Ok - I think I see what you did - the basic idea is the same as what I did (using the timer) but you added a lot of functionality with the buffer (filling and dumping) than I did. In mine, I saved the data to an dynamically created array each time, then shipped the contents of the array to the SD card when I had the time during the program, then released the array when the cycle starts over. The max array size is about 1500, so I can't exceed that before dumping it, but it works. I set up 3 timers - one which turns the LED on, one which turns the LED off, and one which times the entire run. Since I'm not using the Tone functionality, I suppose I still have one timer left.

Code:
/*
DESCRIPTION:
 This file is used to measure pulse modulated fluorescence (PMF) using a saturating pulse and a measuring pulse.  The measuring pulse LED (for example, Rebel Luxeon Orange) can itself produce some infra-red
 fluorescence which is detected by the detector.  This signal causes the detector response to be higher than expected which causes bias in the results.  So, in order to account for this added infra-red, we 
 perform a calibration.  The calibration is a short set of pulses which occur at the beginning of the program from an infra-red LED (810nm).  The detector response from these flashes indicates how "reflective"
 the sample is to infra-red light in the range produced by our measuring LED.  We can create a calibration curve by measuring a very reflective sample (like tin foil) and a very non-reflective sample (like
 black electrical tape) with both the calibrating LED and the measuring LED.  This curve tells us when we see detector response X for the calibrating LED, that correspond to a baseline response of Y for the
 measuring LED.  Once we have this calibration curve, when we measure an actual sample (like a plant leaf) we automatically calculate and remove the reflected IR produced by the measuring pulse.  Vioala!
 
 The LED on/off cycles are taken care of by using interval timers.  One timer turns the measuring pulse on (and takes a measurement using analogRead()), one timer turns it off, and one timer controls the length 
 of the whole run.  
 
 To run this file, please make sure you have the following libraries installed: <SdFat.h>, <SdFatUtil.h>, <Time.h>, <Wire.h>, <DS1307RTC.h>, <PITimer.h>
 
 Using Arduino 1.0.3 w/ Teensyduino installed downloaded from http://www.pjrc.com/teensy/td_download.html .   
 */

// NOTES FOR USER
// When changing protocols, makes sure to change the name of the saved file (ie data-I.csv) so you know what it is.
// Max file name size is 12 TOTAL characters (8 characters + .csv extension).  Program does not distinguish between upper and lower case
// Files have a basename (like ALGAE), and then for each subroutine an extension is added (like ALGAE-I) to indicate from which subroutine the data came.  
// When you create a new experiment, you save the data (ALGAE-I) in a folder.  If you want stop and restart but save data to the same file, you may append that file.
// Otherwise, you can create a new data file (like ALGAE-I) file an a different folder, which will be named 01GAE, 02GAE, 03GAE and so on.
// Calibration is performed to create an accurate reflectance in the sample using a 850nm LED.  You can find a full explanation of the calibration at https://opendesignengine.net/documents/14
// A new calibration should be performed when sample conditions have changed (a new sample is used, the same sample is used in a different position)
// The previous calibration value is saved in the SD card, so if no calibration is performed the most recently saved calibration value will be used!
// See KEY VARIABLES USED IN THE PROTOCOL below to change the measurement pulse cycles, pulse lengths, saturation pulses, etc.
// Note that the output values minimum and maximum are dependent on the analog resolution.  From them, you can calculate the actual current through the detector.
// ... So - at 10 bit resolution, min = 0, max = 10^2 = 1023; 16 bit resolution, min = 0, max = 16^2 = 65535; etc.
// ... To calculate actual voltage on the analog pin: 3.3*((measured value) / (max value based on analog resolution)).
// If you want to dig in and change values in this file, search for "NOTE!".  These areas provide key information that you will need to make changes.
// Pin A10 and A11 are 16 bit enabed with some added coding in C - the other pins cannot achieve 16 bit resolution.
// Real Time clock - includes sync to real time clock in ASCII 10 digit format and printed time with each sample (ie T144334434)

// NOTES:
// Chris needs to add an experiment start time stamp for setTime() at beginning of program
// time format: %m/%d/%Y %H:%M:%S

// SPECS USING THIS METHOD: 
// Timing of Measuring pulse and Saturation pulse is within 500ns.  Peak to Peak variability, ON and OFF length variability all is <1us.  Total measurement variability = 0 to +2us (regardless of how many cycles)

// SD CARD ENABLE AND SET
#include <SdFat.h>
#include <SdFatUtil.h> // use functions to print strings from flash memory
#include <Time.h>   // enable real time clock library
#include <Wire.h>
#include <DS1307RTC.h>  // a basic DS1307 library that returns time as a atime_t
#include <PITimer.h>

const uint8_t SD_CHIP_SELECT = SS;

SdFat sd;
#define error(s) sd.errorHalt_P(PSTR(s))  // store error strings in flash to save RAM

// KEY VARIABLES USED IN THE PROTOCOL
int measurements = 8; // # of measurements per pulse (min 1 measurement per 6us pulselengthon)
unsigned long pulselengthon = 50; // pulse length in us... minimum = 6us
float cyclelength = .01; // in seconds... minimum = pulselengthon + 7.5us
unsigned long starttimer0, starttimer1, starttimer2;
float runlength = 2; // in seconds... minimum = cyclelength
char filename[13] = "ALGAE"; // Base filename used for data files and directories on the SD Card
const char* basicfluorending = "-I.CSV"; // filename ending for the basicfluor subroutine - just make sure it's no more than 6 digits total including the .CSV
int saturatingcycleon = 50; //The cycle number in which the saturating light turns on (set = 0 for no saturating light)
int saturatingcycleoff = 125; //The cycle number in which the saturating light turns off

// KEY CALIBRATION VARIABLES
unsigned long calpulsecycles = 50; // Number of times the "pulselengthon" and "pulselengthoff" cycle during calibration (on/off is 1 cycle)
// data for measuring and saturating pulses --> to calculate total time=pulsecycles*(pulselengthon + pulselengthoff)
unsigned long calpulselengthon = 30; // Pulse LED on length for calibration in uS (minimum = 5us based on a single ~4us analogRead - +5us for each additional analogRead measurement in the pulse).
unsigned long calpulselengthoff = 49970; // Pulse LED off length for calibration in uS (minimum = 20us + any additional operations which you may want to call during that time).
float reference = 1.2; // The reference (AREF) supplied to the ADC - currently set to INTERNAL = 1.2V
int analogresolution = 16; // Set the resolution of the analog to digital converter (max 16 bit, 13 bit usable)  
int measuringlight1 = 3; // Teensy pin for measuring light
int saturatinglight1 = 4; // Teensy pin for saturating light
int calibratinglight1 = 2; // Teensy pin for calibrating light
int detector1 = A10; // Teensy analog pin for detector

// INTERNAL VARIABLES, COUNTERS, ETC.
unsigned long start1,start1orig,end1, calstart1orig, calend1, start5, start6, start7, end5;
unsigned long pulselengthoncheck, pulselengthoffcheck, pulsecyclescheck, totaltimecheck, caltotaltimecheck;
float data1f, data2f, data3f, data4f, irtinvalue, irtapevalue, rebeltapevalue, rebeltinvalue, irsamplevalue, rebelsamplevalue, baselineir, dataaverage, caldataaverage1, caldataaverage2, rebelslope, irslope, baseline = 0;
char filenamedir[13];
char filenamelocal[13];
int data1=0, data2=0, data3=0, data4=0, data5=0, data6=0, data7=0, data8=0, data9=0, data10=0, caldata1, caldata2, caldata3, caldata4, analogresolutionvalue;
int i = 0; // Used as a counter
int j = 0; // Used as a counter
int k = 0; // Used as a counter
int z = 0; // Used as a counter
int* caldatatape;
int* caldatatin;
int* caldatasample;
int* datasample;
int* rebeldatasample;
int val = 0;
int cal = 0;
int cal2 = 0;
int cal3 = 0;  
int val2 = 0;
int flag = 0;
int flag2 = 0;
int keypress = 0;
SdFile file;
SdFile sub1;
SdBaseFile* dir = &sub1;

void setup() {

  delay(3000); // give operator time to turn on the serial monitor and the capacitor to charge
  Serial.begin(38400); // set baud rate for serial communication

  // SET INTERRUPT TIMER FOR LED CONTROL AND DETECTOR MEASUREMENTS
  PITimer0.period(cyclelength); // Distance between pulses.  Set in Seconds.
  PITimer1.period(cyclelength); // set == to PITimer0
  PITimer2.period(runlength);

  // SET SYSTEM TIME IN ASCII "T" PLUS 10 DIGITS
  setSyncProvider(Teensy3Clock.get);
  while (!Serial);  // Wait for Arduino Serial Monitor to open
  if(timeStatus()!= timeSet) 
    Serial.println("Unable to sync with the RTC");
  else
    Serial.println("RTC has set the system time"); // NOTE! Chris enter experiment start time stamp here

  //  for (i=0;i<15;i++) { 
  Serial.println("Please type in T followed by 10 digit ASCII time (eg 'T1231231231')");
  Serial.println("(if you make a mistake, restart teensy and reenter)");
  //  Serial.println(Serial.available());
  while (Serial.available()<11) {
    // c = Serial.read();
    //  Serial.print(Serial.available());
    //  Serial.print(",");
    //  Serial.println(Serial.peek());
    //  delay(500);
  }
  atime_t t = processSyncMessage();
  Serial.print("'T' plus ASCII 10 digit time: ");
  Serial.println(t);
  Serial.print("Serial buffer size: ");
  Serial.println(Serial.available());
  setTime(t);          
  Serial.print("UTC time: ");
  SerialPrintClock();  
  //  delay(3000);
  //  digitalClockDisplay();
  //  }
  Serial.println("");


  pinMode(measuringlight1, OUTPUT); // set pin to output
  pinMode(saturatinglight1, OUTPUT); // set pin to output
  pinMode(calibratinglight1, OUTPUT); // set pin to output  
  analogReadAveraging(1); // set analog averaging to 1 (ie ADC takes only one signal, takes ~3u
  pinMode(detector1, EXTERNAL);
  analogReadRes(analogresolution);
  analogresolutionvalue = pow(2,analogresolution); // calculate the max analogread value of the resolution setting

  if (!sd.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) sd.initErrorHalt(); // Set SD Card to full speed ahead!

  //PRINT SD CARD MEMORY INFORMATION
  PgmPrint("Free RAM: ");
  Serial.println(FreeRam());  

  PgmPrint("Volume is FAT");
  Serial.println(sd.vol()->fatType(), DEC);
  Serial.println();

  // list file in root with date and size
  PgmPrintln("Files found in root:");
  sd.ls(LS_DATE | LS_SIZE);
  Serial.println();

  // Recursive list of all directories
  PgmPrintln("Files found in all dirs:");
  sd.ls(LS_R);

  // PRINT OTHER INFORMATION
  printout();

  //CREATE NEW DIRECTORY OR DATA FILE, OR APPEND EXISTING  
  //If the primary file name does not yet exist as a file or folder, then create the folder and store all subsequent files (ie file-m.csv, file.c.csv...) in that folder
  //... if the primary file name does already exist as a file or folder, then create a new folder with that name and store subsequent files in it.

  strcpy(filenamedir, filename);
  Serial.println(sd.exists(filenamedir));

  if (sd.exists(filenamedir) == 1) {
    Serial.print("Press 1 to append the existing data files in directory ");
    Serial.print(filenamedir);
    Serial.println(" or press 2 to create a new directory.");    

    while (cal3 != 1 && cal3 !=2) {
      cal3 = Serial.read()-48; // for some crazy reason, inputting 0 in the Serial Monitor yields 48 on the Teensy itself, so this just shifts everything by 48 so that when you input X, it's saved in Teensy as X.
      if (cal3 == 2) {
        for (j = 0; j<100; j++) {
          filenamedir[0] = j/10 + '0';
          filenamedir[1] = j%10 + '0';
          if (sd.exists(filenamedir) == 0) {
            break;
          }
        }
        Serial.print("OK - creating a new directory called:  ");
        Serial.println(filenamedir);
        SdFile sub1;
        sub1.makeDir(sd.vwd(), filenamedir); // Creating new folder
        Serial.print(".  Data files are saved in that directory as ");
        Serial.print(filename);
        Serial.println(" plus -(subroutine extension letter).CSV for each subroutine.");
      }
      else if (cal3 == 1) {
        for (j = 0; j<100; j++) {
          filenamedir[0] = j/10 + '0';
          filenamedir[1] = j%10 + '0';
          if (sd.exists(filenamedir) == 0) {
            filenamedir[0] = (j-1)/10 + '0';
            filenamedir[1] = (j-1)%10 + '0';
            break;
          }
        }
        Serial.print("Ok - appending most recently used files located in directory ");
        Serial.println(filenamedir);
        break;
      }
    }
  }
  else if (sd.exists(filenamedir) == 0) {

    Serial.print("OK - creating a new directory called ");
    Serial.print(filenamedir);
    Serial.print(".  Data files are saved in that directory as ");
    Serial.print(filenamedir);
    Serial.println(" plus -(subroutine extension letter).CSV for each subroutine.");
    sub1.makeDir(sd.vwd(), filenamedir); // Creating new folder

  }



  // MAIN PROGRAM
  Serial.println("Would you like to run a calibration? -- type 1 for yes or 2 for no");
  while (cal == 0 | flag == 0) {
    cal = Serial.read()-48; //
    if (cal == 1) {
      delay(50);
      Serial.println("Ok - calibrating and then running normal protocal");
      Serial.println("Place the shiny side of the calibration piece face up in the photosynq, and close the lid.");
      Serial.println("When you're done, press any key to continue");
      Serial.flush();
      while (Serial.read() <= 0) {
      }

      Serial.println("Thanks - calibrating...");
      calibrationrebel(); // order matters here - make sure that calibrationrebel() is BEFORE calibrationtin()!
      calibrationtin();

      Serial.println("Now place the black side of the calibration piece face up in the photosynq, and close the lid.");
      Serial.println("When you're done, press any key to continue");
      Serial.flush();
      while (Serial.read() <= 0) {
      }

      Serial.println("Thanks - calibrating...");
      calibrationrebel(); // order matters here - make sure that calibrationrebel() is BEFORE calibrationtape()!
      calibrationtape();
      Serial.println("Calibration finished!");
      cal = 2;
    }
    if (cal == 2) {
      delay(50);
      Serial.println("Proceeding with normal measurement protocol");
      Serial.println("Place the sample in front of the light guide and press any key to continue");
      Serial.flush();
      while (Serial.read() <= 0) {
      }
      Serial.println("Thanks - running protocol...");
      while (1) {
        calibrationsample(); // order matters here - calibrationsample() must be BEFORE basicflour()
        basicfluor();
        calculations();
        Serial.println("");        
        Serial.println("Press any key to take another measurement, or press reset button to calibrate or start a new file");
        while (Serial.read() <= 0) {
        }
      }      
    }

    else if (cal > 2) {
      delay(50);
      Serial.println("That's not a 1 or a 2!  Please enter either 1 for yes, or 2 for no");
      cal = 0;
    }
  }
}


void calibrationrebel() {

  // CALIBRATION REBEL
  // Short pulses for calibration using the measuring LED (rebel orange)

  rebeldatasample = (int*)malloc(calpulsecycles*sizeof(int)); // create the array of proper size to save one value for all each ON/OFF cycle
  noInterrupts();

  start1orig = micros();
  start1 = micros();
  for (i=0;i<calpulsecycles;i++) {
    digitalWriteFast(measuringlight1, HIGH); 
    data1 = analogRead(detector1); 
    start1=start1+calpulselengthon;
    while (micros()<start1) {
    }
    start1=start1+calpulselengthoff;
    digitalWriteFast(measuringlight1, LOW); 
    rebeldatasample[i] = data1; 
    while (micros()<start1) {
    } 
  }
  end1 = micros();
  interrupts();

  free(rebeldatasample); // release the memory allocated for the data

  for (i=0;i<calpulsecycles;i++) {
    rebelsamplevalue += rebeldatasample[i]; // totals all of the analogReads taken
  }
  delay(50);

  rebelsamplevalue = (float) rebelsamplevalue; // create a float for rebelsamplevalue so it can be saved later
  rebelsamplevalue = (rebelsamplevalue / calpulsecycles);
  Serial.print("Rebel sample value:  ");
  Serial.println(rebelsamplevalue);
  Serial.println("");  
  for (i=0;i<calpulsecycles;i++) { // Print the results!
    Serial.print(rebeldatasample[i]);
    Serial.print(", ");
    Serial.print(" ");  
  }
  Serial.println("");
  Serial.print("see the data here:  ");  
  Serial.println(data1);
}


void calibrationtin() {

  // CALIBRATION TIN
  // Flash calibrating light to determine how reflective the sample is to ~850nm light with the tin foil as the sample (low reflectance).  This has been tested with Luxeon Rebel Orange as measuring pulse.

  caldatatin = (int*)malloc(calpulsecycles*sizeof(int)); // create the array of proper size to save one value for all each ON/OFF cycle
  noInterrupts(); // turn off interrupts to reduce interference from other calls

    start1 = micros();
  for (i=0;i<calpulsecycles;i++) {
    digitalWriteFast(calibratinglight1, HIGH);
    caldata1 = analogRead(detector1);
    start1=start1+calpulselengthon;
    while (micros()<start1) {
    }
    start1=start1+calpulselengthoff;
    digitalWriteFast(calibratinglight1, LOW); 
    caldatatin[i] = caldata1;  
    while (micros()<start1) {
    } 
  }

  interrupts();

  for (i=0;i<calpulsecycles;i++) {
    irtinvalue += caldatatin[i]; // totals all of the analogReads taken
  }
  Serial.println(irtinvalue);  
  irtinvalue = (float) irtinvalue;
  irtinvalue = (irtinvalue / calpulsecycles); //  Divide by the total number of samples to get the average reading during the calibration - NOTE! The divisor here needs to be equal to the number of analogReads performed above!!!! 
  rebeltinvalue = rebelsamplevalue;
  rebelsamplevalue = (int) rebelsamplevalue; // reset rebelsamplevalue to integer for future operations
  for (i=0;i<calpulsecycles;i++) { // Print the results!
    Serial.print(caldatatin[i]);
    Serial.print(", ");
    Serial.print(" ");  
  }
  Serial.println(" ");    
  Serial.print("the baseline high reflectace value from calibration: ");
  Serial.println(irtinvalue, 7);
  Serial.print("The last 4 data points from the calibration: ");  
  Serial.println(caldata1);
}

void calibrationtape() {

  // CALIBRATION TAPE
  // Flash calibrating light to determine how reflective the sample is to ~850nm light with the black tape as the sample (low reflectance).  This has been tested with Luxeon Rebel Orange as measuring pulse.

  caldatatape = (int*)malloc(calpulsecycles*sizeof(int)); // create the array of proper size to save one value for all each ON/OFF cycle
  noInterrupts(); // turn off interrupts to reduce interference from other calls

    start1 = micros();
  for (i=0;i<calpulsecycles;i++) {
    digitalWriteFast(calibratinglight1, HIGH);
    caldata1 = analogRead(detector1);
    start1=start1+calpulselengthon;
    while (micros()<start1) {
    }
    start1=start1+calpulselengthoff;
    digitalWriteFast(calibratinglight1, LOW);
    caldatatape[i] = caldata1; 
    while (micros()<start1) {
    } 
  }

  interrupts();

  for (i=0;i<calpulsecycles;i++) {
    irtapevalue += caldatatape[i]; // totals all of the analogReads taken
  }
  Serial.println(irtapevalue);
  irtapevalue = (float) irtapevalue;
  irtapevalue = (irtapevalue / calpulsecycles); 
  rebeltapevalue = rebelsamplevalue;
  rebelsamplevalue = (int) rebelsamplevalue; // reset rebelsamplevalue to integer for future operations 
  for (i=0;i<calpulsecycles;i++) { // Print the results!
    Serial.print(caldatatape[i]);
    Serial.print(", ");
    Serial.print(" ");  
  }
  Serial.println(" ");    
  Serial.print("the baseline low reflectace value from calibration:  ");
  Serial.println(irtapevalue, 7);
  Serial.print("The last 4 data points from the calibration: ");  
  Serial.println(caldata1);



  //CALCULATE AND SAVE CALIBRATION DATA TO SD CARD
  rebelslope = rebeltinvalue - rebeltapevalue;
  irslope = irtinvalue - irtapevalue;

  file.open("RTAPE.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(rebeltapevalue, 8);
  file.close();

  file.open("RTIN.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(rebeltinvalue, 8);
  file.close();

  file.open("ITAPE.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(irtapevalue, 8);
  file.close();

  file.open("ITIN.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(irtinvalue, 8);
  file.close();

  file.open("RSLOP.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(rebelslope, 8);
  file.close();

  file.open("ISLOP.CSV", O_CREAT | O_WRITE);
  file.seekSet(0);
  file.print(irslope, 8);
  file.close();

  Serial.print("IR slope: ");  
  Serial.println(irslope, 8);
  Serial.print("rebel slope: ");  
  Serial.println(rebelslope, 8);
  Serial.print("ir tin value: ");  
  Serial.println(irtinvalue, 8);
  Serial.print("ir tape value: ");  
  Serial.println(irtapevalue, 8);
  Serial.print("rebel tin value: ");  
  Serial.println(rebeltinvalue, 8);
  Serial.print("rebel tape value: ");  
  Serial.println(rebeltapevalue, 8);

}

void calibrationsample() {

  // CALIBRATION SAMPLE
  // Flash calibrating light to determine how reflective the sample is to ~850nm light with the actual sample in place.  This has been tested with Luxeon Rebel Orange as measuring pulse.

  caldatasample = (int*)malloc(calpulsecycles*sizeof(int)); // create the array of proper size to save one value for all each ON/OFF cycle
  noInterrupts(); // turn off interrupts to reduce interference from other calls

    calstart1orig = micros();
  start1 = micros();
  for (i=0;i<calpulsecycles;i++) {
    digitalWriteFast(calibratinglight1, HIGH);
    caldata1 = analogRead(detector1);
    start1=start1+calpulselengthon;
    while (micros()<start1) {
    }
    start1=start1+calpulselengthoff;
    digitalWriteFast(calibratinglight1, LOW); 
    caldatasample[i] = caldata1; 
    while (micros()<start1) {
    } 
  }
  calend1 = micros();

  interrupts();

  for (i=0;i<calpulsecycles;i++) {
    irsamplevalue += caldatasample[i]; // totals all of the analogReads taken
  }
  //  Serial.println(irsamplevalue);
  irsamplevalue = (float) irsamplevalue;
  irsamplevalue = (irsamplevalue / calpulsecycles); 
  for (i=0;i<calpulsecycles;i++) { // Print the results!
    Serial.print(caldatasample[i]);
    Serial.print(", ");
    Serial.print(" ");  
  }
  Serial.println(" ");    
  Serial.print("the baseline sample reflectace value from calibration:  ");
  Serial.println(irsamplevalue, 7);

  // RETRIEVE STORED TIN AND TAPE CALIBRATION VALUES AND CALCULATE BASELINE VALUE

  int c;
  char local[12];

  Serial.println("Calibration values pulled from SD Card");

  file.open("ISLOP.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  irslope = strtod(local, NULL);
  Serial.println(irslope, 8);
  file.close();

  file.open("RSLOP.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  rebelslope = strtod(local, NULL);
  Serial.println(rebelslope, 8);
  file.close();

  file.open("ITIN.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  irtinvalue = strtod(local, NULL);
  Serial.println(irtinvalue, 8);
  file.close();

  file.open("ITAPE.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  irtapevalue = strtod(local, NULL);
  Serial.println(irtapevalue, 8);
  file.close();

  file.open("RTIN.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  rebeltinvalue = strtod(local, NULL);
  Serial.println(rebeltinvalue, 8);
  file.close();

  file.open("RTAPE.CSV", O_READ);
  i = 0; // reset counter just in case
  while ((c = file.read()) > 0) {
    local[i] = (char) c;
    i++;
  }
  i = 0; // reset counter
  rebeltapevalue = strtod(local, NULL);
  Serial.println(rebeltapevalue, 8);

  file.close();
  sub1.close();

  // CALCULATE BASELINE VALUE
  baseline = (rebelslope*(irsamplevalue-irtinvalue)/(irslope-rebeltinvalue));

}

void basicfluor() {
  // Flash the LED in a cycle with defined ON and OFF times, and take analogRead measurements as fast as possible on the ON cycle

  strcpy(filenamelocal,filename); // set the local filename variable with the extension for this subroutine.
  strcat(filenamelocal, basicfluorending); // add the variable's extension defined in header
  sub1.open(filenamedir, O_READ);
  file.open(dir, filenamelocal, O_CREAT | O_WRITE | O_APPEND);
  strcpy(filenamelocal,filename); // reset the localfilename variable;

  // SAVE CURRENT TIME TO SD CARD
  printTime();

  Serial.print("THIS IS THE BASELINE: ");
  Serial.println(baseline);  

  datasample = (int*)malloc((runlength/cyclelength)*sizeof(int)); // create the array of proper size to save one value for all each ON/OFF cycle   

  starttimer0 = micros()+100; // This is some arbitrary reasonable value to give the Arduino time before starting
  starttimer1 = starttimer0+pulselengthon;
  while (micros()<starttimer0) {
  }
  PITimer0.start(pulseon); // LED on.  Takes about 1us to call "start" function in PITimer plus 5us per analogRead()
  while (micros()<starttimer1) {
  }
  PITimer1.start(pulseoff); // LED off.
  PITimer2.start(stoptimers); // turn off timers after runlength time

  // WAIT FOR TIMERS TO END (give it runlength plus a 10ms to be safe)
  delay(runlength*1000+10);

  z = 0; // reset counter z

  file.println("");
  file.close(); // close out the file
  sub1.close(); // close out the director (you MUST do this - if you don't, the file won't save the data!)

}

// USER PRINTOUT OF MAIN PARAMETERS
void printout() {

  Serial.println("");
  Serial.print("Pulse cycles during calibration                    ");
  Serial.println(calpulsecycles); // Number of times the "pulselengthon" and "pulselengthoff" cycle (on/off is 1 cycle) (maximum = 1000 until we can store the read data in an SD card or address the interrupts issue)
  Serial.print("Time when saturation pulse turns on       ");
  Serial.println(saturatingcycleon*cyclelength); //The cycle number in which the saturating light turns on (set = 0 for no saturating light)
  Serial.print("Time when saturation pulse turns off      ");
  Serial.println(saturatingcycleoff*cyclelength); //The cycle number in which the saturating light turns off
  Serial.print("Length of a single measuring pulse in us           ");
  Serial.println(pulselengthon); // Pulse LED on length in uS (minimum = 5us based on a single ~4us analogRead - +5us for each additional analogRead measurement in the pulse).
  Serial.print("Length of the wait time between pulses pulse in us ");
  Serial.println(cyclelength-((float)pulselengthon/1000000)); // Pulse LED on length in uS (minimum = 5us based on a single ~4us analogRead - +5us for each additional analogRead measurement in the pulse).
  Serial.print("Base data file name                                ");
  Serial.println(filename);
  Serial.println("");
  Serial.print("Teensy pin for measuring light      ");
  Serial.println(measuringlight1);
  Serial.print("Teensy pin for saturating light     ");
  Serial.println(saturatinglight1);
  Serial.print("Teensy pin for calibration light    ");
  Serial.println(calibratinglight1);
  Serial.print("Teensy pin for detector             ");
  Serial.println(detector1);
  Serial.println("");

}

// USER PRINTOUT OF TEST RESULTS
void calculations() {

  Serial.println("DATA - RAW ANALOG VALUES IN uV");
  for (i=0;i<(runlength/cyclelength);i++) {
    datasample[i] = 1000000*((reference*datasample[i])/(analogresolutionvalue*measurements)); 
  }
  for (i=0;i<(runlength/cyclelength);i++) { // Print the results!
    Serial.print(datasample[i], 8);
    Serial.print(",");
  }
  Serial.println("");

  Serial.println("ALL DATA IN CURRENT DIRECTORY - BASELINE ADJUSTED VALUES");

  int16_t c;
  sub1.open(filenamedir, O_READ);
  strcpy(filenamelocal,filename); // set the local filename variable with the extension for this subroutine.
  strcat(filenamelocal, basicfluorending);
  Serial.print("file directory: ");
  Serial.println(filenamedir);
  Serial.print("file name: ");
  Serial.println(filenamelocal);
  file.open(dir, filenamelocal, O_READ);
  while ((c = file.read()) > 0) Serial.write((char)c); // copy data to the serial port
  strcpy(filenamelocal,filename); // reset the localfilename variable;

  file.close(); // close out the file
  sub1.close(); // close out the director (you MUST do this - if you don't, the file won't save the data!)

  totaltimecheck = end1 - start1orig;
  caltotaltimecheck = calend1 - calstart1orig;

  Serial.println("");
  Serial.print("Size of the baseline:  ");
  Serial.println(baseline, 8);

  totaltimecheck = end1 - start1orig;
  caltotaltimecheck = calend1 - calstart1orig;

  Serial.println("");
  Serial.println("GENERAL INFORMATION");
  Serial.println("");

  Serial.print("total run length (measuring pulses):  ");
  Serial.println(totaltimecheck);

  Serial.print("expected run length(measuring pulses):  ");
  Serial.println(runlength);

  Serial.print("total run length (calibration pulses):  ");
  Serial.println(caltotaltimecheck);

  Serial.println("");
  Serial.println("CALIBRATION DATA");
  Serial.println("");


  Serial.print("The baseline from the sample is:  ");
  Serial.println(baseline);
  Serial.print("The calibration value using the reflective side for the calibration LED and measuring LED are:  ");
  Serial.print(rebeltinvalue);
  Serial.print("and ");
  Serial.println(irtinvalue);
  Serial.print("The calibration value using the black side for the calibration LED and measuring LED are:  ");
  Serial.print(rebeltapevalue);
  Serial.print("and ");
  Serial.println(irtapevalue);

  delay(50);

}

/*  code to process time sync messages from the serial port   */
#define TIME_MSG_LEN  11   // time sync to PC is HEADER followed by unix atime_t as ten ascii digits
#define TIME_HEADER  'T'   // Header tag for serial time sync message

atime_t processSyncMessage() {
  // return the time if a valid sync message is received on the serial port.
  while(Serial.available() >=  TIME_MSG_LEN ){  // time message consists of a header and ten ascii digits
    char c = Serial.read() ; 
    Serial.print(c);  
    if(c == TIME_HEADER ) {       
      atime_t pctime = 0;
      for(int i=0; i < TIME_MSG_LEN -1; i++){   
        c = Serial.read();          
        if( c >= '0' && c <= '9'){   
          pctime = (10 * pctime) + (c - '0') ; // convert digits to a number    
        }
      }   
      return pctime;
      Serial.println("this is pc time");
      Serial.println(pctime);
    }  
  }
  return 0;
  i=0; // reset i
}

// PRINT CURRENT TIME TO DISPLAY IN SERIAL PORT
void SerialPrintClock(){
  Serial.print(month());
  Serial.print("/");
  Serial.print(day());
  Serial.print("/");
  Serial.print(year()); 
  Serial.print(" ");
  Serial.print(hour());
  serialprintDigits(minute());
  serialprintDigits(second());
  Serial.println(); 
}

void serialprintDigits(int digits){
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/*
void fileprintDigits(int digits){
 // utility function for digital clock display: prints preceding colon and leading 0
 file.print(":");
 if(digits < 10)
 file.print('0');
 file.print(minute());
 }
 */

void printTime () {
  file.print(month());
  file.print("/");
  file.print(day());
  file.print("/");
  file.print(year()); 
  file.print(" ");
  file.print(hour());
  file.print(":");
  if(minute() < 10)
    file.print('0');
  file.print(minute());
  file.print(":");
  if(second() < 10)
    file.print('0');
  file.print(second());
  file.print(","); 
}

void pulseon() {
  //  Serial.print(z);
  if (z==saturatingcycleon-1) { // turn on saturating light at beginning of measuring light
    digitalWriteFast(saturatinglight1, HIGH);
  }
  digitalWriteFast(measuringlight1, HIGH);
  data1 = analogRead(detector1);
  i = 0;
}

void pulseoff() {

  // NOTE! for very short OFF cycles, just store the data in the datasample[], and write to 
  // the SD card at the end.  If OFF cycle is long enough (50us or more), then you can write
  // directly to the SD card.  The advantage is you are limited to ~1500 data points for
  // datasample[] before it becomes too big for the memory to hold.

  digitalWriteFast(measuringlight1, LOW);
  if (z==saturatingcycleoff-1) { // turn off saturating light at end of measuring light pulse
    digitalWriteFast(saturatinglight1, LOW);
  }
  datasample[z] = data1; 
  Serial.println(datasample[z]);
  file.print(datasample[z]);
  file.print(",");  
  data1 = 0; // reset data1 for the next round
  z=z+1;
}


void stoptimers() { // Stop timers, close file and directory on SD card, free memory from datasample[], turn off lights, reset counter variables, move to next row in .csv file, 
  PITimer0.stop();
  PITimer1.stop();
  PITimer2.stop();
  end1 = micros();
  free(datasample); // release the memory allocated for the data
  digitalWriteFast(measuringlight1, LOW);
  digitalWriteFast(calibratinglight1, LOW);
  digitalWriteFast(saturatinglight1, LOW);
  z=0; // reset counters
  i=0;
  Serial.print("Total run time is ~: ");
  Serial.println(end1-starttimer0);
}

void loop(){
}
 
This is a great project. I hope you'll post some photos and let us show it on the projects page?

IntervalTimer will be built in to Teensyduino 1.14 and later, so you should definitely use IntervalTimer instead of PITimer. In fact, your best path right now would be to use 1.14-rc2, so you can start building with IntervalTimer as it will be in 1.14.

On Teensy 3.0, there's no need to use things like PSTR() and PgmPrint. String constants are automatically placed only in flash memory.
 
Definitely - let me get some cooler pictures first - I should have them by early next week. Actually, we run our bioreactors on the Teensy 2.0++ so I'll snap some pictures of those too!

Thanks for the explanation - I'll switch over to intervaltimer. Thanksfully, linuxgeek already gave me a great example of how to do it!

Greg
 
Hmmm... Interval timer does not yet have all the functionality that PITimer does, like setting the period and frequency of the timer... do you know when that might be added, or am I missing something?
 
Hey, haven't posted in a while but just wanted to say that we've finally got a working prototype. It has a CO2 sensor, temp, humidity, light meter with light temperature, and the ability to measure fluorescence and photosynthetic efficiency. We're going to be at the Open Hardware Summit on Friday, so if you happen to be there stop by our dema and you can see how it works!

There's still a lot of improvements to make, so I'm sure I'll be asking more questions as we go along.

Here's a picture:
IMG_20130905_100924.jpg
 
Last edited:
Status
Not open for further replies.
Back
Top