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.
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.