Teensy 3.1, 3.2, LC - Increase DAC resolution from 12 bits to 16 bits

Status
Not open for further replies.

TelephoneBill

Well-known member
I wanted very fine control of an analogue voltage, so rather than using a 10 turn pot across a reference voltage (subject to the vagueries of tiny hand tweaks), I decided to use the DAC output from a Teensy 3.1 board.

The DAC in the Teensy 3.1 has a resolution of 12 bits which means I could set this with a digital value from 0 to 4095. Using a reference voltage of 3.3 volts, this means each step can increment by 3.3/4096 = 0.806 milliVolts.

But this was just not quite "fine" enough control for my application. Ideally, I would like the voltage control steps to be ten times this resolution (smaller than 0.1 millivolts). After a little study, the solution was quite simple - why not use something akin to "pulse width modulation" (PWM) to increase the DAC resolution from 12 bits to 16 bits? The PWM technique effectively uses on/off switching to get values intermediate between digital "0" and digital "1", and is how you get analogue outputs from digital pins.

Using the DAC, the quality of the analogue signal created can be even better than PWM on a digital pin. By "dithering" the value of the DAC output between two adjacent values, on a fixed and fast regular basis, then the average value of the DAC output can be set anywhere in between these values. For example, if the DAC output is set at 2123 for five passes of the main program loop, and then set to 2124 for another five passes of the main loop, then the average value will be 2123.5 overall. Or if this was changed to 2123 for one pass and then nine passes at 2124, then the average would be 2123.9 overall.

A simple RC filter would then also remove any "noise" created by the "dither" on the analogue output, and with the main loop doing in excess of 200,000 loops per second (noise becomes 200 KHz), then the capacitance value required to filter could be relatively small.

The following code shows how this can be easily implemented in a main loop...

//increment DStep each time through loop
DStep++;
if (DStep > 9) {
DStep = 0;
}

//compute current DACPulse value
if (DStep<NumDecPulses) {
DACPulse = 1;
}
else {
DACPulse = 0;
}

//update DAC output value
analogWrite(A14, DACOutput + DACPulse);

DStep is a simple integer which keeps track of where we are in the number of passes. It cycles around from "0" to "9", then back to "0" again. It does this continuously incrementing for every loop pass. NumDecPulses is the decimal fractional increment that we want to obtain. If we want 2123.5, then NumDecPulses would be set to "5". If we want 2123.9, then NumDecPulses would be set to "9". DACOutput is the integer part of the desired output, which would be "2123" in the aforesaid examples. DACPulse is either "0" or "1" and is the "dither" parameter that is added to the output (or effectively not if it is "0") depending on which part of the overall pass cycle we are currently executing.

Technically, because the output here is varying in "tenths" of integer values (2123 being the integer), then this is not quite the extra four bits to take the resolution from 12 bits to 16 bits. It is greater than three bits more but less than four (four bits would be "sixteenths" of the integer).

In theory you can extend this to even greater resolution than 16 bits. The nice feature of this method is that your DAC analogue output is guaranteed to be "finer" than what you would achieve with just the normal 12 bits, but the increments are not necessarily going to be "linear" at that resolution all the way from 0 to 4095 (min to max values). It should be fairly good though.
 
I'm not an expert in this field, but I think this is very close or even equal to what an oversampling DAC does. Digital interpolation. In a nutshell, you are trading bandwith vs. bit depth. Afair, Signal-to-noise ratio is enhanced by the square root of the oversampling factor.
Ben
 
The theory is good but I'm curious how well this works in practice? At a minimum, if you don't have the DAC dither/PWM updates driven by a hardware clock, any interrupts (eg. msec timer, USB etc) will cause variable DAC update delay, and hence a variable offset.
 
This will ususally work, but note that the DAC spec has DNL (basically error in the step) of +/- 1 LSB. This means that for a given ideal step size, the actual step could be between 0x and 2x this step. In practice you usually won't see a part that close to the spec, but expecially around the MSB transition (0x111111.... to 0x100000...) you'll have a larger than usual error. This will limit the amount of additional resolution you'll be able to get.

2nd -- instead of coding 'n' pulses on and (N-n) pulses off, you can spread them a little more uniformly (on average) by doing something like this:
n=0;
N = 10
sum=0;

for each loop:
sum += n;
if (sum>=N) {DACPulse=1; sum-=N;} else DACPulse=0;
 
This works very well in practice. With better (finer) quantisation you will be limited by effects such as noise floor and nonlinearities of the DAC (not each LSB-Step of the DAC will result in the _exact_ same increase/decrease of output voltage). The mandatory low pass filter on the output will add an frequency dependent complex output impedance, which may or may not affect your real world application, depending on what the DAC is supposed to drive.

Speaking of increased DAC resolution by sacrificing bandwidth, one could also use two PWM- or DAC-Outputs that drive both ends of a voltage divider. Let's call the generated effective voltages of these Outputs U1 and U2. With the correct resistance ratio one can have the middle of the voltage be, say, 255/256*U1+1/256*U2. In theory, this adds 8 bits of resolution to the output, provided the low pass filter is good enough. But one will run into noise issues very soon. On a compact, unshielded board like the Teensy my guess is there will be a limit somewhere around CD quality, at about 16 bits. But im just guessing, ymmv.

Ben

Edit: Posted this at the same time as Jp3141 did his post. He explains DNL better than me :)
 
Last edited:
Good comments. Any latency in loop timing would upset the applecart (and maybe bias the result), so a timer driven Interrupt Service Routine might be desirable. I like the "distributed" dither idea. And noise becomes an issue all too quickly. Differential non-linearity (DNL) is an intriguing aspect and without knowing more about the chip implementation then its anyone's guess. Also, not to forget the stability of the VRef used.

How does it work in practice? For mine it seems to deliver. I'm controlling a 10 MHz OCXO with it in a PLL, and over time (1 hour) my control voltage seems to drop in steps of 0.1 every few minutes (getting longer between changes as time passes) as the whole experiment warms up and drifts with temp. This gives me "some confidence" that the sub-integer control is being effective - well more so than integer control alone would do.

I don't have any gear sufficiently accurate (and stable) to really test the output as a discrete voltage measurement. It would be a great help if someone does and could report...
 
If you are mostly interested in long-term stability, the fast jitter from any interrupt latency wouldn't affect it, the closed loop will keep the long-term average constant. If you tune a shortwave receiver to your 10 MHz osc frequency, you can get some idea of the audio band stability, if that matters.
 
How does it work in practice? For mine it seems to deliver. I'm controlling a 10 MHz OCXO with it in a PLL, and over time (1 hour) my control voltage seems to drop in steps of 0.1 every few minutes (getting longer between changes as time passes) as the whole experiment warms up and drifts with temp. This gives me "some confidence" that the sub-integer control is being effective - well more so than integer control alone would do.
Yes that looks like it's working ok. What source ist your OCXO disciplined with? GPS?
I didn't know the application you used your DAC for when I did my first post. Now that I know, I highly recommend a dedicated DAC IC as a companion to your VCOCXO.
 
Status
Not open for further replies.
Back
Top