The Direct Answer: How to Read Analog Signals on Arduino
To read an analog voltage on an Arduino, use the analogRead(pin) function. For the standard Arduino Uno R3 (and any board using the ATmega328P microcontroller), this function queries the internal 10-bit Analog-to-Digital Converter (ADC) and returns an integer between 0 and 1023. A reading of 0 represents 0V, and 1023 represents the reference voltage (default 5V).
However, simply calling analogRead(A0) is rarely enough for a reliable production or bench build. The ATmega328P ADC requires specific source impedance matching and reference voltage configuration to achieve its rated 10-bit resolution. According to the official Arduino analogRead reference, the ADC clock runs at 125 kHz, meaning each conversion takes roughly 100 microseconds. If your external circuit cannot charge the internal 14pF sample-and-hold capacitor within that window, your lower bits will be pure noise.
Hardware Decision Tree: Choosing Your Reference and Pin
Before writing code, you must decide on your Analog Reference (AREF). The analogReference(type) function dictates what voltage equals a reading of 1023. Choosing the wrong reference is the number one cause of poor ADC resolution.
| Scenario / Sensor Type | Required Reference | Code Command | Concrete Pick / Action |
|---|---|---|---|
| Standard 5V sensors (potentiometers, basic LDRs, joysticks) | DEFAULT (5V on Uno, 3.3V on 3.3V boards) | analogReference(DEFAULT); |
Default Pick. Use this for 90% of hobbyist builds. Resolution is ~4.88mV per step. |
| Low voltage sensors (0-1.1V range, like raw thermistors or shunt resistors) | INTERNAL (1.1V on ATmega328P) | analogReference(INTERNAL); |
Use INTERNAL to get 1.07mV per step resolution. Warning: Never apply >1.1V to A0 if this is set. |
| Precision measurement requiring exact 3.3V or 4.096V scaling | EXTERNAL (Voltage applied to AREF pin) | analogReference(EXTERNAL); |
Use a dedicated voltage reference IC (like LM4040) wired to the AREF pin. Do not just tie AREF to the 3.3V pin without a decoupling cap. |
Parts List and Pin Mapping for the Benchmark Build
This build creates a rock-solid analog reading circuit with hardware noise filtering. We are moving beyond the basic "potentiometer wired straight to A0" tutorial and adding proper signal conditioning.
| Component | Exact Part / Variant | Est. Cost | Purpose |
|---|---|---|---|
| Microcontroller | Arduino Uno R3 (Rev3) or genuine Nano (ATmega328P) | $27.00 | Main processing board with 10-bit ADC. |
| Analog Sensor | Bourns 3386P-1-103LF (10kΩ Cermet Trimmer) | $1.50 | Provides a variable voltage divider. 10kΩ keeps impedance under the 10kΩ ADC limit. |
| Decoupling Capacitor | 100nF (0.1µF) X7R Ceramic Capacitor (50V) | $0.10 | Filters high-frequency EMI and stabilizes the ADC sample-and-hold circuit. |
| Pull-down Resistor | 100kΩ Carbon Film Resistor (Optional) | $0.05 | Prevents floating pin states when the sensor is disconnected. |
Pin Mapping Table
| Component Pin | Arduino Uno R3 Pin | Notes |
|---|---|---|
| Potentiometer Wiper (Middle) | A0 (Analog In 0) | Do not use digital pins 14-19; use the 'A' prefix in code. |
| Potentiometer Leg 1 | 5V | Provides the upper bound of the voltage divider. |
| Potentiometer Leg 3 | GND | Provides the lower bound (0V). |
| 100nF Capacitor Leg 1 | A0 | Wired in parallel with the wiper. |
| 100nF Capacitor Leg 2 | GND | Completes the low-pass filter to ground. |
Complete Compilable Code with Noise Filtering
Raw ADC readings on a workbench will fluctuate by ±2 to ±5 counts due to USB power ripple and electromagnetic interference. The code below targets the Arduino Uno R3 (ATmega328P) and implements an Exponential Moving Average (EMA) filter. This provides smooth, stable output without the memory overhead of a large sampling array.
// Target Board: Arduino Uno R3 / Nano (ATmega328P)
// Hardware: 10k Potentiometer + 100nF cap on A0
#define SENSOR_PIN A0
#define VREF_VOLTAGE 5.0 // Board operates at 5V logic/AREF
#define ADC_RESOLUTION 1023.0 // 10-bit ADC
// EMA Filter Configuration
// Alpha determines smoothing. 0.1 = heavy smoothing, 0.9 = fast response.
#define EMA_ALPHA 0.15
float filteredVoltage = 0.0;
bool isInitialized = false;
void setup() {
Serial.begin(115200);
// Explicitly set reference to DEFAULT (5V).
// Crucial if previous code left it on INTERNAL.
analogReference(DEFAULT);
// Perform 5 dummy reads to settle the ADC multiplexer and sample-and-hold cap
for(int i = 0; i < 5; i++) {
analogRead(SENSOR_PIN);
delay(10);
}
Serial.println("System Ready. ADC Initialized.");
}
void loop() {
int rawAdc = analogRead(SENSOR_PIN);
// Error Handling: Check for physically disconnected sensor (floating high)
// If using a pull-down resistor, a reading of exactly 1023 with no physical
// movement usually indicates a broken wiper connection.
if (rawAdc >= 1022) {
Serial.println("WARNING: Sensor reading railed at 1023. Check wiper connection.");
}
// Calculate actual voltage
float currentVoltage = (rawAdc / ADC_RESOLUTION) * VREF_VOLTAGE;
// Apply Exponential Moving Average (EMA) Filter
if (!isInitialized) {
filteredVoltage = currentVoltage; // Seed the filter on first run
isInitialized = true;
} else {
filteredVoltage = (EMA_ALPHA * currentVoltage) + ((1.0 - EMA_ALPHA) * filteredVoltage);
}
// Output formatted data for Serial Plotter or debugging
Serial.print("Raw:");
Serial.print(rawAdc);
Serial.print(" | Smooth_V:");
Serial.println(filteredVoltage, 3); // Print to 3 decimal places
delay(50); // 20Hz sample rate is sufficient for human-interface sensors
}
Debugging: First Three Things to Check When Analog Reads Fail
When your serial output doesn't match your multimeter, don't immediately blame the microcontroller. Here is the ranked decision path for the three most common ADC failure modes.
1. Symptom: Serial output stuck at 1023 (or 5.00V)
Exact Error String: WARNING: Sensor reading railed at 1023. Check wiper connection.
- Cause A (Most Likely): You are reading the wrong pin. If you wired your sensor to physical pin A1, but defined
SENSOR_PINas1instead ofA1, the Arduino reads digital pin 1 (the TX pin), which is usually HIGH (5V). Fix: Always use the 'A' prefix (A0, A1, A2). - Cause B: The AREF pin is shorted to 5V, or you previously called
analogReference(INTERNAL)in an old sketch and didn't clear it. Fix: Upload a blank sketch, power cycle the board, and re-upload withanalogReference(DEFAULT). - Cause C: The potentiometer wiper is physically broken or disconnected, and the pin is floating high due to internal leakage. Fix: Add a 100kΩ pull-down resistor from A0 to GND.
2. Symptom: Wildly fluctuating values (e.g., jumping 412 to 588)
Exact Error String: Raw:412 | Smooth_V:2.013 rapidly alternating with Raw:588 | Smooth_V:2.874
- Cause A (Most Likely): High source impedance. You are using a 100kΩ or 1MΩ potentiometer, and the ADC sample-and-hold capacitor cannot charge fully in 100µs. Fix: Swap to a 10kΩ pot or add a 100nF capacitor at the pin.
- Cause B: USB power noise. The 5V rail from a cheap PC USB port can have 100mV+ of ripple. Fix: Power the Arduino via the barrel jack with a regulated 9V wall adapter, or use the EXTERNAL AREF pin with a clean 4.096V reference.
- Cause C: Missing software filtering. Fix: Implement the EMA filter provided in the code block above.
3. Symptom: Reads exactly 0 (or 0.00V) constantly
Exact Error String: Raw:0 | Smooth_V:0.000
- Cause A (Most Likely): The sensor ground is not shared with the Arduino ground. If your sensor is powered by an external battery, the battery's GND must be wired to the Arduino GND. Voltage is a relative measurement; without a shared reference, the ADC reads zero.
- Cause B: You accidentally called
analogReference(EXTERNAL)but have nothing wired to the AREF pin. The ADC reference is now 0V, making all readings 0. Fix: Change toDEFAULT.
Raw:675 | Smooth_V:3.300 (since 3.3V / 5.0V * 1023 = 675). If you get 675, your code and chip are fine; the bug is in your external wiring.
Extending and Simplifying the Build
Once you have a stable baseline reading, you will inevitably need to adapt the circuit for real-world voltages or simpler deployments.
How to Extend: Reading a 12V Car Battery
The Arduino will instantly destroy its ATmega328P silicon if you apply 12V to A0. You must use a voltage divider. According to Adafruit's battery measurement guide, a safe divider for a 14.4V automotive maximum uses a 33kΩ (R1) and 10kΩ (R2) resistor.
- Wiring: Battery (+) → 33kΩ → A0 → 10kΩ → Battery (-) / Arduino GND.
- Math: The divider ratio is 10 / (33 + 10) = 0.2325. A 14.4V battery yields 3.34V at A0.
- Code Change: Update
#define VREF_VOLTAGE 5.0and multiply your final calculated voltage by4.3(the inverse of the divider ratio) to get the true battery voltage. - Hardware Addition: Place the 100nF capacitor across the 10kΩ resistor to filter automotive alternator whine.
How to Simplify: Switching to a Digital Sensor
If you are spending hours fighting analog noise, impedance matching, and voltage dividers, you are likely using the wrong sensor for the job. If your project allows it, swap the analog sensor for an I2C digital equivalent. For example, replace an analog TMP36 temperature sensor with a digital BME280 or DS18B20. Digital sensors handle the ADC conversion on their own silicon, immune to the Arduino's 5V rail noise, and transmit a clean digital packet over I2C or 1-Wire. Use analog only when a digital equivalent doesn't exist, or when you are strictly constrained by BOM cost and board space.






