[018] [STM32] 定时器 基本定时/输出比较/输入捕获功能详解与HAL库编程

STM32
Contents
定时器功能介绍
时钟源
时基单元
计数模式
通道结构
输出比较模式
——PWM输出
输入捕获模式
主要寄存器
定时器HAL库编程
基本定时器功能
输出比较功能
输入捕获功能

1 定时器功能介绍

image-20220403161603562

1.1 时钟源

image-20220403211240896

1.2 时基单元

image-20220403163459839

工作在定时模式时:预分频时钟CK_PSC = 定时器时钟TIM_CLK(来自APB总线)

预分频模块

  • 预分频计数器:对预分频时钟CK_PSC进行分频
  • 预分频寄存器TIMx_PSC:设置预分频系数PSC

image-20220403165342422

如:CK_PSC = TIM_CLK = PCLK2 = 72M,设置预分频系数PSC = 3时,则计数时钟CK_CNT = 72 / (3 + 1) = 18M.

因此是从0计数到PSC的,因此实际计数值为PSC+1。

计数模块

  • 核心计数器:对计数时钟CK_CNT进行二次计数
  • 计数器寄存器TIMx_CNT:存放核心计数器运行时的当前计数值

自动重载模块

由自动重载寄存器TIMx_ARR组成,根据定时器不同的计数模式

  • 递增计数模式:TIMx_ARR值为核心计数器的计数终值

  • 递减计数模式:TIMx_ARR值为核心计数器的计数初值

1.3 计数模式

image-20220403170324165

  • 递增计数模式:计数器从 0 计数到自动重载值(TIMx_ARR),然后产生上溢事件(可触发更新中断),同时再从0开始继续计数
  • 递减计数模式:计数器从自动重载值(TIMx_ARR)计数到0,然后重新从自动重载值ARR开始计数并生成计数器下溢事件
  • 中心对齐模式(递增/递减计数):计数从0开始计数到TIMx_ARR - 1,产生计数器上溢事件;然后从TIMx_ARR开始向下计数到1并生成计数器下溢事件。之后再从0开始重新计数。

计数值溢出值与计数器重载值的关系表:

计数模式 计数器溢出值 计数器重载值
递增计数 CNT = ARR CNT = 0
递减计数 CNT = 0 CNT = ARR
中心对齐计数 CNT = ARR - 1 CNT = ARR
中心对齐计数 CNT =1 CNT = 0

一般使用递增计数

image-20220403171814276

▲ 定时器时序图

计数频率CK_CNT = CK_PSC / 2,当计数器使能时(TIMx_CR1寄存器CEN置1),每个CK_CNT 的上升沿计数器寄存器CNT值+1,当从0加到ARR时,触发计数器上溢事件,同时将更新事件标志UEV置位,触发定时器更新中断。

因此,可得到定时周期计算公式:

定时时间 = 计数值 / 时钟频率CK_CNT

其中,计数值 = ARR + 1, 时钟频率 = CK_CNT = CK_PSC / PSC + 1,最后得到:

image-20220403172825256

如:定时器时钟频率为72M,ARR = 999, PSC = 71,则定时器周期 = 1000 * 72 / 72M = 1ms

计数频率受限于PSC,定时周期(频率)受限于PSC与ARR。例如,72M计数频率1秒钟理论上最多有72000000个计数,但是计数值CNT和自动重载值ARR的范围为0~65535,即最大计数65535,定时频率最大为1098.65HZ,因此将PSC设为定值,根据ARR的范围可以得到在相应的PSC值下的定时频率范围(F103的几组典型值):

计数频率CK_CNT/HZ PSC 最高定时频率/HZ(ARR=1) 最低定时频率/HZ(ARR=65535)
72M 1-1 36M 1098.65
36M 2-1 18M 549.32
1M 72-1 500K 15.26
100K 720-1 50K 1.526
65454 1100-1 32727 1
60K 1200-1 30K 0.9155

注意:ARR=0定时器不工作

上述讨论的是16位定时器,对于32位定时器,ARR最大值为4294967295

1.4 通道结构

image-20220403213617985

每个定时器具有1~4个独立的通道,各通道具有独立的输入捕获单元、捕获/比较寄存器、输出比较单元,且共享一个时基单元。因此,每个通道可以选择输出比较和输入捕获功能,但共用一个捕获/比较寄存器,只能2选1。

  • 输入捕获单元

用于捕获外部触发信号,捕获方式为上升沿/下降沿/双边沿捕获。发生捕获事件时,将此刻计数器的值锁存到比较/捕获寄存器中,同时可以产生捕获中断

  • 捕获/比较寄存器

TIMx_CCR寄存器在输入捕获模式下用于存放发送捕获事件时的当前计数值;在输出比较模式下用于存放预设的比较值。该寄存器具备预装载功能。

  • 输出比较单元

用于信号输出。定时器通过将预设的比较值与计数器的值做匹配比较,以实现各类输出,如PWM输出、单脉冲输出等。

1.5 输出比较模式——PWM输出

1.5.1 PWM基本原理

PWM输出基于输出比较模式,将计数值CNT与捕获/比较值CCR就行比较,根据比较结果和工作模式决定有效电平,同时可设置有效电平为低电平还是高电平。

PWM的工作模式

通过TIMx_CCMRx寄存器OCxM位配置:

image-20220403223129871

  • PWM模式1(OCxM[2:0] = 110)
    • 向上计数:CNT < CCR为有效电平
    • 向下计数:CNT <= CCR为有效电平
  • PWM模式2(OCxM[2:0] = 111)
    • 向上计数:CNT >= CCR为有效电平
    • 向下计数:CNT > CCR为有效电平

有效电平配置

通过TIMx_CCER寄存器CCxP位配置(CCxP中的x为通道号):

  • CCxP = 0:有效电平为电平

  • CCxP = 1:有效电平为电平

下图为PWM模式2(向上计数)、有效电平为高电平(或者可以看作PWM模式1<向上计数>、有效电平为低电平):

image-20220403215853276

此PWM配置下的频率与占空比计算公式:

PWM频率TIMx_CLK / ((ARR + 1) * (PSC+ 1))

PWM占空比(1 - CCR / (ARR + 1)) * 100%

一般为了占空比计算方便,使用PWM模式1(向上计数)、有效电平为高电平,即CNT < CRR时输出电平

image-20220403230931813

PWM占空比CCR / (ARR + 1) * 100%

PWM模式1向上计数CNT < CCR为有效电平,CNT最大值为ARR,因此CCR要为ARR+1,才能保证占空比100%

注意:**高级定时器(Advanced-control timers)**要想输出PWM波形,必须将TIMx_BDTR寄存器的MOE 位置位,以使能主输出,否则不会输出PWM。

定时器的每个通道都可以输出PWM信号,但因为共享同一个自动重装载寄存器ARR,因此可以输出占空比不同,但周期相同的PWM信号。(在输出比较模式下可以软件切换完成不同占空比和不同周期的PWM输出)

1.5.2 PWM的工作过程

image-20220403235413862

  1. CNT与CCR比较,根据比较结果输出有效/无效电平(OC1REF=0 无效电平, OC1REF=1 无效电平<注意这不是寄存器,只是个电平信号>)

  2. TIMx_CCMR1寄存器的OCxM[2:0]位:用于设置PWM模式

    • 110:PWM模式1
    • 111:PWM模式2
  3. TIMx_CCER寄存器的CCxP位:捕获 / 比较输出极性,下面为通道配置为输出的情况:

    • 0:高电平为有效电平
    • 1:低电平为有效电平
  4. TIMx_CCER寄存器的CCxE位:捕获 / 比较输出使能

    • 0:关闭使能

    • 1:打开使能

  5. OC:最终输出的电平信号

1.6 输入捕获模式

输入捕获一般用于测量信号的周期、频率、占空比等。

image-20220404151740813

  1. 待捕获信号通过引脚TIMx_CH1送入捕获通道
  2. 经过输入滤波和边沿检测后得到TI1FP1和TI1FP2信号
  3. TI1FP1和TI1FP2信号实际是同一个信号,由于输出方向不同因此有不同的名称,TI1FP1属于直接输入,送入捕获信号IC1;TI1FP2属于间接输入,送入捕获信号IC2
  4. 捕获信号IC1选择(CCMRx寄存器的CCxS位):
    • 来自捕获通道1的TI1FP1(直接输入方式)
    • 来自捕获通道2的TI2FP1(间接输入方式)
    • 来自从模式管理器的触发信号TRC
  5. IC1信号经过预分频器得到最终捕获信号IC1PS,其中预分频器可以设置每1/2/4/8次检测到边沿信号才触发捕获

输入捕获过程示意图:

image-20220404153323594

  1. 设置输入捕获为上升沿检测,递增计数模式
  2. 第一次捕获到的计数值锁存到CCR寄存器中,记为CCR1,同时将输入捕获设为下升沿检测
  3. 第二次捕获到的计数值锁存到CCR寄存器中,记为CCR2,同时将输入捕获设为上升沿检测
  4. 第三次捕获到的计数值锁存到CCR寄存器中,记为CCR3

捕获差值diff(CCR2与CCR3同理)

  • 如果CCR1 < CCR2,捕获差值diff = CCR2 - CCR1
  • 如果CCR1 > CCR2,捕获差值diff = ARR + 1 - (CCR1 - CCR2)= ARR + 1 - CCR1 + CCR2

若计数器溢出产生更新中断,此时需要在更新中断函数中记录溢出次数,每溢出一次diff加上ARR,说明信号周期太长了(一般计数值CNT都设为最大)。注意出现更新中断不代表信号周期太长导致的溢出

image-20220404155511130

如上图,第一次捕获后,计数值计数到最大值65535(16位定时器)发生更新中断,但是第二次捕获到的CCR值小于第一次捕获到的值,此时diff并没有大于65535,因此记录溢出次数时还需要判断两次捕获到的CCR值的大小关系。

令CCR1与CCR2捕获差值为diff1,CCR1与CCR3捕获差值为diff2

信号频率/周期与占空比计算

  • 频率Freq(HZ):计数频率/diff2= TIM_CLK / (PSC + 1) / diff2 = TIM_CLK / (diff2 * (PSC + 1))
  • 周期Period(S):1/freq = diff2 / TIM_CLK * (PSC + 1)
  • 占空比:diff1 / diff2 * 100%

1.6.1 PWM输入捕获

PWM输入捕获是输入捕获模式的一个特例,仅存在以下不同:

  • 两个ICx信号被映射到同一个TIx输入
  • 两个ICx信号触发边沿极性相反
  • 选择TIxFP信号之一作为触发输入,并使能定时器主从模式,且从模式必须工作在复位模式

如CH1为输入通道,IC1选择直接输入,IC2选择间接输入:

image-20220404165036637

此时第一次捕获时会复位计数器,即将CNT置零,IC2捕获脉宽,IC1捕获周期

当CH1为输入通道,IC1选择间接输入,IC2选择直接输入时,IC1捕获脉宽,IC2捕获周期。

注意:仅定时器的通道1和2可以进行pwm输入捕获。

1.7 主要寄存器

image-20220403214102379

2 定时器HAL库编程

2.1 基本定时器功能

2.1.1 时基初始化

typedef struct
{
    
    
  uint32_t Prescaler;          // 预分频系数PSC
  uint32_t CounterMode;        // 计数模式
  uint32_t Period;             // 自动重装载值ARR (不能为0)
  uint32_t ClockDivision;      // 设置定时器时钟TIM_CLK的分配值,用于输入信号滤波
  uint32_t AutoReloadPreload;  // 自动重载预加载值, 表示设置ARR寄存器内容的生效时刻
} TIM_Base_InitTypeDef;
  • CounterMode
#define TIM_COUNTERMODE_UP                 // 递增计数模式
#define TIM_COUNTERMODE_DOWN               // 递减计数模式
#define TIM_COUNTERMODE_CENTERALIGNED1     // 中心对齐计数模式1
#define TIM_COUNTERMODE_CENTERALIGNED2     // 中心对齐计数模式2
#define TIM_COUNTERMODE_CENTERALIGNED3     // 中心对齐计数模式3

TIMx_CR1寄存器:

image-20220403200314762

三种中心对齐计数模式的区别主要为输出比较中断标志位的设置方式。

一般选择递增计数模式。

  • ClockDivision
#define TIM_CLOCKDIVISION_DIV1             // 对定时器时钟TIM_CLK进行1分频
#define TIM_CLOCKDIVISION_DIV2             // 对定时器时钟TIM_CLK进行2分频
#define TIM_CLOCKDIVISION_DIV4             // 对定时器时钟TIM_CLK进行4分频

TIMx_CR1寄存器:

image-20220403200428406

主要用于输入信号的滤波,一般使用默认值:1分频。

  • AutoReloadPreload
#define TIM_AUTORELOAD_PRELOAD_DISABLE                // 预装载功能关闭
#define TIM_AUTORELOAD_PRELOAD_ENABLE                 // 预装载功能开启

TIMx_CR1寄存器:

image-20220403200748970

主要功能:

  1. 用于设置自动重装寄存器TIMx_ARR的预装载功能,即自动重装寄存器的内容是更新事件产生时写入有效,还是立即写入有效
  2. 预装载功能在多个定时器同时输出信号时比较有用,可以确保多个定时器的输出信号在同一个时刻变化,实现同步输出
  3. 单个定时器输出时,一般不开启预装载功能

最后调用HAL_TIM_Base_Init函数完成定时器的时钟、中断、引脚的配置

2.1.2 轮询模式函数

HAL_TIM_Base_Start(TIM_HandleTypeDef *htim);	// 轮询模式启动定时器
  1. 该函数在定时器初始化完成之后调用
  2. 函数需要由用户调用,用于轮询方式下启动定时器运行
HAL_TIM_Base_Stop(TIM_HandleTypeDef *htim);		// 轮询模式关闭定时器

2.1.3 中断模式函数

HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);		// 中断模式启动定时器
  1. 该函数在定时器初始化完成之后调用
  2. 函数需要由用户调用,用于使能定时器的更新中断,并启动定时器
  3. 启动前需要调用宏函数__HAL_TIM_CLEAR_IT来清除更新中断标志
HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim);		// 中断模式关闭定时器

2.1.4 DMA模式函数

HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim);	// DMA模式启动定时器
  1. 该函数在定时器初始化完成之后调用
  2. 函数需要由用户调用,用于轮询方式下启动定时器运行
HAL_TIM_Base_Stop_DMA(TIM_HandleTypeDef *htim);		// DMA模式关闭定时器

2.1.5 定时器中断处理函数

void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)

函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成中断处理:

  • 定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

完成定时器更新中断的处理。

2.1.6 常用宏接口

  • 计数值CNT读取
#define __HAL_TIM_GET_COUNTER(__HANDLE__)  ((__HANDLE__)->Instance->CNT)
  • 清除定时器中断标志
/** @brief  Clear the specified TIM interrupt flag.
  * @param  __HANDLE__ specifies the TIM Handle.
  * @param  __FLAG__ specifies the TIM interrupt flag to clear.
  *        This parameter can be one of the following values:
  *            @arg TIM_FLAG_UPDATE: 更新中断标志
  *            @arg TIM_FLAG_CC1: 通道1的捕获/比较中断标志
  *            @arg TIM_FLAG_CC2: 通道2的捕获/比较中断标志
  *            @arg TIM_FLAG_CC3: 通道3的捕获/比较中断标志
  *            @arg TIM_FLAG_CC4: 通道4的捕获/比较中断标志
  *            @arg TIM_FLAG_TRIGGER: Trigger interrupt flag
  *            @arg TIM_FLAG_CC1OF: Capture/Compare 1 overcapture flag
  *            @arg TIM_FLAG_CC2OF: Capture/Compare 2 overcapture flag
  *            @arg TIM_FLAG_CC3OF: Capture/Compare 3 overcapture flag
  *            @arg TIM_FLAG_CC4OF: Capture/Compare 4 overcapture flag
  * @retval The new state of __FLAG__ (TRUE or FALSE).
  */
#define __HAL_TIM_CLEAR_FLAG(__HANDLE__, __FLAG__)        ((__HANDLE__)->Instance->SR = ~(__FLAG__))

2.1.7 CubeMX配置

MCU使用的是STM32L071,主频32M,配置1ms周期的定时器

image-20220403203616228

  • PSC = 32 - 1

  • 向上(递增)计数模式

  • ARR = 1000 - 1

  • 不使能自动重载预装载功能

  • Trigger Output(TRGO):用于定时器同步或链接。当一个定时器处于主模式时,它可以对另一个处于从模式的定时器的计数器进行复位、启动、停止或提供时钟等操作。(不使用主从模式,默认即可)

NVIC使能定时器更新中断:

image-20220403203820529

2.1.8 基本定时应用示例

  • 初始化
int main(void)
{
    
    
	[...]
    MX_TIM6_Init();
    // 清除定时器初始化过程中的更新中断标志,避免定时器一启动就进入中断
    __HAL_TIM_CLEAR_FLAG(&htim6, TIM_FLAG_UPDATE);
    // 使能定时器6更新中断并启动定时器
    HAL_TIM_Base_Start_IT(&htim6);  
    [...]
}
  • 回调函数
uint16_t led_cnt = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    
    
    if (htim == &htim6)
    {
    
    
        if (++led_cnt % 1000 == 0)
            BOARD_LED_TOGGLE;
    }
}

定时中断流程:

image-20220403204744069

2.1.9 外部触发应用示例

将时钟源由内部时钟改为外部时钟模式2(外部触发引脚ETR提供)

  • 配置定时器2时钟源:

image-20220403211731015

  • 将定时器2的ARR配置为较大的计数终值,避免计数溢出:

image-20220403211832118

  • 按键按下时控制TIM2_ETR引脚(PA0)发送一个2ms周期的脉冲
  if (key_flag)
  {
    
    
    key_flag = 0;
    HAL_GPIO_WritePin(GPIOA, GPIO_pin_0, GPIO_PIN_RESET);
    HAL_Delay(1);
    HAL_GPIO_WritePin(GPIOA, GPIO_pin_0, GPIO_PIN_RESET);
    HAL_Delay(1);
    printf("cnt: %u.\n", __HAL_TIM_GET_COUNTER(&htim2));
  }

实验结果:每次按键按下,TIM2->CNT++

2.2 输出比较功能

2.2.1 输出比较单元初始化

typedef struct
{
    
    
  uint32_t OCMode;        // 定时器输出模式
  uint32_t Pulse;         // 捕获/比较值CRR,即TIMx_CRR寄存器的内容
  uint32_t OCPolarity;    // 设置通道输出有效电平的极性
  uint32_t OCNPolarity;   // 设置互补通道输出有效电平的极性(用于互补输出)
  uint32_t OCFastMode;    // 快速输出模式使能,该参数只在PWM模式1和2下有效
  uint32_t OCIdleState;   // 设置空闲状态下通道输出的电平状态(用于互补输出)
  uint32_t OCNIdleState;  // 设置空闲状态下互补通道输出的电平状态(用于互补输出)
} TIM_OC_InitTypeDef;
  • OCMode
#define TIM_OCMODE_TIMING                   // 输出比较冻结模式, 匹配时无通道输出
#define TIM_OCMODE_ACTIVE                   // 匹配时设置通道输出为有效电平
#define TIM_OCMODE_INACTIVE                 // 匹配时设置通道输出为无效电平
#define TIM_OCMODE_TOGGLE                   // 匹配时设置通道输出电平翻转
#define TIM_OCMODE_PWM1                     // PWM输出模式1
#define TIM_OCMODE_PWM2                     // PWM输出模式2
#define TIM_OCMODE_FORCED_ACTIVE            // 不进行匹配, 强制通道输出为有效电平
#define TIM_OCMODE_FORCED_INACTIVE          // 不进行匹配, 强制通道输出为无效电平     

匹配CNT = CCR

  • OCPolarity
#define TIM_OCPOLARITY_HIGH                // 输出有效电平为高电平
#define TIM_OCPOLARITY_LOW                 // 输出有效电平为低电平 
  • OCFastMode
#define TIM_OCFAST_DISABLE                 // 不使能快速输出模式
#define TIM_OCFAST_ENABLE                  // 使能快速输出模式   

注意该参数只在PWM模式1和2下有效

2.2.2 PWM输出启动函数

/**
  * @brief  Starts the PWM signal generation.
  * @param  htim TIM handle
  * @param  Channel TIM Channels to be enabled
  *          This parameter can be one of the following values:
  *            @arg TIM_CHANNEL_1: TIM Channel 1 selected
  *            @arg TIM_CHANNEL_2: TIM Channel 2 selected
  *            @arg TIM_CHANNEL_3: TIM Channel 3 selected
  *            @arg TIM_CHANNEL_4: TIM Channel 4 selected
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
  1. 该函数在定时器初始化完成之后调用
  2. 函数需要由用户调用,用于启动定时器的指定通道输出PWM信号

2.2.3 CCR寄存器设置宏

/**
  * @brief  Set the TIM Capture Compare Register value on runtime without calling another time ConfigChannel function.
  * @param  __HANDLE__ TIM handle.
  * @param  __CHANNEL__ TIM Channels to be configured.
  *          This parameter can be one of the following values:
  *            @arg TIM_CHANNEL_1: TIM Channel 1 selected
  *            @arg TIM_CHANNEL_2: TIM Channel 2 selected
  *            @arg TIM_CHANNEL_3: TIM Channel 3 selected
  *            @arg TIM_CHANNEL_4: TIM Channel 4 selected
  * @param  __COMPARE__ specifies the Capture Compare register new value.
  * @retval None
  */
#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \
  (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCR1 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCR2 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCR3 = (__COMPARE__)) :\
   ((__HANDLE__)->Instance->CCR4 = (__COMPARE__)))

2.2.4 CubeMX配置

MCU使用STM32F407(主频168M),配置定时器3通道1输出200HZ,占空比为50%的PWM波:

image-20220404011306174

  • 选择内部时钟源
  • 选择通道1

Counter Settings

  • PSC = 167
  • 计数模式:默认向上计数
  • ARR = 4999 (则PWM频率为168M / 168 / 5000 = 200HZ)
  • 内部时钟分频:不分频
  • 自动重载预装载功能:不使能

Trigger Output (TRGO) Parameters

  • 主从模式:不使能
  • 事件触发选择:复位(同步模式有效,PWM输出不用管)

PWM Generation Channel 1

  • 选择PWM模式1
  • Pulse:CCR = 2500
  • 使能CCR寄存器的预装载功能
  • 关闭快速输出模式
  • 输出有效电平为高电平

则占空比为 CRR/(ARR+1) = 2500 / (4999+1) = 50%

注意:在大多数情况下,选择开启CCR寄存器的预装载功能,让占空比的变化在下一个PWM信号周期才生效,但是如果利用输出比较的翻转模式生成多通道不同周期与不同转占空比的PWM波,则需要关闭预装载功能,让CCR寄存器的修改立即生效!

2.2.5 应用示例

在main函数中定时器初始化完成后,添加下面代码即可:

// 启动定时器3的通道1输出频率为200HZ,占空比为50%的PWM信号
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);

2.3 输入捕获功能

2.3.1 输入捕获单元初始化

typedef struct
{
    
    
  uint32_t  ICPolarity;  // 设置输入信号的捕获边沿
  uint32_t ICSelection;  // 选择输入捕获通道
  uint32_t ICPrescaler;  // 设置输入信号的预分频系数,取值为1/2/4/8
  uint32_t ICFilter;     // 设置输入信号的滤波器长度, 取值范围0~0xf
} TIM_IC_InitTypeDef;     
  • ICPolarity
#define  TIM_ICPOLARITY_RISING             // 上升沿捕获
#define  TIM_ICPOLARITY_FALLING            // 下升沿捕获
#define  TIM_ICPOLARITY_BOTHEDGE           // 双边沿捕获
  • ICSelection
#define TIM_ICSELECTION_DIRECTTI           // 直接输入模式(捕获通道CH1和CH2分别与IC1和IC2相连, 捕获通道CH3和CH4分别与IC3和IC4相连)
#define TIM_ICSELECTION_INDIRECTTI         // 间接输入模式(捕获通道CH1和CH2分别与IC2和IC1相连, 捕获通道CH3和CH4分别与IC4和IC3相连)
#define TIM_ICSELECTION_TRC                // 选择从模式管理器的触发信号TRC作为捕获信号
  • ICPrescaler
#define TIM_ETRPRESCALER_DIV1                 // 检测到输入信号的每1个有效边沿触发1次捕获
#define TIM_ETRPRESCALER_DIV2                 // 检测到输入信号的每2个有效边沿触发1次捕获
#define TIM_ETRPRESCALER_DIV4                 // 检测到输入信号的每4个有效边沿触发1次捕获
#define TIM_ETRPRESCALER_DIV8                 // 检测到输入信号的每8个有效边沿触发1次捕获

2.3.2 输入捕获启动与停止函数

  • 启动函数

一般用中断方式启动:

HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
  1. 该函数在定时器初始化完成之后调用
  2. 函数需要由用户调用,用于使能定时器的捕获中断,并启动定时器运行
  • 停止函数
HAL_TIM_IC_Stop_IT(TIM_HandleTypeDef *htim, uint32_t Channel);

该函数需要由用户调用,用于禁止捕获中断,关闭输入捕获通道,停止定时器运行

2.3.3 输入捕获中断回调函数

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
  1. 该函数由定时器中断通用处理函数HAL_TIM_IRQHandler调用,完成所有定时器输入捕获中断的任务处理
  2. 函数内部需要根据定时器句柄的实例来判断是哪一个定时器的哪一个通道产生的本次输入捕获中断

2.3.4 捕获值读取函数

uint32_t HAL_TIM_ReadCapturedValue(TIM_HandleTypeDef *htim, uint32_t Channel);

返回:htim->Instance->CCRx

2.3.5 捕获边沿设置宏

/**
  * @brief  Set the TIM Capture x input polarity on runtime.
  * @param  __HANDLE__ TIM handle.
  * @param  __CHANNEL__ TIM Channels to be configured.
  *          This parameter can be one of the following values:
  *            @arg TIM_CHANNEL_1: TIM Channel 1 selected
  *            @arg TIM_CHANNEL_2: TIM Channel 2 selected
  *            @arg TIM_CHANNEL_3: TIM Channel 3 selected
  *            @arg TIM_CHANNEL_4: TIM Channel 4 selected
  * @param  __POLARITY__ Polarity for TIx source
  *            @arg TIM_INPUTCHANNELPOLARITY_RISING:   上升沿
  *            @arg TIM_INPUTCHANNELPOLARITY_FALLING:  下降沿
  *            @arg TIM_INPUTCHANNELPOLARITY_BOTHEDGE: 双边沿
  * @retval None
  */
#define __HAL_TIM_SET_CAPTUREPOLARITY(__HANDLE__, __CHANNEL__, __POLARITY__)    \
  do{
      
                                                                           \
    TIM_RESET_CAPTUREPOLARITY((__HANDLE__), (__CHANNEL__));               \
    TIM_SET_CAPTUREPOLARITY((__HANDLE__), (__CHANNEL__), (__POLARITY__)); \
  }while(0)

2.3.6 CubeMX配置

MCU使用为STM32F103,主频72M,配置定时器2的CH1为捕获通道:

image-20220404172315943

image-20220404172401818

定时器模式配置

  • 选择内部时钟源,通道1位输入捕获通道且为直接连接(CH1映射到IC1)

Counter Settings

  • psc = 71,arr = 65535,即以1M频率去计数

Input Capture Channel 1

  • 捕获有效边沿为上升沿
  • 捕获通道为直接输入方
  • 捕获信号不分频
  • 捕获信号不进行滤波

最后使能定时器中断。

配置定时器3的通道1输出PWM波:

image-20220404173050948

PWM频率为10K,占空比为25%

2.3.7 应用示例

启动定时器和PWM输出:

void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    
    
	[...]
    MX_TIM2_Init();
    MX_TIM3_Init();
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
	HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
    while (1)
    {
    
    
		capture();
    }
}

编写捕获中断回调函数HAL_TIM_IC_CaptureCallback

uint16_t CCR1, CCR2, CCR3;
uint8_t measure_flag = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    
    
	static uint8_t measure_cnt = 1;
	if (htim == &htim2)
	{
    
    
		if (measure_cnt == 1)
		{
    
    
			CCR1 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
			__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
			measure_cnt = 2;
		}
		else if (measure_cnt == 2)	
		{
    
    
			CCR2 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
			__HAL_TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
			measure_cnt = 3;
		}
		else if (measure_cnt == 3)	
		{
    
    
			CCR3 = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
			HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_1);
			measure_cnt = 1;
			measure_flag = 1;
		}
	}
}

每1秒打印一次测量结果并再次开启捕获中断:

uint32_t freq;
uint8_t duty;
void capture(void)
{
    
    
	uint16_t diff1 = 0, diff2 = 0;
	if (measure_flag)
	{
    
    
		measure_flag = 0;
		
		if (CCR1 < CCR2)
			diff1 = CCR2 - CCR1;
		else
			diff1 = 0xffff + 1 + CCR2 - CCR1;
		
		if (CCR1 < CCR3)
			diff2 = CCR3 - CCR1;
		else
			diff2 = 0xffff + 1 + CCR3 - CCR1;
		freq = (72000000 / 72) / diff2;
		duty = diff1 * 100 / diff2;
		printf("freq: %dHZ,  duty: %d%%\r\n", freq, duty);
		HAL_Delay(1000);
		HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
	}
}

image-20220404180711127

END

猜你喜欢

转载自blog.csdn.net/kouxi1/article/details/123957629