diff --git a/README.md b/README.md index 88ccc74..7664f60 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,21 @@ # 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 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 + +# 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. + +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..587aebd --- /dev/null +++ b/src/Overdrive/README.md @@ -0,0 +1,21 @@ +# Overview +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. + +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. + +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 + +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/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 diff --git a/src/Overdrive/diagram.png b/src/Overdrive/diagram.png new file mode 100644 index 0000000..58d94a5 Binary files /dev/null and b/src/Overdrive/diagram.png differ diff --git a/src/TriggerScope_V3/README.md b/src/TriggerScope_V3/README.md index c4d9b34..373b7a8 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 Programming 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/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..2c4d30d --- /dev/null +++ b/src/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/TriggerScope_V4B/TriggerScope_V4B.ino b/src/TriggerScope_V4B/TriggerScope_V4B.ino new file mode 100644 index 0000000..374c9f9 --- /dev/null +++ b/src/TriggerScope_V4B/TriggerScope_V4B.ino @@ -0,0 +1,1793 @@ +/****************************************** + 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 + 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. + * + * + * + *******************/