Updates 12/11/2020:
- Board v2.1, contributed by Filipe Carvalho. Improved signal-to-noise on outputs - 1.7 mV down from 16 mV.
- Sketch for a Metro M4 base board. Output of 100,000 samples/sec with memory for 20,000 waypoints.
- Driver code is Python3-compatible.
octoDAC 8-channel analog output Arduino shield
A cheap USB-programmable analog output is great for setting intensities, power levels, or general analog signalling in many experimental contexts. There are lots of solutions when you want one or two of these on your microcontroller. But, for example, if you want to trim the output of all of the lasers in your launch then you need 4-6 analog outputs. There are many fewer examples for how to do this at low cost and with straightforward programming.
The board presented here is a shield for the Arduino Uno with 8 individually-controllable analog outputs. Each is buffered thorugh an op amp and has an individual SMA output connector. Board can stack with a NicoLase shield for synchronized + triggered digital and analog control of a dozen or more devices. With the provided firmware communication is through a standard USB serial port. A Python package is provided as well.
Analog output is based on TI DAC8568 16-bit octal digital-to-analog converter, programmed over Arduino SPI bus. Given the remainder of the board and use case, it is unlikely that the resolution of this chip can be fully realized in the output, but the 14- or 12-bit version is only a few $$$ cheaper and is not avaliable in the right package. More extensive filtering of the output would likely help at the cost of some time resolution. The use case effectively builds in a low-pass filter, manifested as a light source or inertial mass.
Channel output can be addressed individually over USB, limited by time resolution of the USB communciation. Alternatively the user can pre-load a waveform into the microcontroller memory. The waveform is supplied as sets of 'waypoints', or [channel, time (µsec), amplitude] values. Upon an input trigger, provided over USB or to Pin 2 (via header or MASTERFIRE on NicoLase shield), the microcontroller will cycle through the programmed waypoints sequence. A max of 100 of these waypoints can fit into an Arduino Uno's RAM capacity; for more, consider upgrading to an Adafruit Metro M4 or other higher-powered microcontroller as base driver.
Board is designed to stack between an Arduino headers and NicoLase shield. Sketch includes commands for both boards (.\code\Arduino\octoDAC_NicoLase_combo). With both you could control 6+ digital devices and 8 analog devices in a programmable sequence or waveform, all sync'd to a common trigger. Only advantage to sever these systems is if you'd like ~2x as many waypoints in the Arduino RAM.
Current board layout uses a pair of quad op amps as 2x amplifiers and buffers for signal coming off of the DAC8568. These are rail-to-rail op amps that cover a 0-5v range from 0-2v5 output from the DAC. Output will be of very high impedence from these outputs. Quoted op amps (MAX40078aud) are rated for 50 mA output per channel.
Python package is provided (.\code\Python\octoDAC) with a driver class (.\code\Python\octoDAC\octoDACDriver.py) and demo script illustrating use (.\code\Python\octoDAC\octoDACdemo.py). Feel free to extend these for your use case. Install with included setup.py file (pip install -e . from the .\code\Python directory). Note - a NicoLase and an octoDAC object can be loaded on same COM port if stacking both boards on a single Arduino.
Validation scripts quantifying the performance of the system and output data are included in .\code\Python\octoDAC\validation.
What this board can do:
- Analog output partner to NicoLase digital control
- 8 analog outputs, 0-5v
- Rudimentary waveform generation (100 'waypoints' across all channels)
- Triggered waveform output
- Pseudo-shutter mode
- USB serial communication for anticipated functions
In progress:
- Demos with 12+ devices on digital + analog outputs
- Ramps or programmatic waveforms
What this board will never do:
- Fast (> 5 kHz) waveforms
- High SNR analog outputs
- Fancy level shifting
- Dual-supply op amp output
PCB Build Notes:
Design for PCB has plenty of optional connection pads, depending on desired operation.
On each amplifier unit, there are resistors R(N) and R(N + 1). N is odd numbers 1 : 15; N + 1 is even numbers 2 : 16. If both are populated, voltage at output will be Vout = Vin * [1 + R(N) / R (N + 1)]. If unity gain is desired, then R(N) should be 0Ω and R(N + 1) should be unpopulated.
The specified op amps (MAX40078aud) are rail-to-rail output, but not input. To accommodate this, the 'A' series of the DAC8568 is used, which outputs 0-2v5 for full scale. This is combined with a 2x gain on the op amp output to realize 0-5v output. Resistors R1 to R16 are 10 kΩ in this config, though any value where each pair is matched would work fine to realize gain = 2.
To realize op amp outputs outside of 0-5v, the input voltages to the op amp registers must be changed from the default. Jumpers JP5 and JP6 on bottom of board select for op amp input voltage coming from either the Arduino on-board 5v regulator (default) or the unregulated Vin to Arduino through barrel connector. JP5 -> op amp 1 (IC2) -> Chan1-Chan4, JP6 -> op amp 2 (IC3) -> Chan5-Chan8. Input voltage to op amps can be up to 12v. To change, cut existing trace between jumper pads and bridge the other pads with solder. Remember the resistors chosen at each op amp output channel must reflect the desired output gain. For example, if supplying Arduino with +12v and jumper set to supply op amp from Vin, resistors R(N) = 30kΩ and R(N + 1) = 10kΩ will give a gain of 4, so output 0-10v from that channel. Dual supply (e.g. -6v to +6v) to op amp is supported by ICs but too complicated to implement on such a small board.
Even for 0-5v output, the noise performance and resolution may be able to be improved if using a more steady voltage source. Here you'd want to supply the Arduino with a low-noise source (like a battery) at <5v. Configure the resistors at each output to realize the desired gain.
The DAC8568 has a CLR pin to clear inputs. In the firmware this is used as a shutter for all channels. To use this functionality, populate R19 with a 0Ω resistor. If this pin is not going to be used, tie it to ground by populating R20 with a 0Ω resistor.
The LDAC pin on the DAC8568 is similar. The current firmware updates the DAC in 'synchronous' mode, where each output is addressed (and timed) separately. In this case, tie LDAC to ground by populating R18 with a 0Ω resistor and R17 open. If desired, the LDAC pin can be used as the trigger for 'asynchronous' mode updates to the DAC register. This may be adventageous if you want all channels to update at the same time. For this mode, leave R18 open and R17 populated with a 0Ω resistor.
Performance (w/ Arduino Uno base board):
Timing and voltage performance is summarized in .\code\Python\octoDAC\validation\summary\octoDACvalidation.pdf (nb - this is for v1 now). Results of tests summarized in .\code\Python\octoDAC\validation\ are available in .\code\Python\octoDAC\validation\output\ (includes v1 and v2 of board, with Arduino and Metro bases). See octoDACTestScript.py for acquisition of testing data through a USB-connected oscilloscope. This data is analyzed octoDACTestAnalysis.py.
These tests show ~60 µsec delay for each communication channel engaged in the DAC. All channels can be updated in < 450 µsec. Waveform timing precision can be < 1 µsec. These delays are predictable and can be offset in the waveform timing programmed in to the device.
Voltage output is linear across the 0-5V range, with ~15 mV (~1.5 mV in v2.1 board) ripple measured at each position. With any sort of low-pass filtering or averaging this resolution would increase.
Overall this performance is moderate. But for the cost (~$50 in parts) this is excellent. Any commercial alternative costs 10x more or you have to build your own!
Changes w/ Metro M4 SAMD51 base board:
Metro M4 w/ SAMD51 microcontroller has much higher clock speed (120 MHz) and memory compared to Arduino Uno. The performance of the octoDAC with this base board is conserdably improved from a timing perspective. Noise performance is slightly degraded (2.1 mV ripple vs 1.5-1.7 mV).
Delay between trigger and analog output rise is ~14 µsec. All channels are updated in under 75 µsec. Waveform waypoints take ~1 µsec to update at the output and the board memory can hold 20,000 of these points (vs 100 on Uno). A 100-sample 0-5v sine wave completes in under 1 ms at max speed.
The sketch for the Metro M4 includes changes to the NicoLase outputs, which require serial, rather than parallel, updating. Still, these updates complete with a delay of a few hundred nanoseconds from the first to last channel. Further changes affect the USB serial and SPI ports on the board. Overall performance is very similar to the Arduino sketch, wtih the enhancement in response speed and memory capacity.
Metro sketch can be applied with Arduino IDE, using the Adafruit-recommended updates. Likely similar results would come from the SparkFun version of the SAMD51 board. File is at ./code/Arduino/octoDAC_NicoLase_Metro.