![]() |
JQI Arduino Libraries
Arduino for the modern scientist.
|
SetListArduino is an Arduino library designed to provide easy integration for Arduino-controlled devices into the SetList computer control software used at the JQI. To set up a new Arduino device for SetList control, simply "register" the device with SetListArduino in your sketch, and provide as many callback functions as you need to implement the desired functionality. SetListArduino orchestrates the communication between LabView's SetList and each registered device, executing the appropriate callback function for each new line passed from SetList.
For examples and more details, see below!
Here is a short sketch to illustrate how one might integrate an AD9954 DDS board.
There are a few #define
statements in SetListArduino.h
to be aware of. If you want to overwrite these, do so before including SetListArduino.h
in your Arduino sketch.
#define MAX_SETLIST_LINES 512
– SetList array size; don't make a setlist with more than 512 lines!#define MAX_DEVICE_NUMBER 6
– deviceList array size. If you want more than 6 devices connected to a single Arduino, you'll need to increase this.#define MAX_PARAM_NUM 8
– Parameter array size. This limits the number of parameters that will be parsed from the serial stream; if you need more than 8, be sure to redefine this!#define SERIALCOMMAND_BUFFER 512
– Buffer length for serial commands. This limits a single serial line to 512 characters, which should hopefully be enough.#define SERIALCOMMAND_MAXCOMMANDLENGTH 8
– Max length of "short command." But to be economical, you should try to limit your short commands to a single character.#define SETLIST_ERROR_CHECK 1
– Sends error info back to LabView#define SETLIST_DEBUG
– If you want debug info printed back to the serial stream, make sure to define this.The "device channel" (see registerDevice()
below) corresponds directly to an index in _deviceList. Thus, make sure your channels start at 0 and are sequential. If you don't do this, the Arduino may segfault as it tries to execute functions in memory that has not been properly initialized.
Because of idiosyncrasies in how we decided to trigger the Arduino, SetListArduino will immediately initialize all devices with the callback given on the first line, and wait for a falling edge trigger to move to line #2. Thus, if you have some non-static output behavior on line #1, it will begin before the official "start" of the cycle.
The reason for this triggering behavior is that SetList (LabView edition) passes a single state per SetList line to a slaved digital channel on the PulseBlaster; if your SetList has an odd number of lines, you might end in the same state as the first SetList line, and thus not actually send a trigger for the first line.
Ultimately, this is a bug which should be fixed.
SetListArduino
is the name of the class which orchestrates the SetList computer control, while SetListImage
is the instance name you want to use.
Doing it this way allows us to actually attach trigger interrupts properly, since the interrupts need to know which class instance methods to call. Since SetListArduino
really should be a singleton class, requiring the name you use to be SetListImage
fixes this issue.
The SetListArduino library has two main classes: class SetListArduino
and class SetListDevice
. The class SetListArduino
is what you mainly have to worry about when coding an Arduino sketch; it orchestrates all of the communication between LabView and the controlled devices connected to your Arduino.
SetListDevice
is a templated class which inherits from a generic class SetListBase
(which does nothing by itself). When you register a device by calling SetListImage.registerDevice(...)
, SetListArduino
will create an instance of SetListDevice
. This instance of SetListDevice
contains the SetList and pointers to any callbacks necessary to execute said SetList.
To make this more concrete, consider a DDS-type device we want to control (like in the example sketch above). Say I have already instantiated the device itself, call it DDS1
. To make it computer controllable, I first need to register it with the singleton class object, SetListImage
:
SetListArduino
has now added the object DDS1
to position 0 in an array data member it owns called _deviceList
. I can register more devices if I wish, being sure to use sequentially increasing channel numbers so they are inserted properly into the array _deviceList
.
Now, when I register a command, I need to tell the Arduino what serial character it should listen to during SetList programming, and what it should do when it receives such a command. To this effect, I use
The callback function, setDDSFreq
, must take two arguments: a pointer of the same type as the device you're trying to control (here it is AD9954
), and an int *
pointer to a list of parameters. When a particular SetList line is executed, SetListDevice
will call that function and pass it a pointer to your device (ie, DDS) and a pointer to the parameter array.
Once you've uploaded your sketch with all the devices registered and command callbacks defined, etc., this is an example of what it expects to see from the serial stream:
What this means (and see the special short command summary in the next section) is:
f
, each taking a single parameterr
, some short command f
; short command r
takes 3 arguments.$
.Something worth noting: If you have, eg, 2 DDS devices on the same Arduino, you don't need to create separate callback functions for the same behavior. As long as you register both, you can use the same callback. For example,
Here, there are two different DDSs which implement the same kind of behavior (ie, "set a single-tone frequency"). They are on channels 0 and 1, so we register that behavior with SetListArduino
. There is also a PLL, which we might want to use the same short command for (eg, "f" for "frequency", to set a single-tone output frequency). But, because it is a different device type, we need a separate callback setPLLFreq
which we would define elsewhere like
or something similar. Hopefully you're starting to get the gist.
There are a few specially reserved short commands:
@
- activates a new device, eg, @ 1
activates device on channel 1.$
- initializes for new cycle. Call this when you want the Arduino ready to receive triggers from the PulseBlaster.?
- echo setlist back to serial terminal.#
- execute single setlist line, eg, # 0 5
executes (index) line 5 on channel 0. Note this is the index of the line, not the line number – the index counts from 0.Currently, as discussed briefly above, SetListArduino
immediately outputs the state for the first line after receiving the serial command $
indicating a sequence is beginning. It advances to the next line upon a falling-edge trigger, and then switches the interrupt to listen for a falling- or rising-edge.
When it encounters said trigger, an ISR routine jumps in and loops over the list of registered devices, calling _deviceList[index]->executeSetList(_line)
to execute the callbacks for the next line.
Because this is a single-threaded microcontroller, be cognizant of timing delays! In a spot check, it looks like a "simple" device which just toggles the output of a digital pin on the Arduino, there was a 6.25 us delay between the trigger edge and the pin state change. Depending on how many devices you have connected and how fast the Arduino can update each device, there may be a substantially longer delay before it actually updates an output.
For this reason, you also don't want triggers to come too soon after each other! As always, test offline before stretching to the limit.
At any rate, LabView should provide a single state change trigger for each ramp line in SetList where some controlled device needs to change its output. Thus, as long as you aren't changing outputs too quickly, you should be fine. Just be careful it is doing what you expect.
As alluded to above, you can actually use the digital/analog output pins on the Arduino itself. Just register a device of type int
(or some other dummy class). Then, in the callback function, change the output of your line as desired. You can get creative!
For better timing, we might consider implementing a feature where the Arduino pre-programs a device (eg, AD9954 DDS) before the next SetList trigger. When the trigger arrives, it simply has to trigger a (fast) digital IO update on the controlled device. Or, if we care even more about timing, we could implement additional SlavedDigital channels in LabView to trigger the actual device IO update. At some level, however, it becomes necessary to use an FPGA for precise timing applications.
Currently, we have the Arduino controlling a few different devices in the Sr lab. These libraries are also in the AMOArduino repository, and documentation of the hardware should be on the JQI wiki.