Getting Started with Raspberry Pi GPIO: From Beginner to Practice

Hardware Environment: Raspberry Pi 4 Model B Rev 1.5 · Debian 12 Bookworm · Kernel 6.12.25
Test Date: April 2026
Author: zeruns (https://blog.zeruns.com/)
Note: This article was written by AI, for reference only (Hermes Agent + DeepSeek V4 Flash)


Table of Contents

  1. GPIO Basics
  2. Environment Setup & Tools
  3. GPIO Input/Output (Shell Method)
  4. GPIO Input/Output (Python Method)
  5. I2C Communication
  6. SPI Communication
  7. Integrated Examples
  8. Notes & Troubleshooting

1. GPIO Basics

1.1 What is GPIO

GPIO (General Purpose Input/Output) refers to a set of programmable pins on a microprocessor. Through software control, each pin can be configured as:

  • Output mode: Output high (3.3V) or low (0V) logic levels to control devices like LEDs, relays, and buzzers
  • Input mode: Read external signal states (high/low), useful for reading buttons or sensor outputs
  • Alternate functions: Dedicated protocols such as I2C, SPI, UART, PWM, etc.

1.2 Raspberry Pi 4B Pin Layout

The Raspberry Pi 4B features a 40-pin expansion header, with three numbering schemes:

Numbering Scheme Description Typical Libraries
Physical Pin (Board) Numbered 1 to 40, starting from top-left as 1 RPi.GPIO setmode(GPIO.BOARD)
BCM Numbering Broadcom chip GPIO numbers, e.g., GPIO17 RPi.GPIO setmode(GPIO.BCM), libgpiod
wiringPi Numbering Custom wiringPi numbering (deprecated) wiringPi library

40-Pin Function Diagram

                    ┌─────────────────────────────┐
                    │  🥝 Raspberry Pi 4B 40-Pin Layout   │
  3.3V  (01) ──●  ●── (02)  5V                    │
 GPIO2  (03) ──●  ●── (04)  5V                    │
 GPIO3  (05) ──●  ●── (06)  GND                   │
 GPIO4  (07) ──●  ●── (08)  GPIO14 (UART TX)      │
   GND  (09) ──●  ●── (10)  GPIO15 (UART RX)      │
GPIO17  (11) ──●  ●── (12)  GPIO18 (PWM0)         │
GPIO27  (13) ──●  ●── (14)  GND                   │
GPIO22  (15) ──●  ●── (16)  GPIO23                │
  3.3V  (17) ──●  ●── (18)  GPIO24                │
GPIO10 (19) ──●  ●── (20)  GND  (SPI_MOSI)        │
 GPIO9 (21) ──●  ●── (22)  GPIO25 (SPI_MISO)      │
GPIO11 (23) ──●  ●── (24)  GPIO8  (SPI_SCLK)      │
   GND (25) ──●  ●── (26)  GPIO7  (SPI_CE0)       │
 GPIO0 (27) ──●  ●── (28)  GPIO1  (ID_SDA / I2C)  │
 GPIO5 (29) ──●  ●── (30)  GND                    │
 GPIO6 (31) ──●  ●── (32)  GPIO12 (PWM0)          │
GPIO13 (33) ──●  ●── (34)  GND                    │
GPIO19 (35) ──●  ●── (36)  GPIO16                 │
GPIO26 (37) ──●  ●── (38)  GPIO20                 │
   GND (39) ──●  ●── (40)  GPIO21                 │
                    └─────────────────────────────┘

Main Pin Functions of Raspberry Pi 4B

Physical Pin BCM Number Function Physical Pin BCM Number Function
1 3.3V 2 5V
3 GPIO2 I2C1 SDA 4 5V
5 GPIO3 I2C1 SCL 6 GND
7 GPIO4 8 GPIO14 UART TX
9 GND 10 GPIO15 UART RX
11 GPIO17 12 GPIO18 PCM_CLK / PWM0
13 GPIO27 14 GND
15 GPIO22 16 GPIO23
17 3.3V 18 GPIO24
19 GPIO10 SPI0 MOSI 20 GND
21 GPIO9 SPI0 MISO 22 GPIO25
23 GPIO11 SPI0 SCLK 24 GPIO8 SPI0 CE0
25 GND 26 GPIO7 SPI0 CE1
27 GPIO0 ID_SDA (EEPROM) 28 GPIO1 ID_SCL (EEPROM)
29 GPIO5 30 GND
31 GPIO6 32 GPIO12 PWM0
33 GPIO13 PWM1 34 GND
35 GPIO19 PCM_FS / PWM1 36 GPIO16
37 GPIO26 38 GPIO20 PCM_DIN
39 GND 40 GPIO21 PCM_DOUT

1.3 Important Notes

  • Logic Levels: Raspberry Pi GPIO operates at 3.3V logic and must not be directly connected to 5V signals
  • Current Limits: Each GPIO pin can source about 16mA, and all GPIOs combined should not exceed 50mA
  • Default State: Most GPIOs default to input mode, some with internal pull-up/down resistors
  • 3.3V Pins (01/17): Maximum output current ~500mA
  • 5V Pins (02/04): Powered directly from USB-C supply; current depends on power adapter

2. Environment Setup & Tools

2.1 Pre-Installed Tools and Libraries

This guide was tested on Debian 12 Bookworm. The following tools are pre-installed:

Tool/Library Version Purpose
libgpiod / gpioset / gpioget 1.6.3 Shell-based GPIO control
python3-libgpiod 1.6.3 Python bindings for libgpiod
RPi.GPIO 0.7.2 Classic Python GPIO library
smbus2 Installed Python I2C communication
spidev Installed Python SPI communication
pigpiod 1.79 GPIO daemon (PWM/remote control)
i2c-tools 4.3 I2C device scanning

2.2 Installing Required Tools

If any tools are missing, install them using:

# Basic GPIO tools
sudo apt install gpiod libgpiod-dev python3-libgpiod

# I2C tools
sudo apt install i2c-tools

# Python libraries
pip install RPi.GPIO smbus2 spidev gpiozero

# pigpio daemon (recommended for hardware PWM)
sudo apt install pigpio pigpiod
sudo systemctl enable pigpiod --now

2.3 Enabling I2C / SPI Interfaces

Use raspi-config to enable:

sudo raspi-config

Menu path:
Interface OptionsI2CEnableYes
Interface OptionsSPIEnableYes

Alternatively, manually edit /boot/firmware/config.txt (Debian 12):

# Manually add
sudo tee -a /boot/firmware/config.txt <<EOF
dtparam=i2c_arm=on
 dtparam=spi=on
 EOF

 # Restart to apply
 sudo reboot

Verify enabling success:

 # Check I2C
 ls /dev/i2c*
 # Output: /dev/i2c-20  /dev/i2c-21

 # Check SPI (after enabling)
 ls /dev/spi*
 # Output: /dev/spidev0.0  /dev/spidev0.1

2.4 View all gpiochip

 $ gpioinfo
 gpiochip0 - 58 lines:
         line   0:     "ID_SDA"       unused   input  active-high
         line   1:     "ID_SCL"       unused   input  active-high
         line   2:      "GPIO2"       unused   input  active-high
         ...

The BCM2711 chip on Raspberry Pi 4B provides one gpiochip0 with 58 GPIOs, but only GPIO0-GPIO27 are exposed via the 40-pin header.


3. GPIO Input/Output (Shell Method)

3.1 libgpiod Toolkit

It is recommended to use the libgpiod tools, which do not depend on wiringPi and represent the current mainstream solution.

Main Commands:

Command Function Example
gpioinfo View all GPIO states gpioinfo
gpioset Set GPIO output level gpioset 0 17=1
gpioget Read GPIO input level gpioget 0 17
gpiomon Monitor GPIO events gpiomon 0 17

Format Description: gpioset <chip> <pin>=<va> chip is usually 0 (gpiochip0), and pin` refers to the BCM numbering

3.2 Output: Blinking an LED

Connect the positive lead (longer leg) of an LED through a 330Ω resistor to GPIO17 (physical pin 11), and the negative lead (shorter leg) to GND (physical pin 9).

# Set GPIO17 to high → LED on
gpioset 0 17=1

# Set GPIO17 to low → LED off
gpioset 0 17=0

3.3 Input: Reading Button State

Connect one end of a push button to GPIO18 (physical pin 12), and the other end to GND (physical pin 14).

# Read GPIO18 level (requires external pull-up or internal pull-up enabled)
gpioget 0 18
# Output: 1 (button not pressed, high level)
# Output: 0 (button pressed, low level)

3.4 View All Pin States

# View all pins
gpioinfo

# View specific pin (BCM number 17)
gpioinfo | grep "GPIO17"
# Output: line  17:      "GPIO17"       "myapp"   output  active-high

3.5 gpioset: Setting Pull-up/Pull-down

libgpiod 1.6 supports setting bias when requesting:

# Set GPIO17 as output, initial high, with pull-up
gpioset --bias=enable 0 17=1

4. GPIO Input/Output (Python Methods)

4.1 Using RPi.GPIO (Classic Library)

RPi.GPIO is the most commonly used Python GPIO library—simple and intuitive.

4.1.1 Output: Blinking LED

import RPi.GPIO as GPIO
import time

# Use BCM pin numbering
GPIO.setmode(GPIO.BCM)

# Set GPIO17 as output
GPIO.setup(17, GPIO.OUT)

# Blink LED 5 times
for _ in range(5):
    GPIO.output(17, GPIO.HIGH)  # Turn on
    time.sleep(0.5)
    GPIO.output(17, GPIO.LOW)   # Turn off
    time.sleep(0.5)

# Clean up
GPIO.cleanup()

4.1.2 Input: Reading a Button

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

# Set GPIO18 as input with internal pull-up resistor
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)

try:
    while True:
        if GPIO.input(18) == GPIO.LOW:
            print("Button pressed")
        else:
            print("Button released")
        time.sleep(0.1)
except KeyboardInterrupt:
    GPIO.cleanup()

GPIO.PUD_UP = Internal pull-up (default high, goes low when pressed)
GPIO.PUD_DOWN = Internal pull-down (default low, goes high when pressed)

4.1.3 PWM Output: Breathing LED

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)  # GPIO18 supports hardware PWM

pwm = GPIO.PWM(18, 1000)  # Frequency 1kHz
pwm.start(0)  # Initial duty cycle 0%

try:
    while True:
        # Fade in
        for duty in range(0, 101, 5):
            pwm.ChangeDutyCycle(duty)
            time.sleep(0.05)
        # Fade out
        for duty in range(100, -1, -5):
            pwm.ChangeDutyCycle(duty)
            time.sleep(0.05)
except KeyboardInterrupt:
    pwm.stop()
    GPIO.cleanup()

4.1.4 Event Detection: Interrupt-based Button Reading

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)

def button_callback(channel):
    print(f"Button pressed! (Pin {channel})")

# Trigger interrupt on falling edge
GPIO.add_event_detect(18, GPIO.FALLING,
                      callback=button_callback,
                      bouncetime=200)

try:
    print("Waiting for button press...")
    time.sleep(60)
except KeyboardInterrupt:
    GPIO.cleanup()

4.2 Using gpiozero (Higher-Level Abstraction)

gpiozero provides a higher-level object-oriented interface, ideal for rapid prototyping:

# Install
tpip install gpiozero
from gpiozero import LED, Button, Buzzer
from signal import pause

# LED: Automatic blinking
led = LED(17)
led.blink()

# Button: LED turns on when pressed
btn = Button(18, pull_up=True)

# Method 1: Direct assignment
btn.when_pressed = led.on
btn.when_released = led.off

# Method 2: Custom callback
btn.when_pressed = lambda: print("Pressed!")

# Keep running
pause()

Built-in components in gpiozero:

Component Class Name Description
LED LED(pin) Control on/off, blink, breathing
Button Button(pin, pull_up=True) Read button, detect long press
Buzzer Buzzer(pin) Control active buzzer
Distance Sensor DistanceSensor(echo, trigger) HC-SR04 ultrasonic sensor
Servo Servo(pin) Control servo angle
Motor Motor(forward, backward) Control DC motor

4.3 Using libgpiod Python Bindings (Low-Level)

import gpiod
import time

# Open gpiochip0
chip = gpiod.Chip('gpiochip0')

# Get GPIO17 and request as output
line = chip.get_line(17)
line.request(consumer='myapp', type=gpiod.LINE_REQ_DIR_OUT)

# Set high
line.set_value(1)
time.sleep(1)

# Set low
line.set_value(0)

# Release
line.release()

Input mode:

import gpiod

chip = gpiod.Chip('gpiochip0')
line = chip.get_line(18)

# Request as input with internal pull-up
line.request(consumer='myapp',
             type=gpiod.LINE_REQ_DIR_IN,
             flags=gpiod.LINE_REQ_FLAG_BIAS_PULL_UP)

value = line.get_value()  # 0 or 1
print(f"GPIO18 = {value}")

line.release()

5. I2C Communication

5.1 I2C Basics

I2C (Inter-Integrated Circuit) is a serial communication bus using two wires:

Signal Pin Function
SCL GPIO3 (Pin 5) Clock line
SDA GPIO2 (Pin 3) Data line

Features:

  • Master-slave architecture: Raspberry Pi acts as master, sensors as slaves
  • 7-bit address: Each slave has a unique address (e.g., 0x44)
  • Shared bus: Multiple devices can be connected to one I2C bus
  • Speed: Standard 100kHz, Fast 400kHz

5.2 Listing I2C Buses

# List I2C buses
$ i2cdetect -l
i2c-20  i2c        bcm2711_i2c1                   I2C adapter
i2c-21  i2c        bcm2711_i2c0                   I2C adapter
  • i2c-20: Maps to physical pins 3(SDA)/5(SCL) — main I2C bus
  • i2c-21: Maps to ID_EEPROM (pins 27/28), usually not used for external devices

5.3 Scanning I2C Devices

# Scan devices on i2c-20
$ i2cdetect -y 20
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- 44 -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Device found at address 0x44 — this is the SHT30 temperature & humidity sensor (I2C address 0x44).

If no devices appear (all --), no device is connected.

5.4 Using smbus2 to Read/Write I2C

smbus2 is the most widely used Python I2C library.

5.4.1 Reading SHT30 Sensor Data

import smbus2
import time

# SHT30 I2C address
SHT30_ADDR = 0x44

# Open I2C bus 20
bus = smbus2.SMBus(20)

# Send measurement command: high repeatability mode
bus.write_i2c_block_data(SHT30_ADDR, 0x2C, [0x06])

time.sleep(0.015)  # Wait for measurement (15ms)

# Read 6 bytes
data = bus.read_i2c_block_data(SHT30_ADDR, 0x00, 6)

# Calculate temperature and humidity
temp_raw = ((data[0] << 8) | data[1])
temp = -45 + 175 * temp_raw / 65535.0

hum_raw = ((data[3] << 8) | data[4])
hum = 100 * hum_raw / 65535.0

print(f"Temperature: {temp:.2f}°C")
print(f"Humidity: {hum:.2f}%")

5.4.2 Encapsulate into a Class

import smbus2
import time

class SHT30:
    def __init__(self, bus_id=20, addr=0x44):
        self.bus = smbus2.SMBus(bus_id)
        self.addr = addr

    def read(self):
        """Read temperature and humidity, return (temp°C, humidity%)"""
        self.bus.write_i2c_block_data(self.addr, 0x2C, [0x06])
        time.sleep(0.015)
        data = self.bus.read_i2c_block_data(self.addr, 0x00, 6)

        temp_raw = (data[0] << 8) | data[1]
        temp = -45 + 175 * temp_raw / 65535.0

        hum_raw = (data[3] << 8) | data[4]
        hum = 100 * hum_raw / 65535.0

        return round(temp, 2), round(hum, 2)

# Usage
sensor = SHT30()
t, h = sensor.read()
print(f"{t}°C, {h}%RH")

5.4.3 Generic I2C Read/Write Operations

import smbus2

bus = smbus2.SMBus(20)
addr = 0x44  # Device I2C address

# Write single byte to register
bus.write_byte_data(addr, 0x2C, 0x06)

# Read single byte
value = bus.read_byte_data(addr, 0x00)

# Read multiple bytes
data = bus.read_i2c_block_data(addr, 0x00, 6)

# Write multiple bytes
bus.write_i2c_block_data(addr, 0x2C, [0x06])

# Write one byte (no register address)
bus.write_byte(addr, 0x06)

# Read one byte (no register address)
value = bus.read_byte(addr)

bus.close()

5.5 Using i2cget / i2cset Commands

# Read status register of SHT30 (1 byte)
$ i2cget -y 20 0x44 0x00
0x65

# Read 6 bytes of raw data
$ i2cget -y 20 0x44 0x00 b 6

6. SPI Communication

6.1 SPI Basics

SPI (Serial Peripheral Interface) is a full-duplex synchronous serial bus using four lines:

Signal Raspberry Pi Pin Function
MOSI GPIO10 (Pin 19) Master Out, Slave In
MISO GPIO9 (Pin 21) Master In, Slave Out
SCLK GPIO11 (Pin 23) Clock
CE0 GPIO8 (Pin 24) Chip Select 0
CE1 GPIO7 (Pin 26) Chip Select 1

Features:

  • Full-duplex: Simultaneous send and receive
  • Master-slave: One master (Raspberry Pi), multiple slaves (each with own chip select)
  • High speed: Can reach tens of MHz
  • No addressing: Slave selected via chip select line

6.2 Enable and Verify SPI

# After enabling SPI
$ ls /dev/spi*
/dev/spidev0.0  /dev/spidev0.1
  • /dev/spidev0.0: SPI0 bus, CE0
  • /dev/spidev0.1: SPI0 bus, CE1

6.3 Using spidev for SPI Communication

pip install spidev

6.3.1 Reading MCP3008 ADC (8-Channel ADC)

import spidev

# Open SPI
spi = spidev.SpiDev()
spi.open(0, 0)  # Bus 0, CE0

# Configure
spi.max_speed_hz = 1350000  # 1.35MHz
spi.mode = 0  # SPI mode

def read_adc(channel):
    """Read MCP3008 channel (0–7)"""
    # Send 3 bytes: start bit + single-ended mode + channel
    r = spi.xfer2([1, (8 + channel) << 4, 0])
    value = ((r[1] & 3) << 8) + r[2]
    return value

# Read channel 0
adc_value = read_adc(0)
voltage = adc_value * 3.3 / 1023
print(f"ADC value: {adc_value}, Voltage: {voltage:.3f}V")

spi.close()

6.3.2 Generic SPI Data Transmission and Reception

import spidev

spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 500000
spi.mode = 0

# Send and receive simultaneously (full-duplex: send 0xFF while reading)
data_out = [0xFF, 0x00, 0x55]
data_in = spi.xfer2(data_out)  # Send 3 bytes, receive 3 bytes
print(f"Sent: {[hex(x) for x in data_out]}")
print(f"Received: {[hex(x) for x in data_in]}")

# Send only (ignore received data)
spi.writebytes([0x01, 0x02, 0x03])

spi.close()

7. Comprehensive Examples

7.1 Example 1: LED Breathing Light + Button Control

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

LED_PIN = 17      # BCM 17
BTN_PIN = 18      # BCM 18

GPIO.setup(LED_PIN, GPIO.OUT)
GPIO.setup(BTN_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)

pwm = GPIO.PWM(LED_PIN, 500)
pwm.start(0)

running = True  # LED status

try:
    while True:
        if GPIO.input(BTN_PIN) == GPIO.LOW:
            running = not running
            time.sleep(0.3)  # Debounce

        if running:
            # Breathing effect
            for duty in range(0, 101, 2):
                pwm.ChangeDutyCycle(duty)
                time.sleep(0.01)
            for duty in range(100, -1, -2):
                pwm.ChangeDutyCycle(duty)
                time.sleep(0.01)
        else:
            pwm.ChangeDutyCycle(0)

except KeyboardInterrupt:
    pwm.stop()
    GPIO.cleanup()

7.2 Example 2: Reading SHT30 Temperature and Humidity + Auto Logging

import smbus2
import time
import csv
from datetime import datetime

class SHT30:
    def __init__(self, bus_id=20, addr=0x44):
        self.bus = smbus2.SMBus(bus_id)
        self.addr = addr

    def read(self):
        self.bus.write_i2c_block_data(self.addr, 0x2C, [0x06])
        time.sleep(0.015)
        data = self.bus.read_i2c_block_data(self.addr, 0x00, 6)
        t = -45 + 175 * ((data[0] << 8) | data[1]) / 65535.0
        h = 100 * ((data[3] << 8) | data[4]) / 65535.0
        return round(t, 2), round(h, 2)

sensor = SHT30()

# Test reading
t, h = sensor.read()
print(f"[{datetime.now()}] Temperature: {t}°C, Humidity: {h}%")

# Continuously log to CSV (once per minute)
with open('sensor_log.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['Time', 'Temperature (°C)', 'Humidity (%)'])
    for i in range(10):  # Record 10 times
        t, h = sensor.read()
        now = datetime.now().strftime('%H:%M:%S')
        writer.writerow([now, t, h])
        print(f"[{now}] {t}°C, {h}%")
        time.sleep(60)

7.3 Example 3: One-Liner GPIO Control Using gpiozero

from gpiozero import LED, Button
from signal import pause
import time

led = LED(17)
btn = Button(18, pull_up=True)

# Turn on when pressed, off when released
btn.when_pressed = led.on
btn.when_released = led.off

# Double-click toggles blink mode
press_count = 0

def double_click():
    global press_count
    press_count += 1
    time.sleep(0.3)
    if press_count == 2:
        led.blink()
        print("Double-click! LED blinking mode")
        press_count = 0

btn.when_pressed = double_click

pause()

8. Important Notes and Troubleshooting

8.1 Voltage Level Conversion (3.3V vs 5V)

:warning: Raspberry Pi GPIO uses 3.3V logic. Do NOT connect directly to 5V devices!

Scenario Solution
5V sensor output → Raspberry Pi input Use a level shifter module (e.g., TXS0108E) or voltage divider
Raspberry Pi output → 5V device input Some 5V devices accept 3.3V as high (e.g., WS2812B)
3.3V sensor → Raspberry Pi Can be connected directly :white_check_mark:

Voltage Divider (5V → 3.3V):

    5V ──┬── R1 (1.8kΩ) ──┬── Raspberry Pi GPIO
         │                │
        GND ── R2 (3.3kΩ) ─┘

8.2 Pull-Up / Pull-Down Resistors

  • Internal pull-up: RPi.GPIO uses pull_up_down=GPIO.PUD_UP
  • Internal pull-down: RPi.GPIO uses pull_up_down=GPIO.PUD_DOWN
  • libgpiod: use gpiod.LINE_REQ_FLAG_BIAS_PULL_UP
  • External pull-up: 4.7kΩ ~ 10kΩ resistor to 3.3V

8.3 Current Limitations

Parameter Limit
Max output per GPIO 16mA
Total across all GPIOs 50mA
Total current on 3.3V pin ~500mA
Total current on 5V pin Depends on power supply

:light_bulb: Always use a 220Ω ~ 330Ω current-limiting resistor in series when driving an LED directly!

8.4 Common Errors

Error Cause Solution
RuntimeError: No access to /dev/mem Insufficient permissions Run with sudo python3 script.py
RuntimeError: Pin already in use Pin occupied by another program Check /sys/class/gpio/, release or reboot
[Errno 13] Permission denied + I2C I2C not enabled or insufficient permissions Add user to i2c group
Could not open /dev/spidev0.0 SPI not enabled Check /boot/config.txt
LED does not light Polarity reversed Connect LED long leg to GPIO, short leg to GND
Button triggers falsely Missing debounce Add bouncetime or delay in code

8.5 Permission Setup

# Add current user to i2c and gpio groups
sudo usermod -aG i2c,gpio $USER

# Reload session to apply changes
exec su - $USER

8.6 Quick Online References


Author: zeruns
This guide is tested and written based on Raspberry Pi 4B + Debian 12 Bookworm