第12章 输入捕获简介

第十二章 输入捕获简介

1. 简介

输入捕获模式可以用来测量脉冲宽度或者测量频率。 我们以测量脉宽为例,用一个简图来说明输入捕获的原理:

屏幕截图 2024 09 17 114736

如图所示,就是输入捕获测量高电平脉宽的原理,假定定时器工作在向上计数模式,图中 t1~t2 时间,就是我们需要测量的高电平时间。测量方法如下:首先设置定时器通道 x 为上升沿捕获,这样, t1 时刻,就会捕获到当前的 CNT 值,然后立即清零 CNT,并设置通道x为下降沿捕获,这样到 t2 时刻,又会发生捕获事件,得到此时的 CNT 值,记为 CCRx2。 这样,根据定时器的计数频率,我们就可以算出 t1~t2 的时间,从而得到高电平脉宽。

在 t1~t2 之间,可能产生 N 次定时器溢出,这就要求我们对定时器溢出,做处理,防止高电平太长,导致数据不准确。如图所示, t1~t2之间, CNT计数的次数等于: N*ARR+CCRx2,有了这个计数次数,再乘以 CNT 的计数周期,即可得到 t2 - t1 的时间长度,即高电平持续时间。下面我们再来详细介绍一下:

输入捕获模式的基本原理

  1. 概念
  • 输入捕获模式允许微控制器的定时器在检测到外部信号的特定边缘(上升沿或下降沿)时记录当前计数器的值。这使得用户能够捕获信号事件发生的时间。
  1. 工作原理
  • 定时器配置为输入捕获模式后,定时器会监控外部引脚的状态变化。
  • 当外部输入引脚的电平变化到设定的边缘(例如上升沿),定时器会立即记录当前计数器的值(TIMx_CNT)到输入捕获寄存器(CCR)。
  • 这个值可以用来计算时间间隔、脉冲宽度等。
  1. 触发条件
  • 输入捕获可以配置为响应上升沿、下降沿或两者的任意组合。这由定时器的输入捕获配置设置。
  1. 中断
  • 当捕获事件发生时,可以选择触发中断,以便在中断服务程序中处理捕获的数据。这对于实时系统非常重要。

输入捕获的应用

  • 脉宽调制(PWM)信号测量:可以测量PWM信号的高电平和低电平持续时间。
  • 频率测量:通过捕获信号的周期,可以计算频率。
  • 时间测量:用于测量外部事件之间的时间间隔。

输入捕获示例

以下是一个简单的输入捕获配置示例:

#include "stm32f4xx_hal.h"

TIM_HandleTypeDef htimx;

// 输入捕获初始化
void MX_TIMx_InputCapture_Init(void) {
    TIM_IC_InitTypeDef sConfigIC = {0};

    // 定时器初始化
    htimx.Instance = TIMx;
    htimx.Init.Prescaler = 0; // 不分频
    htimx.Init.CounterMode = TIM_COUNTERMODE_UP;
    htimx.Init.Period = 0xFFFF; // 最大计数
    HAL_TIM_Base_Init(&htimx);

    // 配置输入捕获
    sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING; // 上升沿捕获
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直接输入
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不分频
    sConfigIC.ICFilter = 0; // 无过滤
    HAL_TIM_IC_ConfigChannel(&htimx, &sConfigIC, TIM_CHANNEL_1);

    // 启动输入捕获
    HAL_TIM_IC_Start_IT(&htimx, TIM_CHANNEL_1); // 启动并使能中断
}

// 中断服务程序
void TIMx_IRQHandler(void) {
    HAL_TIM_IRQHandler(&htimx);
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIMx) {
        uint32_t captureValue = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        // 处理捕获值
    }
}

STM32F4 的定时器,除了 TIM6 和 TIM7,其他定时器都有输入捕获功能。 STM32F4 的输入捕获,简单的说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值( TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。

2. 相关寄存器

接下来,我们介绍我们本章需要用到的一些寄存器配置,需要用到的寄存器有: TIMx_ARR、TIMx_PSC、 TIMx_CCMR1、 TIMx_CCER、 TIMx_DIER、 TIMx_CR1、 TIMx_CCR1 这些寄存器在前面 2 章全部都有提到(这里的 x=5),我们这里就不再全部罗列了,我们这里针对性的介绍这几个寄存器的配置。

首先 TIMx_ARR 和 TIMx_PSC,这两个寄存器用来设自动重装载值和 TIMx 的时钟分频,用法同前面介绍的,我们这里不再介绍。

再来看看捕获/比较模式寄存器 1: TIMx_CCMR1,这个寄存器在输入捕获的时候,非常有用,有必要重新介绍,该寄存器的各位描述如图:

屏幕截图 2024 09 17 115218

从图中可以看出, TIMx_CCMR1 明显是针对 2 个通道的配置,低八位[7:0]用于捕获/比较通道 1 的控制,而高八位[15:8]则用于捕获/比较通道 2 的控制,因为 TIMx 还有 CCMR2 这个寄存器,所以可以知道CCMR2 是用来控制通道 3 和通道 4。

这里我们用到的是 TIM5 的捕获/比较通道 1,我们重点介绍 TIMx_CCMR1 的[7:0]位(其高 8 位配置类似), TIMx_CCMR1 的[7:0]位详细描述见图:

屏幕截图 2024 09 17 115329

其中 CC1S[1:0],这两个位用于 CCR1 的通道配置, 这里我们设置 IC1S[1:0]=01,也就是配置 IC1 映射在 TI1 上

输入捕获 1 预分频器 IC1PSC[1:0],这个比较好理解。我们是 1 次边沿就触发 1 次捕获,所以选择 00 就是了。

屏幕截图 2024 09 17 115514

再来看看捕获/比较使能寄存器: TIMx_CCER,本章我们要用到这个寄存器的最低 2 位, CC1E 和 CC1P 位。这两个位的描述如图:

屏幕截图 2024 09 17 115634

所以,要使能输入捕获,必须设置 CC1E=1,而 CC1P 则根据自己的需要来配置。

接下来我们再看看 DMA/中断使能寄存器: TIMx_DIER,我们需要用到中断来处理捕获数据,所以必须开启通道 1 的捕获比较中断,即 CC1IE 设置为 1。

控制寄存器: TIMx_CR1,我们只用到了它的最低位,也就是用来使能定时器的,这里前面两章都有介绍,请大家参考前面的章节。

最后再来看看捕获/比较寄存器 1: TIMx_CCR1,该寄存器用来存储捕获发生时, TIMx_CNT的值,我们从 TIMx_CCR1 就可以读出通道 1 捕获发生时刻的 TIMx_CNT 值,通过两次捕获(一次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度(注意,对于脉宽太长的情况,还要计算定时器溢出的次数)。

至此,我们把本章要用的几个相关寄存器都介绍完了, 本章要实现通过输入捕获,来获取TIM5_CH1(PA0)上面的高电平脉冲宽度,并从串口打印捕获结果。 下面我们介绍库函数配置上述功能输入捕获的步骤:

3. 基本步骤

3.1 开启TIM5时钟,配置PA0为复用功能,开启下拉电阻

要使用 TIM5,我们必须先开启 TIM5 的时钟。同时我们要捕获 TIM5_CH1 上面的高电平脉宽,所以先配置 PA0 为带下拉的复用功能,同时,为了让 PA0 的复用功能选择连接到 TIM5,所以设置 PA0 的复用功能为 AF2,即连接到 TIM5 上面。

开启定时器和 GPIO 时钟的方法和上一章是一样的,这里我们就不做过多讲解。

配置 PA0 为复用功能(AF2) 并开启下拉功能也和上一章一样是通过函数 HAL_GPIO_Init来实现。由于这一步配置过程和上一章几乎没有区别,所以这里我们直接列出配置代码:

__HAL_RCC_TIM5_CLK_ENABLE();         // 使能 TIM5 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();        // 开启 GPIOA 时钟
GPIO_Initure.Pin = GPIO_PIN_0;       // PA0
GPIO_Initure.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_Initure.Pull = GPIO_PULLDOWN;   // 下拉
GPIO_Initure.Speed = GPIO_SPEED_HIGH;// 高速
GPIO_Initure.Alternate = GPIO_AF2_TIM5;// PA0 复用为 TIM5 通道 1
HAL_GPIO_Init(GPIOA, &GPIO_Initure);

跟上一讲 PWM 输出类似,这里我们使用的是定时器 5 的通道 1,所以我们从 STM32F4 对应的数据手册可以查看到对应的 IO 口为 PA0:

屏幕截图 2024 09 17 120021

3.2 初始化TIM5,设置TIM5的ARR和PSC

和上一讲 PWM 输出实验一样,当使用定时器做输入捕获功能时,在 HAL 库中并不使用定时器初始化函数 HAL_TIM_Base_Init 来实现,而是使用输入捕获特定的定时器初始化函数HAL_TIM_IC_Init。当我们使用函数 HAL_TIM_IC_Init 来初始化定时器的输入捕获功能时,该函数内部会调用输入捕获初始化回调函数HAL_TIM_IC_MspInit来初始化与 MCU 无关的步骤。

函数 HAL_TIM_IC_Init 声明如下:

HAL_StatusTypeDef HAL_TIM_IC_Init(TIM_HandleTypeDef *htim);

该函数非常简单,和 HAL_TIM_Base_Init 函数以及函数 HAL_TIM_PWM_Init 使用方法是一模一样的,这里我们就不累赘。

回调函数 HAL_TIM_IC_MspInit 声明如下:

void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim);

该函数使用方法和 PWM 初始化回调函数 HAL_TIM_PWM_MspInit 使用方法一致。一般情况下,输入捕获初始化回调函数中会编写步骤 1 内容,以及后面讲解的 NVIC 配置。

有了 PWM 实验基础知识,这两个函数的使用就非常简单,这里我们列出该步骤程序如下:

TIM_HandleTypeDef TIM5_Handler;
TIM5_Handler.Instance = TIM5;     // 通用定时器 5
TIM5_Handler.Init.Prescaler = 89; // 分频系数
TIM5_Handler.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数器
TIM5_Handler.Init.Period = 0XFFFFFFFF; // 自动装载值
TIM5_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟分频银子
HAL_TIM_IC_Init(&TIM5_Handler); // 初始化输入捕获时基参数

3.3 设置TIM5的输入捕获参数,开启输入捕获

TIM5_CCMR1 寄存器控制着输入捕获 1 和 2 的模式,包括映射关系,滤波和分频等。这里我们需要设置通道 1 为输入模式,且 IC1 映射到 TI1(通道 1)上面,并且不使用滤波(提高响应速度)器。 HAL 库是通过 HAL_TIM_IC_ConfigChannel 函数来初始化输入比较参数的:

HAL_StatusTypeDef HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim, TIM_IC_InitTypeDef* sConfig, uint32_t Channel);

该函数有三个参数,第一个参数是定时器初始化结构体指针类型,该参数很好理解。第二个参数是设置要初始化的定时器通道值,取值范围为 TIM_CHANNEL_1~ TIM_CHANNEL_4。接下来我们着重讲解第二个入口参数 sConfig,该参数是 TIM_IC_InitTypeDef 结构体指针类型,它是真正用来初始化定时器通道的捕获参数的。该结构体类型定义为:

typedef struct
{
    uint32_t ICPolarity;
    uint32_t ICSelection;
    uint32_t ICPrescaler;
    uint32_t ICFilter;
} TIM_IC_InitTypeDef;

成员变量 ICPolarity 用来设置输入信号的有效捕获极性,取值范围为:TIM_ICPOLARITY_RISING(上升沿捕获), TIM_ICPOLARITY_FALLING(下降沿捕获)和TIM_ICPOLARITY_BOTHEDGE(双边沿)捕获。实际上, HAL 还提供了设置输入捕获极性以及清除输入捕获极性设置方法。如下:

TIM_RESET_CAPTUREPOLARITY(&TIM5_Handler,TIM_CHANNEL_1); // 清除极性设置
TIM_SET_CAPTUREPOLARITY(&TIM5_Handler,TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);//定时器 5 通道 1 设置为下降沿捕获

成员变量 ICSelection 用来设置映射关系,我们配置 IC1 直接映射在 TI1 上,选择TIM_ICSELECTION_DIRECTTI。

成员变量ICPrescaler用来设置输入捕获分频系数, 可以设置为TIM_ICPSC_DIV1(不分频), TIM_ICPSC_DIV2(2 分频), TIM_ICPSC_DIV4(4 分频)以及 TIM_ICPSC_DIV8(8 分频),本实验需要设置为不分频,所以选值为 TIM_ICPSC_DIV1。

成员变量 ICFilter 用来设置滤波器长度,这里我们不使用滤波器,所以设置为 0。

本实验,我们要设置输入捕获参数为:上升沿捕获,不分频,不滤波,同时 IC1 映射到 TI1(通道 1)上,实例代码如下:

TIM_IC_InitTypeDef TIM5_CH1Config;
TIM5_CH1Config.ICPolarity = TIM_ICPOLARITY_RISING; // 上升沿捕获
TIM5_CH1Config.ICSelection = TIM_ICSELECTION_DIRECTTI; // IC1 映射到 TI1 上
TIM5_CH1Config.ICPrescaler = TIM_ICPSC_DIV1; // 配置输入分频,不分频
TIM5_CH1Config.ICFilter = 0; // 配置输入滤波器,不滤波
HAL_TIM_IC_ConfigChannel(&TIM5_Handler,&TIM5_CH1Config,TIM_CHANNEL_1);

3.4 使能捕获和更新中断(设置TIM5和DIER寄存器)

因为我们要捕获的是高电平信号的脉宽,所以,第一次捕获是上升沿,第二次捕获时下降沿,必须在捕获上升沿之后,设置捕获边沿为下降沿,同时,如果脉宽比较长,那么定时器就会溢出,对溢出必须做处理,否则结果就不准了,不过,由于 STM32F4 的 TIM5 是 32 位定时器,假设计数周期为 1us,那么需要 4294 秒才会溢出一次,这基本上是不可能的。这两件事,我们都在中断里面做,所以必须开启捕获中断和更新中断。

HAL 库中开启定时器中断方法在定时器中断实验已经讲解,方法为:

__HAL_TIM_ENABLE_IT(&TIM5_Handler,TIM_IT_UPDATE); //使能更新中断

实际上,由于本章使用的是定时器的输入捕获功能, HAL 还提供了一个函数同时用来开启定时器的输入捕获通道和使能捕获中断,该函数为:

HAL_StatusTypeDef HAL_TIM_IC_Start_IT (TIM_HandleTypeDef *htim, uint32_t Channel);

实际上该函数同时还使能了定时器,一个函数具备三个功能。

如果我们不需要开启捕获中断,只是开启输入捕获功能, HAL 库函数为:

HAL_StatusTypeDef HAL_TIM_IC_Start (TIM_HandleTypeDef *htim, uint32_t Channel);

3.5 使能定时器(设置TIM5的CR1寄存器)

在步骤 4 中,如果我们调用了函数 HAL_TIM_IC_Start_IT 来开启输入捕获通道以及输入捕获中断,实际上它同时也开启了相应的定时器。单独的开启定时器的方法为:

__HAL_TIM_ENABLE(); // 开启定时器方法

3.6 设置NVIC中断优先级

因为我们要使用到中断,所以我们在系统初始化之后,需要先设置中断优先级,这里方法跟我们前面讲解一致,这里我们就不累赘了。

这里大家要注意,一般情况下 NVIC 配置我们都会放在 MSP 回调函数中。对于输入捕获功能,回调函数是我们步骤 2 讲解的函数 HAL_TIM_IC_MspInit。

3.7 编写中断服务函数

最后编写中断服务函数。定时器 5 中断服务函数为:

void TIM5_IRQHandler(void);

和定时器中断实验一样,一般情况下,我们都不把中断控制逻辑直接编写在中断服务函数中,因为 HAL 库提供了一个共用的中断处理入口函数 HAL_TIM_IRQHandler,该函数中会对中断来源进行判断然后调用相应的中断处理回调函数。 HAL 库提供了多个中断处理回调函数,本章实验,我们要使用到更新中断和捕获中断,所以我们要使用的回调函数为:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); // 更新(溢出) 中断
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);    // 捕获中断

我们只需要在我们工程中,重新定义这两个函数,编写中断处理控制逻辑即可。


2024.10.3 第一次修订,后期不再维护

posted @ 2024-10-03 14:22  hazy1k  阅读(3)  评论(0编辑  收藏  举报