超声波距离感知

Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.


本项目是一个基于 Quectel Pi M2 智能主控板实现的超声波距离感知示例。

系统通过幻彩 RGB 超声波模块实时测距,并由板载 PWM 驱动舵机带动探头在 0~180° 之间旋转扫描;整圈读数按方位划分为 左 / 中 / 右 三个扇区,据此给出「最近的物体在哪一侧」的判定,并在图形界面上以俯视极坐标图、距离曲线等方式实时呈现。

../../../_images/image_FjUhb7xP7oZPTmxsiyhcqcxXnRf.webp

开发资源汇总

开发配件清单

Note:tips

开发配件只是作为参考,不同的开发环境可能会有所差异

配件名称

数量

规格参数

Quectel Pi M2 智能主控板

1 块

-

降压稳压模块

1 个

多路独立输出可调降压稳压模块

7.4V 锂电池(2S)

1 个

18650锂电池组7.4v

幻彩 RGB 超声波模块

1 个

RGB超声波模块

TXS0108E 双向电平转换模块

1 个

8路电平转换模块

SG90 舵机云台

1 个

SG90舵机云台

杜邦线

若干

母对母,建议 20cm 以内以减少干扰

显示屏

1 台

8英寸触摸屏 也可使用HDMI显示器

USB 键盘、鼠标(选配)

1 套

图形界面操作

Note:tips

本示例使用 降压稳压模块 统一供电:主控板与舵机分别接模块的 不同输出通道,既省掉一个独立电源,又天然隔绝了舵机电流对主控板的干扰。请勿把舵机与主控板接在同一路输出上。

配件实物参考

../../../_images/image_Ry8JbKUCZoTKhvx6urNcXPdNndg.webp

快速上手

开发准备

Quectel Pi M2 智能主控板出厂默认搭载 Debian 13 系统镜像,因此无需再次烧录镜像。

本示例需要用到 I2C7 与 板载 PWM(PWM2_CH3) 两路外设,其中:

  • I2C7 默认已可用,对应设备节点 /dev/i2c-7;

  • PWM 通道需要用 root 权限 一次性 导出并放开权限(内核 sysfs 的 export 与导出后的属性默认只有 root 可写),执行一次 deploy/setup_servo_pwm.sh 即可,脚本会同时安装开机自启服务与 udev 规则,之后重启也无需再授权。

硬件连接

完成以下所有硬件连接操作,序号与下表一致:

../../../_images/image_OFp0bZvXuoICbDxjaANcOYHsnDb.webp
  1. 超声波模块连接: 模块为 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。

  2. 舵机连接: 舵机信号线接主控板 Pin33;电源线接 降压稳压模块的可调输出端(调至 5.0~5.5V),该路输出的地线必须与主控板 共地。

  3. 供电连接: 将 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,对应 2ade3000.pwm

降压模块 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° 即舵机行程,扇区标 LEFT / CENTER / RIGHT;红虚线弧是当前探测阈值;每个采样点画成小方块(上一圈暗、本轮亮),最近的点加高亮圈并标注距离;左上角直接写 Nearest object: …

左下 距离历史

常显

Distance history (last 30 s):最近 30 秒的距离曲线。读取失败处不连线,短缺口(≤2 个采样点)用虚线搭桥;黄虚线是探测阈值

右侧 Distance

常显

大字号读数,以及一行状态,说明 探测范围内 有没有障碍物:● Obstacle within detection range(红)/ ○ No obstacle within detection range(绿)

右侧 Detection threshold

常显

阈值滑块。该数值既决定上面那行状态,也是方位判定「读数是否入范围内」的门槛

右侧 Rotate & Scan

常显

舵机状态(含极性)、⟳ Rotate & Scan (180°) 按钮、Continuous scan (repeat) 持续扫描开关、Enable servo 舵机使能开关,以及结论区 NEAREST OBJECT + 扇区 + 距离明细

右侧 ▸ Probe direction & scan step

收起(嵌在 Rotate & Scan 内)

探头朝向滑块(0° 左 · 90° 前 · 180° 右)与扫描步进

右侧 ▸ Link statistics

收起

刷新率、单次耗时、累计丢点、原始读数、中值窗口

右侧 ▸ Indicator LEDs

收起

障碍指示开关、目标探头(双探头 / R 收 / T 发)、8 个预设色、自定义颜色

右侧 ▸ Event log

收起

运行日志;收起时标题带未读条数(如 Event log (5)),展开即清零

底部 状态栏

常显

I2C 节点 · 舵机状态 · scanning/static · 目标刷新率 · 阈值

扫描方式与方位判定

扫描有两种触发方式,共用同一套扫描与判定逻辑:

方式

触发点

行为

单次扫描(默认)

点击 ⟳ Rotate & Scan (180°) 按钮

扫完一整圈 → 给出结论 → 舵机回到 90° 停靠位。扫描期间按钮显示 Scanning… n/N 并禁用,重复点击不会排队重扫

持续扫描

勾选 Continuous scan (repeat)(或 --auto-scan)

一圈接一圈,每圈刷新一次结论;取消勾选后回到停靠位

界面只使用 一个角度坐标系(观察角):0° = 左、90° = 正前方、180° = 右。舵机角与观察角在本示例的实物装配下是镜像关系(出厂参数 SERVO_INVERT = True):

界面观察角

0°(左)

90°(正前方)

180°(右)

对应舵机角

180°

90°

0°

该镜像关系只解释「0° 算左还是算右」,与脉宽、行程标定无关;若你的云台装配方向相反,把 src/config.py 中的 SERVO_INVERT 翻转即可,停靠角 90° 在任何情况下都是正前方。

判定规则(结论 = 最近物体所在的那一扇区):

  1. 只统计 落在探测范围内(≤ 阈值)的有效读数,无回波(读数为 0)的采样点直接丢弃;

  2. 按观察角把整圈读数归入三个扇区:Left 0~60°、Center 60~120°、Right 120~180°;

  3. 每个扇区各取 最近 的那一点;

  4. 三个扇区里距离最小的那个即为结论;

  5. 与结论相差不到 DIRECTION_MARGIN_CM(默认 3 cm)的扇区,只作为明细行里的 also close: … 提示,不改变结论;

  6. 三个扇区都没有范围内读数时给出 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):

../../../_images/image_AJNmbz11lo3FKXxe4CDcUjcinQS.webp

点击 ⟳ Rotate & Scan (180°) 后扫描进行中:按钮变为 Scanning… n/N,极坐标图上的采样点随扫描逐点累积:

../../../_images/image_Q8vkbvV7VoLSdlxG5dycemKkn9e.webp

一圈扫完后的方位判定结果(结论区与扫描图左上角同时给出 Nearest object):

../../../_images/image_DAvzb1GVvoJXLAx8g2YcNOilnEb.webp

演示视频

ultrasonic_distance_perception

常见问题及解决方案

界面显示 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 贡献代码改进!