I needed a crude voltage reference and started connecting a few resistors to microcontroller pins. This is the simplest form of a DAC, a resistor network connected to controllable outputs. But how many levels can you create? If the pins output a binary code the upper limit would be 2^n, but microcontroller pins also have the option of a high-impedance 'input' state, increasing this to 3^n combinations.
This is a problem which has been solved many times before, there are standard solutions, such as resistor ladders (n-ary ladders, ternary and quaternary). But these often require particular resistor ratios which don't match E12 resistors, and there are problems with component tolerance. To achieve high accuracy with 1% tolerance components you need to calibrate the output and use a lookup table (not a problem with microcontroller RAM).
But what is the best result with standard 1% E12 series resistors and the smallest number of processor pins. It turns out to be a fairly computationally intensive problem because the ±1% variation in components causes ordering of output levels to swap.
The following network comes out as one of the best, achieving near 8-bits of precision using 6 microcontroller pins, 13-resistors and 1 op-amp.
The performance should be: (Based on the worst of about 100 samples in the search space of resistor tolerances)
Output: 250-levels (~8-bit)
Peak INL < 0.5 LSB
RMS INL ~ 0.15 LSB
AC Performance: Poor.
The pins must be driven using 3-state microcontroller pins ('0','1','Z') to achieve the target number of outputs, giving 729 possible drive combinations. But not all of these patterns are actually used, only the closest to each of the 256 target levels is necessary. Due to the variation in components it isn't possible to predict which code should map to which output pattern in advance, this has to be determined for the part after assembly using an ADC. The pseudo code is roughly:
for each output pattern p (729)
Set output pins (p)
output_level[p] := filtered ADC
Sort(output_level)
lowest := min(output_level)
range := max(output_level)-lowest
for each target code c (256)
ideal := lowest + (c/(256-1))*range
lookup_table[c] := Find nearest(output_level, ideal).p
This is a problem which has been solved many times before, there are standard solutions, such as resistor ladders (n-ary ladders, ternary and quaternary). But these often require particular resistor ratios which don't match E12 resistors, and there are problems with component tolerance. To achieve high accuracy with 1% tolerance components you need to calibrate the output and use a lookup table (not a problem with microcontroller RAM).
But what is the best result with standard 1% E12 series resistors and the smallest number of processor pins. It turns out to be a fairly computationally intensive problem because the ±1% variation in components causes ordering of output levels to swap.
The following network comes out as one of the best, achieving near 8-bits of precision using 6 microcontroller pins, 13-resistors and 1 op-amp.
The performance should be: (Based on the worst of about 100 samples in the search space of resistor tolerances)
Output: 250-levels (~8-bit)
Peak INL < 0.5 LSB
RMS INL ~ 0.15 LSB
AC Performance: Poor.
Real world results
A quick breadboard prototype with a 5.6k resistor in the output stage (to prevent saturation near the rails) gets 0.44 LSB INL for 8-bit output.
Drive and Calibration
The pins must be driven using 3-state microcontroller pins ('0','1','Z') to achieve the target number of outputs, giving 729 possible drive combinations. But not all of these patterns are actually used, only the closest to each of the 256 target levels is necessary. Due to the variation in components it isn't possible to predict which code should map to which output pattern in advance, this has to be determined for the part after assembly using an ADC. The pseudo code is roughly:
for each output pattern p (729)
Set output pins (p)
output_level[p] := filtered ADC
Sort(output_level)
lowest := min(output_level)
range := max(output_level)-lowest
for each target code c (256)
ideal := lowest + (c/(256-1))*range
lookup_table[c] := Find nearest(output_level, ideal).p
No comments:
Post a Comment