Simulate analogRead: Read a Potentiometer, No Hardware

๐Ÿ“…
โœ๏ธ By CircuPilot Team
โ† Back to Blog Hub

analogRead is one line of code and almost none of what it does is obvious from reading it. It is also the first thing that breaks when a project moves from a tutorial to a real sensor, usually for a reason that has nothing to do with the sensor.

This is a short tour of what that line measures, and how to watch it happen without wiring anything.

What the number means

analogRead(A0) runs an analog-to-digital conversion and returns an integer from 0 to 1023. It is not a voltage. It is a ratio, scaled to ten bits:

reading = 1023 ร— (V_pin / V_reference)

Two consequences follow immediately, and both cause bugs.

It measures against a reference, not against volts. On a 5 V board the reference is 5 V by default, so 1023 means 5 V. Run the same board at 3.3 V and 1023 still means "the reference" โ€” now 3.3 V. The code is unchanged and every number it produces means something different.

It is a division, so resolution is fixed. One count is the reference divided by 1023 โ€” about 4.9 mV on a 5 V reference. No amount of averaging gets you below one count of true resolution, though averaging does help with noise.

To get volts, do the division explicitly:

float volts = analogRead(A0) * 5.0 / 1023.0;

The 1023 is worth a moment. There are 1024 possible codes, 0 through 1023, so it is tempting to divide by 1024. Divide by 1023 if you want the top code to read exactly the reference; divide by 1024 if you want each code to represent an equal-width bin. The difference is one part in a thousand and nobody will notice โ€” but pick one on purpose rather than by accident.

The circuit: a potentiometer is a divider you can turn

A potentiometer is a resistive track with a wiper sliding along it. Connect the two ends to 5 V and ground and it becomes a voltage divider whose ratio you control by hand. The wiper goes to the analog pin.

That is why a potentiometer is the standard first analog input: it sweeps the full range cleanly, and the maths is the divider formula you already know. At the midpoint the wiper sees half the supply, and analogRead returns roughly 511.

The resistance value barely matters for the reading โ€” the ratio is what sets the voltage, and a 1 kฮฉ and a 100 kฮฉ pot at the midpoint both give half the supply. It matters for two other things: current wasted through the track, and how much the input loads the divider. 10 kฮฉ is the usual compromise.

Watching it work

Four LEDs turn analogRead into something you can see across the room. This lights more of them as the wiper rises โ€” the bar-graph exercise:

const int LED_PINS[] = {2, 3, 4, 5};

void setup() {
  for (int i = 0; i < 4; i++) pinMode(LED_PINS[i], OUTPUT);
}

void loop() {
  int raw = analogRead(A0);
  int lit = map(raw, 0, 1023, 0, 4);
  for (int i = 0; i < 4; i++)
    digitalWrite(LED_PINS[i], i < lit ? HIGH : LOW);
}

map rescales one range onto another. Here it turns 0โ€“1023 into 0โ€“4, so a quarter turn is one LED. It does integer arithmetic and truncates, which is exactly what is wanted for counting LEDs and exactly what is not wanted if you feed the result into a calculation.

In a simulator you drag the potentiometer's control and the LEDs follow immediately, which makes the mapping visible in a way that a serial printout does not. If you want to check the numbers instead of the pattern, the same wiring with a Serial.println(raw) works.

Where analogRead actually goes wrong

Almost every failure is one of these five.

The pin is not the pin you meant. On an Arduino Uno only A0โ€“A5 reach the ADC; digital pins have no converter behind them. The trap is that analogRead takes a bare number as an analog channel, so analogRead(2) reads A2 โ€” not digital pin 2. Write analogRead(A2) and the ambiguity disappears.

Nothing is connected. A floating analog input is a high-impedance node with no defined voltage. It will return a number, that number will drift, and it will sometimes follow your hand. If a reading wanders with nothing attached, that is the explanation, not a broken board.

The source cannot drive the input. The ADC samples onto a small internal capacitor, and the datasheet asks for a source impedance of roughly 10 kฮฉ or less. Feed it from a 1 Mฮฉ divider and the capacitor does not finish charging within the sample window, so the reading comes back low and depends on what you read previously. This is the same effect as a voltage divider collapsing under load โ€” the ADC is the load.

The reference is not what the code assumes. If analogReference() has been called, or AREF is wired to something, the scale factor in your conversion is wrong even though the reading is right.

It is slower than it looks. A conversion takes on the order of 100 ยตs. Ten analog reads in a loop is a millisecond gone, which is enough to disturb timing-sensitive code and far too slow to sample audio.

The same thing at register level

If you are working with a bare AVR rather than an Arduino board, there is no analogRead โ€” you drive the converter yourself:

ADMUX = (1 << REFS0) | channel;      // reference and channel
ADCSRA |= (1 << ADSC);               // start
while (ADCSRA & (1 << ADSC)) { }     // wait for the bit to clear
uint16_t raw = ADC;                  // 0..1023

Same converter, same 0โ€“1023, no library. ADMUX chooses the channel and the reference, ADCSRA starts the conversion and reports when it is done. Worth writing once even if you use the library afterwards, because it makes the reference and the prescaler visible instead of implicit.

The ATmega16 walkthrough covers the register version in full, including the AREF and AGND pins that the Arduino form hides.

Try it

Open the simulator, place a microcontroller and a potentiometer, wire the wiper to A0 and the ends to 5 V and ground, add four LEDs with resistors on pins 2 to 5, and paste the sketch above. Press Run and turn the potentiometer.

You can also skip the wiring and describe the circuit โ€” "an Arduino Uno with a potentiometer on A0 and four LEDs on pins 2 to 5" โ€” and get the schematic built for you.