【Let'sDo 2026 P2】简易超声波测距仪实践帖:FRDM-MCXA153 + HC-SR04 + 16×1 字符 LCD
目标:每 500 ms 测一次距离,LCD 稳定显示 Dist: 123.4 cm,串口同步回显;超量程显示 ----。
平台:NXP FRDM-MCXA153(MCXA153,Arm Cortex-M33,96 MHz,裸机 / MCUXpresso SDK)
一、器件清单
型号 说明 关键参数 单价
1 FRDM-MCXA153(568-FRDM-MCXA153-ND) NXP Freedom 开发板 Cortex-M33 @96 MHz,128 KB Flash / 32 KB RAM,Arduino 兼容排针,板载 MCU-Link 调试器(虚拟串口) ¥130.25
2 Adafruit 3942(1528-2711-ND) HC-SR04 兼容超声波模块 40 kHz,DC 5 V,工作电流 15 mA,量程 2–400 cm,精度约 3 mm,Trig ≥10 µs 高电平触发,Echo 为 5 V 电平(附 2×10 kΩ 电阻用于分压) ¥32.66
3 NHD-0116AZ-FL-YBW(NHD-0116AZ-FL-YBW-ND) Newhaven 16×1 字符 LCD ST7066U 控制器(HD44780 兼容),4/8 位并口,5 V 逻辑,黄绿透反屏带背光 ¥58.96
二、测距原理(ToF)
MCU 向 Trig 发一个 ≥10 µs 高电平脉冲;
模块自动发射 8 个 40 kHz 声波脉冲,同时 Echo 拉高;
收到回波后 Echo 拉低,高电平持续时间 t 即声波往返时间;
距离 = t × 声速 ÷ 2。20 ℃ 空气中声速约 343 m/s,即 cm = t(µs) × 0.0343 / 2,工程上常用 t / 58。
三、硬件连接
整体接线关系如下(含 Echo 分压电路、Arduino 排针引脚走向、5 V 共地供电与串口链路):
3.1 电平兼容(本设计第一个关键点)
HC-SR04 的 Echo 输出 5 V TTL,而 MCXA153 的 IO 是 3.3 V。用模块附赠的两只 10 kΩ 电阻搭分压,把 5 V 降到约 2.5 V 再进 MCU:
模块 Echo ── 10k R1 ──┬── 10k R2 ── GND
└── MCU D4 (P2_5)
模块 GND ──────────────── MCU GND(必须共地)
Trig 是输入脚,3.3 V 高电平即可触发,可直连。
3.2 引脚分配(均走 Arduino 排针)
器件引脚 开发板 说明
超声波 Trig D2(P2_4) GPIO 输出
超声波 Echo D4(P2_5) GPIO 输入(经 10k/10k 分压)
LCD RS D7(P3_1) 寄存器选择
LCD E D8(P3_15) 使能,下降沿锁存
LCD DB4~DB7 D9/D10/D11/D12(P3_14 / P2_6 / P2_13 / P2_16) 4 位数据总线
LCD R/W 直接接 GND 只写模式,省一根线
LCD V0 10 kΩ 电位器中点 调对比度
LCD VDD / 背光 5 V(背光串限流电阻) 模块逻辑与背光均为 5 V,可由外部 5 V 或开发板 5 V 轨供电,注意共地
四、软件设计
工程基于 MCUXpresso SDK 裸机框架,延时/计时全部用 Cortex-M33 的 DWT 周期计数器(DWT->CYCCNT),不占定时器、不用中断。代码分四块:app_delay、ultrasonic、lcd16x1、main。
4.1 延时基准
static uint32_t s_cyclesPerUs;
void APP_DelayInit(void)
{
SystemCoreClockUpdate(); /* 关键:先与寄存器校验时钟,否则延时整体失真 */
s_cyclesPerUs = SystemCoreClock / 1000000u;
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
static uint32_t APP_GetTick(void) { return DWT->CYCCNT; }
void APP_DelayUs(uint32_t us)
{
uint32_t start = APP_GetTick();
while ((APP_GetTick() - start) < us * s_cyclesPerUs) { }
}
void APP_DelayMs(uint32_t ms) { while (ms--) APP_DelayUs(1000); }
4.2 超声波驱动
define TRIG_PORT GPIO2
define TRIG_PIN 4u
define ECHO_PORT GPIO2
define ECHO_PIN 5u
define US_TIMEOUT_US 30000u /* 400cm 往返约 23.3ms,30ms 超时足够 */
float US_MeasureCm(void)
{
GPIO_PinWrite(TRIG_PORT, TRIG_PIN, 0);
APP_DelayUs(4);
GPIO_PinWrite(TRIG_PORT, TRIG_PIN, 1);
APP_DelayUs(10); /* ≥10µs 高电平触发 */
GPIO_PinWrite(TRIG_PORT, TRIG_PIN, 0);
uint32_t t0 = APP_GetTick();
while (!GPIO_PinRead(ECHO_PORT, ECHO_PIN)) /* 等上升沿 */
if ((APP_GetTick() - t0) > US_TIMEOUT_US * s_cyclesPerUs) return -1.0f;
uint32_t hi0 = APP_GetTick();
while (GPIO_PinRead(ECHO_PORT, ECHO_PIN)) /* 等下降沿 */
if ((APP_GetTick() - hi0) > US_TIMEOUT_US * s_cyclesPerUs) return -1.0f;
uint32_t hiUs = (APP_GetTick() - hi0) / s_cyclesPerUs;
float cm = (float)hiUs * 0.0343f / 2.0f; /* 343 m/s @20℃,往返÷2 */
return (cm < 2.0f || cm > 400.0f) ? -1.0f : cm;
}
4.3 16×1 LCD 驱动(4 位总线、只写)
static GPIO_Typeconst kDbPort[4] = {GPIO3, GPIO2, GPIO2, GPIO2}; / DB4..DB7 */
static const uint32_t kDbPin[4] = {14u, 6u, 13u, 16u};
define LCD_RS_PORT GPIO3
define LCD_RS_PIN 1u
define LCD_E_PORT GPIO3
define LCD_E_PIN 15u
static void LCD_Nibble(uint8_t d4)
{
for (int i = 0; i < 4; i++)
GPIO_PinWrite(kDbPort[i], kDbPin[i], (d4 >> i) & 1u);
GPIO_PinWrite(LCD_E_PORT, LCD_E_PIN, 1);
APP_DelayUs(1);
GPIO_PinWrite(LCD_E_PORT, LCD_E_PIN, 0); /* 下降沿锁存 */
APP_DelayUs(60);
}
static void LCD_Cmd(uint8_t c)
{
GPIO_PinWrite(LCD_RS_PORT, LCD_RS_PIN, 0);
LCD_Nibble(c >> 4); LCD_Nibble(c & 0x0F);
APP_DelayUs(1600); /* 给清屏/回位指令留余量 */
}
static void LCD_Data(uint8_t d)
{
GPIO_PinWrite(LCD_RS_PORT, LCD_RS_PIN, 1);
LCD_Nibble(d >> 4); LCD_Nibble(d & 0x0F);
APP_DelayUs(60);
}
void LCD_Init(void)
{
GPIO_PinWrite(LCD_RS_PORT, LCD_RS_PIN, 0); /* 上电先把总线拉成空闲态 */
GPIO_PinWrite(LCD_E_PORT, LCD_E_PIN, 0);
APP_DelayMs(20); /* 等 LCD 上电自初始化 */
LCD_Nibble(0x3); APP_DelayMs(5); /* 8 位唤醒序列 ×3 */
LCD_Nibble(0x3); APP_DelayUs(150);
LCD_Nibble(0x3);
LCD_Nibble(0x2); /* 切 4 位 */
LCD_Cmd(0x28); /* 4 位 / N=1:ST7066 的 16×1 必须置 N=1 */
LCD_Cmd(0x0C); /* 显示开、光标关 */
LCD_Cmd(0x06); /* 地址自增 */
LCD_Cmd(0x01); /* 清屏 */
}
/* 16×1 的 DDRAM 是 8×2 结构:第 1~8 列 -> 0x00~0x07,第 9~16 列 -> 0x40~0x47 */
void LCD_SetCursor(uint8_t col)
{
LCD_Cmd(0x80 | ((col < 8) ? col : (0x40u + col - 8u)));
}
void LCD_Print(const char *s)
{
uint8_t col = 0;
while s && col < 16) { LCD_Data((uint8_t)s++); col++; }
}
4.4 主循环
int main(void)
{
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole(); /* MCU-Link 虚拟串口,115200 */
APP_DelayInit();
/* GPIO 初始化:Trig/RS/E/DB4~DB7 输出,Echo 输入;
Echo 脚必须用 PORT_SetPinConfig() 且 inputBuffer = kPORT_InputBufferEnable */
LCD_Init();
LCD_SetCursor(0); LCD_Print("Dist: ---- cm ");
for (;;)
{
float cm = US_MeasureCm();
char line[17];
if (cm < 0.0f) snprintf(line, sizeof(line), "Dist: ---- cm ");
else snprintf(line, sizeof(line), "Dist: %5.1f cm ", (double)cm);
LCD_SetCursor(0);
LCD_Print(line);
PRINTF("%srn", line);
APP_DelayMs(500);
}
}
五、踩坑记录(重点)
Echo 永远读到 0。 MCXA153 外设上电处于复位保持状态,用 GPIO 前必须 CLOCK_EnableClock() + RESET_ReleasePeripheralReset();更隐蔽的是 PORT_SetPinMux() 只设 MUX、不使能输入缓冲 IBE,必须改用 PORT_SetPinConfig() 并把 inputBuffer 设为 kPORT_InputBufferEnable,否则 GPIO_PinRead() 恒为 0。
LCD 只显示左边 8 个字符。 NHD-0116AZ 的 ST7066U 是 1/16 duty,16×1 屏内部 DDRAM 按 8×2 排布:第 9~16 列地址在 0x40~0x47。Function Set 必须写 0x28(N=1);写成 0x20 控制器只扫 COM0~7,右半边无信号。写第 9 列前手动切 DDRAM 地址即可(见 LCD_SetCursor)。
复位后屏幕内容随机。 复位释放到 LCD_Init() 之间总线悬空 1~3 ms,E 是下降沿锁存,噪声抖一个沿就被当写数据。软件上在 main() 第一句把 RS/E 拉低(LCD_Init 开头两行)缩短窗口;根治靠硬件:RS、E 各加 10 kΩ 下拉到 GND。
延时基准不可信。 BOARD_InitBootClocks() 若为空实现,系统跑复位默认时钟,而 SystemCoreClock 只是 C 初值、从未与寄存器校验,所有延时按比例缩放,LCD 时序直接不达标。解决:APP_DelayInit() 里先调 SystemCoreClockUpdate()。
5 V/3.3 V 电平。 Echo 直连 3.3 V IO 属过压使用,务必用附赠电阻分压;调试时先把 Echo 脚直接接 3.3 V,确认 GPIO_PinRead 能读到 1,再往上叠测距逻辑,可快速排除"读数不真实"类问题。
六、效果与小结
上电后 LCD 稳定显示 Dist: xx.x cm,手掌在 5~150 cm 移动读数跟随流畅、无明显跳变;超量程或无回波显示 Dist: ---- cm,串口同步打印。
全程代码约 300 行,覆盖了嵌入式入门最易翻车的五件事:外设复位释放、输入缓冲配置、控制器 DDRAM 映射、时钟基准校验、跨电平接口。
建议的扩展方向:用板载 P3T1755 温度传感器做声速温度补偿(v ≈ 331.4 + 0.6×T ℃),把 ±cm 级温漂压下来;或改用 CTIMER 输入捕获替代轮询测脉宽,进一步降低 CPU 占用。
参考资料
Adafruit 3942 产品页(5 V 逻辑、40 kHz、附 10 kΩ 分压电阻说明):adafruit.com/product/3942
HC-SR04 数据手册(2–400 cm、3 mm 精度、≥10 µs 触发):cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf
NHD-0116AZ-FL-YBW 规格书(5 V、4/8 位 6800 并口、ST7066U):newhavendisplay.com/content/specs/NHD-0116AZ-FL-YBW.pdf
NXP FRDM-MCXA153 产品页与 Zephyr 板级文档(Cortex-M33 @96 MHz、128 KB Flash / 32 KB RAM):nxp.com/design/design-center/development-boards-and-designs/FRDM-MCXA153
电子产品世界论坛同题过程帖(引脚分配与踩坑佐证):forum.eepw.com.cn/thread/401630/1
我要赚赏金
