上一节看清了那排针,这一节要学会指挥它们。
Python 控制 GPIO 有好几套库,而且换代了——老教程里那句人人都在抄的 import RPi.GPIO,在新板子上会直接报错。本节把"该用哪个、为什么、报错了怎么办"一次讲清。
6-5 你学过"模块就是一个 .py 文件"、学过 import 的几种写法,还学过装库的两条正路(apt 与 venv);本节是那些知识第一次用在硬件上。
上一节讲的"BCM 编号"从这里开始变成代码里的数字:LED(17) 里的这个 17 就是 BCM 号。
4-3 讲的"用户与组"会在本节末尾回来——GPIO 设备文件不是谁都能碰的。
一、三代库:为什么会有"该用哪个"的问题
Python 控制 GPIO 这件事,历史上出现过三代做法,现在三代都还活着:
| 代 | 名字 | 出现时间 | 它怎么工作 | 现在还用吗 |
|---|---|---|---|---|
| 第一代 | RPi.GPIO | 2012 | 直接读写主芯片的内存寄存器 | ⚠️ 在新板子上失效(见第二节) |
| 第二代 | gpiozero | 2015 | 把一根针包装成"设备对象",底层外包 | ✅ 官方推荐,新项目首选 |
| 底层 | lgpio / libgpiod | 近年 | 走内核提供的通用 GPIO 接口 | ✅ 高级用法/底层工具 |
RPi.GPIO 像给你一把螺丝刀:告诉你"一号针的寄存器第 3 位是方向位,写 1 是输出"——你得自己拧。
gpiozero 像给你一盏已经装好的灯:你只要说 led.on(),灯亮;led.off(),灯灭。它自己知道该怎么拧那根螺丝。
这也解释了名字:zero 就是"零基础"——这个库是专门为初学者(和树莓派 Zero 那种教学场景)设计的。
二、⚠️ 本节最大的坑:RPi.GPIO 在新板子上用不了了
网上绝大多数中文树莓派教程的开头都是这两行:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
在老板子(Pi 4 及以前)上没问题。但在这台新机器上,它会报错:
RuntimeError: Cannot determine SOC peripheral base address
为什么?因为 Pi 5 换了新的 I/O 芯片(RP1)。
以前 GPIO 的寄存器长在主芯片自己身上,RPi.GPIO 直接去读那块内存就能操作针脚。Pi 5 把 GPIO 交给了一颗专门的外围芯片 RP1 管——主芯片不再直接连针脚了,RPi.GPIO 按老地址去找,扑了个空,于是报"找不到外设基地址"。
- 写新代码 → 用 gpiozero(本节和后面五节全部用它)。这是官方推荐,也是本课统一的选择。
- 手上有一段老的
RPi.GPIO代码,不想改 → 装一个叫rpi-lgpio的包(sudo apt install python3-rpi-lgpio)。它顶上RPi.GPIO的名字,让老代码原封不动跑起来(import RPi.GPIO照写),但内部走的是新接口。 - 要最大控制力/要性能 → 直接用
lgpio或libgpiod(代码会长很多,新手不必)。
记住这个判断原则:凡是你看到教程里出现 GPIO.setmode、GPIO.setup、GPIO.cleanup,就知道这是老一代写法。它在老板子上能用,在新板子上要么换成 gpiozero,要么先装 rpi-lgpio。
三、gpiozero 好在哪:从"五步"到"一行"
同一件事——让 BCM 17 这根针变高,两代写法对比:
老一代(RPi.GPIO)要五步:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM) # ① 声明用哪套编号
GPIO.setup(17, GPIO.OUT) # ② 把这根针设成输出
GPIO.output(17, GPIO.HIGH) # ③ 拉高
GPIO.output(17, GPIO.LOW) # ④ 拉低
GPIO.cleanup() # ⑤ 收拾现场(不写会留一堆针在占用状态)
gpiozero 只要一行:
from gpiozero import LED
led = LED(17) # 建立对象=设成输出并在用完后自动收拾
led.on() # 拉高(LED 亮)
led.off() # 拉低(LED 灭)
省掉的不是"打字量",而是三类出错机会:
- 不用声明编号体系——
LED(17)里的 17 一律是 BCM。 - 不用记得
cleanup()——对象不用了会自动释放针(也可以显式led.close())。 - 不用自己管"这根针是什么模式"——你说"这是 LED",它就知道该设输出。
3.1 本章后面要用的"设备类"
gpiozero 的用法就是"先想清楚你接的是什么设备,再选对应的类":
| 类名 | 它是干什么的 | 本章/本课哪里用 |
|---|---|---|
LED |
一颗灯:亮 / 灭 | 7-3 |
PWMLED |
一颗灯:能调亮度 | 7-4 |
RGBLED |
三色灯:能调颜色 | 7-4 |
Buzzer |
蜂鸣器:响 / 不响 | 练习用 |
Button |
按钮:按下 / 松开 | 7-3 |
MotionSensor |
人体红外感应模块 | 练习用 |
LightSensor |
光敏元件 | 7-5 |
MCP3008 / MCP3208 |
ADC 芯片:把模拟信号读成数字 | 7-5 |
看这张表就能明白 gpiozero 的设计哲学:它按"你手上有件什么设备"来组织 API,而不是按"你要操作哪个寄存器"。这才是零基础能上手的原因。
3.2 引脚还能写名字
LED(17) 这种写法里的整数一律按 BCM 解释。除此之外还能写字符串:
led = LED("BOARD11") # 明确按物理针号
led = LED("GPIO17") # 明确按 BCM(和写 17 等价)
什么时候用:你照着一张只在针脚上标"11"的接线图接线时,写 "BOARD11" 比把它翻译成 17 更不容易错。
什么时候别用:本章统一写整数 BCM,因为这是社区通用写法,看别人的代码不用二次翻译。
四、pin factory:gpiozero 是怎么"隔空"指挥硬件的
这是 gpiozero 最聪明的一处设计,值得单独看。
gpiozero 自己一点硬件都不碰。它把"真正去操作针脚"这件事,外包给一个叫 pin factory(引脚工厂)的部件。
这带来两个好处:
- 它能跨所有树莓派型号(从 Pi 1 到 Pi 5),因为它不写死任何寄存器地址——它只跟"工厂"打交道。
- 它能在没有硬件的地方测试(用假工厂),也能换底层实现而不改你的代码。
在你这台机器上,它会自动选对工厂,你不用配置。想确认它选了哪个,一条命令:
python3 -c "import gpiozero; print(gpiozero.Device.pin_factory)"
正常输出:<gpiozero.pins.lgpio.LGPIOFactory object at 0x7f...>
读结果:看到 LGPIOFactory 就对了——这说明它认出了新硬件,正在用新的 lgpio 后端。如果看到 MockFactory,说明它没找到真实后端(那时你的代码跑得起来,但针一点反应都没有——这是最阴的一类问题)。
想手动指定?不用改代码,加个环境变量就行:
GPIOZERO_PIN_FACTORY=lgpio python3 my_script.py
五、权限:你会遇到的第一个报错
第一次跑 GPIO 程序,很可能撞上这个:
PermissionError: [Errno 13] Permission denied: '/dev/gpiochip0'
原因:控制针脚的设备文件(/dev/gpiochip0,上一节 gpiodetect 见过它)属于 root 用户和一个叫 gpio 的组。4-3 讲过 Linux 的权限模型——你的普通用户不在这个组里,就打不开它。
解法(两步):
sudo usermod -aG gpio $USER
sudo reboot
注意两件事:
-aG那个a不能省(4-3 讲过:写-G会把你已有的组全踢掉)。- 必须重启,注销再登录不够——设备文件的权限是开机时定的。
一是危险:你的脚本一旦有机会写错文件路径(比如那句 rm),root 权限下就没有"删不掉"这一说;
二是会留下麻烦:脚本生成的日志、写出的文件都变成 root 属主,以后普通用户改不动;
三是掩盖问题:真正的原因是"你的用户不在 gpio 组",修它才是正路。
正确习惯:系统配置用 sudo,跑你自己的程序用普通用户。
六、一根针同一时间只能有一个"主人"
这是新接口相比老 RPi.GPIO 的一处重要变化(而且是个好变化):
- 老
RPi.GPIO:两个程序同时抢一根针,不报错,但两个程序互相覆盖对方的状态——现象是"灯莫名其妙乱闪"。 - 新接口:第二个来抢的会被直接拒绝,报错告诉你这根针已被占用。
所以你可能会看到:上一个测试脚本没关掉、还挂在后台,新脚本一跑就报"针被占用"。
排错两招:
ps aux | grep python3 # 看看是不是还有别的 python 脚本在跑
或者重启一次(最省事)。养成习惯:测试脚本用 Ctrl + C 结束,别留着。
七、动手 Demo:库与后端的体检(不接线)
这一步不接任何线,只验证"软件这条路是通的"。(点亮 LED 留在 7-3,因为那一步开始就必须接线了。)
第 1 步 · 确认库在不在,什么版本
dpkg -s python3-gpiozero | grep -E "Package|Version"
你会看到什么:类似 Package: python3-gpiozero + Version: 2.x.x-...。
读结果:有 Version 就说明预装了(这套查法是 4-5 学的 dpkg 用法)。这条命令也顺便告诉你:为什么不用 pip——gpiozero 是系统自带的包。
第 2 步 · 看它选了哪个引脚工厂
python3 -c "import gpiozero; print(gpiozero.Device.pin_factory)"
你会看到什么:<gpiozero.pins.lgpio.LGPIOFactory object at 0x...>(内存地址会不同)。
第 3 步 · 写第一个 GPIO 脚本
nano gpio_hello.py
内容:
from gpiozero import Device, LED
print("引脚工厂:", Device.pin_factory)
led = LED(17) # BCM 17 = 物理针 11
print("对象已建立:BCM 17(物理针 11)")
led.on()
print("已拉高,读回:", led.value)
led.off()
print("已拉低,读回:", led.value)
led.close()
print("针已释放")
跑:
python3 gpio_hello.py
你会看到什么算成功:
引脚工厂: <gpiozero.pins.lgpio.LGPIOFactory object at 0x7f8a...>
对象已建立:BCM 17(物理针 11)
已拉高,读回: 1.0
已拉低,读回: 0.0
针已释放
它读的是这根针的驱动状态("我现在把它推到高还是低"),不是"外接电路上的电压"。 所以这两个数字能证明你的代码确实指挥到了这根针——但它不能告诉你"灯亮了没有"。灯亮不亮还要看接线对不对、电阻有没有串上、LED 极性有没有反。 要眼见为实,就得接一颗 LED 上去看。下一节就干这件事。
光跑脚本不报错,只能说明软件这条路通了。针上到底有没有电压、有没有电流回路,必须靠外接的元件来确认。 新手常犯的一个错:脚本跑通就以为"硬件没问题",然后接线接错了半天找不到原因。软件通过 ≠ 硬件通过,这两件事要分开验。
第 4 步 · 顺手做个"针被占用"实验
再写一个小脚本,让它占住这根针不放:
nano gpio_hold.py
内容:
from gpiozero import LED
import time
led = LED(17)
print("已占住 BCM 17,60 秒内别的程序抢不到")
time.sleep(60)
先让它跑到后台占住针,然后立刻跑上一个脚本:
python3 gpio_hold.py &
python3 gpio_hello.py
你会看到什么:gpio_hello.py 大概率报错,错误里带 PinInUse 或 GPIO busy 字样(不同版本的措辞略有不同)。
读结果:这就是第六节讲的现象——一根针只能有一个主人。看到这个报错,说明你理解了;然后把那个后台脚本收掉:
kill %1
(%1 是"最近那个后台任务"的编号,4-1 讲过作业控制。也可以用 ps aux | grep gpio_hold 找到进程号再 kill。)
八、30 秒实验:一条命令看清三层
python3 -c "
import gpiozero, platform
print('gpiozero 版本:', getattr(gpiozero, '__version__', '未知'))
print('引脚工厂:', gpiozero.Device.pin_factory)
print('机器:', platform.machine())
"
你会看到什么:三行信息——库的版本、工厂类名、CPU 架构。
读结果:这里的"三层"要分清——最上面是你的代码(from gpiozero import LED),中间是gpiozero 库,最下面是工厂 + 内核接口。本节讲的所有"换代",换的其实都是中间和下面这两层,你的代码只写最上面那一层。这就是为什么换个库比换个板子容易得多。
九、常见误区
- "网上教程都用
RPi.GPIO,我也照抄" —— 在老板子上可以。在 Pi 5 上会直接报Cannot determine SOC peripheral base address;要么改用 gpiozero,要么先装python3-rpi-lgpio顶上去。 - "gpiozero 只是
RPi.GPIO的新名字" —— 不是。它是完全不同的一层:RPi.GPIO 是"操作针",gpiozero 是"操作设备"。从GPIO.output(17, 1)变成led.on()不是换了写法,是换了思路。 - "跑 GPIO 程序必须
sudo" —— 如果必须sudo才能跑,说明你的用户没在gpio组里。正确做法是加组 + 重启,不是一直用 root 跑自己的脚本。 - "
led.value读回 0 就是针上真的 0V" —— 没有接线的针是浮空的,读回来的值不可靠。要验证输出,就接一颗 LED 上去看。 - "装了库就完事了" —— 库只是中间层。针不动的时候,往下要查工厂选对没有(是不是
LGPIOFactory)、用户权限(在不在gpio组)、针是否被别的程序占着。按"代码 → 库 → 工厂 → 内核 → 硬件"这个顺序往下查,比乱改代码快得多。
自测三问(点开对答案)
1. 为什么 `RPi.GPIO` 在 Pi 5 上会失效,而在 Pi 4 上好好的? 2. `led = LED(17)` 里的 17,指的是物理针 17 吗? 3. 你的程序报 `PermissionError: [Errno 13] Permission denied: '/dev/gpiochip0'`,最正确的处理是什么? **参考答案** 1. 因为 **Pi 5 换了新的 I/O 芯片(RP1)**:GPIO 不再挂在主芯片上,而 `RPi.GPIO` 是**直接去读主芯片的寄存器地址**的,地址变了它自然找不到(报 `Cannot determine SOC peripheral base address`)。**根本原因是它用了"直接访问内存"这种跟硬件绑得太紧的方式**——这也是官方推荐新代码用 gpiozero 的原因:gpiozero 通过工厂层间接访问,不写死任何地址。 2. **不是。**`17` 是 **BCM 号**,对应的物理针是 **11**。想按物理针号写,得明确写成字符串 `LED("BOARD11")`。(这就是 7-1 讲"三套编号"的原因——**写代码的号和插线的位置,是两个概念。**) 3. **把自己的用户加进 `gpio` 组然后重启**:`sudo usermod -aG gpio $USER` + `sudo reboot`。**不要用 `sudo python3` 绕过去**——那会让脚本带着最高权限跑,还可能留下 root 属主的文件,而且把真正的问题(权限没配好)藏起来了。- ☐ 下一节(7-3 数字输出与输入) —— 真正接线,让 LED 亮起来、让按钮有反应
- ☑ 本章第二节已读 —— 认清三代库的关系,知道
RPi.GPIO为什么在新板子上失效
相关笔记
- 认识树莓派的 GPIO 引脚 —— 上一节:40 针构成与 BCM 编号,
LED(17)里那个 17 的出处 - Python 函数与模块 ——
from gpiozero import LED这种写法、以及装库的两条正路,都在那里 - Linux 的用户与文件权限指令 —— 本节权限报错的根因:组、设备文件、
usermod -aG - 使用命令行安装和卸载应用程序 ——
dpkg -s与apt install python3-rpi-lgpio的用法 - 🍓 树莓派 · 索引 —— 全课路线图