Skip to main content

Basics - Pins, Timers, ADC, PWM

Prerequisites​

  • Raspberry Pi Pico or Pico W
  • Micro USB cable
  • A MicroPython-capable editor
  • MicroPython firmware flashed on the board

Parts you'll use​

  • 1 x push button
  • 1 x potentiometer
  • 1 x LED
  • 1 x resistor (for the LED)
  • Breadboard + jumper wires

Safety: Never connect anything to the Pico's VBUS or 3V3 incorrectly. Use the 3V3 pin for sensors and GND for ground. Protect LEDs with a resistor.


Pin cheat sheet​

  • Digital GPIO: GP0-GP28
  • ADC inputs: GP26 (ADC0), GP27 (ADC1), GP28 (ADC2)
  • Onboard LED:
    • Pico W: Pin("LED")
    • Pico: Pin(25) (GP25)
  • PWM: almost any GPIO supports PWM via PWM(Pin(n))

pinout


1) Toggling pins (LED)​

We'll blink an external LED.

Wiring​

  • LED anode (long leg) -> GP0 through the resistor
  • LED cathode (short leg) -> GND
from machine import Pin
from time import sleep

led = Pin(0, Pin.OUT)

while True:
led.toggle()
sleep(0.5)

How it works​

  • Pin(..., Pin.OUT) sets the GPIO as an output.
  • toggle() flips the pin state without tracking it yourself.
  • sleep(0.5) waits 0.5 seconds.

Exercises​

  1. Change the blink rate to 5 times per second.
  2. Make a pattern: short-short-long (like Morse "U").

2) Reading digital pins​

We'll read a push button and control an LED.

Wiring (pull-down example)​

  • Button one leg -> GP1
  • Button other leg -> 3V3
  • 10 kOhm resistor from GP14 -> GND (external pull-down) Or use the Pico's internal pull-down to skip the resistor (see code).
  • LED (optional): anode -> GP0 through resistor, cathode -> GND

You can use the internal pull-up/pull-down resistors to simplify wiring.

Code: Button with internal pull-down​

from machine import Pin
from time import sleep

# Input with internal pull-down means the pin reads 0 when unpressed
button = Pin(1, Pin.IN, Pin.PULL_DOWN)

led = Pin(0, Pin.OUT)

while True:
if button.value(): # 1 when pressed
led.on()
else:
led.off()
sleep(0.01) # tiny delay helps CPU + debounce a bit

Exercises​

  1. Invert the logic (LED on when not pressed).
  2. Add a long-press (>1s) action that blinks the LED rapidly.

3) Timers​

We'll toggle the led using a timer interrupt.

Wiring (pull-down example)​

  • Button one leg -> GP1
  • Button other leg -> 3V3
  • 10 kOhm resistor from GP14 -> GND (external pull-down) Or use the Pico's internal pull-down to skip the resistor (see code).
  • LED (optional): anode -> GP0 through resistor, cathode -> GND

You can use the internal pull-up/pull-down resistors to simplify wiring.

Code: Button with internal pull-down​

from machine import Pin, Timer

led = Pin(0, Pin.OUT)
timer = Timer()

def blink(timer):
led.toggle()

timer.init(freq=2.5, mode=Timer.PERIODIC, callback=blink)

# We need to stall for the program to keep running
while True:
pass

Exercises​

When the button is pressed, change between two blinking frequencies.


4) Reading ADC (potentiometer)​

We'll read an analog voltage (0-3.3 V) from a potentiometer using the Pico's ADC.

Wiring​

  • Potentiometer middle wiper -> ADC0 (GP26)
  • Pot one side -> 3V3
  • Pot other side -> GND

Code: Read ADC and print both raw and scaled values​

from machine import ADC, Pin
from time import sleep

adc = ADC(26) # GP26 = ADC0

while True:
raw = adc.read_u16() # 0..65535
volts = (raw / 65535) * 3.3 # scale to 0..3.3V
print("raw:", raw, " volts:", round(volts, 3))
sleep(0.1)

The Pico's read_u16() returns a 16-bit value (0-65535). The analog reference is 3.3 V (board's 3V3 rail).

Exercise: Disco!​

Change the frequency with which the led blinks based on the potentiometer.


5) PWM (fade an LED)​

PWM lets you control brightness by switching the LED on and off very fast. The perceived brightness depends on the duty cycle.

Wiring​

  • LED anode -> GP0 through the resistor
  • LED cathode -> GND

Code: Fade up and down with PWM​

from machine import Pin, PWM
from time import sleep

led_pin = Pin(0, Pin.OUT) # external LED on GP0
pwm = PWM(led_pin)
pwm.freq(1000) # 1 kHz is a good default

# Duty range is 0..65535
while True:
# fade up
for duty in range(0, 65536, 1024):
pwm.duty_u16(duty)
sleep(0.01)
# fade down
for duty in range(65535, -1, -1024):
pwm.duty_u16(duty)
sleep(0.01)

Mapping potentiometer -> brightness​

Combine ADC + PWM: twist the pot to change LED brightness.

from machine import ADC, Pin, PWM
from time import sleep

adc = ADC(26) # potentiometer on GP26 (ADC0)
pwm = PWM(Pin(0)) # LED on GP0
pwm.freq(1000)

while True:
duty = adc.read_u16() # already 0..65535, perfect for duty
pwm.duty_u16(duty)
sleep(0.01)

Exercises​

  1. Try different PWM frequencies (10 Hz, 120 Hz, 500 Hz). Do you notice flicker?
  2. Add a gamma curve so brightness appears more linear to your eyes:
    • duty = int((value/65535) ** 2.2 * 65535)
  3. Use the button as an on/off for the PWM brightness from the pot.

Putting it all together (mini-project)​

Create a touch lamp:

  • Button toggles the lamp (PWM LED) on/off.
  • Potentiometer sets brightness when the lamp is on.
  • Long-press the button (>1s) to enter a breathing mode (automatic fade).
  • Short press exits breathing mode back to manual brightness.

Hints:

  • Track a mode variable ("off", "manual", "breathing").
  • Use a debounced button with long-press detection.
  • In breathing mode, run the fade loop; in manual, map ADC -> duty.

Troubleshooting checklist​

  • LED never lights?
    • Check polarity (long leg to resistor/GPIO).
  • Button always reads 1 or 0?
    • Ensure you enabled the correct pull (PULL_UP or PULL_DOWN) and wired to GND/3V3 accordingly.
  • ADC readings jumpy?
    • Use a moving average, keep wires short, share a solid GND.
  • PWM looks weird?
    • Lower or raise pwm.freq() to avoid visible flicker or buzzing.

Challenge ideas​

  • Implement software PWM on a pin without hardware PWM (for learning).
  • Add a status LED that blinks out the current mode (1 blink = off, 2 = manual, 3 = breathing).
  • Log ADC values over USB serial and plot them on your computer.
  • Implement a semaphore using an RGB LED. Use a button.
  • Build a "Christmas tree" lighting system using an RGB LED. It should change the sequence when the button is pressed.

Reference snippets​

Internal pull-ups example (wire button to GND):

button = Pin(1, Pin.IN, Pin.PULL_UP)  # reads 1 idle, 0 when pressed

Button interrupt (edge-triggered) to toggle an LED:

from machine import Pin
from time import sleep

led = Pin(0, Pin.OUT) # or Pin("LED")
state = 0

def on_press(pin):
global state
state ^= 1
led.value(state)

button = Pin(1, Pin.IN, Pin.PULL_DOWN)
button.irq(trigger=Pin.IRQ_RISING, handler=on_press)

while True:
sleep(1) # main loop can do other work