3、GPIO基础控制:输出模式点亮LED、输入模式读取按键、PWM呼吸灯、软件防抖处理
GPIO,说白了就是芯片的“手脚”。你想让它输出高电平点亮LED,或者读取外部是高还是低,都得靠它。这一章我们就把GPIO的几种基本玩法过一遍,都是实战里天天用的东西。
本章核心知识点一览
- 输出模式:推挽输出点亮LED,控制电平高低
- 输入模式:读取按键状态,判断按下/释放
- PWM呼吸灯:用软件PWM模拟呼吸效果
- 软件防抖:消除按键机械抖动,避免误判
3.1 输出模式:点亮LED
输出模式是最简单的。你把GPIO设为推挽输出,然后写高电平,LED就亮了。写低电平,LED就灭了。就这么直接。
我个人习惯用树莓派或者Jetson Nano做实验,Python的RPi.GPIO或者Jetson.GPIO库都支持。先看代码:
import RPi.GPIO as GPIO
import time
LED_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT) # 设为输出模式
# 循环闪烁
while True:
GPIO.output(LED_PIN, GPIO.HIGH) # 点亮
time.sleep(0.5)
GPIO.output(LED_PIN, GPIO.LOW) # 熄灭
time.sleep(0.5)
这里有个坑——GPIO.setmode一定要在最前面调用。我刚开始学的时候,忘了设模式,结果LED死活不亮,查了半天才发现是模式没设对。嗯,这种低级错误谁都会犯。
小提示:推挽输出模式下,GPIO可以直接驱动小功率LED(串一个220Ω~1kΩ限流电阻)。如果是大功率负载,比如继电器、电机,必须加驱动电路,别直接怼GPIO上,会烧引脚的。
3.2 输入模式:读取按键
输入模式就是读引脚电平。按键按下,引脚接地(低电平);松开,引脚被上拉电阻拉到高电平。代码也很简单:
BUTTON_PIN = 17
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # 内部上拉
while True:
if GPIO.input(BUTTON_PIN) == GPIO.LOW:
print("按键按下")
else:
print("按键松开")
time.sleep(0.1)
为什么用内部上拉?省一个外部电阻啊。你想想看,如果每个按键都配一个10kΩ上拉电阻,板子面积和成本都上去了。内部上拉虽然弱一点(一般50kΩ左右),但对付机械按键足够了。
不过,直接这么读会有一个问题——抖动。按键是机械触点,按下和松开的时候会弹跳几次,电平会在高低之间快速跳变。如果不处理,一次按键可能被误判成好几次。
3.3 软件防抖处理
防抖的原理很简单:检测到电平变化后,等一段时间(通常10~20ms),再确认一次电平。如果还是那个状态,就认为是有效动作。
我常用的两种方法:
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 延时消抖 | 检测到变化后,delay 20ms再读一次 | 实现简单,代码直观 | 阻塞延时,影响其他任务 |
| 状态机消抖 | 连续采样N次,状态一致才确认 | 非阻塞,适合多任务 | 代码稍复杂 |
延时消抖示例:
def debounce_read(pin, delay_ms=20):
if GPIO.input(pin) == GPIO.LOW:
time.sleep(delay_ms / 1000.0)
if GPIO.input(pin) == GPIO.LOW:
return True # 确认按下
return False
状态机消抖示例(非阻塞):
class DebounceFSM:
def __init__(self, pin, stable_count=5):
self.pin = pin
self.stable_count = stable_count
self.count = 0
self.last_state = GPIO.HIGH
self.current_state = GPIO.HIGH
def update(self):
raw = GPIO.input(self.pin)
if raw == self.last_state:
self.count += 1
if self.count >= self.stable_count:
self.current_state = raw
else:
self.count = 0
self.last_state = raw
return self.current_state
注意:延时消抖虽然简单,但如果在中断服务函数里用time.sleep,会阻塞整个系统。我曾经在一个项目里这么干过,结果按键一按,整个UI都卡死了……后来改成状态机才解决。
3.4 PWM呼吸灯
PWM就是脉宽调制。通过改变高电平占空比,让LED看起来从暗到亮渐变。硬件PWM精度高,但很多GPIO不支持。没关系,我们用软件PWM模拟。
软件PWM的原理:在一个周期内,控制高电平的时间比例。比如周期20ms,高电平占10ms就是50%占空比。
import RPi.GPIO as GPIO
import time
LED_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)
def soft_pwm(pin, duty, period=0.02):
"""duty: 0~100"""
high_time = period * duty / 100.0
low_time = period - high_time
GPIO.output(pin, GPIO.HIGH)
time.sleep(high_time)
GPIO.output(pin, GPIO.LOW)
time.sleep(low_time)
# 呼吸效果:占空比从0渐变到100,再回到0
while True:
for duty in range(0, 101, 2): # 渐亮
soft_pwm(LED_PIN, duty)
for duty in range(100, -1, -2): # 渐暗
soft_pwm(LED_PIN, duty)
这里有个细节:time.sleep的精度有限,周期太短(比如1ms)会导致占空比不准。我一般把周期设在20ms左右,人眼看起来还是连续的。
优化建议:如果追求更平滑的呼吸效果,可以用指数曲线调整占空比变化率,而不是线性变化。人眼对亮度的感知是对数的,线性变化看起来会“中间跳变快,两端慢”。
3.5 综合实战:按键控制呼吸灯
把上面几个知识点串起来:按键按下时,LED开始呼吸;再按一次,LED停止呼吸。同时加入防抖处理。
import RPi.GPIO as GPIO
import time
LED_PIN = 18
BTN_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)
GPIO.setup(BTN_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
breathing = False
last_btn_state = GPIO.HIGH
def soft_pwm(pin, duty, period=0.02):
high_time = period * duty / 100.0
low_time = period - high_time
GPIO.output(pin, GPIO.HIGH)
time.sleep(high_time)
GPIO.output(pin, GPIO.LOW)
time.sleep(low_time)
def debounce(pin, delay_ms=20):
if GPIO.input(pin) == GPIO.LOW:
time.sleep(delay_ms / 1000.0)
return GPIO.input(pin) == GPIO.LOW
return False
try:
while True:
# 检测按键(带防抖)
if debounce(BTN_PIN):
if not breathing:
breathing = True
print("呼吸灯开启")
else:
breathing = False
GPIO.output(LED_PIN, GPIO.LOW)
print("呼吸灯关闭")
# 等待按键松开,避免重复触发
while GPIO.input(BTN_PIN) == GPIO.LOW:
time.sleep(0.01)
# 呼吸效果
if breathing:
for duty in range(0, 101, 2):
if not breathing:
break
soft_pwm(LED_PIN, duty)
for duty in range(100, -1, -2):
if not breathing:
break
soft_pwm(LED_PIN, duty)
except KeyboardInterrupt:
GPIO.cleanup()
这个例子虽然简单,但包含了输出、输入、PWM、防抖四个核心知识点。你想想看,实际项目里很多功能都是这些基础操作的组合。
本章要点回顾:
- 输出模式:
GPIO.setup(pin, GPIO.OUT)+GPIO.output(pin, level) - 输入模式:
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)+GPIO.input(pin) - 软件防抖:延时确认或状态机采样,避免机械抖动误判
- 软件PWM:通过控制高低电平时间比例模拟占空比,实现呼吸灯效果