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 channelGPIOPhysical pinUse
ADC0GP2631External input
ADC1GP2732External input
ADC2GP2834External input
ADC3GP29Not on headerOn the Pico, measures VSYS through a divide-by-3 network
ADC4NoneInternalOn-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 - averaged ADC reading on GP26 drives PWM brightness on GP15
# 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 - read the on-chip temperature sensor with averaging and a moving filter
# 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 VREF in 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.
  • ValueError creating 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 VREF is 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.