From c820d8cdc60e656f0ccca604b401a5aee35d90b3 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 23 Feb 2023 16:54:18 -0800 Subject: [PATCH 01/10] add TriggerScope V4B firmware Co-Authored-By: Austin Blanco <35380024+AdvancedResearchConsulting@users.noreply.github.com> --- src/TriggerScope_V4B/Linduino.h | 109 ++ src/TriggerScope_V4B/README.md | 14 + src/TriggerScope_V4B/TriggerScope_V4B.ino | 1796 +++++++++++++++++++++ 3 files changed, 1919 insertions(+) create mode 100644 src/TriggerScope_V4B/Linduino.h create mode 100644 src/TriggerScope_V4B/README.md create mode 100644 src/TriggerScope_V4B/TriggerScope_V4B.ino diff --git a/src/TriggerScope_V4B/Linduino.h b/src/TriggerScope_V4B/Linduino.h new file mode 100644 index 0000000..85ee651 --- /dev/null +++ b/src/TriggerScope_V4B/Linduino.h @@ -0,0 +1,109 @@ +//! @todo Review this file. +/* +Linduino.h + +This file contains the hardware definitions for the Linduino. + +REVISION HISTORY +$Revision: 1906 $ +$Date: 2013-08-26 15:09:18 -0700 (Mon, 26 Aug 2013) $ + +Copyright (c) 2013, Linear Technology Corp.(LTC) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of Linear Technology Corp. + +The Linear Technology Linduino is not affiliated with the official Arduino team. +However, the Linduino is only possible because of the Arduino team's commitment +to the open-source community. Please, visit http://www.arduino.cc and +http://store.arduino.cc , and consider a purchase that will help fund their +ongoing work. +*/ + +//! @defgroup Linduino Linduino: Linear Technology Arduino-Compatible Demonstration Board + +/*! @file + @ingroup Linduino + @ingroup QuikEval + Header File for Linduino Libraries and Demo Code +*/ + +#ifndef LINDUINO_H +#define LINDUINO_H + +//! @name LINDUINO PIN ASSIGNMENTS +//! @{ + +#define QUIKEVAL_GPIO 9 //!< Linduino QuikEval GPIO pin (QuikEval connector pin 14) connects to Arduino pin 9 +#define QUIKEVAL_CS SS //!< QuikEval CS pin (SPI chip select on QuikEval connector pin 6) connects to Arduino SS pin. +#define QUIKEVAL_MUX_MODE_PIN 8 /*!< QUIKEVAL_MUX_MODE_PIN defines the control pin for the QuikEval MUX. +The I2C port's SCL and the SPI port's SCK signals share the same pin on the Linduino's QuikEval connector. +Additionally, the I2C port's SDA and the SPI port's MOSI signals share the same pin on the Linduino's QuikEval connector. +The pair of pins connected to the QuikEval connector is switched using a MUX on the Linduino board. +The control pin to switch the MUX is defined as QUIKEVAL_MUX_MODE_PIN (Arduino pin 8). */ +//! @} + +// Macros +//! Set "pin" low +//! @param pin pin to be driven LOW +#define output_low(pin) digitalWrite(pin, LOW) +//! Set "pin" high +//! @param pin pin to be driven HIGH +#define output_high(pin) digitalWrite(pin, HIGH) +//! Return the state of pin "pin" +//! @param pin pin to be read (HIGH or LOW). +//! @return the state of pin "pin" +#define input(pin) digitalRead(pin) + +//! @todo Make a note about whether Arduino/Linduino is Big Endian or Little Endian. Raspberry Pi appears to be the opposite. +//! This union splits one int16_t (16-bit signed integer) or uint16_t (16-bit unsigned integer) +//! into two uint8_t's (8-bit unsigned integers) and vice versa. + union LT_union_int16_2bytes + { + int16_t LT_int16; //!< 16-bit signed integer to be converted to two bytes + uint16_t LT_uint16; //!< 16-bit unsigned integer to be converted to two bytes + uint8_t LT_byte[2]; //!< 2 bytes (unsigned 8-bit integers) to be converted to a 16-bit signed or unsigned integer + }; + +//! @todo Make a note about whether Arduino/Linduino is Big Endian or Little Endian. Raspberry Pi appears to be the opposite. +//! This union splits one int32_t (32-bit signed integer) or uint32_t (32-bit unsigned integer) +//! into four uint8_t's (8-bit unsigned integers) and vice versa. +union LT_union_int32_4bytes +{ + int32_t LT_int32; //!< 32-bit signed integer to be converted to four bytes + uint32_t LT_uint32; //!< 32-bit unsigned integer to be converted to four bytes + uint8_t LT_byte[4]; //!< 4 bytes (unsigned 8-bit integers) to be converted to a 32-bit signed or unsigned integer +}; + +//! @todo Make a note about whether Arduino/Linduino is Big Endian or Little Endian. Raspberry Pi appears to be the opposite. +//! This union splits one float into four uint8_t's (8-bit unsigned integers) and vice versa. +union LT_union_float_4bytes +{ + float LT_float; //!< float to be converted to four bytes + uint8_t LT_byte[4]; //!< 4 bytes (unsigned 8-bit integers) to be converted to a float +}; + + +#endif // LINDUINO_H diff --git a/src/TriggerScope_V4B/README.md b/src/TriggerScope_V4B/README.md new file mode 100644 index 0000000..76bce99 --- /dev/null +++ b/src/TriggerScope_V4B/README.md @@ -0,0 +1,14 @@ +# Triggerscope +Micro-Manager-specific firmware for the Triggerscope. + +The [Triggerscope 4](http://arc.austinblanco.com/product/triggerscope-4/) +is an Arduino-based device build and sold by "Advanced Research Consulting" +(https://arc.austinblanco.com/) that provides analog and digital outputs. + +This firmware - in combination with the Micro-Manager "TriggerscopeMM" device adapter, +allows for control of 16 analog and 16 digital outputs. Outputs can +transition between settings triggered by a Trigger input +signal at micro-second time scales. For full documentation +of the use of this firmare, see: https://micro-manager.org/wiki/TriggerScopeMM + +To install this firmware, you will first need to download and install the [TeensyDuino package](https://www.pjrc.com/teensy/td_download.html), and the [Arduino IDE](https://www.arduino.cc/en/software). Then power your TriggerScope, connect the USB cable to your computer, and open the file TriggerScope_613MM/TriggerScope_613MM.ino in the [Arduino IDE](https://www.arduino.cc/en/software). Select the port under which your device appears (Tools > Port), and select the Teensy 4.1 (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. diff --git a/src/TriggerScope_V4B/TriggerScope_V4B.ino b/src/TriggerScope_V4B/TriggerScope_V4B.ino new file mode 100644 index 0000000..20d283a --- /dev/null +++ b/src/TriggerScope_V4B/TriggerScope_V4B.ino @@ -0,0 +1,1796 @@ +/****************************************** + Trigger Scope v. 604MM for Arduino microscope control by + ADVANCED RESEARCH CONSULTING @ 2015 + Regents of the University of California, 2020 + + + + Command set: + + Commands to the device are in ASCI and terminated with "\n". + Returns can be multiple lines, terminated with "\r\n" (carriage retun / new line). + Error messages start with "!ERROR_" followed by the command causing the error, semi-colon + and error message (example: "!ERROR_RANGE: Command out of range, DAC=1-16, Range = 1-5...") + + "*" - prints ID (example: "ARC TRIGGERSCOPE 16 R3C v.604-MM" + "?" - pirnts message listing available commands and their parameters + ----------------------analog output functions------------------------- + "SAO" - sets the voltage of specified DAC. Expected format: "DAC1-4236" + where 1 is the line nr, and 4236 the new value to be send to the DAC. + Resulting voltage depends on bit range of the DAC, and the voltage range + Returns: "!DAC1,4236" or error message (not starting with !DAC) on failure. +"PAN" - Queries the number of analog output states that can be pre-programmed. + Format: "PANn". + Where n=1-16 (DAC1-16). + Returns: "!PANn,q" where q is the maximum number of states that can be programmed + "PAO" - Sends sequence of analog output states to be used in triggered sequences. + Format: "PAOn-s-o1-o2-on", where + - n=1-16 for DAC 1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!PA0n-s-q" where q is the number of values successfully inserted in the internal buffer. + "PAC" - Clears the sequence of ananlog states + Format: "PACn", where n is the DAC pinNr (1-16) + "PAS" - Starts triggered transitions in analog output state as pre-loaded in PAO + Format: "PASn-s-t" where + - n=1-16 for DAC 1-16 + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin +"BAO" - Activates blanking mode of the analog output. Output state will be coupled to the + state of the input trigger. If input trigger is active, the DAC will be active ( + as set with SDA), if input trigger is inactive, the output will go (which may not be 0V, depending on the range). + Format: "BAOn-s-t" + - n=1-16 for DAC 1-16 + - s=0 or 1 where 0 stops and 1 starts balnking mode + - t translates state of the input pin to its activity for blanking. If t = 0, + DAC will be 0 when input trigger pin is low. t = 1: DAC will be active when input trigger is low. +"BAD" - Sets a delay in blanking mode of analog output. When the trigger is received to activate the analog output, + wait for the set amount of time (in micro-seconds) to actually activate the analog output. This command will only + have an effect in combination iwth blanking mode (BAO). + Format: "BADn-t" + - n=1-16 for DAC 1-16 + - t=delay between trigger and actually activating the output in micro-seconds (default is 0 for no delay, + no negative numbers allowed, maximum delay is 4,294,967,295) +"BAL" - Sets the lenght of analog output "pulse" in blanking mode" in micro-seconds. After start of the pulse (determined + by BAO and BAD, output will go to 0V after the given number of microseonds, or when the input trigger so dictates + (whichever comes first). + Format: "BALn-t" + - n=1-16 for DAC 1-16 + - t=maximum duration of the output pulse in microseconds (0 for no limit, maximum value is 4,294,967,295) + "SAR" - Sets the output range in volts of the DAC. Example: "SAR2-1" where 2 + specified the DAC number, and 1 the range. Allowed ranges are number 1-5: + 1: 0-5V + 2: 0-10V + 3: -5-+5V + 4: -10-+10V + 5: -2-+2V + Returns: "!RANGE2,1" +-------------------------digital output functions------------------------ +"SDO" - sets TTL outputs of pins1-8 and 9-16 in a single operation + Format: DOn-o + Where n=0 or 1. 0 address pins 1-8, 1 goes to pins 9-16 + 0 is digital output bitmask, 0: all off, 1: pin 1 on, 2: pin 2 on, 3: pins 1&2 on, etc.. + Example: DO1,4 sets pin 3 high and pins 1,2, 4-8 low + Returns: "!DOn-o" or error message ("!ERROR_DO: ") on failure. +"PDN" - Queries the number of digital output states that can be pre-programmed. + Format: "PDNn". + Where n=0 or 1. 0 address pins 1-8, 1 goes to pins 9-16 + Returns: "!PDNn-q" where q is the maximum number of states that can be programmed +"PDO" - Sends sequence of digital output states to be used in triggered sequences. + Format: "PDOn-s-o1-o2-on", where + - n=0 or 1 and directs the command to either pins1-8 or 9-16, + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values to be consecutively inserted in the list. + Returns: "!PD0n-s-q" where q is the number of values successfully inserted in the internal buffer. +"PDC" - Clears the sequence of digital states + Format: "PDCn", where n is the pingroup, 0=pins1-8, 1=pins9-16 +"PDS" - Starts triggered transitions in digital output state as pre-loaded in PDO + Format: "PDSn-s-t" where + - n=0 or 1 and directs the command to either pins 1-8 or 9-16 + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin +"BDO" - Activates blanking mode of the digital output. Output state will be coupled to the + state of the input trigger. If input trigger is active, the pingroup will be active ( + as set with DO), if input trigger is inactive, the pingroup will go low. + Format: "BDOn-s-t" + where n is the pingroup (0 or 1) and s switched blanking mode on (1) or off (0). + t translates state of the input pin to its activity for blanking. If t = 0, + output pins will be low when input trigger pin is low. When t = 1, output pins + will be active when input trigger is low. + "SSL" - Set Signal LEDs. + In some cases, setting signal LEDs slows down triggered operation, so offer the option to + not set them. + Format: "SSLn" where + - n - 0 ("Off") or 1 ("On") + Signal LEDs are On by default + + + +***************************** +Contact Advanced Research Consulting for Driver libraries! www.advancedresearch.consulting + ******************************/ +#include +#include +#include +#include "Linduino.h" + +#define focus 15 //sets focus line # +#define pwrLed 28 //POWER indication +#define dacLed 29 //DAC indication +#define ttlLed 34 //TTL indication +#define trigLed 35 //TRIGGER indication +#define readyLed 32 //READY indication +#define ttlblock2OE 23 +#define ttlblock2DIR 24 +#define ttlblock1OE 21 +#define ttlblock1DIR 22 +#define ttlblock3OE 37 +#define ttlblock3DIR 36 + +#define NR_DACS 16 +#define NR_DAC_STATES 1200 +#define NR_DO_STATES 1200 + + +String idname = "ARC TRIGGERSCOPE 16 R4 Board 4B v.620-MM"; + +const char* helpString = "Available commands: \r\n" + "SAOn-s - sets DACs. n = DAC number (1-16), s = value (0-65535)\r\n" + "PANn - queries the number of programmable analogoutput states, n = DAC pin number (1-16)\r\n" + "PAOn-s-o1-o2-on - sends sequence of programmable analog output states, n = DAC pin number (1-16)\r\n" + " s = 0-based index in the sequence to start inserting, \r\n" + " o = comma separated series of output values (0-65535)\r\n" + "PACn - clears the sequence of analog states, n = DAC pin number (1-16)\r\n" + "PASn-s-t - starts triggered transition in programmable analog output states as programmed in PAO\r\n" + " n = DAC pin number (1-16), s = 0 (stops) or 1 (starts), \r\n" + " t = transition on falling (0) or rising (1) edge of input trigger.\r\n" + "BAOn-s-t starts blanking mode of ananlog output. n = DAC pin number (1-16), \r\n" + " s = 0 (stops) or 1 (starts), t = output low when trigger low (0), or inverse (1)\r\n" + "BADn-t sets delay between blanking transition and setting analog output.\r\n" + " n = DAC pin number (1-16), t = delay in micro-seconds\r\n" + "BALn-t sets maximum duration of the analog output in blanking mode.\r\n" + " n = DAC pin number (1-16), t = maximumi duration in microseconds.\r\n" + "SARn-s - sets DAC voltages range. n = DAC number (1-16), s= 1:0-5V 2:0-10V 3:-5-+5V 4: -10-+10V 5:-2-+2V\r\n" + "SDOn-s - sets digital output state, n = 0(pins1-8) or 1(pins9-16), s binary mask 0-255\r\n" + "PDNn - queries the number of programmable digital output states, n = pin group 0(1-8) or 1 (9-16)\r\n" + "PDOm-s-o1-o2-on - sends sequence of programmable digital output states, n = pin group 0(1-8) or 1(9-16)\r\n" + " s = 0-based index in the sequence to start inserting\r\n" + " o = comma separated series of output states (0-255)\r\n" + "PDCn - clears the sequence of digital states, n = pin group 0(1-8) or 1 (9-16)\r\n" + "PDSn-s-t - starts triggered transitions in digital output state as pre-loaded in PDO, \r\n" + " n = pin group 0(1-8) or 1 (9-16), s = 0 stops 1 starts, \r\n" + " t = transition on falling (0) or rising (1) edge of input trigger.\r\n" + "BDn-s-t - sync digital output with input trigger. n = pin group 0(1-8) or 1 (9-16)\r\n" + " s = 0 stops 1 starts, t = output low when trigger low (0), or inverse (1)\r\n" + "SSLn - switches use of signal LEDs. n=0 (Off) or n=1 (On)\r\n" + "\r\n"; // empty line to signal end of help textd + + +const char sep = '-'; + +//new IC assignments and SD card access for V4 board +File myFile; //create settings file +const int chipSelect = BUILTIN_SDCARD; //set SD card access CS line + +//set up menu modes and vars +byte opMode = 1; //sets operation mode, 1=MANUAL control, 2 = PC CONTROL, 3=TTL Slave via Programmed Steps +boolean trigArmed=false; //enables while loop for just the high speed trig sequencing +unsigned long debugT=0; //debugger flag - not used in a few versions +unsigned long trigInterval; //tracks frequency of inputs from camera or external source +int trigStep=0; //optionally used to sequence more than 1 PROG line at a time, not fully implemented +String inputString = ""; // a string to hold incoming data +boolean stringComplete = false; // whether the string is complete + +//PIN ASSIGNMENTS +const byte DAC[NR_DACS] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; //MOVE THESE FOR CUSTOMERS IF NEEDED! +const byte ttl[NR_DACS] = {5,6,7,8,14,15,16,17,20,25,26,27,38,39,40,41}; //ttl pin #'s +const byte trig[4] = {0,1,2,3}; + +/*HIGH SPEED PROGRAMMING MODE MEMORY BLOCK*/ +int dacArray[NR_DAC_STATES][NR_DACS] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; // DACprogram list +uint8_t ttlArray[NR_DO_STATES][2] = {{0,0}}; // digital output states program list +int ttlArrayMaxIndex[2] = {0, 0}; // maintains the max index in the array that was set +int ttlArrayIndex[2] = {0, 0}; // keeps track of current position in the array +int dacArrayMaxIndex[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int dacArrayIndex[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint32_t dacBlankDelay[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint32_t dacBlankDuration[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +boolean useSignalLEDs_ = true; + +// data structures to be assembled from blanking settings above that have a time-ordered sequence of events +int dacBlankEventsNr = 0; +// there can be a delay and a duration for each blanking event +uint32_t dacBlankEventNextWait[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint8_t dacBlankEventPinNr[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +// 0 : off, 1: set normal state, 2: set value from dacArray +uint8_t dacBlankEventState[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +byte pinGroupState[2] = {0, 0}; +byte pinGroupStoredState[2] = {0, 0}; +bool pinGroupBlanking[2] = {false, false}; +bool pinGroupBlankOnLow[2] = {true, true}; +bool pinGroupSequencing[2] = {false, false}; +byte pinGroupSequenceMode[2] = {0, 0}; // 0: falling edge, 1: rising edge +int dacState[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int dacStoredState[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +bool dacBlanking[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +bool dacBlankOnLow[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +bool dacSequencing[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +byte dacSequenceMode[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +bool triggerPinState = false; + +int delArray[500]; //time delay array for high speed sequences +int focArray[6]; //array for focus stacks = start, step, #loops,direction,slave,current step +uint16_t ttlState = 0; +// boolean ttlActive[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int timeCycles = 1; //used for high speed switching inside of a loop +int runCycles = 0; //holds running position vs total cycles for timelapse + +volatile boolean inTrigger=false; +byte program=0; //this sets the active program # used in the Mode 3 high speed sequencer +byte maxProgram=0; //this holds the maximum program value entered, and is used to set the maxium sequence step. +// byte stepMode = 1; //1 = waits for TTL IN, 2=runs continually +// unsigned long timeOut = 1000; //timeout for sequence (set to 10 seconds by default) +// unsigned long tStart = 0; //sequence start timer +// unsigned long trigLedTimer = 0; +// boolean reportTime = 0; +// boolean umArmIgnore = 0; //this handles micromanagers multiple ARM commands which are issued back-to-back on MDA acqtivation. Ignores after a short wait. +// boolean usepwm = false; +// byte pChannel =0; //number of channels micromanager has attempted to control +// byte lastPT=20; +// byte trigMode = 2; //0 = LOW 1 = HIGH 2 = RISING 3 = FALLING 4 = CHANGE + +bool error = false; + +const char* saoErrorString = "!ERROR_SAO: Format: SAOn-s n=1-16 (DAC1-16), s=value 0-65535"; +const char* sarErrorString = "!ERROR_SAR: Format: SARn-s n=1-16 (DAC=1-16), s=1:0-5V 2:0-10V 3:-5-+5V 4:-10-+10V 5:-2-+2V"; +const char* panErrorString = "!ERROR_PAN: Format: PANn n=1-16 (DAC1-16)"; +const char* paoErrorString = "!ERROR_PAO: Format: PAOn-s-01-02-0n n=1-16 (DAC1-16), s= >=0 (position), 0n=values 0-65535"; +const char* pacErrorString = "!ERROR_PAC: Format: PACn n=1-16 (DAC1-16)"; +const char* pasErrorString = "!ERROR_PAS: Format: PASn-s-t n=1-16 (DAC1-16) s 0=stop 1=start, t=transition on falling(0) or rising(1) edge"; +const char* baoErrorString = "!ERROR_BAO: Format: BAOn-s-t n=1-16 (DAC1-16), s blank 0(off) or 1(on), t 0 (blank on low) or 1 (blank on high)"; +const char* badErrorString = "!ERROR_BAD: Format: BADn-t n=1-16 (DAC1-16), t = 0 - 2147483647"; +const char* balErrorString = "!ERROR_BAD: Format: BALn-t n=1-16 (DAC1-16), t = 0 - 2147483647"; +const char* sdoErrorString = "!ERROR_SDO: Format: SDOn-s n=pingroup 0-1, s=value 0-255"; +const char* pdnErrorString = "!ERROR_PDN: Format: PDNn n=pingroup 0-1"; +const char* pdoErrorString = "!ERROR_PDO: Format: PDOn-s-01-02-0n n=pingroup 0-1, s=position, 0n=values 0-255"; +const char* bdoErrorString = "!ERROR_BDO: Format: BDOn-s-t n=pingroup 0-1, s blank 0(off) or 1(on), t 0 (blank on low) or 1 (blank on high)"; +const char* pdcErrorString = "!ERROR_PDC: Format: PDCn n=pinGroup(1/2)"; +const char* pdsErrorString = "!ERROR_PDS: Format: PDSn-s-t n=pinGroup(1/2) s 0=stop 1=start, t=transition on falling(0) or rising(1) edge"; +const char* sslErrorString = "!ERROR_SSL: Format: SSLn n=0 (Off) or 1 (On)"; +const char* generalErrorString = "ERROR_UNKNOWN_COMMAND"; + + +void setup() +{ + + //indicate power is on for Teensy + pinMode(pwrLed,OUTPUT); + digitalWrite(pwrLed,HIGH); + + //configure LED pins for output + pinMode(trigLed,OUTPUT); + pinMode(ttlLed,OUTPUT); + pinMode(dacLed,OUTPUT); + + //Configure TTL outputs 13-16 + pinMode(ttlblock2OE,OUTPUT); + digitalWrite(ttlblock2OE,LOW); //low for enable + pinMode(ttlblock2DIR,OUTPUT); + digitalWrite(ttlblock2DIR,HIGH); //HIGH to enable 3.3v -> 5V output + + //configure TTL outputs 5-12 + pinMode(ttlblock1OE,OUTPUT); + digitalWrite(ttlblock1OE,LOW); //*** DISABLED FOR BOARDS PRODUCED 12/21 DUE TO LED CONFLICT. low for enable + pinMode(ttlblock1DIR,OUTPUT); + digitalWrite(ttlblock1DIR,HIGH); //HIGH to enable 3.3v -> 5V output + + //Configure TTL IN for TRIG 1-4 + pinMode(ttlblock3OE,OUTPUT); + digitalWrite(ttlblock3OE,LOW); //low for enable + pinMode(ttlblock3DIR,OUTPUT); + digitalWrite(ttlblock3DIR,LOW); //LOW to enable 5v -> 3.3V input + + delay(10); + //configureTrigger(trigMode); //will attach interrupt + for(byte i=0;i<16;++i) { pinMode(ttl[i],OUTPUT); digitalWrite(ttl[i],LOW); } //SET OUTPUT PINS ON TTL AND CAMERA LINES + + Serial.begin(115200); // start serial @ 115,200 baud + while (!Serial) { ; } // wait for serial port + + //read from SD card + //Serial.print("Reading Settings..."); + if (!SD.begin(chipSelect)) { + //Serial.println("SD Read Failure. Contact ARC"); + return; + } + myFile = SD.open("tgs.txt", FILE_WRITE); + if (myFile) { + //Serial.print("Writing to test.txt..."); + myFile.println("testing 1, 2, 3."); + myFile.close(); + Serial.println("done."); + } + else { + // if the file didn't open, print an error: + //Serial.println("SD Read Failure. Contact ARC"); + } + + /***Dac startup ***/ + pinMode(9,OUTPUT); //CLR pin must stay high! + digitalWrite(9,LOW); //CLR Stays high ALWAYAS + delay(50); + digitalWrite(9,HIGH); //CLR Stays high ALWAYAS + delay(50); + + SPI.begin(); + pinMode(10,OUTPUT); // DAC CS + SPI.beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0)); //teensy can do 30000000 !! + + //Drive All DACs & TTLs to 0 + for(int i=1;i<=16;++i) { + setTTL(i,0); + setDac(i, 0); + } + opMode=3; //HYBRID mode default HYBRID=3 / MANUAL=0 / + delay(100); + Serial.println(idname); //issue identifier so software knows we are running + // configureTrigger(trigMode); //will attach interrupt + pinMode(readyLed,OUTPUT); + digitalWrite(readyLed,HIGH); //indicate setup complete + triggerPinState = digitalReadFast(trig[0]); + +} + +void loop() +{ + //************************ DEVICE CONTROL & COMMAND CODE ***********************// + //************************ SERIAL COMMUNICATION CODE ******************/// + /* + if(inTrigger && reportTime) //reports time delay from previous interrupt + { + Serial.print("@TIME ="); + Serial.println(trigInterval); + inTrigger = false; + } + */ + + if (triggerPinState != digitalReadFast(trig[0])) + { + triggerPinState = ! triggerPinState; + if (useSignalLEDs_) + { + digitalWriteDirect(trigLed, triggerPinState); + } + for (byte i = 0; i < NR_DACS; i++) // todo: optimize by ordering an array with sequenceable DACS and only cycle through those + { + if (dacSequencing[i]) + { + if (dacSequenceMode[i] == triggerPinState) + { + dacState[i] = dacArray[dacArrayIndex[i]][i]; + dacArrayIndex[i]++; + if (dacArrayIndex[i] == dacArrayMaxIndex[i]) { dacArrayIndex[i] = 0; } + if (!dacBlanking[i]) + { + setDac(i, dacState[i]); + } + } + } + if (dacBlanking[i]) { + dacBlankOnLow[i] == triggerPinState ? setDac(i, dacState[i]) : setDac(i, 0); + } + } + + for (byte i = 0; i < 2; i++) + { + if (pinGroupSequencing[i]) + { + if (pinGroupSequenceMode[i] == triggerPinState) + { + pinGroupState[i] = ttlArray[ttlArrayIndex[i]][i]; + ttlArrayIndex[i]++; + if (ttlArrayIndex[i] == ttlArrayMaxIndex[i]) { ttlArrayIndex[i] = 0; } + if (!pinGroupBlanking[i]) + { + setPinGroup(i, pinGroupState[i]); + } // if we are blanking, fall through to the code below to set + } + } + if (pinGroupBlanking[i]) + { + pinGroupBlankOnLow[i] == triggerPinState ? setPinGroup(i, pinGroupState[i]) : + setPinGroup(i, 0); + } + } + + + } + + if (stringComplete) // There is a new line terminated string ready to be processed + { + digitalWrite(readyLed,LOW); + + String command = inputString.substring(0,3); + + if (inputString == "?\n") + { + Serial.println(helpString); + } + + else if(inputString == "*\n") + { + Serial.println(idname); + } //return ID line if queried + + // SAO command - Set DAC + // Expected format: "SAO1-4236" where 1 is the line nr, and 4236 the new value to be send to the DAC + else if(command == "SAO") + { + error = false; + byte dacNum = 0; // initialize to error conditino + int offset = 5; + if (inputString[4] == sep) + { + dacNum = atoi(&inputString[3]); + } else if (inputString[5] == sep) + { + dacNum = atoi(inputString.substring(3,5).c_str()); + offset++; + } else + { + error = true; + } + if (dacNum < 1 || dacNum > 16) + { + error = true; + } + + int value = atoi(inputString.substring(offset).c_str()); + if (value < 0 || value > 65535) + { + error = true; + } + + if (!error) //if inputs are valid + { + Serial.print("!SAO"); //print recieve message to operator + Serial.print(dacNum); //print recieve message to operator + Serial.print(sep); + Serial.println(value); + dacState[dacNum - 1] = value; + setDacCheckBlanking(dacNum - 1); + } else + { + Serial.println(saoErrorString); + } + } + + // PAN - + // Queries the number of analog output states that can be pre-programmed. + // Format: "PANn", n is 1-6 for DAC1-16. + // Returns: "!PANn-q" where q is the maximum number of states that can be programmed + else if (command == "PAN") + { + byte dac = atoi(&inputString[3]); + if (dac < 1 || dac > 16) + { + Serial.println(panErrorString); + } else + { + Serial.print("!PAN"); + Serial.print(dac); + Serial.print(sep); + Serial.println(NR_DAC_STATES); + } + } + + /* + * + *"PAO" - Sends sequence of analog output states to be used in triggered sequences. + Format: "PAOn,s,o1,o2,on", where + - n=1-16 for DACS1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!PA0n,s,q" where q is the number of values successfully inserted in the internal buffer. + */ + else if (command == "PAO") + { + error = false; + int n = 0; + int s = 0; + int dacNr = 0; + unsigned int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep){ dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16) { error = true; } + unsigned int ecp = scp + 1; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) { s = inputString.substring(scp, ecp).toInt(); } + else { error = true; } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 65535) + { + error = true; + } else + { + dacArray[n + s][dacNr-1] = val; + n++; + int index = n+s; + if (index > dacArrayMaxIndex[dacNr-1]) + { + dacArrayMaxIndex[dacNr-1] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!PAO%d%c%d%c%d%c%d", dacNr, sep, s, sep, n, sep, dacArrayMaxIndex[dacNr-1]); + Serial.println(out); + } else + { + Serial.println(paoErrorString); + } + } + + + /* + * "PAC" - Clears the sequence of ananlog states + Format: "PACn", where n is the DAC pinNr (1-16) + */ + else if (command == "PAC") + { + error = false; + if (inputString.length() == 5 || inputString.length() == 6) + { + int dacNr = inputString.substring(3).toInt(); + if (dacNr < 1 || dacNr > 16) { error = true; } + if (!error) + { + dacSequencing[dacNr - 1] = false; + dacArrayIndex[dacNr - 1] = 0; + dacArrayMaxIndex[dacNr - 1] = 0; + clearPAO(dacNr - 1); + char out[20]; + sprintf(out, "!PAC%d", dacNr); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pacErrorString); + } + } + + /* + "PAS" - Starts and stops triggered transitions in analog output state as pre-loaded in PAO + Format: "PASn,s,t" where + - n=1-16 (DAC 1-16) + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin + */ + else if (command == "PAS") + { + error = false; + if (inputString.length() == 9 || inputString.length() == 10) + { + error = false; + int dacNr = 0; + int scp = 4; + if (inputString[scp] == sep) { dacNr = inputString.substring(3,scp).toInt(); } + else if (inputString[scp+1] == sep) + { + scp++; + dacNr = inputString.substring(3,scp).toInt(); + } + if (dacNr < 1 || dacNr > 16 || inputString[scp] != sep) + { + error = true; + } + int state = inputString.substring(scp+1,scp+2).toInt(); + if (state < 0 || state > 1) { error = true; } + int rising = inputString.substring(scp+3).toInt(); + if (rising < 0 || rising > 1) { error = true; } + if (!error) + { + dacSequencing[dacNr - 1] = (boolean) state; + dacSequenceMode[dacNr - 1] = rising; + if (state) + { + dacStoredState[dacNr - 1] = dacState[dacNr - 1]; + dacArrayIndex[dacNr - 1] = 0; + if (!rising) { // if we trigger on the falling edge, set initial state now, and advance counter here + setDac(dacNr -1, dacArray[dacArrayIndex[dacNr - 1]][dacNr - 1]); // Check blanking? + dacArrayIndex[dacNr - 1]++; + } + } else + { + dacState[dacNr - 1] = dacStoredState[dacNr - 1]; + } + char out[20]; + sprintf(out, "!PAS%d%c%d%c%d", dacNr, sep, state, sep, rising); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pasErrorString); + } + + } + + /* + "BAO" - Activates blanking mode of the analog output. Output state will be coupled to the + state of the input trigger. If input trigger is active, the DAC will be active ( + as set with SDA), if input trigger is inactive, the output will go (which may not be 0V, depending on the range). + Format: "BAOn,s,t" + - n=1-16 for DACS1-16 + - s=0 or 1 where 0 stops and 1 starts balnking mode + - t translates state of the input pin to its activity for blanking. If t = 0, + DAC will be 0 when input trigger pin is low. t = 1: DAC will be active when input trigger is low. + */ + else if (command == "BAO") + { + error = false; + int dacNr = 0; + int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep) { dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16){ error = true; } + int state = inputString.substring(scp, scp+1).toInt(); + if (state < 0 || state > 1) { error = true; } + if (inputString[scp+1] != sep) { error = true; } + int mode = inputString.substring(scp+2).toInt(); + if (mode < 0 || mode > 1) { error = true; } + if (!error) + { + dacBlanking[dacNr - 1] = state == 1; + dacBlankOnLow[dacNr - 1] = mode == 0; + setDacCheckBlanking(dacNr - 1); + char out[20]; + sprintf(out, "!BAO%d%c%d%c%d", dacNr, sep, state, sep, mode); + Serial.println(out); + } else + { + Serial.println(baoErrorString); + } + } + + /* + "BAD" - Sets a delay in blanking mode of analog output. When the trigger is received to activate the analog output, + wait for the set amount of time (in micro-seconds) to actually activate the analog output. This command will only + have an effect in combination iwth blanking mode (BAO). + Format: "BADn-t" + - n=1-16 for DAC 1-16 + - t=delay between trigger and actually activating the output in micro-seconds (default is 0 for no delay, + no negative numbers allowed, maximum delay is 2,147,483,647) + */ + else if (command == "BAD") + { + error = false; + int dacNr = 0; + int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep) { dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16){ error = true; } + uint32_t duration = inputString.substring(scp).toInt(); + if (!error) + { + dacBlankDelay[dacNr - 1] = duration; + Serial.print("!BAD"); + Serial.print(dacNr); + Serial.print(sep); + Serial.println(duration); + } else + { + Serial.println(badErrorString); + } + } + + /* + * "BAL" - Sets the lenght of analog output "pulse" in blanking mode" in micro-seconds. After start of the pulse (determined + by BAO and BAD, output will go to 0V after the given number of microseonds, or when the input trigger so dictates + (whichever comes first). + Format: "BALn-t" + - n=1-16 for DAC 1-16 + - t=maximum duration of the output pulse in microseconds (0 for no limit, maximum value is 2,147,483,647) + */ + else if (command == "BAL") + { + error = false; + int dacNr = 0; + int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep) { dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16){ error = true; } + uint32_t duration = inputString.substring(scp).toInt(); + if (!error) + { + dacBlankDuration[dacNr - 1] = duration; + Serial.print("!BAL"); + Serial.print(dacNr); + Serial.print(sep); + Serial.println(duration); + } else + { + Serial.println(balErrorString); + } + } + + // SDO + // sets TTL outputs of pins1-8 and 9-16 in a single operation + // Format: SDOn,o + // Where n=0 or 1. 0 address pins 1-8, 1 goes to pins 9-16 + // 0 is digital output bitmask, 0: all off, 1: pin 1 on, 2: pin 2 on, 3: pins 1&2 on, etc.. + // Example: DO1,4 sets pin 3 high and pins 1,2, 4-8 low + else if (command == "SDO") + { + byte pinGroup = atoi(&inputString[3]); + if (pinGroup < 0 || pinGroup > 1) + { + Serial.println(sdoErrorString); + } else + { + int value = atoi(inputString.substring(5).c_str()); + if (value < 0 || value > 255) + { + Serial.println(sdoErrorString); + } else + { + Serial.print("!SDO"); + Serial.print(pinGroup); + Serial.print(sep); + Serial.println(value); + pinGroupState[pinGroup] = value; + setPinGroupCheckBlanking(pinGroup); + } + } + } + + // PDN - + // Queries the number of digital output states that can be pre-programmed. + // Format: "PDNn". + // Returns: "!PDNn,q" where q is the maximum number of states that can be programmed + else if (command == "PDN") + { + byte pinGroup = atoi(&inputString[3]); + if (pinGroup < 0 || pinGroup > 1) + { + Serial.println(pdnErrorString); + } else + { + Serial.print("!PDN"); + Serial.print(pinGroup); + Serial.print(sep); + Serial.println(NR_DO_STATES); + } + } + + //"PDO" - Sends sequence of digital output states to be used in triggered sequences. + // Format: "PDOn-s-o1-o2-on", where + // - n=1 or 2 and directs the command to eitherpins1-8 or 9-16, + // - s: position in the sequence to start inserting values. First position is 1. + // - o1, o2, etc... values to be consecutively inserted in the list. + // Returns: "!PD0n,s,q" where q is the number of values successfully inserted in the internal buffer. + else if (command == "PDO") + { + error = false; + int s = 0; + int n = 0; + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) + { + error = true; + } + if (inputString[4] != sep) + { + error = true; + } + unsigned int scp = 5; + unsigned int ecp = 6; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) + { + s = inputString.substring(scp, ecp).toInt(); + } + else + { + error = true; + } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 255) + { + error = true; + } else + { + ttlArray[n + s][pinGroup] = (byte) val; + n++; + int index = n+s; + if (index > ttlArrayMaxIndex[pinGroup]) + { + ttlArrayMaxIndex[pinGroup] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!PDO%d%c%d%c%d%c%d", pinGroup, sep, s, sep, n, sep, ttlArrayMaxIndex[pinGroup]); + Serial.println(out); + } else + { + Serial.println(pdoErrorString); + } + } + + /* + * "PDC" - Clears the sequence of digital states + Format: "PDCn", where n is the pingroup, 0=pins1-8, 1=pins9-16 + */ + else if (command == "PDC") + { + error = false; + if (inputString.length() == 5) + { + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) { error = true; } + if (!error) + { + pinGroupSequencing[pinGroup] = false; + ttlArrayIndex[pinGroup] = 0; + ttlArrayMaxIndex[pinGroup] = 0; + clearPDO(pinGroup); + char out[20]; + sprintf(out, "!PDC%d", pinGroup); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pdcErrorString); + } + } + + /* + "PDS" - Starts and stops triggered transitions in digital output state as pre-loaded in PDO + Format: "PDSn,s,t" where + - n=0 or 1 and directs the command to either pins 1-8 or 9-16 + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin + */ + else if (command == "PDS") + { + error = false; + if (inputString.length() == 9) + { + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) { error = true; } + int state = inputString.substring(5,6).toInt(); + if (state < 0 || state > 1) { error = true; } + int rising = inputString.substring(7,8).toInt(); + if (rising < 0 || rising > 1) { error = true; } + if (!error) + { + pinGroupSequencing[pinGroup] = (boolean) state; + pinGroupSequenceMode[pinGroup] = rising; + if (state) + { + pinGroupStoredState[pinGroup] = pinGroupState[pinGroup]; + ttlArrayIndex[pinGroup] = 0; + if (!rising) + { // if we trigger on the falling edge, set initial state now, and advance counter here + setPinGroup(pinGroup, ttlArray[ttlArrayIndex[pinGroup]][pinGroup]); // Check blanking? + ttlArrayIndex[pinGroup]++; + } + } else + { + pinGroupState[pinGroup] = pinGroupStoredState[pinGroup]; + } + char out[20]; + sprintf(out, "!PDS%d%c%d%c%d", pinGroup, sep, state, sep, rising); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pdsErrorString); + } + } + + /** + * "BDO" - Activates blanking mode of the digital output. Output state will be coupled to the + * state of the input trigger. If input trigger is active, the pingroup will be active ( + * as set with DO), if input trigger is inactive, the pingroup will go low. + * Format: "BDOn,s,t" where n is the pingroup (0 or 1), s switches blanking mode on (1) + * or off (0), and t sets the mode (0 blank on low, 1 blank on high) + */ + else if (command == "BDO") + { + error = false; + if (inputString.length() == 9) + { + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) { error = true; } + byte state = inputString.substring(5,6).toInt(); + if (state < 0 || state > 1) { error = true; } + byte mode = inputString.substring(7,8).toInt(); + if (mode < 0 || mode > 1) { error = true; } + if (!error) + { + pinGroupBlanking[pinGroup] = state == 1; + pinGroupBlankOnLow[pinGroup] = mode == 0; + setPinGroupCheckBlanking(pinGroup); + char out[20]; + sprintf(out, "!BDO%d%c%d%c%d", pinGroup, sep, state, sep, mode); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(bdoErrorString); + } + } + + /** + * Sets voltage range of DACS + */ + else if(command == "SAR") + { + error = false; + byte dacNum = inputString.substring(3).toInt(); + byte pp = 5; + if(dacNum >9) + { + pp=6; + } + byte rangeVal = inputString.substring(pp).toInt(); + if(rangeVal < 1 || rangeVal > 5) + { + error = true; + } //force to max range + if(dacNum < 1 || dacNum > 16) + { + error = true; + } //confirm if input channel range is valid + if(!error) //if range is OK perform command + { + Serial.print("!SAR"); //print recieve message to operator + Serial.print(dacNum); + Serial.print(sep); + Serial.println(rangeVal); + setDacRange(dacNum-1,rangeVal-1); + // 0 the output + int value = 0; + if (rangeVal > 2) + { + value = 65535 / 2; + } + dacState[dacNum - 1] = value; + setDac(dacNum - 1, value); + } else + { + Serial.println(sarErrorString); + } + } + + // Set Signal LED flag + else if (command == "SSL") + { + error = false; + byte result = inputString.substring(3).toInt();; + if (result == 0) + { + useSignalLEDs_ = false; + digitalWrite(dacLed, 0); + digitalWrite(ttlLed, 0); + digitalWriteDirect(trigLed,0); + } + else if (result == 1) + { + useSignalLEDs_ = true; + // TODO: make sure the LEDs are set correctly? + } + else error = true; + if (!error) + { + Serial.print("!SSL"); + Serial.println(result); + } else + { + Serial.println(sslErrorString); + } + } + + //status commands + else if(inputString == "STAT?\n") {debug(); } + else if(inputString == "TEST?\n") {diagTest(); } + else if(inputString == "CLEAR_ALL\n") {clearTable(); } + else if(inputString.substring(0,9) == "CLEAR_DAC") {clearDac(); } + + else + { + Serial.println(generalErrorString); + } + + + clearSerial(); + digitalWrite(readyLed,HIGH); + } //EXIT LOOP FOR SERIAL HERE + + +/*********************This block runs the high speed control interface *********************/ + +/****checks the acquisition order + * mode 0 == channel first eg set ch1 stweep Z + * mode 1 == Z first EG step Z then Ch1 Ch2 then Step Z ... + */ + +/* + while(trigArmed) + { // just sit here and wait for the next command until armed is off, which can only happen @ end of sequence + unsigned long tStart = millis() + timeOut; //set timeout position + unsigned long tLed = 0; + // focus commands = start, step, #loops,direction,slave,current step + //if(program == 0) {inTrigger=true; } //force a first step + + if( inTrigger ) + { //we recieved a trigger from our source + // and is mode 0, and is at 0 position OR + // and is mode 1, and is at any position OR + // if focus isn't enabled + */ + /* + * When should a channel update be issued? When + * focus is ON, and mode is sweep per channel (0) AND F position is 0 + * focus is ON, and mode is slave to channel (1) + * focus is off any time + */ + + // boolean runUpdate = true; + /* + if( (focArray[1] != 0) && (focArray[4] == 0) ){ //if we are using the focus and focus will sweep through a single channel setting + if( focArray[5] == 0 ) { runUpdate = true;} //AND if the focus is at position 0 (start of sweep) + } + + if( (focArray[1] !=0) && (focArray[4] == 1)) { runUpdate = true; } // Case where channel is switching each Z plane + if(focArray[1] == 0) {runUpdate=true;} //Case where no focus block used so update the channel + */ + /* + //do DAC and TTL control stuff, not focus stuff though + if(runUpdate) { + byte walker=0; + for(walker = 0 ; walker < 15 ; ++walker ){ //sets DACs 1-16 + dac_write(10,0, DAC [walker], dacArray [program] [walker]); // Set DAC Lines + digitalWriteDirect( ttl [walker] , ttlArray[program] [walker] ); //set TTL lines + } + digitalWriteDirect( ttl [walker] , ttlArray[program] [walker] ); //set 16th TTL line - + } + ++program; +*/ + /* THIS MOVES AROUND THE Z FOCUS + * in this case, we assume a trigger was recieved, but we should only mess with focus stuff if it's on and if it's needed + * here we only want to update the focus position in the case that EITHER + * Mode = 0 Channel 1 - Z 1, Z2, Z3 + * mode = 1 Z 1 - CH1, CH2, Z2, Ch1, Ch2 + */ +/* + if( (focArray[1] != 0) && ( focArray[4]==0 )){ fastFocus(); } // if any focus array is active, and MODE = SWEEP OVER CHANNEL + if( (focArray[1] != 0) && ( focArray[4]==1 )){ // in this case sweep is active and MODE = STEP AFTER CHANNEL + if((program == maxProgram-1) && (focArray[5] <= focArray[2])) {fastFocus();} + } + + delay(delArray[program]); //wait for specified delay + + if( focArray[1] == 0) {++program;} //if not using focus at all + if( (focArray[1] != 0) && (focArray[4] == 1) ) { //focus is used but in step mode + ++program; + if( (program > maxProgram) && (focArray[5] != 0) ) { //because we are stepping the focus, program must be reset to 0 in this case we know the focus has not completed, so we can reset the main program array position + program=0; + } + } + */ + } //END OF TRIGGER RESPONSE + + /* + inTrigger=false; //turn off trigger + tStart = millis() + timeOut; //update timeout delta for next run + + //Done moving stuff, now wait for the next input + while(!inTrigger){ //wait for next pulse + if(millis() > tStart) { + trigArmed=false; + program = maxProgram+1; + Serial.println("Timeout Exceeded"); + allOff(); + break; + } + //we hit the timeout so end the sequence + if(tLed < millis() ) + { + digitalWrite(readyLed,!digitalRead(readyLed)); + tLed = millis() + 30; + } + } + + if(program > maxProgram) { //end and cleanup + ++runCycles; + if(runCycles == timeCycles) { //we are done + trigArmed=false; + program=0; + allOff(); + Serial.println("!SAFE"); + digitalWrite(readyLed,HIGH); + } + if(runCycles < timeCycles) {program = 0;} + } + } //close trigarmed +} //close main loop +*/ + + + + +void allOff() +{ + for(byte walker=0;walker<15;++walker) + { //sets DACs 1-16 + dac_write(10,0, DAC [walker], 0); // Set DAC Lines + } + setPinGroup(0,0); + setPinGroup(1,0); +} + + +void clearSerial() +{ + //STUFF TO CLEAN OUT THE SERIAL LINE + inputString = ""; // clear the string: + stringComplete = false; +} + +/*PC DAC CONTROL*/ +void setDac(byte dNum,int dVal) +{ + dac_write(10,0, dNum, dVal); // Send dac_code + //led indication + for (byte d=0; d < 16; d++) + { + if (dacState[dNum] > 0) + { + digitalWrite(dacLed, 1); + return; + } + } + if (useSignalLEDs_) + { + digitalWrite(dacLed, 0); + } +} + +// sets the output state of the given pin number +void setTTL(byte t1, boolean t2) +{ + byte pin = t1 - 1; + digitalWriteFast(ttl[pin], t2); + if(t2) + { + bitSet(ttlState, pin); + } else + { + bitClear(ttlState, pin); + } + if (useSignalLEDs_) + { + digitalWriteFast(ttlLed, ttlState > 0); + } +} + +void setDacCheckBlanking(byte dacNr) +{ + if (dacBlanking[dacNr]) + { + triggerPinState = digitalReadFast(trig[0]); + dacBlankOnLow[dacNr] == triggerPinState ? + setDac(dacNr, dacState[dacNr]) : setDac(dacNr, 0); + } else { + setDac(dacNr, dacState[dacNr]); + } +} + + +void setPinGroupCheckBlanking(byte pinGroup) +{ + if (pinGroupBlanking[pinGroup]) + { + triggerPinState = digitalReadFast(trig[0]); + pinGroupBlankOnLow[pinGroup] == triggerPinState ? + setPinGroup(pinGroup, pinGroupState[pinGroup]) : setPinGroup(pinGroup, 0); + } else { + setPinGroup(pinGroup, pinGroupState[pinGroup]); + } +} + +inline void setPinGroup(byte, byte) __attribute__((always_inline)); +// sets pins 1-8 (pinGroup 0) or 9-16 (pinGroup 1) according to the mask in value +inline void setPinGroup(byte pinGroup, byte value) +{ + byte adder = (pinGroup) * 8; + for (byte b = 0; b < 8; b++) + { + if( b+adder < 9 ) { + digitalWriteFast(ttl[b + adder], (((value) >> (b)) & 0x01) ); + } + + else if( b+adder > 8) { + digitalWriteFast(ttl[b+adder],(((value) >> (b)) & 0x01)); + } + + //Serial.print("TTL " ); + //Serial.print(b + adder); + //Serial.print(" , "); + //Serial.println((((value) >> (b)) & 0x01)); + + } + if (pinGroup == 1) + { + ttlState = (ttlState & 0xff00) | value; + } else + { + ttlState = (ttlState & 0x00ff) | (value << 8); + } + if (useSignalLEDs_) + { + digitalWriteFast(ttlLed, ttlState > 0); + } +} + +/* + SerialEvent occurs whenever a new data comes in the hardware serial RX. This + routine is run between each time loop() runs, so using delay inside loop can + delay response. Multiple bytes of data may be available. +*/ +void serialEvent() { + trigArmed = false; + while (Serial.available()) { + // get the new byte: + char inChar = (char)Serial.read(); + // add it to the inputString: + inputString += inChar; + // if the incoming character is a newline, set a flag + // so the main loop can do something about it: + if (inChar == '\n') { + stringComplete = true; + } + } +} + +/*INTERRUPT CODE FOR TTL INPUT ***************/ +/* +void sigIn() //sigin is the input response line - this recieves the trigger input from an external source +{ + //if(!trigArmed) + //{ + digitalWrite(trigLed,!digitalRead(trigLed)); + //} + inTrigger=true; +} + +void configureTrigger(byte tOption) +{ + trigMode = tOption; //assign input value to global + switch (trigMode) { + case 0: + attachInterrupt(trig[0],sigIn,LOW); + break; + case 1: + attachInterrupt(trig[0],sigIn,HIGH); + break; + case 2: + attachInterrupt(trig[0],sigIn,RISING); + break; + case 3: + attachInterrupt(trig[0],sigIn,FALLING); + break; + case 4: + attachInterrupt(trig[0],sigIn,CHANGE); + break; + } +} +*/ + +void debug() +{ + if(debugT < millis() ) //if this is the first time debug has run, this will execute, otherwise has enough time elapsed? + { + Serial.print("Input Trigger = "); + Serial.println(digitalReadFast(trig[0])); + //Serial.print(", OpMode = "); + //Serial.println(opMode); + + // REPORT TTL + Serial.print("TTL: "); + for(int i=0;i<16;++i) + { + char sOut[100]; + sprintf(sOut,"%d=%d,",i+1, digitalRead(ttl[i])); + Serial.print(sOut); + } + Serial.println(""); + + //REPORT DAC + Serial.print("DAC:"); + for(int i=0;i<16;++i) + { + char sOut[200]; + sprintf(sOut,"%d=%u,",i+1, dacState[i]); + Serial.print(sOut); //used to print sOut + } + + Serial.println(); + Serial.println("Digital output buffers:"); + for (int pg = 0; pg < 2; pg++) + { + if (pg == 0) Serial.print("Pins 1-8 buffer size 1-8: "); + else if (pg == 1) Serial.print("Pins 9-16 buffer size: "); + char sOut[6]; + sprintf(sOut, "%d", ttlArrayMaxIndex[pg]); + Serial.println(sOut); + for (int i = 0; i < ttlArrayMaxIndex[pg]; i++) + { + sprintf(sOut, "%u ", ttlArray[i][pg]); + Serial.print(sOut); + } + if (ttlArrayMaxIndex[pg] > 0) { Serial.println(); } + } + + Serial.println("DAC buffers:"); + for (int dac = 0; dac < NR_DACS; dac++) + { + char sOut[25]; + sprintf(sOut, "DAC buffer size: %d", dacArrayMaxIndex[dac]); + Serial.println(sOut); + for (int i = 0; i < dacArrayMaxIndex[dac]; i++) + { + sprintf(sOut, "%u ", dacArray[i][dac]); + Serial.print(sOut); + } + if (dacArrayMaxIndex[dac] > 0) { Serial.println(); } + } + + } + // send an empty line so that the receiver know we are done + Serial.println(""); +} + + /* + //Report program arrays + if(opMode == 3) + { + Serial.println("Sequencer Programming Status"); + Serial.print("MaxProgram = "); + Serial.println(maxProgram); + Serial.print("PCHANNEL = "); + Serial.println(pChannel); + //report DACs + Serial.println("PROG, DAC1, DAC2, DAC3, DAC4, DAC5, DAC6, DAC7, DAC8, DAC9,DAC10,DAC11,DAC12,DAC13,DAC14,DAC15,DAC16/FOCUS"); + for(int p=0;p= 0; i--) + rx[i] = SPI.transfer(tx[i]); //! 2) Read and send byte array + + output_high(cs_pin); //! 3) Pull CS high +} + +inline void digitalWriteDirect(int, boolean) __attribute__((always_inline)); + +void digitalWriteDirect(int pin, boolean val){ + digitalWriteFast(pin,val); +} + +/* +inline int digitalReadDirect(int) __attribute__((always_inline)); + +int digitalReadDirect(int pin){ + return !!(g_APinDescription[pin].pPort -> PIO_PDSR & g_APinDescription[pin].ulPin); +} + +inline int digitalReadOutputPin(int) __attribute__((always_inline)); + +int digitalReadOutputPin(int pin){ + return !!(g_APinDescription[pin].pPort -> PIO_ODSR & g_APinDescription[pin].ulPin); +} +*/ + + + +/*******************LICENSING INFO************** + + * Copyright (c) 2018, Advanced Research Consulting + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * + *******************/ From a1f33f8e35d225f25a56acffa5f39f6d7768ea06 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 23 Feb 2023 16:55:29 -0800 Subject: [PATCH 02/10] update setDac --- src/TriggerScope_V4B/TriggerScope_V4B.ino | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/TriggerScope_V4B/TriggerScope_V4B.ino b/src/TriggerScope_V4B/TriggerScope_V4B.ino index 20d283a..cdbb630 100644 --- a/src/TriggerScope_V4B/TriggerScope_V4B.ino +++ b/src/TriggerScope_V4B/TriggerScope_V4B.ino @@ -1214,17 +1214,14 @@ void setDac(byte dNum,int dVal) { dac_write(10,0, dNum, dVal); // Send dac_code //led indication - for (byte d=0; d < 16; d++) + if (useSignalLEDs_) { - if (dacState[dNum] > 0) + int dacSum = 0; + for (byte d=0; d < 16; d++) { - digitalWrite(dacLed, 1); - return; + dacSum += dacState[d]; } - } - if (useSignalLEDs_) - { - digitalWrite(dacLed, 0); + digitalWrite(dacLed, dacSum > 0); } } From adf91325c5062d54940d2fdb77cfafea98c99d10 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 23 Feb 2023 16:56:35 -0800 Subject: [PATCH 03/10] comment out variable reinit --- src/TriggerScope_V4B/TriggerScope_V4B.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TriggerScope_V4B/TriggerScope_V4B.ino b/src/TriggerScope_V4B/TriggerScope_V4B.ino index cdbb630..374c9f9 100644 --- a/src/TriggerScope_V4B/TriggerScope_V4B.ino +++ b/src/TriggerScope_V4B/TriggerScope_V4B.ino @@ -1546,7 +1546,7 @@ void diagTest() */ void generateDacBlankEvents() { - + /* int dacArray[NR_DAC_STATES][NR_DACS] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; // DACprogram list uint8_t ttlArray[NR_DO_STATES][2] = {{0,0}}; // digital output states program list int ttlArrayMaxIndex[2] = {0, 0}; // maintains the max index in the array that was set @@ -1568,7 +1568,7 @@ uint32_t dacBlankEventNextWait[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 uint8_t dacBlankEventPinNr[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // 0 : off, 1: set normal state, 2: set value from dacArray uint8_t dacBlankEventState[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; - + */ // note: this should probably be duplicated for both trigger directions, ignore now for simplicity From c9c549221c35f2788e65c1db807ba86dceecb947 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 23 Feb 2023 17:21:56 -0800 Subject: [PATCH 04/10] update readme --- README.md | 7 +++++++ src/TriggerScope_V3/README.md | 18 +++--------------- src/TriggerScope_V4/README.md | 16 +++------------- src/TriggerScope_V4B/README.md | 14 ++------------ 4 files changed, 15 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 88ccc74..b91f618 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,10 @@ allows for control of 16 analog and 16 digital outputs. Outputs can transition between settings triggered by a Trigger input signal at micro-second time scales. For full documentation of the use of this firmare, see: https://micro-manager.org/wiki/TriggerScopeMM + +# Firmwave versions +TriggerScope version 3 boards (serial numbers 1932-1970) must use the TriggerScope_V3 firmware. + +TriggerScope version 4 boards (serial numbers 1971-2117) must use the TriggerScope_V4 firmware. + +TriggerScope version 4B boards (serial numbers 2118 and later) must use the TriggerScope_V4 firmware. \ No newline at end of file diff --git a/src/TriggerScope_V3/README.md b/src/TriggerScope_V3/README.md index c4d9b34..bac160b 100644 --- a/src/TriggerScope_V3/README.md +++ b/src/TriggerScope_V3/README.md @@ -1,16 +1,4 @@ -# Triggerscope -Micro-Manager-specific firmware for the Triggerscope. +# Installation +This firmware is intended for TriggerScope V3 boards (serial numbers 1932-1970). -The [Triggerscope 3](http://arc.austinblanco.com/product/triggerscope-3/) -is an Arduino-based device build and sold by "Advanced Research Consulting" -(https://arc.austinblanco.com/) that provides analog and digital outputs. - -This firmware - in combination with the Micro-Manager "TriggerscopeMM" device adapter, -allows for control of 16 analog and 16 digital outputs. Outputs can -transition between settings triggered by a Trigger input -signal at micro-second time scales. For full documentation -of the use of this firmare, see: https://micro-manager.org/wiki/TriggerScopeMM - -To install this firmware, you download and install the [Arduino IDE](https://www.arduino.cc/en/software). Then power your TriggerScope, connect the USB cable to your computer, and open the file TriggerScope_V3/TriggerScope_V3.ino in the [Arduino IDE](https://www.arduino.cc/en/software). Select the port under which your device appears (Tools > Port), and select the Arduino Due Programmin port (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. - -Some timing measurements using the "blanking" mode for TTL1-8 in Micro-Manager: It takes about 10-13 micro-seconds for the TTL output to respond to a change in the input trigger. Adding sequences to DACs or other outputs will likely increase this delay. +To install this firmware, you download and install the [Arduino IDE](https://www.arduino.cc/en/software). Then power your TriggerScope, connect the USB cable to your computer, and open the file TriggerScope_V3/TriggerScope_V3.ino in the [Arduino IDE](https://www.arduino.cc/en/software). Select the port under which your device appears (Tools > Port), and select the Arduino Due Programmin port (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. \ No newline at end of file diff --git a/src/TriggerScope_V4/README.md b/src/TriggerScope_V4/README.md index 76bce99..4c6f0ea 100644 --- a/src/TriggerScope_V4/README.md +++ b/src/TriggerScope_V4/README.md @@ -1,14 +1,4 @@ -# Triggerscope -Micro-Manager-specific firmware for the Triggerscope. +# Installation +This firmware is intended for TriggerScope V4 boards (serial numbers 1971-2117). -The [Triggerscope 4](http://arc.austinblanco.com/product/triggerscope-4/) -is an Arduino-based device build and sold by "Advanced Research Consulting" -(https://arc.austinblanco.com/) that provides analog and digital outputs. - -This firmware - in combination with the Micro-Manager "TriggerscopeMM" device adapter, -allows for control of 16 analog and 16 digital outputs. Outputs can -transition between settings triggered by a Trigger input -signal at micro-second time scales. For full documentation -of the use of this firmare, see: https://micro-manager.org/wiki/TriggerScopeMM - -To install this firmware, you will first need to download and install the [TeensyDuino package](https://www.pjrc.com/teensy/td_download.html), and the [Arduino IDE](https://www.arduino.cc/en/software). Then power your TriggerScope, connect the USB cable to your computer, and open the file TriggerScope_613MM/TriggerScope_613MM.ino in the [Arduino IDE](https://www.arduino.cc/en/software). Select the port under which your device appears (Tools > Port), and select the Teensy 4.1 (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. +To install this firmware, you will first need to download and install the [TeensyDuino package](https://www.pjrc.com/teensy/td_download.html), and the [Arduino IDE](https://www.arduino.cc/en/software). Then power your TriggerScope, connect the USB cable to your computer, and open the file TriggerScope_V4/TriggerScope_V4.ino in the [Arduino IDE](https://www.arduino.cc/en/software). Select the port under which your device appears (Tools > Port), and select the Teensy 4.1 (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. diff --git a/src/TriggerScope_V4B/README.md b/src/TriggerScope_V4B/README.md index 76bce99..2c4d30d 100644 --- a/src/TriggerScope_V4B/README.md +++ b/src/TriggerScope_V4B/README.md @@ -1,14 +1,4 @@ -# Triggerscope -Micro-Manager-specific firmware for the Triggerscope. - -The [Triggerscope 4](http://arc.austinblanco.com/product/triggerscope-4/) -is an Arduino-based device build and sold by "Advanced Research Consulting" -(https://arc.austinblanco.com/) that provides analog and digital outputs. - -This firmware - in combination with the Micro-Manager "TriggerscopeMM" device adapter, -allows for control of 16 analog and 16 digital outputs. Outputs can -transition between settings triggered by a Trigger input -signal at micro-second time scales. For full documentation -of the use of this firmare, see: https://micro-manager.org/wiki/TriggerScopeMM +# Installation +This firmware is intended for TriggerScope V4B boards (serial numbers 2118 and later). To install this firmware, you will first need to download and install the [TeensyDuino package](https://www.pjrc.com/teensy/td_download.html), and the [Arduino IDE](https://www.arduino.cc/en/software). Then power your TriggerScope, connect the USB cable to your computer, and open the file TriggerScope_613MM/TriggerScope_613MM.ino in the [Arduino IDE](https://www.arduino.cc/en/software). Select the port under which your device appears (Tools > Port), and select the Teensy 4.1 (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. From a480fa1e971de9b06d2c415f425af68942736de9 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 23 Feb 2023 17:28:51 -0800 Subject: [PATCH 05/10] update readme links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b91f618..bc81f9e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Triggerscope Micro-Manager-specific firmware for the Triggerscope. -The Triggerscope (http://arc.austinblanco.com/product/triggerscope-3/) +The Triggerscope (https://advancedresearch-consulting.com/product/triggerscope-4/) is an Arduino-based device build and sold by "Advanced Research Consulting" -(https://arc.austinblanco.com/) that provides analog and digital outputs. +(https://advancedresearch-consulting.com/) that provides analog and digital outputs. This firmware - in combination with the Micro-Manager "TriggerscopeMM" device adapter, allows for control of 16 analog and 16 digital outputs. Outputs can From a01df33df5bf5174054f4627321458d8ff4d612c Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Fri, 24 Feb 2023 09:16:20 -0800 Subject: [PATCH 06/10] add overdrive firmware Co-Authored-By: Austin Blanco <35380024+AdvancedResearchConsulting@users.noreply.github.com> --- .../TriggerScope_V4/Adafruit_MCP23017.cpp | 332 +++ .../TriggerScope_V4/Adafruit_MCP23017.h | 91 + src/Overdrive/TriggerScope_V4/Linduino.h | 109 + src/Overdrive/TriggerScope_V4/README.md | 4 + .../TriggerScope_V4/TriggerScope_V4.ino | 1936 +++++++++++++++++ src/Overdrive/TriggerScope_V4B/Linduino.h | 109 + src/Overdrive/TriggerScope_V4B/README.md | 4 + .../TriggerScope_V4B/TriggerScope_V4B.ino | 1917 ++++++++++++++++ 8 files changed, 4502 insertions(+) create mode 100644 src/Overdrive/TriggerScope_V4/Adafruit_MCP23017.cpp create mode 100644 src/Overdrive/TriggerScope_V4/Adafruit_MCP23017.h create mode 100644 src/Overdrive/TriggerScope_V4/Linduino.h create mode 100644 src/Overdrive/TriggerScope_V4/README.md create mode 100644 src/Overdrive/TriggerScope_V4/TriggerScope_V4.ino create mode 100644 src/Overdrive/TriggerScope_V4B/Linduino.h create mode 100644 src/Overdrive/TriggerScope_V4B/README.md create mode 100644 src/Overdrive/TriggerScope_V4B/TriggerScope_V4B.ino diff --git a/src/Overdrive/TriggerScope_V4/Adafruit_MCP23017.cpp b/src/Overdrive/TriggerScope_V4/Adafruit_MCP23017.cpp new file mode 100644 index 0000000..79d193c --- /dev/null +++ b/src/Overdrive/TriggerScope_V4/Adafruit_MCP23017.cpp @@ -0,0 +1,332 @@ +/*! + * @file Adafruit_MCP23017.cpp + * + * @mainpage Adafruit MCP23017 Library + * + * @section intro_sec Introduction + * + * This is a library for the MCP23017 i2c port expander + * + * These displays use I2C to communicate, 2 pins are required to + * interface + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * @section author Author + * + * Written by Limor Fried/Ladyada for Adafruit Industries. + * + * @section license License + * + * BSD license, all text above must be included in any redistribution + */ + +#ifdef __AVR +#include +#elif defined(ESP8266) +#include +#endif +#include "Adafruit_MCP23017.h" + +#if ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +// minihelper to keep Arduino backward compatibility +static inline void wiresend(uint8_t x, TwoWire *theWire) { +#if ARDUINO >= 100 + theWire->write((uint8_t)x); +#else + theWire->send(x); +#endif +} + +static inline uint8_t wirerecv(TwoWire *theWire) { +#if ARDUINO >= 100 + return theWire->read(); +#else + return theWire->receive(); +#endif +} + +/** + * Bit number associated to a give Pin + */ +uint8_t Adafruit_MCP23017::bitForPin(uint8_t pin) { return pin % 8; } + +/** + * Register address, port dependent, for a given PIN + */ +uint8_t Adafruit_MCP23017::regForPin(uint8_t pin, uint8_t portAaddr, + uint8_t portBaddr) { + return (pin < 8) ? portAaddr : portBaddr; +} + +/** + * Reads a given register + */ +uint8_t Adafruit_MCP23017::readRegister(uint8_t addr) { + // read the current GPINTEN + _wire->beginTransmission(MCP23017_ADDRESS | i2caddr); + wiresend(addr, _wire); + _wire->endTransmission(); + _wire->requestFrom(MCP23017_ADDRESS | i2caddr, 1); + return wirerecv(_wire); +} + +/** + * Writes a given register + */ +void Adafruit_MCP23017::writeRegister(uint8_t regAddr, uint8_t regValue) { + // Write the register + _wire->beginTransmission(MCP23017_ADDRESS | i2caddr); + wiresend(regAddr, _wire); + wiresend(regValue, _wire); + _wire->endTransmission(); +} + +/** + * Helper to update a single bit of an A/B register. + * - Reads the current register value + * - Writes the new register value + */ +void Adafruit_MCP23017::updateRegisterBit(uint8_t pin, uint8_t pValue, + uint8_t portAaddr, + uint8_t portBaddr) { + uint8_t regValue; + uint8_t regAddr = regForPin(pin, portAaddr, portBaddr); + uint8_t bit = bitForPin(pin); + regValue = readRegister(regAddr); + + // set the value for the particular bit + bitWrite(regValue, bit, pValue); + + writeRegister(regAddr, regValue); +} + +//////////////////////////////////////////////////////////////////////////////// + +/*! + * Initializes the MCP23017 given its HW selected address, see datasheet for + * Address selection. + * @param addr Selected address + * @param theWire the I2C object to use, defaults to &Wire + */ +void Adafruit_MCP23017::begin(uint8_t addr, TwoWire *theWire) { + if (addr > 7) { + addr = 7; + } + i2caddr = addr; + _wire = theWire; + + _wire->begin(); + + // set defaults! + // all inputs on port A and B + writeRegister(MCP23017_IODIRA, 0xff); + writeRegister(MCP23017_IODIRB, 0xff); +} + +/** + * Initializes the default MCP23017, with 000 for the configurable part of the + * address + * @param theWire the I2C object to use, defaults to &Wire + */ +void Adafruit_MCP23017::begin(TwoWire *theWire) { begin(0, theWire); } + +/** + * Sets the pin mode to either INPUT or OUTPUT + * @param p Pin to set + * @param d Mode to set the pin + */ +void Adafruit_MCP23017::pinMode(uint8_t p, uint8_t d) { + updateRegisterBit(p, (d == INPUT), MCP23017_IODIRA, MCP23017_IODIRB); +} + +/** + * Reads all 16 pins (port A and B) into a single 16 bits variable. + * @return Returns the 16 bit variable representing all 16 pins + */ +uint16_t Adafruit_MCP23017::readGPIOAB() { + uint16_t ba = 0; + uint8_t a; + + // read the current GPIO output latches + _wire->beginTransmission(MCP23017_ADDRESS | i2caddr); + wiresend(MCP23017_GPIOA, _wire); + _wire->endTransmission(); + + _wire->requestFrom(MCP23017_ADDRESS | i2caddr, 2); + a = wirerecv(_wire); + ba = wirerecv(_wire); + ba <<= 8; + ba |= a; + + return ba; +} + +/** + * Read a single port, A or B, and return its current 8 bit value. + * @param b Decided what gpio to use. Should be 0 for GPIOA, and 1 for GPIOB. + * @return Returns the b bit value of the port + */ +uint8_t Adafruit_MCP23017::readGPIO(uint8_t b) { + + // read the current GPIO output latches + _wire->beginTransmission(MCP23017_ADDRESS | i2caddr); + if (b == 0) + wiresend(MCP23017_GPIOA, _wire); + else { + wiresend(MCP23017_GPIOB, _wire); + } + _wire->endTransmission(); + + _wire->requestFrom(MCP23017_ADDRESS | i2caddr, 1); + return wirerecv(_wire); +} + +/** + * Writes all the pins in one go. This method is very useful if you are + * implementing a multiplexed matrix and want to get a decent refresh rate. + */ +void Adafruit_MCP23017::writeGPIOAB(uint16_t ba) { + _wire->beginTransmission(MCP23017_ADDRESS | i2caddr); + wiresend(MCP23017_GPIOA, _wire); + wiresend(ba & 0xFF, _wire); + wiresend(ba >> 8, _wire); + _wire->endTransmission(); +} + +/*! + * @brief Writes to a pin on the MCP23017 + * @param pin Pin to write to + * @param d What to write to the pin + */ +void Adafruit_MCP23017::digitalWrite(uint8_t pin, uint8_t d) { + uint8_t gpio; + uint8_t bit = bitForPin(pin); + + // read the current GPIO output latches + uint8_t regAddr = regForPin(pin, MCP23017_OLATA, MCP23017_OLATB); + gpio = readRegister(regAddr); + + // set the pin and direction + bitWrite(gpio, bit, d); + + // write the new GPIO + regAddr = regForPin(pin, MCP23017_GPIOA, MCP23017_GPIOB); + writeRegister(regAddr, gpio); +} + +/*! + * @brief Enables the pull-up resistor on the specified pin + * @param p Pin to set + * @param d Value to set the pin + */ +void Adafruit_MCP23017::pullUp(uint8_t p, uint8_t d) { + updateRegisterBit(p, d, MCP23017_GPPUA, MCP23017_GPPUB); +} + +/*! + * @brief Reads the specified pin + * @param pin Pin to read + * @return Value of the pin + */ +uint8_t Adafruit_MCP23017::digitalRead(uint8_t pin) { + uint8_t bit = bitForPin(pin); + uint8_t regAddr = regForPin(pin, MCP23017_GPIOA, MCP23017_GPIOB); + return (readRegister(regAddr) >> bit) & 0x1; +} + +/** + * Configures the interrupt system. both port A and B are assigned the same + * configuration. + * @param mirroring Mirroring will OR both INTA and INTB pins. + * @param openDrain Opendrain will set the INT pin to value or open drain. + * @param polarity polarity will set LOW or HIGH on interrupt. + * Default values after Power On Reset are: (false, false, LOW) + * If you are connecting the INTA/B pin to arduino 2/3, you should configure the + * interupt handling as FALLING with the default configuration. + */ +void Adafruit_MCP23017::setupInterrupts(uint8_t mirroring, uint8_t openDrain, + uint8_t polarity) { + // configure the port A + uint8_t ioconfValue = readRegister(MCP23017_IOCONA); + bitWrite(ioconfValue, 6, mirroring); + bitWrite(ioconfValue, 2, openDrain); + bitWrite(ioconfValue, 1, polarity); + writeRegister(MCP23017_IOCONA, ioconfValue); + + // Configure the port B + ioconfValue = readRegister(MCP23017_IOCONB); + bitWrite(ioconfValue, 6, mirroring); + bitWrite(ioconfValue, 2, openDrain); + bitWrite(ioconfValue, 1, polarity); + writeRegister(MCP23017_IOCONB, ioconfValue); +} + +/** + * Set's up a pin for interrupt. uses arduino MODEs: CHANGE, FALLING, RISING. + * + * Note that the interrupt condition finishes when you read the information + * about the port / value that caused the interrupt or you read the port itself. + * Check the datasheet can be confusing. + * @param pin Pin to set + * @param mode Mode to set the pin + * + */ +void Adafruit_MCP23017::setupInterruptPin(uint8_t pin, uint8_t mode) { + + // set the pin interrupt control (0 means change, 1 means compare against + // given value); + updateRegisterBit(pin, (mode != CHANGE), MCP23017_INTCONA, MCP23017_INTCONB); + // if the mode is not CHANGE, we need to set up a default value, different + // value triggers interrupt + + // In a RISING interrupt the default value is 0, interrupt is triggered when + // the pin goes to 1. In a FALLING interrupt the default value is 1, interrupt + // is triggered when pin goes to 0. + updateRegisterBit(pin, (mode == FALLING), MCP23017_DEFVALA, MCP23017_DEFVALB); + + // enable the pin for interrupt + updateRegisterBit(pin, HIGH, MCP23017_GPINTENA, MCP23017_GPINTENB); +} + +/*! + * @brief Gets the last interrupt pin + * @return Returns the last interrupt pin + */ +uint8_t Adafruit_MCP23017::getLastInterruptPin() { + uint8_t intf; + + // try port A + intf = readRegister(MCP23017_INTFA); + for (int i = 0; i < 8; i++) + if (bitRead(intf, i)) + return i; + + // try port B + intf = readRegister(MCP23017_INTFB); + for (int i = 0; i < 8; i++) + if (bitRead(intf, i)) + return i + 8; + + return MCP23017_INT_ERR; +} +/*! + * @brief Gets the value of the last interrupt pin + * @return Returns the value of the last interrupt pin + */ +uint8_t Adafruit_MCP23017::getLastInterruptPinValue() { + uint8_t intPin = getLastInterruptPin(); + if (intPin != MCP23017_INT_ERR) { + uint8_t intcapreg = regForPin(intPin, MCP23017_INTCAPA, MCP23017_INTCAPB); + uint8_t bit = bitForPin(intPin); + return (readRegister(intcapreg) >> bit) & (0x01); + } + + return MCP23017_INT_ERR; +} diff --git a/src/Overdrive/TriggerScope_V4/Adafruit_MCP23017.h b/src/Overdrive/TriggerScope_V4/Adafruit_MCP23017.h new file mode 100644 index 0000000..dde203c --- /dev/null +++ b/src/Overdrive/TriggerScope_V4/Adafruit_MCP23017.h @@ -0,0 +1,91 @@ +/*! + * @file Adafruit_MCP23017.h + */ + +#ifndef _Adafruit_MCP23017_H_ +#define _Adafruit_MCP23017_H_ + +// Don't forget the Wire library +#ifndef ARDUINO_AVR_GEMMA +// TinyWireM is now part of +// Adafruit version of Wire Library, so this +// will work with Adafruit ATtiny85's +// But Arduino Gemma doesn't use that library +// We do NOT want to include Wire if it's an arduino Gemma +#include +#else +#include +#define Wire TinyWireM +#endif + +/*! + * @brief MCP23017 main class + */ +class Adafruit_MCP23017 { +public: + void begin(uint8_t addr, TwoWire *theWire = &Wire); + void begin(TwoWire *theWire = &Wire); + + void pinMode(uint8_t p, uint8_t d); + void digitalWrite(uint8_t p, uint8_t d); + void pullUp(uint8_t p, uint8_t d); + uint8_t digitalRead(uint8_t p); + + void writeGPIOAB(uint16_t); + uint16_t readGPIOAB(); + uint8_t readGPIO(uint8_t b); + + void setupInterrupts(uint8_t mirroring, uint8_t open, uint8_t polarity); + void setupInterruptPin(uint8_t p, uint8_t mode); + uint8_t getLastInterruptPin(); + uint8_t getLastInterruptPinValue(); + +private: + uint8_t i2caddr; + TwoWire *_wire; //!< pointer to a TwoWire object + + uint8_t bitForPin(uint8_t pin); + uint8_t regForPin(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr); + + uint8_t readRegister(uint8_t addr); + void writeRegister(uint8_t addr, uint8_t value); + + /** + * Utility private method to update a register associated with a pin (whether + * port A/B) reads its value, updates the particular bit, and writes its + * value. + */ + void updateRegisterBit(uint8_t p, uint8_t pValue, uint8_t portAaddr, + uint8_t portBaddr); +}; + +#define MCP23017_ADDRESS 0x20 //!< MCP23017 Address + +// registers +#define MCP23017_IODIRA 0x00 //!< I/O direction register A +#define MCP23017_IPOLA 0x02 //!< Input polarity port register A +#define MCP23017_GPINTENA 0x04 //!< Interrupt-on-change pins A +#define MCP23017_DEFVALA 0x06 //!< Default value register A +#define MCP23017_INTCONA 0x08 //!< Interrupt-on-change control register A +#define MCP23017_IOCONA 0x0A //!< I/O expander configuration register A +#define MCP23017_GPPUA 0x0C //!< GPIO pull-up resistor register A +#define MCP23017_INTFA 0x0E //!< Interrupt flag register A +#define MCP23017_INTCAPA 0x10 //!< Interrupt captured value for port register A +#define MCP23017_GPIOA 0x12 //!< General purpose I/O port register A +#define MCP23017_OLATA 0x14 //!< Output latch register 0 A + +#define MCP23017_IODIRB 0x01 //!< I/O direction register B +#define MCP23017_IPOLB 0x03 //!< Input polarity port register B +#define MCP23017_GPINTENB 0x05 //!< Interrupt-on-change pins B +#define MCP23017_DEFVALB 0x07 //!< Default value register B +#define MCP23017_INTCONB 0x09 //!< Interrupt-on-change control register B +#define MCP23017_IOCONB 0x0B //!< I/O expander configuration register B +#define MCP23017_GPPUB 0x0D //!< GPIO pull-up resistor register B +#define MCP23017_INTFB 0x0F //!< Interrupt flag register B +#define MCP23017_INTCAPB 0x11 //!< Interrupt captured value for port register B +#define MCP23017_GPIOB 0x13 //!< General purpose I/O port register B +#define MCP23017_OLATB 0x15 //!< Output latch register 0 B + +#define MCP23017_INT_ERR 255 //!< Interrupt error + +#endif diff --git a/src/Overdrive/TriggerScope_V4/Linduino.h b/src/Overdrive/TriggerScope_V4/Linduino.h new file mode 100644 index 0000000..85ee651 --- /dev/null +++ b/src/Overdrive/TriggerScope_V4/Linduino.h @@ -0,0 +1,109 @@ +//! @todo Review this file. +/* +Linduino.h + +This file contains the hardware definitions for the Linduino. + +REVISION HISTORY +$Revision: 1906 $ +$Date: 2013-08-26 15:09:18 -0700 (Mon, 26 Aug 2013) $ + +Copyright (c) 2013, Linear Technology Corp.(LTC) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of Linear Technology Corp. + +The Linear Technology Linduino is not affiliated with the official Arduino team. +However, the Linduino is only possible because of the Arduino team's commitment +to the open-source community. Please, visit http://www.arduino.cc and +http://store.arduino.cc , and consider a purchase that will help fund their +ongoing work. +*/ + +//! @defgroup Linduino Linduino: Linear Technology Arduino-Compatible Demonstration Board + +/*! @file + @ingroup Linduino + @ingroup QuikEval + Header File for Linduino Libraries and Demo Code +*/ + +#ifndef LINDUINO_H +#define LINDUINO_H + +//! @name LINDUINO PIN ASSIGNMENTS +//! @{ + +#define QUIKEVAL_GPIO 9 //!< Linduino QuikEval GPIO pin (QuikEval connector pin 14) connects to Arduino pin 9 +#define QUIKEVAL_CS SS //!< QuikEval CS pin (SPI chip select on QuikEval connector pin 6) connects to Arduino SS pin. +#define QUIKEVAL_MUX_MODE_PIN 8 /*!< QUIKEVAL_MUX_MODE_PIN defines the control pin for the QuikEval MUX. +The I2C port's SCL and the SPI port's SCK signals share the same pin on the Linduino's QuikEval connector. +Additionally, the I2C port's SDA and the SPI port's MOSI signals share the same pin on the Linduino's QuikEval connector. +The pair of pins connected to the QuikEval connector is switched using a MUX on the Linduino board. +The control pin to switch the MUX is defined as QUIKEVAL_MUX_MODE_PIN (Arduino pin 8). */ +//! @} + +// Macros +//! Set "pin" low +//! @param pin pin to be driven LOW +#define output_low(pin) digitalWrite(pin, LOW) +//! Set "pin" high +//! @param pin pin to be driven HIGH +#define output_high(pin) digitalWrite(pin, HIGH) +//! Return the state of pin "pin" +//! @param pin pin to be read (HIGH or LOW). +//! @return the state of pin "pin" +#define input(pin) digitalRead(pin) + +//! @todo Make a note about whether Arduino/Linduino is Big Endian or Little Endian. Raspberry Pi appears to be the opposite. +//! This union splits one int16_t (16-bit signed integer) or uint16_t (16-bit unsigned integer) +//! into two uint8_t's (8-bit unsigned integers) and vice versa. + union LT_union_int16_2bytes + { + int16_t LT_int16; //!< 16-bit signed integer to be converted to two bytes + uint16_t LT_uint16; //!< 16-bit unsigned integer to be converted to two bytes + uint8_t LT_byte[2]; //!< 2 bytes (unsigned 8-bit integers) to be converted to a 16-bit signed or unsigned integer + }; + +//! @todo Make a note about whether Arduino/Linduino is Big Endian or Little Endian. Raspberry Pi appears to be the opposite. +//! This union splits one int32_t (32-bit signed integer) or uint32_t (32-bit unsigned integer) +//! into four uint8_t's (8-bit unsigned integers) and vice versa. +union LT_union_int32_4bytes +{ + int32_t LT_int32; //!< 32-bit signed integer to be converted to four bytes + uint32_t LT_uint32; //!< 32-bit unsigned integer to be converted to four bytes + uint8_t LT_byte[4]; //!< 4 bytes (unsigned 8-bit integers) to be converted to a 32-bit signed or unsigned integer +}; + +//! @todo Make a note about whether Arduino/Linduino is Big Endian or Little Endian. Raspberry Pi appears to be the opposite. +//! This union splits one float into four uint8_t's (8-bit unsigned integers) and vice versa. +union LT_union_float_4bytes +{ + float LT_float; //!< float to be converted to four bytes + uint8_t LT_byte[4]; //!< 4 bytes (unsigned 8-bit integers) to be converted to a float +}; + + +#endif // LINDUINO_H diff --git a/src/Overdrive/TriggerScope_V4/README.md b/src/Overdrive/TriggerScope_V4/README.md new file mode 100644 index 0000000..4c6f0ea --- /dev/null +++ b/src/Overdrive/TriggerScope_V4/README.md @@ -0,0 +1,4 @@ +# Installation +This firmware is intended for TriggerScope V4 boards (serial numbers 1971-2117). + +To install this firmware, you will first need to download and install the [TeensyDuino package](https://www.pjrc.com/teensy/td_download.html), and the [Arduino IDE](https://www.arduino.cc/en/software). Then power your TriggerScope, connect the USB cable to your computer, and open the file TriggerScope_V4/TriggerScope_V4.ino in the [Arduino IDE](https://www.arduino.cc/en/software). Select the port under which your device appears (Tools > Port), and select the Teensy 4.1 (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. diff --git a/src/Overdrive/TriggerScope_V4/TriggerScope_V4.ino b/src/Overdrive/TriggerScope_V4/TriggerScope_V4.ino new file mode 100644 index 0000000..5935a6e --- /dev/null +++ b/src/Overdrive/TriggerScope_V4/TriggerScope_V4.ino @@ -0,0 +1,1936 @@ +/****************************************** + Trigger Scope v. 604MM for Arduino microscope control by + ADVANCED RESEARCH CONSULTING @ 2015 + Regents of the University of California, 2020 + + Command set: + + Commands to the device are in ASCI and terminated with "\n". + Returns can be multiple lines, terminated with "\r\n" (carriage retun / new line). + Error messages start with "!ERROR_" followed by the command causing the error, semi-colon + and error message (example: "!ERROR_RANGE: Command out of range, DAC=1-16, Range = 1-5...") + + "*" - prints ID (example: "ARC TRIGGERSCOPE 16 R3C v.604-MM" + "?" - pirnts message listing available commands and their parameters + ----------------------analog output functions------------------------- + "SAO" - sets the voltage of specified DAC. Expected format: "DAC1-4236" + where 1 is the line nr, and 4236 the new value to be send to the DAC. + Resulting voltage depends on bit range of the DAC, and the voltage range + Returns: "!DAC1,4236" or error message (not starting with !DAC) on failure. + "PAN" - Queries the number of analog output states that can be pre-programmed. + Format: "PANn". + Where n=1-16 (DAC1-16). + Returns: "!PANn,q" where q is the maximum number of states that can be programmed + "PAO" - Sends sequence of analog output states to be used in triggered sequences. + Format: "PAOn-s-o1-o2-on", where + - n=1-16 for DAC 1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!PA0n-s-q" where q is the number of values successfully inserted in the internal buffer. + "POV" - Sets an array of overvoltage values for use before typical state transition. + Format: "POVn-s-o1-o2-on", where + - n=1-16 for DAC 1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!PA0n-s-q" where q is the number of values successfully inserted in the internal buffer. + + "POD" - Sets an array of overvoltage delay times for use with POV commands. + Format: "POVn-s-o1-o2-on", where + - n=1-16 for DAC 1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-4294967295) to be consecutively inserted in the list. + Returns: "!PA0n-s-q" where q is the number of values successfully inserted in the internal buffer. + + "PAC" - Clears the sequence of ananlog states + Format: "PACn", where n is the DAC pinNr (1-16) + "PAS" - Starts triggered transitions in analog output state as pre-loaded in PAO + Format: "PASn-s-t" where + - n=1-16 for DAC 1-16 + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin +"BAO" - Activates blanking mode of the analog output. Output state will be coupled to the + state of the input trigger. If input trigger is active, the DAC will be active ( + as set with SDA), if input trigger is inactive, the output will go (which may not be 0V, depending on the range). + Format: "BAOn-s-t" + - n=1-16 for DAC 1-16 + - s=0 or 1 where 0 stops and 1 starts balnking mode + - t translates state of the input pin to its activity for blanking. If t = 0, + DAC will be 0 when input trigger pin is low. t = 1: DAC will be active when input trigger is low. + "SAR" - Sets the output range in volts of the DAC. Example: "SAR2-1" where 2 + specified the DAC number, and 1 the range. Allowed ranges are number 1-5: + 1: 0-5V + 2: 0-10V + 3: -5-+5V + 4: -10-+10V + 5: -2-+2V + Returns: "!RANGE2,1" +-------------------------digital output functions------------------------ +"SDO" - sets TTL outputs of pins1-8 and 9-16 in a single operation + Format: DOn-o + Where n=0 or 1. 0 address pins 1-8, 1 goes to pins 9-16 + 0 is digital output bitmask, 0: all off, 1: pin 1 on, 2: pin 2 on, 3: pins 1&2 on, etc.. + Example: DO1,4 sets pin 3 high and pins 1,2, 4-8 low + Returns: "!DOn-o" or error message ("!ERROR_DO: ") on failure. +"PDN" - Queries the number of digital output states that can be pre-programmed. + Format: "PDNn". + Where n=0 or 1. 0 address pins 1-8, 1 goes to pins 9-16 + Returns: "!PDNn-q" where q is the maximum number of states that can be programmed +"PDO" - Sends sequence of digital output states to be used in triggered sequences. + Format: "PDOn-s-o1-o2-on", where + - n=0 or 1 and directs the command to either pins1-8 or 9-16, + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values to be consecutively inserted in the list. + Returns: "!PD0n-s-q" where q is the number of values successfully inserted in the internal buffer. +"PDC" - Clears the sequence of digital states + Format: "PDCn", where n is the pingroup, 0=pins1-8, 1=pins9-16 +"PDS" - Starts triggered transitions in digital output state as pre-loaded in PDO + Format: "PDSn-s-t" where + - n=0 or 1 and directs the command to either pins 1-8 or 9-16 + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin +"BDO" - Activates blanking mode of the digital output. Output state will be coupled to the + state of the input trigger. If input trigger is active, the pingroup will be active ( + as set with DO), if input trigger is inactive, the pingroup will go low. + Format: "BDOn-s-t" + where n is the pingroup (0 or 1) and s switched blanking mode on (1) or off (0). + t translates state of the input pin to its activity for blanking. If t = 0, + output pins will be low when input trigger pin is low. When t = 1, output pins + will be active when input trigger is low. + "SSL" - Set Signal LEDs. + In some cases, setting signal LEDs slows down triggered operation, so offer the option to + not set them. + Format: "SSLn" where + - n - 0 ("Off") or 1 ("On") + Signal LEDs are On by default + + + +***************************** +Contact Advanced Research Consulting for Driver libraries! www.advancedresearch.consulting + ******************************/ +#include +#include +#include +#include "Linduino.h" +#include "Adafruit_MCP23017.h" + +#define focus 15 //sets focus line # +#define pwrLed 11 //POWER indication +#define dacLed 12 //POWER indication +#define ttlLed 13 //POWER indication +#define trigLed 14 //POWER indication +#define readyLed 15 //POWER indication +#define ttlblock2OE 10 +#define ttlblock2DIR 8 +#define ttlblock1OE 9 +#define ttlblock1DIR 7 + +#define NR_DACS 16 +#define NR_DAC_STATES 1200 +#define NR_DO_STATES 1200 + + +String idname = "ARC TRIGGERSCOPE 16 R3C Board 4 v.613-MM"; + +const char* helpString = "Available commands: \r\n" + "SAOn-s - sets DACs. n = DAC number (1-16), s = value (0-65535)\r\n" + "PANn - queries the number of programmable analogoutput states, n = DAC pin number (1-16)\r\n" + "PAOn-s-o1-o2-on - sends sequence of programmable analog output states, n = DAC pin number (1-16)\r\n" + " s = 0-based index in the sequence to start inserting, \r\n" + " o = comma separated series of output values (0-65535)\r\n" + "POVn-s-o1-o2-on - sends sequence of analog overdrive states, n = DAC pin number (1-16)\r\n" + " s = 0-based index in the sequence to start inserting, \r\n" + " o = comma separated series of output values (0-65535)\r\n" + "PODn-s-o1-o2-on - sends sequence of overdrive durations in microseconds, n = DAC pin number (1-16)\r\n" + " s = 0-based index in the sequence to start inserting, \r\n" + " o = comma separated series of delay times (0-(0-4294967295))\r\n" + + "PACn - clears the sequence of analog states, n = DAC pin number (1-16)\r\n" + "PASn-s-t - starts triggered transition in programmable analog output states as programmed in PAO\r\n" + " n = DAC pin number (1-16), s = 0 (stops) or 1 (starts), \r\n" + " t = transition on falling (0) or rising (1) edge of input trigger.\r\n" + "BAOn-s-t starts blanking mode of ananlog output. n = DAC pin number (1-16), \r\n" + " s = 0 (stops) or 1 (starts), t = output low when trigger low (0), or inverse (1)\r\n" + + + "SARn-s - sets DAC voltages range. n = DAC number (1-16), s= 1:0-5V 2:0-10V 3:-5-+5V 4: -10-+10V 5:-2-+2V\r\n" + "SDOn-s - sets digital output state, n = 0(pins1-8) or 1(pins9-16), s binary mask 0-255\r\n" + "PDNn - queries the number of programmable digital output states, n = pin group 0(1-8) or 1 (9-16)\r\n" + "PDOm-s-o1-o2-on - sends sequence of programmable digital output states, n = pin group 0(1-8) or 1(9-16)\r\n" + " s = 0-based index in the sequence to start inserting\r\n" + " o = comma separated series of output states (0-255)\r\n" + "PDCn - clears the sequence of digital states, n = pin group 0(1-8) or 1 (9-16)\r\n" + "PDSn-s-t - starts triggered transitions in digital output state as pre-loaded in PDO, \r\n" + " n = pin group 0(1-8) or 1 (9-16), s = 0 stops 1 starts, \r\n" + " t = transition on falling (0) or rising (1) edge of input trigger.\r\n" + "BDn-s-t - sync digital output with input trigger. n = pin group 0(1-8) or 1 (9-16)\r\n" + " s = 0 stops 1 starts, t = output low when trigger low (0), or inverse (1)\r\n" + "SSLn - switches use of signal LEDs. n=0 (Off) or n=1 (On)\r\n" + "\r\n"; // empty line to signal end of help textd + + +const char sep = '-'; + +//new IC assignments and SD card access for V4 board +Adafruit_MCP23017 mcp; //create mux object +File myFile; //create settings file +const int chipSelect = BUILTIN_SDCARD; //set SD card access CS line + +//set up menu modes and vars +byte opMode = 1; //sets operation mode, 1=MANUAL control, 2 = PC CONTROL, 3=TTL Slave via Programmed Steps +boolean trigArmed=false; //enables while loop for just the high speed trig sequencing +unsigned long debugT=0; //debugger flag - not used in a few versions +unsigned long trigInterval; //tracks frequency of inputs from camera or external source +int trigStep=0; //optionally used to sequence more than 1 PROG line at a time, not fully implemented +String inputString = ""; // a string to hold incoming data +boolean stringComplete = false; // whether the string is complete + +//PIN ASSIGNMENTS +const byte DAC[NR_DACS] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; //MOVE THESE FOR CUSTOMERS IF NEEDED! +const byte ttl[NR_DACS] = {5,6,7,8,14,15,16,17,20,0,1,2,3,4,5,6}; //ttl pin #'s +const byte trig[4] = {0,1,2,3}; + +/*HIGH SPEED PROGRAMMING MODE MEMORY BLOCK*/ +int dacArray[NR_DAC_STATES][NR_DACS] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; // DACprogram list +uint8_t ttlArray[NR_DO_STATES][2] = {{0,0}}; // digital output states program list +int ttlArrayMaxIndex[2] = {0, 0}; // maintains the max index in the array that was set +int ttlArrayIndex[2] = {0, 0}; // keeps track of current position in the array +int dacArrayMaxIndex[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int dacArrayIndex[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int dacOverdriveIndex[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint32_t dacBlankDelay[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint32_t dacBlankDuration[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint32_t dacBlankExpire[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +boolean useSignalLEDs_ = true; + +// data structures to be assembled from blanking settings above that have a time-ordered sequence of events +int dacBlankEventsNr = 0; +// there can be a delay and a duration for each blanking event +uint32_t dacBlankEventNextWait[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint8_t dacBlankEventPinNr[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +// 0 : off, 1: set normal state, 2: set value from dacArray +uint8_t dacBlankEventState[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +byte pinGroupState[2] = {0, 0}; +byte pinGroupStoredState[2] = {0, 0}; +bool pinGroupBlanking[2] = {false, false}; +bool pinGroupBlankOnLow[2] = {true, true}; +bool pinGroupSequencing[2] = {false, false}; +byte pinGroupSequenceMode[2] = {0, 0}; // 0: falling edge, 1: rising edge +int dacState[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int dacStoredState[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +bool dacBlanking[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +bool dacBlankOnLow[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +bool dacSequencing[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +byte dacSequenceMode[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int povArray[NR_DAC_STATES][NR_DACS] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; // DAC overdrive value list +uint32_t podArray[NR_DAC_STATES][NR_DACS] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; // overdrive delay on state +uint32_t podExpire[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //timers for real overdrive expire +int lastDac[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //holes the last DAC value and used for implement on timer expire + +bool triggerPinState = false; + +int delArray[500]; //time delay array for high speed sequences +int focArray[6]; //array for focus stacks = start, step, #loops,direction,slave,current step +uint16_t ttlState = 0; +// boolean ttlActive[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int timeCycles = 1; //used for high speed switching inside of a loop +int runCycles = 0; //holds running position vs total cycles for timelapse + +volatile boolean inTrigger=false; +byte program=0; //this sets the active program # used in the Mode 3 high speed sequencer +byte maxProgram=0; //this holds the maximum program value entered, and is used to set the maxium sequence step. +uint32_t maxDel = 0; //used fro maximum delay on state inside sequencing - AB addition +// byte stepMode = 1; //1 = waits for TTL IN, 2=runs continually +// unsigned long timeOut = 1000; //timeout for sequence (set to 10 seconds by default) +// unsigned long tStart = 0; //sequence start timer +// unsigned long trigLedTimer = 0; +// boolean reportTime = 0; +// boolean umArmIgnore = 0; //this handles micromanagers multiple ARM commands which are issued back-to-back on MDA acqtivation. Ignores after a short wait. +// boolean usepwm = false; +// byte pChannel =0; //number of channels micromanager has attempted to control +// byte lastPT=20; +// byte trigMode = 2; //0 = LOW 1 = HIGH 2 = RISING 3 = FALLING 4 = CHANGE + +bool error = false; + +const char* saoErrorString = "!ERROR_SAO: Format: SAOn-s n=1-16 (DAC1-16), s=value 0-65535"; +const char* sarErrorString = "!ERROR_SAR: Format: SARn-s n=1-16 (DAC=1-16), s=1:0-5V 2:0-10V 3:-5-+5V 4:-10-+10V 5:-2-+2V"; +const char* panErrorString = "!ERROR_PAN: Format: PANn n=1-16 (DAC1-16)"; +const char* paoErrorString = "!ERROR_PAO: Format: PAOn-s-01-02-0n n=1-16 (DAC1-16), s= >=0 (position), 0n=values 0-65535"; +const char* pacErrorString = "!ERROR_PAC: Format: PACn n=1-16 (DAC1-16)"; +const char* baoErrorString = "!ERROR_BAO: Format: BAOn-s-t n=1-16 (DAC1-16), s blank 0(off) or 1(on), t 0 (blank on low) or 1 (blank on high)"; +const char* povErrorString = "!ERROR_POV: Format: POVn-s-01-02-0n n=1-16 (DAC1-16), s= >=0 (position), 0n=values 0-65535"; +const char* podErrorString = "!ERROR_POD: Format: PODn-s-01-02-0n n=1-16 (DAC1-16), s= >=0 (position), 0n=values 0-4294967295"; +const char* pasErrorString = "!ERROR_PAS: Format: PASn-s-t n=1-16 (DAC1-16) s 0=stop 1=start, t=transition on falling(0) or rising(1) edge"; +const char* sdoErrorString = "!ERROR_SDO: Format: SDOn-s n=pingroup 0-1, s=value 0-255"; +const char* pdnErrorString = "!ERROR_PDN: Format: PDNn n=pingroup 0-1"; +const char* pdoErrorString = "!ERROR_PDO: Format: PDOn-s-01-02-0n n=pingroup 0-1, s=position, 0n=values 0-255"; +const char* bdoErrorString = "!ERROR_BDO: Format: BDOn-s-t n=pingroup 0-1, s blank 0(off) or 1(on), t 0 (blank on low) or 1 (blank on high)"; +const char* pdcErrorString = "!ERROR_PDC: Format: PDCn n=pinGroup(1/2)"; +const char* pdsErrorString = "!ERROR_PDS: Format: PDSn-s-t n=pinGroup(1/2) s 0=stop 1=start, t=transition on falling(0) or rising(1) edge"; +const char* sslErrorString = "!ERROR_SSL: Format: SSLn n=0 (Off) or 1 (On)"; +const char* generalErrorString = "ERROR_UNKNOWN_COMMAND"; + + +void setup() +{ + mcp.begin(0x27); //turn on MUX comms + + for(int i=0;i<16;++i) { //configure MCP pins as outputs + mcp.pinMode(i, OUTPUT); + mcp.digitalWrite(i,LOW); + } + mcp.digitalWrite(pwrLed,HIGH); //indicate setup complete + + //configure TTL outputs 5-12 + mcp.digitalWrite(ttlblock2OE,LOW); //low for enable + mcp.digitalWrite(ttlblock2DIR,HIGH); //high to enable 3.3v -> 5V output + + //configure TTL outputs 13-16 & TRIG 1-4 + mcp.digitalWrite(ttlblock1OE,LOW); //low for enable + mcp.digitalWrite(ttlblock1DIR,LOW); //high to enable 3.3v -> 5V output + delay(10); + //configureTrigger(trigMode); //will attach interrupt + for(byte i=0;i<9;++i) { pinMode(ttl[i],OUTPUT); digitalWrite(ttl[i],LOW); } //SET OUTPUT PINS ON TTL AND CAMERA LINES + for(byte i=9;i<16;++i) { + mcp.pinMode(ttl[i],OUTPUT); + delay(5); + mcp.digitalWrite(ttl[i],LOW); + delay(10); + } //SET OUTPUT PINS ON TTL AND CAMERA LINES + + Serial.begin(115200); // start serial @ 115,200 baud + while (!Serial) { ; } // wait for serial port + + //read from SD card + //Serial.print("Reading Settings..."); + if (!SD.begin(chipSelect)) { + //Serial.println("SD Read Failure. Contact ARC"); + return; + } + myFile = SD.open("tgs.txt", FILE_WRITE); + if (myFile) { + //Serial.print("Writing to test.txt..."); + myFile.println("testing 1, 2, 3."); + myFile.close(); + Serial.println("done."); + } + else { + // if the file didn't open, print an error: + //Serial.println("SD Read Failure. Contact ARC"); + } + + /***Dac startup ***/ + pinMode(9,OUTPUT); //CLR pin must stay high! + digitalWrite(9,LOW); //CLR Stays high ALWAYAS + delay(50); + digitalWrite(9,HIGH); //CLR Stays high ALWAYAS + delay(50); + + SPI.begin(); + pinMode(10,OUTPUT); // DAC CS + SPI.beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0)); //teensy can do 30000000 !! + + //Drive All DACs & TTLs to 0 + for(int i=1;i<=16;++i) { + setTTL(i,0); + setDac(i, 0); + } + opMode=3; //HYBRID mode default HYBRID=3 / MANUAL=0 / + delay(100); + Serial.println(idname); //issue identifier so software knows we are running + // configureTrigger(trigMode); //will attach interrupt + mcp.digitalWrite(15,HIGH); //indicate setup complete + triggerPinState = digitalReadFast(trig[0]); + +} + +void loop() +{ + //************************ DEVICE CONTROL & COMMAND CODE ***********************// + //************************ SERIAL COMMUNICATION CODE ******************/// + /* + if(inTrigger && reportTime) //reports time delay from previous interrupt + { + Serial.print("@TIME ="); + Serial.println(trigInterval); + inTrigger = false; + } + */ + for (byte i = 0; i < NR_DACS; i++) // investigates if an overdrive or delay timer is enabled + { + if (podExpire[i] != 0) + { // delay is still on + if (podExpire[i] < micros()) + { // time has expired + setDac(i, lastDac[i]); //issue normal on state value + podExpire[i] = 0; //remove exire timer + } + } + } + if (triggerPinState != digitalReadFast(trig[0])) + { + triggerPinState = ! triggerPinState; + if (useSignalLEDs_) + { + digitalWriteDirect(trigLed, triggerPinState); + } + for (byte i = 0; i < NR_DACS; i++) // todo: optimize by ordering an array with sequenceable DACS and only cycle through those + { + if (dacSequencing[i]) + { + if (dacSequenceMode[i] == triggerPinState) + { + dacState[i] = dacArray[dacArrayIndex[i]][i]; + dacArrayIndex[i]++; + + if (dacArrayIndex[i] == dacArrayMaxIndex[i]) { dacArrayIndex[i] = 0; } + if (!dacBlanking[i]) + { + if(povArray[dacArrayIndex[i]][i] ) //if pov has an overdrive value + { + setDac(i, povArray[dacArrayIndex[i]][i]); //issue overdrive + podExpire[i] = micros() + podArray[dacArrayIndex[i]][i]; //set disable timer + lastDac[i] = dacState[i]; //save the normal dac output value for later update + } + else{ + setDac(i, dacState[i]); + } + } + + } + } + if (dacBlanking[i]) { + dacBlankOnLow[i] == triggerPinState ? setDac(i, dacState[i]) : setDac(i, 0); + } + } + + for (byte i = 0; i < 2; i++) + { + if (pinGroupSequencing[i]) + { + if (pinGroupSequenceMode[i] == triggerPinState) + { + pinGroupState[i] = ttlArray[ttlArrayIndex[i]][i]; + ttlArrayIndex[i]++; + if (ttlArrayIndex[i] == ttlArrayMaxIndex[i]) { ttlArrayIndex[i] = 0; } + if (!pinGroupBlanking[i]) + { + setPinGroup(i, pinGroupState[i]); + } // if we are blanking, fall through to the code below to set + } + } + if (pinGroupBlanking[i]) + { + pinGroupBlankOnLow[i] == triggerPinState ? setPinGroup(i, pinGroupState[i]) : + setPinGroup(i, 0); + } + } + //delayMicroseconds(maxDel); //AB addition + for (byte i = 0; i < NR_DACS; i++) // todo: optimize by ordering an array with sequenceable DACS and only cycle through those + { + if (dacSequencing[i]) + { + if(dacBlankDuration[i] > 0){ //AB addition + setDac(i, 0); + } + } + } + + } + + if (stringComplete) // There is a new line terminated string ready to be processed + { + digitalWrite(readyLed,LOW); + + String command = inputString.substring(0,3); + + if (inputString == "?\n") + { + Serial.println(helpString); + } + + else if(inputString == "*\n") + { + Serial.println(idname); + } //return ID line if queried + + // SAO command - Set DAC + // Expected format: "SAO1-4236" where 1 is the line nr, and 4236 the new value to be send to the DAC + else if(command == "SAO") + { + error = false; + byte dacNum = 0; // initialize to error conditino + int offset = 5; + if (inputString[4] == sep) + { + dacNum = atoi(&inputString[3]); + } else if (inputString[5] == sep) + { + dacNum = atoi(inputString.substring(3,5).c_str()); + offset++; + } else + { + error = true; + } + if (dacNum < 1 || dacNum > 16) + { + error = true; + } + + int value = atoi(inputString.substring(offset).c_str()); + if (value < 0 || value > 65535) + { + error = true; + } + + if (!error) //if inputs are valid + { + Serial.print("!SAO"); //print recieve message to operator + Serial.print(dacNum); //print recieve message to operator + Serial.print(sep); + Serial.println(value); + dacState[dacNum - 1] = value; + setDacCheckBlanking(dacNum - 1); + } else + { + Serial.println(saoErrorString); + } + } + + // PAN - + // Queries the number of analog output states that can be pre-programmed. + // Format: "PANn", n is 1-6 for DAC1-16. + // Returns: "!PANn-q" where q is the maximum number of states that can be programmed + else if (command == "PAN") + { + byte dac = atoi(&inputString[3]); + if (dac < 1 || dac > 16) + { + Serial.println(panErrorString); + } else + { + Serial.print("!PAN"); + Serial.print(dac); + Serial.print(sep); + Serial.println(NR_DAC_STATES); + } + } + + /* + * + *"PAO" - Sends sequence of analog output states to be used in triggered sequences. + Format: "PAOn,s,o1,o2,on", where + - n=1-16 for DACS1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!PA0n,s,q" where q is the number of values successfully inserted in the internal buffer. + */ + else if (command == "PAO") + { + error = false; + int n = 0; + int s = 0; + int dacNr = 0; + unsigned int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep){ dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16) { error = true; } + unsigned int ecp = scp + 1; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) { s = inputString.substring(scp, ecp).toInt(); } + else { error = true; } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 65535) + { + error = true; + } else + { + dacArray[n + s][dacNr-1] = val; + n++; + int index = n+s; + if (index > dacArrayMaxIndex[dacNr-1]) + { + dacArrayMaxIndex[dacNr-1] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!PAO%d%c%d%c%d%c%d", dacNr, sep, s, sep, n, sep, dacArrayMaxIndex[dacNr-1]); + Serial.println(out); + } else + { + Serial.println(paoErrorString); + } + } + + + /* + * "PAC" - Clears the sequence of ananlog states + Format: "PACn", where n is the DAC pinNr (1-16) + */ + else if (command == "PAC") + { + error = false; + if (inputString.length() == 5 || inputString.length() == 6) + { + int dacNr = inputString.substring(3).toInt(); + if (dacNr < 1 || dacNr > 16) { error = true; } + if (!error) + { + dacSequencing[dacNr - 1] = false; + dacArrayIndex[dacNr - 1] = 0; + dacArrayMaxIndex[dacNr - 1] = 0; + dacBlankDelay[dacNr - 1] = 0; + dacBlankDuration[dacNr - 1] = 0; + clearPAO(dacNr - 1); + clearPOD(dacNr - 1);//addition for overdrive + clearPOV(dacNr - 1);//addition for overdrive + for ( byte d = 0;d 16 || inputString[scp] != sep) + { + error = true; + } + int state = inputString.substring(scp+1,scp+2).toInt(); + if (state < 0 || state > 1) { error = true; } + int rising = inputString.substring(scp+3).toInt(); + if (rising < 0 || rising > 1) { error = true; } + if (!error) + { + dacSequencing[dacNr - 1] = (boolean) state; + dacSequenceMode[dacNr - 1] = rising; + if (state) + { + dacStoredState[dacNr - 1] = dacState[dacNr - 1]; + dacArrayIndex[dacNr - 1] = 0; + if (!rising) { // if we trigger on the falling edge, set initial state now, and advance counter here + setDac(dacNr -1, dacArray[dacArrayIndex[dacNr - 1]][dacNr - 1]); // Check blanking? + dacArrayIndex[dacNr - 1]++; + } + } else + { + dacState[dacNr - 1] = dacStoredState[dacNr - 1]; + } + char out[20]; + sprintf(out, "!PAS%d%c%d%c%d", dacNr, sep, state, sep, rising); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pasErrorString); + } + + } + + + + + /* + * + *"POV" - Sends sequence of analog overdrive states to be used in triggered sequences. + Format: "POVn,s,o1,o2,on", where + - n=1-16 for DACS1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!POVn,s,q" where q is the number of values successfully inserted in the internal buffer. + */ + else if (command == "POV") + { + error = false; + int n = 0; + int s = 0; + int dacNr = 0; + unsigned int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep){ dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16) { error = true; } + unsigned int ecp = scp + 1; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) { s = inputString.substring(scp, ecp).toInt(); } + else { error = true; } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 65535) + { + error = true; + } else + { + povArray[n + s][dacNr-1] = val; + n++; + int index = n+s; + if (index > dacArrayMaxIndex[dacNr-1]) + { + dacArrayMaxIndex[dacNr-1] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!POV%d%c%d%c%d%c%d", dacNr, sep, s, sep, n, sep, dacArrayMaxIndex[dacNr-1]); + Serial.println(out); + } else + { + Serial.println(povErrorString); + } + } + + /* + * + *"POD" - Sends sequence of delay times for anaog overdrive staates specified in POV. + Format: "PODn,s,o1,o2,on", where + - n=1-16 for DACS1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (uInt32_t) to be consecutively inserted in the list. + Returns: "!PODn,s,q" where q is the number of values successfully inserted in the internal buffer. + */ + else if (command == "POD") + { + error = false; + int n = 0; + int s = 0; + int dacNr = 0; + unsigned int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep){ dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16) { error = true; } + unsigned int ecp = scp + 1; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) { s = inputString.substring(scp, ecp).toInt(); } + else { error = true; } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 65535) + { + error = true; + } else + { + podArray[n + s][dacNr-1] = val; + n++; + int index = n+s; + if (index > dacArrayMaxIndex[dacNr-1]) + { + dacArrayMaxIndex[dacNr-1] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!POD%d%c%d%c%d%c%d", dacNr, sep, s, sep, n, sep, dacArrayMaxIndex[dacNr-1]); + Serial.println(out); + } else + { + Serial.println(podErrorString); + } + } + + + + /* + "BAO" - Activates blanking mode of the analog output. Output state will be coupled to the + state of the input trigger. If input trigger is active, the DAC will be active ( + as set with SDA), if input trigger is inactive, the output will go (which may not be 0V, depending on the range). + Format: "BAOn,s,t" + - n=1-16 for DACS1-16 + - s=0 or 1 where 0 stops and 1 starts balnking mode + - t translates state of the input pin to its activity for blanking. If t = 0, + DAC will be 0 when input trigger pin is low. t = 1: DAC will be active when input trigger is low. + */ + else if (command == "BAO") + { + error = false; + int dacNr = 0; + int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep) { dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16){ error = true; } + int state = inputString.substring(scp, scp+1).toInt(); + if (state < 0 || state > 1) { error = true; } + if (inputString[scp+1] != sep) { error = true; } + int mode = inputString.substring(scp+2).toInt(); + if (mode < 0 || mode > 1) { error = true; } + if (!error) + { + dacBlanking[dacNr - 1] = state == 1; + dacBlankOnLow[dacNr - 1] = mode == 0; + setDacCheckBlanking(dacNr - 1); + char out[20]; + sprintf(out, "!BAO%d%c%d%c%d", dacNr, sep, state, sep, mode); + Serial.println(out); + } else + { + Serial.println(baoErrorString); + } + } + + + // SDO + // sets TTL outputs of pins1-8 and 9-16 in a single operation + // Format: SDOn,o + // Where n=0 or 1. 0 address pins 1-8, 1 goes to pins 9-16 + // 0 is digital output bitmask, 0: all off, 1: pin 1 on, 2: pin 2 on, 3: pins 1&2 on, etc.. + // Example: DO1,4 sets pin 3 high and pins 1,2, 4-8 low + else if (command == "SDO") + { + byte pinGroup = atoi(&inputString[3]); + if (pinGroup < 0 || pinGroup > 1) + { + Serial.println(sdoErrorString); + } else + { + int value = atoi(inputString.substring(5).c_str()); + if (value < 0 || value > 255) + { + Serial.println(sdoErrorString); + } else + { + Serial.print("!SDO"); + Serial.print(pinGroup); + Serial.print(sep); + Serial.println(value); + pinGroupState[pinGroup] = value; + setPinGroupCheckBlanking(pinGroup); + } + } + } + + // PDN - + // Queries the number of digital output states that can be pre-programmed. + // Format: "PDNn". + // Returns: "!PDNn,q" where q is the maximum number of states that can be programmed + else if (command == "PDN") + { + byte pinGroup = atoi(&inputString[3]); + if (pinGroup < 0 || pinGroup > 1) + { + Serial.println(pdnErrorString); + } else + { + Serial.print("!PDN"); + Serial.print(pinGroup); + Serial.print(sep); + Serial.println(NR_DO_STATES); + } + } + + //"PDO" - Sends sequence of digital output states to be used in triggered sequences. + // Format: "PDOn-s-o1-o2-on", where + // - n=1 or 2 and directs the command to eitherpins1-8 or 9-16, + // - s: position in the sequence to start inserting values. First position is 1. + // - o1, o2, etc... values to be consecutively inserted in the list. + // Returns: "!PD0n,s,q" where q is the number of values successfully inserted in the internal buffer. + else if (command == "PDO") + { + error = false; + int s = 0; + int n = 0; + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) + { + error = true; + } + if (inputString[4] != sep) + { + error = true; + } + unsigned int scp = 5; + unsigned int ecp = 6; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) + { + s = inputString.substring(scp, ecp).toInt(); + } + else + { + error = true; + } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 255) + { + error = true; + } else + { + ttlArray[n + s][pinGroup] = (byte) val; + n++; + int index = n+s; + if (index > ttlArrayMaxIndex[pinGroup]) + { + ttlArrayMaxIndex[pinGroup] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!PDO%d%c%d%c%d%c%d", pinGroup, sep, s, sep, n, sep, ttlArrayMaxIndex[pinGroup]); + Serial.println(out); + } else + { + Serial.println(pdoErrorString); + } + } + + /* + * "PDC" - Clears the sequence of digital states + Format: "PDCn", where n is the pingroup, 0=pins1-8, 1=pins9-16 + */ + else if (command == "PDC") + { + error = false; + if (inputString.length() == 5) + { + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) { error = true; } + if (!error) + { + pinGroupSequencing[pinGroup] = false; + ttlArrayIndex[pinGroup] = 0; + ttlArrayMaxIndex[pinGroup] = 0; + clearPDO(pinGroup); + char out[20]; + sprintf(out, "!PDC%d", pinGroup); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pdcErrorString); + } + } + + /* + "PDS" - Starts and stops triggered transitions in digital output state as pre-loaded in PDO + Format: "PDSn,s,t" where + - n=0 or 1 and directs the command to either pins 1-8 or 9-16 + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin + */ + else if (command == "PDS") + { + error = false; + if (inputString.length() == 9) + { + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) { error = true; } + int state = inputString.substring(5,6).toInt(); + if (state < 0 || state > 1) { error = true; } + int rising = inputString.substring(7,8).toInt(); + if (rising < 0 || rising > 1) { error = true; } + if (!error) + { + pinGroupSequencing[pinGroup] = (boolean) state; + pinGroupSequenceMode[pinGroup] = rising; + if (state) + { + pinGroupStoredState[pinGroup] = pinGroupState[pinGroup]; + ttlArrayIndex[pinGroup] = 0; + if (!rising) + { // if we trigger on the falling edge, set initial state now, and advance counter here + setPinGroup(pinGroup, ttlArray[ttlArrayIndex[pinGroup]][pinGroup]); // Check blanking? + ttlArrayIndex[pinGroup]++; + } + } else + { + pinGroupState[pinGroup] = pinGroupStoredState[pinGroup]; + } + char out[20]; + sprintf(out, "!PDS%d%c%d%c%d", pinGroup, sep, state, sep, rising); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pdsErrorString); + } + } + + /** + * "BDO" - Activates blanking mode of the digital output. Output state will be coupled to the + * state of the input trigger. If input trigger is active, the pingroup will be active ( + * as set with DO), if input trigger is inactive, the pingroup will go low. + * Format: "BDOn,s,t" where n is the pingroup (0 or 1), s switches blanking mode on (1) + * or off (0), and t sets the mode (0 blank on low, 1 blank on high) + */ + else if (command == "BDO") + { + error = false; + if (inputString.length() == 9) + { + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) { error = true; } + byte state = inputString.substring(5,6).toInt(); + if (state < 0 || state > 1) { error = true; } + byte mode = inputString.substring(7,8).toInt(); + if (mode < 0 || mode > 1) { error = true; } + if (!error) + { + pinGroupBlanking[pinGroup] = state == 1; + pinGroupBlankOnLow[pinGroup] = mode == 0; + setPinGroupCheckBlanking(pinGroup); + char out[20]; + sprintf(out, "!BDO%d%c%d%c%d", pinGroup, sep, state, sep, mode); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(bdoErrorString); + } + } + + /** + * Sets voltage range of DACS + */ + else if(command == "SAR") + { + error = false; + byte dacNum = inputString.substring(3).toInt(); + byte pp = 5; + if(dacNum >9) + { + pp=6; + } + byte rangeVal = inputString.substring(pp).toInt(); + if(rangeVal < 1 || rangeVal > 5) + { + error = true; + } //force to max range + if(dacNum < 1 || dacNum > 16) + { + error = true; + } //confirm if input channel range is valid + if(!error) //if range is OK perform command + { + Serial.print("!SAR"); //print recieve message to operator + Serial.print(dacNum); + Serial.print(sep); + Serial.println(rangeVal); + setDacRange(dacNum-1,rangeVal-1); + // 0 the output + int value = 0; + if (rangeVal > 2) + { + value = 65535 / 2; + } + dacState[dacNum - 1] = value; + setDac(dacNum - 1, value); + } else + { + Serial.println(sarErrorString); + } + } + + // Set Signal LED flag + else if (command == "SSL") + { + error = false; + byte result = inputString.substring(3).toInt();; + if (result == 0) + { + useSignalLEDs_ = false; + digitalWrite(dacLed, 0); + digitalWrite(ttlLed, 0); + digitalWriteDirect(trigLed,0); + } + else if (result == 1) + { + useSignalLEDs_ = true; + // TODO: make sure the LEDs are set correctly? + } + else error = true; + if (!error) + { + Serial.print("!SSL"); + Serial.println(result); + } else + { + Serial.println(sslErrorString); + } + } + + //status commands + else if(inputString == "STAT?\n") {debug(); } + else if(inputString == "TEST?\n") {diagTest(); } + else if(inputString == "CLEAR_ALL\n") {clearTable(); } + else if(inputString.substring(0,9) == "CLEAR_DAC") {clearDac(); } + + else + { + Serial.println(generalErrorString); + } + + + clearSerial(); + mcp.digitalWrite(readyLed,HIGH); + } //EXIT LOOP FOR SERIAL HERE + + +/*********************This block runs the high speed control interface *********************/ + +/****checks the acquisition order + * mode 0 == channel first eg set ch1 stweep Z + * mode 1 == Z first EG step Z then Ch1 Ch2 then Step Z ... + */ + +/* + while(trigArmed) + { // just sit here and wait for the next command until armed is off, which can only happen @ end of sequence + unsigned long tStart = millis() + timeOut; //set timeout position + unsigned long tLed = 0; + // focus commands = start, step, #loops,direction,slave,current step + //if(program == 0) {inTrigger=true; } //force a first step + + if( inTrigger ) + { //we recieved a trigger from our source + // and is mode 0, and is at 0 position OR + // and is mode 1, and is at any position OR + // if focus isn't enabled + */ + /* + * When should a channel update be issued? When + * focus is ON, and mode is sweep per channel (0) AND F position is 0 + * focus is ON, and mode is slave to channel (1) + * focus is off any time + */ + + // boolean runUpdate = true; + /* + if( (focArray[1] != 0) && (focArray[4] == 0) ){ //if we are using the focus and focus will sweep through a single channel setting + if( focArray[5] == 0 ) { runUpdate = true;} //AND if the focus is at position 0 (start of sweep) + } + + if( (focArray[1] !=0) && (focArray[4] == 1)) { runUpdate = true; } // Case where channel is switching each Z plane + if(focArray[1] == 0) {runUpdate=true;} //Case where no focus block used so update the channel + */ + /* + //do DAC and TTL control stuff, not focus stuff though + if(runUpdate) { + byte walker=0; + for(walker = 0 ; walker < 15 ; ++walker ){ //sets DACs 1-16 + dac_write(10,0, DAC [walker], dacArray [program] [walker]); // Set DAC Lines + digitalWriteDirect( ttl [walker] , ttlArray[program] [walker] ); //set TTL lines + } + digitalWriteDirect( ttl [walker] , ttlArray[program] [walker] ); //set 16th TTL line - + } + ++program; +*/ + /* THIS MOVES AROUND THE Z FOCUS + * in this case, we assume a trigger was recieved, but we should only mess with focus stuff if it's on and if it's needed + * here we only want to update the focus position in the case that EITHER + * Mode = 0 Channel 1 - Z 1, Z2, Z3 + * mode = 1 Z 1 - CH1, CH2, Z2, Ch1, Ch2 + */ +/* + if( (focArray[1] != 0) && ( focArray[4]==0 )){ fastFocus(); } // if any focus array is active, and MODE = SWEEP OVER CHANNEL + if( (focArray[1] != 0) && ( focArray[4]==1 )){ // in this case sweep is active and MODE = STEP AFTER CHANNEL + if((program == maxProgram-1) && (focArray[5] <= focArray[2])) {fastFocus();} + } + + delay(delArray[program]); //wait for specified delay + + if( focArray[1] == 0) {++program;} //if not using focus at all + if( (focArray[1] != 0) && (focArray[4] == 1) ) { //focus is used but in step mode + ++program; + if( (program > maxProgram) && (focArray[5] != 0) ) { //because we are stepping the focus, program must be reset to 0 in this case we know the focus has not completed, so we can reset the main program array position + program=0; + } + } + */ + } //END OF TRIGGER RESPONSE + + /* + inTrigger=false; //turn off trigger + tStart = millis() + timeOut; //update timeout delta for next run + + //Done moving stuff, now wait for the next input + while(!inTrigger){ //wait for next pulse + if(millis() > tStart) { + trigArmed=false; + program = maxProgram+1; + Serial.println("Timeout Exceeded"); + allOff(); + break; + } + //we hit the timeout so end the sequence + if(tLed < millis() ) + { + digitalWrite(readyLed,!digitalRead(readyLed)); + tLed = millis() + 30; + } + } + + if(program > maxProgram) { //end and cleanup + ++runCycles; + if(runCycles == timeCycles) { //we are done + trigArmed=false; + program=0; + allOff(); + Serial.println("!SAFE"); + digitalWrite(readyLed,HIGH); + } + if(runCycles < timeCycles) {program = 0;} + } + } //close trigarmed +} //close main loop +*/ + + + + +void allOff() +{ + for(byte walker=0;walker<15;++walker) + { //sets DACs 1-16 + dac_write(10,0, DAC [walker], 0); // Set DAC Lines + } + setPinGroup(0,0); + setPinGroup(1,0); +} + + +void clearSerial() +{ + //STUFF TO CLEAN OUT THE SERIAL LINE + inputString = ""; // clear the string: + stringComplete = false; +} + +/*PC DAC CONTROL*/ +void setDac(byte dNum,int dVal) +{ + dac_write(10,0, dNum, dVal); // Send dac_code + //led indication + if (useSignalLEDs_) + { + int dacSum = 0; + for (byte d=0; d < 16; d++) + { + dacSum += dacState[d]; + } + mcp.digitalWrite(dacLed, dacSum > 0); + } +} + +// sets the output state of the given pin number +void setTTL(byte t1, boolean t2) +{ + byte pin = t1 - 1; + if(pin < 9) { + digitalWriteFast(ttl[pin], t2); + } + if(pin > 8) { + mcp.digitalWrite(ttl[pin], t2); + } + if(t2) + { + bitSet(ttlState, pin); + } else + { + bitClear(ttlState, pin); + } + if (useSignalLEDs_) + { + mcp.digitalWrite(ttlLed, ttlState > 0); + } +} + +void setDacCheckBlanking(byte dacNr) +{ + if (dacBlanking[dacNr]) + { + triggerPinState = digitalReadFast(trig[0]); + dacBlankOnLow[dacNr] == triggerPinState ? + setDac(dacNr, dacState[dacNr]) : setDac(dacNr, 0); + } else { + setDac(dacNr, dacState[dacNr]); + } +} + + +void setPinGroupCheckBlanking(byte pinGroup) +{ + if (pinGroupBlanking[pinGroup]) + { + triggerPinState = digitalReadFast(trig[0]); + pinGroupBlankOnLow[pinGroup] == triggerPinState ? + setPinGroup(pinGroup, pinGroupState[pinGroup]) : setPinGroup(pinGroup, 0); + } else { + setPinGroup(pinGroup, pinGroupState[pinGroup]); + } +} + +inline void setPinGroup(byte, byte) __attribute__((always_inline)); +// sets pins 1-8 (pinGroup 0) or 9-16 (pinGroup 1) according to the mask in value +inline void setPinGroup(byte pinGroup, byte value) +{ + byte adder = (pinGroup) * 8; + for (byte b = 0; b < 8; b++) + { + if( b+adder < 9 ) { + digitalWriteFast(ttl[b + adder], (((value) >> (b)) & 0x01) ); + } + + else if( b+adder > 8) { + mcp.digitalWrite(ttl[b+adder],(((value) >> (b)) & 0x01)); + } + + //Serial.print("TTL " ); + //Serial.print(b + adder); + //Serial.print(" , "); + //Serial.println((((value) >> (b)) & 0x01)); + + } + if (pinGroup == 1) + { + ttlState = (ttlState & 0xff00) | value; + } else + { + ttlState = (ttlState & 0x00ff) | (value << 8); + } + if (useSignalLEDs_) + { + mcp.digitalWrite(ttlLed, ttlState > 0); + } +} + +/* + SerialEvent occurs whenever a new data comes in the hardware serial RX. This + routine is run between each time loop() runs, so using delay inside loop can + delay response. Multiple bytes of data may be available. +*/ +void serialEvent() { + trigArmed = false; + while (Serial.available()) { + // get the new byte: + char inChar = (char)Serial.read(); + // add it to the inputString: + inputString += inChar; + // if the incoming character is a newline, set a flag + // so the main loop can do something about it: + if (inChar == '\n') { + stringComplete = true; + } + } +} + +/*INTERRUPT CODE FOR TTL INPUT ***************/ +/* +void sigIn() //sigin is the input response line - this recieves the trigger input from an external source +{ + //if(!trigArmed) + //{ + digitalWrite(trigLed,!digitalRead(trigLed)); + //} + inTrigger=true; +} + +void configureTrigger(byte tOption) +{ + trigMode = tOption; //assign input value to global + switch (trigMode) { + case 0: + attachInterrupt(trig[0],sigIn,LOW); + break; + case 1: + attachInterrupt(trig[0],sigIn,HIGH); + break; + case 2: + attachInterrupt(trig[0],sigIn,RISING); + break; + case 3: + attachInterrupt(trig[0],sigIn,FALLING); + break; + case 4: + attachInterrupt(trig[0],sigIn,CHANGE); + break; + } +} +*/ + +void debug() +{ + if(debugT < millis() ) //if this is the first time debug has run, this will execute, otherwise has enough time elapsed? + { + Serial.print("Input Trigger = "); + Serial.println(digitalReadFast(trig[0])); + //Serial.print(", OpMode = "); + //Serial.println(opMode); + + // REPORT TTL + Serial.print("TTL: "); + for(int i=0;i<16;++i) + { + char sOut[100]; + if(i<9) {sprintf(sOut,"%d=%d,",i+1, digitalRead(ttl[i]));} + else if(i>8) {sprintf(sOut,"%d=%d,",i+1, mcp.digitalRead(ttl[i]));} + Serial.print(sOut); + } + Serial.println(""); + + //REPORT DAC + Serial.print("DAC:"); + for(int i=0;i<16;++i) + { + char sOut[200]; + sprintf(sOut,"%d=%u,",i+1, dacState[i]); + Serial.print(sOut); //used to print sOut + } + + Serial.println(); + Serial.println("Digital output buffers:"); + for (int pg = 0; pg < 2; pg++) + { + if (pg == 0) Serial.print("Pins 1-8 buffer size 1-8: "); + else if (pg == 1) Serial.print("Pins 9-16 buffer size: "); + char sOut[6]; + sprintf(sOut, "%d", ttlArrayMaxIndex[pg]); + Serial.println(sOut); + for (int i = 0; i < ttlArrayMaxIndex[pg]; i++) + { + sprintf(sOut, "%u ", ttlArray[i][pg]); + Serial.print(sOut); + } + if (ttlArrayMaxIndex[pg] > 0) { Serial.println(); } + } + + Serial.println("DAC buffers:"); + for (int dac = 0; dac < NR_DACS; dac++) + { + char sOut[25]; + sprintf(sOut, "DAC buffer size: %d", dacArrayMaxIndex[dac]); + Serial.println(sOut); + for (int i = 0; i < dacArrayMaxIndex[dac]; i++) + { + sprintf(sOut, "%u ", dacArray[i][dac]); + Serial.print(sOut); + } + if (dacArrayMaxIndex[dac] > 0) { Serial.println(); } + } + + } + // send an empty line so that the receiver know we are done + Serial.println(""); +} + + /* + //Report program arrays + if(opMode == 3) + { + Serial.println("Sequencer Programming Status"); + Serial.print("MaxProgram = "); + Serial.println(maxProgram); + Serial.print("PCHANNEL = "); + Serial.println(pChannel); + //report DACs + Serial.println("PROG, DAC1, DAC2, DAC3, DAC4, DAC5, DAC6, DAC7, DAC8, DAC9,DAC10,DAC11,DAC12,DAC13,DAC14,DAC15,DAC16/FOCUS"); + for(int p=0;p= 0; i--) + rx[i] = SPI.transfer(tx[i]); //! 2) Read and send byte array + + output_high(cs_pin); //! 3) Pull CS high +} + +inline void digitalWriteDirect(int, boolean) __attribute__((always_inline)); + +void digitalWriteDirect(int pin, boolean val){ + if(pin < 9) {digitalWriteFast(pin,val);} + else if(pin>8) {mcp.digitalWrite(pin,val);} +} + +/* +inline int digitalReadDirect(int) __attribute__((always_inline)); + +int digitalReadDirect(int pin){ + return !!(g_APinDescription[pin].pPort -> PIO_PDSR & g_APinDescription[pin].ulPin); +} + +inline int digitalReadOutputPin(int) __attribute__((always_inline)); + +int digitalReadOutputPin(int pin){ + return !!(g_APinDescription[pin].pPort -> PIO_ODSR & g_APinDescription[pin].ulPin); +} +*/ + + + +/*******************LICENSING INFO************** + + * Copyright (c) 2018, Advanced Research Consulting + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * + *******************/ \ No newline at end of file diff --git a/src/Overdrive/TriggerScope_V4B/Linduino.h b/src/Overdrive/TriggerScope_V4B/Linduino.h new file mode 100644 index 0000000..85ee651 --- /dev/null +++ b/src/Overdrive/TriggerScope_V4B/Linduino.h @@ -0,0 +1,109 @@ +//! @todo Review this file. +/* +Linduino.h + +This file contains the hardware definitions for the Linduino. + +REVISION HISTORY +$Revision: 1906 $ +$Date: 2013-08-26 15:09:18 -0700 (Mon, 26 Aug 2013) $ + +Copyright (c) 2013, Linear Technology Corp.(LTC) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of Linear Technology Corp. + +The Linear Technology Linduino is not affiliated with the official Arduino team. +However, the Linduino is only possible because of the Arduino team's commitment +to the open-source community. Please, visit http://www.arduino.cc and +http://store.arduino.cc , and consider a purchase that will help fund their +ongoing work. +*/ + +//! @defgroup Linduino Linduino: Linear Technology Arduino-Compatible Demonstration Board + +/*! @file + @ingroup Linduino + @ingroup QuikEval + Header File for Linduino Libraries and Demo Code +*/ + +#ifndef LINDUINO_H +#define LINDUINO_H + +//! @name LINDUINO PIN ASSIGNMENTS +//! @{ + +#define QUIKEVAL_GPIO 9 //!< Linduino QuikEval GPIO pin (QuikEval connector pin 14) connects to Arduino pin 9 +#define QUIKEVAL_CS SS //!< QuikEval CS pin (SPI chip select on QuikEval connector pin 6) connects to Arduino SS pin. +#define QUIKEVAL_MUX_MODE_PIN 8 /*!< QUIKEVAL_MUX_MODE_PIN defines the control pin for the QuikEval MUX. +The I2C port's SCL and the SPI port's SCK signals share the same pin on the Linduino's QuikEval connector. +Additionally, the I2C port's SDA and the SPI port's MOSI signals share the same pin on the Linduino's QuikEval connector. +The pair of pins connected to the QuikEval connector is switched using a MUX on the Linduino board. +The control pin to switch the MUX is defined as QUIKEVAL_MUX_MODE_PIN (Arduino pin 8). */ +//! @} + +// Macros +//! Set "pin" low +//! @param pin pin to be driven LOW +#define output_low(pin) digitalWrite(pin, LOW) +//! Set "pin" high +//! @param pin pin to be driven HIGH +#define output_high(pin) digitalWrite(pin, HIGH) +//! Return the state of pin "pin" +//! @param pin pin to be read (HIGH or LOW). +//! @return the state of pin "pin" +#define input(pin) digitalRead(pin) + +//! @todo Make a note about whether Arduino/Linduino is Big Endian or Little Endian. Raspberry Pi appears to be the opposite. +//! This union splits one int16_t (16-bit signed integer) or uint16_t (16-bit unsigned integer) +//! into two uint8_t's (8-bit unsigned integers) and vice versa. + union LT_union_int16_2bytes + { + int16_t LT_int16; //!< 16-bit signed integer to be converted to two bytes + uint16_t LT_uint16; //!< 16-bit unsigned integer to be converted to two bytes + uint8_t LT_byte[2]; //!< 2 bytes (unsigned 8-bit integers) to be converted to a 16-bit signed or unsigned integer + }; + +//! @todo Make a note about whether Arduino/Linduino is Big Endian or Little Endian. Raspberry Pi appears to be the opposite. +//! This union splits one int32_t (32-bit signed integer) or uint32_t (32-bit unsigned integer) +//! into four uint8_t's (8-bit unsigned integers) and vice versa. +union LT_union_int32_4bytes +{ + int32_t LT_int32; //!< 32-bit signed integer to be converted to four bytes + uint32_t LT_uint32; //!< 32-bit unsigned integer to be converted to four bytes + uint8_t LT_byte[4]; //!< 4 bytes (unsigned 8-bit integers) to be converted to a 32-bit signed or unsigned integer +}; + +//! @todo Make a note about whether Arduino/Linduino is Big Endian or Little Endian. Raspberry Pi appears to be the opposite. +//! This union splits one float into four uint8_t's (8-bit unsigned integers) and vice versa. +union LT_union_float_4bytes +{ + float LT_float; //!< float to be converted to four bytes + uint8_t LT_byte[4]; //!< 4 bytes (unsigned 8-bit integers) to be converted to a float +}; + + +#endif // LINDUINO_H diff --git a/src/Overdrive/TriggerScope_V4B/README.md b/src/Overdrive/TriggerScope_V4B/README.md new file mode 100644 index 0000000..2c4d30d --- /dev/null +++ b/src/Overdrive/TriggerScope_V4B/README.md @@ -0,0 +1,4 @@ +# Installation +This firmware is intended for TriggerScope V4B boards (serial numbers 2118 and later). + +To install this firmware, you will first need to download and install the [TeensyDuino package](https://www.pjrc.com/teensy/td_download.html), and the [Arduino IDE](https://www.arduino.cc/en/software). Then power your TriggerScope, connect the USB cable to your computer, and open the file TriggerScope_613MM/TriggerScope_613MM.ino in the [Arduino IDE](https://www.arduino.cc/en/software). Select the port under which your device appears (Tools > Port), and select the Teensy 4.1 (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. diff --git a/src/Overdrive/TriggerScope_V4B/TriggerScope_V4B.ino b/src/Overdrive/TriggerScope_V4B/TriggerScope_V4B.ino new file mode 100644 index 0000000..211f0f1 --- /dev/null +++ b/src/Overdrive/TriggerScope_V4B/TriggerScope_V4B.ino @@ -0,0 +1,1917 @@ +/****************************************** + Trigger Scope v. 604MM for Arduino microscope control by + ADVANCED RESEARCH CONSULTING @ 2015 + Regents of the University of California, 2020 + + + + Command set: + + Commands to the device are in ASCI and terminated with "\n". + Returns can be multiple lines, terminated with "\r\n" (carriage retun / new line). + Error messages start with "!ERROR_" followed by the command causing the error, semi-colon + and error message (example: "!ERROR_RANGE: Command out of range, DAC=1-16, Range = 1-5...") + + "*" - prints ID (example: "ARC TRIGGERSCOPE 16 R3C v.604-MM" + "?" - pirnts message listing available commands and their parameters + ----------------------analog output functions------------------------- + "SAO" - sets the voltage of specified DAC. Expected format: "DAC1-4236" + where 1 is the line nr, and 4236 the new value to be send to the DAC. + Resulting voltage depends on bit range of the DAC, and the voltage range + Returns: "!DAC1,4236" or error message (not starting with !DAC) on failure. + "PAN" - Queries the number of analog output states that can be pre-programmed. + Format: "PANn". + Where n=1-16 (DAC1-16). + Returns: "!PANn,q" where q is the maximum number of states that can be programmed + "PAO" - Sends sequence of analog output states to be used in triggered sequences. + Format: "PAOn-s-o1-o2-on", where + - n=1-16 for DAC 1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!PA0n-s-q" where q is the number of values successfully inserted in the internal buffer. + "POV" - Sets an array of overvoltage values for use before typical state transition. + Format: "POVn-s-o1-o2-on", where + - n=1-16 for DAC 1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!POVn-s-q" where q is the number of values successfully inserted in the internal buffer. + "POD" - Sets an array of overvoltage delay times for use with POV commands. + Format: "PODn-s-o1-o2-on", where + - n=1-16 for DAC 1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-4,294,967,295) to be consecutively inserted in the list. + Returns: "!PODn-s-q" where q is the number of values successfully inserted in the internal buffer. + + "PAC" - Clears the sequence of ananlog states + Format: "PACn", where n is the DAC pinNr (1-16) + "PAS" - Starts triggered transitions in analog output state as pre-loaded in PAO + Format: "PASn-s-t" where + - n=1-16 for DAC 1-16 + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin + "BAO" - Activates blanking mode of the analog output. Output state will be coupled to the + state of the input trigger. If input trigger is active, the DAC will be active ( + as set with SDA), if input trigger is inactive, the output will go (which may not be 0V, depending on the range). + Format: "BAOn-s-t" + - n=1-16 for DAC 1-16 + - s=0 or 1 where 0 stops and 1 starts balnking mode + - t translates state of the input pin to its activity for blanking. If t = 0, + DAC will be 0 when input trigger pin is low. t = 1: DAC will be active when input trigger is low. + "SAR" - Sets the output range in volts of the DAC. Example: "SAR2-1" where 2 + specified the DAC number, and 1 the range. Allowed ranges are number 1-5: + 1: 0-5V + 2: 0-10V + 3: -5-+5V + 4: -10-+10V + 5: -2-+2V + Returns: "!RANGE2,1" +-------------------------digital output functions------------------------ +"SDO" - sets TTL outputs of pins1-8 and 9-16 in a single operation + Format: DOn-o + Where n=0 or 1. 0 address pins 1-8, 1 goes to pins 9-16 + 0 is digital output bitmask, 0: all off, 1: pin 1 on, 2: pin 2 on, 3: pins 1&2 on, etc.. + Example: DO1,4 sets pin 3 high and pins 1,2, 4-8 low + Returns: "!DOn-o" or error message ("!ERROR_DO: ") on failure. +"PDN" - Queries the number of digital output states that can be pre-programmed. + Format: "PDNn". + Where n=0 or 1. 0 address pins 1-8, 1 goes to pins 9-16 + Returns: "!PDNn-q" where q is the maximum number of states that can be programmed +"PDO" - Sends sequence of digital output states to be used in triggered sequences. + Format: "PDOn-s-o1-o2-on", where + - n=0 or 1 and directs the command to either pins1-8 or 9-16, + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values to be consecutively inserted in the list. + Returns: "!PD0n-s-q" where q is the number of values successfully inserted in the internal buffer. +"PDC" - Clears the sequence of digital states + Format: "PDCn", where n is the pingroup, 0=pins1-8, 1=pins9-16 +"PDS" - Starts triggered transitions in digital output state as pre-loaded in PDO + Format: "PDSn-s-t" where + - n=0 or 1 and directs the command to either pins 1-8 or 9-16 + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin +"BDO" - Activates blanking mode of the digital output. Output state will be coupled to the + state of the input trigger. If input trigger is active, the pingroup will be active ( + as set with DO), if input trigger is inactive, the pingroup will go low. + Format: "BDOn-s-t" + where n is the pingroup (0 or 1) and s switched blanking mode on (1) or off (0). + t translates state of the input pin to its activity for blanking. If t = 0, + output pins will be low when input trigger pin is low. When t = 1, output pins + will be active when input trigger is low. + "SSL" - Set Signal LEDs. + In some cases, setting signal LEDs slows down triggered operation, so offer the option to + not set them. + Format: "SSLn" where + - n - 0 ("Off") or 1 ("On") + Signal LEDs are On by default + + + +***************************** +Contact Advanced Research Consulting for Driver libraries! www.advancedresearch.consulting + ******************************/ +#include +#include +#include +#include "Linduino.h" + +#define focus 15 //sets focus line # +#define pwrLed 28 //POWER indication +#define dacLed 29 //DAC indication +#define ttlLed 34 //TTL indication +#define trigLed 35 //TRIGGER indication +#define readyLed 32 //READY indication +#define ttlblock2OE 23 +#define ttlblock2DIR 24 +#define ttlblock1OE 21 +#define ttlblock1DIR 22 +#define ttlblock3OE 37 +#define ttlblock3DIR 36 + +#define NR_DACS 16 +#define NR_DAC_STATES 1200 +#define NR_DO_STATES 1200 + + +String idname = "ARC TRIGGERSCOPE 16 R4 Board 4B v.620-MM"; + +const char* helpString = "Available commands: \r\n" + "SAOn-s - sets DACs. n = DAC number (1-16), s = value (0-65535)\r\n" + "PANn - queries the number of programmable analogoutput states, n = DAC pin number (1-16)\r\n" + "PAOn-s-o1-o2-on - sends sequence of programmable analog output states, n = DAC pin number (1-16)\r\n" + " s = 0-based index in the sequence to start inserting, \r\n" + " o = comma separated series of output values (0-65535)\r\n" + "POVn-s-o1-o2-on - sends sequence of analog overdrive states, n = DAC pin number (1-16)\r\n" + " s = 0-based index in the sequence to start inserting, \r\n" + " o = comma separated series of output values (0-65535)\r\n" + "PODn-s-o1-o2-on - sends sequence of overdrive durations in microseconds, n = DAC pin number (1-16)\r\n" + " s = 0-based index in the sequence to start inserting, \r\n" + " o = comma separated series of delay times (0-(0-4294967295))\r\n" + + "PACn - clears the sequence of analog states, n = DAC pin number (1-16)\r\n" + "PASn-s-t - starts triggered transition in programmable analog output states as programmed in PAO\r\n" + " n = DAC pin number (1-16), s = 0 (stops) or 1 (starts), \r\n" + " t = transition on falling (0) or rising (1) edge of input trigger.\r\n" + "BAOn-s-t starts blanking mode of ananlog output. n = DAC pin number (1-16), \r\n" + " s = 0 (stops) or 1 (starts), t = output low when trigger low (0), or inverse (1)\r\n" + "SARn-s - sets DAC voltages range. n = DAC number (1-16), s= 1:0-5V 2:0-10V 3:-5-+5V 4: -10-+10V 5:-2-+2V\r\n" + "SDOn-s - sets digital output state, n = 0(pins1-8) or 1(pins9-16), s binary mask 0-255\r\n" + "PDNn - queries the number of programmable digital output states, n = pin group 0(1-8) or 1 (9-16)\r\n" + "PDOm-s-o1-o2-on - sends sequence of programmable digital output states, n = pin group 0(1-8) or 1(9-16)\r\n" + " s = 0-based index in the sequence to start inserting\r\n" + " o = comma separated series of output states (0-255)\r\n" + "PDCn - clears the sequence of digital states, n = pin group 0(1-8) or 1 (9-16)\r\n" + "PDSn-s-t - starts triggered transitions in digital output state as pre-loaded in PDO, \r\n" + " n = pin group 0(1-8) or 1 (9-16), s = 0 stops 1 starts, \r\n" + " t = transition on falling (0) or rising (1) edge of input trigger.\r\n" + "BDn-s-t - sync digital output with input trigger. n = pin group 0(1-8) or 1 (9-16)\r\n" + " s = 0 stops 1 starts, t = output low when trigger low (0), or inverse (1)\r\n" + "SSLn - switches use of signal LEDs. n=0 (Off) or n=1 (On)\r\n" + "\r\n"; // empty line to signal end of help textd + + +const char sep = '-'; + +//new IC assignments and SD card access for V4 board +File myFile; //create settings file +const int chipSelect = BUILTIN_SDCARD; //set SD card access CS line + +//set up menu modes and vars +byte opMode = 1; //sets operation mode, 1=MANUAL control, 2 = PC CONTROL, 3=TTL Slave via Programmed Steps +boolean trigArmed=false; //enables while loop for just the high speed trig sequencing +unsigned long debugT=0; //debugger flag - not used in a few versions +unsigned long trigInterval; //tracks frequency of inputs from camera or external source +int trigStep=0; //optionally used to sequence more than 1 PROG line at a time, not fully implemented +String inputString = ""; // a string to hold incoming data +boolean stringComplete = false; // whether the string is complete + +//PIN ASSIGNMENTS +const byte DAC[NR_DACS] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; //MOVE THESE FOR CUSTOMERS IF NEEDED! +const byte ttl[NR_DACS] = {5,6,7,8,14,15,16,17,20,25,26,27,38,39,40,41}; //ttl pin #'s +const byte trig[4] = {0,1,2,3}; + +/*HIGH SPEED PROGRAMMING MODE MEMORY BLOCK*/ +int dacArray[NR_DAC_STATES][NR_DACS] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; // DACprogram list +uint8_t ttlArray[NR_DO_STATES][2] = {{0,0}}; // digital output states program list +int ttlArrayMaxIndex[2] = {0, 0}; // maintains the max index in the array that was set +int ttlArrayIndex[2] = {0, 0}; // keeps track of current position in the array +int dacArrayMaxIndex[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int dacArrayIndex[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint32_t dacBlankDelay[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint32_t dacBlankDuration[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +boolean useSignalLEDs_ = true; + +// data structures to be assembled from blanking settings above that have a time-ordered sequence of events +int dacBlankEventsNr = 0; +// there can be a delay and a duration for each blanking event +uint32_t dacBlankEventNextWait[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +uint8_t dacBlankEventPinNr[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +// 0 : off, 1: set normal state, 2: set value from dacArray +uint8_t dacBlankEventState[2 * NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +byte pinGroupState[2] = {0, 0}; +byte pinGroupStoredState[2] = {0, 0}; +bool pinGroupBlanking[2] = {false, false}; +bool pinGroupBlankOnLow[2] = {true, true}; +bool pinGroupSequencing[2] = {false, false}; +byte pinGroupSequenceMode[2] = {0, 0}; // 0: falling edge, 1: rising edge +int dacState[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int dacStoredState[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +bool dacBlanking[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +bool dacBlankOnLow[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +bool dacSequencing[16] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +byte dacSequenceMode[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int povArray[NR_DAC_STATES][NR_DACS] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; // DAC overdrive value list +uint32_t podArray[NR_DAC_STATES][NR_DACS] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; // overdrive delay on state +uint32_t podExpire[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //timers for real overdrive expire +int lastDac[NR_DACS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //holes the last DAC value and used for implement on timer expire + +bool triggerPinState = false; + +int delArray[500]; //time delay array for high speed sequences +int focArray[6]; //array for focus stacks = start, step, #loops,direction,slave,current step +uint16_t ttlState = 0; +// boolean ttlActive[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; +int timeCycles = 1; //used for high speed switching inside of a loop +int runCycles = 0; //holds running position vs total cycles for timelapse + +volatile boolean inTrigger=false; +byte program=0; //this sets the active program # used in the Mode 3 high speed sequencer +byte maxProgram=0; //this holds the maximum program value entered, and is used to set the maxium sequence step. +// byte stepMode = 1; //1 = waits for TTL IN, 2=runs continually +// unsigned long timeOut = 1000; //timeout for sequence (set to 10 seconds by default) +// unsigned long tStart = 0; //sequence start timer +// unsigned long trigLedTimer = 0; +// boolean reportTime = 0; +// boolean umArmIgnore = 0; //this handles micromanagers multiple ARM commands which are issued back-to-back on MDA acqtivation. Ignores after a short wait. +// boolean usepwm = false; +// byte pChannel =0; //number of channels micromanager has attempted to control +// byte lastPT=20; +// byte trigMode = 2; //0 = LOW 1 = HIGH 2 = RISING 3 = FALLING 4 = CHANGE + +bool error = false; + +const char* saoErrorString = "!ERROR_SAO: Format: SAOn-s n=1-16 (DAC1-16), s=value 0-65535"; +const char* sarErrorString = "!ERROR_SAR: Format: SARn-s n=1-16 (DAC=1-16), s=1:0-5V 2:0-10V 3:-5-+5V 4:-10-+10V 5:-2-+2V"; +const char* panErrorString = "!ERROR_PAN: Format: PANn n=1-16 (DAC1-16)"; +const char* paoErrorString = "!ERROR_PAO: Format: PAOn-s-01-02-0n n=1-16 (DAC1-16), s= >=0 (position), 0n=values 0-65535"; +const char* pacErrorString = "!ERROR_PAC: Format: PACn n=1-16 (DAC1-16)"; +const char* pasErrorString = "!ERROR_PAS: Format: PASn-s-t n=1-16 (DAC1-16) s 0=stop 1=start, t=transition on falling(0) or rising(1) edge"; +const char* baoErrorString = "!ERROR_BAO: Format: BAOn-s-t n=1-16 (DAC1-16), s blank 0(off) or 1(on), t 0 (blank on low) or 1 (blank on high)"; +const char* povErrorString = "!ERROR_POV: Format: POVn-s-01-02-0n n=1-16 (DAC1-16), s= >=0 (position), 0n=values 0-65535"; +const char* podErrorString = "!ERROR_POD: Format: PODn-s-01-02-0n n=1-16 (DAC1-16), s= >=0 (position), 0n=values 0-4294967295"; +const char* sdoErrorString = "!ERROR_SDO: Format: SDOn-s n=pingroup 0-1, s=value 0-255"; +const char* pdnErrorString = "!ERROR_PDN: Format: PDNn n=pingroup 0-1"; +const char* pdoErrorString = "!ERROR_PDO: Format: PDOn-s-01-02-0n n=pingroup 0-1, s=position, 0n=values 0-255"; +const char* bdoErrorString = "!ERROR_BDO: Format: BDOn-s-t n=pingroup 0-1, s blank 0(off) or 1(on), t 0 (blank on low) or 1 (blank on high)"; +const char* pdcErrorString = "!ERROR_PDC: Format: PDCn n=pinGroup(1/2)"; +const char* pdsErrorString = "!ERROR_PDS: Format: PDSn-s-t n=pinGroup(1/2) s 0=stop 1=start, t=transition on falling(0) or rising(1) edge"; +const char* sslErrorString = "!ERROR_SSL: Format: SSLn n=0 (Off) or 1 (On)"; +const char* generalErrorString = "ERROR_UNKNOWN_COMMAND"; + + + +void setup() +{ + + //indicate power is on for Teensy + pinMode(pwrLed,OUTPUT); + digitalWrite(pwrLed,HIGH); + + //configure LED pins for output + pinMode(trigLed,OUTPUT); + pinMode(ttlLed,OUTPUT); + pinMode(dacLed,OUTPUT); + + //Configure TTL outputs 13-16 + pinMode(ttlblock2OE,OUTPUT); + digitalWrite(ttlblock2OE,LOW); //low for enable + pinMode(ttlblock2DIR,OUTPUT); + digitalWrite(ttlblock2DIR,HIGH); //HIGH to enable 3.3v -> 5V output + + //configure TTL outputs 5-12 + pinMode(ttlblock1OE,OUTPUT); + digitalWrite(ttlblock1OE,LOW); //*** DISABLED FOR BOARDS PRODUCED 12/21 DUE TO LED CONFLICT. low for enable + pinMode(ttlblock1DIR,OUTPUT); + digitalWrite(ttlblock1DIR,HIGH); //HIGH to enable 3.3v -> 5V output + + //Configure TTL IN for TRIG 1-4 + pinMode(ttlblock3OE,OUTPUT); + digitalWrite(ttlblock3OE,LOW); //low for enable + pinMode(ttlblock3DIR,OUTPUT); + digitalWrite(ttlblock3DIR,LOW); //LOW to enable 5v -> 3.3V input + + delay(10); + //configureTrigger(trigMode); //will attach interrupt + for(byte i=0;i<16;++i) { pinMode(ttl[i],OUTPUT); digitalWrite(ttl[i],LOW); } //SET OUTPUT PINS ON TTL AND CAMERA LINES + + Serial.begin(115200); // start serial @ 115,200 baud + while (!Serial) { ; } // wait for serial port + + //read from SD card + //Serial.print("Reading Settings..."); + if (!SD.begin(chipSelect)) { + //Serial.println("SD Read Failure. Contact ARC"); + return; + } + myFile = SD.open("tgs.txt", FILE_WRITE); + if (myFile) { + //Serial.print("Writing to test.txt..."); + myFile.println("testing 1, 2, 3."); + myFile.close(); + Serial.println("done."); + } + else { + // if the file didn't open, print an error: + //Serial.println("SD Read Failure. Contact ARC"); + } + + /***Dac startup ***/ + pinMode(9,OUTPUT); //CLR pin must stay high! + digitalWrite(9,LOW); //CLR Stays high ALWAYAS + delay(50); + digitalWrite(9,HIGH); //CLR Stays high ALWAYAS + delay(50); + + SPI.begin(); + pinMode(10,OUTPUT); // DAC CS + SPI.beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0)); //teensy can do 30000000 !! + + //Drive All DACs & TTLs to 0 + for(int i=1;i<=16;++i) { + setTTL(i,0); + setDac(i, 0); + } + opMode=3; //HYBRID mode default HYBRID=3 / MANUAL=0 / + delay(100); + Serial.println(idname); //issue identifier so software knows we are running + // configureTrigger(trigMode); //will attach interrupt + pinMode(readyLed,OUTPUT); + digitalWrite(readyLed,HIGH); //indicate setup complete + triggerPinState = digitalReadFast(trig[0]); + +} + +void loop() +{ + //************************ DEVICE CONTROL & COMMAND CODE ***********************// + //************************ SERIAL COMMUNICATION CODE ******************/// + /* + if(inTrigger && reportTime) //reports time delay from previous interrupt + { + Serial.print("@TIME ="); + Serial.println(trigInterval); + inTrigger = false; + } + */ + for (byte i = 0; i < NR_DACS; i++) // investigates if an overdrive or delay timer is enabled + { + if (podExpire[i] != 0) + { // delay is still on + if (podExpire[i] < micros()) + { // time has expired + setDac(i, lastDac[i]); //issue normal on state value + podExpire[i] = 0; //remove exire timer + } + } + } + + if (triggerPinState != digitalReadFast(trig[0])) + { + triggerPinState = ! triggerPinState; + if (useSignalLEDs_) + { + digitalWriteDirect(trigLed, triggerPinState); + } + for (byte i = 0; i < NR_DACS; i++) // todo: optimize by ordering an array with sequenceable DACS and only cycle through those + { + if (dacSequencing[i]) + { + if (dacSequenceMode[i] == triggerPinState) + { + dacState[i] = dacArray[dacArrayIndex[i]][i]; + dacArrayIndex[i]++; + + if (dacArrayIndex[i] == dacArrayMaxIndex[i]) { dacArrayIndex[i] = 0; } + if (!dacBlanking[i]) + { + if(povArray[dacArrayIndex[i]][i] ) //if pov has an overdrive value + { + setDac(i, povArray[dacArrayIndex[i]][i]); //issue overdrive + podExpire[i] = micros() + podArray[dacArrayIndex[i]][i]; //set disable timer + lastDac[i] = dacState[i]; //save the normal dac output value for later update + } + else{ + setDac(i, dacState[i]); + } + } + + } + } + if (dacBlanking[i]) { + dacBlankOnLow[i] == triggerPinState ? setDac(i, dacState[i]) : setDac(i, 0); + } + } + + for (byte i = 0; i < 2; i++) + { + if (pinGroupSequencing[i]) + { + if (pinGroupSequenceMode[i] == triggerPinState) + { + pinGroupState[i] = ttlArray[ttlArrayIndex[i]][i]; + ttlArrayIndex[i]++; + if (ttlArrayIndex[i] == ttlArrayMaxIndex[i]) { ttlArrayIndex[i] = 0; } + if (!pinGroupBlanking[i]) + { + setPinGroup(i, pinGroupState[i]); + } // if we are blanking, fall through to the code below to set + } + } + if (pinGroupBlanking[i]) + { + pinGroupBlankOnLow[i] == triggerPinState ? setPinGroup(i, pinGroupState[i]) : + setPinGroup(i, 0); + } + } + + + } + + if (stringComplete) // There is a new line terminated string ready to be processed + { + digitalWrite(readyLed,LOW); + + String command = inputString.substring(0,3); + + if (inputString == "?\n") + { + Serial.println(helpString); + } + + else if(inputString == "*\n") + { + Serial.println(idname); + } //return ID line if queried + + // SAO command - Set DAC + // Expected format: "SAO1-4236" where 1 is the line nr, and 4236 the new value to be send to the DAC + else if(command == "SAO") + { + error = false; + byte dacNum = 0; // initialize to error conditino + int offset = 5; + if (inputString[4] == sep) + { + dacNum = atoi(&inputString[3]); + } else if (inputString[5] == sep) + { + dacNum = atoi(inputString.substring(3,5).c_str()); + offset++; + } else + { + error = true; + } + if (dacNum < 1 || dacNum > 16) + { + error = true; + } + + int value = atoi(inputString.substring(offset).c_str()); + if (value < 0 || value > 65535) + { + error = true; + } + + if (!error) //if inputs are valid + { + Serial.print("!SAO"); //print recieve message to operator + Serial.print(dacNum); //print recieve message to operator + Serial.print(sep); + Serial.println(value); + dacState[dacNum - 1] = value; + setDacCheckBlanking(dacNum - 1); + } else + { + Serial.println(saoErrorString); + } + } + + // PAN - + // Queries the number of analog output states that can be pre-programmed. + // Format: "PANn", n is 1-6 for DAC1-16. + // Returns: "!PANn-q" where q is the maximum number of states that can be programmed + else if (command == "PAN") + { + byte dac = atoi(&inputString[3]); + if (dac < 1 || dac > 16) + { + Serial.println(panErrorString); + } else + { + Serial.print("!PAN"); + Serial.print(dac); + Serial.print(sep); + Serial.println(NR_DAC_STATES); + } + } + /* + "BAO" - Activates blanking mode of the analog output. Output state will be coupled to the + state of the input trigger. If input trigger is active, the DAC will be active ( + as set with SDA), if input trigger is inactive, the output will go (which may not be 0V, depending on the range). + Format: "BAOn,s,t" + - n=1-16 for DACS1-16 + - s=0 or 1 where 0 stops and 1 starts balnking mode + - t translates state of the input pin to its activity for blanking. If t = 0, + DAC will be 0 when input trigger pin is low. t = 1: DAC will be active when input trigger is low. + */ + else if (command == "BAO") + { + error = false; + int dacNr = 0; + int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep) { dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16){ error = true; } + int state = inputString.substring(scp, scp+1).toInt(); + if (state < 0 || state > 1) { error = true; } + if (inputString[scp+1] != sep) { error = true; } + int mode = inputString.substring(scp+2).toInt(); + if (mode < 0 || mode > 1) { error = true; } + if (!error) + { + dacBlanking[dacNr - 1] = state == 1; + dacBlankOnLow[dacNr - 1] = mode == 0; + setDacCheckBlanking(dacNr - 1); + char out[20]; + sprintf(out, "!BAO%d%c%d%c%d", dacNr, sep, state, sep, mode); + Serial.println(out); + } else + { + Serial.println(baoErrorString); + } + } + + + + + /* + * + *"POV" - Sends sequence of analog overdrive states to be used in triggered sequences. + Format: "POVn,s,o1,o2,on", where + - n=1-16 for DACS1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!POVn,s,q" where q is the number of values successfully inserted in the internal buffer. + */ + else if (command == "POV") + { + error = false; + int n = 0; + int s = 0; + int dacNr = 0; + unsigned int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep){ dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16) { error = true; } + unsigned int ecp = scp + 1; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) { s = inputString.substring(scp, ecp).toInt(); } + else { error = true; } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 65535) + { + error = true; + } else + { + povArray[n + s][dacNr-1] = val; + n++; + int index = n+s; + if (index > dacArrayMaxIndex[dacNr-1]) + { + dacArrayMaxIndex[dacNr-1] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!POV%d%c%d%c%d%c%d", dacNr, sep, s, sep, n, sep, dacArrayMaxIndex[dacNr-1]); + Serial.println(out); + } else + { + Serial.println(povErrorString); + } + } + + /* + * + *"POD" - Sends sequence of delay times for anaog overdrive staates specified in POV. + Format: "PODn,s,o1,o2,on", where + - n=1-16 for DACS1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (uInt32_t) to be consecutively inserted in the list. + Returns: "!PODn,s,q" where q is the number of values successfully inserted in the internal buffer. + */ + else if (command == "POD") + { + error = false; + int n = 0; + int s = 0; + int dacNr = 0; + unsigned int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep){ dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16) { error = true; } + unsigned int ecp = scp + 1; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) { s = inputString.substring(scp, ecp).toInt(); } + else { error = true; } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 65535) + { + error = true; + } else + { + podArray[n + s][dacNr-1] = val; + n++; + int index = n+s; + if (index > dacArrayMaxIndex[dacNr-1]) + { + dacArrayMaxIndex[dacNr-1] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!POD%d%c%d%c%d%c%d", dacNr, sep, s, sep, n, sep, dacArrayMaxIndex[dacNr-1]); + Serial.println(out); + } else + { + Serial.println(podErrorString); + } + } + + /* + * + *"PAO" - Sends sequence of analog output states to be used in triggered sequences. + Format: "PAOn,s,o1,o2,on", where + - n=1-16 for DACS1-16 + - s: position in the sequence to start inserting values. First position is 0. + - o1, o2, etc... values (0-65535) to be consecutively inserted in the list. + Returns: "!PA0n,s,q" where q is the number of values successfully inserted in the internal buffer. + */ + else if (command == "PAO") + { + error = false; + int n = 0; + int s = 0; + int dacNr = 0; + unsigned int scp = 5; + if (inputString[4] == sep) { dacNr = inputString.substring(3,4).toInt(); } + else if (inputString[5] == sep){ dacNr = inputString.substring(3,5).toInt(); scp = 6; } + else { error = true; } + if (dacNr < 1 || dacNr > 16) { error = true; } + unsigned int ecp = scp + 1; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) { s = inputString.substring(scp, ecp).toInt(); } + else { error = true; } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 65535) + { + error = true; + } else + { + dacArray[n + s][dacNr-1] = val; + n++; + int index = n+s; + if (index > dacArrayMaxIndex[dacNr-1]) + { + dacArrayMaxIndex[dacNr-1] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!PAO%d%c%d%c%d%c%d", dacNr, sep, s, sep, n, sep, dacArrayMaxIndex[dacNr-1]); + Serial.println(out); + } else + { + Serial.println(paoErrorString); + } + } + + + /* + * "PAC" - Clears the sequence of ananlog states + Format: "PACn", where n is the DAC pinNr (1-16) + */ + else if (command == "PAC") + { + error = false; + if (inputString.length() == 5 || inputString.length() == 6) + { + int dacNr = inputString.substring(3).toInt(); + if (dacNr < 1 || dacNr > 16) { error = true; } + if (!error) + { + dacSequencing[dacNr - 1] = false; + dacArrayIndex[dacNr - 1] = 0; + dacArrayMaxIndex[dacNr - 1] = 0; + dacBlankDelay[dacNr - 1] = 0; + dacBlankDuration[dacNr - 1] = 0; + clearPAO(dacNr - 1); + clearPOD(dacNr - 1);//addition for overdrive + clearPOV(dacNr - 1);//addition for overdrive + for ( byte d = 0;d 16 || inputString[scp] != sep) + { + error = true; + } + int state = inputString.substring(scp+1,scp+2).toInt(); + if (state < 0 || state > 1) { error = true; } + int rising = inputString.substring(scp+3).toInt(); + if (rising < 0 || rising > 1) { error = true; } + if (!error) + { + dacSequencing[dacNr - 1] = (boolean) state; + dacSequenceMode[dacNr - 1] = rising; + if (state) + { + dacStoredState[dacNr - 1] = dacState[dacNr - 1]; + dacArrayIndex[dacNr - 1] = 0; + if (!rising) { // if we trigger on the falling edge, set initial state now, and advance counter here + setDac(dacNr -1, dacArray[dacArrayIndex[dacNr - 1]][dacNr - 1]); // Check blanking? + dacArrayIndex[dacNr - 1]++; + } + } else + { + dacState[dacNr - 1] = dacStoredState[dacNr - 1]; + } + char out[20]; + sprintf(out, "!PAS%d%c%d%c%d", dacNr, sep, state, sep, rising); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pasErrorString); + } + } + + // SDO + // sets TTL outputs of pins 1-8 and 9-16 in a single operation + // Format: SDOn,o + // Where n=0 or 1. 0 address pins 1-8, 1 goes to pins 9-16 + // 0 is digital output bitmask, 0: all off, 1: pin 1 on, 2: pin 2 on, 3: pins 1&2 on, etc.. + // Example: DO1,4 sets pin 3 high and pins 1,2, 4-8 low + else if (command == "SDO") + { + byte pinGroup = atoi(&inputString[3]); + if (pinGroup < 0 || pinGroup > 1) + { + Serial.println(sdoErrorString); + } else + { + int value = atoi(inputString.substring(5).c_str()); + if (value < 0 || value > 255) + { + Serial.println(sdoErrorString); + } else + { + Serial.print("!SDO"); + Serial.print(pinGroup); + Serial.print(sep); + Serial.println(value); + pinGroupState[pinGroup] = value; + setPinGroupCheckBlanking(pinGroup); + } + } + } + + // PDN - + // Queries the number of digital output states that can be pre-programmed. + // Format: "PDNn". + // Returns: "!PDNn,q" where q is the maximum number of states that can be programmed + else if (command == "PDN") + { + byte pinGroup = atoi(&inputString[3]); + if (pinGroup < 0 || pinGroup > 1) + { + Serial.println(pdnErrorString); + } else + { + Serial.print("!PDN"); + Serial.print(pinGroup); + Serial.print(sep); + Serial.println(NR_DO_STATES); + } + } + + //"PDO" - Sends sequence of digital output states to be used in triggered sequences. + // Format: "PDOn-s-o1-o2-on", where + // - n=1 or 2 and directs the command to eitherpins1-8 or 9-16, + // - s: position in the sequence to start inserting values. First position is 1. + // - o1, o2, etc... values to be consecutively inserted in the list. + // Returns: "!PD0n,s,q" where q is the number of values successfully inserted in the internal buffer. + else if (command == "PDO") + { + error = false; + int s = 0; + int n = 0; + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) + { + error = true; + } + if (inputString[4] != sep) + { + error = true; + } + unsigned int scp = 5; + unsigned int ecp = 6; + if (!error) + { + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if (ecp < inputString.length()) + { + s = inputString.substring(scp, ecp).toInt(); + } + else + { + error = true; + } + while (!error && ecp < inputString.length()) + { + scp = ecp; + ecp++; + while (inputString[ecp - 1] != sep && ecp < inputString.length()) + { + ecp++; + } + if ( (ecp - scp) > 1) + { + int val = inputString.substring(scp, ecp).toInt(); + if (val < 0 || val > 255) + { + error = true; + } else + { + ttlArray[n + s][pinGroup] = (byte) val; + n++; + int index = n+s; + if (index > ttlArrayMaxIndex[pinGroup]) + { + ttlArrayMaxIndex[pinGroup] = index; + } + } + } + } + } + + if (!error) + { + char out[20]; + sprintf(out, "!PDO%d%c%d%c%d%c%d", pinGroup, sep, s, sep, n, sep, ttlArrayMaxIndex[pinGroup]); + Serial.println(out); + } else + { + Serial.println(pdoErrorString); + } + } + + /* + * "PDC" - Clears the sequence of digital states + Format: "PDCn", where n is the pingroup, 0=pins1-8, 1=pins9-16 + */ + else if (command == "PDC") + { + error = false; + if (inputString.length() == 5) + { + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) { error = true; } + if (!error) + { + pinGroupSequencing[pinGroup] = false; + ttlArrayIndex[pinGroup] = 0; + ttlArrayMaxIndex[pinGroup] = 0; + clearPDO(pinGroup); + char out[20]; + sprintf(out, "!PDC%d", pinGroup); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pdcErrorString); + } + } + + /* + "PDS" - Starts and stops triggered transitions in digital output state as pre-loaded in PDO + Format: "PDSn,s,t" where + - n=0 or 1 and directs the command to either pins 1-8 or 9-16 + = s=0 or 1 where 0 stops and 1 starts triggered transitions + - t=0 or 1 and determines whether transition will happen on the falling (0) or rising (1) edge + of a pulse on the input pin + */ + else if (command == "PDS") + { + error = false; + if (inputString.length() == 9) + { + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) { error = true; } + int state = inputString.substring(5,6).toInt(); + if (state < 0 || state > 1) { error = true; } + int rising = inputString.substring(7,8).toInt(); + if (rising < 0 || rising > 1) { error = true; } + if (!error) + { + pinGroupSequencing[pinGroup] = (boolean) state; + pinGroupSequenceMode[pinGroup] = rising; + if (state) + { + pinGroupStoredState[pinGroup] = pinGroupState[pinGroup]; + ttlArrayIndex[pinGroup] = 0; + if (!rising) + { // if we trigger on the falling edge, set initial state now, and advance counter here + setPinGroup(pinGroup, ttlArray[ttlArrayIndex[pinGroup]][pinGroup]); // Check blanking? + ttlArrayIndex[pinGroup]++; + } + } else + { + pinGroupState[pinGroup] = pinGroupStoredState[pinGroup]; + } + char out[20]; + sprintf(out, "!PDS%d%c%d%c%d", pinGroup, sep, state, sep, rising); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(pdsErrorString); + } + } + + /** + * "BDO" - Activates blanking mode of the digital output. Output state will be coupled to the + * state of the input trigger. If input trigger is active, the pingroup will be active ( + * as set with DO), if input trigger is inactive, the pingroup will go low. + * Format: "BDOn,s,t" where n is the pingroup (0 or 1), s switches blanking mode on (1) + * or off (0), and t sets the mode (0 blank on low, 1 blank on high) + */ + else if (command == "BDO") + { + error = false; + if (inputString.length() == 9) + { + int pinGroup = inputString.substring(3,4).toInt(); + if (pinGroup < 0 || pinGroup > 1) { error = true; } + byte state = inputString.substring(5,6).toInt(); + if (state < 0 || state > 1) { error = true; } + byte mode = inputString.substring(7,8).toInt(); + if (mode < 0 || mode > 1) { error = true; } + if (!error) + { + pinGroupBlanking[pinGroup] = state == 1; + pinGroupBlankOnLow[pinGroup] = mode == 0; + setPinGroupCheckBlanking(pinGroup); + char out[20]; + sprintf(out, "!BDO%d%c%d%c%d", pinGroup, sep, state, sep, mode); + Serial.println(out); + } + } else + { + error = true; + } + if (error) + { + Serial.println(bdoErrorString); + } + } + + /** + * Sets voltage range of DACS + */ + else if(command == "SAR") + { + error = false; + byte dacNum = inputString.substring(3).toInt(); + byte pp = 5; + if(dacNum >9) + { + pp=6; + } + byte rangeVal = inputString.substring(pp).toInt(); + if(rangeVal < 1 || rangeVal > 5) + { + error = true; + } //force to max range + if(dacNum < 1 || dacNum > 16) + { + error = true; + } //confirm if input channel range is valid + if(!error) //if range is OK perform command + { + Serial.print("!SAR"); //print recieve message to operator + Serial.print(dacNum); + Serial.print(sep); + Serial.println(rangeVal); + setDacRange(dacNum-1,rangeVal-1); + // 0 the output + int value = 0; + if (rangeVal > 2) + { + value = 65535 / 2; + } + dacState[dacNum - 1] = value; + setDac(dacNum - 1, value); + } else + { + Serial.println(sarErrorString); + } + } + + // Set Signal LED flag + else if (command == "SSL") + { + error = false; + byte result = inputString.substring(3).toInt();; + if (result == 0) + { + useSignalLEDs_ = false; + digitalWrite(dacLed, 0); + digitalWrite(ttlLed, 0); + digitalWriteDirect(trigLed,0); + } + else if (result == 1) + { + useSignalLEDs_ = true; + // TODO: make sure the LEDs are set correctly? + } + else error = true; + if (!error) + { + Serial.print("!SSL"); + Serial.println(result); + } else + { + Serial.println(sslErrorString); + } + } + + //status commands + else if(inputString == "STAT?\n") {debug(); } + else if(inputString == "TEST?\n") {diagTest(); } + else if(inputString == "CLEAR_ALL\n") {clearTable(); } + else if(inputString.substring(0,9) == "CLEAR_DAC") {clearDac(); } + + else + { + Serial.println(generalErrorString); + } + + + clearSerial(); + digitalWrite(readyLed,HIGH); + } //EXIT LOOP FOR SERIAL HERE + + +/*********************This block runs the high speed control interface *********************/ + +/****checks the acquisition order + * mode 0 == channel first eg set ch1 stweep Z + * mode 1 == Z first EG step Z then Ch1 Ch2 then Step Z ... + */ + +/* + while(trigArmed) + { // just sit here and wait for the next command until armed is off, which can only happen @ end of sequence + unsigned long tStart = millis() + timeOut; //set timeout position + unsigned long tLed = 0; + // focus commands = start, step, #loops,direction,slave,current step + //if(program == 0) {inTrigger=true; } //force a first step + + if( inTrigger ) + { //we recieved a trigger from our source + // and is mode 0, and is at 0 position OR + // and is mode 1, and is at any position OR + // if focus isn't enabled + */ + /* + * When should a channel update be issued? When + * focus is ON, and mode is sweep per channel (0) AND F position is 0 + * focus is ON, and mode is slave to channel (1) + * focus is off any time + */ + + // boolean runUpdate = true; + /* + if( (focArray[1] != 0) && (focArray[4] == 0) ){ //if we are using the focus and focus will sweep through a single channel setting + if( focArray[5] == 0 ) { runUpdate = true;} //AND if the focus is at position 0 (start of sweep) + } + + if( (focArray[1] !=0) && (focArray[4] == 1)) { runUpdate = true; } // Case where channel is switching each Z plane + if(focArray[1] == 0) {runUpdate=true;} //Case where no focus block used so update the channel + */ + /* + //do DAC and TTL control stuff, not focus stuff though + if(runUpdate) { + byte walker=0; + for(walker = 0 ; walker < 15 ; ++walker ){ //sets DACs 1-16 + dac_write(10,0, DAC [walker], dacArray [program] [walker]); // Set DAC Lines + digitalWriteDirect( ttl [walker] , ttlArray[program] [walker] ); //set TTL lines + } + digitalWriteDirect( ttl [walker] , ttlArray[program] [walker] ); //set 16th TTL line - + } + ++program; +*/ + /* THIS MOVES AROUND THE Z FOCUS + * in this case, we assume a trigger was recieved, but we should only mess with focus stuff if it's on and if it's needed + * here we only want to update the focus position in the case that EITHER + * Mode = 0 Channel 1 - Z 1, Z2, Z3 + * mode = 1 Z 1 - CH1, CH2, Z2, Ch1, Ch2 + */ +/* + if( (focArray[1] != 0) && ( focArray[4]==0 )){ fastFocus(); } // if any focus array is active, and MODE = SWEEP OVER CHANNEL + if( (focArray[1] != 0) && ( focArray[4]==1 )){ // in this case sweep is active and MODE = STEP AFTER CHANNEL + if((program == maxProgram-1) && (focArray[5] <= focArray[2])) {fastFocus();} + } + + delay(delArray[program]); //wait for specified delay + + if( focArray[1] == 0) {++program;} //if not using focus at all + if( (focArray[1] != 0) && (focArray[4] == 1) ) { //focus is used but in step mode + ++program; + if( (program > maxProgram) && (focArray[5] != 0) ) { //because we are stepping the focus, program must be reset to 0 in this case we know the focus has not completed, so we can reset the main program array position + program=0; + } + } + */ + } //END OF TRIGGER RESPONSE + + /* + inTrigger=false; //turn off trigger + tStart = millis() + timeOut; //update timeout delta for next run + + //Done moving stuff, now wait for the next input + while(!inTrigger){ //wait for next pulse + if(millis() > tStart) { + trigArmed=false; + program = maxProgram+1; + Serial.println("Timeout Exceeded"); + allOff(); + break; + } + //we hit the timeout so end the sequence + if(tLed < millis() ) + { + digitalWrite(readyLed,!digitalRead(readyLed)); + tLed = millis() + 30; + } + } + + if(program > maxProgram) { //end and cleanup + ++runCycles; + if(runCycles == timeCycles) { //we are done + trigArmed=false; + program=0; + allOff(); + Serial.println("!SAFE"); + digitalWrite(readyLed,HIGH); + } + if(runCycles < timeCycles) {program = 0;} + } + } //close trigarmed +} //close main loop +*/ + + +void allOff() +{ + for(byte walker=0;walker<15;++walker) + { //sets DACs 1-16 + dac_write(10,0, DAC [walker], 0); // Set DAC Lines + } + setPinGroup(0,0); + setPinGroup(1,0); +} + + +void clearSerial() +{ + //STUFF TO CLEAN OUT THE SERIAL LINE + inputString = ""; // clear the string: + stringComplete = false; +} + +void setDac(byte dNum,int dVal) +{ + dac_write(10,0, dNum, dVal); // Send dac_code + //led indication + if (useSignalLEDs_) + { + int dacSum = 0; + for (byte d=0; d < 16; d++) + { + dacSum += dacState[d]; + } + digitalWrite(dacLed, dacSum > 0); + } +} + +// sets the output state of the given pin number +void setTTL(byte t1, boolean t2) +{ + byte pin = t1 - 1; + digitalWriteFast(ttl[pin], t2); + if(t2) + { + bitSet(ttlState, pin); + } else + { + bitClear(ttlState, pin); + } + if (useSignalLEDs_) + { + digitalWriteFast(ttlLed, ttlState > 0); + } +} + +void setDacCheckBlanking(byte dacNr) +{ + if (dacBlanking[dacNr]) + { + triggerPinState = digitalReadFast(trig[0]); + dacBlankOnLow[dacNr] == triggerPinState ? + setDac(dacNr, dacState[dacNr]) : setDac(dacNr, 0); + } else { + setDac(dacNr, dacState[dacNr]); + } +} + + +void setPinGroupCheckBlanking(byte pinGroup) +{ + if (pinGroupBlanking[pinGroup]) + { + triggerPinState = digitalReadFast(trig[0]); + pinGroupBlankOnLow[pinGroup] == triggerPinState ? + setPinGroup(pinGroup, pinGroupState[pinGroup]) : setPinGroup(pinGroup, 0); + } else { + setPinGroup(pinGroup, pinGroupState[pinGroup]); + } +} + +inline void setPinGroup(byte, byte) __attribute__((always_inline)); +// sets pins 1-8 (pinGroup 0) or 9-16 (pinGroup 1) according to the mask in value +inline void setPinGroup(byte pinGroup, byte value) +{ + byte adder = (pinGroup) * 8; + for (byte b = 0; b < 8; b++) + { + if( b+adder < 9 ) { + digitalWriteFast(ttl[b + adder], (((value) >> (b)) & 0x01) ); + } + + else if( b+adder > 8) { + digitalWriteFast(ttl[b+adder],(((value) >> (b)) & 0x01)); + } + + //Serial.print("TTL " ); + //Serial.print(b + adder); + //Serial.print(" , "); + //Serial.println((((value) >> (b)) & 0x01)); + + } + if (pinGroup == 1) + { + ttlState = (ttlState & 0xff00) | value; + } else + { + ttlState = (ttlState & 0x00ff) | (value << 8); + } + if (useSignalLEDs_) + { + digitalWriteFast(ttlLed, ttlState > 0); + } +} + +/* + SerialEvent occurs whenever a new data comes in the hardware serial RX. This + routine is run between each time loop() runs, so using delay inside loop can + delay response. Multiple bytes of data may be available. +*/ +void serialEvent() { + trigArmed = false; + while (Serial.available()) { + // get the new byte: + char inChar = (char)Serial.read(); + // add it to the inputString: + inputString += inChar; + // if the incoming character is a newline, set a flag + // so the main loop can do something about it: + if (inChar == '\n') { + stringComplete = true; + } + } +} + +/*INTERRUPT CODE FOR TTL INPUT ***************/ +/* +void sigIn() //sigin is the input response line - this recieves the trigger input from an external source +{ + //if(!trigArmed) + //{ + digitalWrite(trigLed,!digitalRead(trigLed)); + //} + inTrigger=true; +} + +void configureTrigger(byte tOption) +{ + trigMode = tOption; //assign input value to global + switch (trigMode) { + case 0: + attachInterrupt(trig[0],sigIn,LOW); + break; + case 1: + attachInterrupt(trig[0],sigIn,HIGH); + break; + case 2: + attachInterrupt(trig[0],sigIn,RISING); + break; + case 3: + attachInterrupt(trig[0],sigIn,FALLING); + break; + case 4: + attachInterrupt(trig[0],sigIn,CHANGE); + break; + } +} +*/ + +void debug() +{ + if(debugT < millis() ) //if this is the first time debug has run, this will execute, otherwise has enough time elapsed? + { + Serial.print("Input Trigger = "); + Serial.println(digitalReadFast(trig[0])); + //Serial.print(", OpMode = "); + //Serial.println(opMode); + + // REPORT TTL + Serial.print("TTL: "); + for(int i=0;i<16;++i) + { + char sOut[100]; + sprintf(sOut,"%d=%d,",i+1, digitalRead(ttl[i])); + Serial.print(sOut); + } + Serial.println(""); + + //REPORT DAC + Serial.print("DAC:"); + for(int i=0;i<16;++i) + { + char sOut[200]; + sprintf(sOut,"%d=%u,",i+1, dacState[i]); + Serial.print(sOut); //used to print sOut + } + + Serial.println(); + Serial.println("Digital output buffers:"); + for (int pg = 0; pg < 2; pg++) + { + if (pg == 0) Serial.print("Pins 1-8 buffer size 1-8: "); + else if (pg == 1) Serial.print("Pins 9-16 buffer size: "); + char sOut[6]; + sprintf(sOut, "%d", ttlArrayMaxIndex[pg]); + Serial.println(sOut); + for (int i = 0; i < ttlArrayMaxIndex[pg]; i++) + { + sprintf(sOut, "%u ", ttlArray[i][pg]); + Serial.print(sOut); + } + if (ttlArrayMaxIndex[pg] > 0) { Serial.println(); } + } + + Serial.println("DAC buffers:"); + for (int dac = 0; dac < NR_DACS; dac++) + { + char sOut[25]; + sprintf(sOut, "DAC buffer size: %d", dacArrayMaxIndex[dac]); + Serial.println(sOut); + for (int i = 0; i < dacArrayMaxIndex[dac]; i++) + { + sprintf(sOut, "%u ", dacArray[i][dac]); + Serial.print(sOut); + } + if (dacArrayMaxIndex[dac] > 0) { Serial.println(); } + } + + } + // send an empty line so that the receiver know we are done + Serial.println(""); +} + + /* + //Report program arrays + if(opMode == 3) + { + Serial.println("Sequencer Programming Status"); + Serial.print("MaxProgram = "); + Serial.println(maxProgram); + Serial.print("PCHANNEL = "); + Serial.println(pChannel); + //report DACs + Serial.println("PROG, DAC1, DAC2, DAC3, DAC4, DAC5, DAC6, DAC7, DAC8, DAC9,DAC10,DAC11,DAC12,DAC13,DAC14,DAC15,DAC16/FOCUS"); + for(int p=0;p= 0; i--) + rx[i] = SPI.transfer(tx[i]); //! 2) Read and send byte array + + output_high(cs_pin); //! 3) Pull CS high +} + +inline void digitalWriteDirect(int, boolean) __attribute__((always_inline)); + +void digitalWriteDirect(int pin, boolean val){ + digitalWriteFast(pin,val); +} + +/* +inline int digitalReadDirect(int) __attribute__((always_inline)); + +int digitalReadDirect(int pin){ + return !!(g_APinDescription[pin].pPort -> PIO_PDSR & g_APinDescription[pin].ulPin); +} + +inline int digitalReadOutputPin(int) __attribute__((always_inline)); + +int digitalReadOutputPin(int pin){ + return !!(g_APinDescription[pin].pPort -> PIO_ODSR & g_APinDescription[pin].ulPin); +} +*/ + + + +/*******************LICENSING INFO************** + + * Copyright (c) 2018, Advanced Research Consulting + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * + *******************/ \ No newline at end of file From 45a05f4ac49efa78c5e322c29d08ebf5310c428c Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Fri, 24 Feb 2023 10:15:21 -0800 Subject: [PATCH 07/10] Update readme --- README.md | 8 +++++--- src/Overdrive/README.md | 19 +++++++++++++++++++ src/Overdrive/diagram.png | Bin 0 -> 104169 bytes src/TriggerScope_V3/README.md | 2 +- 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 src/Overdrive/README.md create mode 100644 src/Overdrive/diagram.png diff --git a/README.md b/README.md index bc81f9e..7664f60 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,13 @@ This firmware - in combination with the Micro-Manager "TriggerscopeMM" device ad allows for control of 16 analog and 16 digital outputs. Outputs can transition between settings triggered by a Trigger input signal at micro-second time scales. For full documentation -of the use of this firmare, see: https://micro-manager.org/wiki/TriggerScopeMM +of the use of this firmware, see: https://micro-manager.org/wiki/TriggerScopeMM -# Firmwave versions +# Firmware versions TriggerScope version 3 boards (serial numbers 1932-1970) must use the TriggerScope_V3 firmware. TriggerScope version 4 boards (serial numbers 1971-2117) must use the TriggerScope_V4 firmware. -TriggerScope version 4B boards (serial numbers 2118 and later) must use the TriggerScope_V4 firmware. \ No newline at end of file +TriggerScope version 4B boards (serial numbers 2118 and later) must use the TriggerScope_V4 firmware. + +An extension of the firmware which allows overdrive pulses to be applied at the beginning of the analog signal is also available for V4 and V4B boards. \ No newline at end of file diff --git a/src/Overdrive/README.md b/src/Overdrive/README.md new file mode 100644 index 0000000..66416bb --- /dev/null +++ b/src/Overdrive/README.md @@ -0,0 +1,19 @@ +# Overview +This firmware allows voltage overdrive and underdrive pulses to be applied at the beginning of the analog output signal as illustrated in this ![diagram](diagram.png). + +Overdrive pulses can be incorporated into DAC sequences. The voltage and duration of each overdrive pulse can be independently programmed. + +Extensions of the original firmware to enable overdrive pulses include: + - POV: "Program Over Voltage". + This command sets the voltage of the overdrive pulse + - POD: "Program OverVoltage Delay". + This command sets the duration of the overdrive pulse. After this time expires, the voltage on the specified pin will set to the state specified in the PAS list. + - BAD (sets delay in blanking mode of analog output) and BAL (sets length of analog output pulse in blanking mode) commands are deprecated. + + - See command info using "?\n" command for details on use. + +Ensure the correct firmware version is used for a given board serial number, as described earlier. + +For more information on serial # or if you have errors please contact ARC at advancedreseach-consulting.com + +Special thanks to the Mehta Lab at the Chan Zuckerburg Biohub for supporting these extensions! \ No newline at end of file diff --git a/src/Overdrive/diagram.png b/src/Overdrive/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..58d94a5afe9398214fe416d51b9592b330c632e7 GIT binary patch literal 104169 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 RlO6r3-`BZUc<1r+{{^eXv~B Port), and select the Arduino Due Programmin port (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. \ No newline at end of file +To install this firmware, you download and install the [Arduino IDE](https://www.arduino.cc/en/software). Then power your TriggerScope, connect the USB cable to your computer, and open the file TriggerScope_V3/TriggerScope_V3.ino in the [Arduino IDE](https://www.arduino.cc/en/software). Select the port under which your device appears (Tools > Port), and select the Arduino Due Programming port (Tools > Board). Press the "verify" button, and - if compilation succeeds - press the Upload button. \ No newline at end of file From 4e2c03463170beda0f228be56420d9b1675a3a80 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Fri, 24 Feb 2023 10:19:17 -0800 Subject: [PATCH 08/10] update image formatting format image update image formatting update image formatting --- src/Overdrive/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Overdrive/README.md b/src/Overdrive/README.md index 66416bb..443f070 100644 --- a/src/Overdrive/README.md +++ b/src/Overdrive/README.md @@ -1,5 +1,5 @@ # Overview -This firmware allows voltage overdrive and underdrive pulses to be applied at the beginning of the analog output signal as illustrated in this ![diagram](diagram.png). +This firmware allows voltage overdrive and underdrive pulses to be applied at the beginning of the analog output signal as illustrated below: . Overdrive pulses can be incorporated into DAC sequences. The voltage and duration of each overdrive pulse can be independently programmed. From 931be45a2a4f3ef9d68c6024d00feb42712a0755 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Fri, 24 Feb 2023 10:27:26 -0800 Subject: [PATCH 09/10] Update README.md --- src/Overdrive/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Overdrive/README.md b/src/Overdrive/README.md index 443f070..781d629 100644 --- a/src/Overdrive/README.md +++ b/src/Overdrive/README.md @@ -10,7 +10,7 @@ Extensions of the original firmware to enable overdrive pulses include: This command sets the duration of the overdrive pulse. After this time expires, the voltage on the specified pin will set to the state specified in the PAS list. - BAD (sets delay in blanking mode of analog output) and BAL (sets length of analog output pulse in blanking mode) commands are deprecated. - - See command info using "?\n" command for details on use. +See command info using "?\n" command for details on use. Ensure the correct firmware version is used for a given board serial number, as described earlier. From 1b888789c30c445530a515edbdb38f4ebf3251b9 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Fri, 24 Feb 2023 10:50:53 -0800 Subject: [PATCH 10/10] Update README.md --- src/Overdrive/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Overdrive/README.md b/src/Overdrive/README.md index 781d629..587aebd 100644 --- a/src/Overdrive/README.md +++ b/src/Overdrive/README.md @@ -12,6 +12,8 @@ Extensions of the original firmware to enable overdrive pulses include: See command info using "?\n" command for details on use. +This functionality will be available in the TriggerScopeMM Micro-manager device adapter in an upcoming update. + Ensure the correct firmware version is used for a given board serial number, as described earlier. For more information on serial # or if you have errors please contact ARC at advancedreseach-consulting.com