在树莓派的世界里,点亮第一个LED灯就像是打开了一扇通往物理计算的大门。但如果你已经掌握了基础的点灯技巧,可能会开始思考:除了简单的闪烁,还能用这些小小的发光二极管做些什么更有趣的事情?本文将带你超越基础,探索如何利用树莓派的GPIO和Python编程实现两个经典项目——交通灯模拟和呼吸灯效果。
在开始任何硬件项目之前,充分的准备工作是成功的关键。对于树莓派灯光项目,我们需要确保硬件和软件环境都配置妥当。
首先,让我们检查一下完成这两个项目所需的硬件组件:
提示:使用不同颜色的LED可以增强项目的视觉效果,交通灯项目建议使用红、黄、绿三色LED。
连接电路时,务必注意以下几点:
典型的连接方式如下表所示:
| LED颜色 | GPIO引脚(BCM编号) | 面包板连接 |
|---|---|---|
| 红色 | GPIO17 | 正极 |
| 黄色 | GPIO27 | 正极 |
| 绿色 | GPIO22 | 正极 |
| 公共端 | GND | 所有LED负极 |
树莓派操作系统通常已经预装了Python和RPi.GPIO库,但我们还是应该确认一下:
bash复制# 检查Python版本
python3 --version
# 检查RPi.GPIO是否安装
python3 -c "import RPi.GPIO; print(RPi.GPIO.VERSION)"
如果尚未安装RPi.GPIO库,可以通过以下命令安装:
bash复制sudo apt update
sudo apt install python3-rpi.gpio
对于更现代的树莓派系统,也可以考虑使用gpiozero库,它提供了更高层次的抽象:
bash复制sudo apt install python3-gpiozero
交通灯是我们日常生活中最常见的灯光系统之一,用树莓派模拟交通灯不仅能学习GPIO控制,还能理解状态机的编程概念。
一个标准的交通灯循环通常遵循以下序列:
我们可以用Python代码实现这个逻辑:
python复制import RPi.GPIO as GPIO
import time
# 设置引脚编号模式
GPIO.setmode(GPIO.BCM)
# 定义LED引脚
RED = 17
YELLOW = 27
GREEN = 22
# 初始化GPIO
GPIO.setup(RED, GPIO.OUT)
GPIO.setup(YELLOW, GPIO.OUT)
GPIO.setup(GREEN, GPIO.OUT)
try:
while True:
# 红灯亮10秒
GPIO.output(RED, GPIO.HIGH)
GPIO.output(YELLOW, GPIO.LOW)
GPIO.output(GREEN, GPIO.LOW)
time.sleep(10)
# 绿灯亮8秒
GPIO.output(RED, GPIO.LOW)
GPIO.output(GREEN, GPIO.HIGH)
time.sleep(8)
# 黄灯闪烁2秒
for _ in range(4):
GPIO.output(YELLOW, GPIO.HIGH)
time.sleep(0.25)
GPIO.output(YELLOW, GPIO.LOW)
time.sleep(0.25)
except KeyboardInterrupt:
GPIO.cleanup()
基础版本虽然能工作,但我们可以做得更好。下面是一个更灵活的交通灯实现,允许自定义各状态持续时间:
python复制import RPi.GPIO as GPIO
import time
from dataclasses import dataclass
@dataclass
class TrafficLightTiming:
red: float
green: float
yellow_blinks: int
blink_interval: float = 0.25
def setup_gpio():
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT) # 红
GPIO.setup(27, GPIO.OUT) # 黄
GPIO.setup(22, GPIO.OUT) # 绿
def run_traffic_light(timing: TrafficLightTiming):
try:
while True:
# 红灯阶段
GPIO.output(17, GPIO.HIGH)
GPIO.output(27, GPIO.LOW)
GPIO.output(22, GPIO.LOW)
time.sleep(timing.red)
# 绿灯阶段
GPIO.output(17, GPIO.LOW)
GPIO.output(22, GPIO.HIGH)
time.sleep(timing.green)
# 黄灯闪烁阶段
GPIO.output(22, GPIO.LOW)
for _ in range(timing.yellow_blinks):
GPIO.output(27, GPIO.HIGH)
time.sleep(timing.blink_interval)
GPIO.output(27, GPIO.LOW)
time.sleep(timing.blink_interval)
except KeyboardInterrupt:
GPIO.cleanup()
if __name__ == "__main__":
# 可自定义的时间配置
timing = TrafficLightTiming(
red=10.0, # 红灯10秒
green=8.0, # 绿灯8秒
yellow_blinks=4 # 黄灯闪烁4次
)
setup_gpio()
run_traffic_light(timing)
这个改进版本使用了Python的dataclass来组织配置参数,使代码更清晰、更易于维护和修改。
呼吸灯效果是指LED的亮度平滑地从暗变亮,再从亮变暗,如同呼吸一般。这种效果需要使用PWM(脉冲宽度调制)技术。
PWM通过快速开关GPIO引脚来控制平均电压,从而调节LED亮度。关键参数包括:
树莓派的RPi.GPIO库提供了PWM功能:
python复制import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
# 创建PWM实例,频率为100Hz
pwm = GPIO.PWM(18, 100)
pwm.start(0) # 初始占空比为0
try:
while True:
# 渐亮
for dc in range(0, 101, 5):
pwm.ChangeDutyCycle(dc)
time.sleep(0.1)
# 渐暗
for dc in range(100, -1, -5):
pwm.ChangeDutyCycle(dc)
time.sleep(0.1)
except KeyboardInterrupt:
pwm.stop()
GPIO.cleanup()
我们可以通过数学函数来创造更自然的呼吸效果。指数函数能模拟更接近真实呼吸的亮度变化:
python复制import math
import RPi.GPIO as GPIO
import time
def exponential_breathing_led(pin, cycles=5, speed=0.05):
GPIO.setmode(GPIO.BCM)
GPIO.setup(pin, GPIO.OUT)
pwm = GPIO.PWM(pin, 100)
pwm.start(0)
try:
for _ in range(cycles):
# 呼气阶段(渐亮)
for x in range(0, 314, 2): # 0到π
value = math.sin(x / 100) # 0到1
dc = value * 100 # 转换为0-100%
pwm.ChangeDutyCycle(dc)
time.sleep(speed)
# 吸气阶段(渐暗)
for x in range(314, 628, 2): # π到2π
value = math.sin(x / 100)
dc = value * 100
pwm.ChangeDutyCycle(dc)
time.sleep(speed)
except KeyboardInterrupt:
pwm.stop()
GPIO.cleanup()
if __name__ == "__main__":
exponential_breathing_led(18, cycles=10, speed=0.03)
这个版本使用了正弦函数来产生平滑的亮度过渡,参数speed控制呼吸速度,cycles控制循环次数。
掌握了交通灯和呼吸灯的基本实现后,我们可以将这些技术结合起来,创造更有趣的项目。
结合传感器和交通灯逻辑,可以创建一个更智能的系统。例如:
python复制import RPi.GPIO as GPIO
import time
from enum import Enum, auto
class TrafficLightState(Enum):
RED = auto()
GREEN = auto()
YELLOW = auto()
PEDESTRIAN = auto()
class SmartTrafficLight:
def __init__(self):
self.red_pin = 17
self.yellow_pin = 27
self.green_pin = 22
self.button_pin = 23
self.buzzer_pin = 24
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.red_pin, GPIO.OUT)
GPIO.setup(self.yellow_pin, GPIO.OUT)
GPIO.setup(self.green_pin, GPIO.OUT)
GPIO.setup(self.button_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(self.buzzer_pin, GPIO.OUT)
self.state = TrafficLightState.RED
self.last_change = time.time()
self.pedestrian_request = False
def run(self):
try:
while True:
self.check_button()
current_time = time.time()
if self.state == TrafficLightState.RED:
if current_time - self.last_change > 10: # 红灯10秒
self.change_state(TrafficLightState.GREEN)
elif self.pedestrian_request and current_time - self.last_change > 5: # 至少红灯5秒后才响应
self.change_state(TrafficLightState.PEDESTRIAN)
elif self.state == TrafficLightState.GREEN:
if current_time - self.last_change > 8: # 绿灯8秒
self.change_state(TrafficLightState.YELLOW)
elif self.state == TrafficLightState.YELLOW:
if current_time - self.last_change > 2: # 黄灯2秒
self.change_state(TrafficLightState.RED)
elif self.state == TrafficLightState.PEDESTRIAN:
if current_time - self.last_change > 5: # 行人通行5秒
self.change_state(TrafficLightState.RED)
time.sleep(0.1)
except KeyboardInterrupt:
GPIO.cleanup()
def change_state(self, new_state):
# 关闭所有灯
GPIO.output(self.red_pin, GPIO.LOW)
GPIO.output(self.yellow_pin, GPIO.LOW)
GPIO.output(self.green_pin, GPIO.LOW)
if new_state == TrafficLightState.RED:
GPIO.output(self.red_pin, GPIO.HIGH)
elif new_state == TrafficLightState.GREEN:
GPIO.output(self.green_pin, GPIO.HIGH)
elif new_state == TrafficLightState.YELLOW:
for _ in range(4):
GPIO.output(self.yellow_pin, GPIO.HIGH)
time.sleep(0.25)
GPIO.output(self.yellow_pin, GPIO.LOW)
time.sleep(0.25)
elif new_state == TrafficLightState.PEDESTRIAN:
GPIO.output(self.green_pin, GPIO.HIGH)
self.beep_buzzer()
self.pedestrian_request = False
self.state = new_state
self.last_change = time.time()
def check_button(self):
if GPIO.input(self.button_pin) == GPIO.LOW:
self.pedestrian_request = True
def beep_buzzer(self):
for _ in range(3):
GPIO.output(self.buzzer_pin, GPIO.HIGH)
time.sleep(0.2)
GPIO.output(self.buzzer_pin, GPIO.LOW)
time.sleep(0.2)
if __name__ == "__main__":
light = SmartTrafficLight()
light.run()
结合呼吸灯技术,可以创建一个能反映不同情绪的灯光系统:
python复制import RPi.GPIO as GPIO
import time
import math
from dataclasses import dataclass
from enum import Enum, auto
class Emotion(Enum):
CALM = auto()
HAPPY = auto()
ANGRY = auto()
SAD = auto()
@dataclass
class LEDConfig:
pin: int
pwm: GPIO.PWM = None
class EmotionLight:
def __init__(self, led_pins):
GPIO.setmode(GPIO.BCM)
self.leds = [LEDConfig(pin) for pin in led_pins]
for led in self.leds:
GPIO.setup(led.pin, GPIO.OUT)
led.pwm = GPIO.PWM(led.pin, 100)
led.pwm.start(0)
def set_emotion(self, emotion):
if emotion == Emotion.CALM:
self.breathing_effect(0.1, 3)
elif emotion == Emotion.HAPPY:
self.rainbow_effect()
elif emotion == Emotion.ANGRY:
self.flashing_red()
elif emotion == Emotion.SAD:
self.slow_pulse()
def breathing_effect(self, speed, cycles):
for _ in range(cycles):
for x in range(0, 314, 2): # 0 to π
value = math.sin(x / 100)
for led in self.leds:
led.pwm.ChangeDutyCycle(value * 50) # 限制最大亮度为50%
time.sleep(speed)
def rainbow_effect(self):
colors = [
(100, 0, 0), # 红
(100, 50, 0), # 橙
(100, 100, 0), # 黄
(0, 100, 0), # 绿
(0, 0, 100), # 蓝
(50, 0, 100) # 紫
]
for r, g, b in colors:
self.leds[0].pwm.ChangeDutyCycle(r)
self.leds[1].pwm.ChangeDutyCycle(g)
self.leds[2].pwm.ChangeDutyCycle(b)
time.sleep(1)
def flashing_red(self):
for _ in range(5):
self.leds[0].pwm.ChangeDutyCycle(100)
time.sleep(0.2)
self.leds[0].pwm.ChangeDutyCycle(0)
time.sleep(0.2)
def slow_pulse(self):
for _ in range(2):
for dc in range(0, 101, 5):
for led in self.leds:
led.pwm.ChangeDutyCycle(dc)
time.sleep(0.1)
for dc in range(100, -1, -5):
for led in self.leds:
led.pwm.ChangeDutyCycle(dc)
time.sleep(0.1)
def cleanup(self):
for led in self.leds:
led.pwm.stop()
GPIO.cleanup()
if __name__ == "__main__":
try:
light = EmotionLight([17, 27, 22]) # 红、黄、绿
while True:
light.set_emotion(Emotion.CALM)
time.sleep(5)
light.set_emotion(Emotion.HAPPY)
time.sleep(5)
light.set_emotion(Emotion.ANGRY)
time.sleep(5)
light.set_emotion(Emotion.SAD)
time.sleep(5)
except KeyboardInterrupt:
light.cleanup()