GPIO 基础控制:从点亮一颗 LED 开始
各位同学,欢迎来到第二章。这一章我们不讲虚的,直接上手操作。你想想看,嵌入式开发里最基础、最直观的反馈是什么?就是让一个灯亮起来。今天我们就用树莓派的 GPIO,把这件事彻底搞明白。
我个人习惯,学任何新平台,第一件事就是控制 GPIO。这就像学编程先写 "Hello World" 一样,是建立信心的第一步。而且,GPIO 控制背后涉及到的寄存器操作、电平逻辑,是所有外设驱动的基础。搞懂了它,后面电机、传感器、通信协议,你都会觉得顺理成章。
RPi.GPIO 库:你的 GPIO 操作瑞士军刀
树莓派上操作 GPIO,最常用的就是 RPi.GPIO 这个 Python 库。它把底层的寄存器操作封装成了几个简单的函数。你不需要去翻 BCM2835 的数据手册,也能把引脚玩得飞起。
但注意,封装归封装,底层原理你得懂。我在项目中遇到过好几次,有人因为不理解 GPIO 的电气特性,直接把 5V 信号怼到 3.3V 的引脚上,结果就是——冒烟。嗯,所以咱们既要会用库,也要知道它在背后干了什么。
核心函数一览
RPi.GPIO 库的核心函数就那么几个,我列个表,你一眼就能看明白:
| 函数 | 作用 | 我的经验 |
|---|---|---|
setmode() |
设置引脚编号模式 | 我习惯用 BCM,因为和芯片引脚对应,查手册方便 |
setup() |
配置引脚方向(输入/输出) | 别忘了设初始电平,否则引脚可能处于不确定状态 |
output() |
输出高/低电平 | 这就是让 LED 亮灭的关键 |
input() |
读取引脚电平 | 读取按键、传感器信号就靠它 |
setmode():先定规矩
树莓派的 GPIO 引脚有两种编号方式:BOARD 和 BCM。
- BOARD:按物理引脚位置编号,从 1 到 40。好处是,你拿着板子数引脚就行,不用记芯片型号。
- BCM:按博通芯片的 GPIO 编号,比如 GPIO17、GPIO18。好处是,和芯片数据手册一致,写驱动时更直观。
我个人习惯用 BCM。为什么?因为我在做项目时,经常需要对照芯片手册查复用功能。用 BCM 编号,一眼就能找到对应的寄存器。当然,如果你只是搭个简单的电路,用 BOARD 也没问题。关键是——统一。别在一个脚本里混用,否则你会疯掉。
重要提醒: setmode() 必须在所有 GPIO 操作之前调用。而且,一个脚本里只能调用一次。如果你不小心调了两次,程序会报错。我曾经在调试时犯过这个错,找了半天才发现是重复调用了 setmode()。
setup():给引脚定方向
每个 GPIO 引脚,要么是输入,要么是输出。你不能让它同时干两件事。这就好比一个人,不能同时当司机和乘客。
setup(channel, direction, initial) 这个函数,就是用来干这个的。
channel:引脚编号,根据你设定的模式来。direction:GPIO.OUT或GPIO.IN。initial:可选参数,设置初始电平。比如initial=GPIO.HIGH。
这里有个坑,我必须要说。如果你把引脚设为输出,但没设初始电平,那这个引脚在程序启动时可能处于一个不确定的状态。有时候是高,有时候是低,完全看运气。所以,我建议你每次 setup() 时,都明确指定 initial 参数。养成好习惯,能省去很多排查时间。
output() 与 input():真正的读写操作
这两个函数,就是 GPIO 的核心操作了。
output(channel, state):让引脚输出高电平(GPIO.HIGH)或低电平(GPIO.LOW)。input(channel):读取引脚当前的电平状态,返回GPIO.HIGH或GPIO.LOW。
你可能会问:高电平和低电平到底是多少伏?
树莓派的 GPIO 是 3.3V 逻辑电平。也就是说,GPIO.HIGH 对应 3.3V,GPIO.LOW 对应 0V。注意,不是 5V!如果你需要驱动 5V 的设备,必须加电平转换电路。我曾经见过有人直接把 5V 的舵机信号线接到 GPIO 上,结果树莓派的 GPIO 引脚直接烧了。嗯,血的教训。
GPIO 高低电平控制原理
说白了,GPIO 输出高低电平,就是控制引脚内部的 MOSFET 开关,让它连接到 VCC(3.3V)或者 GND(0V)。
我画了一张图,帮你理解这个过程:
当你调用 output(17, GPIO.HIGH) 时,树莓派内部的寄存器会被设置,让 GPIO17 这个引脚内部的 PMOS 导通,把引脚拉到 3.3V。电流从引脚流出,经过 LED 和限流电阻,流回 GND。LED 就亮了。
当你调用 output(17, GPIO.LOW) 时,NMOS 导通,引脚被拉到 GND。LED 两端没有电压差,自然就灭了。
小提示: 树莓派的 GPIO 输出电流有限,每个引脚最大只能提供 16mA。驱动 LED 没问题,但驱动电机、继电器这类大电流设备,必须加驱动电路(比如 ULN2003、L298N 或 MOSFET)。
实战:点亮一颗 LED
理论说完了,咱们来点实际的。下面这个例子,就是让 LED 亮 2 秒,然后灭 2 秒,循环 5 次。
import RPi.GPIO as GPIO
import time
# 设置引脚编号模式
GPIO.setmode(GPIO.BCM)
# 定义 LED 引脚
LED_PIN = 17
# 设置引脚为输出,初始电平为低
GPIO.setup(LED_PIN, GPIO.OUT, initial=GPIO.LOW)
print("LED 开始闪烁...")
# 循环 5 次
for i in range(5):
GPIO.output(LED_PIN, GPIO.HIGH) # 点亮
time.sleep(2) # 保持 2 秒
GPIO.output(LED_PIN, GPIO.LOW) # 熄灭
time.sleep(2) # 保持 2 秒
print(f"第 {i+1} 次闪烁完成")
# 清理 GPIO 资源
GPIO.cleanup()
print("程序结束,GPIO 已清理")
这段代码很简单,但有几个细节我要强调:
- GPIO.cleanup():程序结束时一定要调用。它会释放 GPIO 资源,把引脚恢复到默认状态。否则,下次运行程序时,引脚可能还是上次的状态,导致奇怪的问题。
- 限流电阻:LED 不能直接接在 GPIO 引脚和 GND 之间。必须串联一个 220Ω ~ 1kΩ 的电阻,否则电流过大,会烧坏 LED 甚至 GPIO 引脚。
- 接线:LED 的正极(长脚)接 GPIO 引脚,负极(短脚)通过电阻接 GND。别接反了。
警告: 千万不要把 GPIO 引脚直接接到 5V 电源上!树莓派的 GPIO 是 3.3V 逻辑,耐压只有 3.3V。接到 5V 上,芯片会立即损坏。我曾经在项目中因为误操作烧过一块树莓派,那感觉,就像看着自己的钱包被烧了一样心疼。
GPIO 输入:读取按键状态
输出搞定了,咱们再看看输入。读取按键状态,是 GPIO 输入的经典应用。
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
BUTTON_PIN = 18
LED_PIN = 17
# 设置按键引脚为输入,启用内部上拉电阻
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(LED_PIN, GPIO.OUT, initial=GPIO.LOW)
print("按下按键,LED 会亮起")
try:
while True:
# 读取按键状态
button_state = GPIO.input(BUTTON_PIN)
# 按键按下时,引脚为低电平(因为上拉电阻)
if button_state == GPIO.LOW:
GPIO.output(LED_PIN, GPIO.HIGH)
print("按键按下,LED 亮")
else:
GPIO.output(LED_PIN, GPIO.LOW)
time.sleep(0.1) # 防抖
except KeyboardInterrupt:
print("程序被用户中断")
finally:
GPIO.cleanup()
这里有个关键点:上拉电阻。当按键没有按下时,GPIO 引脚处于悬空状态,电平不确定。启用内部上拉电阻后,引脚被拉到 3.3V,读取为高电平。按键按下时,引脚被拉到 GND,读取为低电平。
你可能会问:为什么不用下拉电阻?当然也可以。但树莓派内部只有上拉电阻,没有下拉。所以,用上拉是更简单的方案。
避坑指南: 我曾经在读取按键时,没有加防抖处理。结果每次按下按键,程序会误判为多次触发。后来加了一个 10ms 的延时,问题就解决了。嗯,这就是所谓的「按键抖动」,是硬件设计中的常见问题。
本章小结
这一章,我们搞定了 GPIO 最核心的操作:输出和输入。你学会了:
- 用
setmode()选择引脚编号模式 - 用
setup()配置引脚方向 - 用
output()控制 LED 亮灭 - 用
input()读取按键状态 - 理解了 GPIO 高低电平的硬件原理
这些知识,是后面所有驱动开发的基础。不管是电机控制,还是 UART 通信,本质上都是在操作 GPIO。只不过,通信协议把简单的电平变化,组合成了有意义的时序。
好了,拿起你的树莓派,动手试试吧。让那个小小的 LED 亮起来,你会感受到嵌入式开发的乐趣。