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

共1条 1/1 1 跳转至

【let'sdo|2026年第2期】简易超声波测距仪实践-3,成果贴

助工
2026-09-01 15:51:44     打赏

        目前已完成超声波测距和LCD显示功能,接下来将进行代码整合,并新增按键与LED控制逻辑。其中,SW2按键用于循环切换显示的距离量程(mm → cm → m),当测量距离在5cm-200cm之间绿灯常亮,低于5cm之间红灯闪烁,测量失败蓝灯亮。

        frdmmcxa153ultrasonic_ranginghardware_init.c,注意:要开启相应的时钟,不然运行不起来

void BOARD_InitHardware(void)
{
    // 定时器1时钟
    CLOCK_SetClockDiv(kCLOCK_DivCTIMER1, 1u);
    CLOCK_AttachClk(kFRO_HF_to_CTIMER1);
    // I2C时钟
    CLOCK_SetClockDiv(kCLOCK_DivLPI2C0, 1u);
    CLOCK_AttachClk(kFRO12M_to_LPI2C0);
    // 定时器2时钟
    CLOCK_SetClockDiv(kCLOCK_DivCTIMER2, 1u);
    CLOCK_AttachClk(kFRO_HF_to_CTIMER2);

    BOARD_InitPins();
    BOARD_InitBootClocks();
    BOARD_InitDebugConsole();
}

        ultrasonic_ranging.c

/*******************************************************************************
 * Include
 ******************************************************************************/
#include "fsl_gpio.h"
#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "board.h"
#include "app.h"
#include "fsl_ctimer.h"
#include "pin_mux.h"
#include "fsl_lpi2c.h"
#include <stdio.h>
#include <string.h>

/*******************************************************************************
 * Definitions
 ******************************************************************************/
// 距离阈值定义(单位:微米 um)
#define CM_TO_UM(cm)        ((cm) * 10000)
#define DISTANCE_WARNING_CM 5      // 5cm 警戒距离

// 滤波器参数
// 限幅阈值:真实移动时允许的变化量
#define MAX_REAL_CHANGE      200000   // 20cm(手移动的速度不可能超过这个)
#define MIN_JITTER_THRESHOLD 5000     // 5mm 以下的波动认为是抖动


/*******************************************************************************
 * Prototypes
 ******************************************************************************/
// 定时器相关
static void Timer_Init(void);
static void Timer_Start_Timing(void);
static uint32_t Timer_GetTimestampUs(void);
static void Timer_DelayUs(uint32_t us);
static void Timer_DelayMs(uint32_t ms);

// 超声波相关
static void Echo_Init(void);
static uint32_t Ultrasonic_Ranging_GetUm(void);
static uint32_t ultrasonic_filter(uint32_t new_value);
static void reset_filter(void);

// LCD相关
static void LCD_I2C_init(void);
static void LCD_Device_Init(void);
static void LCD_PrintString(char *str);
static void Get_Show_Distance(char *show_data, uint32_t length);

// 其他
static void Key_SW2_init(void);
static void LED_Ctimer_Init(void);
static void format_distance(int32_t value, char unit, char *buffer);
static int32_t convert_um_to_unit(uint32_t um, char unit);

/*******************************************************************************
 * Variables
 ******************************************************************************/
// 超声波测量变量
volatile uint32_t echo_start_time = 0;
volatile uint32_t echo_pulse_width = 0;
volatile uint8_t echo_ready = 0;
volatile uint8_t sw2_status = 0;

// 定时器回调
static ctimer_callback_t timer_callback_array[1] = {NULL};

// 距离数据结构
typedef struct {
    uint32_t um;        // 微米原始值
    int32_t  value;     // 转换后的值(放大100倍,保留两位小数)
    char     unit;      // 单位:'m'、'c'、'l'(mm)
    char     str[20];   // 格式化字符串
} DistanceData;

// LED状态枚举
typedef enum {
    LED_GREEN,      // 正常(距离安全)
    LED_RED_BLINK,  // 警告(距离过近,红灯闪烁)
    LED_BLUE        // 超时/错误
} LedState_t;



static uint32_t last_valid_value = 0;
static uint8_t first_sample = 1;

// 连续稳定计数器:用于判断是否真的稳定下来
static uint8_t stable_count = 0;
static uint32_t candidate_value = 0;   // 正在验证的新值

// 系统状态
LedState_t system_state = LED_GREEN;

/*******************************************************************************
 * Code
 ******************************************************************************/

/*!
 * @brief Main function
 */
int main(void)
{
    /* Init hardware*/
    BOARD_InitHardware();
    PRINTF("MCUX SDK version: %srn", MCUXSDK_VERSION_FULL_STR);

    // 初始化各模块
    Echo_Init();
    LED_Ctimer_Init();
    Timer_Init();
    Key_SW2_init();
    LCD_I2C_init();
    LCD_Device_Init();
    
    // 初始化滤波器
    reset_filter();
    
    // 初始显示
    LCD_PrintString("Ultrasonic Rng");
    Timer_DelayMs(1000);
    
    char lcd_show_buf[20] = {0};
    uint32_t raw_dist = 0;
    uint32_t filtered_dist = 0;
    
    while (1)
    {
        // 获取原始测量值
        raw_dist = Ultrasonic_Ranging_GetUm();
        PRINTF("raw_dist = %urn", raw_dist);
        if (raw_dist > 0) 
        {
            // 应用滤波
            filtered_dist = ultrasonic_filter(raw_dist);
            PRINTF("filtered_dist = %urn", filtered_dist);
            // 显示滤波后的数据
            memset(lcd_show_buf, 0, sizeof(lcd_show_buf));
            Get_Show_Distance(lcd_show_buf, filtered_dist);
            LCD_PrintString(lcd_show_buf);
            
            // 状态判断:使用滤波后的数据
            if (filtered_dist < CM_TO_UM(DISTANCE_WARNING_CM)) 
            {
                system_state = LED_RED_BLINK;  // 距离过近,红灯闪烁
            } 
            else 
            {
                system_state = LED_GREEN;      // 距离安全,绿灯常亮
            }
            // 调试打印
            // PRINTF("Raw: %d um, Filtered: %d umrn", raw_dist, filtered_dist);
        } 
        else 
        {
            LCD_PrintString("Timeout!!!");
            PRINTF("Measurement timeout!rn");
            system_state = LED_BLUE;  // 超时,蓝灯常亮
        }
        
        Timer_DelayMs(500);
    }
}


/*******************************************************************************
 * 滤波函数
 ******************************************************************************/
static uint32_t ultrasonic_filter(uint32_t new_value) 
{
    // 1. 第一次测量
    if (first_sample) 
    {
        first_sample = 0;
        last_valid_value = new_value;
        candidate_value = new_value;
        stable_count = 0;
        return new_value;
    }
    
    // 2. 计算变化量
    int32_t diff = (int32_t)new_value - (int32_t)last_valid_value;
    uint32_t abs_diff = (diff > 0) ? diff : -diff;
    
    // 3. 小抖动:直接忽略
    if (abs_diff < MIN_JITTER_THRESHOLD) 
    {
        // 抖动范围内,保持上次值不变
        stable_count = 0;  // 重置稳定计数(因为还在抖动)
        return last_valid_value;
    }
    
    // 4. 大变化:判断是真实移动还是飞点
    if (abs_diff > MAX_REAL_CHANGE) 
    {
        // 变化超过20cm,可能是飞点
        // 策略:如果连续两次测量都指向同一个方向,才认为是真实移动
        
        // 检查候选值
        if (candidate_value == last_valid_value) 
        {
            // 第一次出现大幅变化
            candidate_value = new_value;
            stable_count = 1;
            return last_valid_value;  // 先返回旧值
        } 
        else 
        {
            // 已经有候选值了,比较方向是否一致
            int32_t diff_candidate = (int32_t)candidate_value - (int32_t)last_valid_value;
            int32_t diff_new = (int32_t)new_value - (int32_t)last_valid_value;
            
            // 方向一致(同正或同负)
            if ((diff_candidate > 0 && diff_new > 0) || (diff_candidate < 0 && diff_new < 0)) 
            {
                stable_count++;
                if (stable_count >= 2) 
                {
                    // 连续2次向同一方向大幅变化,接受
                    last_valid_value = new_value;
                    candidate_value = new_value;
                    stable_count = 0;
                    return new_value;
                }
                // 更新候选值
                candidate_value = new_value;
                return last_valid_value;
            } 
            else 
            {
                // 方向不一致,可能是飞点,重置
                candidate_value = new_value;
                stable_count = 1;
                return last_valid_value;
            }
        }
    }
    
    // 5. 正常范围内的变化(5mm ~ 20cm):直接接受,并做轻微平滑
    stable_count = 0;  // 重置候选状态
    
    // 如果变化小于3cm,做一点平滑减少视觉抖动
    if (abs_diff < 30000) 
    {
        // 60% 旧值 + 40% 新值
        uint32_t smoothed = (uint32_t)(last_valid_value * 0.6f + new_value * 0.4f);
        last_valid_value = smoothed;
        return smoothed;
    } 
    else 
    {
        // 变化较大,快速响应
        last_valid_value = new_value;
        return new_value;
    }
}

// 重置滤波器
static void reset_filter(void) 
{
    first_sample = 1;
    last_valid_value = 0;
    candidate_value = 0;
    stable_count = 0;
}

/*******************************************************************************
 * 定时器相关 (CTIMER1 - 用于超声波测距)
 ******************************************************************************/

// 定时器初始化
static void Timer_Init(void)
{
    ctimer_config_t config;
    CTIMER_GetDefaultConfig(&config);
    uint32_t timer_clk = CLOCK_GetCTimerClkFreq(1U);
    PRINTF("CTIMER1 input frequency: %u Hzrn", timer_clk);
    
    // 预分频:96MHz -> 1MHz (1 tick = 1us)
    config.prescale = (timer_clk / 1000000UL) - 1;
    
    PRINTF("Prescaler value: %urn", config.prescale);
    PRINTF("Counting frequency: %u Hz (1 tick = 1 us)rn", timer_clk / (config.prescale + 1));
    
    // 初始化定时器
    CTIMER_Init(TIMER, &config);
    Timer_Start_Timing();
}

// 开始计时
static void Timer_Start_Timing(void)
{
    TIMER->TC = 0;
    CTIMER_StartTimer(TIMER);
}

// 获取微秒时间戳
static uint32_t Timer_GetTimestampUs(void)
{
    return CTIMER_GetTimerCountValue(TIMER);
}

// 微秒延时
static void Timer_DelayUs(uint32_t us)
{
    uint32_t start = CTIMER_GetTimerCountValue(TIMER);
    while ((CTIMER_GetTimerCountValue(TIMER) - start) < us);
}

// 毫秒延时
static void Timer_DelayMs(uint32_t ms)
{
    Timer_DelayUs(ms * 1000);
}

/*******************************************************************************
 * 超声波测距
 ******************************************************************************/
static void Echo_Init(void)
{
    // 配置Echo中断
    GPIO_GpioClearInterruptFlags(BOARD_INITPINS_ECHO_GPIO, 1U << BOARD_INITPINS_ECHO_GPIO_PIN);
    GPIO_SetPinInterruptConfig(BOARD_INITPINS_ECHO_GPIO, BOARD_INITPINS_ECHO_GPIO_PIN, kGPIO_InterruptEitherEdge);// 设置双边沿触发
    EnableIRQ(BOARD_ECHO_IRQ);
}
// 获取超声测距数据(单位:微米)
static uint32_t Ultrasonic_Ranging_GetUm(void)
{
    // 清空上次测量数据
    echo_ready = 0;
    echo_pulse_width = 0;
    
    // 触发超声波模块
    GPIO_PortSet(BOARD_INITPINS_TRIG_GPIO, BOARD_INITPINS_TRIG_GPIO_PIN_MASK);
    Timer_DelayUs(15);
    GPIO_PortClear(BOARD_INITPINS_TRIG_GPIO, BOARD_INITPINS_TRIG_GPIO_PIN_MASK);
    
    // 等待测量完成,超时时间 50ms
    uint32_t start_wait = Timer_GetTimestampUs();
    while (!echo_ready) 
    {
        if ((Timer_GetTimestampUs() - start_wait) > 50000) 
        {
            return 0;  // 超时
        }
    }
    
    // 计算距离:脉冲宽度(us) * 声速(um/us) / 2
    // 声速 343m/s = 34300cm/s = 343000um/ms = 343um/us, 基于20℃
    // 距离 = 脉冲宽度 * 343 / 2 = 脉冲宽度 * 171.5 (um)
    // 使用整数运算:距离(um) = echo_pulse_width * 1715 / 10
    uint32_t distance_um = (uint32_t)(echo_pulse_width * 1715 / 10);
    
    return distance_um;
}

// Echo引脚中断处理
void BOARD_ECHO_IRQ_HANDLER(void)
{
    GPIO_GpioClearInterruptFlags(BOARD_INITPINS_ECHO_GPIO, 1U << BOARD_INITPINS_ECHO_GPIO_PIN);

    if (GPIO_PinRead(BOARD_INITPINS_ECHO_GPIO, BOARD_INITPINS_ECHO_GPIO_PIN) == 1U) 
    {
        // 上升沿:开始计时
        echo_start_time = Timer_GetTimestampUs();
    } 
    else 
    {
        // 下降沿:计算脉冲宽度
        uint32_t end_time = Timer_GetTimestampUs();
        if (end_time > echo_start_time) 
        {
            echo_pulse_width = end_time - echo_start_time;
            echo_ready = 1;
        }
    }
    SDK_ISR_EXIT_BARRIER;
}

/*******************************************************************************
 * 距离格式化
 ******************************************************************************/

// 单位转换:微米转指定单位(放大100倍)
static int32_t convert_um_to_unit(uint32_t um, char unit) 
{
    switch(unit) 
    {
        case 'm':  // 转米:um / 1000000 * 100 = um / 10000
            return um / 10000;
        case 'c':  // 转厘米:um / 10000 * 100 = um / 100
            return um / 100;
        case 'l':  // 转毫米:um / 1000 * 100 = um / 10
            return um / 10;
        default:
            return 0;
    }
}

// 格式化输出字符串
static void format_distance(int32_t value, char unit, char *buffer) 
{
    int32_t integer_part = value / 100;
    int32_t decimal_part = value % 100;
    
    switch(unit) 
    {
        case 'm':
            sprintf(buffer, "DIST: %ld.%02ld m", integer_part, decimal_part);
            break;
        case 'c':
            sprintf(buffer, "DIST: %ld.%02ld cm", integer_part, decimal_part);
            break;
        case 'l':
            sprintf(buffer, "DIST: %ld.%02ld mm", integer_part, decimal_part);
            break;
        default:
            sprintf(buffer, "Unknown unit");
    }
}

// 获取并格式化距离显示字符串
static void Get_Show_Distance(char *show_data, uint32_t length)
{
    DistanceData data;
    data.um = length;
    
    // 根据按键切换单位
    if(sw2_status == 0)
        data.unit = 'l';// 毫米
    else if(sw2_status == 1)
        data.unit = 'c';// 厘米
    else if(sw2_status == 2)
        data.unit = 'm';// 米
    
    data.value = convert_um_to_unit(data.um, data.unit);
    format_distance(data.value, data.unit, data.str);
    memcpy(show_data, data.str, strnlen(data.str, 20));
}

/*******************************************************************************
 * LCD部分 (I2C + PCF8574T)
 ******************************************************************************/

// I2C写数据
static status_t PCF8574_WriteByte(uint8_t data)
{
    status_t status;
    
    status = LPI2C_MasterStart(EXAMPLE_I2C_MASTER, PCF8574T_I2C_ADDRESS, kLPI2C_Write);
    if (status != kStatus_Success) 
    {
        LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
        return status;
    }

    status = LPI2C_MasterSend(EXAMPLE_I2C_MASTER, &data, 1);
    if (status != kStatus_Success) 
    {
        LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
        return status;
    }

    status = LPI2C_MasterStop(EXAMPLE_I2C_MASTER);
    return status;
}

// 发送4位半字节
static void LCD_SendNibble(uint8_t nibble, uint8_t is_command)
{
    uint8_t data = 0x00; 
    
    if (nibble & 0x01) data |= LCD_PIN_D4;
    if (nibble & 0x02) data |= LCD_PIN_D5;
    if (nibble & 0x04) data |= LCD_PIN_D6;
    if (nibble & 0x08) data |= LCD_PIN_D7;
    
    if (!is_command) 
    {
        data |= LCD_PIN_RS;
    }
    data &= ~LCD_PIN_RW;
    data |= LCD_PIN_BL; 

    // 产生使能脉冲
    PCF8574_WriteByte(data | LCD_PIN_EN);
    Timer_DelayUs(5);
    PCF8574_WriteByte(data & ~LCD_PIN_EN);
    Timer_DelayUs(2);
}

// 向LCD发送完整字节
static void LCD_WriteByte(uint8_t byte, uint8_t is_command)
{
    LCD_SendNibble((byte >> 4) & 0x0F, is_command);
    LCD_SendNibble(byte & 0x0F, is_command);
    
    if (is_command) 
    {
        if (byte == 0x01 || byte == 0x02) 
        {
            Timer_DelayMs(2);
        } 
        else 
        {
            Timer_DelayUs(100);
        }
    } 
    else 
    {
        Timer_DelayUs(50);
    }
}

// I2C初始化
static void LCD_I2C_init(void)
{
    lpi2c_master_config_t masterConfig;
    LPI2C_MasterGetDefaultConfig(&masterConfig);
    masterConfig.baudRate_Hz = LPI2C_BAUDRATE;
    LPI2C_MasterInit(EXAMPLE_I2C_MASTER, &masterConfig, LPI2C_MASTER_CLOCK_FREQUENCY);
    PRINTF("I2C initialized at %d Hzrn", LPI2C_BAUDRATE);
}

// LCD初始化
static void LCD_Device_Init(void)
{
    PRINTF("LCD Init Start...rn");
    
    Timer_DelayMs(50);
    
    // 发送3次0x30唤醒
    for (int i = 0; i < 3; i++) 
    {
        LCD_SendNibble(0x03, 1);
        Timer_DelayMs(5);
    }
    
    // 设置为4-bit模式
    LCD_SendNibble(0x02, 1);
    Timer_DelayUs(100);
    
    // 配置LCD
    LCD_WriteByte(0x28, 1);  // 4-bit, 2行, 5x8
    LCD_WriteByte(0x08, 1);  // 显示关闭
    LCD_WriteByte(0x01, 1);  // 清屏
    Timer_DelayMs(2);
    LCD_WriteByte(0x06, 1);  // 地址递增
    LCD_WriteByte(0x0C, 1);  // 显示开,光标关
    
    PRINTF("LCD Init Complete!rn");
}

// 设置光标位置
static void LCD_SetCursor(uint8_t row, uint8_t col)
{
    uint8_t address;
    if (row == 0) 
    {
        address = 0x00 + col;
    } 
    else 
    {
        address = 0x40 + col;
    }
    LCD_WriteByte(0x80 | address, 1);
}

// 打印字符串(自动换行)
static void LCD_PrintString(char *str)
{
    uint8_t len = 0;
    uint8_t col = 0;
    LCD_WriteByte(0x01, 1);  // 清屏
    
    while (str[len] != '\0') 
    {
        len++;
    }
    
    if (len > LCD_COLS) 
    {
        len = LCD_COLS;
    }
    
    if (len > 8) 
    {
        LCD_SetCursor(0, 0);
        for (col = 0; col < 8; col++) 
        {
            LCD_WriteByte(str[col], 0);
        }
        LCD_SetCursor(1, 0);
        for (col = 8; col < len; col++) 
        {
            LCD_WriteByte(str[col], 0);
        }
    } 
    else 
    {
        LCD_SetCursor(0, 0);
        for (col = 0; col < len; col++) 
        {
            LCD_WriteByte(str[col], 0);
        }
    }
}

/*******************************************************************************
 * 按键SW2 (单位切换)
 ******************************************************************************/

static void Key_SW2_init(void)
{
    GPIO_GpioClearInterruptFlags(BOARD_SW2_GPIO, 1U << BOARD_SW2_GPIO_PIN);
    GPIO_SetPinInterruptConfig(BOARD_SW2_GPIO, BOARD_SW2_GPIO_PIN, 
                               kGPIO_InterruptFallingEdge);
    EnableIRQ(BOARD_SW2_IRQ);
}

// 按键中断处理
void BOARD_SW2_IRQ_HANDLER(void)
{
    GPIO_GpioClearInterruptFlags(BOARD_SW2_GPIO, 1U << BOARD_SW2_GPIO_PIN);
    sw2_status++;
    sw2_status = sw2_status % 3;
    SDK_ISR_EXIT_BARRIER;
}

/*******************************************************************************
 * LED 定时器中断 (100ms)
 ******************************************************************************/

// 定时器中断回调
void timer_callback(uint32_t flags)
{
    if (flags & (1 << TIMER_INTERRUPT_CHANNEL))
    {
        switch(system_state) 
        {
            case LED_GREEN:
                LED_RED_OFF();
                LED_BLUE_OFF();
                LED_GREEN_ON();
                break;
                
            case LED_RED_BLINK:
                LED_GREEN_OFF();
                LED_BLUE_OFF();
                GPIO_PortToggle(BOARD_LED_RED_GPIO, 1u << BOARD_LED_RED_GPIO_PIN);
                break;
                
            case LED_BLUE:
                LED_RED_OFF();
                LED_GREEN_OFF();
                LED_BLUE_ON();
                break;
                
            default:
                break;
        }
    }
}

// LED 定时器初始化 (CTIMER0 - 100ms中断)
static void LED_Ctimer_Init(void)
{
    ctimer_config_t timerConfig;
    ctimer_match_config_t matchConfig;
    uint32_t srcClock_Hz;
    uint32_t timerClock;

    srcClock_Hz = CTIMER_CLK_FREQ;
    PRINTF("CTIMER source clock frequency: %d Hzrn", srcClock_Hz);

    CTIMER_GetDefaultConfig(&timerConfig);
    timerConfig.prescale = srcClock_Hz / 1000 - 1;  // 96MHz -> 1kHz
    
    timerClock = srcClock_Hz / (timerConfig.prescale + 1);
    PRINTF("Timer counting frequency: %d Hz (period: %d ms)rn", timerClock, 1000 / timerClock);

    CTIMER_Init(CTIMER, &timerConfig);

    // 注册回调
    timer_callback_array[0] = timer_callback;
    CTIMER_RegisterCallBack(CTIMER, timer_callback_array, kCTIMER_SingleCallback);

    // 配置匹配通道
    matchConfig.matchValue = TIMER_MS_COUNT;  // 100
    matchConfig.enableCounterReset = true;
    matchConfig.enableCounterStop = false;
    matchConfig.outControl = kCTIMER_Output_NoAction;
    matchConfig.outPinInitState = false;
    matchConfig.enableInterrupt = true;
    
    PRINTF("Match Value: %d (corresponding to %d ms)rn", matchConfig.matchValue, matchConfig.matchValue);

    CTIMER_SetupMatch(CTIMER, TIMER_INTERRUPT_CHANNEL, &matchConfig);
    CTIMER_StartTimer(CTIMER);

    PRINTF("LED timer started! Interrupt every 100 ms.rn");
}

/*******************************************************************************
 * End of file
 ******************************************************************************/

        也尝试使用FreeRTOS,响应更加顺畅,效果更好。

简易超声波测距仪实践工程与文件.zip

视频演示:




关键词: 超声波测距     演示     成果贴    

共1条 1/1 1 跳转至

回复

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