How Arduino Turns Sensor Voltage into a Number: ADC Basics for Beginners
Series navigation: Episode 1: The Roadmap · Episode 2: Digital Multimeter and LED Circuit · Episode 3: Voltage Divider Under Load · Episode 4: What an Oscilloscope Shows · Episode 5
What you will learn: How a changing voltage becomes an integer inside an Arduino sketch.
What problem it solves: Sensor readings often move even when the physical world appears still.
What you will be able to do: Wire a potentiometer to A0, interpret a raw ADC code, estimate its input voltage, and decide whether smoothing is appropriate.

AI-generated instructional image of a typical low-voltage setup. It is not a photograph of measurements performed for this article.
Reader guide
| Indicator | Details |
|---|---|
| Article type | Concept guide with an optional low-voltage exercise |
| Reading time | About 10 minutes |
| Difficulty | 2/5 — Beginner |
| Hands-on time | Optional; about 15 minutes |
| Parts | Arduino Uno R3 example, 10 kΩ potentiometer, breadboard, jumper wires |
| Prerequisite | Basic voltage and ground concepts |
| Safety level | USB-powered, extra-low-voltage circuits only |
| Reader outcome | Explain a raw ADC reading without treating it as a perfect measurement |
The sensor value would not stay still
I wanted an Arduino or Raspberry Pi project to react to the physical world. The plan sounded simple: connect a light, temperature, or position sensor, read its value, and make a decision in software.
Then the serial monitor showed something like this:
511
513
510
512
514
Nothing appeared to be moving. My first suspicion was a software bug.
That reaction makes sense if I imagine a sensor as a peripheral that sends a clean number. Some sensors do communicate digitally, but many simple sensors do something more physical first: they change a voltage, current, resistance, or capacitance. A microcontroller must convert that electrical quantity into data.
For a voltage-output sensor, that bridge is the analog-to-digital converter, or ADC.
The changing last digit is not automatically an error. It may reflect real electrical noise, reference-voltage movement, wiring, sensor behaviour, or the unavoidable rounding that occurs when a continuous voltage is divided into a finite number of codes.
What an ADC actually does
An ADC compares an input voltage with a reference range and assigns it one digital code.
In this article I use the classic Arduino Uno R3 as a concrete example. Its ATmega328P provides a 10-bit ADC and the board exposes six analog inputs. With the usual 10-bit Arduino reading, the result has 1,024 possible codes:
0, 1, 2, ... 1022, 1023
If the reference is 5.00 V, an ideal conversion is approximately:
ADC code ≈ input voltage / reference voltage × 1023
input voltage ≈ ADC code × reference voltage / 1023
One code step is then approximately:
5.00 V / 1024 ≈ 4.88 mV per step
The formulas are useful models, not calibration certificates. USB power may not be exactly 5.00 V, the ADC and reference have tolerances, and the signal can change while it is being sampled. Other Arduino boards may use different voltage ranges or ADC resolutions, so check the documentation for the exact board.
The steps in the diagram are quantization. A voltage between two boundaries must still become one available code. Even an electrically perfect input cannot produce infinitely precise digital data.
Use a potentiometer as a controllable sensor
Before using one, it helps to understand why a potentiometer appears in so many beginner projects.
A potentiometer turns a physical action—rotating a knob—into a changing voltage that software can read. This makes it a simple way for a person to give a microcontroller a value, not just an ON or OFF command.
For example, the knob can become a:
- brightness setting for an LED;
- speed command for a fan or motor controller;
- volume setting for an audio project;
- position or angle command for a servo;
- sensitivity or alarm-threshold adjustment;
- menu value, timer duration, or calibration control.
The important distinction is that the potentiometer normally provides a control signal to the microcontroller. It should not be expected to power a motor, bright lamp, heater, or other substantial load directly. The program reads the knob position, decides what the requested setting means, and controls the load through suitable output and driver circuitry.
This creates a useful chain:
turn the knob
↓
potentiometer creates a voltage
↓
ADC converts the voltage to a number
↓
software interprets the number
↓
output changes brightness, speed, position, or another setting
It is also an excellent learning component. A real sensor introduces two unknowns at once: the sensor's behaviour and the ADC. With a potentiometer, I control the input myself. I can turn it slowly toward ground, the middle, or the supply rail and check whether the ADC value moves in the expected direction. If it does, I know the analog input and software path are working before replacing the knob with a light, temperature, pressure, or position sensor.
A 10 kΩ potentiometer is convenient for this first explanation because it acts as an adjustable voltage divider without placing an unnecessarily heavy load on the board's supply.
Connect it as follows:
- one outer pin to the Uno's
5Vpin; - the other outer pin to
GND; - the center pin, called the wiper, to analog input
A0.
Turning the knob moves the wiper between the ground end and the 5 V end. The voltage on A0 should therefore move from near 0 V to near the supply voltage. Reversing the two outer pins only reverses the knob direction.
This is also a useful model for resistive sensors. A photoresistor or thermistor can be combined with a fixed resistor to create a voltage divider, but its output then depends on light or temperature rather than a knob.
For a first practical result, the potentiometer does not need to control another component yet. Seeing the serial-monitor value follow the knob is enough to prove the complete input path. A later project can map that same reading to LED PWM, a servo command, or a motor-driver setting.
Input safety comes before code
An analog pin is not a general-purpose voltmeter input. Keep its voltage within the permitted range for the exact board, share a valid ground, and never attach mains voltage. Do not assume that every board is a 5 V board: many microcontrollers use 3.3 V inputs.
Read the raw code first
The smallest useful sketch prints both the raw result and an estimated voltage:
const int sensorPin = A0;
const float assumedReferenceVoltage = 5.00;
void setup() {
Serial.begin(9600);
}
void loop() {
int raw = analogRead(sensorPin);
float voltage = raw * assumedReferenceVoltage / 1023.0;
Serial.print("raw = ");
Serial.print(raw);
Serial.print(", estimated voltage = ");
Serial.println(voltage, 3);
delay(200);
}
The word assumed in the variable name is deliberate. If the ADC reference is actually 4.86 V but the code assumes 5.00 V, the calculated voltage will inherit that error. A digital multimeter can check the supply and wiper voltages when better voltage estimates matter.
Ideal calculated examples are:
| A0 input | Approximate 10-bit code | Interpretation |
|---|---|---|
| 0.00 V | 0 | Bottom of range |
| 1.25 V | 256 | About one quarter |
| 2.50 V | 512 | About halfway |
| 3.75 V | 767 | About three quarters |
| 5.00 V | 1023 | Top of range |
These are expected values calculated from an ideal 5.00 V reference. They are not readings collected for this article.
Why does a stationary sensor produce changing numbers?
A raw ADC reading is a measurement, not an immutable variable. Several small effects can move it:
| Cause | What it means | First check |
|---|---|---|
| Floating input | A0 has no defined electrical source | Confirm the wiper really reaches A0 and ground is shared |
| Quantization | A voltage near a code boundary rounds between adjacent codes | A movement of one code can be normal |
| Reference variation | The conversion scale moves with the reference | Measure the supply/reference when accuracy matters |
| Electrical noise | USB power, wires, switching circuits, and nearby fields add variation | Shorten wires and separate motors or PWM loads |
| High source impedance | The ADC's internal sampling capacitor may not settle fully | Check the board datasheet and buffer or lower the source impedance if needed |
| Real sensor behaviour | Light, temperature, pressure, and contact position are not perfectly constant | Compare the sensor's expected noise and response time |
| Loose breadboard contact | Mechanical connection changes the signal | Reseat one connection at a time |
The pattern matters. A reading that moves by one or two counts is different from one that jumps across hundreds of counts. Before adding software, check the raw minimum, maximum, average, wiring, ground, and input voltage range.
Smoothing is useful, but it is not a repair
If small, fast variations are acceptable for the application, averaging several samples can make the displayed value calmer:
const int sensorPin = A0;
const int sampleCount = 16;
void setup() {
Serial.begin(9600);
}
void loop() {
long total = 0;
for (int i = 0; i < sampleCount; i++) {
total += analogRead(sensorPin);
delay(2);
}
float average = total / float(sampleCount);
Serial.println(average, 1);
delay(100);
}
Averaging trades responsiveness for stability. It can reduce random variation, but it cannot fix an open ground, an over-voltage input, a poor reference, or a sensor that is fundamentally unsuitable. It can also hide brief events that the project needs to detect.
A software engineer may be tempted to filter first because filtering is easy to code. Hardware debugging works better when the raw signal is understood first.
Turn a measurement into a decision
Most projects do not need a beautifully formatted voltage. They need a decision such as “dark enough,” “too hot,” or “knob above halfway.”
const int sensorPin = A0;
const int threshold = 600;
void setup() {
Serial.begin(9600);
}
void loop() {
int raw = analogRead(sensorPin);
if (raw >= threshold) {
Serial.println("ABOVE threshold");
} else {
Serial.println("BELOW threshold");
}
delay(100);
}
If the raw value hovers around 600, the decision can rapidly switch between states. One solution is hysteresis: use one threshold to switch on and a different, lower threshold to switch off.
const int sensorPin = A0;
const int highThreshold = 620;
const int lowThreshold = 580;
bool state = false;
void setup() {
Serial.begin(9600);
}
void loop() {
int raw = analogRead(sensorPin);
if (!state && raw >= highThreshold) state = true;
if (state && raw <= lowThreshold) state = false;
Serial.println(state ? "ON" : "OFF");
delay(100);
}
This does not make the measurement more accurate. It makes the software decision less sensitive to harmless movement around one boundary.
DMM, oscilloscope, and ADC answer different questions
| Tool | Useful question |
|---|---|
| Digital multimeter | What stable voltage is present at A0? |
| Oscilloscope | How does the A0 voltage change over time? |
| Microcontroller ADC | What digital code can my program use now? |
The three readings may not look identical because the instruments have different input characteristics, sampling behaviour, bandwidth, references, and display update rates. That disagreement is a debugging clue, not proof that one instrument must be broken.
What about Raspberry Pi?
Do not treat “Raspberry Pi” as one electrical platform. Raspberry Pi Pico microcontroller boards include ADC-capable GPIO pins; the RP2040 ADC is 12-bit, although software APIs may present its result differently. Standard Raspberry Pi Linux computers expose GPIO but do not provide the same built-in general-purpose analog inputs, so a project commonly uses an external ADC chip or an analog sensor module with a digital interface.
Always check the exact board, pinout, voltage limit, and software API before moving this Uno example to another platform.
Completion checklist
- [ ] I can explain that many simple sensors produce an electrical quantity before software sees a number.
- [ ] I know that the ADC maps a voltage range to a finite set of codes.
- [ ] I can wire a potentiometer as a voltage divider with its wiper connected to A0.
- [ ] I can estimate voltage from a 10-bit raw code when the reference is known.
- [ ] I understand why one or two changing counts do not automatically indicate a bug.
- [ ] I inspect raw data and wiring before adding a filter.
- [ ] I know that averaging reduces noise but also slows response.
- [ ] I can use hysteresis to prevent rapid switching near a threshold.
- [ ] I will verify the input-voltage limit for the exact board.
Next experiment
Reading a sensor is only half of a physical system. The next step is to control a load that a microcontroller pin cannot safely power by itself, using a MOSFET and the protection components appropriate to the load.
Comments
Post a Comment