Psychophysics experiments often require passing messages and signals between different computers and devices. There are a number of methods that are generally used and the limitations of these methods are often a source of consternation for researchers.
Experiments designed to probe human temporal perception are particularly sensitive to timing errors in the apparatus. Therefore we have been developing a high resolution signal acquisition device for sending and receiving signals and doing some basic processing.

digital_signal.png

This post will be updated with details as we continue development but for now, here’s a nice graphic of a digital signal sent and read, then displayed on an LCD screen attached to an Arduino Due. The sampling is at 1ms resolution.

edit 18/12/13

After working with a variety of connections, creative wiring and cardboard boxes, Matt and I arrived at a far more modular solution to signal acquisition. The attached picture shows the repackaged signal acquisition device that contains 2 x Arduino Due, a touch screen and a Massachusetts Analog CVP attenuator. It’s all made simpler by being in the standard Eurorack format.

The trace you can see is a simulated leftward saccade. device.png

/*
PlatEYEpus general IO sketch V2.0 for Arduino Due  04/09/13                             
Open Source licence GPL2                                                                
Code developed for PlatEYEpus signal acquisition box                                    
by F. Giorlando and M.Stainer                                                  
code utilises routines from buffered/unbuffered timing test code of                     
Andrew Thomas http://www.geocomputing.co.uk/getpage.php?type=page&page=megaspeedtest   
also uses Average Library by Majenko http://hacking.majenko.co.uk/libs/average
general routines allow up to 1ms resolution analog and digital I/O                      
see http://forum.arduino.cc/index.php?topic=160927.0 for digital port manipulation      
control from MATLAB or python scripts supplied at http://playEYEpus.com.au   
 
 The circuit:
 * 2 arduino DUE boards (one input, one ouptut)
 *
 *
 
created Jun 2013
modified 8 Aug 2013
modified over Aug with multiple changes
by F. Giorlando & M. Stainer 
 
*/
 
// images
  unsigned short no_saccade_img[256] ={
  0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x0010 (16) pixels
  0x632C, 0x9CD3, 0x632C, 0x9980, 0x6180, 0x9980, 0x6180, 0x9980, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x0020 (32) pixels
  0x9CD3, 0x632C, 0x9B20, 0xFB26, 0xFCCC, 0xFB26, 0xFCCC, 0xFB26, 0x9B20, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x0030 (48) pixels
  0x632C, 0x9CD3, 0x6180, 0xFCC6, 0x0000, 0xFCC6, 0x0000, 0xFCC6, 0x6180, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x0040 (64) pixels
  0x9CD3, 0x632C, 0x9B20, 0xFB26, 0xFCCC, 0xFB26, 0xFCCC, 0xFB26, 0x9B20, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x0050 (80) pixels
  0x632C, 0x9CD3, 0x6180, 0x9980, 0x6180, 0x9980, 0x6180, 0x9980, 0x6180, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x0060 (96) pixels
  0x9CD3, 0x632C, 0x9B20, 0xFB26, 0xFCCC, 0xFB26, 0xFCCC, 0xFB26, 0x9B20, 0x632C, 0x9CD3, 0x632C, 0xFCCC, 0xFB26, 0xFCCC, 0x632C,   // 0x0070 (112) pixels
  0x632C, 0x9CD3, 0x632C, 0x9980, 0x6180, 0x9980, 0x6180, 0x9980, 0x632C, 0x9CD3, 0x632C, 0xF800, 0xFB26, 0xFCC6, 0xFB26, 0x9CD3,   // 0x0080 (128) pixels
  0x9CD3, 0x632C, 0x9CD3, 0x6180, 0x9B20, 0x6180, 0x9B20, 0x6180, 0x9CD3, 0x632C, 0xF800, 0xF800, 0xFCCC, 0xFB26, 0xFCCC, 0x632C,   // 0x0090 (144) pixels
  0x632C, 0x9CD3, 0xF800, 0xFFFF, 0x001F, 0x001F, 0x001F, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xFB26, 0x9CD3, 0x632C, 0x9CD3,   // 0x00A0 (160) pixels
  0x9CD3, 0xF800, 0xF800, 0xF800, 0xFFFF, 0x001F, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xFCCC, 0x632C, 0x9CD3, 0x632C,   // 0x00B0 (176) pixels
  0x632C, 0xF800, 0xF800, 0xF800, 0xF800, 0x001F, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x00C0 (192) pixels
  0x9CD3, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x00D0 (208) pixels
  0x632C, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x00E0 (224) pixels
  0x9CD3, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x00F0 (240) pixels
  0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x0100 (256) pixels
  };
 
  unsigned short saccade_img[256] ={
  0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x0010 (16) pixels
  0x632C, 0x9CD3, 0x632C, 0x9980, 0x6180, 0x9980, 0x6180, 0x9980, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x0020 (32) pixels
  0x9CD3, 0x632C, 0x9B20, 0xFB26, 0xFCCC, 0xFB26, 0xFCCC, 0xFB26, 0x9B20, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x0030 (48) pixels
  0x632C, 0x9CD3, 0x6180, 0xFCC6, 0x0000, 0xFCC6, 0x0000, 0xFCC6, 0x6180, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x0040 (64) pixels
  0x9CD3, 0x632C, 0x9B20, 0xFB26, 0xFCCC, 0xFB26, 0xFCCC, 0xFB26, 0x9B20, 0x632C, 0x9CD3, 0x632C, 0xFCCC, 0x632C, 0x9CD3, 0x632C,   // 0x0050 (80) pixels
  0x632C, 0x9CD3, 0x6180, 0x9980, 0x6180, 0x9980, 0x6180, 0x9980, 0x6180, 0x9CD3, 0x632C, 0x9CD3, 0xFB26, 0x9CD3, 0x632C, 0x9CD3,   // 0x0060 (96) pixels
  0x9CD3, 0x632C, 0x9B20, 0xFB26, 0xFCCC, 0xFB26, 0xFCCC, 0xFB26, 0x9B20, 0x632C, 0x9CD3, 0x632C, 0xFCCC, 0xFB26, 0xFCCC, 0x632C,   // 0x0070 (112) pixels
  0x632C, 0x9CD3, 0x632C, 0x9980, 0x6180, 0x9980, 0x6180, 0x9980, 0x632C, 0x9CD3, 0x632C, 0x04C0, 0xFB26, 0xFCC6, 0xFB26, 0x9CD3,   // 0x0080 (128) pixels
  0x9CD3, 0x632C, 0x9CD3, 0x6180, 0x9B20, 0x6180, 0x9B20, 0x6180, 0x9CD3, 0x632C, 0x04C0, 0x0320, 0xFCCC, 0xFB26, 0xFCCC, 0x632C,   // 0x0090 (144) pixels
  0x632C, 0x9CD3, 0x0320, 0xFFFF, 0x001F, 0x001F, 0x001F, 0xFFFF, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x9CD3, 0x632C, 0x9CD3,   // 0x00A0 (160) pixels
  0x9CD3, 0x0320, 0x04C0, 0x0320, 0xFFFF, 0x001F, 0xFFFF, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x00B0 (176) pixels
  0x632C, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x001F, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x00C0 (192) pixels
  0x9CD3, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x00D0 (208) pixels
  0x632C, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x00E0 (224) pixels
  0x9CD3, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x0320, 0x04C0, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x00F0 (240) pixels
  0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x0100 (256) pixels
  };
 
  unsigned short alternative_img[256] ={
  0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C,   // 0x0010 (16) pixels
  0x0000, 0x9CD3, 0x632C, 0x9980, 0x6180, 0x9980, 0x6180, 0x9980, 0x632C, 0x9CD3, 0x0000, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x0020 (32) pixels
  0x0000, 0x0000, 0x9B20, 0xFB26, 0xFCCC, 0xFB26, 0xFCCC, 0xFB26, 0x9B20, 0x0000, 0x0000, 0xCE79, 0x9CD3, 0xCE79, 0x9CD3, 0xCE79,   // 0x0030 (48) pixels
  0x632C, 0x0000, 0x6180, 0xFCC6, 0xF800, 0xFCC6, 0xF800, 0xFCC6, 0x6180, 0x0000, 0x632C, 0xCE79, 0x632C, 0xCE79, 0x632C, 0xCE79,   // 0x0040 (64) pixels
  0x9CD3, 0x632C, 0x9B20, 0xFB26, 0xFCCC, 0xFB26, 0xFCCC, 0xFB26, 0x9B20, 0x632C, 0x9CD3, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79,   // 0x0050 (80) pixels
  0x632C, 0x9CD3, 0x6180, 0x9980, 0x6180, 0x9980, 0x6180, 0x9980, 0x6180, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0xCE79, 0x632C, 0x9CD3,   // 0x0060 (96) pixels
  0x9CD3, 0x632C, 0x9B20, 0xFB26, 0x0000, 0x0000, 0x0000, 0xFB26, 0x9B20, 0x632C, 0x9CD3, 0x632C, 0xFCCC, 0xFB26, 0xFCCC, 0x632C,   // 0x0070 (112) pixels
  0x632C, 0x9CD3, 0x632C, 0x9980, 0x0000, 0x0000, 0x0000, 0x9980, 0x632C, 0x9CD3, 0x632C, 0xF800, 0xFB26, 0xFCC6, 0xFB26, 0x9CD3,   // 0x0080 (128) pixels
  0x9CD3, 0x632C, 0x9CD3, 0x6180, 0x9B20, 0x6180, 0x9B20, 0x6180, 0x9CD3, 0x632C, 0xF800, 0xF800, 0xFCCC, 0xFB26, 0xFCCC, 0x632C,   // 0x0090 (144) pixels
  0x632C, 0x9CD3, 0xF800, 0xFFFF, 0x001F, 0x001F, 0x001F, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xFB26, 0xFCC6, 0xFB26, 0x9CD3,   // 0x00A0 (160) pixels
  0x9CD3, 0xF800, 0xF800, 0xF800, 0xFFFF, 0x001F, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x9CD3, 0xCE79, 0x9CD3, 0x632C,   // 0x00B0 (176) pixels
  0x632C, 0xF800, 0xF800, 0xF800, 0xF800, 0x001F, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x9CD3, 0x632C, 0xCE79, 0x632C, 0x9CD3,   // 0x00C0 (192) pixels
  0x9CD3, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x9CD3, 0x632C, 0x9CD3, 0xCE79, 0x9CD3, 0x632C,   // 0x00D0 (208) pixels
  0x632C, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0xCE79, 0x632C, 0x9CD3,   // 0x00E0 (224) pixels
  0x9CD3, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0xCE79, 0x9CD3, 0x632C,   // 0x00F0 (240) pixels
  0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3, 0x632C, 0x9CD3,   // 0x0100 (256) pixels
  };
 
 
   int debug=0;
 
// for LCD Display IC_ILI9481_TFT with shield
   #include <UTFT.h>
 
// for calculating maximum - see http://playground.todo.arduino.cc/en/Main/Average   
   #include <Average.h>
   // #include "./images/saccade.c"
 
   UTFT myGLCD(CTE32HR,25,26,27,28);
   // Declare which fonts we will be using
   extern uint8_t SmallFont[];
   // specify screen size
   float screenx=myGLCD.getDisplayYSize()-1; // 480
   float screeny=myGLCD.getDisplayXSize()-1; // 320
   // define channel colours
 
 
// digital settings
   int led = 13;  // default for onboard LED
   int dout1 = 8; // 1st digital channel: currently wired for testing to AIN 1
   int dout2 = 9; // 2nd digital channel
   // also should set DIN/DOUT here
 
// other variables
   //long accelbuffZ[200];  // this was originally int
   //unsigned long time;
   unsigned long startTime;
   unsigned long endTime;
   unsigned long endTime_serial;
   unsigned long endTime_draw;
   unsigned long timeTaken;
   unsigned long timeTaken2;
   unsigned long timeTaken3;
   int ndelay=1;
   int inByte = 0;
 
// screen variables
  float axes_width=16;   
  float ch_divider_width=0;
  float divider_size=50;
 
  //String ch1_col= consider how to define colours here
 
// analogue settings
   int Zpin=2;  // analogue read pin
   int Zval;
   int c,zad;
 
// saccade detection variables
   int i = 0;
   double t = 0;
   double extra_time = 0;
   double s = 0;
   double A = 0;
   int saccade=0;
   double Voltage[3000];
   double Timestamp[3000];
   // set saccade threshold in mV/ms
   // analog reading goes from 0 - 1023, equivalent to voltage (0 - 3.3V):
   // float thresh_mV = 0.01;                // mV/msec
   // float thresh_bit = thresh_mV/5*4096;  // for 0.01=8.192
   // consider using the map() function for this instead
   //long thresh_bit = 2; // default threshold
   long thresh_size=1; // between bit criterion
   long thresh_time=4; // how many bits to integrate
 
void setup()
  {
   Serial.begin(115200);
   // Setup the LCD
     myGLCD.InitLCD();
     myGLCD.setFont(SmallFont);
 
   // initialize the digital pins 
 
       pinMode(led, OUTPUT); //pin 13 specified above
 
       // NB leave ports 0 and 1 alone they are used for serial RX/TX
       // these are output pins on channel D
       pinMode(2,OUTPUT);
       pinMode(3,OUTPUT);
       pinMode(4,OUTPUT);
       pinMode(5,OUTPUT);
       pinMode(6,OUTPUT);
       pinMode(7,OUTPUT);
 
       pinMode(8,INPUT);
       pinMode(9,INPUT);
       pinMode(10,INPUT);
       pinMode(11,INPUT);
 
       pinMode(12,OUTPUT);
       pinMode(14,OUTPUT); 
  }
 
// the main loop; provides menu of functions
// which are called via serial inputs
// once option is executed it returns serial output
// then waits for next menu command
// menu:  b=Aread,delay,Dout
//            c=AOscilloscope
//            s=Aread,saccdetect
//            o=Dout
//            p=Dout-pulse
//            d=Dread
void loop()
{
 if (Serial.available() > 0)
 {
   inByte = Serial.read();
 
   // call Analog Read with digital out after delay
   if (inByte=="b") // call with "b [time] [ms delay]"
       {
         long nsamples=0;
         nsamples = Serial.parseInt();
         fastz_specs(nsamples);
       }  
 
    // call Oscilloscope
   else if (inByte=="c") // call with "c [timeout] [res] [channels] [window]"
       {
         long nsamples=0;
         long timeout  = Serial.parseInt();
         int res    = Serial.parseInt(); // samples per msec, def=1
         int channels   = Serial.parseInt();
         long window   = Serial.parseInt(); // time shown in window is ms
         // channels should be an array, how do we parse this?
         oscilloscope(timeout, res, channels, window);
       } 
 
   // call Analog Read and saccade detect
   else if (inByte=="s") // call with "s [time] [channel] [threshold_size] [threshold_time] [resolution(opt)]"
       {
         long nsamples=0;
         nsamples = Serial.parseInt();
         Zpin = Serial.parseInt();
         thresh_size = Serial.parseInt();
         thresh_time = Serial.parseInt();
         ndelay = Serial.parseInt();
         saccade_detect(nsamples, Zpin, thresh_size, thresh_time, ndelay);
       } 
 
   // call digital out only
   else if (inByte=="o")  // output only, "o [outpin] [pulsewidth] [ddelay]"
       // this could be changed to an outbit but 
       // not necessary currently 
       {
       int outpin = Serial.parseInt();        // read digital out bit (as int) 
       long pulsewidth = Serial.parseInt();   // pulswidth in ms
       long ddelay = Serial.parseInt();       // delay before pulse in ms
       digital_output(outpin,pulsewidth,ddelay); 
        //       pins   13 12 11 10 9  8  
        //            B 1  1  1  1  1  1  ;  
       }  
 
   // call hybrid command
   else if (inByte=="p")  // output and read, "p [Doutpin] [Ainpin] [pulsewidth] [ddelay]" 
       {
       int outpin = Serial.parseInt();        // read digital out bit (as int)
       int inpin = Serial.parseInt();        // read analog in pin (as int) 
       long pulsewidth = Serial.parseInt();   // pulswidth in ms
       long ddelay = Serial.parseInt();       // delay before pulse in ms
       digital_output_read(outpin,inpin,pulsewidth,ddelay);         
        //       pins   13 12 11 10 9  8  
        //            B 1  1  1  1  1  1  ; 
       } 
 
   // call digital read
   else if (inByte=="d") // digital read "o [timeout] [channel]"
       {
        int timeout = Serial.parseInt();        // timeout in ms 
        int Dpin = Serial.parseInt();            // digital pin to read
        digital_read(timeout, Dpin);    
      }
 }
}
 
/////////////////////////////////
// Functions called from Menu  //
/////////////////////////////////
 
// OUTPUT to specified digital pin 
// called with "o 3 100 200" for pulse of 300ms after delay of 200ms
void digital_output(int outpin,long pulsewidth,long ddelay)
   {
     delay(ddelay);
 
   //    REG_PIOB_ODSR = outbit;  // sets 8 bits on port B (PIOB) - pins 0 to 7, B is 8-13
   //    delay(pulsewidth);
   //    REG_PIOB_ODSR = B00000000; // reset to all off
     digitalWrite(outpin, HIGH);   // turn the LED on (HIGH is the voltage level)
     delay(pulsewidth);            // wait for defined pulsewidth
     digitalWrite(outpin, LOW);    // turn the LED off by making the voltage LOW
   }
 
// OUTPUT to specified digital pin with analog read *** incomplete
// called with "p 3 100 200" for pulse of 300ms after delay of 200ms
void digital_output_read(int outpin, int inpin, long pulsewidth,long ddelay)
   {
 
     long ndelay = 1;  // delay in ms intervals
     long nsamples=2000;
     long accelbuffZ[nsamples];
     int time_array[nsamples];
 
     //digitalWrite(A2, HIGH);  // set pullup on analog pin 2
 
     //long pulsewidth=1000; // default pulse of 100ms
     delay(ddelay);
 
     // acquire data and write digital
     startTime = millis();
     for (c = 0; c <= nsamples-1; c++)  // remember arrays are 0 referenced
     {
       accelbuffZ[c]=analogRead(inpin);
       time_array[c]=c;
 
       if (c<ddelay)
       {
         digitalWrite(outpin, LOW);   // turn the LED off by making the voltage LOW
       }
       else if (c>=ddelay && c<ddelay+pulsewidth)
       {
         digitalWrite(outpin, HIGH);    // turn the LED on (HIGH is the voltage level)
       }
       else if (c>=ddelay+pulsewidth)
       {
         //digitalWrite(outpin, LOW);
       }
       delay(ndelay);
 
 
     }  
     endTime = millis();
     digitalWrite(A2, LOW);  // set pullup on analog pin 2
 
     // send back buffered data
     for (c = 0; c <= nsamples-1; c++)  // this could be simplified to c<nsamples!
     {
     Serial.println(accelbuffZ[c],DEC);
     }
 
     endTime_serial = millis();
     // send timing data
     Serial.println(startTime,DEC);
     Serial.println(endTime,DEC);
     timeTaken=endTime-startTime;
     Serial.println(timeTaken,DEC);
 
     // call graphing function
     myGLCD.setBackColor(64, 64, 64);  // set background colour
     myGLCD.setColor(255, 0, 0);       // set draw colour
     myGLCD.clrScr();                  // clear display to bg
     for (c = 0; c <= nsamples-1; c++)
     {
     draw_pixels(c,accelbuffZ[c],nsamples);
     }
 
    endTime_draw = millis();
    if (debug==1)
    { 
      timeTaken2 = endTime_serial-endTime;
      timeTaken3 = endTime_draw-endTime_serial;
      Serial.println(timeTaken2,DEC);
      Serial.println(timeTaken3,DEC);
    }
 
   }
 
// INPUT digital read - with specified timeout 
// called with "d 2000 14" for 2000 samples, pin 14
void digital_read(long timeout, int Dpin)
  {
    int channels=1;
    int d_detect=0;
    int d_time=0;
    int timer=0;
    int dsig=LOW;
    digitalWrite(Dpin, LOW);
 
    // acquire data
    startTime = millis();
     while (timer < timeout && d_detect==0)
     {
       dsig=digitalRead(Dpin);
       timer=millis()-startTime;
       if (dsig==HIGH)
       {d_detect=1;}
     }
 
      if (d_detect==1)
      {Serial.println("D");}
      else
      {Serial.println("N");}
      Serial.println(timer,DEC);
 
      draw_axes(channels);
 
      myGLCD.setColor(255, 0, 0);   // set draw colour
      myGLCD.drawLine(axes_width, axes_width, 200, axes_width);
      if (d_detect==1)
      {myGLCD.drawLine(200, axes_width, 250, 100);
      myGLCD.drawLine(250, 100, 250, axes_width);
      }
      else {myGLCD.drawLine(200, axes_width, 250, axes_width);}
      myGLCD.drawLine(250, axes_width, screenx, axes_width);
      myGLCD.setColor(255, 255, 255);   // set draw colour
      //char timer_string=String(timer);
      myGLCD.printNumI(timer, 250, 100);
  }
 
// INPUT analog read - fast (buffered) with specified time 
// called with "b 200 1" for 200 samples with 1ms delay 
void fastz_specs(long nsamples)
   {
 
     // get the specified time required
     //long nsamples = 0;
     long ndelay = 1;  // delay in ms intervals
     //nsamples = Serial.parseInt();
     ndelay = Serial.parseInt();
     if (ndelay==0)
     {
       ndelay=1;
     }
 
     long accelbuffZ[nsamples];
     int time_array[nsamples];
 
     // acquire data
     startTime = millis();
     for (c = 0; c <= nsamples-1; c++)
     {
       accelbuffZ[c]=analogRead(Zpin);
       time_array[c]=c;
       delay(ndelay);
     }  
     endTime = millis();
 
     // send back buffered data
     for (c = 0; c <= nsamples-1; c++)
     {
     Serial.println(accelbuffZ[c],DEC);
     }
 
     // send timing data
     Serial.println(startTime,DEC);
     Serial.println(endTime,DEC);
     timeTaken=endTime-startTime;
     Serial.println(timeTaken,DEC);
 
     // call graphing function
     myGLCD.setBackColor(64, 64, 64);  // set background colour
     myGLCD.setColor(255, 0, 0);       // set draw colour
     myGLCD.clrScr();                  // clear display to bg
     for (c = 0; c <= nsamples-1; c++)
     {
     draw_pixels(c,accelbuffZ[c],nsamples);
     }
 
   }
 
// INPUT analog read - with saccade detect 
// called with "s 2000 2 4 2 (1)" for 2000 samples on Ch2 with thresh size 2 over 4 samples and 1ms delay
void saccade_detect(long nsamples, int Zpin, long thresh_size, long thresh_time, int ndelay)
   {
      int channels=1;
      saccade=0;
      int sacc_time=0;
      int sacc_dir=0;
     // get the specified time required
     //long nsamples = 0;
     //long ndelay = 1;  // delay in ms intervals
     //nsamples = Serial.parseInt();
     //ndelay = Serial.parseInt();  // moved to menu
     if (ndelay==0)
     {
       ndelay=1;
     }
     long accelbuffZ[nsamples];
     int time_array[nsamples];
 
     // acquire data
     startTime = millis();
     c = 0;
     while (c <= nsamples-1 && saccade==0)
     {
       accelbuffZ[c]=analogRead(Zpin);
       time_array[c]=c;
       delay(ndelay);
 
       if (c>thresh_time && saccade==0)
         { 
 
          /* // function call taken out for the moment
          //float out_values[3];
          //out_values={float(accelbuffZ[c-2]),float(accelbuffZ[c-1]),float(accelbuffZ[c])};
          //saccade_calc(ndelay,out_values); 
          */
 
          int sum_threshleft = 0;
          int sum_threshright = 0;
 
 
          for (int rep=1; rep<=thresh_time; rep++){
 
           //int test_threshL = 0;
           //int test_threshR = 0;
           long dv_s=accelbuffZ[c-(rep-1)]-accelbuffZ[c-rep];
 
           // if (dv_s > thresh_size) test_threshL = 1;     // if true, output 1
           // if (dv_s < -(thresh_size)) test_threshR = 1;  // if true, output 1 
           int test_threshL = dv_s > thresh_size;     // if true, output 1
           int test_threshR = dv_s < -(thresh_size);  // if true, output 1
           sum_threshleft=sum_threshleft+test_threshL; // sum of trues
           sum_threshright=sum_threshright+test_threshR; // sum of trues 
 
          }
 
          // long dv_s=accelbuffZ[c-4]-accelbuffZ[c-3];
          // long dv_s2=accelbuffZ[c-3]-accelbuffZ[c-2];
          // long dv_s3=accelbuffZ[c-2]-accelbuffZ[c-1];
          // long dv_s4=accelbuffZ[c-1]-accelbuffZ[c];
          // long thresh_bit_n=0-thresh_bit;
          //if (dv_s>thresh_bit && dv_s2>thresh_bit && dv_s3>thresh_bit && dv_s4>thresh_bit) // left saccade
          if(sum_threshleft==thresh_time)
          {
            saccade=1;
            Serial.println("SL");
            sacc_dir=1; //left
            sacc_time=c-thresh_time;  // set time that saccade happened
            Serial.println(sacc_time,DEC);
          }
          //if (dv_s<thresh_bit_n && dv_s2<thresh_bit_n && dv_s3<thresh_bit_n && dv_s4<thresh_bit_n) // right saccade
          if(sum_threshright==thresh_time)
          {
            saccade=1;
            Serial.println("SR");
            sacc_dir=2; //right
            sacc_time=c-thresh_time;  // set time that saccade happened
            Serial.println(sacc_time,DEC);
          }
         }
        c++;
     }  
 
     if (saccade==1)
       {         
        int xt=0;
         while (xt<100 && c<nsamples)  // keep going for 100 samples after saccade
         {
                 accelbuffZ[c]=analogRead(Zpin);
                 time_array[c]=c;
                 delay(ndelay);
                 c++;
                 xt++;
         }
       }
 
       else // no saccade serial output
       {
        Serial.println("N");
        Serial.println(999,DEC);
      }
 
     endTime = millis();
     timeTaken=endTime-startTime;
     Serial.println(timeTaken,DEC);
     long totsamples=c;
 
     // send back buffered data
     for (c = 0; c <= totsamples-1; c++)
        {
         Serial.println(accelbuffZ[c],DEC);
        }
 
     endTime_serial=millis();
 
     // send timing data
     if (debug==1)
     {
       Serial.println(startTime,DEC);
       Serial.println(endTime,DEC);
     }
 
     Serial.println(9999,DEC); // send terminator character
     // Serial.println(sacc_time,DEC); // move above
     //     Serial.println(dv_dt,DEC);
     //     Serial.println(dv_dt2,DEC);
 
     // call graphing function
     myGLCD.setBackColor(0, 0, 0);        // set background colour
     myGLCD.setColor(255, 255, 255);   // set draw colour
     myGLCD.clrScr();                             // clear display to bg
 
     // draw axis bars
     myGLCD.setColor(80, 80, 80);           // set draw colour
     myGLCD.fillRect(0, 0, axes_width, screeny-axes_width);    // channel box
     //myGLCD.fillRect(50,50,screenx,screeny); // test box
     myGLCD.fillRect(axes_width, screeny-axes_width, screenx, screeny);    // time box
     if (ch_divider_width!=0) 
     {myGLCD.fillRect(axes_width, ((screeny-axes_width)/channels)-(ch_divider_width/2)-1, screenx, ((screeny-axes_width)/channels)+ch_divider_width/2);} // channel divider
 
 
 
     for (int ch=1; ch<=channels; ch++)
     {
           String channel_name=String(ch);
           channel_name="CH"+channel_name;
           int channel_length=((screeny-axes_width-(channels-1)*ch_divider_width)/channels); // six is the divider width
           myGLCD.print(channel_name, 0, (ch-1)*channel_length + channel_length/2 + (ch-1)*ch_divider_width-ch_divider_width/2);
     }
 
     myGLCD.setColor(255, 255, 102);           // set draw colour
 
     // calculate plot_res (interpolation of line plot)  // not used
     int plot_res=totsamples/20;
     long max_sig=maximum(accelbuffZ,totsamples);
 
 
      int chan_col=255/channels;
 
     for (int ch=1; ch<=channels; ch++)
     {
         myGLCD.setColor(chan_col*(channels-ch),chan_col*ch,60);
 
         for (c = 1; c <= totsamples-1; c++)
         {
            draw_lines(c-1, accelbuffZ[c-1], c, accelbuffZ[c], totsamples, max_sig, channels,ch); // ch is current channel
 
             // interpolation code removed
             // //draw_pixels(c,accelbuffZ[c],totsamples);
             // if (c==0) {}
             // else {
             //      if (c % plot_res==0)
             //        {
             //        draw_lines(c-plot_res, accelbuffZ[c-plot_res], c, accelbuffZ[c], totsamples, max_sig, channels);
             //        }
             //      }
         }
    }
 
     if (saccade==1)
     {
 
       if (sacc_dir==1)  {            // left sacc draw colour
          myGLCD.setColor(255, 0, 0); 
          myGLCD.printNumI(sacc_time, sacc_time*(screenx-axes_width)/totsamples+axes_width-35, 0);
          myGLCD.print("L", sacc_time*(screenx-axes_width)/totsamples+axes_width+3, 0);
          }          
       if (sacc_dir==2)  {            // right sacc draw colour
          myGLCD.setColor(0, 0, 255); 
          myGLCD.printNumI(sacc_time, sacc_time*(screenx-axes_width)/totsamples+axes_width-35, 0);
          myGLCD.print("R", sacc_time*(screenx-axes_width)/totsamples+axes_width+3, 0);
          }          
       myGLCD.drawLine(sacc_time*(screenx-axes_width)/totsamples+axes_width, 0, sacc_time*(screenx-axes_width)/totsamples+axes_width, screeny);
       myGLCD.setColor(255, 255, 102);           // set draw colour
       myGLCD.drawBitmap (0, screeny-16, 16, 16, saccade_img);
     }
     else {myGLCD.drawBitmap (0, screeny-16, 16, 16, no_saccade_img);}
 
 
           float ticker_width= (screenx-axes_width)/totsamples*divider_size; // 50ms n_bars 
           float n_bars=totsamples/divider_size;  
            myGLCD.setColor(255, 255, 255);           // set draw colour
 
           for (int bar=1; bar<=n_bars; bar++)
           { 
            if (bar%2==0) // is even
            {
            myGLCD.setColor(255, 255, 255);           // set draw colour
            float bar_start=axes_width+(bar-1)*ticker_width;
            float bar_end=axes_width+bar*ticker_width;
            myGLCD.fillRect(bar_start, screeny-axes_width, bar_end, screeny);
            }
            else
            {
            myGLCD.setColor(80, 80, 80);           // set draw colour
            float bar_start=axes_width+(bar-1)*ticker_width;
            float bar_end=axes_width+bar*ticker_width;
            myGLCD.fillRect(bar_start, screeny-axes_width, bar_end, screeny);
         }
           }
 
          myGLCD.setColor(255, 255, 255);           // set draw colour
           myGLCD.printNumI(totsamples, screenx-40, screeny-15);
     endTime_draw=millis();
    if (debug==1)
    { 
      timeTaken2 = endTime_serial-endTime;
      timeTaken3 = endTime_draw-endTime_serial;
      Serial.println(timeTaken2,DEC);
      Serial.println(timeTaken3,DEC);
      //Serial.println(screenx,DEC);
      //Serial.println(screeny,DEC);
    }
   }
 
// INPUT oscilloscope  
// called with "c [timeout] [res] [channels]"
// can be cancelled with a "stop" sent by serial
void oscilloscope(long timeout, int res, int channels, long window) 
   {
 
     startTime = millis();
     long ndelay = 1/res;  // delay in ms intervals
 
     if (ndelay==0)
     {
       ndelay=1;  // default if no res specified
     }
 
     // channel selection logic (modify for array handling)
     Zpin=channels;
 
     // allocate array for data dependent on window
     long t_window=window/res;
     long accelbuffZ[t_window];
     int time_array[t_window];
 
     // acquire data loop
     startTime = millis();
     long c = 0; // loop timer
     int t=0;  //master timer
     int ser_stop = 0; //unused currently, implement for STOP command
 
     // prepare display
     myGLCD.setBackColor(64, 64, 64);  // set background colour
     myGLCD.setColor(255, 0, 0);       // set draw colour
     myGLCD.clrScr();                  // clear display to bg
     //draw axes
     //draw_axes(channels);
 
     // draw scale tickers
     // float ticker_width= (screenx-axes_width)/t_window*divider_size; // 50ms n_bars 
     // float n_bars=t_window/divider_size;  
     //        myGLCD.setColor(255, 255, 255);           // set draw colour
 
     //       for (int bar=1; bar<=n_bars; bar++)
     //       { 
     //        if (bar%2==0) // is even
     //        {
     //        myGLCD.setColor(255, 255, 255);           // set draw colour
     //        float bar_start=axes_width+(bar-1)*ticker_width;
     //        float bar_end=axes_width+bar*ticker_width;
     //        myGLCD.fillRect(bar_start, screeny-axes_width, bar_end, screeny);
     //        }
     //        else
     //        {
     //        myGLCD.setColor(80, 80, 80);           // set draw colour
     //        float bar_start=axes_width+(bar-1)*ticker_width;
     //        float bar_end=axes_width+bar*ticker_width;
     //        myGLCD.fillRect(bar_start, screeny-axes_width, bar_end, screeny);
     //     }
     //       }
     //       // set channel draw colour
           myGLCD.setColor(0, 255, 0);
 
     while (t < timeout && ser_stop == 0)  // master timer
     {
       accelbuffZ[c]=analogRead(Zpin);
       time_array[c]=c;
       delay(ndelay);  // needs to be changed to different command for <1ms
       c++;
       t++;
       if (c == t_window){
          c=0;
          // clear display apart from axes
          // myGLCD.setColor(0, 0, 0);
          // //myGLCD.fillRect(axes_width, axes_width, screenx, screeny);
          // myGLCD.clrScr();
          // myGLCD.setColor(0, 255, 0);
         }
       // draw simple
       myGLCD.setColor(0, 0, 0);
       myGLCD.drawLine(c, 0, c, screeny);
       myGLCD.setColor(0, 255, 0);
       myGLCD.drawPixel(c*screenx/t_window, accelbuffZ[c]*screeny/1023);
       // consider axes
       //myGLCD.drawPixel((axes_width+c)*((screenx-axes_width)/t_window), accelbuffZ[c]/screeny);
       // subfunctions
       //draw_pixels(c,accelbuffZ[c],t_window);
       //draw_lines(c-1, accelbuffZ[c-1], c, accelbuffZ[c], t_window, 1023, channels,1);
     }  
 
     endTime = millis(); 
     long elapsedTime=endTime-startTime;
     Serial.println("end");
     Serial.println(elapsedTime);
 }
 
 
/////////////////////////////////
// Helper Functions            //
/////////////////////////////////
 
// to draw lines to UTFT screen
void draw_lines(int x1, long y1, int x2, long y2, long nsamples, long max_sig, int channels, int ch) 
   {
 
       y1=y1*(screeny-axes_width-channels*ch_divider_width)/max_sig/channels+(ch-1)*ch_divider_width+(ch-1)*((screeny-axes_width)/channels); 
       x1=x1*(screenx-axes_width)/nsamples+axes_width; 
       y2=y2*(screeny-axes_width-channels*ch_divider_width)/max_sig/channels+(ch-1)*ch_divider_width+(ch-1)*((screeny-axes_width)/channels); 
       x2=x2*(screenx-axes_width)/nsamples+axes_width; 
       myGLCD.drawLine(x1, y1, x2, y2);
 
   }
 
// to draw pixels to UTFT screen
void draw_pixels(int x, long y, long nsamples) 
   {
       y=y*screeny/4095; //!!! this should be scaling with tft.height()
       x=x*screenx/nsamples; 
       myGLCD.drawPixel(x, y);
   }
 
// to calculate saccade derivatives -> feeds back to void saccade_detect
int saccade_calc(int resolution,float Voltage_sample[3])  // saccade detect function, returns 0/1
   {
     // calculates saccade detection from last 3 data points
     // Voltage_sample is a 3 element array
 
     // convert values to floats
     float V1 = float(Voltage[1]);
     float V2 = float(Voltage[2]);
     float V3 = float(Voltage[3]);
     float res = float(resolution);
 
     //Rate of change between Sample1 and Sample2
     float dv = V1 - V2; // d_voltage
     float dt = res; // d_time
     float dv_dt = dv / dt;
     // Serial.println(dv_dt,DEC);  // debug output
 
     //Rate of change between Sample2 and Sample3
     float dv2 = V2 - V3; // d_voltage
     float dt2 = res; // d_time
     float dv_dt2 = dv2 / dt2;
 
     //if (abs(dv_dt) > thresh_bit && abs(dv_dt2) > thresh_bit) {
     if (abs(V1-V2)>20)  // simple formula for testing
     //digitalWrite(13, HIGH);  // light up indicator LED on saccade  
     saccade=1;
     else saccade=0;
 
     return saccade; // outputs 0/1
     // how do we return other variables of interest?
   }
 
// to draw axes
void draw_axes(int channels)
  {
     // call graphing function
     myGLCD.setBackColor(0, 0, 0);        // set background colour
     myGLCD.setColor(255, 255, 255);   // set draw colour
     myGLCD.clrScr();                             // clear display to bg
 
     // draw axis bars
     myGLCD.setColor(80, 80, 80);           // set draw colour
     myGLCD.fillRect(0, 0, axes_width, screeny-axes_width);    // channel box
     //myGLCD.fillRect(50,50,screenx,screeny); // test box
     myGLCD.fillRect(axes_width, screeny-axes_width, screenx, screeny);    // time box
     if (ch_divider_width!=0) 
     {myGLCD.fillRect(axes_width, ((screeny-axes_width)/channels)-(ch_divider_width/2)-1, screenx, ((screeny-axes_width)/channels)+ch_divider_width/2);} // channel divider
   }
 
// untested functions to add
 
  // external trigger - see file:///Applications/Arduino/Arduino.app/Contents/Resources/JAVA/reference/AttachInterrupt.html
  // void external_trigger()
  // {
 
  // }
 
 
 
// deleted functions
 
   //// fast (buffered) analogue read - 200 samples
   //void fastz() //called with "f"
   //{
   //  long accelbuffZ[200];
   //  startTime = millis();
   //  for (c = 0; c <= 199; c++)
   //  {
   //    accelbuffZ[c]=analogRead(Zpin);
   //  }  
   //  endTime = millis();
   //  for (c = 0; c <= 199; c++)
   //  {
   //  Serial.println(accelbuffZ[c],DEC);
   //  }
   //  Serial.println(startTime,DEC);
   //  Serial.println(endTime,DEC);
   //  timeTaken=endTime-startTime;
   //  Serial.println(timeTaken,DEC);
   //}
 
   //void slowz() //called with "s"
   //{
   //  startTime = millis();
   //  for (c = 0; c <= 199; c++)
   //  {
   //    zad=analogRead(Zpin);
   //    Serial.println(zad,DEC);
   //  }  
   //  endTime = millis();
   //  Serial.println(startTime,DEC);
   //  Serial.println(endTime,DEC);
   //  timeTaken=endTime-startTime;
   //  Serial.println(timeTaken,DEC);
   //}
   //  
   //void iotest_char() //called with "t"
   //{
   //        byte ctlByte = "x";
   //      
   //      delay(2000); // wait to allow time to enter byte
   //      
   //      ctlByte = Serial.read();
   //      Serial.write(ctlByte);
   //      Serial.write(13); // send LF
   //      Serial.write(10);
   //      
   //}  
   //
   //void iotest_int() //called with "i"
   //{
   //      long ctl_int = 0;
   //      
   //      delay(2000); // wait to allow time to enter byte
   //      
   //      ctl_int = Serial.parseInt();
   //      Serial.println(ctl_int,DEC);
   //      
   //}  
 
 
   // deleted calls from menu
   //   else if (inByte=="s")
   //    {
   //      slowz();
   //    }
   //    else if (inByte=="t")
   //    {
   //      iotest_char();
   //    }
   //        else if (inByte=="i")
   //    {
   //      iotest_int();
   //    }