在智能硬件项目中,距离检测是一类非常常见的基础能力。自动避障小车需要判断前方是否存在障碍物,智能垃圾桶需要感知物体靠近,仓储设备需要检测物体位置,教学实验中也常用距离数据来演示"传感器如何把真实世界转换成程序变量"。这些场景背后的核心逻辑并不复杂:硬件负责采集环境变化,程序负责读取数据、计算结果并输出判断。
本实验使用 CanMV K210 开发板连接 HC-SR04 超声波测距模块,通过 Trig 引脚发送触发脉冲,通过 Echo 引脚接收回波信号,并根据 Echo 高电平持续时间换算距离。程序会持续输出当前距离,并按照距离范围显示"距离过近""正常检测""距离较远""超出常用检测范围"等状态。
| 学习目标 | 说明 |
|---|---|
| 理解超声波测距原理 | 认识 Trig 触发、Echo 回波和声波往返时间之间的关系 |
| 掌握 GPIO 输入输出 | 使用 GPIOHS 输出触发脉冲,并读取 Echo 输入电平 |
| 学会微秒级计时 | 使用 ticks_us() 记录 Echo 高电平持续时间 |
| 完成距离换算 | 将回波时间转换成厘米或毫米距离 |
| 建立传感器处理流程 | 把测距、异常处理、平均滤波和状态判断组合成完整实验 |
这段代码的价值不只是完成一次测距,而是把传感器驱动、GPIO 输入输出、脉冲计时、异常处理、数据平均和状态判断整合成一个完整实验。对于硬件编程入门而言,这类代码可以直观看到 Python 如何读取真实环境数据,并把物理距离转换成可判断、可输出、可扩展的程序状态。
文章目录
理论基础
HC-SR04 超声波测距模块的工作过程可以理解成一次"发出声音并等待回声"的过程。程序通过 Trig 引脚给模块一个短暂高电平脉冲,模块开始发射超声波;超声波遇到障碍物后反射回来,Echo 引脚会输出一个高电平脉冲。这个高电平持续时间越长,说明声波往返距离越远;持续时间越短,说明障碍物越近。
程序读取到的并不是距离本身,而是 Echo 高电平持续了多少微秒。由于超声波从模块发出后,需要到达障碍物再反射回来,所以 Echo 时间对应的是"往返时间"。计算单程距离时,需要把时间除以 2,再结合声速进行换算。代码中的 distance_cm() 使用 (pulse_time / 2) / 29.1 计算厘米距离,表示声波传播约 29.1 微秒对应 1 厘米。
超声波测距实验比普通按键实验多了计时过程。按键实验只需要判断某个 GPIO 是高电平还是低电平,而 HC-SR04 需要先输出触发脉冲,再等待 Echo 从低电平变成高电平,接着记录 Echo 高电平持续时间,最后把时间转换成距离。这个过程把 GPIO 输出、GPIO 输入、微秒计时和数学换算连接到了一起。
#mermaid-svg-QebVc3BAwnl3GuUP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QebVc3BAwnl3GuUP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QebVc3BAwnl3GuUP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QebVc3BAwnl3GuUP .error-icon{fill:#552222;}#mermaid-svg-QebVc3BAwnl3GuUP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QebVc3BAwnl3GuUP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QebVc3BAwnl3GuUP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QebVc3BAwnl3GuUP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QebVc3BAwnl3GuUP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QebVc3BAwnl3GuUP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QebVc3BAwnl3GuUP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QebVc3BAwnl3GuUP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QebVc3BAwnl3GuUP .marker.cross{stroke:#333333;}#mermaid-svg-QebVc3BAwnl3GuUP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QebVc3BAwnl3GuUP p{margin:0;}#mermaid-svg-QebVc3BAwnl3GuUP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QebVc3BAwnl3GuUP .cluster-label text{fill:#333;}#mermaid-svg-QebVc3BAwnl3GuUP .cluster-label span{color:#333;}#mermaid-svg-QebVc3BAwnl3GuUP .cluster-label span p{background-color:transparent;}#mermaid-svg-QebVc3BAwnl3GuUP .label text,#mermaid-svg-QebVc3BAwnl3GuUP span{fill:#333;color:#333;}#mermaid-svg-QebVc3BAwnl3GuUP .node rect,#mermaid-svg-QebVc3BAwnl3GuUP .node circle,#mermaid-svg-QebVc3BAwnl3GuUP .node ellipse,#mermaid-svg-QebVc3BAwnl3GuUP .node polygon,#mermaid-svg-QebVc3BAwnl3GuUP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QebVc3BAwnl3GuUP .rough-node .label text,#mermaid-svg-QebVc3BAwnl3GuUP .node .label text,#mermaid-svg-QebVc3BAwnl3GuUP .image-shape .label,#mermaid-svg-QebVc3BAwnl3GuUP .icon-shape .label{text-anchor:middle;}#mermaid-svg-QebVc3BAwnl3GuUP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QebVc3BAwnl3GuUP .rough-node .label,#mermaid-svg-QebVc3BAwnl3GuUP .node .label,#mermaid-svg-QebVc3BAwnl3GuUP .image-shape .label,#mermaid-svg-QebVc3BAwnl3GuUP .icon-shape .label{text-align:center;}#mermaid-svg-QebVc3BAwnl3GuUP .node.clickable{cursor:pointer;}#mermaid-svg-QebVc3BAwnl3GuUP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QebVc3BAwnl3GuUP .arrowheadPath{fill:#333333;}#mermaid-svg-QebVc3BAwnl3GuUP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QebVc3BAwnl3GuUP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QebVc3BAwnl3GuUP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QebVc3BAwnl3GuUP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QebVc3BAwnl3GuUP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QebVc3BAwnl3GuUP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QebVc3BAwnl3GuUP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QebVc3BAwnl3GuUP .cluster text{fill:#333;}#mermaid-svg-QebVc3BAwnl3GuUP .cluster span{color:#333;}#mermaid-svg-QebVc3BAwnl3GuUP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QebVc3BAwnl3GuUP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QebVc3BAwnl3GuUP rect.text{fill:none;stroke-width:0;}#mermaid-svg-QebVc3BAwnl3GuUP .icon-shape,#mermaid-svg-QebVc3BAwnl3GuUP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QebVc3BAwnl3GuUP .icon-shape p,#mermaid-svg-QebVc3BAwnl3GuUP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QebVc3BAwnl3GuUP .icon-shape .label rect,#mermaid-svg-QebVc3BAwnl3GuUP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QebVc3BAwnl3GuUP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QebVc3BAwnl3GuUP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QebVc3BAwnl3GuUP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-QebVc3BAwnl3GuUP .ctrl>*{fill:#E8F3FF!important;stroke:#5B9BFF!important;stroke-width:2px!important;color:#1F3B6D!important;}#mermaid-svg-QebVc3BAwnl3GuUP .ctrl span{fill:#E8F3FF!important;stroke:#5B9BFF!important;stroke-width:2px!important;color:#1F3B6D!important;}#mermaid-svg-QebVc3BAwnl3GuUP .ctrl tspan{fill:#1F3B6D!important;}#mermaid-svg-QebVc3BAwnl3GuUP .sensor>*{fill:#EAFBF1!important;stroke:#42B983!important;stroke-width:2px!important;color:#1F5C3A!important;}#mermaid-svg-QebVc3BAwnl3GuUP .sensor span{fill:#EAFBF1!important;stroke:#42B983!important;stroke-width:2px!important;color:#1F5C3A!important;}#mermaid-svg-QebVc3BAwnl3GuUP .sensor tspan{fill:#1F5C3A!important;}#mermaid-svg-QebVc3BAwnl3GuUP .calc>*{fill:#F4EEFF!important;stroke:#8E6AD8!important;stroke-width:2px!important;color:#3D2B68!important;}#mermaid-svg-QebVc3BAwnl3GuUP .calc span{fill:#F4EEFF!important;stroke:#8E6AD8!important;stroke-width:2px!important;color:#3D2B68!important;}#mermaid-svg-QebVc3BAwnl3GuUP .calc tspan{fill:#3D2B68!important;}#mermaid-svg-QebVc3BAwnl3GuUP .power>*{fill:#FCEEF4!important;stroke:#E76F51!important;stroke-width:2px!important;color:#7A2740!important;}#mermaid-svg-QebVc3BAwnl3GuUP .power span{fill:#FCEEF4!important;stroke:#E76F51!important;stroke-width:2px!important;color:#7A2740!important;}#mermaid-svg-QebVc3BAwnl3GuUP .power tspan{fill:#7A2740!important;}#mermaid-svg-QebVc3BAwnl3GuUP .warn>*{fill:#FFF4E8!important;stroke:#F4A261!important;stroke-width:2px!important;color:#7A4A00!important;}#mermaid-svg-QebVc3BAwnl3GuUP .warn span{fill:#FFF4E8!important;stroke:#F4A261!important;stroke-width:2px!important;color:#7A4A00!important;}#mermaid-svg-QebVc3BAwnl3GuUP .warn tspan{fill:#7A4A00!important;} Python 程序
启动一次测距
CanMV IO6
GPIOHS0 输出
HC-SR04 Trig
10us 高电平触发
超声波发射
遇到目标后反射
HC-SR04 Echo
输出高电平脉冲
CanMV IO7
GPIOHS1 输入计时
时间换算
pulse_time → distance_cm
状态判断
过近 / 正常 / 较远 / 超出范围
5V / GND
模块供电与公共地
Echo 电平保护
分压或电平转换
这张流程图展示的是超声波测距的完整电路和数据流程。CanMV K210 负责输出 Trig 脉冲和读取 Echo 电平,HC-SR04 负责发射和接收超声波,程序负责计算 Echo 高电平时间并换算距离。真正进入应用逻辑的不是 Echo 电平本身,而是经过换算后的厘米距离和状态文本。
需要特别注意的是,HC-SR04 常见模块使用 5V 供电,Echo 输出也可能是 5V 电平,而 CanMV K210 的 IO 通常是 3.3V 逻辑。程序层面可以读取 Echo 状态,但电气保护必须在硬件接线阶段完成。实际实验时,建议在 Echo 与 CanMV IO7 之间加入电阻分压或电平转换模块,降低 5V 信号直接进入开发板 IO 的风险。
硬件设施
本实验围绕 HC-SR04 超声波测距模块和 CanMV K210 的 GPIO 控制能力展开。代码中没有使用 LCD、蜂鸣器、摄像头、按键、电机等模块,因此这些内容不作为本节讲解重点。软件侧主要依赖 time、maix.GPIO 和 fpioa_manager.fm,它们分别负责延时与计时、GPIO 输入输出控制、物理引脚功能映射。
接线关系可以先通过下面这张图片建立整体印象。当前实验真正用到 HC-SR04 的 VCC、GND、Trig 和 Echo 四个接口,其中 Trig 接 CanMV IO6,Echo 接 CanMV IO7,模块供电和开发板需要共地。

| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 负责执行 MicroPython 程序,并通过 GPIO 与超声波模块通信 |
| HC-SR04 超声波模块 | 距离检测模块 | 通过 Trig 触发超声波发射,通过 Echo 返回回波脉冲宽度 |
| 5V 电源 | 模块供电 | HC-SR04 通常使用 5V 供电,供电不足会影响测距稳定性 |
| GND | 公共地 | 开发板与传感器需要共地,保证信号电平参考一致 |
| IO6 | Trig 触发线 | 代码中作为 TRIGGER_PIN,用于输出 10us 高电平触发信号 |
| IO7 | Echo 回波线 | 代码中作为 ECHO_PIN,用于读取回波高电平持续时间 |
maix.GPIO |
GPIO 控制模块 | 用于创建 Trig 输出对象和 Echo 输入对象 |
fpioa_manager.fm |
引脚映射模块 | 用于把物理引脚注册为 GPIOHS 功能 |
time |
延时与计时模块 | 使用 sleep、sleep_us、ticks_us 控制脉冲和计算时间 |
实验中使用的核心模块如下。HC-SR04 负责测距,CanMV K210 负责输出触发脉冲并读取回波时间。接线时除了关注 IO6 和 IO7,还需要重点检查供电、共地和 Echo 电平保护。

接线关系在代码注释和硬件配置区中已经明确给出。TRIGGER_PIN = 6 表示 HC-SR04 的 Trig 接到 CanMV IO6,ECHO_PIN = 7 表示 Echo 接到 CanMV IO7。类初始化时,程序又通过 fm.register() 将 IO6 注册为 GPIOHS0,将 IO7 注册为 GPIOHS1,进一步完成物理引脚到内部 GPIO 功能的绑定。
| 物理引脚 / 接口 | GPIO 功能 | 代码变量 | 对应硬件 | 说明 |
|---|---|---|---|---|
| 5V | 电源 | 无 | HC-SR04 VCC | 为超声波模块供电,通常使用 5V |
| GND | 地线 | 无 | HC-SR04 GND | 与 CanMV 开发板共地 |
| IO6 | GPIOHS0 | self.trigger / TRIGGER_PIN |
HC-SR04 Trig | 负责输出触发脉冲 |
| IO7 | GPIOHS1 | self.echo / ECHO_PIN |
HC-SR04 Echo | 负责读取回波脉冲 |
| Echo 保护电路 | 分压或电平转换 | 无 | Echo 到 IO7 之间 | 建议把 Echo 电平转换到 3.3V 逻辑范围 |
物理引脚是开发板上真实连接杜邦线的位置,GPIOHS 是 K210 中用于高速 GPIO 控制的功能编号,代码变量则是程序中操作这些引脚的对象名称。当前程序把 IO6 映射为 GPIOHS0,并封装成 self.trigger,用于输出脉冲信号;把 IO7 映射为 GPIOHS1,并封装成 self.echo,用于读取输入电平。这样写可以把底层引脚细节放在驱动类内部,主循环只需要调用测距函数即可获得距离结果。
完成接线后的整体状态如下。检查时重点关注 HC-SR04 的四根线是否连接正确,尤其是 Trig 和 Echo 不要接反;Echo 接入 CanMV IO7 前建议加入分压或电平转换;被测物体应放在模块正前方,距离不要太近,也不要超过常用检测范围。

| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 程序启动 | 串口打印实验启动信息和 Trig / Echo 引脚信息 | 如果无输出,检查程序运行和串口连接 |
| 放置近距离物体 | 串口显示较小厘米数,状态可能为"距离过近" | 如果一直无回波,检查 Trig、Echo 和供电 |
| 物体在正常范围内 | 串口显示稳定距离,状态为"正常检测" | 如果数值跳动较大,检查目标表面和测量角度 |
| 物体较远 | 串口显示较大距离,状态为"距离较远" | 如果反复丢失回波,可能已接近检测极限 |
| 超出范围 | 串口提示未检测到有效回波或超出范围 | 检查目标是否过远、反射面是否太小或角度是否偏斜 |
| 靠近模块 2cm 以内 | 读数可能异常或被过滤 | HC-SR04 存在最小测距盲区,测试时保持合理距离 |
软件代码
本实验代码围绕 HC-SR04 的测距流程展开。程序把超声波模块封装成 HCSR04 类,类内部负责引脚注册、Trig 脉冲发送、Echo 电平等待、回波时间计算和距离换算。主程序负责周期性读取距离,并根据距离范围输出对应状态。这样的结构把传感器驱动和应用逻辑拆开,后续扩展显示屏、报警提示或自动避障判断时,代码会更容易维护。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能正常运行基础 print() 测试 |
| CanMV 固件 | 提供 maix.GPIO、fpioa_manager、time 等模块 |
固件环境需要支持 GPIOHS 输入输出和微秒计时 |
| USB 串口驱动 | 让电脑识别开发板串口 | 串口工具中能看到对应端口 |
| 串口终端 | 查看测距结果 | 能看到距离数值和状态文本持续输出 |
| Echo 电平保护 | 保护开发板 IO | 确认 Echo 到 IO7 之间已经做分压或电平转换 |
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CanMV K210 HC-SR04 超声波测距实验 - 单文件优化版
接线说明:
HC-SR04 VCC -> 5V
HC-SR04 GND -> GND
HC-SR04 Trig -> CanMV IO6
HC-SR04 Echo -> CanMV IO7
注意:
HC-SR04 供电通常使用 5V。
Echo 输出可能为 5V,CanMV IO 为 3.3V 逻辑,建议给 Echo 做分压或电平转换保护。
实验效果:
程序会持续读取距离,并根据距离范围输出状态:
距离过近、正常检测、距离较远、超出范围。
"""
from time import sleep, sleep_us, ticks_us
from maix import GPIO
from fpioa_manager import fm
# =========================
# 超声波驱动类
# =========================
class HCSR04:
"""
HC-SR04 超声波测距模块驱动
工作流程:
1. Trig 引脚输出一个 10us 高电平脉冲
2. Echo 引脚接收返回高电平
3. 计算 Echo 高电平持续时间
4. 根据声波往返时间换算距离
"""
def __init__(self, trigger_pin, echo_pin, echo_timeout_us=30000):
"""
trigger_pin : Trig 触发引脚
echo_pin : Echo 回响引脚
echo_timeout_us : 超时时间,单位微秒
"""
self.echo_timeout_us = echo_timeout_us
fm.register(trigger_pin, fm.fpioa.GPIOHS0, force=True)
fm.register(echo_pin, fm.fpioa.GPIOHS1, force=True)
self.trigger = GPIO(GPIO.GPIOHS0, GPIO.OUT, GPIO.PULL_NONE)
self.echo = GPIO(GPIO.GPIOHS1, GPIO.IN, GPIO.PULL_NONE)
self.trigger.value(0)
sleep_us(5)
def _wait_for_value(self, target_value, timeout_us):
"""
等待 Echo 引脚变成指定电平
返回:
True : 在超时时间内检测到目标电平
False : 超时
"""
start_time = ticks_us()
while self.echo.value() != target_value:
if ticks_us() - start_time > timeout_us:
return False
return True
def _send_pulse_and_wait(self):
"""
发送 Trig 脉冲,并计算 Echo 高电平持续时间
"""
self.trigger.value(0)
sleep_us(5)
self.trigger.value(1)
sleep_us(10)
self.trigger.value(0)
# 等待 Echo 从低电平变成高电平
if not self._wait_for_value(1, self.echo_timeout_us):
raise OSError("Out of range")
start_time = ticks_us()
# 等待 Echo 从高电平回到低电平
if not self._wait_for_value(0, self.echo_timeout_us):
raise OSError("Out of range")
end_time = ticks_us()
pulse_time = end_time - start_time
return pulse_time
def distance_mm(self):
"""
获取距离,单位:毫米
"""
pulse_time = self._send_pulse_and_wait()
# 声波需要往返,所以时间要除以 2
# 约等于 pulse_time * 100 // 582
distance = pulse_time * 100 // 582
return distance
def distance_cm(self):
"""
获取距离,单位:厘米
"""
pulse_time = self._send_pulse_and_wait()
# 声速约 343m/s,约等于 29.1us 传播 1cm
# 距离 = 时间 / 2 / 29.1
distance = (pulse_time / 2) / 29.1
return distance
# =========================
# 硬件配置区
# =========================
TRIGGER_PIN = 6
ECHO_PIN = 7
MEASURE_INTERVAL = 0.3
TOO_CLOSE_CM = 10
NORMAL_MAX_CM = 80
FAR_MAX_CM = 200
# =========================
# 创建超声波对象
# =========================
sensor = HCSR04(
trigger_pin=TRIGGER_PIN,
echo_pin=ECHO_PIN,
echo_timeout_us=30000
)
# =========================
# 距离状态判断
# =========================
def get_distance_state(distance_cm):
"""
根据距离返回状态文本
"""
if distance_cm < TOO_CLOSE_CM:
return "距离过近"
if distance_cm <= NORMAL_MAX_CM:
return "正常检测"
if distance_cm <= FAR_MAX_CM:
return "距离较远"
return "超出常用检测范围"
def read_distance_average(times=3):
"""
多次读取距离并取平均值
这样可以减少单次测量抖动,让数据显示更稳定。
"""
values = []
for _ in range(times):
try:
distance = sensor.distance_cm()
if 2 <= distance <= 400:
values.append(distance)
except OSError:
pass
sleep(0.05)
if len(values) == 0:
return None
return sum(values) / len(values)
# =========================
# 主循环
# =========================
def loop():
"""
循环读取超声波距离
"""
print("HC-SR04 超声波测距实验启动")
print("Trig: IO%d, Echo: IO%d" % (TRIGGER_PIN, ECHO_PIN))
print("-" * 35)
while True:
distance = read_distance_average(times=3)
if distance is None:
print("未检测到有效回波,可能超出范围或接线异常")
else:
state = get_distance_state(distance)
print("距离:%.2f cm,状态:%s" % (distance, state))
sleep(MEASURE_INTERVAL)
# =========================
# 程序入口
# =========================
if __name__ == "__main__":
try:
loop()
except KeyboardInterrupt:
print("程序已停止")
这段程序可以分成三个层级。底层是 HCSR04 类,负责 GPIOHS 引脚映射、Trig 脉冲输出、Echo 电平等待和回波时间计算;中间层是 distance_cm()、distance_mm() 和 read_distance_average(),负责把原始脉冲时间转换成距离,并通过多次采样降低抖动;上层是 get_distance_state() 和 loop(),负责把距离转换成状态文本并持续输出。
_send_pulse_and_wait() 是测距动作的核心。程序先让 Trig 保持低电平,再输出 10 微秒高电平脉冲,触发 HC-SR04 发射超声波。Echo 引脚变成高电平后,程序记录开始时间;Echo 回到低电平后,程序记录结束时间。两个时间点之间的差值就是回波持续时间。由于超声波从模块发出后需要到达障碍物再反射回来,所以距离换算时需要考虑往返时间。
| 函数名 | 功能 | 对应现象 |
|---|---|---|
__init__() |
初始化超声波模块 | 完成 IO6、IO7 的功能映射,并创建 Trig 输出对象和 Echo 输入对象 |
_wait_for_value() |
等待 Echo 达到目标电平 | 判断回波信号是否在超时时间内出现或结束 |
_send_pulse_and_wait() |
发送触发脉冲并计算回波时间 | 触发模块测距,得到 Echo 高电平持续时间 |
distance_mm() |
获取毫米距离 | 将回波时间换算成毫米数值 |
distance_cm() |
获取厘米距离 | 将回波时间换算成厘米数值,主循环使用该函数 |
get_distance_state() |
判断距离状态 | 根据距离范围输出距离过近、正常检测、距离较远等文本 |
read_distance_average() |
多次测距并取平均值 | 减少单次测量抖动,提高距离显示稳定性 |
loop() |
循环读取并打印结果 | 终端持续显示距离和检测状态 |
主程序中的 MEASURE_INTERVAL = 0.3 表示每轮测距之间间隔 0.3 秒。这个间隔可以避免测距频率过高导致回波干扰,也让串口终端输出更容易观察。TOO_CLOSE_CM、NORMAL_MAX_CM、FAR_MAX_CM 三个常量负责划分距离状态,后续如果实验场景发生变化,只需要调整这些阈值,不需要修改底层测距代码。
扩展应用
超声波测距实验常见问题主要集中在供电、Echo 电平保护、Trig / Echo 接线、检测范围、回波干扰和测量抖动。排查时可以先确认模块是否正常供电,再检查 IO 映射和终端输出信息。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| 终端一直提示未检测到有效回波 | Trig 或 Echo 接线错误、模块供电异常、目标物超出检测范围 | 核对 IO6 接 Trig、IO7 接 Echo,确认 HC-SR04 使用 5V 供电,并把障碍物放在合理距离内 |
| 距离数值明显不稳定 | 被测物表面不平整、角度偏斜、测量间隔过短、环境回波复杂 | 调整模块正对目标物,保持被测表面平整,适当增大 times 或 MEASURE_INTERVAL |
| 测距结果一直偏大 | Echo 回波结束检测异常、目标物反射弱、环境中存在远处反射面 | 缩短测试距离,使用较大的平整物体测试,排除周围墙面或杂物干扰 |
| 距离过近时读数异常 | HC-SR04 存在最小测距盲区 | 保持目标物距离模块 2 厘米以上,代码中已经过滤小于 2 厘米的结果 |
| 开发板 IO 存在损坏风险 | Echo 可能输出 5V,而 CanMV IO 为 3.3V 逻辑 | 在 Echo 信号线加入分压电路或电平转换模块,避免 5V 直接进入 IO7 |
| 程序启动后无任何输出 | 脚本没有运行、串口终端未连接、程序入口未执行 | 检查运行环境和终端连接,确认脚本以主程序方式执行 |
| 距离偶尔显示 None | 单次采样未收到有效回波 | 保持目标物正对模块,增加平均采样次数,确认 Echo 信号稳定 |
| 近距离检测正常,远距离不稳定 | 目标物反射面积小或角度不合适 | 使用平整硬质物体测试,避免布料、斜面或过小目标造成回波弱 |
超声波测距实验的扩展价值在于把"距离"变成程序可以判断的条件。程序读取到的距离不只是一个数字,还可以进一步驱动状态提示、避障逻辑、开关控制、报警反馈和数据记录。当前代码已经具备传感器封装、异常处理、平均滤波和状态判断能力,后续接入其他模块时,只需要把距离结果作为输入条件即可。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 智能避障小车 | 根据前方距离判断是否需要减速、停止或转向 | 后续可接入电机控制,实现自动避障 |
| 智能垃圾桶感应 | 检测手部或物体靠近,距离小于阈值时触发开盖动作 | 后续可接入舵机,实现自动开合结构 |
| 仓储物体检测 | 定时读取距离变化,判断货物是否存在或位置是否偏移 | 可增加状态记录和异常提醒 |
| 液位高度估算 | 将传感器固定在容器顶部,通过距离变化估算液面高度 | 需要结合容器高度做换算,并增加防水隔离设计 |
| 教学演示实验 | 用距离变化观察传感器数据、条件判断和异常处理 | 可作为 GPIO 输入输出、计时函数和数据过滤的综合案例 |
| 安全距离提醒 | 小于安全距离时输出"距离过近"状态 | 后续可扩展蜂鸣器或 LED,实现声光提示 |
| 数据采集记录 | 周期性读取距离并保存到本地或上传 | 后续可扩展串口通信、网络通信或数据可视化 |
从工程结构看,当前程序已经把测距驱动和业务判断分开。sensor.distance_cm() 只负责获取距离,get_distance_state() 只负责状态分类,loop() 只负责循环调度和输出显示。这种拆分方式适合继续扩展项目功能,例如把距离显示到 LCD 屏幕、通过蜂鸣器提示过近距离、结合摄像头做目标识别,或者把距离数据上传到 Web 后台做实时监控。当前代码没有实现这些模块,因此它们更适合作为后续课程方向。
总结
本实验通过 CanMV K210 开发板完成了 HC-SR04 超声波测距,核心能力包括 FPIOA 引脚映射、GPIOHS 输入输出配置、Trig 脉冲触发、Echo 回波检测、微秒级计时、距离换算、异常处理、平均值滤波和状态判断。代码从硬件接线出发,把超声波往返时间转换成厘米距离,再把距离结果转换成更容易理解的状态文本,完整展示了传感器实验中"真实环境数据进入程序"的过程。
这类实验非常适合作为 AI 硬件课程中的传感器入门案例。距离数据可以成为自动控制系统的判断依据,也可以作为后续智能交互项目的输入来源。后续课程可以在此基础上继续加入 LED 状态灯、蜂鸣器报警、LCD 显示、舵机控制、电机避障、摄像头识别和数据上传等内容,让单一测距实验逐步扩展成完整的智能硬件应用。