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.

signal

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 showing saccade


/*
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();
   //    }