超声波距离感知¶
Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.
本项目是一个基于 Quectel Pi M2 智能主控板实现的超声波距离感知示例。
系统通过幻彩 RGB 超声波模块实时测距,并由板载 PWM 驱动舵机带动探头在 0~180° 之间旋转扫描;整圈读数按方位划分为 左 / 中 / 右 三个扇区,据此给出「最近的物体在哪一侧」的判定,并在图形界面上以俯视极坐标图、距离曲线等方式实时呈现。
开发资源汇总¶
开发配件清单¶
Note:tips
开发配件只是作为参考,不同的开发环境可能会有所差异
配件名称 |
数量 |
规格参数 |
|---|---|---|
Quectel Pi M2 智能主控板 |
1 块 |
- |
降压稳压模块 |
1 个 |
多路独立输出可调降压稳压模块 |
7.4V 锂电池(2S) |
1 个 |
|
幻彩 RGB 超声波模块 |
1 个 |
|
TXS0108E 双向电平转换模块 |
1 个 |
|
SG90 舵机云台 |
1 个 |
|
杜邦线 |
若干 |
母对母,建议 20cm 以内以减少干扰 |
显示屏 |
1 台 |
8英寸触摸屏 也可使用HDMI显示器 |
USB 键盘、鼠标(选配) |
1 套 |
图形界面操作 |
Note:tips
本示例使用 降压稳压模块 统一供电:主控板与舵机分别接模块的 不同输出通道,既省掉一个独立电源,又天然隔绝了舵机电流对主控板的干扰。请勿把舵机与主控板接在同一路输出上。
配件实物参考¶
快速上手¶
开发准备¶
Quectel Pi M2 智能主控板出厂默认搭载 Debian 13 系统镜像,因此无需再次烧录镜像。
本示例需要用到 I2C7 与 板载 PWM(PWM2_CH3) 两路外设,其中:
I2C7 默认已可用,对应设备节点
/dev/i2c-7;PWM 通道需要用 root 权限 一次性 导出并放开权限(内核 sysfs 的
export与导出后的属性默认只有 root 可写),执行一次deploy/setup_servo_pwm.sh即可,脚本会同时安装开机自启服务与 udev 规则,之后重启也无需再授权。
硬件连接¶
完成以下所有硬件连接操作,序号与下表一致:
超声波模块连接: 模块为 5V 器件,而主控板 GPIO 为 3.3V 电平,必须经电平转换 后再接到主控板(模块 4 pin 顺序为
VCC / SDA / SCL / GND,接反可能损坏模块;模块板载IIC / UART焊盘需处于 I2C 位置)。模块 VCC 与电平转换模块的 5V 侧(VCCB)都从整机 5V 网络取电(本示例分别取在排针 Pin2 与 Pin4);模块 SDA / SCL 接电平转换 B1 / B2,再由 A1 / A2 接到主控板 Pin27 / Pin28。舵机连接: 舵机信号线接主控板 Pin33;电源线接 降压稳压模块的可调输出端(调至 5.0~5.5V),该路输出的地线必须与主控板 共地。
供电连接: 将 7.4V 锂电池接入降压稳压模块的输入端;用万用表确认两路输出无误后,把模块 5V 固定输出端 接到主控板 Pin2(5V) 与 Pin14(GND),可调输出端 接舵机,此时系统上电。
Note:tips
使用 40-Pin 排针给主控板供电时,不要同时插入 USB-C 电源,避免两路 5V 相互倒灌;两者选其一。另外请确认该路输出稳定在 5V(不得超过 5.25V),过压会损坏主控板。
具体引脚对应关系如下:
Quectel Pi M2 引脚 |
连接对象 |
说明 |
|---|---|---|
Pin1(3V3) |
电平转换模块 VCCA |
3.3V 侧参考电压,由主控板供给 |
Pin9(GND) |
电平转换模块 GND |
3.3V 侧地参考 |
Pin4(5V) |
电平转换模块 VCCB |
5V 侧参考电压(与 Pin2 同属排针 5V 网络) |
Pin2(5V) |
超声波模块 VCC |
模块供电 5V(模块支持 2.8~5.5V) |
Pin27(I2C7_SDA) |
电平转换模块 A1 |
3.3V 侧数据 |
Pin28(I2C7_SCL) |
电平转换模块 A2 |
3.3V 侧时钟 |
电平转换模块 B1 / B2 |
超声波模块 SDA / SCL |
5V 侧数据 / 时钟 |
Pin2(5V) |
降压模块 5V 固定输出(+) |
给主控板供电;不要接反、不要超过 5.25V |
Pin14(GND,排针任一 GND 均可) |
降压模块 GND 端 |
主控板地,同时是全系统公共地 |
Pin33(PWM) |
舵机 信号线(橙/黄) |
PWM2_CH3,对应 |
降压模块 5V 固定输出(+) |
超声波模块 VCC、电平转换模块 VCCB |
也可分别从排针 Pin2 / Pin4 取电,属同一个 5V 网络 |
降压模块 可调输出(+)(5.0~5.5V) |
舵机 电源线(红) |
单独一路,不要与主控板共用同一路输出 |
降压模块 GND 端 |
舵机地线、超声波模块 GND、电平转换模块 GND、主控板 GND |
所有地都汇到降压模块 GND 端,属于同一个地网络 |
Note:tips
舵机堵转或启动瞬间电流可达 500~700 mA。若舵机与主控板共用同一路输出,这个冲击会把 5V 轨瞬时拉低,可能同时导致超声波模块和电平转换模块离线(现象是转到某些角度后测距连续失败)。本示例使用降压模块的 两路独立输出 分别供电,天然避免了这个干扰。
项目实现¶
获取代码¶
在智能主控板上打开终端,使用 git 克隆项目代码(仓库地址发布前请替换为实际地址)。
git clone https://github.com/Quectel-Pi/demo-ultrasonic-sensing.git
执行完成后,当前目录下应生成 demo-ultrasonic-sensing 文件夹。
安装依赖¶
进入项目目录并创建虚拟环境,安装唯一依赖 PySide6。
cd demo-ultrasonic-sensing
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
Note:tips
板上系统自带的 python3 通常没有 pip 模块,且 sudo 需要密码,因此 不要 尝试
sudo apt install python3-pip;用python3 -m venv创建的环境自带 pip。
PWM授权¶
舵机 PWM 一次性授权(需要输入 root 密码)。
sudo bash deploy/setup_servo_pwm.sh
终端出现授权成功提示即完成。脚本是幂等的,重复执行不会产生副作用;--status 可只查看状态,--revert 可卸载开机自启服务。
验证环境¶
输出 6.9.1 则表示依赖安装成功。
.venv/bin/python -c "import PySide6; print(PySide6.__version__)"
运行程序¶
在项目根目录下执行启动脚本 run_scan.py(它会自动挑选含 PySide6 的解释器,与从哪个目录调用无关);若程序正常启动,会打开图形界面窗口。
cd demo-ultrasonic-sensing
python3 run_scan.py
界面说明¶
左列是两块视图(扫描图 + 距离历史),右列是控制台。
右侧控制台 常显的只有三个分组框 —— Distance(实时距离)、Detection threshold(探测阈值)、Rotate & Scan(扫描与结论),它们上方是面板标题、I2C 副标题与链路状态行;其余 四个分区默认收起,点标题栏即可展开(其中 Probe direction & scan step 嵌在 Rotate & Scan 内部)。
区域 |
显示状态 |
内容 |
|---|---|---|
左上 扫描视图 |
常显 |
俯视极坐标图。圆心是可旋转的探头(R/T 换能器 + 两颗 RGB 灯),上半圆 0~180° 即舵机行程,扇区标 |
左下 距离历史 |
常显 |
|
右侧 |
常显 |
大字号读数,以及一行状态,说明 探测范围内 有没有障碍物: |
右侧 |
常显 |
阈值滑块。该数值既决定上面那行状态,也是方位判定「读数是否入范围内」的门槛 |
右侧 |
常显 |
舵机状态(含极性)、 |
右侧 |
收起(嵌在 |
探头朝向滑块(0° 左 · 90° 前 · 180° 右)与扫描步进 |
右侧 |
收起 |
刷新率、单次耗时、累计丢点、原始读数、中值窗口 |
右侧 |
收起 |
障碍指示开关、目标探头(双探头 / R 收 / T 发)、8 个预设色、自定义颜色 |
右侧 |
收起 |
运行日志;收起时标题带未读条数(如 |
底部 状态栏 |
常显 |
I2C 节点 · 舵机状态 · scanning/static · 目标刷新率 · 阈值 |
扫描方式与方位判定¶
扫描有两种触发方式,共用同一套扫描与判定逻辑:
方式 |
触发点 |
行为 |
|---|---|---|
单次扫描(默认) |
点击 |
扫完一整圈 → 给出结论 → 舵机回到 90° 停靠位。扫描期间按钮显示 |
持续扫描 |
勾选 |
一圈接一圈,每圈刷新一次结论;取消勾选后回到停靠位 |
界面只使用 一个角度坐标系(观察角):0° = 左、90° = 正前方、180° = 右。舵机角与观察角在本示例的实物装配下是镜像关系(出厂参数 SERVO_INVERT = True):
界面观察角 |
0°(左) |
90°(正前方) |
180°(右) |
|---|---|---|---|
对应舵机角 |
180° |
90° |
0° |
该镜像关系只解释「0° 算左还是算右」,与脉宽、行程标定无关;若你的云台装配方向相反,把
src/config.py中的SERVO_INVERT翻转即可,停靠角 90° 在任何情况下都是正前方。
判定规则(结论 = 最近物体所在的那一扇区):
只统计 落在探测范围内(≤ 阈值)的有效读数,无回波(读数为 0)的采样点直接丢弃;
按观察角把整圈读数归入三个扇区:
Left0~60°、Center60~120°、Right120~180°;每个扇区各取 最近 的那一点;
三个扇区里距离最小的那个即为结论;
与结论相差不到
DIRECTION_MARGIN_CM(默认 3 cm)的扇区,只作为明细行里的also close: …提示,不改变结论;三个扇区都没有范围内读数时给出
NONE IN RANGE,明细行仍会显示整圈最近读数。
实现原理¶
测距¶
模块使用 I2C 接口,流程为:向寄存器 0x10 写入 0x01 触发一次测量,等待约 100 ms,再从模块读取 2 字节大端数据。
距离换算关系为 距离(cm) = 原始值 × 0.017,有效量程 2~400 cm。驱动层会对超出量程的读数(例如 I2C 位翻转造成的 0x7FFF)做丢弃,因此异常值不会进入界面与判定逻辑。
舵机¶
舵机由 内核 sysfs PWM 直接驱动(不使用软件 PWM,避免用户态抖动造成角度跳动),频率 50 Hz,脉宽 500~2500 µs 对应 0~180°。
驱动按 PWM 控制器设备名(2ade3000.pwm)查找通道,而不是 pwmchipN 编号,因为编号会随内核与设备树探测顺序变化。本示例使用的板载通道为 Pin33 = PWM2_CH3 = pwmchip2/pwm0。
在板级实测中遇到并已处理的两个内核约束:
通道必须先写入非零的
period,之后才能写enable/duty_cycle,否则返回EINVAL;该通道导出后的默认极性是
inversed,而舵机需要normal,驱动会显式纠正并把极性显示在界面上。
运动平滑¶
PWM 舵机没有片上缓动,脉宽一写即变。若把目标角一次性写下去,从停靠位 90° 到扫描起点 0° 就是 位置阶跃:舵机内部控制器全速冲刺,齿轮撞击声大、容易过冲,还会产生电流尖峰。
因此驱动按 梯形速度曲线 分步逼近目标角:先按限定加速度加速,再匀速,最后按「剩余距离内刹得住」减速到 0。一次大角度移动被拆成几十次小步下发,实测耗时如下:
位移 |
5°(单步) |
30° |
90°(停靠位→扫描起点) |
180°(全行程) |
|---|---|---|---|---|
耗时 |
0.065 s |
0.175 s |
0.425 s |
0.800 s |
由于落位时速度已接近 0,机械余振显著减小,稳定等待时间得以缩短,正好抵消斜坡本身的开销。
性能实测数据¶
项目 |
实测结果 |
|---|---|
单次测距耗时 |
约 100 ms 测量 + 约 60 ms 通信开销 |
静态刷新率 |
约 5 Hz(200 ms 周期下 10/10 次读取干净) |
默认 5° 步进扫一圈 |
37 个采样角,11.3 s |
舵机定位重复性 |
连续两圈 13 个角度逐点重合到 0.1 cm |
模块固有丢点率 |
5~7%(对比实验:立即重读 0/1/2 次的最终有效率分别为 93.3% / 95.0% / 93.3%) |
舵机对测距的干扰 |
无(保持、释放、扫动三种状态下有效率一致) |
丢点属于模块固有特性,因此界面层采用「短时保持 + 曲线虚线搭桥」的方式呈现:连续丢点不超过
HOLD_FAILS(默认 3)次时显示上一次的读数(数字变暗),历史曲线对 ≤2 个点的缺口用虚线段桥接并与真实数据区分;链路统计区提供 累计丢点 计数,便于判断是否真的改善。
应用演示¶
图形界面¶
界面静止状态(真实硬件,读数在探测范围内,状态行显示 Obstacle within detection range):
点击 ⟳ Rotate & Scan (180°) 后扫描进行中:按钮变为 Scanning… n/N,极坐标图上的采样点随扫描逐点累积:
一圈扫完后的方位判定结果(结论区与扫描图左上角同时给出 Nearest object):
演示视频¶
常见问题及解决方案¶
界面显示 SIMULATION(仿真模式)¶
现象:
状态栏:SIMULATION (/dev/i2c-7 not opened)
链路行:○ SIMULATION · I2C bus not opened
(若是在真机模式下总线打开失败,链路行会带上具体原因,例如
SIMULATION · cannot open I2C bus /dev/i2c-7: [Errno 2] No such file or directory。)
解决方案:
确认超声波模块已接好、供电正常,且电平转换模块 A 侧为 3.3V、B 侧为 5V,方向不要接反。
检查设备节点是否存在:
ls -la /dev/i2c*
确认当前用户在
i2c组内(groups命令查看);若不在,加入后需要重新登录。查看界面
Event log或状态栏中的具体报错原因。
舵机不转动,日志提示没有 sysfs 写权限¶
现象:
No write access to /sys/class/pwm (kernel default: root only).
Run this ONCE on the board:
sudo bash deploy/setup_servo_pwm.sh
界面上舵机状态行显示为仿真态(◐ SIMULATION · No write access to …),舵机不会真正转动。
解决方案:
PWM 通道需要一次性授权,回到项目根目录执行:
sudo bash deploy/setup_servo_pwm.sh
脚本会导出通道、放开 dialout 权限,并安装开机自启服务与 udev 规则,之后无需重复授权。执行 sudo bash deploy/setup_servo_pwm.sh --status 可只查看当前授权状态。
自动扫描勾选框点不动 / 自动弹回¶
现象:
Continuous scan (repeat) 勾选后立刻恢复未勾选状态,日志给出说明:
Servo PWM is not actually driven (…), so the probe never turns and any
direction result would be meaningless — automatic scanning is disabled
解决方案:
这是 设计如此,不是故障。当出现「真超声波 + 仿真舵机」的组合时(即测距是真实硬件、但 PWM 未授权导致舵机不会真正转动),程序会拒绝扫描:此时探头始终朝同一方向,任何方位结论都是假的。先按上一条完成 PWM 授权即可恢复。
舵机转动时响声大、起步很冲¶
现象:
舵机从停靠位转到扫描起点时声音很大,有明显的齿轮撞击声。
解决方案:
确认已在最新版本上运行(运动平滑默认开启)。若曾手动关闭,请确认
src/config.py中SERVO_SLEW_DPS不为 0。希望更柔和可调小巡航速度与加速度:
SERVO_SLEW_DPS = 150.0 # 巡航角速度上限(度/秒)
SERVO_SLEW_ACCEL_DPS2 = 3000.0 # 角加速度上限(度/秒²)
若舵机电流不足也会出现抖动与异响,请检查舵机是否单独占用降压稳压模块的一路输出、该路电压是否在 5.0~5.5V,并且已与主控板共地;必要时在舵机供电端就近并联 470~1000 µF 电容。
距离读数偶尔显示 --.-¶
现象:
运行过程中每隔几秒出现一次 --.-。
解决方案:
这是模块固有的 5~7% 丢点(见「性能实测数据」),属正常现象,不是接线问题。可通过参数调整呈现方式:
HOLD_FAILS = 3 # 连续丢点不超过该次数时显示上一次读数(0 = 关闭保持)
HISTORY_GAP_BRIDGE = 2 # 历史曲线允许用虚线桥接的缺口点数(0 = 不桥接)
转到某些角度后测距连续失败¶
现象:
扫描到某个角度区间时读数连续失败,离开该角度后又恢复正常。
解决方案:
多为舵机瞬时电流把 5V 供电轨拉低,导致超声波模块或电平转换模块工作异常。请让舵机与主控板使用降压稳压模块的 不同输出通道,并确认两地 共地;同时确认超声波模块与电平转换模块的电源没有取在舵机那一路上。若两路共用同一个输出,请改成分路。
判定结果左右相反¶
现象:
界面显示 LEFT 时,实物探头实际朝右。
解决方案:
把 src/config.py 中的镜像开关翻转即可(只改「0° 算左还是算右」的解释,脉宽与行程标定不受影响):
SERVO_INVERT = False # 默认 True
可用标定脚本逐个角度核对:脚本会让探头在 0°/90°/180° 各停 3 秒,并打印程序认为的朝向,只需比对实物与提示是否一致。
运行提示 No module named ‘PySide6’¶
现象:
ERROR: PySide6 is not installed.
解决方案:
依赖未安装成功,回到项目根目录重新安装:
cd demo-ultrasonic-sensing
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt \
-i https://mirrors.aliyun.com/pypi/simple/ \
--trusted-host mirrors.aliyun.com
直接执行 python src/main.py 报导入错误¶
现象:
ImportError: attempted relative import with no known parent package
解决方案:
本项目的包导入以项目根目录为基准,请改用模块方式启动:
python -m src.main
或直接运行启动脚本 ./run_scan.py。
技术支持与贡献¶
如在使用过程中遇到问题,欢迎在 移远官方论坛 提交技术咨询,我们的技术支持团队将及时为您解答。
项目开源地址: https://github.com/Quectel-Pi/demo-ultrasonic-sensing
欢迎提交 Issue 反馈问题或 Pull Request 贡献代码改进!