Pin Control - Goodbye pinMode, Hello Pin Class!

Learn how to control GPIO pins in MicroPython using the Pin class instead of Arduino's pinMode, digitalWrite, and digitalRead

By Kevin McAleer,    9 Minutes


Digital Pin Control: A New Approach

Arduino uses separate functions for pin setup and control: pinMode(), digitalWrite(), and digitalRead(). MicroPython uses an object-oriented approach with the Pin class.

Instead of treating pins as numbers and calling global functions, you create Pin objects and call methods on them. Itโ€™s cleaner and more Pythonic.

Setting Up Pins

Arduino C++:

const int LED_PIN = 13;
const int BUTTON_PIN = 2;

void setup() {
    pinMode(LED_PIN, OUTPUT);
    pinMode(BUTTON_PIN, INPUT);
}

MicroPython:

from machine import Pin

LED_PIN = 25
BUTTON_PIN = 14

led = Pin(LED_PIN, Pin.OUT)
button = Pin(BUTTON_PIN, Pin.IN)

Key differences:

  • Import the Pin class from the machine module
  • Create Pin objects instead of calling pinMode()
  • Use Pin.OUT and Pin.IN instead of OUTPUT and INPUT
  • Store the Pin object in a variable (like led or button)

Pin Numbers

Pin numbers vary by board. Hereโ€™s a quick reference:

Arduino Nano ESP32:

  • D2-D13 (digital pins)
  • LED_BUILTIN = pin 13
  • Uses standard Arduino numbering

Raspberry Pi Pico / Pico W / Pico 2W:

  • GP0-GP28 (GPIO pins)
  • Built-in LED = pin 25 (Pico) or โ€œLEDโ€ (Pico W/2W)
  • Uses GP numbering (e.g., GP15 = pin 15)

Example for built-in LED:

# Pico / Pico 2W
led = Pin(25, Pin.OUT)

# Pico W (WiFi LED is different)
led = Pin("LED", Pin.OUT)

# Arduino Nano ESP32
led = Pin(13, Pin.OUT)

Writing to Digital Pins

Arduino C++:

void loop() {
    digitalWrite(LED_PIN, HIGH);
    delay(1000);
    digitalWrite(LED_PIN, LOW);
    delay(1000);
}

MicroPython:

import time

while True:
    led.on()        # or led.value(1)
    time.sleep(1)
    led.off()       # or led.value(0)
    time.sleep(1)

MicroPython gives you two ways to control pins:

  • .on() and .off() - Clear and readable
  • .value(1) and .value(0) - More explicit

Both do the same thing. Most code uses .on() and .off() for clarity.

Reading from Digital Pins

Arduino C++:

void loop() {
    int buttonState = digitalRead(BUTTON_PIN);

    if (buttonState == HIGH) {
        digitalWrite(LED_PIN, HIGH);
    } else {
        digitalWrite(LED_PIN, LOW);
    }
}

MicroPython:

while True:
    button_state = button.value()

    if button_state == 1:
        led.on()
    else:
        led.off()

.value() reads the pin state:

  • Returns 1 for HIGH
  • Returns 0 for LOW

You can also use .value() to write by passing an argument: .value(1) or .value(0).

Pull-up and Pull-down Resistors

Buttons need pull-up or pull-down resistors. MicroPython makes this easy:

Arduino C++:

void setup() {
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    // or INPUT_PULLDOWN (if supported)
}

MicroPython:

# Pull-up resistor (default state HIGH)
button = Pin(14, Pin.IN, Pin.PULL_UP)

# Pull-down resistor (default state LOW)
button = Pin(14, Pin.IN, Pin.PULL_DOWN)

# No resistor (floating - not recommended)
button = Pin(14, Pin.IN)

When to use which:

  • Pin.PULL_UP - Button connects pin to GND. Pressed = LOW (0), Released = HIGH (1)
  • Pin.PULL_DOWN - Button connects pin to 3.3V. Pressed = HIGH (1), Released = LOW (0)

Most buttons use pull-up configuration.

Toggle Method

Hereโ€™s a handy method Arduino doesnโ€™t have:

MicroPython:

led = Pin(25, Pin.OUT)

# Toggle the LED state (onโ†’off or offโ†’on)
led.toggle()

This is perfect for simple blinking:

import time

led = Pin(25, Pin.OUT)

while True:
    led.toggle()
    time.sleep(0.5)  # Blink every 500ms

Getting Pin State

You can read back the current state of an output pin:

MicroPython:

led = Pin(25, Pin.OUT)
led.on()

print(led.value())  # Prints: 1

led.off()
print(led.value())  # Prints: 0

This is useful for maintaining state in complex programs.

Complete Button-LED Example

Arduino C++:

const int LED_PIN = 13;
const int BUTTON_PIN = 2;

void setup() {
    pinMode(LED_PIN, OUTPUT);
    pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
    int buttonState = digitalRead(BUTTON_PIN);

    // Active LOW (pressed = LOW)
    if (buttonState == LOW) {
        digitalWrite(LED_PIN, HIGH);
    } else {
        digitalWrite(LED_PIN, LOW);
    }
}

MicroPython:

from machine import Pin

led = Pin(25, Pin.OUT)
button = Pin(14, Pin.IN, Pin.PULL_UP)

while True:
    button_state = button.value()

    # Active LOW (pressed = 0)
    if button_state == 0:
        led.on()
    else:
        led.off()

Button Debouncing

Mechanical buttons bounce - they send multiple signals when pressed. Hereโ€™s a simple debounce pattern:

Arduino C++:

const int BUTTON_PIN = 2;
const int DEBOUNCE_DELAY = 50;

int lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;

void loop() {
    int reading = digitalRead(BUTTON_PIN);

    if (reading != lastButtonState) {
        lastDebounceTime = millis();
    }

    if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
        // Button state is stable
        if (reading == LOW) {
            // Button pressed
        }
    }

    lastButtonState = reading;
}

MicroPython:

from machine import Pin
import time

button = Pin(14, Pin.IN, Pin.PULL_UP)
DEBOUNCE_DELAY = 0.05  # 50ms

last_state = 1
last_time = 0

while True:
    current_state = button.value()

    if current_state != last_state:
        last_time = time.ticks_ms()

    if (time.ticks_ms() - last_time) > DEBOUNCE_DELAY * 1000:
        # Button state is stable
        if current_state == 0:
            # Button pressed
            print("Button pressed!")

    last_state = current_state
    time.sleep(0.01)  # Small delay

Simpler approach - just add a delay:

from machine import Pin
import time

button = Pin(14, Pin.IN, Pin.PULL_UP)
led = Pin(25, Pin.OUT)

last_state = 1

while True:
    current_state = button.value()

    # Detect button press (transition from HIGH to LOW)
    if last_state == 1 and current_state == 0:
        led.toggle()
        time.sleep(0.2)  # Simple debounce

    last_state = current_state
    time.sleep(0.01)

Multiple Pin Control

Working with multiple pins is cleaner in Python:

Arduino C++:

int leds[] = {10, 11, 12, 13};
int numLeds = 4;

void setup() {
    for (int i = 0; i < numLeds; i++) {
        pinMode(leds[i], OUTPUT);
    }
}

void loop() {
    // Turn all on
    for (int i = 0; i < numLeds; i++) {
        digitalWrite(leds[i], HIGH);
    }
    delay(1000);

    // Turn all off
    for (int i = 0; i < numLeds; i++) {
        digitalWrite(leds[i], LOW);
    }
    delay(1000);
}

MicroPython:

from machine import Pin
import time

# Create list of Pin objects
leds = [Pin(pin_num, Pin.OUT) for pin_num in [10, 11, 12, 13]]

while True:
    # Turn all on
    for led in leds:
        led.on()
    time.sleep(1)

    # Turn all off
    for led in leds:
        led.off()
    time.sleep(1)

That list comprehension [Pin(pin_num, Pin.OUT) for pin_num in [10, 11, 12, 13]] creates all Pin objects in one line!

Running Light Pattern

MicroPython:

from machine import Pin
import time

leds = [Pin(pin_num, Pin.OUT) for pin_num in [10, 11, 12, 13]]

while True:
    # Light up LEDs in sequence
    for led in leds:
        led.on()
        time.sleep(0.2)
        led.off()

    # Light up in reverse
    for led in reversed(leds):
        led.on()
        time.sleep(0.2)
        led.off()

Pin Interrupts (Advanced)

Interrupts let you respond to pin changes instantly without polling. This is advanced but powerful:

MicroPython:

from machine import Pin
import time

led = Pin(25, Pin.OUT)
button = Pin(14, Pin.IN, Pin.PULL_UP)

# Interrupt handler function
def button_pressed(pin):
    led.toggle()
    print("Button interrupt!")

# Set up interrupt on falling edge (HIGHโ†’LOW)
button.irq(trigger=Pin.IRQ_FALLING, handler=button_pressed)

# Main loop can do other things
while True:
    print("Doing other work...")
    time.sleep(2)

Interrupt triggers:

  • Pin.IRQ_FALLING - HIGH to LOW transition
  • Pin.IRQ_RISING - LOW to HIGH transition
  • Pin.IRQ_FALLING | Pin.IRQ_RISING - Both transitions

Important: Interrupt handlers should be short and fast. Donโ€™t use delays or print statements in production interrupt handlers.

Try It Yourself

Exercise 1: Traffic Light

Create a traffic light with 3 LEDs (red, yellow, green) on pins 10, 11, 12:

  • Red on for 5 seconds
  • Green on for 5 seconds
  • Yellow on for 2 seconds
  • Repeat

Answer:

from machine import Pin
import time

red = Pin(10, Pin.OUT)
yellow = Pin(11, Pin.OUT)
green = Pin(12, Pin.OUT)

def all_off():
    red.off()
    yellow.off()
    green.off()

while True:
    all_off()
    red.on()
    time.sleep(5)

    all_off()
    green.on()
    time.sleep(5)

    all_off()
    yellow.on()
    time.sleep(2)

Exercise 2: Button Counter

Create a program that:

  • Counts button presses
  • Displays the count on 4 LEDs in binary (pins 10-13)
  • Button on pin 14 with pull-up
  • Max count = 15 (4 bits), then reset to 0

Answer:

from machine import Pin
import time

button = Pin(14, Pin.IN, Pin.PULL_UP)
leds = [Pin(pin_num, Pin.OUT) for pin_num in [10, 11, 12, 13]]

count = 0
last_state = 1

def display_binary(number):
    """Display a number on 4 LEDs in binary"""
    for i in range(4):
        bit = (number >> i) & 1  # Extract bit i
        leds[i].value(bit)

while True:
    current_state = button.value()

    # Detect button press (HIGH to LOW)
    if last_state == 1 and current_state == 0:
        count = (count + 1) % 16  # Wrap at 16
        display_binary(count)
        print(f"Count: {count} (Binary: {count:04b})")
        time.sleep(0.2)  # Debounce

    last_state = current_state
    time.sleep(0.01)

Exercise 3: Button-Controlled Patterns

Create a program with:

  • 4 LEDs on pins 10-13
  • 1 button on pin 14 with pull-up
  • Each button press cycles through patterns:
    1. All LEDs on
    2. All LEDs off
    3. Alternating pattern (10, 12 on)
    4. Other alternating pattern (11, 13 on)
    5. Running light
  • Hold the pattern until next button press

Answer:

from machine import Pin
import time

button = Pin(14, Pin.IN, Pin.PULL_UP)
leds = [Pin(pin_num, Pin.OUT) for pin_num in [10, 11, 12, 13]]

pattern = 0
last_state = 1

def set_pattern(pattern_num):
    if pattern_num == 0:  # All on
        for led in leds:
            led.on()
    elif pattern_num == 1:  # All off
        for led in leds:
            led.off()
    elif pattern_num == 2:  # Alternating 1
        leds[0].on()
        leds[1].off()
        leds[2].on()
        leds[3].off()
    elif pattern_num == 3:  # Alternating 2
        leds[0].off()
        leds[1].on()
        leds[2].off()
        leds[3].on()

while True:
    current_state = button.value()

    # Detect button press
    if last_state == 1 and current_state == 0:
        pattern = (pattern + 1) % 4
        set_pattern(pattern)
        print(f"Pattern: {pattern}")
        time.sleep(0.2)

    last_state = current_state
    time.sleep(0.01)

< Previous Next >

You can use the arrows  โ† โ†’ on your keyboard to navigate between lessons.