树莓派GPIO实战:用Python制作避障机器人
详细讲解如何使用树莓派GPIO接口与Python语言构建自主避障小车。涵盖L298N电机驱动原理、HC-SR04超声波测距与3.3V电平分压防护、状态机避障算法实现及实车调优。
制作一台基于树莓派的自主避障机器人,核心在于通过通用输入输出接口(GPIO)向电机驱动芯片输出高低电平以驱动小车差速转向,同时通过微秒级声波测距传感器实时计算障碍物距离并闭环调控小车行驶路径。本文基于树莓派 4B/3B、L298N 双 H 桥驱动模块与 HC-SR04 超声波测距模块,完整解析电气分压防护、底层驱动编写与避障状态机控制算法。
一、避障机器人的系统组成与分层架构
避障小车在硬件上由四个核心子系统构成:主控单元、感知单元、驱动执行单元以及供电单元。各部分分工明确,通过树莓派的 40-Pin GPIO 扩展引脚形成闭环控制回路。
- 主控单元(树莓派 4B / 3B+):运行标准 Linux 系统,负责执行 Python 避障控制主程序。它发出高频触发信号,监听回波引脚持续时间,并根据测得的距离做出转向决策。
- 感知单元(HC-SR04 超声波模块):由发射端和接收端两个压电陶瓷换能器组成。发射端在接收到触发信号后发出 8 个 40kHz 的声波脉冲,接收端在收到反射波前输出高电平,其脉宽对应声波往返时间。
- 驱动执行单元(L298N 驱动板 + 双直流减速马达):树莓派 GPIO 的输出电流仅为毫安级(单个引脚最大建议不超过 16mA),无法直接驱动电机。L298N 内部包含双路高电压大电流全桥式驱动芯片,将树莓派的 3.3V 逻辑信号转化为可驱动电机的动力电流。
- 供电单元(动力与逻辑双路独立供电):直流电机在启停和急转瞬间会产生极大的反向电动势与浪涌电流,若与树莓派共用同一路电源,会导致树莓派核心电压瞬间拉低出现欠压重启(Undervoltage Warning)。因此系统必须实行双路物理供电隔离。
二、关键电路连接与 3.3V GPIO 电平安全防护
在搭建电路时,最容易造成硬件损毁的环节是超声波模块与树莓派引脚的电平不匹配。HC-SR04 是一款工作电压为 5V 的标准工业模块,其 Echo 引脚在收到回波后输出的脉冲电平同样为 5V。而树莓派所有 GPIO 引脚的最高耐受电压为 3.3V,直接将 5V 信号接入树莓派引脚会导致片上 SoC 的 GPIO 保护二极管被击穿,造成引脚乃至整块芯片永久烧毁。
1. 串联分压电路计算
根据分压公式,通过两个精密金属膜电阻 $R_1$(1kΩ)和 $R_2$(2kΩ 或 2.2kΩ)对 5V 进行分压:
$$V_{out} = V_{in} imes rac{R_2}{R_1 + R_2} = 5 ext{V} imes rac{2000}{1000 + 2000} pprox 3.33 ext{V}$$
将 Echo 端连至 $R_1$,在 $R_1$ 与 $R_2$ 之间的公共接点引出导线接到树莓派的 GPIO 25,$R_2$ 的另一端连接至树莓派的 GND,即可安全将 5V 脉冲转换为合规的 3.3V 逻辑高电平。
2. GPIO 引脚与硬件端子接线对照表
为了确保后续 Python 驱动代码的引脚编号一致,推荐采用 BCM(Broadcom SOC channel)引脚编号方式:
| 外设引脚 / 端子 | 树莓派物理引脚 (Physical) | BCM 编码 (Python使用) | 电气属性与注意事项 |
|---|---|---|---|
| HC-SR04 VCC | Pin 2 或 Pin 4 | 5V Power | 供电 5V,来自树莓派 5V 总线引脚 |
| HC-SR04 Trig | Pin 18 | GPIO 24 | 树莓派输出 3.3V 脉冲,模块可正常识别 |
| HC-SR04 Echo | Pin 22 | GPIO 25 | 必须经过 1kΩ / 2kΩ 分压后接入 |
| HC-SR04 GND | Pin 6 或 Pin 9 | GND | 系统接地 |
| L298N IN1 (左电机正) | Pin 11 | GPIO 17 | 控制左侧车轮向前转动 |
| L298N IN2 (左电机反) | Pin 13 | GPIO 27 | 控制左侧车轮向后转动 |
| L298N IN3 (右电机正) | Pin 15 | GPIO 22 | 控制右侧车轮向前转动 |
| L298N IN4 (右电机反) | Pin 16 | GPIO 23 | 控制右侧车轮向后转动 |
| L298N 电源 GND | Pin 6 (共地端) | GND | 动力电源地线与树莓派地线必须物理连通 |
三、直流电机差速驱动与运动控制实现
双轮差速小车的移动依靠左右两轮的速度与旋转方向差异来实现:
- 直行前进:左右两电机同时正转(IN1=高, IN2=低; IN3=高, IN4=低)。
- 倒车后退:左右两电机同时反转(IN1=低, IN2=高; IN3=低, IN4=高)。
- 原地左自旋:左电机反转、右电机正转(IN1=低, IN2=高; IN3=高, IN4=低),小车以两轮中心原地逆时针旋转。
- 原地右自旋:左电机正转、右电机反转(IN1=高, IN2=低; IN3=低, IN4=高),小车以两轮中心原地顺时针旋转。
- 紧急制动:所有输入置低(IN1~IN4 全为低电平),电机处于自由滑行或快速刹停状态。
下面是用 Python 封装的电机底层控制模块 motor_driver.py:
# -*- coding: utf-8 -*-
# motor_driver.py - L298N 双 H 桥电机底层控制器
import RPi.GPIO as GPIO
import time
class MotorController:
def __init__(self, in1=17, in2=27, in3=22, in4=23):
self.in1 = in1
self.in2 = in2
self.in3 = in3
self.in4 = in4
# 初始化 GPIO 模式与引脚输出
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
for pin in [self.in1, self.in2, self.in3, self.in4]:
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.LOW)
def forward(self):
# 双轮同向正转
GPIO.output(self.in1, GPIO.HIGH)
GPIO.output(self.in2, GPIO.LOW)
GPIO.output(self.in3, GPIO.HIGH)
GPIO.output(self.in4, GPIO.LOW)
def backward(self):
# 双轮同向反转
GPIO.output(self.in1, GPIO.LOW)
GPIO.output(self.in2, GPIO.HIGH)
GPIO.output(self.in3, GPIO.LOW)
GPIO.output(self.in4, GPIO.HIGH)
def turn_left(self, duration=0.3):
# 差速左转:左轮后退,右轮前进
GPIO.output(self.in1, GPIO.LOW)
GPIO.output(self.in2, GPIO.HIGH)
GPIO.output(self.in3, GPIO.HIGH)
GPIO.output(self.in4, GPIO.LOW)
time.sleep(duration)
self.stop()
def turn_right(self, duration=0.3):
# 差速右转:左轮前进,右轮后退
GPIO.output(self.in1, GPIO.HIGH)
GPIO.output(self.in2, GPIO.LOW)
GPIO.output(self.in3, GPIO.LOW)
GPIO.output(self.in4, GPIO.HIGH)
time.sleep(duration)
self.stop()
def stop(self):
# 制动停转
for pin in [self.in1, self.in2, self.in3, self.in4]:
GPIO.output(pin, GPIO.LOW)
def cleanup(self):
self.stop()
GPIO.cleanup([self.in1, self.in2, self.in3, self.in4])
四、HC-SR04 超声波测距算法与防卡死机制
超声波测距的基本原理是飞行时间法(Time-of-Flight)。在标准大气压与 20℃ 环境下,声速约为 $v = 343 ext{ m/s} = 34300 ext{ cm/s}$。当树莓派向 Trig 发送一个持续至少 10µs 的高电平脉冲后,HC-SR04 会发出超声脉冲,并将 Echo 引脚拉高,直到收到反射波时拉低。
通过测量 Echo 高电平维持的时间差 $\Delta t$,即可计算出障碍物距离:
$$S = rac{v imes \Delta t}{2} = rac{34300 imes \Delta t}{2} = 17150 imes \Delta t \quad ( ext{cm})$$
while GPIO.input(ECHO) == 0 循环中没有超时退出机制,程序将会永久卡死阻塞。因此必须在循环中设置基于时间戳的超时断开(建议单次探测超时上限设为 25ms ~ 30ms)。
超声波传感器驱动与滤波模块 sonar_sensor.py:
# -*- coding: utf-8 -*-
# sonar_sensor.py - HC-SR04 超声波测距与抗阻塞采集
import RPi.GPIO as GPIO
import time
class UltrasonicSensor:
def __init__(self, trig=24, echo=25):
self.trig = trig
self.echo = echo
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(self.trig, GPIO.OUT)
GPIO.setup(self.echo, GPIO.IN)
GPIO.output(self.trig, GPIO.LOW)
time.sleep(0.1) # 等待传感器上电稳态
def measure_distance_once(self, timeout=0.03):
# 发送 10 微秒的超声波触发脉冲
GPIO.output(self.trig, GPIO.HIGH)
time.sleep(0.00001)
GPIO.output(self.trig, GPIO.LOW)
pulse_start = time.time()
timeout_start = pulse_start
# 等待 Echo 引脚变高,防死锁超时
while GPIO.input(self.echo) == 0:
pulse_start = time.time()
if pulse_start - timeout_start > timeout:
return -1.0 # 超时未检测到脉冲起点
pulse_end = pulse_start
# 等待 Echo 引脚拉低,防反射波未归死锁
while GPIO.input(self.echo) == 1:
pulse_end = time.time()
if pulse_end - pulse_start > timeout:
return -1.0 # 超出最大有效测距范围
duration = pulse_end - pulse_start
distance = duration * 17150
return round(distance, 2)
def get_filtered_distance(self, samples=3):
# 多次采样求中值,过滤声学干扰偶发野点
readings = []
for _ in range(samples):
dist = self.measure_distance_once()
if dist > 2.0 and dist < 400.0:
readings.append(dist)
time.sleep(0.015) # 消除发射余震回波
if not readings:
return 999.0 # 探测区域无反射或开阔
readings.sort()
return readings[len(readings) // 2]
五、自主避障决策状态机与完整系统整合
避障算法如果仅仅依靠“有障碍就直接拐弯”,容易在转弯半径内刮碰障碍物或在死角处陷入振荡死循环。更稳健的做法是设计一个三级距离阈值状态机:
- 畅行巡航(距离 $\ge 30 ext{cm}$):前方开阔无阻,双轮全速前进。
- 预警减速($15 ext{cm} \le ext{距离} < 30 ext{cm}$):进入潜在障碍区,降低车速或缩短采样窗口。
- 脱困机动(距离 $< 15 ext{cm}$):
- 立即制动刹车停滞 100ms;
- 后退 200ms 以拉开安全回转半径;
- 原地差速旋转约 60°~90°(左转 350ms);
- 复测开阔度,确认安全后恢复前进。
将电机控制与测距逻辑整合为完整的机器人主程序 robot_main.py:
# -*- coding: utf-8 -*-
# robot_main.py - 树莓派避障机器人主控程序
import sys
import time
import RPi.GPIO as GPIO
from motor_driver import MotorController
from sonar_sensor import UltrasonicSensor
SAFE_DIST_CRUISE = 30.0 # cm: 畅行巡航阈值
WARN_DIST_STOP = 15.0 # cm: 紧急制动避障阈值
def main():
print("[*] 正在初始化树莓派 GPIO 与机器人子系统...")
motor = MotorController(in1=17, in2=27, in3=22, in4=23)
sensor = UltrasonicSensor(trig=24, echo=25)
print("[*] 系统就绪。3 秒后开启自主避障巡航 (按 Ctrl+C 可紧急停机)...")
time.sleep(3.0)
try:
while True:
# 采样前方经过滤波的有效距离
dist = sensor.get_filtered_distance(samples=3)
print(f"[雷达] 前方距离: {dist:.1f} cm")
if dist >= SAFE_DIST_CRUISE:
# 状态 A: 开阔直行
motor.forward()
time.sleep(0.05)
elif WARN_DIST_STOP <= dist < SAFE_DIST_CRUISE:
# 状态 B: 预警区缓行
motor.forward()
time.sleep(0.02)
else:
# 状态 C: 近距遇障,执行标准脱困动作链
print("[!] 触发避障动作:停止 -> 后退 -> 差速转弯")
motor.stop()
time.sleep(0.1)
# 后退微调拉开回转间距
motor.backward()
time.sleep(0.2)
motor.stop()
time.sleep(0.05)
# 差速向左原地旋转寻找通路
motor.turn_left(duration=0.35)
except KeyboardInterrupt:
print("\n[*] 捕获用户键盘中断,执行安全关机...")
finally:
# 无论何时退出,严格执行电机停止与引脚清理
motor.cleanup()
GPIO.cleanup()
print("[*] GPIO 已安全释放,机器人已泊车停机。")
if __name__ == "__main__":
main()
六、系统联调、常见故障排查与性能指标总结
完成组装和代码编写后,不要直接把机器人放在地面狂奔。建议遵循分阶段排查验证法,确保每一级模块可靠运行后再进行整车闭环测试。
实车调试中的三大高频故障及解决方案
-
现象:小车前进指令发出后,就地打转或后退。
原因:两路直流马达的接线极性反接。例如左电机的 OUT1/OUT2 实际上使其倒转。
解决办法:无需大改代码,直接将 L298N 对应端子上的两条马达输出导线物理对调,或在代码中互换对应 IN 引脚的电平即可。 -
现象:传感器读数偶尔跳变到几千厘米或归零,小车突然抽搐式刹车。
原因:超声波声束角度约为 15°,当遇到斜向光滑墙面、布料或地毯时,声波发生镜面散射没有返回探头;或者面包板分压电阻虚焊。
解决办法:使用代码中的get_filtered_distance中值滤波法消除单次异常尖峰;检查 1kΩ/2kΩ 接线是否牢固。 -
现象:小车一旦启动电机,树莓派立即熄火重启或网络断开。
原因:树莓派与电机混用同一个 5V 充电宝或电池组,电机启动的高瞬间电流瞬间拖垮了电压。
解决办法:树莓派必须由独立移动电源(Type-C 接口)供电,L298N 由 7.4V~12V 锂电池盒单独供电,并仅将两者的 GND(地线) 连通共地。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









