Raspberry Pi Pico ADC: Potentiometer and Internal Temperature Sensor
Many sensors, such as light-dependent resistors, thermistors, gas sensors and joysticks, produce a voltage rather than a digital signal. An analog-to-digital converter (ADC) turns that voltage into a number your program can use. The Pico's chip has a 12-bit ADC with three channels on the header and one internal channel connected to a temperature sensor. In this tutorial you will read a potentiometer, use it to dim an LED, and measure the chip temperature, with averaging to reduce noise.
The Pico ADC at a Glance
| ADC channel | GPIO | Physical pin | Use |
|---|---|---|---|
| ADC0 | GP26 | 31 | External input |
| ADC1 | GP27 | 32 | External input |
| ADC2 | GP28 | 34 | External input |
| ADC3 | GP29 | Not on header | On the Pico, measures VSYS through a divide-by-3 network |
| ADC4 | None | Internal | On-chip temperature sensor |
The converter is a 12-bit successive-approximation ADC, so it produces values from 0 to 4095 internally. MicroPython's ADC.read_u16() scales every reading to the 0 to 65535 range so code is portable between boards with different ADC resolutions. The reference voltage is the ADC_VREF pin, which on the Pico board is fed from the 3.3 V rail through a small filter, so full scale is approximately 3.3 V.
voltage = raw_u16 x 3.3 / 65535 Example: read_u16() returns 39321 voltage = 39321 x 3.3 / 65535 = 1.98 V
Wiring a Potentiometer
Use any linear potentiometer from 10 k to 100 k. Connect one outer leg to 3V3(OUT) (pin 36), the other outer leg to AGND (pin 33), and the middle leg, the wiper, to GP26 (pin 31). Turning the knob moves the wiper voltage from 0 V to 3.3 V. Using AGND rather than an ordinary ground pin keeps digital return currents away from the analog measurement. For the dimming example, keep the LED with its 330 ohm resistor on GP15 from the PWM tutorial.
Reading the Potentiometer and Dimming an LED
# pot_dimmer.py - potentiometer on GP26 controls LED brightness on GP15
from machine import ADC, Pin, PWM
import time
VREF = 3.3
SAMPLES = 16 # readings averaged per update
pot = ADC(26) # ADC0; ADC(0) also works
led = PWM(Pin(15))
led.freq(1000)
def read_avg(adc, n=SAMPLES):
# Average n readings to reduce noise
total = 0
for _ in range(n):
total += adc.read_u16()
return total // n
last_print = time.ticks_ms()
while True:
raw = read_avg(pot)
volts = raw * VREF / 65535
percent = raw * 100 // 65535
# Square the fraction for a more natural brightness response
duty = (raw * raw) // 65535
led.duty_u16(duty)
# Print twice per second without slowing the control loop
if time.ticks_diff(time.ticks_ms(), last_print) >= 500:
last_print = time.ticks_ms()
print('raw={:5d} {:.2f} V {:3d}%'.format(raw, volts, percent))
time.sleep_ms(10)
Turn the knob and the LED follows smoothly. Squaring the reading is a quick approximation of the gamma curve from the previous tutorial, so the low end of the knob gives fine control instead of jumping straight to medium brightness.
The Internal Temperature Sensor on ADC4
The temperature sensor is a diode inside the chip whose forward voltage falls as temperature rises. The RP2040 datasheet gives the conversion: about 0.706 V at 27 degrees C, with a slope of minus 1.721 mV per degree.
T (degrees C) = 27 - (V_sensor - 0.706) / 0.001721 Example: V_sensor = 0.700 V T = 27 - (0.700 - 0.706) / 0.001721 = 27 + 3.49 = 30.5 degrees C
# chip_temp.py - internal temperature sensor on ADC channel 4
from machine import ADC
import time
VREF = 3.3
sensor = ADC(4) # channel 4 is the internal sensor
def read_temp_c(samples=64):
total = 0
for _ in range(samples):
total += sensor.read_u16()
volts = (total / samples) * VREF / 65535
return 27 - (volts - 0.706) / 0.001721
filtered = read_temp_c()
while True:
t = read_temp_c()
# Exponential moving average smooths slow drift and noise
filtered = 0.8 * filtered + 0.2 * t
print('raw: {:.1f} C filtered: {:.1f} C'.format(t, filtered))
time.sleep(1)
Hold your finger on the RP2040 chip, the large square chip in the centre of the board, and the reading rises within a few seconds. Treat the result as a rough indication: it measures the silicon, which runs slightly warmer than the room, and a 1 percent error in the 3.3 V reference shifts the sensor voltage by about 7 mV, which moves the result by roughly 4 degrees. For accurate room temperature use a dedicated sensor such as the DHT22, covered later in this series.
Getting Better ADC Readings
- Average several readings. Noise falls roughly with the square root of the number of samples, so 16 samples cut random noise by about four times.
- Measure your actual 3.3 V rail with a multimeter and use that value as
VREFin the code. - Keep analog wires short and away from PWM and motor wires. Return analog grounds to AGND.
- For a precise reference, an external reference such as an LM4040 3.0 V shunt can be connected to ADC_VREF, after which full scale becomes 3.0 V.
- The RP2040 ADC has known non-linearity at a few specific codes (documented as an erratum in the datasheet), so do not expect the full 12 bits of accuracy.
Troubleshooting
- Reading stuck near 0 or 65535: the wiper is not on GP26 or an outer leg is missing.
- Reading jumps by several hundred counts: normal noise; increase averaging and check grounding.
ValueErrorcreating ADC: only GP26 to GP29 and channel numbers 0 to 4 are valid.- Temperature reads far too high: the board is next to a warm regulator or a USB hub, or
VREFis wrong.
Summary
The Pico ADC converts 0 to 3.3 V on GP26, GP27 and GP28 into a 16-bit scaled value with read_u16(). You converted readings to volts, averaged them for stability, used a potentiometer to control PWM brightness, and read the chip temperature on ADC4 using the datasheet formula. Next you will add an SSD1306 OLED display over I2C so readings like these can be shown without a computer.