参考:
-正点原子定时器

定时器概述

定时器定时原理

定时器分类

定时器特性表

基本、通用、高级定时器的功能整体区别

特点芯片定时器资源和引脚

参考hal库usart外设->查看引脚定义

定时器的主从模式

核心概念

  • 主模式(Master):主动发信号。定时器自己跑,并通过 TRGO(触发输出)给别的定时器 / 外设发 “命令”(同步信号)。
  • 从模式(Slave):被动听命令。定时器自己不主动跑,等待外部信号或别的定时器通过 TRGI(触发输入)来控制它(启动、停止、清零、当时钟)。

主模式(Master Mode)详解

作用:让一个定时器作为指挥官,控制其他定时器或外设同步启动 / 停止。
怎么工作?
配置定时器在特定事件发生时,通过 TRGO 引脚 / 内部线 输出一个触发脉冲:

  • 计数器 CNT 归零 (Update) 时发 TRGO
  • 捕获 / 比较事件发生 (Capture) 时发 TRGO
  • 使能时发 TRGO 等
    典型应用
  • 多定时器同步:TIM2(主)发 TRGO → 控制 TIM3、TIM4 同时启动。
  • 触发 ADC 采样:定时器定时发 TRGO → 自动启动 ADC 转换(硬件触发,比软件准)。
  • 触发 DMA:定时事件自动启动数据传输。

从模式(Slave Mode)详解

作用:让定时器作为执行者,受控于外部信号或主定时器。

STM32 定时器有 5 种常用从模式:

  1. 复位模式 (Reset) —— 最常用(测频率)
  • 行为:收到 TRGI 上升沿时,硬件自动把 CNT 清 0。
  • 应用:输入捕获测周期 / 频率。(就是你上一问的 “硬件清除计数器”)
  • 原理:信号每来一个周期,计数器就被清零一次,CCR 里的值就是精准周期。
  1. 门控模式 (Gated) —— 测脉宽
  • 行为:TRGI 高电平时计数,低电平时暂停。
  • 应用:测量高电平持续时间(脉冲宽度)。
  1. 触发模式 (Trigger)
  • 行为:收到 TRGI 信号才开始计数,没收到就一直停着。
  • 应用:多定时器精准同步启动、外部信号触发计时。
  1. 外部时钟模式 1 (External Clock Mode 1)
  • 行为:把 TRGI 信号当作计数器的时钟。每来一个脉冲,CNT+1。
  • 应用:外部脉冲计数、转速测量。
  1. 编码器模式 (Encoder Mode)
  • 行为:专门响应编码器 A/B 相脉冲,自动判断正反转。
  • 应用:电机编码器测速。

主 / 从模式对比表

特性 主模式 (Master) 从模式 (Slave)
角色 指挥官 执行者
信号流向 输出 TRGO 输入 TRGI
控制权 自主运行,控制别人 被动,被别人控制
核心用途 同步其他外设、发命令 测频率、测脉宽、同步计数、编码器测速

基本定时器

简介

框图

计数模式及溢出条件

相关寄存器

溢出时间计算方法

hal库配置

相关函数

中断实验配置步骤

示例

定时器中断控制led闪烁

宏定义

1
2
3
4
#define BTIM_TIMX_INT                       TIM6
#define BTIM_TIMX_INT_IRQn TIM6_DAC_IRQn
#define BTIM_TIMX_INT_IRQHandler TIM6_DAC_IRQHandler
#define BTIM_TIMX_INT_CLK_ENABLE() do{ __HAL_RCC_TIM6_CLK_ENABLE(); }while(0) /* TIM6 时钟使能 */

全局变量

1
TIM_HandleTypeDef g_timx_handle;  /* 定时器句柄 */

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* @brief 基本定时器TIMX定时中断初始化函数
* @note
* 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 基本定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值。
* @param psc: 时钟预分频数
* @retval 无
*/
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
g_timx_handle.Instance = BTIM_TIMX_INT; /* 通用定时器X */
g_timx_handle.Init.Prescaler = psc; /* 设置预分频系数 */
g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_handle.Init.Period = arr; /* 自动装载值 */
HAL_TIM_Base_Init(&g_timx_handle);

HAL_TIM_Base_Start_IT(&g_timx_handle); /* 使能定时器x及其更新中断 */
}

/**
* @brief 定时器底层驱动,开启时钟,设置中断优先级
此函数会被HAL_TIM_Base_Init()函数调用
* @param htim:定时器句柄
* @retval 无
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == BTIM_TIMX_INT)
{
BTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIM时钟 */
HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 1, 3); /* 抢占1,子优先级3,组2 */
HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn); /* 开启ITM3中断 */
}
}

中断服务函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @brief 定时器TIMX中断服务函数
* @param 无
* @retval 无
*/
void BTIM_TIMX_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_handle); /* 定时器中断公共处理函数 */
}

/**
* @brief 定时器更新中断回调函数
* @param htim:定时器句柄
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == BTIM_TIMX_INT)
{
LED1_TOGGLE(); /* LED1反转 */
}
}

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_init(); /* 初始化LED */
btim_timx_int_init(5000 - 1, 7200 - 1); /* 10Khz的计数频率,计数5K次为500ms */

while (1)
{
LED0_TOGGLE();
delay_ms(200);
}
}

通用定时器

简介

框图

计数器时钟源

总览

外部时钟模式1

外部时钟模式2

使用一个定时器作为另一个定时器的预分频器(F1为例)

计数器时钟源寄存器设置方法(F1为例)

更新中断

PWM输出

简介

通过捕获比较寄存器(TIMX_CCRX)的值和计数器(TIMX_CNT)的值相比较,从而输出高低电平,实现PWM输出。

框图

定时器输出PWM原理

PWM模式

hal库配置PWM输出

输入捕获

简介

通过捕获输入信号的上升沿或下降沿,将计数器(TIMX_CNT)的值存储到捕获寄存器(TIMX_CCRx)中,同时用从模式或中断清空计数器(TIMX_CNT)。
通过捕获寄存器(TIMX_CCRx)的值和定时器更新次数,可以计算出输入信号的周期。

也可以直接分别记录两次捕获寄存器(TIMX_CCRx)的值和期间定时器更新次数,计算出输入信号的周期。

框图

输入捕获脉宽测量原理

hal库配置

示例

定时器中断控制led闪烁

宏定义

1
2
3
4
#define GTIM_TIMX_INT                       TIM3
#define GTIM_TIMX_INT_IRQn TIM3_IRQn
#define GTIM_TIMX_INT_IRQHandler TIM3_IRQHandler
#define GTIM_TIMX_INT_CLK_ENABLE() do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0) /* TIM3 时钟使能 */

全局变量

1
TIM_HandleTypeDef g_timx_handle;  /* 定时器句柄 */

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @brief 通用定时器TIMX定时中断初始化函数
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值。
* @param psc: 时钟预分频数
* @retval 无
*/
void gtim_timx_int_init(uint16_t arr, uint16_t psc)
{
GTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIMx时钟 */

g_timx_handle.Instance = GTIM_TIMX_INT; /* 通用定时器x */
g_timx_handle.Init.Prescaler = psc; /* 预分频系数 */
g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_handle.Init.Period = arr; /* 自动装载值 */
HAL_TIM_Base_Init(&g_timx_handle);

HAL_NVIC_SetPriority(GTIM_TIMX_INT_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(GTIM_TIMX_INT_IRQn); /* 开启ITMx中断 */

HAL_TIM_Base_Start_IT(&g_timx_handle); /* 使能定时器x和定时器x更新中断 */
}

中断服务函数

1
2
3
4
5
6
7
8
9
void GTIM_TIMX_INT_IRQHandler(void)
{
/* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
if(__HAL_TIM_GET_FLAG(&g_timx_handle, TIM_FLAG_UPDATE) != RESET)
{
LED1_TOGGLE();
__HAL_TIM_CLEAR_IT(&g_timx_handle, TIM_IT_UPDATE); /* 清除定时器溢出中断标志位 */
}
}

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_init(); /* LED初始化 */
gtim_timx_int_init(5000 - 1, 7200 - 1); /* 10Khz的计数频率,计数5K次为500ms */

while(1)
{
LED0_TOGGLE();
delay_ms(200);
}
}

PWM输出展现呼吸灯效果

PA0接灯
宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* TIMX PWM输出定义 
* 这里输出的PWM控制LED0(RED)的亮度
* 默认是针对TIM2~TIM5
* 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,任意一个IO口输出PWM
*/
#define GTIM_TIMX_PWM_CHY_GPIO_PORT GPIOA
#define GTIM_TIMX_PWM_CHY_GPIO_PIN GPIO_PIN_0
#define GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PB口时钟使能 */

/* TIMX REMAP设置
* 因为我们LED0接在PB5上, 必须通过开启TIM3的部分重映射功能, 才能将TIM3_CH2输出到PB5上
* 因此, 必须实现GTIM_TIMX_PWM_CHY_GPIO_REMAP
* 对那些使用默认设置的定时器PWM输出脚, 不用设置重映射, 是不需要该函数的!
*/
#define GTIM_TIMX_PWM_CHY_GPIO_REMAP() do{__HAL_RCC_AFIO_CLK_ENABLE();\
__HAL_AFIO_REMAP_TIM3_PARTIAL();\
}while(0) /* 通道REMAP设置, 该函数不是必须的, 根据需要实现 */

#define GTIM_TIMX_PWM TIM2
#define GTIM_TIMX_PWM_CHY TIM_CHANNEL_1 /* 通道Y, 1<= Y <=4 */
#define GTIM_TIMX_PWM_CHY_CCRX TIM2->CCR2 /* 通道Y的输出比较寄存器 */
#define GTIM_TIMX_PWM_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM2_CLK_ENABLE(); }while(0) /* TIM3 时钟使能 */

全局变量

1
TIM_HandleTypeDef g_timx_pwm_handle;  /* 定时器句柄 */

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* @brief 通用定时器TIMX 通道Y PWM输出 初始化函数(使用PWM模式1)
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值。
* @param psc: 时钟预分频数
* @retval 无
*/
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_pwm_chy = {0}; /* 定时器PWM输出配置 */

g_timx_pwm_chy_handle.Instance = GTIM_TIMX_PWM; /* 定时器x */
g_timx_pwm_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_pwm_chy_handle.Init.Period = arr; /* 自动重装载值 */
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle); /* 初始化PWM */

timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1 */
timx_oc_pwm_chy.Pulse = arr / 2; /* 设置比较值,此值用来确定占空比 */
/* 默认比较值为自动重装载值的一半,即占空比为50% */
timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW; /* 输出比较极性为低 */
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, GTIM_TIMX_PWM_CHY); /* 配置TIMx通道y */
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY); /* 开启对应PWM通道 */
}

/**
* @brief 定时器底层驱动,时钟使能,引脚配置
此函数会被HAL_TIM_PWM_Init()调用
* @param htim:定时器句柄
* @retval 无
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == GTIM_TIMX_PWM)
{
GPIO_InitTypeDef gpio_init_struct;
GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE(); /* 开启通道y的CPIO时钟 */
GTIM_TIMX_PWM_CHY_CLK_ENABLE();

gpio_init_struct.Pin = GTIM_TIMX_PWM_CHY_GPIO_PIN; /* 通道y的CPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推完输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GTIM_TIMX_PWM_CHY_GPIO_PORT, &gpio_init_struct);
// GTIM_TIMX_PWM_CHY_GPIO_REMAP(); /* IO口REMAP设置, 是否必要查看头文件配置的说明 */
}
}

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int main(void)
{
uint16_t ledrpwmval = 0;
uint8_t dir = 1;

HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
gtim_timx_pwm_chy_init(500 - 1, 72 - 1);/* 1Mhz的计数频率,2Khz的PWM. */

while(1)
{
//PA0接灯,呼吸灯效果
delay_ms(5);

if (dir)ledrpwmval++; /* dir==1 ledrpwmval递增 */
else ledrpwmval--; /* dir==0 ledrpwmval递减 */

if (ledrpwmval >= 500)dir = 0; /* ledrpwmval到达300后,方向为递减 */
if (ledrpwmval == 0)dir = 1; /* ledrpwmval递减到0后,方向改为递增 */

/* 修改比较值控制占空比 */
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY, ledrpwmval);
}
}

输入捕获测按键PB1按下时长

宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 /* TIMX 输入捕获定义 
* 这里的输入捕获使用定时器TIM5_CH1,捕获WK_UP按键的输入
* 默认是针对TIM2~TIM5.
* 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,任意一个IO口做输入捕获
* 特别要注意:默认用的PA0,设置的是下拉输入!如果改其他IO,对应的上下拉方式也得改!
*/
#define GTIM_TIMX_CAP_CHY_GPIO_PORT GPIOB
#define GTIM_TIMX_CAP_CHY_GPIO_PIN GPIO_PIN_1
#define GTIM_TIMX_CAP_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PA口时钟使能 */

#define GTIM_TIMX_CAP TIM3
#define GTIM_TIMX_CAP_IRQn TIM3_IRQn
#define GTIM_TIMX_CAP_IRQHandler TIM3_IRQHandler
#define GTIM_TIMX_CAP_CHY TIM_CHANNEL_4 /* 通道Y, 1<= Y <=4 */
#define GTIM_TIMX_CAP_CHY_CCRX TIM3->CCR4 /* 通道Y的输出比较寄存器 */
#define GTIM_TIMX_CAP_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0) /* TIM2 时钟使能 */

全局变量

1
2
3
4
5
6
7
8
9
10
11
12
TIM_HandleTypeDef g_timx_cap_chy_handle;      /* 定时器x句柄 */
/* 输入捕获状态(g_timxchy_cap_sta)
* [7] :0,没有成功的捕获;1,成功捕获到一次.
* [6] :0,还没捕获到高电平;1,已经捕获到高电平了.
* [5:0]:捕获高电平后溢出的次数,最多溢出63次,所以最长捕获值 = 63*65536 + 65535 = 4194303
* 注意:为了通用,我们默认ARR和CCRy都是16位寄存器,对于32位的定时器(如:TIM5),也只按16位使用
* 按1us的计数频率,最长溢出时间为:4194303 us, 约4.19秒
*
* (说明一下:正常32位定时器来说,1us计数器加1,溢出时间:4294秒)
*/
uint8_t g_timxchy_cap_sta = 0; /* 输入捕获状态 */
uint16_t g_timxchy_cap_val = 0; /* 输入捕获值 */

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* @brief 通用定时器TIMX 通道Y 输入捕获 初始化函数
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值
* @param psc: 时钟预分频数
* @retval 无
*/
void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
{
TIM_IC_InitTypeDef timx_ic_cap_chy = {0};

g_timx_cap_chy_handle.Instance = GTIM_TIMX_CAP; /* 定时器5 */
g_timx_cap_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_cap_chy_handle.Init.Period = arr; /* 自动重装载值 */
HAL_TIM_IC_Init(&g_timx_cap_chy_handle);

timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING; /* 上升沿捕获 */
timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI; /* 映射到TI1上 */
timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1; /* 配置输入分频,不分频 */
timx_ic_cap_chy.ICFilter = 0; /* 配置输入滤波器,不滤波 */
HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy, GTIM_TIMX_CAP_CHY); /* 配置TIM5通道1 */

__HAL_TIM_ENABLE_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE); /* 使能更新中断 */
HAL_TIM_IC_Start_IT(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY); /* 开始捕获TIM5的通道1 */
}

/**
* @brief 通用定时器输入捕获初始化接口
HAL库调用的接口,用于配置不同的输入捕获
* @param htim:定时器句柄
* @note 此函数会被HAL_TIM_IC_Init()调用
* @retval 无
*/
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == GTIM_TIMX_CAP) /*输入通道捕获*/
{
GPIO_InitTypeDef gpio_init_struct;
GTIM_TIMX_CAP_CHY_CLK_ENABLE(); /* 使能TIMx时钟 */
GTIM_TIMX_CAP_CHY_GPIO_CLK_ENABLE(); /* 开启捕获IO的时钟 */

gpio_init_struct.Pin = GTIM_TIMX_CAP_CHY_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; /* 复用输入 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GTIM_TIMX_CAP_CHY_GPIO_PORT, &gpio_init_struct);

HAL_NVIC_SetPriority(GTIM_TIMX_CAP_IRQn, 1, 3); /* 抢占1,子优先级3 */
HAL_NVIC_EnableIRQ(GTIM_TIMX_CAP_IRQn); /* 开启ITMx中断 */
}
}

中断服务函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
* @brief 定时器中断服务函数
* @param 无
* @retval 无
*/
void GTIM_TIMX_CAP_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_cap_chy_handle); /* 定时器HAL库共用处理函数 */
}

/**
* @brief 定时器输入捕获中断处理回调函数
* @param htim:定时器句柄指针
* @note 该函数在HAL_TIM_IRQHandler中会被调用
* @retval 无
*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == GTIM_TIMX_CAP)
{
if ((g_timxchy_cap_sta & 0X80) == 0) /* 还未成功捕获 */
{
if (g_timxchy_cap_sta & 0X40) /* 捕获到一个下降沿 */
{
g_timxchy_cap_sta |= 0X80; /* 标记成功捕获到一次高电平脉宽 */
g_timxchy_cap_val = HAL_TIM_ReadCapturedValue(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY); /* 获取当前的捕获值 */
TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY); /* 一定要先清除原来的设置 */
TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY, TIM_ICPOLARITY_RISING); /* 配置TIM5通道1上升沿捕获 */
}
else /* 还未开始,第一次捕获上升沿 */
{
g_timxchy_cap_sta = 0; /* 清空 */
g_timxchy_cap_val = 0;
g_timxchy_cap_sta |= 0X40; /* 标记捕获到了上升沿 */
__HAL_TIM_SET_COUNTER(&g_timx_cap_chy_handle, 0); /* 定时器5计数器清零 */
TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY); /* 一定要先清除原来的设置!! */
TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY, TIM_ICPOLARITY_FALLING); /* 定时器5通道1设置为下降沿捕获 */
}
}
}
}

/**
* @brief 定时器更新中断回调函数
* @param htim:定时器句柄指针
* @note 此函数会被定时器中断函数共同调用的
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == GTIM_TIMX_CAP)
{
if ((g_timxchy_cap_sta & 0X80) == 0) /* 还未成功捕获 */
{
if (g_timxchy_cap_sta & 0X40) /* 已经捕获到高电平了 */
{
if ((g_timxchy_cap_sta & 0X3F) == 0X3F) /* 高电平太长了 */
{
TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY); /* 一定要先清除原来的设置 */
TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY, TIM_ICPOLARITY_RISING);/* 配置TIM5通道1上升沿捕获 */
g_timxchy_cap_sta |= 0X80; /* 标记成功捕获了一次 */
g_timxchy_cap_val = 0XFFFF;
}
else /* 累计定时器溢出次数 */
{
g_timxchy_cap_sta++;
}
}
}
}
}

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int main(void)
{
uint32_t temp = 0;
uint8_t t = 0;

HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_cap_chy_init(0XFFFF, 72 - 1); /* 以1Mhz的频率计数 捕获 */
printf("Start:\n");

while (1)
{
if (g_timxchy_cap_sta & 0X80) /* 成功捕获到了一次高电平 */
{
temp = g_timxchy_cap_sta & 0X3F;
temp *= 65536; /* 溢出时间总和 */
temp += g_timxchy_cap_val; /* 得到总的高电平时间 */
printf("HIGH:%d us\r\n", temp); /* 打印总的高点平时间 */
g_timxchy_cap_sta = 0; /* 开启下一次捕获*/
}

t++;

if (t > 20) /* 200ms进入一次 */
{
t = 0;
LED0_TOGGLE(); /* LED0闪烁 ,提示程序运行 */
}
delay_ms(10);
}
}

输入捕获测PA6脉冲数

宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* TIMX 计数输入引脚
* 正点原子计数输入使用定时器TIM3_CH2,接PA6引脚
* 默认选择了TIM3, 只有CH1和CH2通道可以用于计数输入, CH3/CH4不支持!
* 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,CH1/CH2对应IO口可以复用为其它功能
* 非常重要注意:默认选的是PA7,如果选了其它IO,相应引脚的复用方式也得改!
*/
#define GTIM_TIMX_CNT_CHY_GPIO_PORT GPIOA
#define GTIM_TIMX_CNT_CHY_GPIO_PIN GPIO_PIN_6
#define GTIM_TIMX_CNT_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */

#define GTIM_TIMX_CNT TIM3
#define GTIM_TIMX_CNT_IRQn TIM3_IRQn
#define GTIM_TIMX_CNT_IRQHandler TIM3_IRQHandler
#define GTIM_TIMX_CNT_CHY TIM_CHANNEL_1 /* 通道Y, 1<= Y <=2 */
#define GTIM_TIMX_CNT_InputTrigger TIM_TS_TI1FP1
#define GTIM_TIMX_CNT_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0) /* TIM3 时钟使能 */

全局变量

1
2
3
4
TIM_HandleTypeDef g_timx_cnt_chy_handle;        /* 定时器x句柄 */

/* 记录定时器计数器的溢出次数, 方便计算总脉冲个数 */
uint32_t g_timxchy_cnt_ofcnt = 0 ; /* 计数溢出次数 */

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* @brief 通用定时器TIMX 通道Y 脉冲计数 初始化函数
* @note
* 本函数选择通用定时器的时钟选择: 外部时钟源模式1(SMS[2:0] = 111)
* 这样CNT的计数时钟源就来自 TIMX_CH1/CH2, 可以实现外部脉冲计数(脉冲接入CH1/CH2)
*
* 时钟分频数 = psc, 一般设置为0, 表示每一个时钟都会计数一次, 以提高精度.
* 通过读取CNT和溢出次数, 经过简单计算, 可以得到当前的计数值, 从而实现脉冲计数
*
* @param arr: 自动重装值
* @retval 无
*/
void gtim_timx_cnt_chy_init(uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
TIM_SlaveConfigTypeDef tim_slave_config = {0};
GTIM_TIMX_CNT_CHY_CLK_ENABLE(); /* 使能TIMx时钟 */
GTIM_TIMX_CNT_CHY_GPIO_CLK_ENABLE(); /* 开启GPIOA时钟 */

g_timx_cnt_chy_handle.Instance = GTIM_TIMX_CNT; /* 定时器x */
g_timx_cnt_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_cnt_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_cnt_chy_handle.Init.Period = 65535; /* 自动重装载值 */
HAL_TIM_IC_Init(&g_timx_cnt_chy_handle);

gpio_init_struct.Pin = GTIM_TIMX_CNT_CHY_GPIO_PIN; /* 输入捕获的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; /* 复用输入 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GTIM_TIMX_CNT_CHY_GPIO_PORT, &gpio_init_struct);

/* 从模式:外部触发模式1 */
tim_slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1; /* 从模式:外部触发模式1 */
tim_slave_config.InputTrigger = GTIM_TIMX_CNT_InputTrigger; /* 输入触发:选择 TI1FP1(TIMX_CH1) 作为输入源 */
tim_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING; /* 触发极性:上升沿 */
tim_slave_config.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1; /* 触发预分频:无 */
tim_slave_config.TriggerFilter = 0x0; /* 滤波:*/
HAL_TIM_SlaveConfigSynchro(&g_timx_cnt_chy_handle, &tim_slave_config);

HAL_NVIC_SetPriority(GTIM_TIMX_CNT_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(GTIM_TIMX_CNT_IRQn);

__HAL_TIM_ENABLE_IT(&g_timx_cnt_chy_handle, TIM_IT_UPDATE); /* 使能更新中断 */
HAL_TIM_IC_Start(&g_timx_cnt_chy_handle, GTIM_TIMX_CNT_CHY); /* 开始捕获TIMx的通道y */
}

中断函数

1
2
3
4
5
6
7
8
9
10
void GTIM_TIMX_CNT_IRQHandler(void)
{
/* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
if(__HAL_TIM_GET_FLAG(&g_timx_cnt_chy_handle, TIM_FLAG_UPDATE) != RESET)
{
g_timxchy_cnt_ofcnt++; /* 累计溢出次数 */
}

__HAL_TIM_CLEAR_IT(&g_timx_cnt_chy_handle, TIM_IT_UPDATE);
}

其他函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @brief 通用定时器TIMX 通道Y 获取当前计数值
* @param 无
* @retval 当前计数值
*/
uint32_t gtim_timx_cnt_chy_get_count(void)
{
uint32_t count = 0;
count = g_timxchy_cnt_ofcnt * 65536; /* 计算溢出次数对应的计数值 */
count += __HAL_TIM_GET_COUNTER(&g_timx_cnt_chy_handle); /* 加上当前CNT的值 */
return count;
}

/**
* @brief 通用定时器TIMX 通道Y 重启计数器
* @param 无
* @retval 当前计数值
*/
void gtim_timx_cnt_chy_restart(void)
{
__HAL_TIM_DISABLE(&g_timx_cnt_chy_handle); /* 关闭定时器TIMX */
g_timxchy_cnt_ofcnt = 0; /* 累加器清零 */
__HAL_TIM_SET_COUNTER(&g_timx_cnt_chy_handle, 0); /* 计数器清零 */
__HAL_TIM_ENABLE(&g_timx_cnt_chy_handle); /* 使能定时器TIMX */
}

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int main(void)
{
uint32_t curcnt = 0;
uint32_t oldcnt = 0;
uint8_t key = 0;
uint8_t t = 0;

HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
gtim_timx_cnt_chy_init(0); /* 定时器计数初始化, 不分频 */
gtim_timx_cnt_chy_restart(); /* 重启计数 */

while (1)
{
key = key_scan(0); /* 扫描按键 */

if (key == KEY0_PRES) /* KEY0按键按下,重启计数 */
{
gtim_timx_cnt_chy_restart(); /* 重新启动计数 */
}

curcnt = gtim_timx_cnt_chy_get_count(); /* 获取计数值 */

if (oldcnt != curcnt)
{
oldcnt = curcnt;
printf("CNT:%d\r\n", oldcnt); /* 打印脉冲个数 */
}

t++;

if (t > 20) /* 200ms进入一次 */
{
t = 0;
LED0_TOGGLE(); /* LED0闪烁 ,提示程序运行 */
}

delay_ms(10);
}
}