Project Overview & Difficulty Rating

Difficulty: Beginner-Intermediate | Time: 30 Minutes | Cost: ~$12 USD

The Arduino analog input is your gateway to reading the real world. Unlike digital pins that only see HIGH (5V) or LOW (0V), the analog-to-digital converter (ADC) on the ATmega328P microcontroller measures voltage in 1,024 discrete steps (10-bit resolution). On a standard 5V board, each step represents roughly 4.88 millivolts. This guide walks through wiring a 10kΩ potentiometer and a TMP36 analog temperature sensor, writing robust C++ code with noise filtering, and debugging the most common hardware and compiler failures.

Parts List & Specifications

ComponentExact Variant / SpecPurpose
MicrocontrollerArduino Uno R3 (ATmega328P) or Nano v3Target board with 6 dedicated ADC channels (A0-A5)
Potentiometer10kΩ Linear Taper (B10K)Ratiometric voltage divider for position/dial input
Temp SensorTMP36GZ (TO-92 package)Absolute analog voltage output temperature sensor
Capacitor100nF (0.1µF) Ceramic (X7R)Low-pass filter to stabilize ADC sampling
Wiring22 AWG solid core jumper wiresBreadboard connections

Pin Mapping & Wiring Steps

Before wiring, understand a critical hardware quirk: the ATmega328P ADC uses an internal sample-and-hold capacitor. If your sensor's output impedance is too high (above 10kΩ), this internal capacitor cannot charge fully during the sampling window, resulting in artificially low or fluctuating readings. The 100nF capacitor in this build acts as a local charge reservoir to solve this.

Pin Mapping Table

Component PinArduino Uno R3 PinNotes
Potentiometer Wiper (Middle)A0Analog Input 0
Potentiometer Left LegGNDGround reference
Potentiometer Right Leg5VVCC reference
TMP36 VCC (Pin 1)5VMust be 5V for standard math, not 3.3V
TMP36 Output (Pin 2)A1Analog Input 1
TMP36 GND (Pin 3)GNDShared ground with Uno
100nF Capacitor Leg 1A1Parallel to TMP36 output
100nF Capacitor Leg 2GNDFilters high-frequency noise

Step-by-Step Wiring

  1. Power the Breadboard: Connect the Uno R3 5V and GND pins to the breadboard's positive and negative power rails using 22 AWG jumper wires.
  2. Wire the Potentiometer: Insert the B10K pot into the breadboard. Connect the left pin to GND, the right pin to 5V, and the middle wiper pin to Arduino A0.
  3. Wire the TMP36: Place the TMP36 with the flat face toward you. Pin 1 (left) goes to 5V, Pin 2 (middle) goes to Arduino A1, and Pin 3 (right) goes to GND.
  4. Install the Bypass Capacitor: Bridge the 100nF ceramic capacitor between the TMP36 output (A1 row) and the GND rail. This is non-negotiable for clean data.
  5. Verify Connections: Use a multimeter in continuity mode to ensure no accidental shorts exist between the 5V rail and the analog input pins before applying power.

Complete Compilable Code (Arduino Uno R3)

This code targets the Arduino Uno R3 (and compatible Nano v3 boards). It implements a moving average filter to smooth out residual ADC noise and includes bounds-checking to catch disconnected sensors. For more on the underlying ADC mechanics, refer to the official Arduino analogRead() documentation and the Analog Devices TMP36 datasheet.


// Target Board: Arduino Uno R3 (ATmega328P) / Nano v3
#define POT_PIN A0
#define TMP_PIN A1
#define ADC_MAX 1023.0
#define V_REF 5.0

// Moving average filter parameters
const int numReadings = 16; // Must be a power of 2 for fast math
int readings[numReadings];
int readIndex = 0;
long total = 0;

void setup() {
  Serial.begin(115200);
  while (!Serial) { ; } // Wait for serial port (native USB boards)
  
  // Initialize array to prevent startup spikes
  for (int i = 0; i < numReadings; i++) {
    readings[i] = 0;
  }
  
  // Optional: Set ADC prescaler for faster sampling if needed
  // ADCSRA = (ADCSRA & 0xF8) | 0x04; // Div 16 (1MHz ADC clock)
}

void loop() {
  // --- Potentiometer Reading (Ratiometric) ---
  int rawPot = analogRead(POT_PIN);
  int potPercent = map(rawPot, 0, 1023, 0, 100);
  
  // --- TMP36 Reading (Absolute Voltage) ---
  // Subtract the last reading:
  total = total - readings[readIndex];
  // Read from the sensor:
  readings[readIndex] = analogRead(TMP_PIN);
  // Add the reading to the total:
  total = total + readings[readIndex];
  // Advance to the next position in the array:
  readIndex = (readIndex + 1) % numReadings;

  // Calculate the smoothed average:
  float avgADC = (float)total / numReadings;
  
  // Convert ADC value to voltage
  float voltage = (avgADC / ADC_MAX) * V_REF;
  
  // Convert voltage to Celsius (TMP36 formula: (Vout - 0.5) * 100)
  float tempC = (voltage - 0.5) * 100.0;
  float tempF = (tempC * 9.0 / 5.0) + 32.0;

  // Error Handling: TMP36 valid range is -40C to 125C
  if (tempC < -40.0 || tempC > 125.0) {
    Serial.println("ERROR: TMP36 reading out of physical bounds. Check VCC/GND.");
  } else {
    Serial.print("Pot: ");
    Serial.print(potPercent);
    Serial.print("% | Temp: ");
    Serial.print(tempC, 1);
    Serial.print("C (");
    Serial.print(tempF, 1);
    Serial.println("F)");
  }
  
  delay(100); // Sample rate ~10Hz
}
Pro-Tip: If you are measuring very small voltages (like a current shunt resistor outputting 0-500mV), change V_REF to 1.1 and add analogReference(INTERNAL); in your setup block. This switches the ADC reference to the internal 1.1V bandgap, giving you roughly 4.5x more resolution for low-voltage signals.

Debugging: First Three Things to Check When It Fails

When your serial monitor outputs garbage, flatlines, or fails to compile, follow this ranked decision tree.

1. Symptom: Wildly Fluctuating Readings (e.g., jumping between 300 and 800)

Cause: Floating input or high source impedance. The ATmega328P ADC requires a source impedance of 10kΩ or less to charge its internal 14pF sampling capacitor within the 1.5 ADC clock cycles allocated for sampling. If you are using a 100kΩ potentiometer or a high-impedance voltage divider without a buffer op-amp, the voltage droops during the read.
Fix: Add the 100nF ceramic capacitor between the analog pin and GND. If using a voltage divider, lower the resistor values (e.g., use 10kΩ and 10kΩ instead of 1MΩ and 1MΩ) or add an LM358 op-amp voltage follower.

2. Symptom: Readings Cap at ~675 Instead of 1023

Cause: VCC mismatch. You have wired the sensor's VCC to the Arduino's 3.3V pin, but the code assumes a 5V reference (V_REF 5.0). 3.3V divided by 5V equals 0.66. 0.66 * 1023 = ~675.
Fix: Move the sensor VCC wire to the 5V pin. Alternatively, if your sensor strictly requires 3.3V, update the code to #define V_REF 3.3 and ensure you do not exceed the 3.3V maximum limit of the analog pin.

3. Symptom: Compiler Error: 'A0' was not declared in this scope

Exact Error String: error: 'A0' was not declared in this scope
Cause 1: You are porting this code to an ESP32 or a non-AVR board where the A0 macro is not predefined in the same way. On ESP32, you must use the actual GPIO numbers (e.g., GPIO 36 for ADC1_CH0).
Cause 2: In a multi-file PlatformIO project, you forgot to include the core header.
Fix: For ESP32, change #define POT_PIN A0 to #define POT_PIN 36. For PlatformIO AVR projects, ensure #include <Arduino.h> is at the very top of your .cpp file.

Extending and Simplifying the Build

Depending on your project phase, you may need to strip this down or scale it up.

How to Simplify

If you only need a basic dial input for a menu system or LED dimmer, drop the TMP36 and the moving average array entirely. Use the built-in map() function to convert the 0-1023 raw ADC value directly into your target range. For example, to drive a PWM fan pin (0-255):
int fanSpeed = map(analogRead(A0), 0, 1023, 0, 255);
This reduces memory overhead and simplifies the loop to three lines of code.

How to Extend

To turn this into a standalone data logger, add an I2C OLED display (SSD1306) and an RTC (DS3231). Wire the SDA/SCL lines to A4/A5 on the Uno R3. Log the temperature and potentiometer setpoint to an SD card module via SPI. If you need higher precision than 10-bit, upgrade the microcontroller to an Arduino Zero (SAMD21) or Teensy 4.1, which feature native 12-bit to 16-bit ADCs, eliminating the need for software oversampling tricks.

Frequently Asked Questions (FAQ)

Why is my Arduino analog input fluctuating so much even with a capacitor?

If the 100nF capacitor doesn't stabilize the reading, you are likely dealing with electromagnetic interference (EMI) or a ground loop. Ensure your USB cable is shielded and high-quality. If the Arduino is powered by a noisy switching power supply, the 5V rail itself will have ripple, which the ADC will read as noise because it uses the 5V rail as its default reference. Switching to the INTERNAL 1.1V reference (and scaling your sensor voltage down accordingly) will isolate the ADC from power supply noise.

Can I use an Arduino analog input as a digital pin?

Yes. The pins A0 through A5 on the Uno R3 are directly mapped to digital pins 14 through 19. You can use them as standard digital I/O by calling pinMode(A0, OUTPUT); or digitalRead(A1);. This is highly useful when you run out of standard digital pins for buttons or relays.

What is the maximum voltage for an Arduino analog input?

The absolute maximum voltage on any analog pin is VCC + 0.5V. On a 5V Uno R3, applying more than 5.5V will permanently destroy the ADC channel and potentially the entire microcontroller. If you need to measure a 12V car battery or a 24V solar array, you must use a resistor voltage divider to step the voltage down below 5V before it reaches the analog pin. Always add a 5.1V Zener diode to ground on the analog pin as a failsafe clamp.

How do I change the Arduino analog input resolution?

The ATmega328P hardware is fixed at 10-bit (1024 steps). You cannot change this via a simple register setting. However, you can achieve 12-bit resolution through oversampling. By taking 16 rapid readings, summing them, and dividing by 4 (right-shifting by 2 bits), you mathematically gain 1 extra bit of resolution per 4x oversampling. If hardware 12-bit or 16-bit resolution is a strict requirement, you must upgrade to a 32-bit ARM board like the Arduino Due, Zero, or an ESP32 (which has a 12-bit ADC, though notably non-linear at the extremes).