This is PerlMonks "Mobile"

Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

I needed to generate resistor pairings for a resistor voltage divider to generate voltages from 0 to full scale in 256 steps for use with configuration on a PCB. The following script parses a CSV file dumped out from component supplier DigiKey and from the over 500 resistor values selects 58 that can be used to generate the 254 pairings needed (255 and 0 are special cases that aren't included).

use strict; use warnings; use Text::CSV qw(csv); use List::BinarySearch; my $maxInValue = 255; # Generate 8 bit +ADC values my $minResistance = 300; my $maxResistance = 100e3; my %unitMul = (kOhms => 1.0e3, Ohms => 1.0,); my $rFileName = 'resistorSeries.csv'; my @rows = @{csv(in => $rFileName, headers => "auto")}; my @resistorValues = @rows; my %rowByValue; # Pull out resistor values for my $rowIdx (0 .. $#rows) { my $row = $rows[$rowIdx]; my ($resistance, $units) = split ' ', $row->{Resistance}; die "Can't scale by $units\n" if !exists $unitMul{$units}; $resistorValues[$rowIdx] = $resistance * $unitMul{$units}; $rowByValue{$resistorValues[$rowIdx]} //= $row; } # Make sure we only have one instance of each resistor value my %unique = map {$_ => 1} @resistorValues; @resistorValues = keys %unique; @resistorValues = grep {$_ >= $minResistance && $_ < $maxResistance} @resistorValues +; @resistorValues = sort {$a <=> $b} @resistorValues; # Target values 0 and 255 are special cases so exclude those. my @targets = map {{target => $_, pairs => []}} 1 .. $maxInValue + - 1; my %targetByValue = map {$targets[$_]->{target} => $_} 0 .. $#targets; my %usedCounts; my $worstBestErr = 0.0; # Find all the ways we can get close to each target value for my $wanted (@targets) { my $values = $wanted->{pairs} //= []; my $usedValues = $wanted->{usedValues} //= {}; # $ratio = Vin / Vout = 255 / target my $ratio = $maxInValue / $wanted->{target}; for my $r2 (@resistorValues) { my $tryR1 = ($ratio - 1) * $r2; next if $tryR1 < $resistorValues[0]; last if $tryR1 > $resistorValues[-1]; my $r1 = nearestR($tryR1); my $actValue = 255 * $r2 / ($r2 + $r1); my $err = abs($wanted->{target} - $actValue); push @$values, {r1 => $r1, r2 => $r2, err => $err, value => $a +ctValue}; $usedValues->{$r1} = $values->[-1]; $usedValues->{$r2} = $values->[-1]; } # Sort values by error @$values = sort {$a->{err} <=> $b->{err}} @$values; $worstBestErr = $values->[0]{err} if $worstBestErr < $values->[0]{ +err}; } # Count number of uses of each resistor value used for my $target (@targets) { my $values = $target->{pairs}; # Eliminate any pairings with a worse error than $worstBestErr @$values = grep {$_->{err} <= $worstBestErr} @$values; for my $value (@$values) { ++$usedCounts{$value->{r1}}; ++$usedCounts{$value->{r2}}; } } # Select resistor pairings my $valuesCount = keys %usedCounts; my %missingTargetIdx = map {$_ => 1} 0 .. $maxInValue - 2; my @rByCount = sort {$usedCounts{$b} <=> $usedCounts{$a}} keys %usedCo +unts; my %usedR; my %targetPairs; while (keys %missingTargetIdx) { if (!@rByCount) { die "Can't generate pairings for: @{[keys %missingTargetIdx]}\ +n"; } my $targetR = shift @rByCount; my @matches; # Find all target values that use the current target resistor for my $targetIdx (keys %missingTargetIdx) { my $target = $targets[$targetIdx]; my $value = $target->{target}; for my $pair (@{$target->{pairs}}) { next if $pair->{r1} != $targetR && $pair->{r2} != $targetR +; push @matches, {targetIdx => $targetIdx, r1 => $pair->{r1}, r2 => $pa +ir->{r2}}; $targetPairs{$target->{target}} = [$pair->{r1}, $pair->{r2}, $pair->{value}, $pair->{err +}]; last; } } next if !@matches; # Remove targets we've just found from %missingTargets and add bot +h # resistors in each target pair to %usedR for my $match (@matches) { ++$usedR{$match->{r1}}; ++$usedR{$match->{r2}}; delete $missingTargetIdx{$match->{targetIdx}}; } # See if there are any other targets that are satisfied by %usedR +values for my $targetIdx (keys %missingTargetIdx) { my $target = $targets[$targetIdx]; my $used = $target->{usedValues}; for my $pairKey (keys %$used) { next if !exists $usedR{$used->{$pairKey}{r1}} || !exists $usedR{$used->{$pairKey}{r2}}; $targetPairs{$target->{target}} = [ $used->{$pairKey}{r1}, $used->{$pairKey}{r2}, $used->{$pairKey}{value}, $used->{$pairKey}{err} ]; ++$usedR{$used->{$pairKey}{r1}}; ++$usedR{$used->{$pairKey}{r2}}; delete $missingTargetIdx{$targetIdx}; last; } } } # BOM report my $bomCount = keys %usedR; my @bomValues = sort {$a <=> $b} keys %usedR; print "$bomCount values used excluding 0 Ohm and 'DNF'\n"; for my $value (@bomValues) { my $row = $rowByValue{$value}; printf "%5d: %15s\n", $value, $row->{"Manufacturer Part Number"}; } # Resistor pairings report my $maxZ = 0; my $minR = 100000; for my $pair (sort {$a <=> $b} keys %targetPairs) { my $r1 = $targetPairs{$pair}[0]; my $r2 = $targetPairs{$pair}[1]; my $r1t = 1.001 * $targetPairs{$pair}[0]; my $r1b = 0.999 * $targetPairs{$pair}[0]; my $r2t = 1.001 * $targetPairs{$pair}[1]; my $r2b = 0.999 * $targetPairs{$pair}[1]; my $topValue1 = 255 * $r2t / ($r2t + $r1b); my $botValue1 = 255 * $r2b / ($r2b + $r1t); my $topValue2 = 255 * $r2b / ($r2b + $r1b); my $botValue2 = 255 * $r2t / ($r2t + $r1t); my $topValue = $topValue1 > $topValue2 ? $topValue1 : $topValue2; my $botValue = $botValue1 < $botValue2 ? $botValue1 : $botValue2; my $span = $topValue - $botValue; my $z = 1 / (1 / $r1 + 1 / $r2); my $r = $r1 + $r2; $maxZ = $z if $maxZ < $z; $minR = $r if $minR > $r; printf "%3d: R1 %5d, R2 %5d = %6.2f (err %5.3f: %6.2f - %6.2f = %4.2f +), z = %.0f\n", $pair, @{$targetPairs{$pair}}, $botValue, $topValue, $span, $z +; } printf "Max Z: %.0f, min R: %.0f\n", $maxZ, $minR; sub nearestR { my ($target) = @_; my $insertPoint = List::BinarySearch::binsearch_pos {$a <=> $b} $target, @resistorValues; if ($insertPoint > 0) { my ($prev, $next) = @resistorValues[$insertPoint - 1, $insertP +oint]; if ($target - $prev < $next - $target) { --$insertPoint; } } return $resistorValues[$insertPoint]; }

Prints:

58 values used excluding 0 Ohm and 'DNF' 301: ERJ-PB3B3010V 340: ERJ-PB3B3400V ... 90900: ERJ-PB3B9092V 95300: ERJ-PB3B9532V 1: R1 90900, R2 357 = 1.00 (err 0.002: 1.00 - 1.00 = 0.00), +z = 356 2: R1 95300, R2 750 = 1.99 (err 0.009: 1.99 - 2.00 = 0.01), +z = 744 3: R1 49900, R2 590 = 2.98 (err 0.020: 2.97 - 2.99 = 0.01), +z = 583 4: R1 80600, R2 1270 = 3.96 (err 0.044: 3.95 - 3.96 = 0.02), +z = 1250 5: R1 80600, R2 1620 = 5.02 (err 0.024: 5.01 - 5.03 = 0.02), +z = 1588 ... 253: R1 340, R2 43200 = 253.01 (err 0.009: 253.00 - 253.01 = 0.01), +z = 337 254: R1 357, R2 90900 = 254.00 (err 0.002: 254.00 - 254.00 = 0.00), +z = 356 Max Z: 36437, min R: 602

The report shows the actual values generated with ideal resistors and the range of values taking resistor tolerances into account. The z value is the effective impedance the ADC input sees. min R is the minimum series resistance of any resistor pair which relates to the maximum current through the resistors.

Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

Replies are listed 'Best First'.
Re: Calculating resistor pairs to generate a range of voltages
by afoken (Chancellor) on Feb 16, 2021 at 19:08 UTC
    I needed to generate resistor pairings for a resistor voltage divider to generate voltages from 0 to full scale in 256 steps for use with configuration on a PCB. [...] The report shows the actual values generated with ideal resistors and the range of values taking resistor tolerances into account. The z value is the effective impedance the ADC input sees. min R is the minimum series resistance of any resistor pair which relates to the maximum current through the resistors.

    Nice.

    Just a warning for future readers: Don't assume that a real-world 8 bit ADC can tell apart 256 different voltages. Even with excellent hardware, the effective resolution is less than 8 bit, and at least the LSB tends to jump around. A 10 or 12 bit ADC should work fine, especially if the resistor divider is connected to the ADC reference voltage. One or two LSB will still jump around, and you should average a few samples. Also, real-world ADCs are not perfectly linear and have an offset voltage.

    You also should consider the ADC startup. Some ADCs (e.g. the one in the Atmel SAMD21) just return garbage for the first sample after power-up or reset or switch to a different reference voltage.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

      We are actually using the 12 bit converter baked into TM4C129 series processors. Time to get a value isn't a concern so I'm using the maximum sample and hold time and have the ADC averaging 256 samples so I'm expecting pretty low noise. I'm throwing away the bottom 4 bits in any case and linearity and offset are of the order of +/-3 LSB so we should be good.

      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: Calculating resistor pairs to generate a range of voltages
by jdporter (Paladin) on Feb 16, 2021 at 14:29 UTC

    Cool. What about resistorSeries.csv? Can you post it? Or is there a link to a source for it, you could share? I didn't find an obvious candidate on DigiKey.com. TIA!

    One thing I find interesting about the results is that the table isn't symmetrical. Yes, the 1 and 254 results are symmetrical (same pair of values but in reverse order), but the 2 and 253 are not, and in general I guess they won't be.

      There is a "Download Table" button just above the table listing the components. I set "Results per Page" to 500 and downloaded two .csv files that I concatenated together (removing the header row on the second file). The result was a 500kB file.

      The algorithm generates 10 - 20 pairings for each target value which are then sorted by difference from desired value. Pairs with an error greater than a calculated threshold are eliminated. All the used resistor values are then counted and a selection process starts with the most frequently used resistor and matches all target values that has a pair using that resistor. It's entirely possible for different pairs to be found for each of a symmetrical pair of target values.

      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      What about resistorSeries.csv? Can you post it? Or is there a link to a source for it, you could share? I didn't find an obvious candidate on DigiKey.com.

      Probably just E series of preferred numbers, at 256 different voltages probably E24 or higher.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        Actually 0.1% resistors which cost about 3 cents each in x1000 quantities. Even one off they are only about 20 cents so the cost of precision resistors isn't really a barrier.

        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond