Arduino High Speed Digital Acquisition
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.
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.
/*
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();
// }