In order to implement a pause pushbutton, I used the ThreadKit library to create threads that run in parallel. At startup, a thread to listen for the button state is automatically initiated. The unit begins in the “paused” state and prompts the user to begin the experiment by pressing the button. At button press, the button state thread triggers an event that will lead to the trial routine (TTL pulse, delay, and LED stimulus presentation). Any time the user presses the pause button, the current trial will finish executing and then the state will switch back to “paused” until a second button press.
One of the key features of this unit is that the brightness of the LED stimuli can be set in code. While a radial potentiometer outputs variable resistance depending on the slider position, a digital potentiometer has a fixed series of resistors that are logically gated, so by commanding the digital pot, a particular series can be connected to produce the desired resistance. I chose Microchip’s MCP4651 because it uses I2C communication, has two separate wipers (thus, each of the two LEDs could have different brightness), and produces an appropriate range of resistance for the LEDs we use (SunLED XZDG55W).
In addition to the Wire reference, I found this page to be extremely helpful in figuring out how to get Arduino I2C communication working with the selected digital pot. Of particular use is this little routine that cycles through all possible byte commands to find those that successfully communicate with the digital pot. This was very helpful to me as I slogged through the datasheet trying to figure out exactly how to “talk” to the digital pot.
/* Goggle Stimulator v1 by Emma Roach, May 2013 Further documentation: https://emmaberylroach.wordpress.com/category/devices/mouse-goggles/ Features: * TTL pulse indicates impending stimulus onset * LED stimuli of varying brightness (controlled via I2C with Microchip digital potentiometer MCP4651) * user-defined stimulus parameters include LED brightness/duration and intervals between events * LCD screen displays current state of experiment * Push button allows experiment to be paused/unpaused (note that the current trial will execute before the pause) Typical trial control (repeated in an infinite loop or up to [max_trials]): * TTL pulse of length [TTL_duration] * wait length of [trigger_delay] * goggles turned on * wait length of [goggle_duration] * goggles turned off * wait length of [minITI] + random length between 0 and [rand_delay] This code uses the Threadkit library for cooperative multi-threading * For more information: https://code.google.com/p/threadkit/ PLEASE NOTE: * The unit should be powered off when not in use. * Once the unit is powered back on, pressing the push button begins the trial presentation loop. * During power up, the goggles will glow briefly and then shut off. To avoid presenting unintended stimuli, either power on the unit prior to placing the goggles or unplug the goggles from the unit until just before starting the experiment. */ // Libraries to include #include <Arduino.h> #include "ThreadKit.h" #include <Wire.h> #include <LiquidCrystal.h> //***************************************************** // The following block contains user-defined variables. // All temporal values are expressed in milliseconds. int minITI = 2000; //minimum time between trials, ie inter-trial interval (ITI) int rand_delay = 0; //upper boundary of random addition to ITI int trigger_delay = 100; //interval between TTL pulse and goggle onset int ttl_duration = 100; //duration of TTL pulse int goggle_duration = 200; //duration of goggle stimulus int max_trials = 10; //total number of trials to present (set to 0 for infinite) byte goggle1R = 240; //goggle 1 resistance, range 0-255, linear scale where 255 = brightest byte goggle2R = 240; //goggle 2 resistance // If you change anything beyond this point you // run the risk of breaking the code's functionality! //****************************************************** // Initialize the LCD library with the numbers of the Arduino interface pins LiquidCrystal lcd(7,8,9,10,11,12); // Wire transmission variables to command digital potentiometer (I2C protocol) byte slave = 0; //address to begin general transmission with potentiometer byte goggle1 = 128; //command byte: write next byte to wiper 0 byte goggle2 = 144; //command byte: write next byte to wiper 1 byte off = 0; //wiper value for maximum resistance // Designate Arduino pins for button input and TTL output int button = 5; //pin for button press input int ttl = 6; //pin for TTL output // Miscellaneous state parameters static boolean paused = 1; //state of experiment static int trials = 1; //total number of trials presented // EVENTS // When broadcasted, events trigger the initiation of individual threads. // The event "STARTUP" is broadcast by default when the unit is powered on. EVENTS ( TRIAL_START, GO ); // User_setup: equivalent to the typical Arduino "setup" function. // Executes before any of the threads begin. void user_setup () { //make dummy connection (the first command fails without this) Wire.beginTransmission(slave); Wire.endTransmission(); //Command max resistance for each LED Wire.beginTransmission(slave); Wire.write(goggle1); //write next byte to wiper 0 Wire.write(off); Wire.write(0); Wire.endTransmission(); Wire.beginTransmission(slave); Wire.write(goggle2); //write next byte to wiper 1 Wire.write(off); Wire.write(0); Wire.endTransmission(); //Initialize LCD and print starting message lcd.begin(16, 2); lcd.setCursor(0,1); lcd.print("press to begin"); //Configure the button and TTL pins for input and output pinMode(button,INPUT); pinMode(ttl,OUTPUT); //Seed the random number generator with floating (highly variable) analog input //(comment out if the same "random" ITI sequence is desired across experiments) randomSeed(analogRead(0)); } // THREADS //Each thread is initiated via an event broadcast: BEGIN_THREAD (EVENT) //Once initiated, the thread will run in parallel with other existing threads // //* note: top-level variables shared between threads must be declared "static" //(e.g. the state parameters [trials] and [paused]) //This thread listens for button input and triggers trial presentation when unpaused THREAD (button_wait) { BEGIN_THREAD (STARTUP); //automatically initiated at powerup while (true) { WAIT (digitalRead(button)); //wait until user button has been pushed if (paused) { //this button press indicates an "un-pause" paused = 0; broadcast (GO); //initiate main_th thread } else { //this button press indicates a "pause" paused = 1; } WAIT (!digitalRead(button)); //make sure button has been released before continuing to monitor button state } END_THREAD; } //This thread is triggered when the experiment is "un-paused" THREAD (main_th) { BEGIN_THREAD (GO); lcd.setCursor(0,0); lcd.print("trial #"); while (!paused){ //check if max number of trials have been presented if ((max_trials > 0) & (trials > max_trials)) { lcd.setCursor(0,1); lcd.print("exp. completed "); } else { lcd.setCursor(0,1); lcd.print("exp. running "); WAIT_DELAY (minITI + random(rand_delay)); //wait minimum ITI plus random delay broadcast (TRIAL_START); //initiate trial thread } } lcd.setCursor(0,1); lcd.print("exp. paused "); END_THREAD; } THREAD (trial) { BEGIN_THREAD (TRIAL_START); lcd.setCursor(7,0); lcd.print(trials); lcd.setCursor(13,0); //execute TTL pulse digitalWrite(ttl,HIGH); lcd.print("TTL"); WAIT_DELAY(ttl_duration); digitalWrite(ttl,LOW); lcd.setCursor(13,0); lcd.print(" "); //delay between TTL and goggle onset WAIT_DELAY(trigger_delay); //turn on goggles Wire.beginTransmission(slave); Wire.write(goggle1); //write next byte to wiper 0 Wire.write(goggle1R); Wire.write(0); Wire.endTransmission(); Wire.beginTransmission(slave); Wire.write(goggle2); //write next byte to wiper 0 Wire.write(goggle2R); Wire.write(0); Wire.endTransmission(); //wait stimulus duration WAIT_DELAY (goggle_duration); //turn off goggles Wire.beginTransmission(slave); Wire.write(goggle1); //write next byte to wiper 0 Wire.write(off); Wire.write(0); Wire.endTransmission(); Wire.beginTransmission(slave); Wire.write(goggle2); //write next byte to wiper 0 Wire.write(off); Wire.write(0); Wire.endTransmission(); //increment trial count trials++; END_THREAD; } //list of all threads THREADS ( button_wait, main_th, trial );