gwordal

Lesson 5 of 6 · 20 min

Analog input and voltage dividers

The real world is not made of ones and zeros. Light, temperature, the angle of a knob: all of them vary smoothly. A digital pin can only tell you "above about 2.5 V or not", so to measure anything in between your microcontroller needs an analog-to-digital converter (ADC), and you need a trick, the voltage divider, to turn almost any sensor into a voltage the ADC can read.

What the ADC does

The ATmega328P on an Uno has a 10-bit ADC. Ten bits means 2^10 = 1024 possible results, numbered 0 to 1023. The converter compares the voltage on the pin with a reference voltage (5 V by default) and answers with how many "steps" fit in between.

The size of one step is:

5 V / 1024 = 0.00488 V = 4.88 mV

So 0 V reads as 0, 2.5 V reads as 512, and 5 V reads as 1023. The conversion back to volts is one line of arithmetic:

const float VREF = 5.0;            // ADC reference voltage on an Uno

void setup() {
  Serial.begin(9600);              // open the serial link to the PC
}

void loop() {
  int raw = analogRead(A0);                 // 0..1023
  float volts = raw * (VREF / 1024.0);      // each step is 4.88 mV
  Serial.print("raw = ");
  Serial.print(raw);
  Serial.print("   volts = ");
  Serial.println(volts, 3);                 // 3 decimal places
  delay(200);
}

Open the Serial Monitor at 9600 baud and you will see the numbers scroll. Serial.print is your oscilloscope-for-beginners: whenever a program misbehaves, print the values it is working with before you guess.

The voltage divider

The ADC measures voltage, but a light sensor or a thermistor changes its resistance. A voltage divider converts one into the other: two resistors in series between Vin and ground, and you read the voltage at the junction.

The same current flows through both resistors, so by Ohm's law:

I = Vin / (R1 + R2)

The output is the voltage dropped across R2 only:

Vout = I * R2 = Vin * R2 / (R1 + R2)

Worked example. Vin = 5 V, R1 = 10 kΩ, R2 = 5 kΩ:

Vout = 5 V * 5000 / (10000 + 5000) = 1.667 V

The ADC will report about 1.667 V / 4.88 mV = 341. Try your own values below: when R1 equals R2 you always get exactly half of Vin.

5VR1R22.50V
Vout = Vin × R2 / (R1 + R2) = 2.500 V

Reading a potentiometer

A potentiometer is a divider you can turn. It has a resistive track (commonly 10 kΩ) with two ends and a sliding contact, the wiper. Connect one end to 5 V, the other to GND and the wiper to A0. Turning the knob moves the wiper along the track, changing R1 and R2 while their sum stays 10 kΩ, so the wiper voltage sweeps smoothly from 0 V to 5 V.

Drag to orbit

Blue pins: analog inputs. Orange pins: digital I/O.

The analog pins A0 to A5 sit on the lower header. Wire the potentiometer wiper to A0.

Sensors through a divider

A light-dependent resistor (LDR) might measure 1 kΩ in bright light and 100 kΩ in the dark. Put it on the 5 V side with a fixed 10 kΩ resistor to ground, and read the junction:

LightR_ldrVout = 5 * 10k / (R_ldr + 10k)analogRead
Bright1 kΩ4.55 Vabout 932
Dark100 kΩ0.45 Vabout 93

More light means lower LDR resistance, so a higher voltage. A thermistor works the same way: a 10 kΩ NTC thermistor equals 10 kΩ at 25 degrees C, so with a 10 kΩ partner the reading is about 512 at room temperature and moves as the temperature changes. Choose the fixed resistor close to the sensor's resistance in the middle of the range you care about, and the output will swing over most of the ADC scale.

Turning raw numbers into useful ones

Raw ADC counts are rarely what you want. map() rescales a value from one range to another, and constrain() clamps it:

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

void loop() {
  int raw = analogRead(A0);                  // 0..1023
  int percent = map(raw, 0, 1023, 0, 100);   // rescale to 0..100
  percent = constrain(percent, 0, 100);      // never leave 0..100
  Serial.println(percent);
  delay(100);
}

map() uses integer arithmetic, so the fractional part is thrown away: a raw reading of 512 gives 50, not 50.05. It does not clamp by itself, which is why constrain() is paired with it, especially when you map a sensor's measured range (say 93 to 932 for the LDR above) rather than the full 0 to 1023.

Noise and averaging

Real readings jitter. Power-supply ripple, long wires and the converter itself add a few counts of noise: a steady 2.5 V might read 510, 513, 511, 514. Averaging N samples shrinks random noise by roughly the square root of N, so 16 samples cut it to a quarter:

int readAveraged(int pin) {
  long sum = 0;                    // long: 16 * 1023 would overflow int on an Uno
  for (int i = 0; i < 16; i++) {
    sum += analogRead(pin);
  }
  return sum / 16;
}

Check yourself

With the default 5 V reference, which analogRead value corresponds to about 2.5 V?

Check yourself

In a divider, Vin is 5 V, R1 is 10 kΩ and R2 is 5 kΩ. What is Vout, measured across R2?