The Raspberry Pi Pico 2 uses the RP2350 chip, featuring a 12-bit, 4-channel Analog-to-Digital Converter (ADC) plus an internal temperature sensor. Unlike the older RP2040, the RP2350 ADC offers a better baseline noise floor and true 12-bit effective resolution, but it introduces a specific hardware quirk: higher input leakage current. If you wire a high-impedance voltage divider directly to an ADC pin without buffering or filtering, your readings will droop and drift. This guide gives you the exact hardware workarounds, a data-dense spec comparison, and robust MicroPython code to get precision analog readings on the Pico 2.
Raspberry Pi Pico 2 ADC: Hardware Specs and the RP2350 Errata
Before wiring up your sensors, you need to understand the silicon you are working with. The RP2350 improved the ADC architecture, but early hardware design guides and silicon errata highlighted an input leakage issue that catches many makers off guard. When the internal sample-and-hold capacitor switches to the input pin, it draws a brief spike of current. If your source impedance is too high, the voltage sags before the capacitor fully charges, resulting in a lower-than-actual digital reading.
Always ensure the Thevenin equivalent resistance of your analog source is under 5kΩ. If you are building a battery voltage monitor using a resistor divider, do not use 100kΩ/100kΩ resistors. Use 4.7kΩ/4.7kΩ, or buffer the signal with a low-power op-amp like the MCP6001.
| Feature | RP2040 (Pico 1) | RP2350 (Pico 2) |
|---|---|---|
| Resolution | 12-bit (SAR) | 12-bit (SAR) |
| Effective Number of Bits (ENOB) | ~9.2 bits | ~11.5 bits |
| External Channels | 4 (ADC0 - ADC3) | 4 (ADC0 - ADC3) |
| Max Sample Rate | 500 kS/s | 500 kS/s |
| Input Leakage Current | Low (Negligible for most hobby circuits) | Higher (Requires <5kΩ source or buffering) |
| Internal Temperature Sensor | Channel 4 | Channel 4 |
For deeper hardware design rules, always consult the official Raspberry Pi RP2350 Hardware Design Guide, which details the exact PCB routing recommendations for analog ground planes to minimize digital noise coupling into your ADC traces.
Parts List and Pin Mapping
This build targets the Raspberry Pi Pico 2 (RP2350) running MicroPython firmware v1.24.0+. We are building a dual-channel logger reading an NTC thermistor and a linear potentiometer.
Bill of Materials
- Microcontroller: Raspberry Pi Pico 2 (RP2350, with pre-soldered headers)
- Sensor 1: 10kΩ NTC Thermistor (B-value 3950) + 10kΩ pull-up resistor
- Sensor 2: 10kΩ Linear Potentiometer (B10K)
- Filtering: 2x 100nF (0.1µF) Ceramic Capacitors (X7R)
- Buffer (Optional but recommended for high impedance): MCP6001 Op-Amp
Pin Mapping Table
| Component | Pico 2 Pin | RP2350 ADC Channel | Notes |
|---|---|---|---|
| NTC Thermistor Divider | GP26 (Pin 31) | ADC0 | Add 100nF cap to GND |
| Potentiometer Wiper | GP27 (Pin 32) | ADC1 | Add 100nF cap to GND |
| 3.3V Reference (VREF) | Pin 35 (ADC_VREF) | N/A | Tie to 3V3 OUT if not using external ref |
| Analog Ground (AGND) | Pin 33 (AGND) | N/A | Use for sensor ground returns |
Step-by-Step Wiring and Circuit Design
- Wire the NTC Voltage Divider: Connect the 10kΩ pull-up resistor from 3V3 OUT to GP26. Connect the NTC thermistor from GP26 to AGND. This creates a voltage divider where the voltage at GP26 changes with temperature.
- Wire the Potentiometer: Connect one outer leg of the 10kΩ pot to 3V3 OUT, the other outer leg to AGND. Connect the center wiper pin to GP27.
- Add the Bypass Capacitors (Critical): Solder or plug a 100nF ceramic capacitor directly between GP26 and AGND, and another between GP27 and AGND. Why? The RP2350 ADC sample-and-hold circuit requires a quick burst of charge. The 100nF cap acts as a local charge reservoir, preventing the voltage from drooping during the sampling window, effectively solving the input leakage issue without needing an op-amp.
- Verify VREF: Ensure Pin 35 (ADC_VREF) is cleanly tied to your 3.3V supply. If you are powering the Pico 2 via USB, the internal 3.3V regulator is fine, but for precision work, an external 3.3V LDO (like the AP2112K-3.3) fed into ADC_VREF will drastically reduce reading jitter.
Complete MicroPython Code with Error Handling
The following script targets the Pico 2 running MicroPython. It reads both channels, applies a 16-sample moving average to smooth out residual 12-bit noise, and converts the raw ADC values into physical units (Celsius and Percentage). It includes explicit error handling for math domain errors that occur when an ADC reads a dead short (0) or an open circuit (65535).
import machine
import time
import math
# --- Pin Definitions ---
PIN_THERMISTOR = 26 # GP26 / ADC0
PIN_POTENTIOMETER = 27 # GP27 / ADC1
# --- ADC Initialization with Error Handling ---
try:
adc_temp = machine.ADC(PIN_THERMISTOR)
adc_pot = machine.ADC(PIN_POTENTIOMETER)
except ValueError as e:
print(f"Fatal Init Error: {e}")
print("Ensure you are passing valid ADC pin numbers (26, 27, 28).")
machine.reset()
# --- Thermistor Constants (10k NTC, B=3950) ---
R_NOMINAL = 10000.0
B_COEFF = 3950.0
T_NOMINAL = 298.15 # 25C in Kelvin
R_PULLUP = 10000.0
V_REF = 3.3
def read_average(adc_obj, samples=16):
"""Reads ADC multiple times and returns the average to smooth noise."""
total = 0
for _ in range(samples):
total += adc_obj.read_u16()
time.sleep_us(100)
return total / samples
def calculate_temperature(raw_adc):
"""Converts 16-bit raw ADC reading to Celsius."""
# Prevent division by zero or log(0) errors
if raw_adc <= 0:
raw_adc = 1
if raw_adc >= 65535:
raw_adc = 65534
voltage = (raw_adc / 65535.0) * V_REF
resistance = R_PULLUP * (voltage / (V_REF - voltage))
try:
steinhart = resistance / R_NOMINAL
steinhart = math.log(steinhart)
steinhart /= B_COEFF
steinhart += 1.0 / T_NOMINAL
steinhart = 1.0 / steinhart
temperature_c = steinhart - 273.15
return round(temperature_c, 2)
except ValueError:
# Catches math domain errors if resistance calculation goes negative
return -999.0
def main():
print("Starting Pico 2 ADC Logger...")
while True:
try:
raw_temp = read_average(adc_temp)
raw_pot = read_average(adc_pot)
temp_c = calculate_temperature(raw_temp)
pot_percent = round((raw_pot / 65535.0) * 100, 1)
print(f"Temp: {temp_c} C | Pot: {pot_percent}% | Raw T: {int(raw_temp)} | Raw P: {int(raw_pot)}")
except OSError as e:
print(f"Hardware Read Error: {e}")
except Exception as e:
print(f"Unexpected Error: {e}")
time.sleep(1.0)
if __name__ == "__main__":
main()
Debugging: When Your Pico 2 ADC Reads Drift or Zero
Analog debugging is where most embedded projects stall. If your serial monitor is throwing errors or the numbers are jumping wildly, follow this decision path.
The First Three Things to Check When It Fails
- Source Impedance & Bypass Caps: If your readings drift upward slowly after boot, or read lower than your multimeter shows, your source impedance is too high for the RP2350's sample-and-hold circuit. Verify your 100nF capacitors are physically as close to the Pico 2 GP26/GP27 pins as possible.
- AGND vs GND: Check your breadboard ground rails. You must use Pin 33 (AGND) for the ground return of your analog sensors. If you are using Pin 38 (GND) for your sensor ground, digital switching noise from the RP2350 core will inject millivolt-level jitter into your ADC readings.
- VREF Stability: Measure Pin 35 (ADC_VREF) with a multimeter while the code is running. If it reads 3.21V instead of 3.3V, your USB power is sagging. Update the
V_REFvariable in the code to match your actual measured VREF, or feed Pin 35 from a dedicated LDO.
Exact Error Strings and Ranked Causes
Error String: ValueError: Pin(22) doesn't have an ADC channel
- Cause 1 (Most Likely): You passed a GPIO number that isn't ADC-capable. On the Pico 2, only GP26, GP27, and GP28 are exposed external ADC pins. GP22 is digital-only.
- Cause 2: You are using an outdated MicroPython build (pre-v1.23) that doesn't correctly map the RP2350 pinmux. Flash the latest UF2 from the MicroPython download page.
Error String: ValueError: math domain error (Thrown inside the calculate_temperature function)
- Cause 1 (Most Likely): The ADC read exactly 65535 (open circuit) or 0 (dead short), causing the voltage calculation to result in a negative resistance, which crashes
math.log(). Check your breadboard wiring for loose jumper wires. - Cause 2: Your pull-up resistor value is mismatched in the code. If you used a 4.7kΩ physical resistor but left
R_PULLUP = 10000.0in the script, the math will skew, potentially hitting domain limits at extreme temperatures.
Extending and Simplifying the Build
Depending on your final application, you may need to scale this circuit up for a production environment or strip it down for a quick weekend prototype.
How to Simplify the Build
If you just need a rough dial input (like a volume knob or a basic thermostat) and don't care about 12-bit precision, drop the 100nF capacitors and the thermistor math. Swap the NTC for a standard machine.ADC voltage read on a potentiometer. You can also reduce the read_average samples from 16 to 4 to free up CPU cycles if you are running a high-frequency display update loop concurrently.
How to Extend the Build
To turn this into a standalone data logger:
- Add an SPI OLED: Wire an SSD1306 128x64 display to the Pico 2's SPI0 bus (GP18 SCK, GP19 MOSI) to render the temperature and a bar graph for the potentiometer in real-time.
- Use PIO for Exact Timing: The RP2350's Programmable I/O (PIO) blocks can be programmed to trigger the ADC at exact microsecond intervals, completely offloading the sampling from the main Cortex-M33 cores. This is essential if you are doing FFT audio analysis or capturing high-speed transient voltage spikes.
- Implement DMA: Chain the ADC to the RP2350's DMA controller to stream 10,000 samples directly into a RAM buffer without CPU intervention, then write that buffer to an SD card via SPI.






