本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目基于STM32F103微控制器,使用HAL库开发了一款超声波测距程序,采用电平模式实现高精度距离测量。程序利用定时器的输入捕捉功能,结合超声波发射与回波检测的时间差,通过声速计算目标距离。适用于智能家居、自动安防等嵌入式应用场景,帮助开发者掌握HAL库开发、定时器配置与传感器数据处理的核心技能。

1. STM32F103微控制器架构概述

STM32F103系列基于ARM Cortex-M3内核,是一款高性能、低成本、低功耗的32位微控制器,广泛应用于工业控制、智能仪表、物联网等领域。其架构采用哈佛结构,支持三级流水线,具备高效的指令执行能力。芯片内置Flash存储器与SRAM,并集成丰富的外设资源,如GPIO、定时器、ADC、SPI、I2C等,便于实现复杂功能的嵌入式系统设计。其支持多种低功耗模式,适应不同应用场景的需求。掌握其架构特性,有助于理解后续HAL库开发与外设驱动实现的底层机制。

2. HAL库开发环境搭建与配置

在嵌入式系统开发中,开发环境的搭建是至关重要的第一步。HAL(Hardware Abstraction Layer)库为STM32系列微控制器提供了标准化的硬件操作接口,大大降低了开发者对底层寄存器的操作难度。本章将详细讲解如何搭建基于HAL库的开发环境,涵盖开发工具链的选择与配置、工程创建与外设初始化流程,以及调试与下载环境的建立。通过本章内容,开发者将能够快速构建一个高效、稳定的STM32F103开发平台。

2.1 开发工具链的搭建

在开始STM32 HAL库开发之前,首先需要完成开发工具链的搭建。这包括STM32CubeMX的安装与配置、IDE的选择与设置,以及相关编译器和调试工具的安装。下面将分别介绍这些工具的功能及其配置方式。

2.1.1 STM32CubeMX的安装与功能介绍

STM32CubeMX 是 ST 官方提供的图形化配置工具,用于快速生成初始化代码,支持STM32全系列芯片。它集成了时钟树配置、外设引脚映射、中间件选择、代码生成等功能,极大提升了开发效率。

安装步骤
  1. 访问 ST官网 下载 STM32CubeMX
  2. 双击安装包运行安装程序,选择安装路径。
  3. 安装完成后,打开 STM32CubeMX,首次运行会提示更新芯片数据库(建议联网更新)。
核心功能介绍
  • 芯片选择 :支持从型号搜索到引脚数、封装类型等多维度筛选。
  • 引脚配置 :图形化界面中可直接拖动外设到对应引脚,自动完成冲突检测。
  • 时钟配置 :提供系统时钟树图形化配置,自动生成各外设时钟频率。
  • 中间件支持 :可启用RTOS、USB、FatFS、LwIP等中间件模块。
  • 代码生成 :支持生成 Keil MDK-ARM IAR STM32CubeIDE Makefile 等多种项目模板。

示例:使用STM32CubeMX配置一个使用HAL库的GPIO输出项目。

STM32CubeMX配置流程图
graph TD
    A[打开STM32CubeMX] --> B[选择芯片型号 STM32F103C8Tx]
    B --> C[配置引脚功能]
    C --> D[选择PC13为GPIO_Output]
    D --> E[配置系统时钟]
    E --> F[生成代码]
    F --> G[选择项目类型为Keil MDK-ARM]
    G --> H[保存并打开工程]

2.1.2 Keil MDK-ARM与STM32CubeIDE的配置对比

在选择开发环境时,常见的两种方案是 Keil MDK-ARM STM32CubeIDE 。它们各有优势,适用于不同开发需求。

Keil MDK-ARM 配置流程

Keil 是经典的嵌入式开发IDE,具有强大的调试功能和成熟的编译器。

  1. 下载并安装 Keil MDK-ARM
  2. 在 STM32CubeMX 中选择目标为 Keil MDK-ARM。
  3. 生成代码后,打开 Keil 工程。
  4. 检查 Startup 文件和 system_stm32f1xx.c 是否正确包含。
  5. 编译工程并下载至目标板。
STM32CubeIDE 配置流程

STM32CubeIDE 是 ST 官方推出的集成开发环境,基于 Eclipse,内置 STM32CubeMX 插件,支持代码生成、调试、烧录一体化操作。

  1. 下载并安装 STM32CubeIDE
  2. 打开 STM32CubeIDE,创建 STM32 Project。
  3. 在配置界面中选择芯片型号并配置外设。
  4. 生成代码后,系统自动创建工程。
  5. 编译、调试、下载均可在同一个界面完成。
对比表格
特性 Keil MDK-ARM STM32CubeIDE
开发界面 经典嵌入式IDE 基于Eclipse的现代界面
调试器支持 支持J-Link、ST-Link等 内置ST-Link调试器支持
代码生成 需STM32CubeMX配合 内置STM32CubeMX插件
免费版本 需注册,代码大小受限 免费完整版
插件生态 丰富 逐步完善
适合人群 资深嵌入式工程师 初学者与进阶用户

小贴士:若你已经熟悉 Keil,且项目复杂度高,推荐继续使用 Keil MDK-ARM;若你希望一站式开发,或为初学者,推荐使用 STM32CubeIDE。

2.2 HAL库的工程创建与外设初始化

在开发环境搭建完成后,下一步是创建工程并配置外设。STM32CubeMX 可以自动生成基于 HAL 库的初始化代码,包括系统时钟、GPIO、定时器等常用外设。

2.2.1 使用STM32CubeMX生成初始化代码

以配置一个 LED 灯闪烁的工程为例,说明如何使用 STM32CubeMX 生成初始化代码。

步骤:
  1. 打开 STM32CubeMX,选择芯片 STM32F103C8Tx。
  2. 在 Pinout 页面,将 PC13 引脚设置为 GPIO_Output。
  3. 在 Clock Configuration 页面,配置系统时钟为 72 MHz。
  4. 在 Project Manager 页面,选择 Target 为 STM32F103C8,IDE 为 STM32CubeIDE。
  5. 点击 Generate Code,生成工程代码。
生成的代码结构

生成的工程包含以下核心文件:

  • main.c :主程序入口。
  • stm32f1xx_hal_msp.c :底层驱动支持。
  • stm32f1xx_it.c :中断服务程序。
  • gpio.c :GPIO初始化代码。
  • tim.c (如有定时器配置):定时器初始化代码。
示例代码片段:GPIO初始化
void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);

  /*Configure GPIO pin : PC13 */
  GPIO_InitStruct.Pin = GPIO_PIN_13;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // 推挽输出模式
  GPIO_InitStruct.Pull = GPIO_NOPULL;             // 无上拉/下拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    // 输出速度低
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
代码逐行分析
  1. GPIO_InitTypeDef GPIO_InitStruct = {0};
    定义一个 GPIO 初始化结构体,并初始化为 0。

  2. __HAL_RCC_GPIOC_CLK_ENABLE();
    使能 GPIOC 的时钟,这是操作任何外设前的必要步骤。

  3. HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
    设置 PC13 初始电平为低电平(LED熄灭)。

  4. GPIO_InitStruct.Pin = GPIO_PIN_13;
    设置要配置的引脚为 PC13。

  5. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    设置为推挽输出模式,适用于驱动LED等负载。

  6. GPIO_InitStruct.Pull = GPIO_NOPULL;
    不启用上拉或下拉电阻。

  7. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    设置输出速度为低频模式,减少功耗和干扰。

  8. HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    调用 HAL 函数完成 GPIO 初始化。

2.2.2 HAL库中GPIO、定时器等外设的基本配置方法

除了 GPIO,定时器是嵌入式开发中最常用的外设之一。HAL库提供了丰富的定时器函数,便于实现精准的延时、PWM 输出、输入捕获等功能。

定时器初始化示例

以下代码展示如何使用 HAL 库初始化 TIM2 定时器,实现 1 秒钟的定时中断。

TIM_HandleTypeDef htim2;

void MX_TIM2_Init(void)
{
  __HAL_RCC_TIM2_CLK_ENABLE();  // 启用定时器时钟

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 71;              // 预分频值,72MHz / 72 = 1MHz
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;  // 向上计数模式
  htim2.Init.Period = 9999;               // 自动重载值,1MHz / 10000 = 100Hz
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  HAL_TIM_Base_Init(&htim2);              // 初始化定时器

  HAL_TIM_Base_Start_IT(&htim2);          // 启动定时器中断
}
参数说明
  • Prescaler = 71 :系统时钟为 72MHz,除以 72 得到 1MHz 的定时器时钟。
  • Period = 9999 :计数到 9999 时触发中断,即每 10000 次计数为 10ms。
  • TIM_COUNTERMODE_UP :向上计数模式,计数器从0递增到Period。
  • TIM_AUTORELOAD_PRELOAD_DISABLE :关闭自动重载预装载,简化操作。
中断服务函数

stm32f1xx_it.c 中,需要添加如下中断服务函数:

void TIM2_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&htim2);  // 调用HAL库处理中断
}
回调函数

main.c 中,添加如下回调函数:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim == &htim2) {
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);  // 每次中断翻转LED状态
  }
}

该回调函数会在定时器溢出时被调用,从而实现 LED 每秒闪烁一次的功能。

2.3 调试与下载环境的建立

开发过程中,调试和下载是验证程序是否正常运行的关键步骤。本节将介绍如何使用 ST-Link 调试器进行程序下载和调试。

2.3.1 ST-Link调试器的连接与使用

ST-Link 是 ST 官方提供的调试工具,广泛用于 STM32 系列芯片的调试。

连接方式
ST-Link引脚 STM32F103C8Tx引脚
SWCLK PA14
SWDIO PA13
GND GND
3.3V 3.3V

注意:部分开发板已内置 ST-Link,可直接通过 USB 接口连接电脑进行调试。

在 STM32CubeIDE 中使用 ST-Link
  1. 连接好 ST-Link 和目标板。
  2. 打开 STM32CubeIDE,点击 Run → Debug As → STM32 Cortex-M C/C++ Application
  3. 首次运行会提示选择调试器,选择 ST-Link。
  4. 成功连接后,可以设置断点、查看寄存器、内存等信息。

2.3.2 程序下载与调试流程详解

下载流程
  1. 在 STM32CubeIDE 中,点击 Run → Run As → STM32 Cortex-M C/C++ Application
  2. 程序会自动编译并下载到芯片 Flash。
  3. 若连接正常,程序会立即运行,LED开始闪烁。
调试流程
  1. 设置断点:在代码行号左侧单击,设置断点。
  2. 启动调试:点击 Run → Debug As
  3. 单步执行:使用 Step Over、Step Into 等按钮逐步执行代码。
  4. 查看变量:在 Variables 窗口查看变量值。
  5. 查看寄存器:在 Register 窗口查看外设寄存器状态。
调试流程图
graph TD
    A[连接ST-Link调试器] --> B[打开工程并编译]
    B --> C[启动调试模式]
    C --> D[设置断点]
    D --> E[单步执行/运行到断点]
    E --> F[查看变量/寄存器/内存]
    F --> G[分析程序行为]

通过本章内容,开发者已经掌握了基于 HAL 库的 STM32 开发环境搭建、工程创建与外设初始化、以及调试与下载的基本流程。下一章将深入讲解超声波测距的原理与信号检测机制,为后续项目开发打下坚实基础。

3. 超声波测距原理与信号检测机制

在嵌入式系统中,超声波测距是一种常见且高效的非接触式距离测量方式。它广泛应用于机器人避障、无人机高度测量、智能停车系统、工业自动化等多个领域。本章将从物理原理出发,深入解析超声波测距的基本机制,并结合STM32微控制器的硬件特性,分析如何在嵌入式系统中实现对超声波信号的检测与处理。

3.1 超声波测距的物理原理

超声波测距依赖于声波的发射与回波接收,通过测量声波往返时间来计算距离。本节将从声波传播的基础物理特性入手,推导测距公式,并分析温度对声速的影响及补偿方法。

3.1.1 声波传播特性与测距公式

超声波是频率高于20kHz的机械波,通常在空气中传播。在标准大气压和20℃环境下,声波在空气中的传播速度约为343 m/s。其传播速度 $ v $ 可由下式估算:

v = 331 + 0.6T

其中:
- $ v $:声速(单位:m/s)
- $ T $:环境温度(单位:℃)

测距的基本公式为:

d = \frac{v \cdot t}{2}

其中:
- $ d $:被测物体与传感器之间的距离(单位:m)
- $ t $:超声波发射到回波接收的时间差(单位:s)

由于超声波在空气中传播具有方向性强、不易受光线影响、成本低等优点,使其在工业测距中广受欢迎。

示例:测距计算

假设测得回波时间 $ t = 5.88ms $,当前温度为25℃。

  1. 计算声速:
    $$
    v = 331 + 0.6 \times 25 = 346 m/s
    $$

  2. 计算距离:
    $$
    d = \frac{346 \times 0.00588}{2} \approx 1.016 \text{m}
    $$

3.1.2 温度对声速的影响及补偿机制

温度变化会导致空气密度变化,从而影响声速。为提高测距精度,必须引入温度补偿机制。常见的做法是使用数字温度传感器(如DS18B20或STM32内部温度传感器)获取环境温度,并实时计算声速。

温度(℃) 声速(m/s) 计算公式
0 331 基准值
10 337 331 + 0.6×10
20 343 331 + 0.6×20
30 349 331 + 0.6×30

通过温度传感器获取当前环境温度,嵌入式系统可以动态更新声速值,从而提高测距精度。

3.2 电平模式下的信号检测

超声波模块(如HC-SR04)通常通过高低电平进行触发和响应。本节将详细分析其工作时序,并讨论如何在STM32平台上测量回波脉冲宽度。

3.2.1 高低电平触发与响应的时序分析

以HC-SR04为例,其工作流程如下:

sequenceDiagram
    participant MCU
    participant HC_SR04

    MCU->>HC_SR04: Trig引脚拉高10μs
    HC_SR04-->>HC_SR04: 触发8个40kHz脉冲
    HC_SR04->>MCU: Echo引脚拉高,持续时间等于回波时间
    MCU->>MCU: 捕获Echo高电平持续时间

该时序流程说明了超声波模块如何通过GPIO控制进行测距。MCU首先在Trig引脚上发送一个10μs的高电平脉冲,触发超声波发射。随后,Echo引脚被拉高,持续时间即为超声波往返时间。

3.2.2 回波信号的脉宽测量原理

在STM32平台中,可以使用定时器的输入捕捉功能(Input Capture)或简单的GPIO中断来测量Echo信号的脉宽。

以下是一个使用GPIO中断方式测量回波时间的示例代码(基于HAL库):

// 定义全局变量用于记录时间
uint32_t rising_time = 0;
uint32_t falling_time = 0;
uint32_t echo_width = 0;

// Echo引脚中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == ECHO_PIN) {
        if (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) == GPIO_PIN_SET) {
            // 上升沿,记录时间
            rising_time = __HAL_TIM_GET_COUNTER(&htim);
        } else {
            // 下降沿,计算宽度
            falling_time = __HAL_TIM_GET_COUNTER(&htim);
            echo_width = falling_time - rising_time;
        }
    }
}
代码分析与参数说明:
  • rising_time falling_time :记录Echo信号上升沿和下降沿的时间戳。
  • __HAL_TIM_GET_COUNTER(&htim) :获取当前定时器计数值,用于精确测量时间间隔。
  • HAL_GPIO_EXTI_Callback :外部中断回调函数,用于处理Echo引脚的状态变化。

该方式适用于低频测距场景。若需更高精度,推荐使用定时器输入捕捉模式(详见第四章)。

3.3 嵌入式系统中的硬件接口设计

在嵌入式系统中,将超声波模块与STM32F103正确连接并设计抗干扰电路,是实现稳定测距的关键。

3.3.1 超声波模块与STM32的引脚连接方式

以HC-SR04为例,其与STM32的连接如下:

模块引脚 功能说明 STM32引脚(示例)
VCC 电源(5V) 5V电源引脚
GND 接地 GND
Trig 触发信号输入 PA1(GPIO输出)
Echo 回波信号输出 PA2(GPIO输入/中断)

注意:STM32F103的GPIO引脚耐压为3.3V,而Echo输出为5V信号。因此,建议在Echo引脚接入一个10kΩ电阻与3.3V稳压二极管组成限压电路,或使用电平转换器。

3.3.2 信号调理电路与抗干扰设计

为了提升测距稳定性,应考虑以下信号调理与抗干扰措施:

  1. 上拉电阻 :Echo引脚可能输出高阻态,建议在MCU端添加10kΩ上拉电阻。
  2. RC滤波 :在Echo信号线上并联RC滤波电路(如100Ω + 100nF),以抑制高频噪声。
  3. 屏蔽线缆 :使用屏蔽线缆连接超声波模块,防止电磁干扰。
  4. 消隐时间(Blanking Time)设置 :软件中加入消隐时间(如2ms),防止因超声波模块未准备好而误触发。
示例:RC滤波电路设计
graph TD
    A[Echo输出] --> B[RC滤波]
    B --> C[STM32 GPIO]
    D[100Ω电阻] --> B
    E[100nF电容] --> F[GND]

该电路能有效滤除高频噪声,防止误触发中断。

本章从超声波测距的物理基础入手,详细分析了其工作原理,并结合STM32平台,介绍了信号检测的硬件连接方式和软件实现逻辑。通过引入温度补偿机制和信号调理电路设计,进一步提升了测距系统的稳定性和精度,为后续软件开发与优化奠定了坚实基础。

4. STM32定时器输入捕捉模式与HAL库实现

在嵌入式系统中,时间测量是许多应用的核心功能之一。STM32F103系列微控制器提供了丰富的定时器资源,其中输入捕捉(Input Capture)模式是实现高精度时间测量的重要手段。本章将深入探讨STM32定时器的输入捕捉机制,结合HAL库的使用方法,详细解析如何通过定时器实现信号时间差的精确测量,为后续超声波测距系统打下坚实基础。

4.1 定时器输入捕捉模式原理

STM32F103系列的通用定时器(如TIM2-TIM5)均支持输入捕捉模式。该模式可用于捕获外部信号的上升沿或下降沿发生时刻,并将当前定时器计数值保存到寄存器中,从而实现时间差的测量。输入捕捉常用于测量脉冲宽度、周期长度、信号频率等场景。

4.1.1 输入捕捉的工作机制与寄存器配置

输入捕捉的基本机制如下:

  1. 输入信号检测 :通过定时器通道引脚(如TIMx_CH1~CH4)连接外部信号源;
  2. 边沿触发配置 :可配置为上升沿触发、下降沿触发或双边沿触发;
  3. 计数器捕获 :当指定边沿发生时,当前定时器计数值(TIMx_CNT)被复制到捕获/比较寄存器(TIMx_CCRx);
  4. 中断通知 :若使能中断,将触发捕获中断标志(CCxIF),并可进入中断服务程序;
  5. 数据读取与处理 :通过读取捕获寄存器的值,结合定时器时钟频率,计算出时间差。

相关的寄存器包括:

寄存器名称 功能说明
TIMx_CR1 控制定时器的计数方向、使能状态等
TIMx_DIER 中断使能寄存器,用于启用捕获中断
TIMx_CCER 捕获/比较使能寄存器,控制通道的极性与使能
TIMx_CCMR1/2 通道模式寄存器,设置输入捕获的边沿类型与滤波
TIMx_CNT 当前计数值寄存器
TIMx_CCRx 捕获比较寄存器,用于存储捕获的计数值

例如,要配置TIM2的通道1为上升沿触发的输入捕捉模式,需要设置TIMx_CCMR1寄存器的CC1S位为01(输入模式),设置IC1PSC为00(无预分频),并设置CC1P为0(上升沿触发)。

4.1.2 捕捉事件与时间测量的实现方法

输入捕捉的核心应用是测量两个信号边沿之间的时间差。例如,在超声波测距中,可以通过测量触发信号和回波信号之间的时间差来计算距离。

实现步骤如下:

  1. 初始化定时器为输入捕捉模式 :配置通道为输入模式,选择触发边沿;
  2. 启动定时器 :使能定时器,开始计数;
  3. 等待第一次捕捉事件 :当第一个边沿到来时,记录捕获值;
  4. 等待第二次捕捉事件 :当第二个边沿到来时,再次记录捕获值;
  5. 计算时间差 :两次捕获值之差乘以定时器的时钟周期,即可得到时间差。

假设系统时钟为72MHz,定时器预分频器为71,则定时器时钟为1MHz(即每计数一次为1μs),若两次捕获值分别为5000和15000,则时间差为10000μs = 10ms。

4.2 HAL库中的定时器配置函数

HAL库(Hardware Abstraction Layer)为开发者提供了标准化的接口,简化了底层寄存器的配置。在使用STM32CubeMX生成初始化代码后,可以通过HAL库提供的API快速配置输入捕捉模式。

4.2.1 HAL_TIM_IC_Init函数的功能与参数详解

函数原型如下:

HAL_StatusTypeDef HAL_TIM_IC_Init(TIM_HandleTypeDef *htim);
  • 参数说明
  • htim :指向 TIM_HandleTypeDef 结构体的指针,包含定时器的基本配置信息,如定时器实例、时钟源、计数模式等。

此函数主要完成以下任务:

  1. 初始化定时器基本参数 :如时钟源、计数方向、周期等;
  2. 配置定时器为输入捕捉模式
  3. 设置默认中断回调函数
  4. 调用底层 HAL_TIM_IC_MspInit 函数进行GPIO与中断配置 (由用户实现)。

示例代码如下:

TIM_HandleTypeDef htim2;

void MX_TIM2_Init(void)
{
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 71;            // 预分频器值,72MHz / 72 = 1MHz
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数
  htim2.Init.Period = 0xFFFFFFFF;        // 最大计数值
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

  if (HAL_TIM_IC_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
}
  • 执行逻辑分析
  • 第3行:指定使用TIM2定时器;
  • 第4~7行:配置定时器的预分频、计数方向、周期等;
  • 第9行:调用 HAL_TIM_IC_Init 完成初始化;
  • 若初始化失败,进入错误处理函数。

4.2.2 定时器通道配置与中断使能设置

配置定时器通道为输入捕捉模式需要调用 HAL_TIM_IC_ConfigChannel 函数:

HAL_StatusTypeDef HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim, 
                                           TIM_IC_InitTypeDef *sConfig, 
                                           uint32_t Channel);
  • 参数说明
  • htim :指向定时器句柄;
  • sConfig :指向输入捕捉配置结构体,定义边沿触发类型、滤波设置等;
  • Channel :指定通道(如TIM_CHANNEL_1)。

示例代码如下:

TIM_IC_InitTypeDef sConfigIC;

sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING;     // 上升沿触发
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 选择通道输入
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;           // 无预分频
sConfigIC.ICFilter = 0x0;                         // 不启用滤波

if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
  Error_Handler();
}
  • 执行逻辑分析
  • 第3行:设置为上升沿触发;
  • 第4行:选择为直连输入模式(即不使用交叉通道);
  • 第5行:不对输入信号进行分频;
  • 第6行:禁用输入滤波;
  • 第8~11行:配置通道1为输入捕捉模式。

接下来,需要使能捕获中断并启动定时器:

HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); // 启动通道1的输入捕捉中断

该函数将自动使能捕获中断(CC1IE位),并启动定时器计数。

4.3 回调函数与中断服务程序

在HAL库中,输入捕捉的中断处理通过回调函数机制实现,开发者可以通过重写回调函数来处理捕获事件。

4.3.1 HAL_TIM_IC_CaptureCallback回调函数的使用

当发生捕获事件时,HAL库会调用以下回调函数:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);

开发者需要在用户代码中重写该函数,以处理捕获的数据。例如:

uint32_t IC_Val1 = 0;
uint32_t IC_Val2 = 0;
uint8_t Is_First_Captured = 0;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if (htim == &htim2)
  {
    if (Is_First_Captured == 0) // 第一次捕获
    {
      IC_Val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
      Is_First_Captured = 1;
      htim->Instance->CNT = 0; // 重置计数器
    }
    else if (Is_First_Captured == 1) // 第二次捕获
    {
      IC_Val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
      Is_First_Captured = 0;
      // 计算时间差
      uint32_t duration = IC_Val2 - IC_Val1;
    }
  }
}
  • 执行逻辑分析
  • 第8行:读取当前捕获值;
  • 第9行:标记为第一次捕获;
  • 第11行:将计数器清零,以便下一次测量;
  • 第13~16行:第二次捕获后计算时间差;
  • 使用 HAL_TIM_ReadCapturedValue 获取捕获寄存器的值。

4.3.2 多次捕捉与时间差计算的逻辑实现

在某些应用中,可能需要进行多次捕捉以提高测量精度或实现周期测量。例如,测量脉冲周期时,可以连续捕获多个上升沿的时间差。

流程图如下:

graph TD
A[开始] --> B[第一次捕获]
B --> C{是否已捕获?}
C -->|否| B
C -->|是| D[记录时间1]
D --> E[等待第二次捕获]
E --> F{是否已捕获?}
F -->|否| E
F -->|是| G[记录时间2]
G --> H[计算时间差]
H --> I[是否继续测量?]
I -->|是| B
I -->|否| J[结束]

该流程图展示了从初始化到多次测量的完整逻辑。通过在回调函数中维护状态标志(如 Is_First_Captured ),可以实现连续的时间测量。

此外,为避免溢出,应确保定时器周期足够大。若使用32位定时器(如TIM2、TIM5),最大计数值为0xFFFFFFFF,对应时间可达4294秒(约1.2小时),足以满足绝大多数应用需求。

通过本章的深入分析,我们掌握了STM32定时器输入捕捉模式的原理、寄存器配置方法,以及HAL库中的实现流程。结合回调函数与中断处理机制,可以高效实现时间测量功能,为后续超声波测距系统的开发提供了坚实的技术基础。

5. 超声波测距系统的软件实现与优化

5.1 超声波发射与接收控制流程

在STM32F103平台上,超声波测距的实现依赖于GPIO和定时器模块的协同工作。以下为超声波发射和接收的基本流程:

5.1.1 GPIO驱动超声波触发信号的生成

以HC-SR04超声波模块为例,其触发信号为一个持续10μs的高电平脉冲。我们使用GPIO端口(例如GPIO_PIN_5)来输出该信号。

// 触发超声波模块
void Trigger_Ultrasonic(void) {
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);   // 拉高电平
    HAL_Delay(1);                                                 // 延时1μs
    HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 拉低电平
}

参数说明:
- TRIG_GPIO_Port :触发引脚所属的GPIO端口。
- TRIG_Pin :触发引脚编号(例如GPIO_PIN_5)。
- HAL_Delay(1) :微秒级延时函数,需配置系统时钟或使用SysTick实现更高精度延时。

该触发信号发送后,超声波模块将发出8个40kHz的超声波脉冲,并等待回波信号。

5.1.2 接收端信号的等待与超时处理机制

回波信号由模块的Echo引脚输出,高电平持续时间即为超声波从发射到返回的时间。我们通过定时器输入捕捉模式获取该时间。

为避免系统死锁,应设置合理的超时时间(如60ms,对应最大测距约10米):

// 等待回波信号上升沿触发
uint32_t wait_echo_rising(void) {
    uint32_t timeout = HAL_GetTick() + 60;  // 设置60ms超时
    while (HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) == GPIO_PIN_RESET) {
        if (HAL_GetTick() > timeout) {
            return 0; // 超时返回0
        }
    }
    return 1; // 成功检测到上升沿
}

参数说明:
- ECHO_GPIO_Port :接收引脚所属GPIO端口。
- ECHO_Pin :接收引脚编号。
- HAL_GetTick() :获取系统当前时间(毫秒级)。

该机制确保即使无回波信号,系统也能及时退出等待,提升鲁棒性。

5.2 时间测量与距离换算算法

5.2.1 捕获时间的单位换算与误差分析

通过定时器输入捕捉模式获取到的高电平持续时间( high_time ),单位为定时器时钟周期数(如72MHz系统时钟下,一个周期为1/72μs)。

计算公式如下:

distance = (high_time * speed_of_sound / 2) / (SystemCoreClock / 1000000)

其中:

  • speed_of_sound :声速(常温下约为343m/s)
  • SystemCoreClock :系统主频(如72MHz)

示例代码如下:

float calculate_distance(uint32_t high_time, float speed_of_sound) {
    float us = high_time / (SystemCoreClock / 1000000);  // 微秒级时间
    return (us * speed_of_sound / 2.0f) / 10000.0f;      // 转换为厘米
}

误差来源分析:
- 定时器精度受限于系统时钟分频。
- 回波信号抖动可能导致捕捉误差。
- 温度变化影响声速,未补偿时误差可达±3%。

5.2.2 基于温度传感器的声速动态补偿算法

使用DS18B20等温度传感器获取环境温度,动态计算声速:

speed_of_sound = 331.3 + 0.606 * temperature

示例代码片段:

float get_sound_speed(float temperature) {
    return 331.3f + 0.606f * temperature;
}

将该函数集成到主循环中,可实时更新声速值,提高测距精度。

5.3 系统稳定性与抗干扰优化

5.3.1 消隐时间的设置与多回波干扰处理

超声波模块在发射后存在短暂的“盲区”,通常为200μs。在此期间Echo引脚可能产生虚假高电平,应设置消隐时间避免误触发:

// 消隐时间延时
void delay_us(uint32_t us) {
    // 使用SysTick或定时器实现微秒延时
}

void handle_echo_signal(void) {
    delay_us(200); // 消隐时间
    if (wait_echo_rising()) {
        // 启动定时器捕捉回波下降沿
    }
}

此外,对于多回波干扰(如墙角反射),可通过检测回波宽度,仅保留第一个脉冲进行计算。

5.3.2 测距结果的滤波算法与异常值剔除

为提高测距稳定性,采用滑动平均滤波与中位值滤波结合的方式:

#define SAMPLES 5
float distance_samples[SAMPLES];

float get_filtered_distance(float new_distance) {
    for (int i = 0; i < SAMPLES - 1; i++) {
        distance_samples[i] = distance_samples[i + 1];
    }
    distance_samples[SAMPLES - 1] = new_distance;

    // 冒泡排序获取中位值
    float temp[SAMPLES];
    memcpy(temp, distance_samples, sizeof(temp));
    for (int i = 0; i < SAMPLES - 1; i++) {
        for (int j = 0; j < SAMPLES - i - 1; j++) {
            if (temp[j] > temp[j + 1]) {
                float swap = temp[j];
                temp[j] = temp[j + 1];
                temp[j + 1] = swap;
            }
        }
    }
    return temp[SAMPLES / 2]; // 返回中位值
}

该方法有效抑制瞬时干扰,提高测距稳定性。

5.4 测距结果显示与系统扩展

5.4.1 LCD显示屏的驱动与数据可视化实现

使用如OLED或TFT-LCD模块(如SSD1306)显示测距结果,需先初始化SPI或I2C接口,并加载字体库。

示例代码片段(基于SSD1306 OLED):

char buffer[20];
sprintf(buffer, "Distance: %.2f cm", distance);
SSD1306_Clear();
SSD1306_SetCursor(0, 0);
SSD1306_WriteString(buffer, Font_11x18, White);
SSD1306_UpdateScreen();

功能说明:
- SSD1306_Clear() :清屏操作。
- SSD1306_SetCursor(x, y) :设置光标位置。
- SSD1306_WriteString() :显示字符串。
- SSD1306_UpdateScreen() :刷新屏幕。

5.4.2 数据通过串口上传与智能家居集成应用展望

使用USART模块将测距数据上传至上位机或网关设备,便于远程监控:

char uart_buffer[50];
sprintf(uart_buffer, "Distance: %.2f cm\r\n", distance);
HAL_UART_Transmit(&huart2, (uint8_t*)uart_buffer, strlen(uart_buffer), HAL_MAX_DELAY);

扩展应用:
- 与Wi-Fi模块(如ESP8266)结合,实现远程测距监控。
- 接入MQTT协议,上传数据至IoT平台(如阿里云、ThingsBoard)。
- 应用于智能家居门禁、自动泊车、机器人避障等场景。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目基于STM32F103微控制器,使用HAL库开发了一款超声波测距程序,采用电平模式实现高精度距离测量。程序利用定时器的输入捕捉功能,结合超声波发射与回波检测的时间差,通过声速计算目标距离。适用于智能家居、自动安防等嵌入式应用场景,帮助开发者掌握HAL库开发、定时器配置与传感器数据处理的核心技能。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐