Chapter 04 · 树莓派与嵌入式 Linux

GPIO 编程(Python)

把电脑变成能碰硬件的机器。搞懂 BCM 编号和 3.3V 电平,用 gpiozero 几行 Python 就能点灯、读按键、做呼吸灯。

课程进度33%

关键概念

GPIO 引脚编号:BCM vs BOARD
同一个物理针脚有两套编号法。BOARD:按物理位置从 1 到 40 数(左上角是 1)。BCM:按 SoC 芯片内部的 GPIO 编号(如 GPIO17),与物理位置无关。gpiozero 和大多数教程用 BCM 编号,本教程统一用 BCM。记住:说"GPIO17"指的是 BCM 17。
3.3V 逻辑电平
树莓派 GPIO 输出高电平是 3.3V(不是 5V!),输入也只能承受 0~3.3V。接入 5V 信号会烧毁 GPIO甚至整颗 SoC。这是与 Arduino(5V)最大的区别。5V 传感器接入前必须用电平转换或分压电阻降到 3.3V。
输出(Output)与输入(Input)
GPIO 引脚可配置为输出(我们控制它,输出高/低电平去点灯、驱动继电器)或输入(读取外部状态,如按键按下、传感器信号)。gpiozero 把它们抽象成 LEDButton 等设备类,无需手动设方向。
上拉 / 下拉电阻(Pull-up / Pull-down)
输入引脚悬空时电平不确定(浮空),会乱跳。上拉电阻把引脚默认拉到高电平,下拉拉到低电平,给它一个确定的默认值。树莓派 GPIO 内置可编程上下拉电阻。接按键时靠它,避免读到随机值。
限流电阻
LED 直接接 3.3V 会因电流过大烧毁。串联一个电阻(常用 220Ω~330Ω)限制电流在安全范围(几到十几 mA)。每个 GPIO 引脚最大输出约 16mA,所有引脚总和有上限,超过会损坏芯片。驱动大电流负载必须用三极管/继电器/驱动板。
PWM(脉冲宽度调制)
GPIO 只能输出 0 或 3.3V(数字),但通过快速开关并调节"通电时间占比"(占空比 Duty Cycle),平均电压可连续变化,从而实现 LED 调亮度、电机调速。占空比 50% 相当于半亮。树莓派可用软件 PWM(任意引脚)或硬件 PWM(少数引脚,更精准)。
gpiozero 库
树莓派官方推荐的高层 GPIO 库,系统预装。面向对象设计:LED(17)Button(2)PWMLED(18) 等,把复杂的底层操作封装成直观的对象方法。它底层可切换多种 pin factory(RPi.GPIO / lgpio / pigpio),Pi 5 用 lgpio。是本教程主力。
RPi.GPIO 与 lgpio
RPi.GPIO 是经典底层库,但不支持 Pi 5(Pi 5 改了 GPIO 硬件)。lgpio 是新一代底层库,Pi 5 默认使用。写代码优先用 gpiozero(自动适配底层),需要底层控制时 Pi 5 用 lgpio、老 Pi 可用 RPi.GPIO。

40 脚 GPIO 引脚图

接线前必须看懂引脚图。牢记电源脚和地脚的位置,别把信号接到电源上。下图标注 BCM 编号:

树莓派 40-Pin GPIO(BCM 编号,缺口朝上俯视) ═══════════════════════════════════════════════════════════════ 左列(奇数脚) 右列(偶数脚) ┌──────────────────────┬──────────────────────┐ │ 3V3 电源 [ 1] │ [ 2] 5V 电源 │ │ GPIO2 (SDA/I2C) [ 3] │ [ 4] 5V 电源 │ │ GPIO3 (SCL/I2C) [ 5] │ [ 6] GND 地 │ │ GPIO4 (1-Wire) [ 7] │ [ 8] GPIO14 (TXD) │ │ GND 地 [ 9] │ [10] GPIO15 (RXD) │ │ GPIO17 [11] │ [12] GPIO18 (PWM0) │ │ GPIO27 [13] │ [14] GND 地 │ │ GPIO22 [15] │ [16] GPIO23 │ │ 3V3 电源 [17] │ [18] GPIO24 │ │ GPIO10(SPI MOSI)[19] │ [20] GND 地 │ │ GPIO9 (SPI MISO)[21] │ [22] GPIO25 │ │ GPIO11(SPI SCLK)[23] │ [24] GPIO8 (SPI CE0) │ │ GND 地 [25] │ [26] GPIO7 (SPI CE1) │ │ ... (省略 27-40,含更多 GPIO/GND) ... │ └──────────────────────┴──────────────────────┘ 配色说明: 红=3.3V 橙=5V(别接GPIO!) 绿=GND 青=总线复用 蓝=串口 用 `pinout` 命令随时在树莓派上查看完整引脚图
接线三条铁律

① GPIO 是 3.3V,绝不接 5V 信号到 GPIO;② LED 必须串限流电阻;③ 所有设备共地(GND 连通)才能正常工作。接线前断电,用 pinout 命令确认引脚位置,避免把 5V 或信号接错到相邻脚。

点亮第一颗 LED(gpiozero)

硬件连接:LED 长脚(正极)→ 220Ω 电阻 → GPIO17(物理 11 脚);LED 短脚(负极)→ GND(物理 9 脚)。代码惊人地简单:

# blink.py — 让 LED 闪烁
from gpiozero import LED
from time import sleep

led = LED(17)      # BCM 17,即物理第 11 脚

while True:
    led.on()         # 点亮(输出高电平 3.3V)
    sleep(1)         # 亮 1 秒
    led.off()        # 熄灭
    sleep(1)
# 运行脚本(gpiozero 已预装,直接跑)
python3 blink.py
# LED 开始 1 秒一次地闪烁,Ctrl+C 停止

# 更简洁的写法:内置 blink 方法自动循环
# blink2.py — 用内置 blink,无需手写循环
from gpiozero import LED
from signal import pause

led = LED(17)
led.blink(on_time=0.5, off_time=0.5)  # 后台自动闪
pause()   # 阻塞主线程,让后台闪烁持续(否则脚本立即退出)

读取按键输入

硬件连接:按键一端 → GPIO2(物理 3 脚),另一端 → GND。gpiozero 的 Button 默认启用内部上拉电阻——未按时读高电平,按下时接通 GND 变低电平。

# button.py — 按键控制 LED
from gpiozero import LED, Button
from signal import pause

led = LED(17)
button = Button(2)   # 默认内部上拉,按下=接地=触发

# 方式一:轮询查询按键状态
# while True:
#     if button.is_pressed:
#         led.on()
#     else:
#         led.off()

# 方式二:事件回调(推荐,不占 CPU)
button.when_pressed = led.on    # 按下时点灯
button.when_released = led.off  # 松开时熄灭

print("按下按键点亮 LED,Ctrl+C 退出")
pause()   # 等待事件(回调在后台线程触发)
gpiozero 已自动帮你消抖

机械按键按下瞬间会因触点弹跳产生多次抖动信号。传统上要写延时消抖逻辑。gpiozero 的 Button 内置了 bounce_time 参数(如 Button(2, bounce_time=0.05)),自动过滤 50ms 内的抖动。事件回调(when_pressed)比轮询更省 CPU,是推荐做法。

PWM 呼吸灯:让 LED 渐亮渐暗

普通 LED 只能全亮全灭。用 PWM 快速开关并调节占空比,就能实现亮度渐变。gpiozero 用 PWMLED,value 取 0.0~1.0 表示亮度。

PWM 占空比原理:调节"通电时间比例"控制平均亮度 ═══════════════════════════════════════════════════════ 占空比 25%(暗) ┌─┐ ┌─┐ ┌─┐ 平均电压 ≈ 0.83V 3.3V ────────────┘ └───┘ └───┘ └── LED 微亮 0V 占空比 50%(半亮) ┌──┐ ┌──┐ ┌──┐ 平均电压 ≈ 1.65V 3.3V ────────────┘ └──┘ └──┘ └─ LED 半亮 0V 占空比 90%(亮) ┌────┐┌────┐┌────┐ 平均电压 ≈ 2.97V 3.3V ───────────┘ └┘ └┘ └ LED 很亮 0V 开关频率足够快(如1kHz)时,人眼看到的是连续的亮度
# breathe.py — LED 呼吸灯
from gpiozero import PWMLED
from time import sleep

led = PWMLED(17)   # 支持 PWM 的 LED 对象

while True:
    # 渐亮:亮度从 0 到 1
    for i in range(0, 101):
        led.value = i / 100.0   # value ∈ [0.0, 1.0]
        sleep(0.01)
    # 渐暗
    for i in range(100, -1, -1):
        led.value = i / 100.0
        sleep(0.01)
# breathe2.py — gpiozero 内置 pulse 一行搞定呼吸效果
from gpiozero import PWMLED
from signal import pause

led = PWMLED(17)
led.pulse(fade_in_time=1, fade_out_time=1)  # 自动呼吸
pause()

底层库对比:gpiozero vs RPi.GPIO

了解底层库有助理解原理和排障。同样点灯,RPi.GPIO 需要更多样板代码:

# 用 RPi.GPIO 底层库点灯(仅 Pi 4 及以前;Pi 5 请用 lgpio)
import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)        # 用 BCM 编号
GPIO.setup(17, GPIO.OUT)      # 手动设为输出

try:
    while True:
        GPIO.output(17, GPIO.HIGH)  # 高电平
        time.sleep(1)
        GPIO.output(17, GPIO.LOW)   # 低电平
        time.sleep(1)
except KeyboardInterrupt:
    GPIO.cleanup()             # 必须手动清理引脚状态
特性gpiozero(推荐)RPi.GPIOlgpio
抽象层次高(对象化)
代码量极少多(样板代码)
Pi 5 支持(自动适配)
事件/消抖内置需手写需手写
适用绝大多数场景老项目/精细控制Pi 5 底层
调试:命令行直接查看引脚

不写代码也能查 GPIO 状态:pinout 显示完整引脚图和板子信息;gpioinfo(lgpio 工具)列出所有 GPIO 当前状态;raspi-gpio get 读取实时电平。接线没反应时先用这些命令确认引脚是否被占用、方向是否正确。

本章小结

GPIO 让树莓派能碰硬件。引脚有 BCM(芯片编号,如 GPIO17)和 BOARD(物理位置)两套编号,本教程统一用 BCM。三条铁律:GPIO 是 3.3V(绝不接 5V)、LED 必须串限流电阻(220~330Ω)、所有设备共地gpiozero 是官方推荐的高层库(系统预装、支持 Pi 5):LED(17) 点灯、Button(2) 读按键(内置上拉和消抖,用 when_pressed 事件回调最省 CPU)、PWMLED(17) 通过占空比调亮度做呼吸灯。底层库 RPi.GPIO 代码繁琐且不支持 Pi 5,Pi 5 底层用 lgpio——但优先永远选 gpiozero,它自动适配底层。调试用 pinout 看引脚图。下一章我们连接真实传感器,学习 I2C/SPI/1-Wire 三大总线。