这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【let'sdo|2026年第2期】简易超声波测距仪实践【过程-任务3:超声波数

共1条 1/1 1 跳转至

【let'sdo|2026年第2期】简易超声波测距仪实践【过程-任务3:超声波数据屏幕显示】

助工
2026-09-13 11:48:33     打赏


一、硬件介绍

1、产品特点

FRDM-MCXA153是一款NXP推出的基于Arm Cortex-M33内核(96MHz)开发板,搭载128KB带ECC纠错Flash与32KB SRAM,板载MCU-Link高速调试器,双USB‑C分别用于调试通信与设备功能,集成RGB指示灯、PMOD接口、用户按键、温度传感器等资源;




主要特性

内核:Arm Cortex-M33(96MHz),无FPU / TrustZone

Flash:128KB(ECC 校验,1 位纠错、2 位检错)

SRAM:32KB,其中8KB 支持 ECC


芯片运行功耗:59μA/MHz

掉电模式(SRAM 全保留):6.5μA

深度掉电休眠:0.39μA(394nA)


集成 MCU-Link 片上调试器(主控 LPC55S69)

RGB三色LED

3个用户按键(Reset、WakeUp、ISP)

I3C/I2C 数字温度传感器(P3T1755)


系统框图



2、引脚示意图



3、超声波测距模块

Unit Ultrasonic-IO 是一款GPIO接口的超声波测距传感器;

硬件采用RCWL-9620超声波测距单芯片搭配16mm探头,能够实现2cm-450 cm范围内的精准测距;



主要特性



4、OLED屏幕

Grove - OLED 黄蓝显示屏 0.96 (SSD1315) 是一款蓝色和黄色双色显示屏,分辨率为 128 * 64;



主要特性




二、环境搭建

详见【快速环境搭建



三、功能实现

1、硬件搭建

将超声波测距模块连接到开发板的PMOD接口的对应引脚上,以及将OLED屏幕模块连接到板载的I2C接口上;



实物搭建效果:







2、功能效果

效果:MCU获取到超声波模块检测到的数据后,在OLED屏幕上实时显示数据;


超声波模块工作原理

通过外部MCU给模块Trig脚一个大于10uS的高电平脉冲;

模块会给出一个与距离等比的高电平脉冲信号,可根据脉宽时间“T”算出当前距离;


距离 = T * C / 2 (C为声速)


声速温度公式:c ≈ 331.5 + 0.61 * t

例:25℃:

c ≈ 331.5 + 0.61 * 25 = 346.75 m/s


时序图



主要运行流程

时钟/引脚/外设/串口等初始化 → SysTick计数器 → 触发11us脉冲 → 等ECHO边沿中断记录时间 → 根据脉宽换算距离 → 屏幕显示距离数值;


四、代码编写

1、在之前`led blinky`模板中,使用 `Config Tools` 图形化工具,继续配置相关功能;


2、引脚功能配置

在之前的基础上,配置 LPI2C0_SDA、LPI2C0_SCL 功能引脚;


GPIO3_12:LED_RED

GPIO3_27:ECHO_PIN (开启 双边沿触发)

GPIO3_28:TRIG_PIN

GPIO1_0:SDA

GPIO1_1:SCL



3、时钟配置

使用内部RC时钟源

系统主频 96Mhz

FRO_HF_clock / LPUART0_clock:96Mhz

开启LPI2C0时钟:48Mhz;


4、外设功能配置

配置LPI2C0:100kHz


5、开启I2C SDK组件



在 Driver Configuration 路径下勾选lpi2c


6、在当前工程文件下移植`u8g2库`,用于屏幕功能的开发使用;

将下载解压后的u8g2文件夹中csrc文件夾复制到当前工程中;



主要相关代码

`u8g2_hal.c` / `u8g2_hal.h` :U8G2库与开发板I2C接口适配;

`main.c`:初始化(时钟/引脚/外设/串口 → HC-SR04 → SysTick → U8G2)+ 主循环(测距显示);


超声波测距:TRIG触发、ECHO双边沿IO中断、SysTick计时距离转换、中值滤波;

屏幕显示

第一行显示标题:距离显示 ;

第二行显示距离值:数字 ;

第三行显示单位:cm ;


u8g2_hal.c: 
...
#define U8G2_I2C_ADDR 0x3C /* 屏幕地址 */

static uint8_t s_buf[64];
static uint8_t s_len;

/* SysTick 微秒延时 */
static void hal_delay_us(uint32_t us)
{
    uint32_t start = SysTick->VAL;
    uint32_t ticks = us * 96u;
    while (((start - SysTick->VAL) & 0xFFFFFFu) < ticks)
    {
    }
}

uint8_t u8x8_byte_lpi2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    uint8_t *p = (uint8_t *)arg_ptr;
    uint8_t i;
    (void)u8x8;

    switch (msg)
    {
    case U8X8_MSG_BYTE_INIT:
    case U8X8_MSG_BYTE_SET_DC:
        break;
    case U8X8_MSG_BYTE_START_TRANSFER:
        s_len = 0;
        break;
    case U8X8_MSG_BYTE_SEND:
        for (i = 0; i < arg_int; i++)
        {
            if (s_len < sizeof(s_buf))
            {
                s_buf[s_len++] = p[i];
            }
        }
        break;
    case U8X8_MSG_BYTE_END_TRANSFER:
        if (s_len > 0)
        {
            lpi2c_master_transfer_t xfer;
            memset(&xfer, 0, sizeof(xfer));
            xfer.slaveAddress = U8G2_I2C_ADDR;
            xfer.direction = kLPI2C_Write;
            xfer.subaddress = 0;
            xfer.subaddressSize = 0;
            xfer.data = s_buf;
            xfer.dataSize = s_len;
            xfer.flags = kLPI2C_TransferDefaultFlag;
            (void)LPI2C_MasterTransferBlocking(LPI2C0_PERIPHERAL, &xfer);
        }
        s_len = 0;
        break;
    default:
        return 0;
    }
    return 1;
}

uint8_t u8x8_gpio_and_delay_lpi2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    (void)u8x8;
    (void)arg_ptr;

    switch (msg)
    {
    case U8X8_MSG_DELAY_MILLI:
    {
        uint32_t ms = arg_int;
        while (ms-- > 0)
        {
            hal_delay_us(1000u);
        }
        break;
    }
    case U8X8_MSG_DELAY_10MICRO:
        hal_delay_us(10u);
        break;
    case U8X8_MSG_DELAY_100NANO:
        hal_delay_us(1u);
        break;
    case U8X8_MSG_DELAY_NANO:
        break;
    case U8X8_MSG_GPIO_AND_DELAY_INIT:
        break;
    default:
        return 0;
    }
    return 1;
}

u8g2_hal.h:
#ifndef _U8G2_HAL_H_
#define _U8G2_HAL_H_
#include "u8g2.h"
/* I2C 数据收发回调函数 */
uint8_t u8x8_byte_lpi2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
/* U8G2 延时与GPIO引脚回调函数 */
uint8_t u8x8_gpio_and_delay_lpi2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
#endif /* _U8G2_HAL_H_ */


main.c:

    ...

// TRIG = P3_28
#define HC_SR04_TRIG_HANDLE BOARD_INITPINS_TRIG_PIN_handle
// ECHO = P3_27
#define HC_SR04_ECHO_HANDLE BOARD_INITPINS_ECHO_PIN_handle

volatile uint32_t echo_start_tick = 0U;
volatile uint32_t echo_end_tick = 0U;
volatile uint8_t echo_capture_done = 0U;

u8g2_t u8g2;

void Systick_FreeCounter_Init(void)
{
    SysTick->LOAD = 0xFFFFFFU;
    SysTick->VAL = 0U;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
}

/*
 * ECHO 引脚中断回调
 * 由 GPIO3_IRQHandler 自动分发并自动清中断标志
 * 读引脚电平判断上升/下降沿
 */
static void HC_SR04_Echo_Callback(void *param)
{
    uint8_t level;
    (void)param;
    (void)HAL_GpioGetInput(HC_SR04_ECHO_HANDLE, &level);
    if (level != 0U)
    {
        echo_start_tick = SysTick->VAL;
        echo_capture_done = 0U;
    }
    else
    {
        echo_end_tick = SysTick->VAL;
        echo_capture_done = 1U;
    }
}

void HC_SR04_Gpio_Init(void)
{
    /* 注册 ECHO 中断回调 */
    (void)HAL_GpioInstallCallback(HC_SR04_ECHO_HANDLE, HC_SR04_Echo_Callback, NULL);
    /* 双边沿中断触发 */
    (void)HAL_GpioSetTriggerMode(HC_SR04_ECHO_HANDLE, kHAL_GpioInterruptEitherEdge);
}

/*
 * SysTick 延时
 * 1us = 96 ticks;SysTick->VAL 递减计数
 */
static void delay_us(uint32_t us)
{
    uint32_t start = SysTick->VAL;
    uint32_t ticks = us * 96U;

    while (((start - SysTick->VAL) & 0xFFFFFFU) < ticks)
    {
    }
}

static void delay_ms(uint32_t ms)
{
    while (ms > 0U)
    {
        delay_us(1000U);
        ms--;
    }
}

void HC_SR04_Trigger(void)
{
    (void)HAL_GpioSetOutput(HC_SR04_TRIG_HANDLE, 1U);
    delay_us(11); /* TRIG ≥10us 精准延时 */
    (void)HAL_GpioSetOutput(HC_SR04_TRIG_HANDLE, 0U);
}

float HC_SR04_ReadDistance(void)
{
    uint32_t delta_tick;
    uint32_t pulse_us;
    uint32_t timeout = 0U;

    echo_capture_done = 0U;
    HC_SR04_Trigger();

    while ((echo_capture_done == 0U) && (timeout < 300000U))
    {
        timeout++;
    }

    if (echo_capture_done == 0U)
    {
        return 0.0f;
    }

    if (echo_end_tick > echo_start_tick)
    {
        delta_tick = (0xFFFFFFU - echo_start_tick) + echo_end_tick;
    }
    else
    {
        delta_tick = echo_start_tick - echo_end_tick;
    }

    pulse_us = delta_tick / 96U;
    float distance_cm = ((float)pulse_us * 0.034f) / 2.0f;
    return distance_cm;
}

/*
 * 3点中值滤波
 */
static float Median_Filter_3(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;
}

/* ---- 标题汉字 ---- */
static const uint8_t zh16x16_data[4][32] = {
    {0x00, 0x00, 0x7D, 0xFE, 0x45, 0x00, 0x45, 0x00, 0x45, 0x00, 0x7D, 0xFC, 0x11, 0x04, 0x11, 0x04,
     0x5D, 0x04, 0x51, 0x04, 0x51, 0xFC, 0x51, 0x00, 0x5D, 0x00, 0xE1, 0x00, 0x01, 0xFE, 0x00, 0x00}, /* 距 */
    {0x02, 0x00, 0x01, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x14, 0x50, 0x13, 0x90, 0x14, 0x50, 0x1F, 0xF0,
     0x01, 0x00, 0x7F, 0xFC, 0x42, 0x04, 0x44, 0x44, 0x4F, 0xE4, 0x44, 0x24, 0x40, 0x14, 0x40, 0x08}, /* 离 */
    {0x00, 0x00, 0x1F, 0xF0, 0x10, 0x10, 0x10, 0x10, 0x1F, 0xF0, 0x10, 0x10, 0x10, 0x10, 0x1F, 0xF0,
     0x04, 0x40, 0x44, 0x44, 0x24, 0x44, 0x14, 0x48, 0x14, 0x50, 0x04, 0x40, 0xFF, 0xFE, 0x00, 0x00}, /* 显 */
    {0x00, 0x00, 0x3F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0x01, 0x00,
     0x01, 0x00, 0x11, 0x10, 0x11, 0x08, 0x21, 0x04, 0x41, 0x02, 0x81, 0x02, 0x05, 0x00, 0x02, 0x00}, /* 示 */
};

/* 用 U8G2 绘制 16x16 汉字 */
static void draw_zh(u8g2_t *u8g2, uint8_t x, uint8_t y, uint8_t idx)
{
    static uint8_t xbm[32];
    uint8_t i, b;
    for (i = 0; i < 32; i++)
    {
        uint8_t v = zh16x16_data[idx][i];
        uint8_t r = 0;
        for (b = 0; b < 8; b++)
        {
            if ((v >> b) & 1U)
            {
                r |= (uint8_t)(1U << (7 - b));
            }
        }
        xbm[i] = r;
    }
    u8g2_DrawXBM(u8g2, x, y, 16, 16, xbm);
}

int main(void)
{
    float dist;
    char str[16];

    BOARD_InitBootClocks();      // 96MHz
    BOARD_InitPins();            // 引脚功能初始化
    BOARD_InitBootPeripherals(); // 外设功能初始化
    BOARD_InitDebugConsole();    // 板载串口功能初始化

    HC_SR04_Gpio_Init();        // 超声波模块初始化
    Systick_FreeCounter_Init(); // SysTick 计数器初始化

    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0,
                                           u8x8_byte_lpi2c,
                                           u8x8_gpio_and_delay_lpi2c);
    u8g2_InitDisplay(&u8g2);
    u8g2_SetPowerSave(&u8g2, 0);
    u8g2_ClearBuffer(&u8g2);

    while (1)
    {
        /* 3点中值滤波 */
        dist = Median_Filter_3(HC_SR04_ReadDistance(),
                               HC_SR04_ReadDistance(),
                               HC_SR04_ReadDistance());

        /* 有效量程 2cm ~ 450cm */
        if ((dist >= 2.0f) && (dist <= 450.0f))
        {
            PRINTF("Distance = %.2f cm\r\n", dist);
        }
        else
        {
            PRINTF("No echo\r\n");
            dist = 0.0f;
        }
        snprintf(str, sizeof(str), "%.2f", dist);

        u8g2_FirstPage(&u8g2);
        do
        {
            /* 标题:顶部 */
            draw_zh(&u8g2, 32, 0, 0); /* 距 */
            draw_zh(&u8g2, 48, 0, 1); /* 离 */
            draw_zh(&u8g2, 64, 0, 2); /* 显 */
            draw_zh(&u8g2, 80, 0, 3); /* 示 */

            /* 数值:中间 */
            u8g2_SetFont(&u8g2, u8g2_font_ncenB24_tr);
            {
                int dw = u8g2_GetStrWidth(&u8g2, str);
                u8g2_DrawStr(&u8g2, (128 - dw) / 2, 44, str);
            }

            /* 单位:底部 */
            u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
            {
                int cw = u8g2_GetStrWidth(&u8g2, "cm");
                u8g2_DrawStr(&u8g2, (128 - cw) / 2, 62, "cm");
            }
        } while (u8g2_NextPage(&u8g2));

        delay_ms(200);
    }
}



五、程序烧录

1、连接USB数据线至开发板MUC-LINK端口;

2、右键当前工程项目;

3、点击 Flash the Selected Target 烧录程序到开发板上;




六、效果演示










共1条 1/1 1 跳转至

回复

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