5V to 3.3V with Preferred Resistors

What do you do when you want to scale a 5V analog sig­nal to 3.3V? You build a resis­tor divider, pos­si­bly with an op amp-based volt­age fol­low­er to dri­ve low imped­ance loads.

But what resis­tors do you buy for your resis­tor bridge? You want resis­tance val­ues of some mul­ti­ple of 1.7 to 3.3, but resis­tors come in pre­ferred val­ues, which are math­emag­i­cat­i­cal­ly spaced out resis­tance scales.

None of the “nice” E24 val­ues that are mul­ti­ples of 170 or 330 real­ly work (330 exists, but not 170; there’s a 510 but no 990; and 680 but no 1320).

There’s got­ta be some kind of ide­al com­bi­na­tion of pre­ferred-val­ue resis­tors that give me either the per­fect 5V to 3.3V divider, or at least min­i­mizes the error. Well, I wasn’t about to do all 24² com­bi­na­tions of upper/lower resis­tors by hand and for some rea­son Google couldn’t find it for me, so I wrote a script C++0x pro­gram to uh… do it for me1.

So what are the results for each EIA resis­tor val­ue 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 per­cent­ages are for volt­age drop over the top half of the resis­tor divider, not for the result­ing volt­ages. For exam­ple, the 180 & 330 com­bo would have an error of \left(\frac{330}{180 + 330} \div \frac{3.3}{5} - 1\right) \times 100\% = -1.96\%. Not bad for parts you can get at RadioShack.

It turns E192 does have a per­fect com­bo, but it’s not like E192-series resis­tors are avail­able from Digi-Key. How­ev­er, you can get E96 resis­tors to build a divider with only -0.0042% error, which will have so much error due to the bimodal dis­tri­b­u­tion of binned resis­tor val­ues that the time you’ve spent on mak­ing it “cor­rect” would be com­plete­ly wast­ed.

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;
}
  1. Actu­al­ly, the code runs in O(N * log N) time rather than O(N²) time: given some high-side resis­tance, I run a bina­ry search on which low-side resis­tor val­ue gives the best divider. I loop over all high-side resis­tances. []