这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【Let'sDo2026P2】简易超声波测距仪实践帖:FRDM-MCXA153+

共1条 1/1 1 跳转至页

【Let'sDo2026P2】简易超声波测距仪实践帖:FRDM-MCXA153+HC-SR04+16×1字符LCD

菜鸟
2026-10-12 00:51:33     打赏

【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



共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]