Sunday, November 17, 2013

Arduino and DAC

My quest to implement a version of the analogWrite() function on the Arduino that actually behaves as the name advertises continues.

In my previous post, I built a 4-stage Sallen-Key low pass filter to convert the PWM output to a flat signal, but it had shortcomings:
  • Lots of components to get a flat signal
  • Only 8 bit of resolution means only 256 voltage levels (although this could probably be improved using PWM tricks).
  • Unbuffered output meant I'd have to add a couple of op-amps behind it to get it to behave like a stable voltage source.
Today, I am trying to do the same thing with a real digital to analogue converter (a DAC) : the Analog Device DAC8412FPZ.

I just got my hands on one of these chip, and this is what it looks like:


Here's the data sheet for this little guy.

Advantage of this chip:
  • 12 bit output resolution gives you 4096 possible output voltage levels.
  • 28-pin DIP packaging makes for easy breadboarding and experimenting.
  • 4 channels (effectively, the chip contains 4 quasi independent DACs) so you can send 4 independent analogue signals to 4 separate devices.
  • Works with both unipolar or bipolar power supplies in [5V, 30V] range means you can power it from an Arduino +5V supply for experiments but still move the final project to a proper bipolar +/- 12V supply later on.
  • Outputs are buffered and can source or sink +/- 5 mA at 10V, which means you don't have to add a power amplification stage after the DAC if all you want is to drive small loads.
  • Data transfers to the chip is done with a parallel bus, which is annoying to wire up, but super simple to manipulate in software.
Disadvantages of this chip:
  • Chip is kinda bulky (4cm x 1.5cm)
  • Unless you manage to get your hands on a sample, chip is expensive (~ $35)
  • The parallel bus is a bit of a pain to wire up (12 wires) and you need a lot of pins on the micro controller unit side. 
This last part is actually the most annoying: you are going to need an Arduino Mega if you want enough digital ports to control this thing directly.

Here's  what it looks like all wired up on the breadboard:



A couple of things to note:
  • The big bundle of green wires on pin 30 to 41 of the Arduino (12 of them) is the data bus and it's the reason why you need a Mega to makes this work :(.
  • The two blue wire are the "address bus", that you use to select which of the 4 DACs you're fiddling with.
  • In case you're wondering what they're for, some patch cables are only in there to mechanically anchor the Mega to the breadboard.
  • The Arduino's digital pins 7, 6, 5, 4, 3, 2 are used to manipulate the control pins of the DAC.
  • The Arduino, via its A0 analog pin, is also used to read back the voltage output of the DAC to check that it all works (we only get a resolution of 1024 levels since the embedded ADC of the Arduino only does 10bits).
  • In the pictures, the DAC is powered by an external +/- 12V power supply. For this, I use this excellent little breakout board from the good folks at dangerous prototypes which neatly converts the output of an old PC ATX power supply I had lying around.
  • In this example, the VRefHigh and VRefLow pins of the DAC are hooked respectively to the +5V and GND of the Arduino, which means the DAC will output 4096 levels in the [0, 5V] interval. This makes it easy to read back the DAC's output voltage directly with the Arduino. However, you could hook these two pins to any other external reference (as long as VRefLow is less than VRefHigh and they're smaller than the power voltages), including making VRefLow negative to get the DAC to crank out negative voltages.
On the software side, here is a simple Arduino sketch that will:
  • Setup the Arduino pins to control the DAC
  • Reset the DAC
  • Control the DAC to repeatedly output all possible values in [VRefLow, VRefHigh] on all 4 channels at once
  • Use pin A0 to read back one of the DAC's voltage output
  • Dump the value read on the serial port.

Here is the code:

 // DAC8412FPZ  
   
 #define ADDR_BIT0 7 // Control pin got LSB of address bus  
 #define ADDR_BIT1 6 // Control pin got MSB of address bus  
 #define DATA_BIT0 30 // Control pin for LSB of data bus  
   
 #define RW 5 // Pin for read-write command : High -> read, Low -> write  
 #define CSB 4 // Pin for chip select : High -> Off, Low -> On. Chip ignores commands unless this is low  
 #define RSTB 3 // Pin to Reset all channels to midscale : High -> do nothing, Low -> do reset  
 #define LDACB 2 // Ping to control "Load DAC"  
   
 #define analogPin A0  
 static uint32_t count = 0;  
   
 // Prep the address bus to talk to one DAC  
 static inline void setAddrBus(  
   int dacId  
 )  
 {  
   digitalWrite(ADDR_BIT0, (dacId & 0x1) ? HIGH : LOW);  
   digitalWrite(ADDR_BIT1, (dacId >> 0x1) ? HIGH : LOW);  
 }  
   
 // Prep the data bus with a given value  
 static inline void setDataBus(  
   int16_t value  
 )  
 {  
   for(int i=0; i<12; ++i) {  
     digitalWrite(  
       i + DATA_BIT0,  
       (value & (1<<i)) ? HIGH : LOW  
     );  
   }  
 }  
   
 // Manipulate the "chip select" signal  
 static inline void ioState(  
   bool on  
 )  
 {  
   digitalWrite(CSB, on ? LOW : HIGH);  
 }  
   
 // Load a value on all 4 DACs  
 static inline void loadValue(  
   int16_t value  
 )  
 {  
   for(int i=0; i<4; ++i) {  
     ioState(false); delay(10);  
       setAddrBus(i); delay(10);  
       setDataBus(value); delay(10);  
     ioState(true); delay(10);  
     ioState(false); delay(10);  
   }  
 }  
   
 // Read a value from A0, output on serial  
 static inline void output()  
 {  
   uint16_t value = analogRead(analogPin);  
   Serial.print("count:");  
   Serial.print(count);  
   Serial.print(" millis:");  
   Serial.print(millis());  
   Serial.print(" value:");  
   Serial.println(value);  
 }  
   
 void setup()  
 {  
   // Prep control pins  
   pinMode( RW, OUTPUT);  
   pinMode( CSB, OUTPUT);  
   pinMode( RSTB, OUTPUT);  
   pinMode( LDACB, OUTPUT);  
   pinMode(ADDR_BIT0, OUTPUT);  
   pinMode(ADDR_BIT1, OUTPUT);  
   for(int i=0; i<12; ++i) {  
     pinMode(i + DATA_BIT0, OUTPUT);  
   }  
   
   // Reset DAC  
   digitalWrite(RSTB, LOW); delay(10); // Turn reset on  
   digitalWrite(RSTB, HIGH); delay(10); // Turn reset off  
   
   // Prep serial for output  
   Serial.begin(230400);  
 }  
   
 void loop()  
 {  
   loadValue(count*10);  
   output();  
   ++count;  
 }  
Here is the code on github.
And finally, here's the oscilloscope output:
Looks neat :)

1 comment :