Of course, it’s too late to change microcontrollers, and the budget won’t support using an external DAC. Fortunately, 10-bit performance can be achieved with an 8-bit DAC by dithering the DAC between two values and filtering the output to remove any ripple.
DACs generate a particular ratio of their reference. Resolution (Δ or LSB) is the range (the total possible span of the output) divided by the total number of possible steps. For example, an 8-bit DAC with a 5-V reference has a range of 5 V across 256 (28) possible steps with a resolution of 19.53 mV (5 V/256). If this particular DAC is set to 63, its output will be 1.23 V (5 V * 63/256).
To increase resolution, it’s necessary to generate multiple values for each step. Achieving 10-bit performance with an 8-bit DAC requires 2(10-8) or four values for each step (see the table). The value to be outputted is stored as two bytes, n and the dither value (Fig. 1). The dithered value determines how often the DAC value is increased by one. The actual DAC output value can be computed in an interrupt routine, running at an appropriate update rate, using the following algorithm:
AccumValue += DitherValue
Temp = DACValue + carry
DAC_Reg = Temp
DITHERS AND RIPPLES
Suppose that the dither value is set to 0x80. Adding the dither value to the accumulator results in a carry every other step. It also causes the value sent to the DAC to be bumped up a count every other step. Likewise, setting the dither value to 0x40 causes the carry to be generated every fourth step, and setting the dither value to 0xC0 causes a carry to be generated three out of four steps. Repeatedly dithering the DAC value in this way generates the desired quarter steps in the DAC output.
Unfortunately, dithering introduces a peak-to-peak ripple of Δ, which will need to be filtered to average out the signal. For a 10-bit system (Δ10bit) using an 8-bit DAC (Δ8bit), I have found an acceptable amount of ripple to be ±0.25Δ10bit or ±0.0625Δ. A specific application with a tighter ripple requirement would require more filtering. Conversely, a looser ripple requirement would require less filtering.
Suppose you want to generate a DAC output of n + 0.25 at a rate of 100 kHz. The output will be the higher value (n + 1) one time followed by three normal values (n). This generates a pulse with a 25% duty cycle and frequency of one-quarter the update rate or 25 kHz. The resulting ripple is +0.75Δ and –0.24Δ.
The ripple can be reduced to an acceptable level by filtering the output with a two-pole low-pass filter with a cutoff frequency that is one-twelfth of the update rate. If less ripple is desired, the filter’s bandwidth can be reduced. If more ripple can be tolerated, the bandwidth can be increased. Setting the bandwidth to one-twelfth the update rate for an acceptable amount of ripple is a good rule of thumb (Fig. 2).
Generating a half step results in a first harmonic greater in amplitude than for a quarter step. One might suppose this should make the signal harder to filter. But fortunately, the output frequency has doubled to half the update rate or 50 kHz. The same filter easily removes enough of the ripple (Fig. 3). Known as deltasigma modulation, this type of dithering is guaranteed to make the frequency of the dithered output be as high as possible.
Take an 8-kHz low-pass filter, for example, set below the required 8.3 kHz to allow for component tolerance (Fig. 4). It should be built with 1% resistors and 5% ceramic NPO capacitors. While the op amp should have a gain bandwidth of at least 10 MHz, it can be as low as 1 MHz and still produce acceptable results. This filter can be easily scaled to your particular application. If your update rate is only 10 ksamples/s, just increase the resistor values by a factor of 10.