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文件,记录硬件连接图、功能说明和遇到的问题,这对后续项目开发很有参考价值。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐