# 超声波距离感知 ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- 本项目是一个基于 Quectel Pi M2 智能主控板实现的超声波距离感知示例。 系统通过幻彩 RGB 超声波模块实时测距,并由板载 PWM 驱动舵机带动探头在 0~180° 之间旋转扫描;整圈读数按方位划分为 **左 / 中 / 右** 三个扇区,据此给出「最近的物体在哪一侧」的判定,并在图形界面上以俯视极坐标图、距离曲线等方式实时呈现。 ```{image} images/image_FjUhb7xP7oZPTmxsiyhcqcxXnRf.webp :width: 2289px :height: 1080px :align: center ``` # 开发资源汇总 ## 开发配件清单 > 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 > > 本示例使用 **降压稳压模块** 统一供电:主控板与舵机分别接模块的 **不同输出通道**,既省掉一个独立电源,又天然隔绝了舵机电流对主控板的干扰。请勿把舵机与主控板接在同一路输出上。 ## 配件实物参考 ```{image} images/image_Ry8JbKUCZoTKhvx6urNcXPdNndg.webp :width: 913px :height: 437px :align: center ``` # 快速上手 ## 开发准备 **Quectel Pi M2** 智能主控板出厂默认搭载 **Debian 13** 系统镜像,因此无需再次烧录镜像。 本示例需要用到 **I2C7** 与 **板载 PWM(PWM2_CH3)** 两路外设,其中: - I2C7 默认已可用,对应设备节点 `/dev/i2c-7`; - PWM 通道需要用 root 权限 **一次性** 导出并放开权限(内核 sysfs 的 `export` 与导出后的属性默认只有 root 可写),执行一次 `deploy/setup_servo_pwm.sh` 即可,脚本会同时安装开机自启服务与 udev 规则,之后重启也无需再授权。 ## 硬件连接 完成以下所有硬件连接操作,序号与下表一致: ```{image} images/image_OFp0bZvXuoICbDxjaANcOYHsnDb.webp :width: 627px :height: 463px :align: center ``` 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 克隆项目代码(仓库地址发布前请替换为实际地址)。 ```bash git clone https://github.com/Quectel-Pi/demo-ultrasonic-sensing.git ``` 执行完成后,当前目录下应生成 demo-ultrasonic-sensing 文件夹。 ### 安装依赖 进入项目目录并创建虚拟环境,安装唯一依赖 PySide6。 ```bash 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 密码)。 ```bash sudo bash deploy/setup_servo_pwm.sh ``` 终端出现授权成功提示即完成。脚本是幂等的,重复执行不会产生副作用;`--status` 可只查看状态,`--revert` 可卸载开机自启服务。 ### 验证环境 输出 *6.9.1* 则表示依赖安装成功。 ```bash .venv/bin/python -c "import PySide6; print(PySide6.__version__)" ``` ### 运行程序 在项目根目录下执行启动脚本 *run_scan.py*(它会自动挑选含 PySide6 的解释器,与从哪个目录调用无关);若程序正常启动,会打开图形界面窗口。 ```bash 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`): ```{image} images/image_AJNmbz11lo3FKXxe4CDcUjcinQS.webp :width: 1320px :height: 860px :align: center ``` 点击 `⟳ Rotate & Scan (180°)` 后扫描进行中:按钮变为 `Scanning… n/N`,极坐标图上的采样点随扫描逐点累积: ```{image} images/image_Q8vkbvV7VoLSdlxG5dycemKkn9e.webp :width: 1320px :height: 860px :align: center ``` 一圈扫完后的方位判定结果(结论区与扫描图左上角同时给出 `Nearest object`): ```{image} images/image_DAvzb1GVvoJXLAx8g2YcNOilnEb.webp :width: 1320px :height: 860px :align: center ``` ### 演示视频
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,方向不要接反。 - 检查设备节点是否存在: ```shell 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 通道需要一次性授权,回到项目根目录执行: ```bash 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。 - 希望更柔和可调小巡航速度与加速度: ```python SERVO_SLEW_DPS = 150.0 # 巡航角速度上限(度/秒) SERVO_SLEW_ACCEL_DPS2 = 3000.0 # 角加速度上限(度/秒²) ``` - 若舵机电流不足也会出现抖动与异响,请检查舵机是否单独占用降压稳压模块的一路输出、该路电压是否在 5.0~5.5V,并且已与主控板共地;必要时在舵机供电端就近并联 470~1000 µF 电容。 ## 距离读数偶尔显示 --.- **现象:** 运行过程中每隔几秒出现一次 `--.-`。 **解决方案:** 这是模块固有的 5~7% 丢点(见「性能实测数据」),属正常现象,不是接线问题。可通过参数调整呈现方式: ```python HOLD_FAILS = 3 # 连续丢点不超过该次数时显示上一次读数(0 = 关闭保持) HISTORY_GAP_BRIDGE = 2 # 历史曲线允许用虚线桥接的缺口点数(0 = 不桥接) ``` ## 转到某些角度后测距连续失败 **现象:** 扫描到某个角度区间时读数连续失败,离开该角度后又恢复正常。 **解决方案:** 多为舵机瞬时电流把 5V 供电轨拉低,导致超声波模块或电平转换模块工作异常。请让舵机与主控板使用降压稳压模块的 **不同输出通道**,并确认两地 **共地**;同时确认超声波模块与电平转换模块的电源没有取在舵机那一路上。若两路共用同一个输出,请改成分路。 ## 判定结果左右相反 **现象:** 界面显示 `LEFT` 时,实物探头实际朝右。 **解决方案:** 把 `src/config.py` 中的镜像开关翻转即可(只改「0° 算左还是算右」的解释,脉宽与行程标定不受影响): ```python SERVO_INVERT = False # 默认 True ``` 可用标定脚本逐个角度核对:脚本会让探头在 0°/90°/180° 各停 3 秒,并打印程序认为的朝向,只需比对实物与提示是否一致。 ## 运行提示 No module named 'PySide6' **现象:** ``` ERROR: PySide6 is not installed. ``` **解决方案:** 依赖未安装成功,回到项目根目录重新安装: ```bash 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 ``` **解决方案:** 本项目的包导入以项目根目录为基准,请改用模块方式启动: ```bash python -m src.main ``` 或直接运行启动脚本 `./run_scan.py`。 # 技术支持与贡献 如在使用过程中遇到问题,欢迎在 [移远官方论坛]() 提交技术咨询,我们的技术支持团队将及时为您解答。 项目开源地址: https://github.com/Quectel-Pi/demo-ultrasonic-sensing 欢迎提交 Issue 反馈问题或 Pull Request 贡献代码改进!