Sunday 19 April 2015

A 9-bit microcontroller DAC using a ternary resistor-ladder.



This DAC project uses the features of microcontroller I/O pins to output 512 Voltage levels (9-bit) using only 6 digital pins. It also uses calibration to achieve good linearity despite the use of 1% tolerance components in the design. (It's not going to beat a purchased DAC).

The digital I/O pins on a microcontroller generally have at least three well defined states: Output '1', Output '0' and Input (high impedance or 'Z'). (The fourth, which activates an internal pull-up-resistor, is less well defined). Using all three states the number of possible output combinations grows as 3n for n-pins, up from 2n for a binary output. For 6-pins this yields 729 possible output combinations, instead of 64 using only 2-valued logic. By using a suitable biasing circuit the three pin states can be translated to different voltage levels, which is in effect a ternary logic value. A ternary/trinary resistor-ladder makes use of these ternary values to produce a voltage output proportional to the input code (ie. a DAC).

I described some of the equations for resistor based DACs in a previous post:
Low-cost n-ary DACs using digital I/O pins.
And 'raronoff' produced a similar DAC in 2011:
The three state trinary/ ternary resistor ladder DAC.

This DAC is an example for 6-inputs with minor improvements, for the limitations skip to the end...

Improvements:
Larger biasing resistors (4.7k) reduce variation in the 'on' voltage.
A resistor ratio slightly below 4:3 ensures the output ranges for a MSB transition overlap.
Calibration and look-up table to achieve low Integral Non-Linearity (INL).

Performance:
Input pins: 6
Output resolution: 9-bits. (512-levels)
Calibrated INL < 0.5LSB (<0.44LSB measured)
Glitches ~10LSB, <100ns


Circuit Diagram


Each pin from the controller is connected to a potential divider, in this case a pair of 4.7k resistors, which hold the pin at Vs/2 when it is configured as a high impedance input. The resistors are suitably weak that the normal pin drive is unaffected, resulting in 3 possible output levels. These three-state voltage outputs are then connected to a ternary resistor-ladder DAC to produce the correctly weighted result.

Logic Pin mode V(pin)
'2' output '1' 5
'1' input (z) 2.5
'0' output '0' 0

I covered the theory for the ternary resistor-ladder in a previous post. In short the resistor ratio required is R:0.75R. This ratio is matched exactly if you have 75k and 100k resistors available. Note the resistors used on the most significant bits do not match this ideal ratio, and result in discontinuities in the output ramp. This mismatch is intentional as it allows calibration of the DAC by overlapping the ranges above and below an MSB transition, which prevents large gaps in the possible outputs.

Note: There must not be any other connections such as pull-up resistors on the board for the pins used, otherwise these will shift the 'Z' output level. On the Arduino Uno digital pins 0,1 and 13 are not suitable.

Output Buffer

It is essential to buffer the output of the DAC with a voltage follower (not shown in the diagrams). The DAC output has a variable impedance from the resistor network, and will not be linear if connected to any load! I used an MCP6292 which has a good rail-to-rail output from 2.4V to 6V.

Voltage-follower output-buffer


Breadboard prototype for the DAC.


DC Performance

On it's own this DAC will have poor performance, it's trying to achieve a 0.2% FSR output resolution with 1% tolerance resistors. The solution is to calibrate the DAC and use a lookup table to select the correct output code for each value.

To calibrate the DAC using an ADC the readings must have a low level of noise. For the example circuit I measured about 3.5mV RMS noise for both the prototype and the final board. This is very close to the size of the LSB (9-bit is approximately 10mV spacing), and so to get a stable reading an average of 256 successive samples was used. The averaged figure was stable to a higher accuracy than the 10-bit ADC.

The DAC is designed so that if the resistors have a tolerance of 1% the DAC should always be able to meet: maximum Integral Non-Linearity (INL) < 0.5 LSB at 9-bits. The INL is the measure of how much each output level differs from the ideal.

For the measured circuit he maximum difference between the ideal output value for a 9-bit DAC and the nearest existing code is approximately 4.5mV, or 0.44LSB.
Max INL = 0.44LSB   (9-bit)


INL before and after calibration.


Control Code

The DAC is more sensitive to glitches than binary designs because the transitions do not have a symmetric drive strength. It is also impossible to change both the direction and output-level of multiple pins simultaneously on some microcontrollers.

The ATMega328P used on the Arduino Uno cannot transition between input (Z) and output '1' without passing through an intermediate step, as noted in the datasheet. There are two possible intermediate states for the pin, output '0' and pull-up enabled. The pull-up state is preferred because this has a far weaker drive strength (approx 40k), and will produce a smaller glitch on the output. (It's also possible to disable pull-ups for the entire chip using the PUD control). The following update code compiles to 8 instructions not including RET.


void DACWriteTrinary(char ddrBits, char portBits)
{
     // implicit port D, no read-modify-write
     
     // optional SW glitch reduction for Z->'1'
     char zto1 = (~DDRD) & portBits;
     char viaPU = PORTD | zto1;
     
     // update registers
     __asm__("  OUT 0x0B, %2\n\t"
             "  OUT 0x0A, %0\n\t"
             "  OUT 0x0B, %1\n\t"    
     :
     : "r" (ddrBits), "r" (portBits), "r" (viaPU)
     :
   );
}


Noise Levels

The calibration routine used 180,000 samples, which gives some idea of the total system noise. Using the same board to measure the result hides variation in the USB power supply which drives both the output and the analogue reference on the ADC. (Most of the noise is probably picked up on the output line)

Noise Case RMS/mV (/LSB) Peak/mV (/LSB)
Unbuffered 2 (0.4) 12.5 (2.5)
Buffered 1.5 (0.3) 15 (3)


AC Performance

It's not stellar, but it might have some use at audio frequencies.

Ramps: 1.6µs/code to 6.8µs/code.

5kHz sine wave, approx 250kSPS.

Limitations

The 'voltage reference' for the DAC is poorly defined. It sees both the 5V supply for the driving chip, and also the internal supply voltage of the chip subject to any drop over it's input pins. Either or both of these could have significant noise, except in the case shown where the microcontroller is dedicated to driving the DAC and has no other activity.

3 comments:

  1. I'm writing an EDN article about base-3 DACs and will reference your page. Do you mind if I use 1 or 2 of your images? thx

    ReplyDelete
    Replies
    1. If it's still relevant feel free to use any resources.

      Delete
    2. Thanks
      https://www.edn.com/electronics-blogs/benchtalk/4458904/Ternary-DAC--Greater-resolution--less-bits

      Delete