raw arduino

Measuring electrical resistance is the process of determining the allowance of an objects current flow. Using cheap multimeters can be hard and quite inaccurate, therefore I set to build my own Arduino-based ohmmeter.

Components

Theory

The simplest way to measure resistance is to build a voltage divider where one resistor value is known and the other is the resistor we want to measure.

The output voltage which we measure with the integrated ADC behind the analog pins of our Arduino depends on the relationship of these two resistors. Since we know one resistor value and how much voltage drops across it, the value of the unknown resistor can be deduced from the relationship of the resistors:

\[\frac{V_\text{out}}{V_\text{in}} = \frac{R_2}{R_1+R_2}\]

Where \(V_\text{in}\) is the reference voltage we put into the voltage divider and \(V_\text{out}\) is the output of the voltage divider, which we measure with the Arduino.

When we read an integer \(x\in [0, 2^b-1]\) from the ADC, using \(b=10\) bit resolution, we can state the output voltage of the voltage divider as:

\[V_\text{out} = \frac{x}{2^b} V_\text{in}\]

We now have two choices, say \(R_1\) is the known resistor value and \(R_2\) the resistor we want to measure and vice versa.

\(R_1\) is the known resistor

\[ \begin{array}{rl} & \frac{V_\text{out}}{V_\text{in}} = \frac{R_2}{R_1+R_2}\\ \Leftrightarrow & V_\text{out} \cdot R_1 + V_\text{out} \cdot R_2 = V_\text{in} \cdot R_2\\ \Leftrightarrow & V_\text{out} \cdot R_1 = (V_\text{in} - V_\text{out}) \cdot R_2\\ \Leftrightarrow & R_2 = \frac{V_\text{out} \cdot R_1}{V_\text{in} - V_\text{out}}\\ \Leftrightarrow & R_2 = R_1\cdot\frac{x}{2^b - x}\\ \end{array} \]

\(R_2\) is the known resistor

\[\begin{array}{rl} & \frac{V_\text{out}}{V_\text{in}} = \frac{R_2}{R_1+R_2}\\ \Leftrightarrow & V_\text{out} \cdot (R_1 + R_2) = V_\text{in} \cdot R_2\\ \Leftrightarrow & R_1 + R_2 = \frac{V_\text{in}}{V_\text{out}}\cdot R_2\\ \Leftrightarrow & R_1 = \frac{V_\text{in}}{V_\text{out}}\cdot R_2 - R_2\\ \Leftrightarrow & R_1 = R_2\cdot\left(\frac{V_\text{in}}{V_\text{out}}-1\right)\\ \Leftrightarrow & R_1 = R_2\cdot\left(\frac{2^b}{x}-1\right)\\ \end{array}\]

So we have two solutions, depending on whether we decide to make \(R_1\) or \(R_2\) the unknown resistor we want to measure. Both cases have the advantage, that they don't rely on \(V_\text{in}\), which means that the accuracy of the measurement does not depend on the reference voltage!

Implementation

As shown in the theory part, it depends on whether \(R_1\) or \(R_2\) is kept constant but in both cases a simple voltage divider can be set up:

In the sketch, an optional capacitor with \(C=10nF\) is used to reduce dynamic loading effects and noise of the ADC input. Lets start with a helper function to pretty-print the resulting resistance:

static void printResistance(float r) {

  if (r >= 1e6) {
    Serial.print(r / 1e6);
    Serial.println("MΩ");
  } else if (r >= 1e3) {
    Serial.print(r / 1e3);
    Serial.println("kΩ");
  } else {
    Serial.print(r);
    Serial.println("Ω");
  }
}

Now with \(R_1:= 1000\) being the constant resistor, the following Arduino code is enough to measure \(R_2\):

void setup() {
  Serial.begin(9600);
}

void loop() {

  uint16_t x = analogRead(A0);

  float R1 = 1000; // Ω
  float R2 = R1 * x / (1024.0 - x);

  printResistance(R2);
  delay(500);
}

Or equivalently with \(R_2:= 1000\) being the constant resistor:

void setup() {
  Serial.begin(9600);
}

void loop() {

  uint16_t x = analogRead(A0);

  float R2 = 1000; // Ω
  float R1 = R2 * (1024.0 / x - 1);

  printResistance(R1);
  delay(500);
}

Both implementations use a hard-coded reference resistance of \(1 k\Omega\). In practice, I used a rotary switch to change the value between \(220 \Omega\), \(1 k\Omega\), \(10 k\Omega\) and \(100 k\Omega\) to keep it in a similar dimension to the resistor that is measured, otherwise the error increases dramatically.

The total resistance \(R_1 + R_2\) between the reference voltage of 5 V and GND should not be lower than \(125 \Omega\), because otherwise more than \(I = \frac{U}{R} = \frac{5 V}{125 \Omega} = 40 mA\) would flow, which exceeds the power rating of the pin.

Please note that since an ADC never works perfectly and the output \(x\) is an integer, a lot of error sources come together. To reduce the overall error, you should use a reference resistor with a low tolerance, preferably with 1% or 0.1%. Furthermore, the USB-port should be avoided as the power supply, since it is quite noisy and unstable.

If all you need is quickly testing your E-series of resistors, errors could be avoided by simply searching for the closest known resistor:

static float nearestResistor(float v) {

  const float Rs[] = {
    1.5,
    4.7,
    10,
    47,
    100,
    220,
    330,
    470,
    680,
    1000,
    2200,
    3300,
    4700,
    10000,
    22000,
    47000,
    100000,
    330000,
    1000000
  };

  float minErr = INFINITY;
  float ret = v;
  
  for (uint8_t i = 0; i < sizeof(Rs) / sizeof(float); i++) {

    float err = abs(Rs[i] - v);
  
    if (err < minErr) {
      minErr = err;
      ret = Rs[i];
    }
  }
  return ret;
}