一、开箱帖:FRDM-MCXA153 初印象
拿到这块板子的时候说实话有点意外——比我想象中小巧得多。FRDM-MCXA153 是 NXP 面向入门和教育市场推出的 Freedom 开发板,核心是一颗 MCXA153VLH,Cortex-M33 内核,最高跑 96MHz,封装是 LQFP64。板子正面最显眼的就是那颗 MCU,旁边排着 Arduino UNO 兼容的扩展座,这一点对我后面外接模块太重要了——不用飞线,直接插杜邦线就能用。
板载资源方面,除了 MCU 本身,还有一颗 OpenSDA 调试器(板载 CMSIS-DAP),一根 USB 线就能搞定供电、烧录和串口调试,省了外接调试器的钱。板子上带了一颗 RGB LED(P3_0/P3_12/P3_13)、两个用户按键 SW2 和 SW3,以及一个复位按键。值得一提的是 MCXA153 内部集成了 FRO(自由运行振荡器),12MHz/48MHz/96MHz 几档可配,不需要外部晶振就能跑起来,我这块板子就是用内部 96MHz FRO 作为系统时钟的。

图1 FRDM-MCXA153 开发板正面
开箱总结一句话:板子做工扎实,Arduino 兼容座对新手极其友好,OpenSDA 一线通的设计省了很多麻烦。唯一需要注意的是 MCXA153 的 IO 是 3.3V 电平,外接 5V 模块时要确认 Echo 引脚是否耐压——我这款模块的 Echo 是 TTL 输出,实测高电平在 3.3V 左右,可以直连,不需要分压电阻,这一点后面过程帖里会细说。
二、过程帖:从接线到调通的踩坑之路
2.1 硬件选型与接线
整个系统的硬件其实就三块:开发板、超声波模块、LCD 屏。超声波模块我用的是单独购买的宽电压版(3.0~5.5V),比经典 HC-SR04 好在可以直接接 3.3V 供电,而且 Echo 输出是标准 TTL 电平,高电平约 3.3V,跟 MCXA153 的 IO 口电平匹配,不需要像老款 HC-SR04 那样在 Echo 线上串分压电阻。Trig 引脚接 P3_15(Arduino D8),Echo 接 P3_1(Arduino D7),VCC 接 5V(模块在 5V 下发射功率更足,测距更稳),GND 共地。
LCD 屏我选的是带 PCF8574 I2C 背板的 LCD1602A,5V 供电,黄底黑字。选 I2C 版本的原因很直接——并口 LCD1602 需要占用至少 6 根 IO(RS、RW、EN、D4~D7),而 I2C 只需要 SDA 和 SCL 两根线,对 IO 资源紧张的项目太友好了。SDA 接 P1_8(D18),SCL 接 P1_9(D19),VCC 接 5V,GND 共地。板载 SW2 按键(P3_29)我拿来做厘米/毫米显示模式切换,不用额外接按键。

图2 系统整体接线实物
这里有个小插曲:一开始我把屏幕 VCC 接到了 3.3V,结果只有背光没有文字,调节电位器后文字也很暗,后来改成 5V 供电再调电位器,最终选了一个比较好的显示效果。所以 LCD1602 这类字符屏一定要按模块标称电压供电,5V 屏接 3.3V 虽然背光能亮,但驱动电压不够,对比度会非常差。
2.2 软件开发环境
开发环境用的是 NXP 官方的 MCUXpresso IDE,基于 Eclipse,免费下载。SDK 直接在 IDE 里导入 FRDM-MCXA153 的 board package,时钟配置、引脚复用、外设初始化都可以用 Config Tools 图形化生成,生成的 pin_mux.c 和 clock_config.c 直接编译进工程。我这个工程的系统时钟配置为 FRO 96MHz(BOARD_BootClockFRO96M),AHB 时钟 96MHz,Flash 等待周期 2 个。
调试用 OpenSDA 虚拟串口,LPUART0 波特率 115200,PRINTF 直接输出到电脑端串口助手,测距数据实时打印,方便调试。
2.3 遇到的主要问题
第一个坑是 I2C 地址。PCF8574 背板的 I2C 地址常见有 0x27 和 0x3F 两种,不同厂家批次不一样。我一开始写死 0x27,结果 LCD 完全没反应,查了半天才发现我这块是 0x3F。后来干脆在代码里做了地址自动探测,依次尝试 0x27、0x3F、0x20、0x38,哪个有 ACK 就用哪个,换屏再也不用改代码了。
第二个坑是超声波测距的抖动。原始数据跳得厉害,同一位置前后两次能差 2~3cm。我加了一个 10 点滑动平均滤波,数据稳定了很多,但又发现快速移动目标时显示滞后明显——因为 10 个点的窗口需要时间刷新。最后做了一个自适应处理:当新采样值与当前滤波值相差超过 5cm 时,认为是目标快速移动,直接清空窗口重新填充,兼顾了静态稳定性和动态响应速度。
第三个坑是延时精度。超声波 Echo 高电平的宽度直接决定了距离,公式是距离(cm) = 高电平时间(us) / 58。如果用普通的 for 循环延时,在 96MHz 下精度不够。我改用了 Cortex-M33 内核自带的 DWT(Data Watchpoint and Trace)周期计数器,使能 CYCCNT 后直接读寄存器计数,一个周期就是 1/96MHz ≈ 10.4ns,测 Echo 脉宽的精度完全够用。
三、成果帖:超声波测距仪完整实现
3.1 项目概述
本项目基于 NXP FRDM-MCXA153 开发板,外接宽电压超声波测距模块和 I2C 接口 LCD1602A 显示屏,实现了一个可实时显示距离的超声波测距仪。系统支持厘米(一位小数)和毫米(整数)两种显示模式,通过板载 SW2 按键切换;测距数据同时通过串口输出到电脑端,方便记录和分析。软件上实现了软件 I2C 驱动、DWT 高精度计时、自适应滑动平均滤波等功能,全部代码基于 MCUXpresso SDK 裸机开发,没有使用操作系统。
3.2 系统框图
图3 系统总体框图
整个系统以 MCXA153 为控制核心。超声波模块通过 Trig 引脚接收 MCU 发出的 10us 以上触发脉冲,模块内部发射 8 个 40kHz 超声波脉冲并检测回波,Echo 引脚输出与距离成正比的高电平脉冲。MCU 通过 GPIO 读取 Echo 高电平宽度,利用 DWT 周期计数器精确计时,换算成距离后经滤波处理,通过软件模拟 I2C 总线发送到 LCD1602 显示。SW2 按键用于切换显示单位,LPUART0 用于调试信息输出。
3.3 电路原理图
图4 电路原理图
模块 | MCU引脚 | Arduino引脚 | 方向 | 说明 |
超声波 Trig | P3_15 | D8 | 输出 | 10us以上触发脉冲,强驱动输出 |
超声波 Echo | P3_1 | D7 | 输入 | TTL电平直连,无需分压 |
LCD SDA | P1_8 | D18 | 双向 | 软件I2C数据,开漏+上拉 |
LCD SCL | P1_9 | D19 | 输出 | 软件I2C时钟,开漏+上拉 |
SW2按键 | P3_29 | — | 输入 | 板载按键,上拉,低电平有效 |
LPUART0 TX | P0_3 | D1 | 输出 | 调试串口115200 |
LPUART0 RX | P0_2 | D0 | 输入 | 调试串口115200 |
表1 引脚分配一览表
3.4 各模块原理详解
3.4.1 超声波测距模块原理
超声波测距的核心原理是回声定位(Echo Location),跟蝙蝠探路一个道理。模块内部有一个超声波发射头和一个接收头。MCU 通过 Trig 引脚给一个至少 10us 的高电平脉冲,模块内部电路就会自动发射 8 个 40kHz 的超声波脉冲,同时把 Echo 引脚拉高。超声波在空气中传播,遇到障碍物后反射回来,接收头检测到回波的瞬间,Echo 引脚被拉低。所以 Echo 高电平的持续时间,就是超声波从发射到接收的往返时间。
图5 超声波模块 Trig/Echo 时序图
距离换算公式:声速在空气中约 340m/s(20°C 时),往返距离 = 声速 × 时间,单程距离 = 声速 × 时间 / 2。换算成厘米和微秒的话:距离(cm) = 时间(us) × 340 / 2 / 10000 = 时间(us) / 58.8,工程上通常取整为除以 58。我的代码里就是用的 pulseUs / 58.0f。
需要注意几个限制:第一,超声波有最小测距盲区,通常 2cm 左右,太近的话发射头的余振会掩盖回波;第二,被测物体表面需要能有效反射超声波,柔软、吸声或角度太大的表面效果差;第三,环境温度影响声速,精确测量需要温度补偿,我这个项目没做,日常使用足够了。我在代码里对有效脉宽做了限制:50us ~ 30000us,对应约 0.86cm ~ 517cm,超出范围直接判为无效读数。
3.4.2 LCD1602 显示原理(I2C 与并口对比)
LCD1602 是最经典的字符型液晶模块,核心控制器是 HD44780(或兼容芯片),能显示 2 行 × 16 个字符。它本身有 8 位数据总线(D0~D7)加上 RS(寄存器选择)、RW(读写选择)、EN(使能)三根控制线,这就是所谓的"并口"模式。8 位模式下需要 11 根线,即使只用高 4 位(D4~D7)的 4 位模式,也至少需要 RS、EN、D4~D7 共 6 根 IO,对 MCU 资源占用不小。而且 RW 引脚通常可以直接接地(只写不读),但即使这样也省不了几根。
图6 LCD1602 并口模式 vs I2C 模式对比
I2C 版本的 LCD1602 其实是在并口模块的背面焊了一块 PCF8574 转接板。PCF8574 是一颗 8 位 I2C 转 GPIO 的扩展芯片,MCU 只需要通过 I2C 总线发送一个字节的数据,PCF8574 就会把这个字节的 8 个 bit 输出到它的 8 个 IO 口上,这 8 个 IO 分别连接到 LCD 的 RS、RW、EN、BL(背光控制)和 D4~D7。也就是说,I2C 版本本质上还是在用 4 位并口模式驱动 LCD,只是中间多了一层 I2C 转并口的桥梁,把 MCU 端需要的线从 6 根降到了 2 根(SDA、SCL)。
PCF8574 到 LCD 的引脚映射(常见的 LiquidCrystal_I2C 风格):bit0=RS,bit1=RW,bit2=EN,bit3=BL(背光),bit4~bit7=D4~D7。写一个字节到 LCD 的过程是:先把高 4 位数据放到 bit4~bit7,设置好 RS 位,然后把 EN 拉高再拉低(产生一个使能脉冲,LCD 在 EN 下降沿锁存数据),接着用同样的方式发送低 4 位。我的代码里 LcdPulse() 函数干的就是这个事,LcdSend() 分两次发高 4 位和低 4 位。
I2C 版本的优势是省线、接线简单,适合 IO 紧张的项目;缺点是刷新速度比并口慢一些(I2C 标准模式 100kHz,写一个字符要发多个字节),但对 LCD1602 这种静态显示来说完全不是问题。另外 I2C 背板上通常有一个可调电位器,用来调 LCD 对比度,第一次使用如果屏幕不显示字符,先调一下对比度电位器,很可能是对比度没调好。
HD44780 的初始化流程有严格的时序要求:上电后先延时 15ms 以上,然后连续三次发送 0x30(功能设置:8 位接口),每次间隔逐步缩短,第四次发送 0x20 切换到 4 位接口,之后才能发送正常的命令字节(0x28 设置 4 位/2行/5x8 字体,0x0C 开显示关光标,0x06 设置输入模式,0x01 清屏)。我的 LCD1602_Init() 函数严格按照这个流程来的,时序不对的话 LCD 会完全没反应。
3.4.3 软件 I2C 实现
为什么用软件 I2C 而不用 MCXA153 硬件 I2C 外设?主要原因是我接的引脚 P1_8/P1_9 不是硬件 I2C 的默认引脚,而且软件 I2C 实现简单、可移植性强,对 LCD 这种低速设备完全够用。软件 I2C 的核心是用 GPIO 模拟 I2C 总线的时序:SCL 和 SDA 都配置为开漏输出(通过切换输入/输出来实现,输出低时拉低,输出高时切换为输入由上拉电阻拉高),按照 I2C 协议产生起始信号(SCL高时SDA下降沿)、停止信号(SCL高时SDA上升沿)、数据位(SCL低时改变SDA,SCL高时读取SDA)和 ACK 检测。
图7 I2C 总线时序图(以地址 0x27 写操作为例)
我的软件 I2C 速率大约 100kHz(每个 bit 前后各延时 5us),LCD 背板完全能跟上。SDA 和 SCL 引脚在 MCU 内部配置了上拉电阻,模块背板上通常也有上拉,双重上拉没问题。需要注意的是 I2C 总线必须共地,否则电平参考不一样,通信会失败。地址探测函数 SoftI2C_Probe() 的原理很简单:发送起始条件 + 7位地址左移一位 + 写标志(0),如果从机返回 ACK(SDA被拉低),说明该地址有设备响应。
3.4.4 DWT 高精度计时与滤波
Cortex-M33 内核集成了 DWT(Data Watchpoint and Trace)调试单元,其中有一个 32 位的循环计数器 CYCCNT,每经过一个内核时钟周期自动加 1。在 96MHz 系统时钟下,CYCCNT 的分辨率是 1/96MHz ≈ 10.4 纳秒,用来测量超声波 Echo 高电平宽度绰绰有余。使用方法很简单:先在 DEMCR 寄存器使能 TRCENA,然后往 CYCCNT 写 0 清零,再在 CTRL 寄存器置位 CYCCNTENA 开始计数,之后随时读 CYCCNT 的值就能得到从启动到现在经过的周期数。
我的 Ultrasonic_ReadCm() 函数流程是:拉低 Trig → 延时 5us → 拉高 Trig → 延时 20us(满足至少 10us 触发要求)→ 拉低 Trig → 等待 Echo 变高(超时退出)→ 记录上升沿时刻 tRise → 等待 Echo 变低(超时退出)→ 记录下降沿时刻 tFall → 计算脉宽 = (tFall - tRise) / 96 → 换算距离 = 脉宽(us) / 58。超时阈值设为 SystemCoreClock / 25,也就是约 40ms,对应最大测距约 6.8 米,远超模块实际能力。
图8 滤波算法效果对比(10点窗口,阶跃阈值5cm)
滤波算法用的是 10 点滑动平均,维护一个环形缓冲区,每次新数据进来覆盖最老的数据,然后求平均值。为了解决快速移动时的滞后问题,加了一个阶跃检测:如果新采样值与当前滤波输出的差值绝对值超过 5cm,认为是目标发生了快速位移,直接把缓冲区全部填充为新值,滤波输出立即跟上,避免了 10 个采样周期(约 2 秒)的滞后。从上面的对比图可以清楚看到:普通滑动平均在目标从 15cm 跳到 30cm 时需要约 10 个采样点才能跟上,而自适应滤波在检测到阶跃后立即重置窗口,响应速度提升非常明显。这个处理在实际使用中效果很好,静态时数字纹丝不动,手快速挥过去时显示也能立刻跟上。
3.5 如何开启运行
第一步,硬件连接:按照表 1 的引脚分配接好超声波模块和 LCD 屏,超声波 VCC 接 5V,LCD VCC 接 5V,所有 GND 共地。确认接线无误后,用 USB 线把开发板接到电脑。
第二步,编译烧录:在 MCUXpresso IDE 中打开工程,选中工程后点击 Build(锤子图标),编译生成 .axf 文件。然后点击 Debug(虫子图标),IDE 会通过 OpenSDA 把程序烧录到 MCU 并停在 main 函数入口,点击 Resume(继续运行)按钮即可。也可以用 GUI Flash Tool 直接把 .hex/.bin 烧进去,不用调试。
第三步,运行验证:程序启动后,LCD 第一行显示 "Rangefinder OK",第二行显示 "Let's do 2026P2",约 0.8 秒后进入正常测距模式,LCD 实时显示距离值。打开串口助手(115200, 8N1)可以看到测距数据持续打印。按一下板载 SW2 按键,显示单位在厘米和毫米之间切换。
图9 LCD 正常显示效果
3.6 主要参数情况
参数项 | 数值/说明 |
主控芯片 | NXP MCXA153VLH,Cortex-M33,96MHz |
系统时钟 | 内部 FRO 96MHz,AHB 96MHz |
超声波模块 | 宽电压 3.0~5.5V,TTL Echo,40kHz |
测距范围 | 约 2cm ~ 400cm(5V供电时) |
测距精度 | ±0.3cm(静态,经10点滤波后) |
刷新频率 | 约 5 次/秒(200ms 间隔) |
显示单位 | 厘米(1位小数)/ 毫米(整数),SW2切换 |
显示屏 | LCD1602A 黄屏,PCF8574 I2C 转接,5V |
I2C 速率 | 约 100kHz(软件模拟) |
I2C 地址 | 自动探测 0x27/0x3F/0x20/0x38 |
计时精度 | DWT CYCCNT,96MHz,约 10.4ns 分辨率 |
滤波算法 | 10点滑动平均 + 5cm阶跃重置 |
调试串口 | LPUART0,115200,8N1 |
供电方式 | USB 5V 供电(OpenSDA 一线通) |
表2 系统主要参数汇总
3.7 实现步骤
图10 主程序流程图
第一步,搭建工程框架。在 MCUXpresso IDE 中基于 FRDM-MCXA153 的 hello_world 例程创建工程,用 Config Tools 配置系统时钟为 FRO 96MHz,配置 LPUART0 为调试串口(P0_2/P0_3,115200),生成 pin_mux.c 和 clock_config.c。
第二步,编写板级 IO 初始化(board_io.c/h)。把超声波 Trig 配置为强推挽输出、Echo 配置为浮空输入,I2C 的 SDA/SCL 配置为开漏输出加内部上拉,SW2 配置为输入上拉。使能 PORT1/PORT2/PORT3 和 GPIO1/GPIO2/GPIO3 的时钟并释放复位。
第三步,编写软件 I2C 驱动(soft_i2c.c/h)。实现 SDA/SCL 的高低电平控制(通过切换 PDDR 输入输出实现开漏)、起始/停止信号、字节发送和 ACK 检测,以及地址探测函数 SoftI2C_Probe()。
第四步,编写 LCD1602 驱动(lcd1602_i2c.c/h)。基于软件 I2C 实现 PCF8574 的字节写入,按照 HD44780 4 位模式初始化流程写初始化函数,实现清屏、设置光标、打印字符串、显示距离等功能,加入 I2C 地址自动探测。
第五步,编写超声波驱动(ultrasonic.c/h)。使能 DWT 周期计数器,实现 Trig 触发脉冲生成、Echo 高电平宽度测量(带超时)、脉宽有效性校验和距离换算。
第六步,编写主程序(ultrasonic_rangefinder.c)。在 main 中依次初始化硬件、IO、超声波、LCD,显示开机画面后进入主循环:检测 SW2 按键切换单位 → 读取超声波距离 → 自适应滑动平均滤波 → LCD 显示 + 串口打印 → 延时 200ms。主程序流程如上图 10 所示。
第七步,上机调试。先不接 LCD,用串口确认超声波数据正常;再接 LCD,确认 I2C 通信和显示正常;最后测试按键切换和滤波效果,调整滤波参数到满意为止。
3.8 关键代码片段
主循环核心逻辑:
while (1)
{
/* SW2 按键切换 cm/mm 显示模式 */
bool btnNow = RangeSw_IsPressed();
if (btnNow && !btnPrev) {
showMm = !showMm;
}
btnPrev = btnNow;
/* 读取超声波距离 */
float cm = 0.0f;
if (Ultrasonic_ReadCm(&cm)) {
float filtered = DistFilter_Push(cm); /* 自适应滑动平均 */
if (lcdOk) {
LCD1602_ShowDistance(filtered, showMm);
}
} else {
LCD1602_SetCursor(0, 0);
LCD1602_Print("Dist: ---- ");
}
Delay_Ms(200U);
}超声波测距核心(DWT 计时):
/* 发送 20us 触发脉冲 */
GPIO_PinWrite(US_TRIG_GPIO, US_TRIG_PIN, 0U);
DelayUsDwt(5U);
GPIO_PinWrite(US_TRIG_GPIO, US_TRIG_PIN, 1U);
DelayUsDwt(20U);
GPIO_PinWrite(US_TRIG_GPIO, US_TRIG_PIN, 0U);
/* 等待 Echo 上升沿,记录时刻 */
while (GPIO_PinRead(US_ECHO_GPIO, US_ECHO_PIN) == 0U) {
if ((DWT->CYCCNT - startWait) > timeoutCycles) return false;
}
uint32_t tRise = DWT->CYCCNT;
/* 等待 Echo 下降沿,记录时刻 */
while (GPIO_PinRead(US_ECHO_GPIO, US_ECHO_PIN) != 0U) {
if ((DWT->CYCCNT - tRise) > timeoutCycles) return false;
}
uint32_t tFall = DWT->CYCCNT;
/* 脉宽(us) = 周期数 / 96;距离(cm) = 脉宽 / 58 */
uint32_t pulseUs = (tFall - tRise) / (SystemCoreClock / 1000000U);
*distanceCm = (float)pulseUs / 58.0f;自适应滑动平均滤波:
static float DistFilter_Push(float sample)
{
static float buf[10];
static uint32_t count = 0, index = 0;
static float lastFiltered = 0.0f;
static bool hasFiltered = false;
/* 阶跃检测:变化超过5cm,认为目标快速移动,清空窗口 */
if (hasFiltered) {
float delta = sample - lastFiltered;
if (delta < 0) delta = -delta;
if (delta >= 5.0f) {
for (int i = 0; i < 10; i++) buf[i] = sample;
count = 10; index = 0;
lastFiltered = sample;
return sample;
}
}
/* 正常滑动平均 */
buf[index] = sample;
index = (index + 1) % 10;
if (count < 10) count++;
float sum = 0;
for (int i = 0; i < count; i++) sum += buf[i];
lastFiltered = sum / count;
hasFiltered = true;
return lastFiltered;
}3.9 效果演示
系统上电后,LCD 先显示开机画面 "Rangefinder OK / Let's do 2026P2",约 0.8 秒后进入测距模式。将手放在超声波模块前方 10cm 处,LCD 第一行显示 "Dist: 10.0 cm",第二行显示 "SW2:cm/mm mode",数值稳定在 10.0 上下浮动不超过 0.1cm。缓慢移动手掌,距离数值平滑变化;快速挥动手掌,数值能在 1~2 个刷新周期内跟上新位置。按下 SW2,显示切换为毫米模式 "Dist: 100 mm",再按一次切回厘米。串口助手同步输出 "Distance: 10.0 cm" 格式的数据。

【let's do 2026年第2期】基于FRDM-MCXA153的超声波测距仪
图11 系统整体运行效果
完整代码:frdmmcxa153_hello_world.zip
3.10 项目总结与改进方向
这个项目从开箱到调通大概花了两个晚上,整体难度不高,但踩了几个实实在在的坑:I2C 地址不匹配、LCD 供电电压不足导致对比度差、原始数据抖动需要滤波。最大的收获是把 MCXA153 的 GPIO、时钟配置、DWT 调试单元、串口这些基础外设都过了一遍,软件 I2C 的实现也让我对 I2C 协议的时序有了更直观的理解。
后续可以改进的方向:一是加温度补偿,用 DS18B20 测环境温度修正声速,提高测距精度;二是改用硬件 I2C 外设(FlexComm 接口配置为 I2C),释放 CPU 并提高通信速率;三是加蜂鸣器报警,距离小于阈值时发出提示音,做成倒车雷达那种效果;四是把数据通过蓝牙或 WiFi 上传到手机,做远程监测。FRDM-MCXA153 的资源还远没用完,有很大的折腾空间。
我要赚赏金
