STM32CubeMX实战:用定时器3实现HC-SR04超声波测距(附完整代码)

在嵌入式开发中,精确测量距离是一个常见需求,而超声波测距因其非接触、成本低和精度适中等特点,成为许多项目的首选方案。本文将详细介绍如何利用STM32CubeMX图形化配置工具和HAL库,通过定时器3的输入捕获功能实现HC-SR04超声波模块的高精度测距。

1. 硬件准备与原理分析

1.1 硬件组成

实现超声波测距需要以下硬件组件:

  • 主控芯片:STM32F407ZET6(本文示例,其他STM32系列类似)
  • 超声波模块:HC-SR04
  • 调试工具:ST-Link调试器
  • 其他:杜邦线、电源等

HC-SR04模块有四个引脚:

  • VCC:5V电源输入
  • GND:地线
  • Trig:触发信号输入(需至少10μs高电平)
  • Echo:回响信号输出(高电平持续时间与距离成正比)

1.2 测距原理

超声波测距的基本原理是测量声波从发射到反射回来的时间差(Time of Flight, ToF)。具体步骤如下:

  1. 触发阶段:向Trig引脚发送至少10μs的高电平脉冲
  2. 发射阶段:模块自动发射8个40kHz的超声波脉冲
  3. 接收阶段:模块检测回波并通过Echo引脚输出高电平
  4. 计算距离:高电平持续时间t(μs)与距离d(cm)的关系为:d = t × 0.017

注意:0.017这个系数来源于声速(340m/s)和单位换算。实际应用中可能需要根据环境温度进行校准。

2. CubeMX工程配置

2.1 定时器3输入捕获配置

定时器输入捕获是测量脉冲宽度的关键功能。以下是具体配置步骤:

  1. 时钟配置:确保系统时钟正确,TIM3挂载在APB1总线上

  2. 定时器基础配置

    • Prescaler(预分频器):71(72MHz时钟分频为1MHz,每个计数周期1μs)
    • Counter Mode(计数模式):Up(向上计数)
    • Counter Period(自动重装载值):65535(16位最大值)
    • Clock Division(时钟分频):No division
    • Auto-reload preload(自动重载预装载):Disable
  3. 输入捕获通道配置

    • Channel1:Input Capture direct mode
    • Polarity:Rising Edge(初始设置为上升沿捕获)
    • IC Selection:Direct
    • IC Prescaler:No division
    • IC Filter:0(不滤波)
  4. NVIC设置:使能TIM3全局中断

2.2 GPIO配置

  • Trig引脚:配置为GPIO输出(如PA7)
  • Echo引脚:连接到TIM3_CH1(如PA6)

2.3 串口配置(可选)

建议配置一个串口用于调试输出,如USART1:

  • Mode:Asynchronous
  • Baud Rate:115200
  • Word Length:8bit
  • Parity:None
  • Stop Bits:1

3. 代码实现与优化

3.1 核心数据结构

首先定义状态机和测量数据结构:

typedef enum {
    TRIG_WAIT = 0,  // 等待触发状态
    RISING_EDGE,    // 已捕获上升沿
    FALLING_EDGE,   // 已捕获下降沿
    MEASURE_DONE    // 测量完成
} MeasureState;

typedef struct {
    MeasureState state;
    uint32_t rise_time;
    uint32_t fall_time;
    float distance_cm;
} HCSR04_Data;

3.2 触发函数

发送10μs以上的触发脉冲:

void HCSR04_Trigger(GPIO_TypeDef* GPIOx, uint16_t Pin) {
    HAL_GPIO_WritePin(GPIOx, Pin, GPIO_PIN_SET);
    delay_us(20);  // 20μs触发脉冲
    HAL_GPIO_WritePin(GPIOx, Pin, GPIO_PIN_RESET);
}

3.3 输入捕获回调函数

这是测量精度的核心,处理定时器捕获中断:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if(htim->Instance == TIM3) {
        switch(hcsr04.state) {
            case TRIG_WAIT:
            case RISING_EDGE:
                // 捕获上升沿
                hcsr04.rise_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
                __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
                hcsr04.state = FALLING_EDGE;
                break;
                
            case FALLING_EDGE:
                // 捕获下降沿
                hcsr04.fall_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
                __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
                hcsr04.state = MEASURE_DONE;
                break;
                
            default:
                break;
        }
    }
}

3.4 定时器溢出处理

为防止长距离测量时定时器溢出,需处理更新中断:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if(htim->Instance == TIM3 && hcsr04.state == FALLING_EDGE) {
        hcsr04.fall_time = 0xFFFF;  // 标记溢出
        hcsr04.state = MEASURE_DONE;
    }
}

3.5 距离计算与滤波

为提高测量稳定性,建议实现简单的滤波算法:

#define FILTER_SIZE 5

float HCSR04_CalculateDistance(void) {
    static float filter_buf[FILTER_SIZE] = {0};
    static uint8_t filter_index = 0;
    float sum = 0;
    
    if(hcsr04.state == MEASURE_DONE) {
        // 计算原始距离
        uint32_t pulse_width = (hcsr04.fall_time - hcsr04.rise_time) & 0xFFFF;
        float raw_distance = pulse_width * 0.017f;  // 单位:cm
        
        // 中值滤波
        filter_buf[filter_index] = raw_distance;
        filter_index = (filter_index + 1) % FILTER_SIZE;
        
        // 计算平均值
        for(uint8_t i=0; i<FILTER_SIZE; i++) {
            sum += filter_buf[i];
        }
        hcsr04.distance_cm = sum / FILTER_SIZE;
        
        hcsr04.state = TRIG_WAIT;
    }
    
    return hcsr04.distance_cm;
}

4. 主程序流程

完整的主程序应包含初始化、测量循环和数据处理:

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM3_Init();
    MX_USART1_UART_Init();
    
    // 启动定时器及中断
    HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
    HAL_TIM_Base_Start_IT(&htim3);
    
    while (1) {
        if(hcsr04.state == TRIG_WAIT) {
            HCSR04_Trigger(TRIG_GPIO_Port, TRIG_Pin);
            
            // 等待测量完成
            while(hcsr04.state != MEASURE_DONE) {
                float distance = HCSR04_CalculateDistance();
                if(distance > 0) {
                    printf("Distance: %.2f cm\r\n", distance);
                }
            }
            
            HAL_Delay(100);  // 两次测量间隔至少60ms
        }
    }
}

5. 常见问题与优化技巧

5.1 测量误差分析

实际应用中可能遇到的误差源包括:

  • 温度影响:声速随温度变化(v = 331.4 + 0.6T,T为摄氏温度)
  • 多径反射:超声波遇到多个反射面
  • 最小距离限制:HC-SR04最小测量距离约2cm
  • 信号抖动:Echo信号边沿不干净

5.2 软件优化建议

  1. 动态调整滤波器大小

    if(pulse_width < 1000) {  // 近距离时减少滤波
        effective_size = 3;
    } else {
        effective_size = FILTER_SIZE;
    }
    
  2. 自动温度补偿

    float speed_of_sound = 331.4f + 0.6f * current_temperature;
    float distance = (pulse_width * 1e-6) * speed_of_sound * 50.0f;
    
  3. 超时处理机制

    uint32_t timeout = HAL_GetTick();
    while(hcsr04.state != MEASURE_DONE) {
        if(HAL_GetTick() - timeout > 100) {  // 100ms超时
            hcsr04.state = TRIG_WAIT;
            break;
        }
    }
    
  4. 低功耗优化

    void Enter_LowPowerMode(void) {
        HAL_TIM_Base_Stop_IT(&htim3);
        HAL_TIM_IC_Stop_IT(&htim3, TIM_CHANNEL_1);
        HAL_UART_DeInit(&huart1);
        HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
        SystemClock_Config();  // 唤醒后重新配置时钟
    }
    

6. 进阶应用:多模块协同

当需要同时使用多个超声波模块时,可采用以下策略:

  1. 分时复用:为每个模块分配不同的触发时间槽

    void MultiModule_Trigger(void) {
        static uint8_t current_module = 0;
        
        switch(current_module) {
            case 0:
                HCSR04_Trigger(MODULE1_TRIG_GPIO_Port, MODULE1_TRIG_Pin);
                break;
            case 1:
                HCSR04_Trigger(MODULE2_TRIG_GPIO_Port, MODULE2_TRIG_Pin);
                break;
            // 更多模块...
        }
        
        current_module = (current_module + 1) % MODULE_COUNT;
    }
    
  2. 多定时器方案:为每个模块分配独立的定时器通道

  3. 信号仲裁:通过外部逻辑电路合并多个Echo信号

7. 性能测试与校准

为确保测量精度,建议进行系统校准:

  1. 固定距离测试:在已知距离(如50cm、100cm)下测量并记录结果
  2. 线性回归:通过最小二乘法拟合校准曲线
    // 校准公式:actual = scale * raw + offset
    float calibrated_distance = CALIB_SCALE * raw_distance + CALIB_OFFSET;
    
  3. 环境补偿:集成温度传感器实时修正声速

以下是一个简单的校准数据记录表示例:

实际距离(cm) 原始测量值(cm) 误差(%)
10.0 10.5 +5.0
50.0 49.3 -1.4
100.0 98.7 -1.3
200.0 203.2 +1.6

通过分析这些数据,可以确定是否需要以及如何进行软件校准。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐