When your Arduino code for a potentiometer outputs erratic values, jumps randomly, or fails to reach the full 0-1023 range, the issue is rarely the microcontroller itself. It is almost always a mismatch between the physical voltage at the wiper pin and the software's Analog-to-Digital Converter (ADC) assumptions. A potentiometer is simply a variable voltage divider. To write robust embedded code, you must first verify the physical hardware with a digital multimeter (DMM) before trusting the serial monitor.

This guide bridges the gap between bench measurement and embedded software, showing you exactly how to probe your circuit, what numerical values to expect, and how to calibrate your code to match real-world physics.

Multimeter Setup and Probe Placement

Before uploading any code, you must establish a baseline of the physical voltage being fed into the microcontroller's GPIO pin. Most hobbyist potentiometers (like the Bourns 3386P or Alpha RD901F 10kΩ) are wired as a three-terminal voltage divider.

DMM Configuration

  • Dial Position: DC Volts (V⎓)
  • Lead Jacks: Black lead to COM, Red lead to V/Ω/Hz
  • Range: Auto-ranging, or manual 20V DC range
  • Resolution: Minimum 3.5 digits (0.01V resolution required to spot ADC quantization errors)
Safety Category (CAT) Requirement: While a 5V DC breadboard is classified as SELV (Safety Extra-Low Voltage), embedded projects frequently integrate relay modules, MOSFETs, or optocouplers that switch 120V/240V AC mains. If your probe slips from the 5V rail onto a live mains terminal, an unrated meter can arc and cause severe injury. Always use a minimum CAT II 600V rated multimeter when probing mixed-signal breadboards, and de-energize any mains-connected relay loads before taking measurements.

Probe Placement Sequence

  1. Verify VREF: Place the black probe on the breadboard GND rail and the red probe on the Arduino 5V (or 3.3V) output pin. Record this exact number (e.g., 4.92V). This is your true Reference Voltage (VREF).
  2. Verify Grounding: Move the red probe to Pin 1 (CCW) of the potentiometer. It should read 0.00V. If it reads >0.05V, you have a floating ground or high-resistance breadboard contact.
  3. Measure the Wiper: Keep the black probe on GND. Place the red probe on Pin 2 (the center wiper). Slowly rotate the shaft from CCW to CW while watching the DMM display.

Expected Readings: Good vs. Bad Values

A linear taper potentiometer (B-taper) should produce a perfectly linear voltage sweep. The table below maps the physical wiper position to the expected DMM voltage and the corresponding raw ADC values for both a 10-bit Arduino Uno (ATmega328P) and a 12-bit ESP32. Use this table to diagnose hardware faults before blaming your code.

Wiper Position Expected DMM Voltage (5V VREF) Arduino ADC (10-bit) ESP32 ADC (12-bit, 11dB) Common Fault if Mismatched
0% (Full CCW) 0.00V - 0.02V 0 - 4 0 - 25 Wiper not grounding; floating GND wire
25% Rotation 1.23V - 1.27V 250 - 260 500 - 540 Track oxidation; dead spot causing jumps
50% (Center) 2.48V - 2.52V 505 - 515 1080 - 1130 VREF sag; USB brownout dropping 5V rail
75% Rotation 3.73V - 3.77V 760 - 775 1650 - 1720 Wrong taper (Audio/Log pot used instead of Linear)
100% (Full CW) 4.90V - 4.98V 1015 - 1023 2020 - 2047 Wiper contact resistance; VCC rail voltage drop

Note on ESP32 Hardware: The original ESP32 (WROOM-32) ADC is notoriously non-linear above 2.5V and often saturates around raw value 3100 (~3.1V) even with 11dB attenuation. If your 100% CW reading caps out early, this is a silicon limitation. Upgrade to an ESP32-S3 or ESP32-C3, which feature significantly improved ADC linearity, or use an external I2C ADC like the ADS1115.

Writing and Calibrating the Arduino Code

Once your DMM confirms the hardware is sweeping cleanly from 0V to your measured VREF, you can write the firmware. The most common mistake in Arduino code for a potentiometer is assuming the VREF is exactly 5.00V and using a basic analogRead() without filtering. Mechanical wipers generate thermal noise and micro-disconnects (jitter) that cause the ADC to fluctuate by 3 to 8 bits even when the knob is completely still.

The code below implements an Exponential Moving Average (EMA) filter. This acts as a software low-pass filter, smoothing out wiper jitter without the memory overhead of a large array required by a Simple Moving Average.

/*
 * Calibrated Potentiometer Reader with EMA Jitter Filter
 * Board: Arduino Uno / Nano (ATmega328P) or ESP32
 * Target: Smooth analog voltage reading mapped to 0-100%
 */

// Pin Definitions
const uint8_t POT_PIN = A0; // Use GPIO 34-39 for ESP32 ADC1 pins

// Calibration Constants (Measured with DMM)
const float VREF_MEASURED = 4.92; // Update this to your actual 5V pin reading!
const int ADC_MAX = 1023;         // Use 4095 for ESP32 12-bit

// Filter Tuning
const float ALPHA = 0.15; // Smoothing factor (0.0 to 1.0). Lower = smoother but slower response.

float filteredValue = 0.0;

void setup() {
  Serial.begin(115200);
  
  // Initialize ADC (ESP32 specific setup, ignored on AVR)
  #if defined(ESP32)
    analogReadResolution(12);
    analogSetAttenuation(ADC_11db);
  #endif

  // Prime the filter with an initial reading to prevent startup lag
  filteredValue = analogRead(POT_PIN);
}

void loop() {
  // 1. Read raw ADC value
  int rawADC = analogRead(POT_PIN);

  // 2. Apply Exponential Moving Average (EMA) Filter
  // Formula: filtered = (alpha * new_value) + ((1 - alpha) * old_value)
  filteredValue = (ALPHA * rawADC) + ((1.0 - ALPHA) * filteredValue);

  // 3. Convert to true voltage using measured VREF
  float trueVoltage = filteredValue * (VREF_MEASURED / ADC_MAX);

  // 4. Map to percentage (0.0% to 100.0%)
  float percentage = (filteredValue / ADC_MAX) * 100.0;

  // Output formatted data for Serial Plotter
  Serial.print("Raw:");
  Serial.print(rawADC);
  Serial.print("\tFiltered:");
  Serial.print(filteredValue, 1);
  Serial.print("\tVoltage:");
  Serial.print(trueVoltage, 3);
  Serial.print("V\tPercent:");
  Serial.print(percentage, 1);
  Serial.println("%");

  delay(20); // ~50Hz sampling rate
}

By defining VREF_MEASURED based on your DMM reading rather than hardcoding 5.0, you eliminate the scaling error caused by USB voltage drop. A 0.1V drop on the 5V rail introduces a 2% error across your entire measurement range.

Measurement Mistakes That Give Misleading Readings

Even with perfect code, incorrect measurement techniques on the bench will lead you to chase software ghosts. Here are the most common mistakes that yield misleading ADC data, and how to test for them.

Measurement Mistake Symptom in Serial Monitor How to Verify and Fix
Probing the Wiper While Loaded Readings compress at the high end; never reaches 1023. The microcontroller's ADC input impedance (typically 100MΩ on AVR, but lower on some ESP32 multiplexers) forms a secondary voltage divider if the pot resistance is too high. Fix: Use a 10kΩ pot. Avoid 100kΩ+ pots which are susceptible to noise injection and ADC sampling capacitor charge-time failures.
Ignoring Breadboard Contact Resistance ADC reads ~10-20 at 0% CW instead of 0; ground offset. Cheap breadboards have high spring-contact resistance. Fix: Measure the voltage drop between the Arduino GND pin and the potentiometer GND leg while under load. If it exceeds 10mV, move the components closer together or solder the ground connections.
Using an Audio Taper Pot for Linear Control First 70% of physical rotation only yields 30% of ADC range. Audio (Logarithmic/A-taper) pots are designed for human hearing perception, not linear position feedback. Fix: Check the pot casing for an 'A' (Audio) or 'B' (Linear) stamp. Replace with a B-taper linear potentiometer for predictable ADC mapping.
Sampling Too Fast Without Capacitor High-frequency noise (±15 bits) superimposed on the signal. The ADC's internal sample-and-hold capacitor needs time to charge. Fix: Add a 100nF ceramic capacitor between the wiper pin and GND, physically close to the microcontroller. This creates a hardware low-pass filter that stabilizes the voltage during the ADC acquisition window.

For deeper insights into voltage divider loading effects and ADC input impedance matching, refer to the SparkFun Voltage Divider Tutorial. If you are porting this code to the ESP32 ecosystem, always consult the official Espressif ADC Oneshot Driver Documentation to ensure your attenuation and resolution settings match your physical voltage expectations. Finally, for standard AVR timing and acquisition details, the Arduino analogRead() Reference remains the definitive source for clock-prescaler limitations.

Debugging analog inputs is an exercise in verifying the physical layer before trusting the digital layer. By pairing a disciplined DMM probe routine with calibrated, filtered firmware, you transform a jittery, unreliable knob into a precision input device.