树莓派Python GPIO编程实战:从基础到传感器应用
1. 树莓派GPIO基础入门
树莓派作为一款功能强大的微型计算机,最吸引人的特性之一就是其GPIO(通用输入输出)引脚。这些引脚让你能够与外部硬件交互,从简单的LED灯到复杂的传感器系统。我刚开始玩树莓派的时候,最兴奋的就是能用Python代码控制真实的物理设备,那种"代码改变世界"的感觉真是太棒了!
GPIO引脚就像是树莓派与外界沟通的桥梁。树莓派有40个GPIO引脚,它们可以配置为输入或输出模式。输出模式下,你可以控制引脚输出高电平(3.3V)或低电平(0V),从而驱动LED、继电器等设备。输入模式下,你可以读取引脚的电平状态,检测按钮按下、传感器信号等。
重要提示:树莓派GPIO引脚的工作电压是3.3V,千万不要接入5V电压,否则可能会损坏你的树莓派!这是我刚开始时犯过的错误,烧了一个GPIO口,希望大家引以为戒。
2. 环境准备与硬件连接
2.1 所需材料清单
开始之前,你需要准备以下材料:
- 树莓派主板(任何型号都可以,推荐树莓派4B)
- microSD卡(至少16GB)并安装Raspberry Pi OS
- 电源适配器
- 面包板和跳线(母对母、公对母)
- LED灯、电阻(220Ω-1kΩ)
- 各种传感器模块(按键、振动开关等)
我建议初学者购买一个树莓派入门套件,通常包含这些基础组件,性价比很高。
2.2 软件安装与配置
首先确保你的树莓派系统是最新的:
sudo apt update
sudo apt upgrade
Python的GPIO库通常已经预装,如果没有,可以通过以下命令安装:
sudo apt install python3-rpi.gpio
验证安装是否成功:
import RPi.GPIO as GPIO
print("GPIO库导入成功!")
2.3 硬件安全连接
连接电路时务必注意:
- 始终断开电源进行连接
- 使用电阻保护LED和GPIO引脚
- 确认连接正确后再通电
我第一次实验时因为心急,带电插拔跳线,结果短路了,幸好没造成永久损坏。现在我都养成了断电操作的好习惯。
3. GPIO编程基础
3.1 引脚编号模式
树莓派GPIO有两种编号方式:BOARD和BCM。BOARD模式使用物理引脚编号,BCM模式使用Broadcom芯片的GPIO编号。
import RPi.GPIO as GPIO
# 设置引脚编号模式
GPIO.setmode(GPIO.BCM) # 推荐使用BCM模式
# 或者 GPIO.setmode(GPIO.BOARD)
# 查询当前模式
current_mode = GPIO.getmode()
我个人偏好BCM模式,因为它在不同树莓派型号间更一致。但如果你是初学者,BOARD模式可能更直观,因为直接对应物理引脚位置。
3.2 输入输出模式设置
设置引脚为输出模式:
led_pin = 18 # BCM编号的GPIO18
GPIO.setup(led_pin, GPIO.OUT)
设置引脚为输入模式,并启用上拉电阻:
button_pin = 17
GPIO.setup(button_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
上拉电阻确保引脚在未连接时保持高电平,避免悬空状态导致的误触发。下拉电阻则相反,保持低电平。
3.3 电平控制与读取
控制输出电平:
GPIO.output(led_pin, GPIO.HIGH) # 输出高电平,点亮LED
GPIO.output(led_pin, GPIO.LOW) # 输出低电平,熄灭LED
读取输入电平:
if GPIO.input(button_pin) == GPIO.LOW:
print("按钮被按下")
else:
print("按钮未按下")
实用技巧:你可以同时设置多个引脚:
pins = [18, 23, 24]
GPIO.setup(pins, GPIO.OUT)
GPIO.output(pins, (GPIO.HIGH, GPIO.LOW, GPIO.HIGH))
4. LED控制实战
4.1 单色LED控制
让我们从最简单的LED控制开始。连接电路:LED正极(长脚)通过220Ω电阻连接到GPIO18,负极连接到GND。
import RPi.GPIO as GPIO
import time
# 设置
LED_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)
try:
while True:
GPIO.output(LED_PIN, GPIO.HIGH)
time.sleep(1) # 亮1秒
GPIO.output(LED_PIN, GPIO.LOW)
time.sleep(1) # 灭1秒
except KeyboardInterrupt:
print("程序被用户中断")
finally:
GPIO.cleanup() # 清理GPIO设置
这个简单的闪烁程序是硬件编程的"Hello World"。记得在程序退出时调用GPIO.cleanup(),这会重置GPIO状态,避免引脚保持上次设置。
4.2 PWM调光控制
想要实现LED亮度调节?这就需要PWM(脉冲宽度调制)技术:
import RPi.GPIO as GPIO
import time
LED_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)
# 创建PWM实例,频率100Hz
pwm = GPIO.PWM(LED_PIN, 100)
pwm.start(0) # 初始占空比为0%
try:
while True:
# 渐亮
for duty_cycle in range(0, 101, 5):
pwm.ChangeDutyCycle(duty_cycle)
time.sleep(0.1)
# 渐暗
for duty_cycle in range(100, -1, -5):
pwm.ChangeDutyCycle(duty_cycle)
time.sleep(0.1)
except KeyboardInterrupt:
pwm.stop()
GPIO.cleanup()
PWM通过快速开关来控制平均电压,从而调节亮度。频率越高,闪烁越不明显,但也不要太高,100-1000Hz是比较合适的范围。
4.3 双色LED控制
双色LED实际上有两个LED芯片封装在一起,通常有共阳或共阴两种配置。我以共阴双色LED为例:
import RPi.GPIO as GPIO
import time
RED_PIN = 17
GREEN_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(RED_PIN, GPIO.OUT)
GPIO.setup(GREEN_PIN, GPIO.OUT)
def set_color(red, green):
GPIO.output(RED_PIN, red)
GPIO.output(GREEN_PIN, green)
try:
while True:
set_color(True, False) # 红色
time.sleep(1)
set_color(False, True) # 绿色
time.sleep(1)
set_color(True, True) # 黄色(红+绿)
time.sleep(1)
except KeyboardInterrupt:
GPIO.cleanup()
重要提醒:不要同时让红绿LED都全亮,这样电流会很大。实际使用时应该用PWM控制亮度,避免过流。
5. 按键输入检测
5.1 轮询方式检测
最简单的按键检测方式就是轮询:
import RPi.GPIO as GPIO
import time
BUTTON_PIN = 2
LED_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(LED_PIN, GPIO.OUT)
try:
while True:
if GPIO.input(BUTTON_PIN) == GPIO.LOW:
GPIO.output(LED_PIN, GPIO.HIGH)
print("按钮按下")
time.sleep(0.1) # 简单防抖
else:
GPIO.output(LED_PIN, GPIO.LOW)
time.sleep(0.01) # 减少CPU占用
except KeyboardInterrupt:
GPIO.cleanup()
轮询的优点是简单,但会占用较多CPU资源,而且在检测期间程序会被阻塞。
5.2 中断方式检测
更好的方式是使用中断检测,这样CPU可以在等待按键时做其他事情:
import RPi.GPIO as GPIO
import time
BUTTON_PIN = 2
LED_PIN = 18
button_pressed = False
def button_callback(channel):
global button_pressed
button_pressed = not button_pressed
GPIO.output(LED_PIN, button_pressed)
print("按钮状态改变:", button_pressed)
GPIO.setmode(GPIO.BCM)
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(LED_PIN, GPIO.OUT)
# 添加中断检测,下降沿触发(按钮按下时)
GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING,
callback=button_callback,
bouncetime=300) # 防抖时间300ms
try:
print("程序运行中,按Ctrl+C退出")
while True:
time.sleep(1) # 主程序可以做其他事情
except KeyboardInterrupt:
GPIO.cleanup()
中断方式更加高效,但要注意回调函数中不要进行耗时操作,否则会影响系统响应。
5.3 按键防抖处理
机械按键在按下和释放时会产生抖动,导致多次误触发。除了软件防抖,也可以硬件防抖:
# 改进的回调函数 with 软件防抖
def button_callback(channel):
global button_pressed
time.sleep(0.05) # 等待抖动结束
if GPIO.input(BUTTON_PIN) == GPIO.LOW: # 确认仍然是按下状态
button_pressed = not button_pressed
GPIO.output(LED_PIN, button_pressed)
print("确认按钮按下")
在实际项目中,我通常结合硬件滤波电容(0.1μF)和软件防抖,效果最好。
6. 振动传感器应用
6.1 振动传感器原理
振动传感器内部有一个弹簧机制,当受到振动时,弹簧会短暂接触,产生电信号。这种传感器常用于防盗报警、震动检测等场景。
import RPi.GPIO as GPIO
import time
VIBRATION_PIN = 4
LED_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(VIBRATION_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(LED_PIN, GPIO.OUT)
vibration_count = 0
def vibration_detected(channel):
global vibration_count
vibration_count += 1
print(f"检测到振动! 总数: {vibration_count}")
GPIO.output(LED_PIN, GPIO.HIGH)
time.sleep(0.5) # LED提示
GPIO.output(LED_PIN, GPIO.LOW)
# 设置中断检测
GPIO.add_event_detect(VIBRATION_PIN, GPIO.FALLING,
callback=vibration_detected,
bouncetime=200)
try:
print振动传感器监控中...")
while True:
time.sleep(10)
print(f"过去10秒振动次数: {vibration_count}")
vibration_count = 0 # 重置计数
except KeyboardInterrupt:
GPIO.cleanup()
6.2 振动报警系统
结合蜂鸣器实现完整的振动报警系统:
import RPi.GPIO as GPIO
import time
VIBRATION_PIN = 4
BUZZER_PIN = 22
LED_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(VIBRATION_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(BUZZER_PIN, GPIO.OUT)
GPIO.setup(LED_PIN, GPIO.OUT)
alarm_active = False
def trigger_alarm():
for _ in range(5): # 响5次
GPIO.output(BUZZER_PIN, GPIO.HIGH)
GPIO.output(LED_PIN, GPIO.HIGH)
time.sleep(0.2)
GPIO.output(BUZZER_PIN, GPIO.LOW)
GPIO.output(LED_PIN, GPIO.LOW)
time.sleep(0.2)
def vibration_callback(channel):
global alarm_active
if not alarm_active:
alarm_active = True
print("报警触发!")
trigger_alarm()
alarm_active = False
GPIO.add_event_detect(VIBRATION_PIN, GPIO.FALLING,
callback=vibration_callback,
bouncetime=1000) # 1秒内不重复触发
try:
print("振动报警系统启动")
while True:
time.sleep(1)
except KeyboardInterrupt:
GPIO.cleanup()
7. 传感器数据读取技巧
7.1 模拟传感器处理
树莓派GPIO只能读取数字信号,但很多传感器输出的是模拟信号。这时候需要ADC(模数转换器),比如常用的MCP3008:
import busio
import digitalio
import board
import adafruit_mcp3xxx.mcp3008 as MCP
from adafruit_mcp3xxx.analog_in import AnalogIn
# 创建SPI总线
spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)
cs = digitalio.DigitalInOut(board.D5) # 片选引脚
mcp = MCP.MCP3008(spi, cs)
# 创建模拟输入通道
channel = AnalogIn(mcp, MCP.P0)
print("原始值: ", channel.value)
print("电压: {:.2f}V".format(channel.voltage))
# 将电压值转换为实际物理量
def voltage_to_temperature(voltage):
# 假设使用LM35温度传感器(10mV/°C)
return voltage * 100
temperature = voltage_to_temperature(channel.voltage)
print("温度: {:.1f}°C".format(temperature))
7.2 数字传感器读取
对于数字传感器,读取过程更简单:
import RPi.GPIO as GPIO
import time
SENSOR_PIN = 14
GPIO.setmode(GPIO.BCM)
GPIO.setup(SENSOR_PIN, GPIO.IN)
def read_sensor():
# 很多数字传感器有特定的通信协议
# 这里以简单的电平读取为例
return GPIO.input(SENSOR_PIN)
try:
while True:
value = read_sensor()
print("传感器值:", value)
time.sleep(1)
except KeyboardInterrupt:
GPIO.cleanup()
7.3 传感器数据滤波
传感器读数往往有噪声,需要滤波处理:
import numpy as np
class SensorFilter:
def __init__(self, window_size=5):
self.window_size = window_size
self.readings = []
def add_reading(self, value):
self.readings.append(value)
if len(self.readings) > self.window_size:
self.readings.pop(0)
def get_filtered_value(self):
if not self.readings:
return None
return np.median(self.readings) # 使用中值滤波
# 使用示例
filter = SensorFilter(5)
for i in range(10):
raw_value = read_sensor() # 你的传感器读取函数
filter.add_reading(raw_value)
filtered = filter.get_filtered_value()
print(f"原始: {raw_value}, 滤波后: {filtered}")
中值滤波能有效去除突发噪声,对于传感器数据处理非常有效。
8. 综合实战项目
8.1 智能报警系统
结合前面学到的知识,我们来创建一个完整的智能报警系统:
import RPi.GPIO as GPIO
import time
import datetime
class SecuritySystem:
def __init__(self):
self.vibration_pin = 4
self.buzzer_pin = 22
self.led_pin = 17
self.button_pin = 2
self.alarm_enabled = False
self.alert_log = []
self.setup_gpio()
def setup_gpio(self):
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.vibration_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(self.buzzer_pin, GPIO.OUT)
GPIO.setup(self.led_pin, GPIO.OUT)
GPIO.setup(self.button_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# 设置中断
GPIO.add_event_detect(self.vibration_pin, GPIO.FALLING,
callback=self.vibration_detected,
bouncetime=1000)
GPIO.add_event_detect(self.button_pin, GPIO.FALLING,
callback=self.button_pressed,
bouncetime=300)
def vibration_detected(self, channel):
if self.alarm_enabled:
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.alert_log.append(f"{timestamp} - 振动报警")
self.trigger_alarm()
print("报警触发!")
def button_pressed(self, channel):
self.alarm_enabled = not self.alarm_enabled
status = "启用" if self.alarm_enabled else "禁用"
print(f"报警系统{status}")
self.blink_led(2) # 闪烁2次提示状态改变
def trigger_alarm(self):
for _ in range(3):
GPIO.output(self.buzzer_pin, GPIO.HIGH)
GPIO.output(self.led_pin, GPIO.HIGH)
time.sleep(0.3)
GPIO.output(self.buzzer_pin, GPIO.LOW)
GPIO.output(self.led_pin, GPIO.LOW)
time.sleep(0.3)
def blink_led(self, times):
for _ in range(times):
GPIO.output(self.led_pin, GPIO.HIGH)
time.sleep(0.2)
GPIO.output(self.led_pin, GPIO.LOW)
time.sleep(0.2)
def run(self):
try:
print("智能报警系统启动")
print("按按钮切换报警状态")
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n报警记录:")
for log in self.alert_log:
print(log)
GPIO.cleanup()
if __name__ == "__main__":
system = SecuritySystem()
system.run()
8.2 环境监测站
创建一个简单的环境监测站,监测温度和振动:
import RPi.GPIO as GPIO
import time
import datetime
import board
import busio
import adafruit_mcp3xxx.mcp3008 as MCP
from adafruit_mcp3xxx.analog_in import AnalogIn
class EnvironmentMonitor:
def __init__(self):
self.temp_sensor_pin = 0 # MCP3008通道0
self.vibration_pin = 4
self.setup_sensors()
self.reading_interval = 60 # 每分钟记录一次
self.data_log = []
def setup_sensors(self):
# 设置ADC
spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)
cs = digitalio.DigitalInOut(board.D5)
self.mcp = MCP.MCP3008(spi, cs)
self.temp_channel = AnalogIn(self.mcp, MCP.P0)
# 设置振动传感器
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.vibration_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def read_temperature(self):
# 假设使用LM35温度传感器
voltage = self.temp_channel.voltage
return voltage * 100 # LM35: 10mV/°C
def read_vibration(self):
return GPIO.input(self.vibration_pin)
def log_data(self):
timestamp = datetime.datetime.now()
temperature = self.read_temperature()
vibration = self.read_vibration()
log_entry = {
'timestamp': timestamp,
'temperature': temperature,
'vibration': vibration
}
self.data_log.append(log_entry)
return log_entry
def generate_report(self):
if not self.data_log:
return "无数据"
temps = [entry['temperature'] for entry in self.data_log]
avg_temp = sum(temps) / len(temps)
max_temp = max(temps)
min_temp = min(temps)
vibrations = sum(1 for entry in self.data_log if entry['vibration'] == 0)
report = f"""
环境监测报告
时间范围: {self.data_log[0]['timestamp']} - {self.data_log[-1]['timestamp']}
温度统计:
平均: {avg_temp:.1f}°C
最高: {max_temp:.1f}°C
最低: {min_temp:.1f}°C
振动事件: {vibrations} 次
"""
return report
def run(self):
try:
print("环境监测站启动")
while True:
data = self.log_data()
print(f"记录数据: {data['temperature']:.1f}°C, 振动: {data['vibration']}")
# 每小时生成报告
if len(self.data_log) % 60 == 0:
report = self.generate_report()
print(report)
time.sleep(self.reading_interval)
except KeyboardInterrupt:
final_report = self.generate_report()
print(final_report)
GPIO.cleanup()
if __name__ == "__main__":
monitor = EnvironmentMonitor()
monitor.run()
这些实战项目综合运用了GPIO控制的各个方面,从简单的输入输出到复杂的系统集成。在实际开发中,我建议先从简单的功能开始,逐步增加复杂度,这样更容易调试和解决问题。
记得在项目完成后好好整理代码,添加注释,这对以后维护和重用非常有帮助。我有个习惯,每个项目都写一个README文件,记录硬件连接图、功能说明和遇到的问题,这对后续项目开发很有参考价值。
更多推荐
所有评论(0)