二、项目概述
────────────────────────────────────────────────────────────────────────
我想做的不是"点一下屏幕跳出个数字"的最小实现,而是一套能拿去做实验台、
能量化标定、能看清误差来源的超声波测距仪。理由很简单:超声波测距是机器人
避障、液位检测、倒车雷达这些应用的公共基础模块,把它测准了、误差摸清楚了,
后面往上叠功能才有意义。
最终完成的系统实现了:
· 2cm ~ 400cm 连续无接触测距,显示分辨率 0.1cm
· LCD 本地实时显示 + 板载串口同步输出,两路可交叉验证
· 3 点中值滤波抑制尖刺读数
· 超时检测与无效目标保护
· 完整的分材质 / 分角度稳定性测试
开发环境:MCUXpresso SDK 裸机工程(无 RTOS),配合 Config Tools 图形化
配置引脚与外设,MCUXpresso for VS Code 编译烧录。
────────────────────────────────────────────────────────────────────────
三、系统框图
────────────────────────────────────────────────────────────────────────
┌──────────────────────────────────────────────────────────┐
│ FRDM-MCXA153 │
│ (Cortex-M33 @ 96MHz) │
│ │
│ ┌────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ GPIO 输出 │ │ GPIO 双边沿 │ │ 4-bit 并口 │ │
│ │ TRIG │ │ 中断+SysTick │ │ LCD 驱动 │ │
│ │ (P3_28) │ │ 测脉宽(P3_27)│ │ ST7066U │ │
│ └─────┬──────┘ └──────▲──────┘ └──────┬───────┘ │
│ │ │ │ │
│ │ 3点中值滤波 显示驱动 │
│ │ │ │ │
│ │ ┌─────┴─────┐ │ │
│ │ │ 距离换算 │ │ │
│ │ │ d = T×c/2 │ │ │
│ │ └─────┬─────┘ │ │
│ │ │ │ │
│ │ ┌─────┴─────┐ │ │
│ │ │ 超时/无效 │ │ │
│ │ │ 保护 │ │ │
│ │ └─────┬─────┘ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │ 驱动 │ │ LPUART0 │ │ LCD 显示 │ │串口输出 │ │
│ │ 40kHz 发射 │ │ 调试串口 │ │ 1×16 STN │ │@115200 │ │
│ └──────┬──────┘ └────┬────┘ └──────────┘ └────┬────┘ │
└─────────┼──────────────┼────────────────────────┼────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ HC-SR04 │ │ PC 串口助手 │ │ 上位机绘图 │
│ ────────── │ │ @115200 8N1 │ │ (可选扩展) │
│ ECHO ─ 分压 │ └──────────────┘ └──────────────┘
└──────┬──────┘
│ 40kHz 超声波往返
▼
┌─────────────┐
│ 被测目标 │ 墙面 / 纸箱 / 玻璃 / 布料 / 人体
└─────────────┘
信号流向:MCU 产生触发脉冲 → 超声波发射 → 目标反射 → 接收放大整形 →
ECHO 输出等宽高电平 → GPIO 双边沿中断测脉宽 → 换算距离 → 中值滤波 →
LCD 显示 + 串口输出。
────────────────────────────────────────────────────────────────────────
四、电路原理图
────────────────────────────────────────────────────────────────────────
FRDM-MCXA153 HC-SR04 ┌───────────────┐ ┌─────────────┐ │ │ │ │
5V (Arduino │ │ │ VCC ◄──────┼── 5V
排针) │ │ │ │
│ │ ┌────────┐ │ │
│ P3_28 (GPIO) ├───┤ R1 ├─────┤ TRIG │
│ 输出 │ │ 10kΩ │ │ │
│ │ └────────┘ │ │
│ P3_27 (GPIO) │◄──┐ │ │
│ 输入/双边沿 │ │ ┌────────┐ │ ECHO │
│ │ │ │ R2 │ │ │
│ │ │ │ 10kΩ │ │ │
│ │ │ └────────┘ │ │
│ │ │ ┌────────┐ │ │
│ GND ┴───┴─┤ ├───┤ GND │
│ └────────┘ └─────────────┘
│
│ LCD (NHD-0116AZ-FL-YBW, 4-bit 模式)
│ ┌─────────────────────────────────────┐
│ │ 1 VSS ◄── GND │
│ │ 2 VDD ◄── 5V │
│ │ 3 V0 ◄── 10kΩ电位器中点(对比度) │
│ │ 4 RS ◄── P3_14 │
│ │ 5 R/W ◄── GND (固定写模式) │
│ │ 6 E ◄── P3_15 │
│ │ 7-10 DB0-3 ✗ 悬空(4-bit 模式) │
│ │ 11 DB4 ◄── P3_16 │
│ │ 12 DB5 ◄── P3_17 │
│ │ 13 DB6 ◄── P3_18 │
│ │ 14 DB7 ◄── P3_19 │
│ │ 15 LED+ ◄── 5V (经限流电阻) │
│ │ 16 LED- ◄── GND │
│ └─────────────────────────────────────┘
三个关键设计点:
ECHO 分压
HC-SR04 的 ECHO 是 5V 电平,MCXA153 GPIO 是 3.3V 域。用模块自带的
R1/R2 = 10kΩ 组成分压器,5V × 10k/(10k+10k) = 2.5V,落在 3.3V 输入的
安全区内。TRIG 侧因为是 MCU 输出,规格书明确 3.3V 电平也能识别 Trig,
所以不用处理。LCD 走 4-bit 模式
只接 DB4DB7 四根数据线,DB0DB3 悬空,省下 4 个 GPIO 给后续扩展。V0 对比度调节
V0 脚不能直接接 GND 或 VDD,必须通过电位器分压取中间电位(约 0.5V)。
这是 LCD 上电后一片空白最常见的原因之一,很容易被误判为驱动失败。
────────────────────────────────────────────────────────────────────────
五、过程:开发环境与三个功能实现
────────────────────────────────────────────────────────────────────────
【5.1 环境搭建】
用的是 MCUXpresso SDK 裸机工程,配合 MCUXpresso for VS Code 扩展 +
Config Tools 图形化配置工具。
从 NXP 官网拉取 MCUXpresso SDK,基于 led_blinky 模板新建工程;
用 Config Tools 打开工程目录下的 .mex 配置文件,统一完成时钟、引脚、
外设三块配置,导出生成 pin_mux.c / peripherals.c / clock_config.c;VS Code 里装好 NXP 官方扩展,左侧栏出现 NXP 图标,右键工程 target
就能 Build 和 Flash the Selected Target。
时钟配置:用内部 RC 振荡源,系统主频 96MHz,FRO_HF / LPUART0 时钟都是
96MHz。不用外部晶振,省一个器件。
【5.2 任务一:串口打印】
板载 MCU-Link 自带虚拟串口,挂在 USB Debug 口上,上电就能在设备管理器看到
COM 口,波特率 115200。
初始化加 BOARD_InitDebugConsole(),然后 PRINTF("Hello Let's Do!rn")。
编译烧录,串口助手收到输出,说明主频、Flash 写入、串口外设、调试链路全通。
这一步单独做很有必要——如果直接上超声波,出问题时分不清是环境没配好还是
传感器没驱动好。
【5.3 任务二:超声波测距】
工作原理:
MCU 给 Trig 一个 ≥10μs 的高电平脉冲,模块发射 40kHz 超声波,声波遇到障碍物
反射回来,接收头把回波转成电信号经放大整形后从 Echo 输出一个高电平脉冲,
脉宽正比于声波往返时间。
距离 = T × c / 2 (T 为 Echo 高电平持续时间,c 为声速)
声速随温度变化:c ≈ 331.5 + 0.61t(m/s,t 为℃)。25℃ 时约 346.75 m/s。
引脚配置:
功能 引脚 配置 ──────────────────────────────────────── Trig P3_28 GPIO 输出 Echo P3_27 GPIO 输入,双边沿中断
计时方案的选择:
这里有个选择:用 CTIMER 的输入捕获,还是 GPIO 中断 + SysTick 计数?
先试了 CTIMER 输入捕获,配置项多、调试不直观;后来简化成 GPIO 双边沿中断 +
自由运行的 SysTick,反而更好理解和调:
· SysTick 设为自由运行计数模式,不产生中断,只当计时基准。
96MHz 下 1μs = 96 ticks;
· Trig 拉高 11μs(规格书要求 ≥10μs,留余量);
· Echo 上升沿中断记 SysTick->VAL,下降沿再记一次;
· 计数差换算微秒,再乘声速除以 2 就是距离。
滤波:
第一版直接用原始值,发现手在模块前小幅晃动时读数跳得厉害。加了个 3 点中值
滤波——连续采 3 次取中间那个。尖刺基本消掉,代价是响应略变慢,对测距场景
完全够用。
同时加了个 30ms 超时保护:超时判为无有效回波(目标太远或角度太偏),返回
无效标志,避免程序卡死在这一步。
【5.4 任务三:LCD 显示】
踩坑:3.3V 与 5V 电平
查规格书,LCD 输入高电平阈值 VIH ≥ 0.7 × VDD = 3.5V,而 MCXA153 的 GPIO
是 3.3V 域。也就是说 3.3V 驱动这块屏在信号上是不满足 VIH 要求的,属于
"能跑但不保证"的区间。
处理办法:给 LCD 的信号线做 5V 上拉——用若干 1kΩ 电阻把 RS / E / DB4~DB7
拉到 5V 侧,配合三态输入;MCU 输出低时由 3.3V 侧下拉形成有效低电平,高时
由 5V 侧提供 5V 电平。这样无论 MCU 输出高还是低,LCD 看到的都是干净的
全摆幅信号。
如果你实测 3.3V 直连完全正常、多角度测试无乱码,也可以如实写成"实测 3.3V
直连可用,但存在规格书裕量不足的理论风险,列在此处供后续参考"——这种记录
比硬吹更受欢迎。
DDRAM 地址坑(重要)
ST7066U 的 1×16 屏,16 个字符地址不连续,第 8 个和第 9 个之间要跳地址:
位置: 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 16 DDRAM: 00 01 02 03 04 05 06 07 | 40 41 42 43 44 45 46 47
不手动跳,中间就会出现空白或乱码。这是查 ST7066U 手册才看出来的,通用
HD44780 兼容代码里往往没处理。
初始化序列(4-bit 模式)
delay_ms(50); LCD_WriteCmd(0x38); // Function Set,8-bit 宣告 delay_ms(5); LCD_WriteCmd(0x38); delay_ms(1); LCD_WriteCmd(0x28); // 切 4-bit,N=1 单行,F=5x8 LCD_WriteCmd(0x08); // Display OFF LCD_WriteCmd(0x01); // Clear Display(内部耗时 1.52ms) delay_ms(3); LCD_WriteCmd(0x06); // Entry Mode:地址自增 LCD_WriteCmd(0x0C); // Display ON,关光标
────────────────────────────────────────────────────────────────────────
六、如何开启运行
────────────────────────────────────────────────────────────────────────
硬件前提:
检查项 要求
─────────────────────────────────────────────────────────
供电 USB-C 接电脑即可,板载 MCU-Link 同时供电与调试
超声波模块 VCC 必须接 5V(Arduino 排针),3.3V 下发射功率不足,
近处会测不到
LCD VDD 接 5V,背光需限流
接地 三者必须共地,否则 ECHO 干扰大、LCD 花屏
软件运行步骤:
// 1. 新建工程(基于 MCUXpresso SDK 的 led_blinky 模板)
// Core/Src/{main.c, hcsr04.c, lcd_st7066u.c, delay.c, pin_mux.c}
// 2. Config Tools 打开 .mex 配置
// 时钟:内部 FRO,主频 96MHz,FRO_HF / LPUART0 = 96MHz
// 引脚:P3_28 = GPIO 输出 (TRIG)
// P3_27 = GPIO 输入 + 双边沿中断 (ECHO)
// P3_14 = RS, P3_15 = E, P3_16P3_19 = DB4DB7 (LCD)
// 外设:LPUART0 (115200)、GPIO3_IRQn、SysTick
// 导出生成 pin_mux.c / peripherals.c / clock_config.c
// 3. 编译:VS Code 装 NXP 官方扩展 → 右键 target → Build
// 4. 烧录:USB-C 接 J15 Debug 口 → 右键 target
// → Flash the Selected Target
// (板载 MCU-Link,无需外接调试器)
// 5. 验证:设备管理器出现新 COM 口 → 串口助手 115200 8N1
// LCD 显示 DIST: xx.x cm,串口同步输出 Distance = xx.xx cm
运行现象:上电约 0.5s,LCD 初始化完成并显示当前距离;串口以 5Hz 频率同步
输出带两位小数的距离值。手放在探头前 10cm 处,读数应在 10cm 附近小幅波动。
────────────────────────────────────────────────────────────────────────
七、主要参数情况
────────────────────────────────────────────────────────────────────────
系统参数:
主控 NXP MCXA153,Cortex-M33 @ 96MHz(无 FPU/无 TrustZone)
存储 128KB Flash(ECC,1位纠错 2位检错)/ 32KB SRAM
测距原理 超声波回波脉宽法,40kHz
换算公式 d = T × c / 2,c 取 340 m/s(常温)
理论量程 2cm ~ 400cm,>25° 时推荐量程收窄
波束角 15°
显示分辨率 0.1 cm
测量周期 200 ms(HC-SR04 最小周期建议 ≥60ms)
滤波方式 3 点中值滤波
超时阈值 30 ms 无回波 → 判无效目标
串口 LPUART0 @ 115200, 8N1(板载 MCU-Link 虚拟串口)
显示 NHD-0116AZ-FL-YBW,1×16 字符 STN,ST7066U,4-bit 并口
功耗 测量瞬间约 15mA;LCD 背光约 110mA(主要功耗项)
实测标定数据(卷尺为基准,25℃ 室温)
※ 此表请替换为你自己的实测值,标准差是评价稳定性的关键。
标称距离 实测均值 绝对误差 相对误差 标准差
──────────────────────────────────────────────────────────
5 cm 【填】 【填】 【填】 【填】
10 cm 【填】 【填】 【填】 【填】
20 cm 【填】 【填】 【填】 【填】
30 cm 【填】 【填】 【填】 【填】
50 cm 【填】 【填】 【填】 【填】
80 cm 【填】 【填】 【填】 【填】
100 cm 【填】 【填】 【填】 【填】
150 cm 【填】 【填】 【填】 【填】
200 cm 【填】 【填】 【填】 【填】
300 cm 【填】 【填】 【填】 【填】
分材质 / 分角度稳定性对比
※ 这组数据最能体现"做透了"。软材质吸声、斜面镜面反射,这两个失效场景要
如实写出来。
测试条件 平均误差 标准差 结论
──────────────────────────────────────────────────────────
墙面,正对 【填】 【填】 基准
纸箱,正对 【填】 【填】 漫反射,接收稳定
玻璃,正对 【填】 【填】 部分角度会漏检
布料(褶皱),正对 【填】 【填】 吸声,误差明显增大
墙面,30° 斜角 【填】 【填】 有效反射面减小
墙面,45° 斜角 【填】 【填】 基本失效,超出15°波束角
人体胸部,正对 【填】 【填】 漫反射特性好
────────────────────────────────────────────────────────────────────────
八、实现步骤
────────────────────────────────────────────────────────────────────────
Step 1 · 串口通路验证
单独跑通 PRINTF,确认主频、Flash、串口、调试链路正常。后面出问题时能
快速定位是环境问题还是传感器问题。
Step 2 · 超声波单次测距
// Trig 发 11μs 脉冲(规格书要求 ≥10μs,留余量)
static void HCSR04_Trigger(void)
{
HAL_GpioSetOutput(HCSR04_TRIG_HANDLE, 1U);
delay_us(11);
HAL_GpioSetOutput(HCSR04_TRIG_HANDLE, 0U);
}
// SysTick 自由运行计数,96MHz 下 1μs = 96 ticks
// ECHO 双边沿中断里记时间戳
static void HCSR04_EchoCallback(void *param)
{
uint8_t level;
HAL_GpioGetInput(HCSR04_ECHO_HANDLE, &level);
if (level != 0U) {
echo_start_tick = SysTick->VAL; // 上升沿
} else {
echo_end_tick = SysTick->VAL; // 下降沿
echo_capture_done = 1U;
}
}
// 脉宽 → 距离,含 24 位回绕处理
float HCSR04_ReadDistance(void)
{
uint32_t delta, pulse_us, timeout = 0;
echo_capture_done = 0U;
HCSR04_Trigger();
while ((echo_capture_done == 0U) && (timeout < 300000U)) timeout++;
if (echo_capture_done == 0U) return -1.0f; // 超时,无效目标
delta = (echo_end_tick > echo_start_tick)
? (0xFFFFFFU - echo_start_tick) + echo_end_tick
: (echo_start_tick - echo_end_tick);
pulse_us = delta / 96U; // ticks → μs
return ((float)pulse_us * 0.034f) / 2.0f; // 340m/s 声速,折半
}Step 3 · 中值滤波 + 超时保护
static float MedianFilter3(float a, float b, float c)
{
if (a > b) { float t = a; a = b; b = t; }
if (b > c) { float t = b; b = c; c = t; }
if (a > b) { float t = a; a = b; b = t; }
return b;
}Step 4 · LCD 驱动(ST7066U)
初始化序列见 5.4 节。关键提醒两点:V0 必须用电位器分压;1×16 的 DDRAM
在第 8→9 字符之间要跳到 0x40。
Step 5 · 主循环整合
int main(void)
{
float dist;
BOARD_InitBootClocks(); // 96MHz
BOARD_InitPins();
BOARD_InitBootPeripherals();
BOARD_InitDebugConsole(); // 串口 115200
HCSR04_Init(); // GPIO + 双边沿中断
SysTick_FreeCounter_Init(); // 自由运行计时基准
LCD_Init(); // LCD 初始化
LCD_ShowString("READY");
while (1) {
/* 3 点中值滤波 */
dist = MedianFilter3(HCSR04_ReadDistance(),
HCSR04_ReadDistance(),
HCSR04_ReadDistance());
if ((dist >= 2.0f) && (dist <= 400.0f)) {
PRINTF("Distance = %.2f cmrn", dist);
LCD_ShowDistance(dist); // "DIST: xx.x cm"
} else {
PRINTF("No echo (timeout)rn");
LCD_ShowString("NO TARGET");
}
delay_ms(200); // 200ms 测量周期
}
}代码结构:
Core/ ├── Src/ │ ├── main.c 主循环、业务逻辑 │ ├── hcsr04.c/h 超声波驱动(触发、测距、超时) │ ├── lcd_st7066u.c/h LCD 驱动(初始化、写字符、清屏、光标定位) │ ├── delay.c/h 基于 SysTick 的 us/ms 延时 │ └── pin_mux.c Config Tools 生成,引脚复用 ├── Drivers/ │ └── fsl_adapter_gpio.c GPIO 适配层 └── board/ 板级初始化
传感器和显示都封成独立驱动,上层 main.c 只管调度。后面想加别的传感器
不用改 LCD 那部分。
────────────────────────────────────────────────────────────────────────
我要赚赏金
