Introduction: OpenDAC Seekat: 8-channel, 16-bit, Low-cost Laboratory DAC

The openDAC Seekat is an 8 channel, +/- 10 V, 16 bit digital-to-analog converter (DAC) based on the AD5764 chip from Analog Devices. This DAC was designed with the needs of a physics research laboratory in mind, but we hope a broader community will find this simple yet versatile instrument useful as well. Please visit openDACs.com for more information and future low-cost, easy to assemble, precision laboratory electronics. The website also includes performance characterization of the Seekat and an output noise comparison of several widely used laboratory DACs, including Seekat.

Step 1: Hardware Overview and Part List

Seekat is based on evaluation boards for the AD5764 chip from Analog Devices. Communication with the boards over SPI is done with an Arduino, controlled by an isolated USB connection to a PC. To simplify communication wiring and power distribution, we use a custom Arduino shield. A complete electrical schematic is shown above.

Here is the part list for Seekat. We assume you have hook-up wire and chip resistors on hand. If not, buy those too (see schematic for resistor values, size 1206).

PartSupplierPart No. (link)Price (9/14/2014)Qty.Subtotal
AD5764 Eval boardDigikeyEVAL-AD5764EBZ-ND$98.752$197.50
ADUM4160 USB isolatorDigikeyEVAL-ADUM4160EBZ-ND$66.501$66.50
D-SUB cable (for power)DigikeyAE9869-ND$3.001$3.00
D-SUB/ terminal block bulkhead adaptorDigikey277-2668-ND$22.071$22.07
Indicator lights - bicolorDigikey558-3001-007F$2.864$11.44
3 position 0.1" terminal blockDigikeyA98334-ND$1.993$5.97
2 position 0.1" terminal blockDigikeyA98333-ND$1.553$4.65
Right angle headerDigikey S1112EC-40-ND$0.741$0.74
Straight headerDigikeyS1012EC-40-ND$0.561$0.56
EnclosurePar-metal 14-19101B$151.001$151.00
6" BNC/SMB bulkhead cableAmazon(link)$6.008$48.00
Jumper wireAmazon(link)$9.451$9.45
Mushroom fastenerAmazon(link)$16.441$16.44
Arduino UNOAmazon(link)$23.111$23.11
6" USB-A to USB-B cableAmazon(link)$4.672$9.34
USB-A to USB-B feedthroughL-comECF504-UAB$6.251$6.25
5/15/15 Triple output power supplyAcopian51515T6A$315.001$315.00
Seekat Arduino ShieldOSHPark(link)$23.301/3$7.76
Total$898.78

A significant fraction of the cost comes from the power supply, the enclosure, and the USB isolator. If cost is a more important concern that noise (and the differences may be minor) and appearance (the differences will not be minor), almost $500 can be saved by eliminating the USB isolator and finding lower cost alternatives for the enclosure and power supply, such as this very inexpensive switching power supply: Mean Well RT-65C.

Step 2: Prepare the AD5764 Boards and Machine the Enclosure

Solder a 14-pin header to the AD5764 eval board so the Arduino can communicate with the AD5764 via SPI. You may want to tape the top side to make it easier to solder the bottom side.

The AD5764 eval board has 14 jumpers that set various options on the board. Refer to the datasheet for a complete description. For the Seekat, set the jumpers like this (refer to the picture above):
LK1: B LK2: B LK3, 5A, 5B, 6, 12, 13, 14: leave in place LK4: B LK7: B LK8: A LK9: B LK10: remove LK11: A

Machine holes in the enclosure front panel for BNC feedthroughs and indicator LEDs, and in the back panel for the D-sub power and USB feedthroughs. Attach all the feedthroughs to the enclosure and attach the front and back panels. For machining simplicity, we used circular BNC feedthroughs. Use lock-washers for the BNCs and make the nuts nice and tight; alternatively, machine D-holes and get appropriate feedthroughs.

Step 3: Solder Components Onto the Seekat Arduino Shield

Now solder right-angle headers, screw terminals, and resistors to the OpenDAC Seekat Arduino shield (see above). You can ensure a good fit to the Arduino if you solder the headers with the shield in place, just make sure not to overheat the Arduino (be quick and minimize soldering iron heat). Resistor values are printed on the shield and can also be found in the schematic in Step 1.

Step 4: Connect PCBs and Install in Enclosure

Connect the Arduino, USB Isolator, and USB feedthrough using the two 6-inch USB A-B cables, and connect the SMB-BNC cables to the chassis and the AD5764 eval boards. Then attach the four PCBs (two AD5764 eval boards, Arduino, and USB isolation board) to the enclosure with mushroom-head fasteners (unisex Velcro). Refer to the picture in the next step for a suggested layout. If you use half as much Velcro as shown above, it may be easier to remove and reposition the PCBs.

Step 5: Make Power Connections

Make power connections as shown below. Twisted pairs for power likely have no benefit, but they keep things tidy. You will have an easier time with the screw terminals if you use solid conductor hook-up wire. The SMB cables to the AD5764 boards are shown connected below, but other wiring may be easier if you save those until the end.

Connect a 9-pin D-sub cable to your power supply according to the pinout shown above.

Don't blow up the eval board: -15 V goes to Vss, +15 V goes to Vdd.

Step 6: Wire the Power and Communication Indicator LEDs

Wire the power and communication indicator LEDs as shown above. If the LED doesn’t light up when you plug in the power cable, try reversing the leads.

Step 7: Add the Communication Wiring

Make all the connections for communication using jumper wires between the Seekat Arduino shield and the J21 pins on the EVAL-AD5764 boards. See above for a communication wiring schematic and a picture of the completed Seekat. Labels (LDAC, MISO, etc.) refer to those printed on the Seekat Arduino shield which differ in some cases from the labels on the AD5764 boards.

Put the lid on your enclosure and the hardware is done! Plug in the power and the +/- 15 V and +5 V indicator LEDs will turn on, but the communication LED will not turn on until the Seekat firmware is uploaded to the Arduino (next step).

Step 8: Software

Install the Arduino IDE on the computer that will be controlling the DAC and upload the Seekat Firmware to the Arduino.

Seekat Firmware

//Ardunio code for controlling Seekat DC voltage box from openDACS.com.  Designed for Arduino UNO.
#include “SPI.h” // necessary library
int sync=10; // using digital pin 10 for SPI slave select
int ldac=9; // Load dac (not implemented). You need to change some jumpers on the boards if you want to use synchronous updating and modify arduino code.
int clr=8; // DAC Clear (not implemented). You need to change some jumpers on the AD58764 boards if you want to use this.
void setup()
{
Serial.begin(9600);
pinMode(7, OUTPUT); // we use this for SS pin
pinMode(sync, OUTPUT); // we use this for SS pin
pinMode(ldac, OUTPUT); // we use this for SS pin
digitalWrite(7, HIGH);
digitalWrite(sync, HIGH);
SPI.begin(); // wake up the SPI bus.
SPI.setBitOrder(MSBFIRST); //correct order for AD5764.
SPI.setClockDivider(SPI_CLOCK_DIV32);
SPI.setDataMode(SPI_MODE1); //1 and 3 communicate with DAC. 1 is the only one that works with no clock divider.
}
void setValue(byte DB[10])
{
if (DB[0] == 255&&DB[1]==254&&DB[2]==253) // These bytes serve as a control that communication is working, and are reserved for future functionality such as synchronous updating, clear, and native arduino autoramp.
{
digitalWrite(sync, LOW); //assert sync-bar
int o1 = SPI.transfer(DB[3]); // send command byte to DAC2 in the daisy chain.
Serial.flush();
int o2 = SPI.transfer(DB[4]); // MS data bits, DAC2
Serial.flush();
int o3 = SPI.transfer(DB[5]);//LS 8 data bits, DAC2
Serial.flush();
int o4 = SPI.transfer(DB[6]);// send command byte to DAC1 in the daisy chain.
Serial.flush();
int o5 = SPI.transfer(DB[7]);// MS data bits, DAC1
Serial.flush();
int o6 = SPI.transfer(DB[8]);//LS 8 data bits, DAC1
Serial.flush();
digitalWrite(sync, HIGH);//raise sync-bar to change the dac voltage. Must have LDAC-bar tied low.
Serial.println(o1);
Serial.println(o2);
Serial.println(o3);
Serial.println(o4);
Serial.println(o5);
Serial.println(o6);
}
else //This allows you to check on the scope what has been received by the Arduino for trouble shooting. Use pin 7 to trigger, then look at output of pins 13 (sclk) and 11 on the arduino to readout the bytes the arduino is getting.
{
digitalWrite(7, LOW);
SPI.transfer(DB[0]); Serial.flush();
SPI.transfer(DB[1]); Serial.flush();
SPI.transfer(DB[2]); Serial.flush();
SPI.transfer(DB[3]); Serial.flush();
SPI.transfer(DB[4]); Serial.flush();
SPI.transfer(DB[5]); Serial.flush();
SPI.transfer(DB[6]); Serial.flush();
SPI.transfer(DB[7]); Serial.flush();
SPI.transfer(DB[8]); Serial.flush();
digitalWrite(7, HIGH);
}
}
void loop()
{
if ( Serial.available()) // wait until all data bytes are avaialable
{
byte bytes[9];
for (int i=0; i<9; i++) {
bytes[i] = Serial.read();
delay(2); //2mS pause to make sure bytes don’t run into each other.
}
setValue(bytes);
}
}

Communication with the Seekat can be done with any program capable of serial communication. We provide Matlab drivers to write and read voltages, and also to calibrate the offset and gain of each channel using a DMM.

Those functions are also included below:

Write voltage

%Find out which port the Arduino is using.  One way is to use the Arduino IDE.
% These are the commands you must execute in matlab to initialize for a virtual serial port COM5 on a PC.
% global a; a = serial(‘COM5′,’baudrate’,115200);fopen(a)
%Mac, for example:  global a; a = serial(‘/dev/tty.usbmodem1431′,’BaudRate’,115200); fopen(a)
 function[] = setvoltageDC(channel,voltage)
 
global a
 
if voltage > 10
voltage = 10.0;
elseif voltage < -10
voltage = -10.0;
 end
switch channel
case 1
n1 = 19;
n2=0;
m1=1;
m2=0;
case 2
n1 = 18;
n2=0;
m1=1;
m2=0;
case 3
n1 = 17;
n2=0;
m1=1;
m2=0;
case 4
n1 = 16;
n2=0;
m1=1;
m2=0;
case 5
n1 = 0;
n2=19;
m1=0;
m2=1;
case 6
n1 = 0;
n2=18;
m1=0;
m2=1;
case 7
n1 = 0;
n2=17;
m1=0;
m2=1;
case 8
n1 = 0;
n2=16;
m1=0;
m2=1;
    otherwise
        disp(‘INVALID CHANNEL’)
end
if voltage >= 0
dec16 = round((2^15-1)*voltage/10); %Decimal equivalent of 16 bit data 
else
    dec16 = round(2^16 – abs(voltage)/10 * 2^15); %Decimal equivalent of 16 bit data 
end
bin16 = de2bi(dec16,16,2,‘left-msb’); %16 bit binary
d1=bi2de(fliplr(bin16(1:8))); %first 8 bits
d2=bi2de(fliplr(bin16(9:16))); %second 8 bits
%disp([255,254,253,n1,d1*m1,d2*m1,n2,d1*m2,d2*m2]); %uncomment to check what’s being sent to the Arduino
pause(.005);
fwrite(a,[255,254,253,n1,d1*m1,d2*m1,n2,d1*m2,d2*m2]);
while a.BytesAvailable
fscanf(a,‘%e’);
end
end

Read voltage

%Find out which port the Arduino is using.  One way is to use the Arduino IDE.
 
% These are the commands you must execute in matlab to initialize for a virtual serial port COM5 on a PC.
 
% global a; a = serial(‘COM5′,’baudrate’,115200);fopen(a)
 
%Mac, for example:  global a; a = serial(‘/dev/tty.usbmodem1431′,’BaudRate’,115200); fopen(a)
function v = getvoltageDC(channel)
global a
switch channel
case 1
n1 = 19+128;
n2=0;
m1=1;
m2=0;
case 2
n1 = 18+128;
n2=0;
m1=1;
m2=0;
case 3
n1 = 17+128;
n2=0;
m1=1;
m2=0;
case 4
n1 = 16+128;
n2=0;
m1=1;
m2=0;
case 5
n1 = 0;
n2=19+128;
m1=0;
m2=1;
case 6
n1 = 0;
n2=18+128;
m1=0;
m2=1;
case 7
n1 = 0;
n2=17+128;
m1=0;
m2=1;
case 8
n1 = 0;
n2=16+128;
m1=0;
m2=1;
    otherwise
        disp(‘INVALID CHANNEL’)
end
pause(.02);
fwrite(a,[255,254,253,n1,0,0,n2,0,0]);
pause(.02);
fwrite(a,[255,254,253,n1,0,0,n2,0,0]);
pause(.02);
while a.BytesAvailable
    fscanf(a,‘%e’); % clear the buffer
end
fwrite(a,[255,254,253,0,0,0,0,0,0]);
pause(.01);
 
bdata=zeros([1,6]);
for i=0:5;
i=i+1;
r=fscanf(a,‘%e’);
bdata(i)=r;
end
bdata2=max(bdata(2)*2^8+bdata(3),bdata(5)*2^8+bdata(6));
 
if bdata2 < 2^15
    %disp(10*bdata2/(2^15-1))
    %bdata3=sprintf(‘%20f’,10.0*bdata2/(2^15-1));
    bdata3=10.0*bdata2/(2^15-1);
else
    %bdata3=sprintf(‘%20f’,-10.0*(2^16-bdata2)/2^15);
    bdata3=-10.0*(2^16-bdata2)/2^15;
    %disp(-10*(2^16-bdata2)/2^15)
end
v = bdata3;
end

Calibrate gain and offset

%plug the channel to be calibrated into a DMM.  You will need to have some function of your own to read the DMM voltage.
function[] = calibrateSeekat(channel)
 
global a
 
switch channel
case 1
n1 = 19;
n2=0;
m1=1;
m2=0;
case 2
n1 = 18;
n2=0;
m1=1;
m2=0;
case 3
n1 = 17;
n2=0;
m1=1;
m2=0;
case 4
n1 = 16;
n2=0;
m1=1;
m2=0;
case 5
n1 = 0;
n2=19;
m1=0;
m2=1;
case 6
n1 = 0;
n2=18;
m1=0;
m2=1;
case 7
n1 = 0;
n2=17;
m1=0;
m2=1;
case 8
n1 = 0;
n2=16;
m1=0;
m2=1;
    otherwise
        disp(‘INVALID CHANNEL’)
end
fwrite(a,[255,254,253,n1+24,0,0,n2+24,0,0]); %zero the offset register
fwrite(a,[255,254,253,n1+16,0,0,n2+16,0,0]); %zero the gain register
measuredValues = zeros(2,1);
 
voltage = 0;
if voltage >= 0
dec16 = round((2^15-1)*voltage/10); %Decimal equivalent of 16 bit data 
else
dec16 = round(2^16 – abs(voltage)/10 * 2^15); %Decimal equivalent of 16 bit data
end
bin16 = de2bi(dec16,16,2,‘left-msb’); %16 bit binary
d1=bi2de(fliplr(bin16(1:8))); %first 8 bits
d2=bi2de(fliplr(bin16(9:16))); %second 8 bits
%disp([255,254,253,n1,d1*m1,d2*m1,n2,d1*m2,d2*m2]);
pause(.005);
fwrite(a,[255,254,253,n1,d1*m1,d2*m1,n2,d1*m2,d2*m2]);
while a.BytesAvailable
fscanf(a,‘%e’);
end
pause(2)
 
blah = smget(‘V’); %your code here to read voltage
offset = -blah{1};
offsetsteps = round(offset/(38.14e-6));
offset8 =  de2bi(mod((offsetsteps),2^8),8,‘left-msb’);
    d1=0;%bi2de(fliplr(bin16(1:8))); %first 8 bits
    d2=bi2de(fliplr(offset8));%bi2de(fliplr(bin16(9:16))); %second 8 bits
 
    %disp([255,254,253,n1,d1*m1,d2*m1,n2,d1*m2,d2*m2])
    pause(.005);
fwrite(a,[255,254,253,n1+24,d1*m1,d2*m1,n2+24,d1*m2,d2*m2]); % +24 to access offset register
while a.BytesAvailable
fscanf(a,‘%e’);
end
pause(1)
voltage = -10;
if voltage >= 0
dec16 = round((2^15-1)*voltage/10); %Decimal equivalent of 16 bit data 
else
dec16 = round(2^16 – abs(voltage)/10 * 2^15); %Decimal equivalent of 16 bit data
end
bin16 = de2bi(dec16,16,2,‘left-msb’); %16 bit binary
d1=bi2de(fliplr(bin16(1:8))); %first 8 bits
d2=bi2de(fliplr(bin16(9:16))); %second 8 bits
%disp([255,254,253,n1,d1*m1,d2*m1,n2,d1*m2,d2*m2]);
pause(.005);
fwrite(a,[255,254,253,n1,d1*m1,d2*m1,n2,d1*m2,d2*m2]);
while a.BytesAvailable
fscanf(a,‘%e’);
end
pause(2)
 
blah = smget(‘V’);  %your code here to read voltage
gainerror = blah{1}+10;
gainsteps = round(gainerror/(152.59e-6));
gain8 =  de2bi(mod((gainsteps),2^8),8,‘left-msb’);
    %bin16 = de2bi(gain16,16,2,’left-msb’); %16 bit binary
    d1=0;%bi2de(fliplr(gain16(1:8))) %first 8 bits
    d2=bi2de(fliplr(gain8));%bi2de(fliplr(gain8(1:8))) %second 8 bits
pause(.005);
fwrite(a,[255,254,253,n1+16,d1*m1,d2*m1,n2+16,d1*m2,d2*m2]); % +16 to access gain register instead of data register
while a.BytesAvailable
fscanf(a,‘%e’);
end
pause(1)
voltage = 0;
if voltage >= 0
dec16 = round((2^15-1)*voltage/10); %Decimal equivalent of 16 bit data 
else
dec16 = round(2^16 – abs(voltage)/10 * 2^15); %Decimal equivalent of 16 bit data
end
bin16 = de2bi(dec16,16,2,‘left-msb’); %16 bit binary
d1=bi2de(fliplr(bin16(1:8))); %first 8 bits
d2=bi2de(fliplr(bin16(9:16))); %second 8 bits
%disp([255,254,253,n1,d1*m1,d2*m1,n2,d1*m2,d2*m2]);
pause(.005);
fwrite(a,[255,254,253,n1,d1*m1,d2*m1,n2,d1*m2,d2*m2]);
while a.BytesAvailable
fscanf(a,‘%e’);
end
end