这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【Let'sdo2026年第2期】成果贴:实现超声波的测量距离

共1条 1/1 1 跳转至页

【Let'sdo2026年第2期】成果贴:实现超声波的测量距离

高工
2026-10-09 19:22:20     打赏

一:超声波模块知识分享:

HC-SR04超声波传感器是通过发送和接收超声波,利用时间差和声音传播速度,计算出模块到前方障碍物的距离的一种传感器模块。与蝙蝠寻找猎物的回声定位基本原理相同,可以广泛应用于无损测量、水文液位测量、工业场地等领域。

二:实物图片如下所示:

03-3.png

模块工作原理如下所示:

在 TRIG 管脚输入一个 10US 以上的高电平(一般建议 50Us 左右),芯片(TP,TN管脚)便可发出8个 40KHZ的超声波脉冲,然后(RP,RN)检测回波信号。当检测到回波信号后,通过 ECHO 管脚输出,如图所示。

03-1.png

根据 ECHO 管脚输出高电平的持续时间可以计算距离值。即距离值为:(高电平时间*340m/s)/2。

当测量距离超过测量范围时,CS100A 仍会通过 ECHO 管脚输出高电平的信号,高电平的宽度约为 33ms。如下图所示:

03-2.png

测量周期:当芯片通过 ECHO 管脚输出的高电平脉冲后,便可进行下一次测量,所以测量周期取决于测量距离,当测距很近时,ECHO 返回的脉冲宽度较窄,测量周期就很短;当测距较远时,ECHO 返回的脉冲宽度较宽,测量周期也就相应的变长。最坏情况下,被测物体超出测量范围,此时返回的脉冲宽度最长,约为33ms,所以最坏情况下的测量周期大于 33ms 即可(比如测量周期可取 50ms)。

三:程序编写流程如下:

3.1 程序编写:

3.1.1初始化CPU外设信息,串口1完成数据的输出显示

3.1.2.初始化定时器,同时配置 TRIG 和 ECHO 脚的配置,采用外部中断与定时器计时功能。

3.1.3.TRIG 脚循环输出一个大于 10us 的高电平,触发 CS100A 芯片发射超声波

3.1.4.联合外部中断的上升沿和下降沿时间,带入公式 距离(m)=高电平持续时间(s)*声速(340m/s)/2,算出距离值

3.1.5.串口输出位移测量的距离信息,同时可以测量出距离信息。

四:软件代码实现如下:

总体思路:

ECHO 引脚配置为 GPIO 输入并开启双边沿外部中断,

CTIMER0 定时器以 1MHz 频率(1 个计数 = 1us)自由运行;

当 ECHO 出现上升沿时进入中断并读取定时器计数值作为计时起点,出现下降沿时再次进入中断读取计数值作为计时终点,两者之差即为回波高电平持续时间;

最后代入公式 距离(m) = 高电平持续时间(s) × 声速(340m/s) ÷ 2 计算距离,并通过串口输出。

4.1 硬件连接与资源分配

开发板外设资源分配如下表所示(TRIG / ECHO 引脚可按实际接线修改,见 4.2 代码顶部宏定义):

4.2 完整工程代码(main.c)

本工程基于 MCUXpresso SDK 的 FRDM-MCXA153 例程(如 hello_world)修改

/*
 * 文件名:main.c
 * 适用平台:NXP FRDM-MCXA153(MCXA153VLH,Cortex-M33,96MHz)
 * 功能说明:读取 HC-SR04 超声波模块,测量并串口输出距离
 * 实现思路:GPIO 外部中断 + CTIMER0 定时器计时(对应第三章 3.1.1 ~ 3.1.5)
 *  - ECHO 上升沿中断:读取 CTIMER0 计数值(计时起点)
 *  - ECHO 下降沿中断:读取 CTIMER0 计数值(计时终点)
 *  - 距离 = 高电平时间(us) × 声速(340m/s) ÷ 2
 */

#include "fsl_common.h"
#include "fsl_gpio.h"
#include "fsl_port.h"
#include "fsl_ctimer.h"
#include "fsl_debug_console.h"
#include "clock_config.h"
#include "pin_mux.h"

/*============================================================================
 * 宏定义:引脚与测量参数(按实际接线修改)
 *==========================================================================*/
/* TRIG 触发脚:GPIO 输出 */
#define HCSR04_TRIG_PORT        PORT3
#define HCSR04_TRIG_GPIO        GPIO3
#define HCSR04_TRIG_PIN         28U

/* ECHO 回波脚:GPIO 输入 + 双边沿外部中断 */
#define HCSR04_ECHO_PORT        PORT3
#define HCSR04_ECHO_GPIO        GPIO3
#define HCSR04_ECHO_PIN         27U
#define HCSR04_ECHO_IRQ         GPIO3_IRQn            /* ECHO 所在端口的 NVIC 中断号 */
#define HCSR04_ECHO_IRQ_HANDLER GPIO3_IRQHandler      /* 外部中断服务函数名 */

/* CTIMER0:32 位定时器,1MHz 计数(1 个计数 = 1us) */
#define HCSR04_TIMER            CTIMER0
#define HCSR04_TIMER_PRESCALE   11U   /* 12MHz / (11+1) = 1MHz */

#define HCSR04_TRIG_PULSE_US     20U     /* 触发高电平宽度,必须大于 10us */
#define HCSR04_MEASURE_PERIOD_US 50000U  /* 测量周期 50ms,大于最大回波 33ms */
#define HCSR04_ECHO_TIMEOUT_US   40000U  /* 回波超时 40ms,防止程序卡死 */
#define HCSR04_MIN_PULSE_US      50U     /* 有效回波最小脉宽(约 0.9cm 盲区) */
#define HCSR04_MAX_PULSE_US      30000U  /* 有效回波最大脉宽(约 5.1m 量程) */

/* 声速 340m/s(20℃ 空气中近似值) */
#define SOUND_SPEED             340U

/*============================================================================
 * 全局变量(供中断服务函数与主循环共享,需加 volatile)
 *==========================================================================*/
static volatile uint32_t s_echoStartTick = 0U;   /* 上升沿时刻计数值 */
static volatile uint32_t s_echoEndTick   = 0U;   /* 下降沿时刻计数值 */
static volatile bool     s_echoValid     = false;/* 本次回波测量是否完成 */
static volatile uint8_t  s_echoState     = 0U;   /* 0=空闲 1=等待上升沿 2=等待下降沿 */

/*============================================================================
 * CTIMER0 初始化:配置为自由运行计数,计数频率 1MHz
 *==========================================================================*/
static void HCSR04_TimerInit(void)
{
    ctimer_config_t config;

    /* 为 CTIMER0 提供 12MHz 时钟(FRO12M),并配置预分频
     * (若所用 SDK 版本枚举名不同,以 Config Tools 生成结果为准) */
    CLOCK_SetClockDiv(kCLOCK_DivCTimer0, 1U);
    CLOCK_AttachClk(kFRO12M_to_CTIMER0);

    CTIMER_GetDefaultConfig(&config);
    config.prescale = HCSR04_TIMER_PRESCALE;    /* 12MHz/12 = 1MHz,即 1us 计数一次 */
    CTIMER_Init(HCSR04_TIMER, &config);
    CTIMER_StartTimer(HCSR04_TIMER);            /* 启动自由运行计数 */
}

/*============================================================================
 * GPIO 初始化:TRIG 输出、ECHO 输入 + 双边沿外部中断
 *==========================================================================*/
static void HCSR04_GPIOInit(void)
{
    gpio_pin_config_t trigConfig = { kGPIO_DigitalOutput, 0U };
    gpio_pin_config_t echoConfig = { kGPIO_DigitalInput,  0U };

    /* 引脚复用为 GPIO(复位后默认为 GPIO,可省略) */
    PORT_SetPinMux(HCSR04_TRIG_PORT, HCSR04_TRIG_PIN, kPORT_MuxAsGpio);
    PORT_SetPinMux(HCSR04_ECHO_PORT, HCSR04_ECHO_PIN, kPORT_MuxAsGpio);

    GPIO_PinInit(HCSR04_TRIG_GPIO, HCSR04_TRIG_PIN, &trigConfig);
    GPIO_PinInit(HCSR04_ECHO_GPIO, HCSR04_ECHO_PIN, &echoConfig);

    /* ECHO 配置双边沿外部中断,并打开 NVIC 总开关 */
    GPIO_SetPinInterruptConfig(HCSR04_ECHO_GPIO, HCSR04_ECHO_PIN, kGPIO_InterruptEitherEdge);
    EnableIRQ(HCSR04_ECHO_IRQ);

    GPIO_PinWrite(HCSR04_TRIG_GPIO, HCSR04_TRIG_PIN, 0U);
}

/*============================================================================
 * ECHO 外部中断服务函数:上升沿记录计时起点,下降沿记录计时终点
 *==========================================================================*/
void HCSR04_ECHO_IRQ_HANDLER(void)
{
    uint32_t flags = GPIO_PortGetInterruptFlags(HCSR04_ECHO_GPIO);

    if (flags & (1UL << HCSR04_ECHO_PIN))
    {
        uint32_t pinState = GPIO_PinRead(HCSR04_ECHO_GPIO, HCSR04_ECHO_PIN);

        switch (s_echoState)
        {
            case 1U:                              /* 等待上升沿 */
                if (pinState != 0U)
                {
                    s_echoStartTick = CTIMER0->TC; /* 上升沿:记录计时起点 */
                    s_echoState = 2U;
                }
                break;

            case 2U:                              /* 等待下降沿 */
                if (pinState == 0U)
                {
                    s_echoEndTick = CTIMER0->TC;  /* 下降沿:记录计时终点 */
                    s_echoValid = true;
                    s_echoState = 0U;
                }
                break;

            default:
                break;
        }

        /* 清除中断标志,避免重复进入中断 */
        GPIO_GpioClearInterruptFlags(HCSR04_ECHO_GPIO, 1UL << HCSR04_ECHO_PIN);
    }

    SDK_ISR_EXIT_BARRIER;
}

/*============================================================================
 * 触发一次测量并等待回波完成(带超时保护)
 * 返回回波高电平持续时间(us);超时返回 0
 *==========================================================================*/
static uint32_t HCSR04_MeasurePulse(void)
{
    uint32_t timeoutStart;

    s_echoValid = false;
    s_echoState = 0U;
    GPIO_GpioClearInterruptFlags(HCSR04_ECHO_GPIO, 1UL << HCSR04_ECHO_PIN);

    /* 若 ECHO 当前为低电平,则准备等待上升沿 */
    if (GPIO_PinRead(HCSR04_ECHO_GPIO, HCSR04_ECHO_PIN) == 0U)
    {
        s_echoState = 1U;
    }

    /* 3.1.3:TRIG 输出大于 10us 的高电平,触发芯片发射超声波 */
    GPIO_PinWrite(HCSR04_TRIG_GPIO, HCSR04_TRIG_PIN, 1U);
    SDK_DelayAtLeastUs(HCSR04_TRIG_PULSE_US, SystemCoreClock);
    GPIO_PinWrite(HCSR04_TRIG_GPIO, HCSR04_TRIG_PIN, 0U);

    /* 等待外部中断完成计时(上升沿 + 下降沿),或超时退出 */
    timeoutStart = CTIMER0->TC;
    while (!s_echoValid)
    {
        if ((uint32_t)(CTIMER0->TC - timeoutStart) > HCSR04_ECHO_TIMEOUT_US)
        {
            s_echoState = 0U;
            return 0U;                            /* 超时:无回波 */
        }
    }

    return s_echoEndTick - s_echoStartTick;       /* 高电平持续时间(us) */
}

/*============================================================================
 * 主函数
 *==========================================================================*/
int main(void)
{
    BOARD_InitHardware();                          /* 3.1.1:CPU 外设、时钟与串口初始化 */

    PRINTF("\r\n========== FRDM-MCXA153 HC-SR04 超声波测距 ==========\r\n");

    HCSR04_TimerInit();                            /* 3.1.2:初始化定时器 */
    HCSR04_GPIOInit();                             /* 3.1.2:配置 TRIG/ECHO 脚与外部中断 */

    while (1)
    {
        uint32_t pulseUs = HCSR04_MeasurePulse();

        if (pulseUs == 0U)
        {
            PRINTF("无回波(超时)\r\n");
        }
        else if (pulseUs < HCSR04_MIN_PULSE_US)
        {
            PRINTF("距离过近(盲区)\r\n");
        }
        else if (pulseUs > HCSR04_MAX_PULSE_US)
        {
            PRINTF("超出测量范围\r\n");
        }
        else
        {
            /* 3.1.4:距离(m) = 高电平时间(s) × 声速(340m/s) ÷ 2
             * 换算成十分之一厘米:十分之一厘米 = 脉宽(us) × 340 ÷ 2000
             * 例:脉宽 1494us → 1494×340÷2000 = 254,即 25.4cm */
            uint32_t distTenthsCm = (uint32_t)(((uint64_t)pulseUs * SOUND_SPEED) / 2000U);

            /* 3.1.5:串口输出距离信息 */
            PRINTF("Distance: %u.%u cm   (Echo pulse: %u us)\r\n",
                   distTenthsCm / 10U,
                   distTenthsCm % 10U,
                   (unsigned int)pulseUs);
        }

        /* 测量周期 50ms:大于最坏情况下的回波宽度 33ms */
        SDK_DelayAtLeastUs(HCSR04_MEASURE_PERIOD_US, SystemCoreClock);
    }
}

4.3 代码与第三章流程的对应说明

3.1.1 初始化CPU外设信息,串口1完成数据的输出显示 —— main 函数中调用 BOARD_InitHardware() 完成系统时钟、引脚复用等外设初始化;调试串口使用板载 USB 虚拟串口(LPUART0,115200-8-N-1),代码中使用 PRINTF 输出测距数据。

3.1.2 初始化定时器,配置 TRIG 和 ECHO 脚,采用外部中断与定时器计时功能 —— HCSR04_TimerInit() 将 CTIMER0 配置为 1MHz 自由运行计数;HCSR04_GPIOInit() 将 TRIG 配置为 GPIO 输出、ECHO 配置为 GPIO 输入并开启双边沿外部中断(本例为 GPIO3_IRQn)。

3.1.3 TRIG 脚循环输出一个大于 10us 的高电平,触发芯片发射超声波 —— HCSR04_MeasurePulse() 中令 TRIG 输出 20us 高电平(大于 10us),触发 HC-SR04 芯片发射 8 个 40kHz 超声波脉冲,同时 ECHO 被拉高开始回波计时。

3.1.4 联合外部中断的上升沿和下降沿时间,带入公式计算距离 —— 中断服务函数 HCSR04_ECHO_IRQ_HANDLER 中:上升沿时读取 CTIMER0 计数值作为计时起点,下降沿时读取计数值作为计时终点,脉宽 = 终点 − 起点;主循环将脉宽代入 距离 = 脉宽 × 声速 ÷ 2 计算距离值。

3.1.5 串口输出位移测量的距离信息 —— 主循环每 50ms 触发一次测量,通过 PRINTF 串口输出距离值;测量周期大于第二章所述最坏情况 33ms 的回波宽度,保证每次测量互不干扰。

4.4 编译烧录与运行结果

编译烧录步骤:

  1. 在 keil中导入 FRDM-MCXA153 的 hello_world 例程(SDK 26.x)。

  2. 将工程中的 main.c 替换为 4.2 节代码;CTIMER0 时钟(FRO 12MHz、预分频 11)与引脚配置也可用 Config Tools 图形化生成。

  3. 编译并烧录到开发板,打开串口助手(波特率 115200,8N1)。

注意事项:

若 40ms 内未收到回波,串口输出“无回波(超时)”;ECHO 高电平超过 30ms 时判为“超出测量范围”(对应第二章介绍的约 33ms 回波情况)。

TRIG / ECHO 引脚按实际接线修改代码顶部宏定义;若 ECHO 改接到其他 GPIO 端口,中断号与中断服务函数名需同步修改(GPIO0_IRQn/GPIO0_IRQHandler … GPIO3_IRQn/GPIO3_IRQHandler)。

计时精度:1MHz 计数(1us)分辨率下,单个计数的距离误差约 0.017cm(340m/s × 1us ÷ 2),与第五章分析一致;实际误差还受环境温度(声速变化)与 ECHO 边沿抖动影响。

若希望进一步降低 CPU 开销,可改用 CTIMER0 的输入捕获功能(硬件自动记录边沿时刻,无需在中断中读取计数值),可参考 SDK 中 ctimer 输入捕获例程。

五:感想如下:

这里使用的定时器来读取返回的时候,之前使用定时器做过基本的计数,PWM输出可调脉宽、频率,输入捕获等等功能,在本次任务中,本想使用定时器的输入捕获功能,但是考虑到编写代码对于一些刚刚接触STM32的,不方便理解,这里我是用定时器+外部中断的方式来获取时间,即检测到某个IO口的上升沿来启动定时器,检测到下降沿的时候关闭定时器,同时读取定时器内部的计数值。由于超声波是往返过程,需要进行平分处理,得到当前超声波检测的数据即可。在开始调试代码的时候,发现定时器配置参数不合适,倒是检测的误差有点大,最后测试在1us的时间间隔内进行进入定时器的中断计数,这样误差在每个脉冲0.034cm。由于超声波模块测量的最大距离是5.6m。考虑到超声波在空气中传播速度因素,以及定时器计数的时间间隔因素,都是影响测量的精度。



关键词: 简易测距     成果贴    

共1条 1/1 1 跳转至页

回复

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