5V to 3.3V with Preferred Resistors
What do you do when you want to scale a 5V analog signal to 3.3V? You build a resistor divider, possibly with an op amp-based voltage follower to drive low impedance loads.
But what resistors do you buy for your resistor bridge? You want resistance values of some multiple of 1.7 to 3.3, but resistors come in preferred values, which are mathemagicatically spaced out resistance scales.
None of the “nice” E24 values that are multiples of 170 or 330 really work (330 exists, but not 170; there’s a 510 but no 990; and 680 but no 1320).
There’s gotta be some kind of ideal combination of preferred-value resistors that give me either the perfect 5V to 3.3V divider, or at least minimizes the error. Well, I wasn’t about to do all 24² combinations of upper/lower resistors by hand and for some reason Google couldn’t find it for me, so I wrote a script C++0x program to uh… do it for me1.
So what are the results for each EIA resistor value series?
E12 best: r1 = 180, r2 = 330 (-5.556%) E24 best: r1 = 470, r2 = 910 (-0.258%) E48 best: r1 = 825, r2 = 1600 (-0.092%) E96 best: r1 = 491, r2 = 953 (-0.012%) E192 best: r1 = 102, r2 = 198 (+0.000%)
Those error percentages are for voltage drop over the top half of the resistor divider, not for the resulting voltages. For example, the 180 & 330 combo would have an error of . Not bad for parts you can get at RadioShack.
It turns E192 does have a perfect combo, but it’s not like E192-series resistors are available from Digi-Key. However, you can get E96 resistors to build a divider with only ‑0.0042% error, which will have so much error due to the bimodal distribution of binned resistor values that the time you’ve spent on making it “correct” would be completely wasted.
Damn.
Appendix: ‘Dat code
/* * 2011 Xo Wang * I hereby release this into the public domain, something about merchantability, blah blah. If you use it, it'd be nice to let me know. * * You'd be an idiot to use this; it does many things very wrong for the sake of expedient solution-getting. */ #include <algorithm> // <3 lower_bound, <3 set_union #include <iostream> #include <limits> #include <string> #include <vector> #include <cstdlib> static double error(const double r1, const double r2) { return (r2 * 1.7) / (r1 * 3.3) - 1.0; } static void process_series(const std::string &name, const std::vector<int> series) { using namespace std; int min_error_r1 = 0; int min_error_r2 = 0; double min_error = numeric_limits<double>::max(); for_each(series.begin(), series.end(), [&](const int r1) { // find nearest resistance const double r2_wanted = r1 * (3.3 / 1.7); // r_wanted is too large, so scale it down by ten when searching const bool r2_wanted_10x = r2_wanted > series.back(); const auto r2_higher_iter = lower_bound(series.begin(), series.end(), r2_wanted_10x ? r2_wanted * 0.1 : r2_wanted); // nearest two resistance values const int r2_higher = r2_wanted_10x ? *r2_higher_iter * 10 : *r2_higher_iter; const int r2_lower = [=]() -> int { if (r2_higher == series[0] * 10) { return series.back(); } return r2_wanted_10x ? *(r2_higher_iter - 1) * 10 : *(r2_higher_iter - 1); }(); if (fabs(min_error) > fabs(error(r1, r2_lower))) { min_error = error(r1, r2_lower); min_error_r1 = r1; min_error_r2 = r2_lower; } if (fabs(min_error) > fabs(error(r1, r2_higher))) { min_error = error(r1, r2_higher); min_error_r1 = r1; min_error_r2 = r2_higher; } //cout << " r1 = " << r1 << ", r2 = { " << // r2_lower << " (" << error(r1, r2_lower) * 100 << "%), " << // r2_higher << " (+" << error(r1, r2_higher) * 100 << "%) }\n"; }); cout << name << " best: r1 = " << min_error_r1 << ", r2 = " << min_error_r2 << " (" << (min_error > 0 ? "+" : "") << min_error * 100 << "%)\n"; } static std::vector<int> merge_series(const std::vector<int> &s1, const std::vector<int> &s2) { using namespace std; vector<int> out_series(s1.size() + s2.size()); auto last = set_union(s1.begin(), s1.end(), s2.begin(), s2.end(), out_series.begin()); out_series.resize(distance(out_series.begin(), last)); return out_series; } int main() { using namespace std; const vector<int> E12 = { 100, 120, 150, 180, 220, 270, 330, 390, 470, 560, 680, 820 }; const vector<int> E24 = { 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910 }; const vector<int> E48 = { 100, 105, 110, 115, 121, 127, 133, 140, 147, 154, 162, 169, 178, 187, 196, 205, 215, 226, 237, 249, 261, 274, 287, 301, 316, 332, 348, 365, 383, 402, 422, 442, 464, 487, 511, 536, 562, 590, 619, 649, 681, 715, 750, 787, 825, 866, 909, 953 }; const vector<int> E96 = { 100, 102, 105, 107, 110, 113, 115, 118, 121, 124, 127, 130, 133, 137, 140, 143, 147, 150, 154, 158, 162, 165, 169, 174, 178, 182, 187, 191, 196, 200, 205, 210, 215, 221, 226, 232, 237, 243, 249, 255, 261, 267, 274, 280, 287, 294, 301, 309, 316, 324, 332, 340, 348, 357, 365, 374, 383, 392, 402, 412, 422, 432, 442, 453, 464, 475, 487, 491, 511, 523, 536, 549, 562, 576, 590, 604, 619, 634, 649, 665, 681, 698, 715, 732, 750, 768, 787, 806, 825, 845, 866, 887, 909, 931, 959, 976 }; const vector<int> E192 = { 100, 101, 102, 104, 105, 106, 107, 109, 110, 111, 113, 114, 115, 117, 118, 120, 121, 123, 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 142, 143, 145, 147, 149, 150, 152, 154, 156, 158, 160, 162, 164, 165, 167, 169, 172, 174, 176, 178, 180, 182, 184, 187, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 221, 223, 226, 229, 232, 234, 237, 240, 243, 246, 249, 252, 255, 258, 261, 264, 267, 271, 274, 277, 280, 284, 287, 291, 294, 298, 301, 305, 309, 312, 316, 320, 324, 328, 332, 336, 340, 344, 348, 352, 357, 361, 365, 370, 374, 379, 383, 388, 392, 397, 402, 407, 412, 417, 422, 427, 432, 437, 442, 448, 453, 459, 464, 470, 475, 481, 487, 493, 499, 505, 511, 517, 523, 530, 536, 542, 549, 556, 562, 569, 576, 583, 590, 597, 604, 612, 619, 626, 634, 642, 649, 657, 665, 673, 681, 690, 698, 706, 715, 723, 732, 741, 750, 759, 768, 777, 787, 796, 806, 816, 825, 835, 845, 856, 866, 876, 887, 898, 909, 920, 931, 942, 953, 965, 976, 988 }; cout.setf(ios::fixed, ios::floatfield); cout.precision(3); process_series("E12", E12); vector<int> series = merge_series(E12, E24); process_series("E24", series); series = merge_series(series, E48); process_series("E48", series); series = merge_series(series, E96); process_series("E96", series); series = merge_series(series, E192); process_series("E192", series); return EXIT_SUCCESS; }
- Actually, the code runs in O(N * log N) time rather than O(N²) time: given some high-side resistance, I run a binary search on which low-side resistor value gives the best divider. I loop over all high-side resistances. [▲]