4、GPIO控制与LED闪烁:用Python让硬件“呼吸”起来
各位同学,欢迎来到第四章。前面我们聊了怎么把传感器数据读进来,但嵌入式系统不光要“听”,还得会“说”。怎么“说”?最基础的方式,就是通过GPIO输出高低电平。
这一章,我们就拿一个最经典的例子——LED闪烁,来彻底搞懂GPIO输出。别小看这个实验,它背后藏着PWM(脉宽调制)的核心思想。你想想看,一个灯能亮能灭,那能不能让它半亮半灭?能不能像呼吸一样渐变?嗯,这就是PWM要解决的问题。
4.1 GPIO是什么?说白了就是“数字开关”
GPIO,全称General Purpose Input Output,通用输入输出口。你可以把它想象成一个数字引脚,要么输出高电平(3.3V或5V),要么输出低电平(0V)。
我刚开始学嵌入式时,总觉得GPIO很神秘。后来发现,它就是个受软件控制的开关。你写个1,它就输出高电平;写个0,它就输出低电平。就这么简单。
核心概念:
- 高电平(HIGH/1):通常对应3.3V或5V,LED亮
- 低电平(LOW/0):对应0V,LED灭
- 输出模式:引脚作为“电源”使用,驱动外部设备
- 输入模式:引脚作为“检测”使用,读取外部信号
这里要注意,GPIO的输出电流非常有限,一般只有几毫安到几十毫安。直接驱动大功率设备?不行,会烧引脚。我在项目中就吃过这个亏,驱动一个继电器时忘了加三极管放大,结果引脚直接冒烟了……
4.2 硬件连接:LED和电阻是“铁搭档”
在动手写代码前,先把硬件搭好。你需要:
- 一块开发板(树莓派、ESP32、STM32都行,原理一样)
- 一个LED灯(颜色随意,我习惯用红色,醒目)
- 一个220Ω电阻(这个很重要,别省!)
- 几根杜邦线
连接方式很简单:GPIO引脚 → 电阻 → LED正极(长脚) → LED负极(短脚) → GND。
警告:LED必须串联限流电阻!我曾经见过有人直接把LED接到3.3V上,结果LED瞬间烧毁,还冒出一股焦味。220Ω到1kΩ都可以,阻值越小越亮,但电流也越大。
为什么一定要加电阻?因为LED是电流型器件,它的内阻很小。不加电阻的话,电流会大到烧坏LED甚至GPIO引脚。你想想看,3.3V直接加在LED上,电流可能达到几十毫安,而GPIO最大输出也就十几毫安。嗯,这就是物理定律,绕不过去的。
4.3 Python控制GPIO:从点亮到闪烁
好了,硬件接好了,我们开始写Python代码。这里以树莓派为例,使用RPi.GPIO库。其他平台(如ESP32用MicroPython)语法类似,只是库名不同。
第一步:点亮LED
import RPi.GPIO as GPIO
import time
# 设置GPIO模式为BCM编号
GPIO.setmode(GPIO.BCM)
# 设置GPIO18为输出模式
LED_PIN = 18
GPIO.setup(LED_PIN, GPIO.OUT)
# 输出高电平,点亮LED
GPIO.output(LED_PIN, GPIO.HIGH)
# 保持5秒
time.sleep(5)
# 清理GPIO资源
GPIO.cleanup()
这段代码做了三件事:设置引脚模式、输出电平、最后清理。我个人习惯在程序结束时总是调用GPIO.cleanup(),否则下次运行可能会报错。
第二步:让LED闪烁
闪烁的本质就是“亮-灭-亮-灭”的循环。我们用while循环加上延时来实现:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
LED_PIN = 18
GPIO.setup(LED_PIN, GPIO.OUT)
try:
while True:
GPIO.output(LED_PIN, GPIO.HIGH) # 亮
time.sleep(0.5) # 保持0.5秒
GPIO.output(LED_PIN, GPIO.LOW) # 灭
time.sleep(0.5) # 保持0.5秒
except KeyboardInterrupt:
# 按Ctrl+C退出
GPIO.cleanup()
这里用了try-except结构,确保程序被中断时能正确清理GPIO。你想想看,如果程序异常退出,GPIO引脚可能一直保持高电平,LED就一直亮着。嗯,这虽然不会烧坏硬件,但总归不专业。
4.4 从闪烁到呼吸:理解PWM原理
现在LED能闪烁了,但只有“全亮”和“全灭”两种状态。能不能让LED半亮?或者像呼吸一样慢慢变亮再慢慢变暗?
答案是:用PWM(Pulse Width Modulation,脉宽调制)。
PWM的原理很简单:在一个周期内,让高电平的时间占比(占空比)变化。占空比50%,就是一半时间亮一半时间灭,人眼看起来就是半亮。占空比越高,看起来越亮。
PWM关键参数:
| 参数 | 说明 | 典型值 |
|---|---|---|
| 频率 | 每秒的周期数 | 1000Hz(1kHz) |
| 周期 | 一个完整波形的时间 | 1ms(频率的倒数) |
| 占空比 | 高电平时间占周期的比例 | 0%~100% |
为什么人眼能看到“半亮”?因为PWM的频率足够高(通常>50Hz),人眼无法分辨闪烁,只能感受到平均亮度。这就像电影一样,24帧/秒的画面在人眼里就是连续的。
我在项目中用PWM控制过舵机、电机调速、LED调光。说白了,PWM就是把数字信号“模拟化”的一种手段。你想想看,一个只有0和1的引脚,通过改变时间比例,就能实现模拟效果,是不是很巧妙?
4.5 Python实现PWM呼吸灯
树莓派的RPi.GPIO库自带PWM功能,用起来非常方便:
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
LED_PIN = 18
GPIO.setup(LED_PIN, GPIO.OUT)
# 创建PWM对象,频率1000Hz
pwm = GPIO.PWM(LED_PIN, 1000)
pwm.start(0) # 初始占空比0%
try:
while True:
# 从暗到亮
for duty in range(0, 101, 1):
pwm.ChangeDutyCycle(duty)
time.sleep(0.01)
# 从亮到暗
for duty in range(100, -1, -1):
pwm.ChangeDutyCycle(duty)
time.sleep(0.01)
except KeyboardInterrupt:
pwm.stop()
GPIO.cleanup()
这段代码实现了呼吸灯效果。核心就是不断改变占空比,从0%到100%再回到0%。每次变化间隔10ms,整个呼吸周期大约2秒。
小技巧:你可以调整time.sleep(0.01)的值来控制呼吸速度。数值越小,呼吸越快。我个人喜欢0.02,看起来更柔和。
这里要注意,PWM的频率不能太低。如果频率低于50Hz,人眼就能看到闪烁,那就不是“呼吸”而是“抽风”了。我一般用1000Hz,既平滑又不会产生可闻噪声。
4.6 避坑指南:GPIO控制常见问题
做GPIO控制时,有几个坑我踩过,分享给大家:
- 引脚编号搞混:树莓派有BOARD(物理引脚编号)和BCM(芯片引脚编号)两种模式。我建议统一用BCM,因为不同型号的树莓派物理引脚布局可能不同,但BCM编号是固定的。
- 忘记清理GPIO:程序退出后,GPIO可能保持最后的状态。下次运行时如果引脚被占用,会报错。记得用try-finally或with语句确保清理。
- 电流超限:单个GPIO引脚最大输出电流约16mA,所有引脚总和不超过50mA。驱动多个LED时,建议用三极管或ULN2003驱动芯片。
- 电平不匹配:3.3V的GPIO直接驱动5V的设备可能无法正常工作。需要电平转换电路,或者用MOS管做电平匹配。
我曾经在一个项目中,用树莓派直接驱动5V的继电器模块,结果继电器死活不动作。查了半天才发现,树莓派的3.3V高电平对继电器来说太低了,需要加一个NPN三极管做电平转换。嗯,从那以后,我每次接新设备都会先查数据手册,确认电平匹配。
4.7 本章小结
这一章我们做了三件事:
- 用GPIO输出高低电平,控制LED亮灭
- 用循环和延时实现LED闪烁
- 用PWM实现呼吸灯效果,理解占空比的概念
PWM是嵌入式系统里非常重要的技术。除了控制LED,它还广泛应用于电机调速、舵机控制、音频输出、电源管理等领域。你想想看,一个简单的“开关”信号,通过改变时间比例,就能实现如此丰富的功能——这就是数字世界的魅力。
下一章,我们会用PWM来做更有趣的事情:控制舵机转动角度。到时候你会发现,PWM的占空比和舵机角度之间,其实有一个精确的对应关系。嗯,敬请期待。