学习STM32 HAL库定时器的基础知识
目录
一、定时器概述
1.1、软件定时原理
1.2、定时器定时原理
1.3、STM32定时器分类
1.4、STM32定时器特性表
1.5、STM32基本、通用、高级定时器的功能整体区别
二、基本定时器(TIM6和TIM7)
2.1、基本定时器简介
2.2、基本定时器框图
2.3、定时器计数模式及溢出条件
2.3.1、递增计数模式
2.3.2、递减计数模式
2.3.3、中心对齐模式
2.4、定时器中断实验相关寄存器
2.4.1、控制寄存器1(TIMx_CR1)
2.4.2、DMA/中断使能寄存器(TIMx_DIER)
2.4.3、状态寄存器(TIMx_SR)
2.4.4、计数器(TIMx_CNT)
2.4.5、预分频器(TIMx_PSC)
2.4.6、自动重装载寄存器(TIMx_ARR)
2.5、定时器溢出时间计算方法
2.6、定时器中断实验配置步骤
2.7、编程实战:定时器中断实验
三、通用定时器
3.1、通用定时器简介
3.2、通用定时器框图
3.3、计数器时钟源
3.3.1、计数器时钟源寄存器设置方法(F4)
3.3.2、外部时钟模式1
3.3.3、外部时钟模式2
3.3.4、使用一个定时器作为另一个定时器的预分频器
3.4、通用定时器中断实验
3.5、通用定时器PWM输出实验
3.5.1、通用定时器输出比较部分框图介绍
3.5.2、通用定时器输出PWM原理
3.5.3、PWM模式
3.5.4、通用定时器PWM输出实验配置步骤
3.5.5、编程实战:通用定时器PWM输出实验
3.6、通用定时器输入捕获实验
3.6.1、通用定时器输入捕获部分框图介绍
3.6.2、通用定时器输入捕获脉宽测量原理
3.6.3、通用定时器输入捕获实验配置步骤
3.6.4、编程实战:通用定时器输入捕获实验
3.7、通用定时器脉冲计数实验
3.7.1、脉冲计数实验原理
3.7.2、通用定时器脉冲计数实验配置步骤
3.7.3、编程实战:通用定时器脉冲计数实验
四、高级定时器
4.1、高级定时器简介
4.2、高级定时器框图
4.3、高级定时器输出指定个数PWM实验
4.3.1、重复计数器特性
4.3.2、高级定时器输出指定个数PWM实验原理
4.3.3、高级定时器输出指定个数PWM实验配置步骤
4.3.4、编程实战:高级定时器输出指定个数PWM实验
4.4、高级定时器输出比较模式实验
4.4.1、高级定时器输出比较模式实验原理
4.4.2、高级定时器输出比较模式实验配置步骤
4.4.3、编程实战:高级定时器输出比较模式实验
4.5、高级定时器互补输出带死区控制实验
4.5.1、互补输出,还带死区控制,什么意思?
4.5.2、带死区控制的互补输出应用之H桥
4.5.3、捕获/比较通道的输出部分(通道1至3)
4.5.4、死区时间计算
4.5.5、刹车(断路)功能
4.5.6、高级定时器互补输出带死区控制实验配置步骤
4.5.7、编程实战:高级定时器互补输出带死区控制实验
4.6、高级定时器PWM输入模式实验
4.6.1、PWM输入模式工作原理
4.6.2、PWM输入模式时序
4.6.3、高级定时器PWM输入模式实验配置步骤
4.6.4、编程实战:高级定时器PWM输入模式实验
一、定时器概述
1.1、软件定时原理
使用纯软件(CPU 死等)的方式实现定时(延时)功能
void delay_us(uint32_t us)
{
us *= 72;
while(us--);
}
缺点
1、延时不精准
2、CPU 死等
1.2、定时器定时原理
使用精准的时基,通过硬件的方式,实现定时功能
定时器核心就是计数器
1.3、STM32定时器分类
1.4、STM32定时器特性表
F1
H7
1.5、STM32基本、通用、高级定时器的功能整体区别
二、基本定时器(TIM6和TIM7)
2.1、基本定时器简介
基本定时器为 TIM6 和 TIM7,主要特性有:
16 位递增计数器(计数值:0~65535) |
16 位预分频器(分频系数:1~65536) |
可用于触发 DAC |
在更新事件(计数器溢出)时,会产生 中断/DMA 请求 |
2.2、基本定时器框图
① 时钟源
② 控制器
③ 计数器(时基单元)
影子寄存器是实际起作用的寄存器,不可直接访问
2.3、定时器计数模式及溢出条件
2.3.1、递增计数模式
递增计数模式实例说明
当 PSC=1,ARR=36 时有:
2.3.2、递减计数模式
递减计数模式实例说明
当 PSC=1,ARR=36 时有:
2.3.3、中心对齐模式
中心对齐模式实例说明
当 PSC=0,ARR=6 时有:
2.4、定时器中断实验相关寄存器
2.4.1、控制寄存器1(TIMx_CR1)
用于设置 ARR 寄存器是否具有缓冲,使能/关闭计数器
2.4.2、DMA/中断使能寄存器(TIMx_DIER)
用于使能更新中断
2.4.3、状态寄存器(TIMx_SR)
用于判断是否发生了更新中断,由硬件置 1,软件清零
2.4.4、计数器(TIMx_CNT)
计数器实时数值,可用于设置计时器初始值,范围:0~65535
2.4.5、预分频器(TIMx_PSC)
用于设置预分频系数,范围:0~65535,实际预分频系数等于 PSC+1
2.4.6、自动重装载寄存器(TIMx_ARR)
用于设置自动重装载值,范围:0~65535
2.5、定时器溢出时间计算方法
定时器溢出时间计算公式
是定时器溢出时间
是定时器的时钟源频率
是自动重装载寄存器的值
是预分频器寄存器的值
2.6、定时器中断实验配置步骤
1、配置定时器基础工作参数:使用 HAL_TIM_Base_Init()
2、定时器基础 MSP 初始化:使用 HAL_TIM_Base_MspInit(),配置 NVIC、CLOCK 等
3、使能更新中断并启动计数器:使用 HAL_TIM_Base_Start_IT()
4、设置优先级,使能中断:使用 HAL_NVIC_SetPriority()、 HAL_NVIC_EnableIRQ()
5、编写中断服务函数:使用 TIMx_IRQHandler() 等 —> HAL_TIM_IRQHandler()
6、编写定时器更新中断回调函数:HAL_TIM_PeriodElapsedCallback()
相关 HAL 库函数介绍
关键结构体介绍
typedef struct
{
TIM_TypeDef *Instance; /* 外设寄存器基地址 */
TIM_Base_InitTypeDef Init; /* 定时器初始化结构体*/
...
}TIM_HandleTypeDef;
typedef struct
{
uint32_t Prescaler; /* 预分频系数 */
uint32_t CounterMode; /* 计数模式 */
uint32_t Period; /* 自动重载值 ARR */
uint32_t ClockDivision; /* 时钟分频因子 */
uint32_t RepetitionCounter; /* 重复计数器寄存器的值 */
uint32_t AutoReloadPreload; /* 自动重载预装载使能 */
} TIM_Base_InitTypeDef;
2.7、编程实战:定时器中断实验
使用定时器 6,实现 500ms 定时器更新中断,在中断里翻转 LED0
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/btim.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
btim_timx_int_init(5000 - 1, 8400 - 1); /* 84 000 000 / 84 00 = 10 000 10Khz的计数频率,计数5K次为500ms */
while (1)
{
delay_ms(500);
}
}
btim.c
#include "./BSP/TIMER/btim.h"
TIM_HandleTypeDef g_timx_handler; /* 定时器参数句柄 */
/**
* @brief 基本定时器TIMX定时中断初始化函数
* @note
* 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 基本定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
* 定时器溢出时间计算方法: 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_handler.Instance = BTIM_TIMX_INT; /* 定时器x */
g_timx_handler.Init.Prescaler = psc; /* 分频 */
g_timx_handler.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_handler.Init.Period = arr; /* 自动装载值 */
HAL_TIM_Base_Init(&g_timx_handler);
HAL_TIM_Base_Start_IT(&g_timx_handler); /* 使能定时器x和定时器更新中断 */
}
/* 定时器底层驱动,开启时钟,设置中断优先级 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == BTIM_TIMX_INT)
{
BTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIMx时钟 */
HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 1, 3); /* 抢占1,子优先级3 */
HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn); /* 开启ITMx中断 */
}
}
/* 基本定时器TIMX中断服务函数 */
void BTIM_TIMX_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_handler); /* 定时器回调函数 */
}
/* 回调函数,定时器中断服务函数调用 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == BTIM_TIMX_INT)
{
LED1_TOGGLE(); /* LED1反转 */
}
}
btim.h
#ifndef _BTIM_H
#define _BTIM_H
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
/******************************************************************************************/
/* 基本定时器 定义 */
/* TIMX 中断定义
* 默认是针对TIM6/TIM7
* 注意: 通过修改这4个宏定义,可以支持TIM1~TIM8任意一个定时器.
*/
#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 时钟使能 */
/******************************************************************************************/
void btim_timx_int_init(uint16_t arr, uint16_t psc); /* 基本定时器 定时中断初始化函数 */
#endif
三、通用定时器
3.1、通用定时器简介
通用定时器为 TIM2 到 TIM5(在 F4 中 TIM9 到 TIM14 为阉割版通用定时器),主要特性有:
16 位递增、递减、中心对齐计数器(计数值:0~65535) |
16 位预分频器(分频系数:1~65536) |
可用于触发 DAC、ADC |
在更新事件、触发事件、输入捕获、输出比较时,会产生 中断/DMA 请求 |
4 个独立通道,可用于:输入捕获、输出比较、输出 PWM、单脉冲模式 |
使用外部信号控制定时器且可实现多个定时器互连的同步电路 |
支持编码器和霍尔传感器电路等 |
3.2、通用定时器框图
① 时钟源
② 控制器
③ 时基单元
④ 输入捕获
⑤ 捕获/比较(公共)
⑥ 输出比较
3.3、计数器时钟源
① 内部时钟(CK_INT),来自外设总线APB提供的时钟
② 外部时钟模式1:外部输入引脚(TIx),来自定时器通道1或者通道2引脚的信号
③ 外部时钟模式2:外部触发输入(ETR),来自可以复用为 TIMx_ETR 的 IO 引脚
④ 内部触发输入(ITRx),用于与芯片内部其它通用/高级定时器级联
3.3.1、计数器时钟源寄存器设置方法(F4)
3.3.2、外部时钟模式1
3.3.3、外部时钟模式2
3.3.4、使用一个定时器作为另一个定时器的预分频器
3.4、通用定时器中断实验
与基本定时器不同点:基本定时器只能递增计数,而通用定时器计数模式有三种
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/gtim.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_int_init(5000 - 1, 8400 - 1); /* 84 000 000 / 84 00 = 10 000 10Khz的计数频率,计数5K次为500ms */
while (1)
{
delay_ms(500);
}
}
gtim.c
#include "./BSP/TIMER/gtim.h"
TIM_HandleTypeDef g_timx_handle; /* 定时器x句柄 */
/**
* @brief 通用定时器TIMX定时中断初始化函数
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
* 定时器溢出时间计算方法: 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更新中断 */
}
/* 定时器中断服务函数 */
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); /* 清除定时器溢出中断标志位 */
}
}
gtim.h
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
/****************************************************************************************************/
/* 通用定时器 定义 */
/**
* 默认是针对TIM2~TIM5.
* 注意: 通过修改这4个宏定义,可以支持TIM1~TIM8任意一个定时器.
*/
#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 时钟使能 */
/****************************************************************************************************/
void gtim_timx_int_init(uint16_t arr, uint16_t psc); /* 通用定时器 定时中断初始化函数 */
#endif
3.5、通用定时器PWM输出实验
3.5.1、通用定时器输出比较部分框图介绍
捕获/比较通道1的主电路—输出部分
捕获/比较通道的输出部分(通道1)
3.5.2、通用定时器输出PWM原理
假设:递增计数模式
ARR:自动重装载寄存器的值
CCRx:捕获/比较寄存器x的值
当 CNT < CCRx,IO 输出0
当 CNT >= CCRx,IO 输出1
总结:PWM 波周期或频率由 ARR 决定,PWM 波占空比由 CCRx 决定
3.5.3、PWM模式
PWM模式1
递增:CNT < CCRx,输出有效电平
CNT >= CCRx,输出无效电平
递减:CNT > CCRx,输出无效电平
CNT <= CCRx,输出有效
有/无效状态由 TIMx_CCER 决定
CCxP=0:OCx高电平有效
CCxP=1:Ocx低电平有效
PWM模式2
递增:CNT < CCRx,输出无效电平
CNT >= CCRx,输出有效电平
递减:CNT > CCRx,输出有效电平
CNT <= CCRx,输出无效
有/无效状态由 TIMx_CCER 决定
CCxP=0:OCx高电平有效
CCxP=1:Ocx低电平有效
3.5.4、通用定时器PWM输出实验配置步骤
1、配置定时器基础工作参数:使用 HAL_TIM_PWM_Init()
2、定时器 PWM 输出 MSP 初始化:使用 HAL_TIM_PWM_MspInit(),配置 NVIC、CLOCK、GPIO 等
3、配置 PWM 模式/比较值等:使用 HAL_TIM_PWM_ConfigChannel()
4、使能输出并启动计数器:使用 HAL_TIM_PWM_Start()
5、修改比较值控制占空比:使用 __HAL_TIM_SET_COMPARE()
6、使能通道预装载:使用 __HAL_TIM_ENABLE_OCxPRELOAD()
相关 HAL 库函数介绍
关键结构体介绍
typedef struct
{
uint32_t OCMode; /* 输出比较模式选择 */
uint32_t Pulse; /* 设置比较值 */
uint32_t OCPolarity; /* 设置输出比较极性 */
uint32_t OCNPolarity; /* 设置互补输出比较极性 */
uint32_t OCFastMode; /* 使能或失能输出比较快速模式 */
uint32_t OCIdleState; /* 空闲状态下OC1输出 */
uint32_t OCNIdleState; /* 空闲状态下OC1N输出 */
} TIM_OC_InitTypeDef;
3.5.5、编程实战:通用定时器PWM输出实验
通过定时器输出的 PWM 控制 LED0,实现类似手机呼吸灯的效果
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/gtim.h"
extern TIM_HandleTypeDef g_timx_pwm_chy_handle; /* 定时器x句柄 */
int main(void)
{
uint16_t ledrpwmval = 0;
uint8_t dir = 1;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_pwm_chy_init(500 - 1, 84 - 1); /* 84 000 000 / 84 = 1 000 000 1Mhz的计数频率,2Khz的PWM */
while (1)
{
delay_ms(2);
if (dir)
ledrpwmval++; /* dir==1 ledrpwmval递增 */
else
ledrpwmval--; /* dir==0 ledrpwmval递减 */
if (ledrpwmval > 400)
dir = 0; /* ledrpwmval到达400后,方向为递减 */
if (ledrpwmval == 0)
dir = 1; /* ledrpwmval递减到0后,方向改为递增 */
/* 修改比较值控制占空比 */
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY, ledrpwmval);
}
}
gtim.c
#include "./BSP/TIMER/gtim.h"
TIM_HandleTypeDef g_timx_pwm_chy_handle; /* 定时器x句柄 */
/**
* @brief 通用定时器TIMX 通道Y PWM输出 初始化函数(使用PWM模式1)
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
* 定时器溢出时间计算方法: 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}; /* 定时器输出句柄 */
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; /* 设置比较值,此值用来确定占空比 */
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通道 */
}
/* 定时器底层驱动,时钟使能,引脚配置 */
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; /* 高速 */
gpio_init_struct.Alternate = GTIM_TIMX_PWM_CHY_GPIO_AF; /* IO口REMAP设置, 是否必要查看头文件配置的说明! */
HAL_GPIO_Init(GTIM_TIMX_PWM_CHY_GPIO_PORT, &gpio_init_struct);
}
}
gtim.h
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
/*********************************以下是通用定时器PWM输出实验相关宏定义*************************************/
/* TIMX PWM输出定义
* 这里输出的PWM控制LED0(RED)的亮度
* 默认是针对TIM2~TIM5
* 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,任意一个IO口输出PWM
*/
#define GTIM_TIMX_PWM_CHY_GPIO_PORT GPIOF
#define GTIM_TIMX_PWM_CHY_GPIO_PIN GPIO_PIN_9
#define GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0) /* PF口时钟使能 */
#define GTIM_TIMX_PWM_CHY_GPIO_AF GPIO_AF9_TIM14 /* 端口复用到TIM14 */
/* TIMX REMAP设置
* 因为我们LED0接在PF9上, 必须通过开启TIM14的部分重映射功能, 才能将TIM14_CH1输出到PF9上
*/
#define GTIM_TIMX_PWM TIM14 /* TIM14 */
#define GTIM_TIMX_PWM_CHY TIM_CHANNEL_1 /* 通道Y, 1<= Y <=4 */
#define GTIM_TIMX_PWM_CHY_CCRX TIM14->CCR1 /* 通道Y的输出比较寄存器 */
#define GTIM_TIMX_PWM_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM14_CLK_ENABLE(); }while(0) /* TIM14 时钟使能 */
/****************************************************************************************************/
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc); /* 通用定时器 PWM初始化函数 */
#endif
3.6、通用定时器输入捕获实验
3.6.1、通用定时器输入捕获部分框图介绍
捕获/比较通道的输入部分(通道1)
捕获/比较通道1的主电路—输入部分
3.6.2、通用定时器输入捕获脉宽测量原理
以捕获测量高电平脉宽为例
假设:递增计数模式
ARR:自动重装载寄存器的值
CCRx1:t1 时间点 CCRx 的值
CCRx2:t2 时间点 CCRx 的值
高电平期间,计时器计数的个数:N * (ARR+1) + CCRx2
3.6.3、通用定时器输入捕获实验配置步骤
1、配置定时器基础工作参数:使用 HAL_TIM_IC_Init()
2、定时器输入捕获 MSP 初始化:使用 HAL_TIM_IC_MspInit(),配置 NVIC、CLOCK、GPIO 等
3、配置输入通道映射、捕获边沿等:使用 HAL_TIM_IC_ConfigChannel()
4、设置优先级、使能中断:使用 HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ()
5、使能定时器更新中断:使用 __HAL_TIM_ENABLE_IT()
6、使能捕获、捕获中断及计数器:使用 HAL_TIM_IC_Start_IT()
7、编写中断服务函数:使用 TIMx_IRQHandler() 等 —> HAL_TIM_IRQHandler()
8、编写更新中断和捕获回调函数:HAL_TIM_PeriodElapsedCallback()、HAL_TIM_IC_CaptureCallback()
相关 HAL 库函数介绍
关键结构体介绍
typedef struct
{
uint32_t ICPolarity; /* 输入捕获触发方式选择,比如上升、下降沿捕获 */
uint32_t ICSelection; /* 输入捕获选择,用于设置映射关系 */
uint32_t ICPrescaler; /* 输入捕获分频系数 */
uint32_t ICFilter; /* 输入捕获滤波器设置 */
} TIM_IC_InitTypeDef;
3.6.4、编程实战:通用定时器输入捕获实验
通过定时器 5 通道 1 来捕获按键高电平脉宽时间,通过串口打印出来
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/gtim.h"
extern uint8_t g_timxchy_cap_sta; /* 输入捕获状态 */
extern uint16_t g_timxchy_cap_val; /* 输入捕获值 */
int main(void)
{
uint32_t temp = 0;
uint8_t t = 1;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_timx_cap_chy_init(0xFFFF, 84 - 1); /* 以1Mhz的频率计数 捕获 */
while (1)
{
if (g_timxchy_cap_sta & 0x80) /* 成功捕获到了一次高电平 */
{
temp = g_timxchy_cap_sta & 0x3F;
temp *= 0xFFFF; /* 溢出时间总和 */
temp += g_timxchy_cap_val; /* 得到总的高电平时间 */
printf("HIGH:%.2f s, %.2f ms, %d us\r\n", temp / 1000000.0f, temp / 1000.0f, temp); /* 打印总的高电平时间 */
g_timxchy_cap_sta = 0; /* 开启下一次捕获 */
}
if (++t > 20) /* 200ms进入一次 */
{
t = 0;
LED0_TOGGLE(); /* LED0闪烁 ,提示程序运行 */
}
delay_ms(10);
}
}
gtim.c
#include "./BSP/TIMER/gtim.h"
TIM_HandleTypeDef g_timx_cap_chy_handle; /* 定时器x句柄 */
/**
* @brief 通用定时器TIMX 通道Y 输入捕获 初始化函数
* @note
* 通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
* 通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值
* @param psc: 预分频系数
* @retval 无
*/
void gtim_timx_cap_chy_init(uint32_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 */
}
/* 通用定时器输入捕获初始化接口 */
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_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
gpio_init_struct.Alternate = GTIM_TIMX_CAP_CHY_GPIO_AF; /* 复用为捕获TIM5的通道1 */
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中断 */
}
}
/* 输入捕获状态(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; /* 输入捕获值 */
/* 定时器中断服务函数 */
void GTIM_TIMX_CAP_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_cap_chy_handle); /* 定时器共用处理函数 */
}
/* 定时器输入捕获中断处理回调函数 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
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设置为下降沿捕获 */
}
}
}
/* 定时器更新中断回调函数 */
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++;
}
}
}
}
}
gtim.h
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
/*********************************以下是通用定时器输入捕获实验相关宏定义*************************************/
/* TIMX 输入捕获定义
* 这里的输入捕获使用定时器TIM5_CH1,捕获WK_UP按键的输入
* 默认是针对TIM2~TIM5.
* 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,任意一个IO口做输入捕获
* 特别要注意:默认用的PA0,设置的是下拉输入!如果改其他IO,对应的上下拉方式也得改!
*/
#define GTIM_TIMX_CAP_CHY_GPIO_PORT GPIOA
#define GTIM_TIMX_CAP_CHY_GPIO_PIN GPIO_PIN_0
#define GTIM_TIMX_CAP_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define GTIM_TIMX_CAP_CHY_GPIO_AF GPIO_AF2_TIM5 /* AF功能选择 */
#define GTIM_TIMX_CAP TIM5
#define GTIM_TIMX_CAP_IRQn TIM5_IRQn
#define GTIM_TIMX_CAP_IRQHandler TIM5_IRQHandler
#define GTIM_TIMX_CAP_CHY TIM_CHANNEL_1 /* 通道Y, 1<= Y <=4 */
#define GTIM_TIMX_CAP_CHY_CCRX TIM5->CCR1 /* 通道Y的输出比较寄存器 */
#define GTIM_TIMX_CAP_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM5_CLK_ENABLE(); }while(0) /* TIM5 时钟使能 */
/****************************************************************************************************/
void gtim_timx_cap_chy_init(uint32_t arr, uint16_t psc); /* 通用定时器 输入捕获初始化函数 */
#endif
3.7、通用定时器脉冲计数实验
3.7.1、脉冲计数实验原理
外部时钟模式1
3.7.2、通用定时器脉冲计数实验配置步骤
1、配置定时器基础工作参数:使用 HAL_TIM_IC_Init()
2、定时器输入捕获 MSP 初始化:使用 HAL_TIM_IC_MspInit(),配置 NVIC、CLOCK、GPIO 等
3、配置定时器从模式等:使用 HAL_TIM_SlaveConfigSynchro()
4、使能输入捕获并启动计数器:使用 HAL_TIM_IC_Start()
5、获取计数器的值:使用 __HAL_TIM_GET_COUNTER()
6、设置计数器的值:使用 __HAL_TIM_SET_COUNTER()
相关 HAL 库函数介绍
关键结构体介绍
typedef struct
{
uint32_t SlaveMode; /* 从模式选择 */
uint32_t InputTrigger; /* 输入触发源选择 */
uint32_t TriggerPolarity; /* 输入触发极性 */
uint32_t TriggerPrescaler; /* 输入触发预分频 */
uint32_t TriggerFilter; /* 输入滤波器设置 */
} TIM_SlaveConfigTypeDef;
3.7.3、编程实战:通用定时器脉冲计数实验
将定时器 2 通道 1 输入的高电平脉冲作为定时器 2 的时钟,并通过串口打印脉冲数
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/gtim.h"
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(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
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按键按下,重启计数 */
{
printf("key0 press \r\n");
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 > 40) /* 200ms进入一次 */
{
t = 0;
LED0_TOGGLE(); /* LED0闪烁, 提示程序运行 */
}
delay_ms(10);
}
}
gtim.c
#include "./BSP/TIMER/gtim.h"
TIM_HandleTypeDef g_timx_cnt_chy_handle; /* 定时器x句柄 */
/* 记录定时器计数器的溢出次数, 方便计算总脉冲个数 */
uint32_t g_timxchy_cnt_ofcnt = 0; /* 计数溢出次数 */
/**
* @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_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
gpio_init_struct.Alternate = GTIM_TIMX_CNT_CHY_GPIO_AF; /* 复用为捕获TIMx的通道 */
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 = TIM_TS_TI1FP1; /* 输入触发:选择 TI1FP1(TIMX_CH1) 作为输入源 */
tim_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING; /* 触发极性:上升沿 */
tim_slave_config.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1; /* 触发预分频:无 */
tim_slave_config.TriggerFilter = 0x0; /* 滤波:本例中不需要任何滤波 */
HAL_TIM_SlaveConfigSynchronization(&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); /* 开启ITMx中断 */
__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 */
}
/* 通用定时器TIMX 通道Y 获取当前计数值 */
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;
}
/* 通用定时器TIMX 通道Y 重启计数器 */
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 */
}
/* 通用定时器TIMX 脉冲计数 更新中断服务函数 */
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);
}
gtim.h
#ifndef __GTIM_H
#define __GTIM_H
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
/*********************************以下是通用定时器脉冲计数实验相关宏定义*************************************/
/* TIMX 输入计数定义
* 这里的输入计数使用定时器TIM2_CH1,捕获WK_UP按键的输入
* 默认是针对TIM2~TIM5, 只有CH1和CH2通道可以用做输入计数, CH3/CH4不支持!
* 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,CH1/CH2对应IO口做输入计数
* 特别要注意:默认用的PA0,设置的是下拉输入!如果改其他IO,对应的上下拉方式也得改!
*/
#define GTIM_TIMX_CNT_CHY_GPIO_PORT GPIOA
#define GTIM_TIMX_CNT_CHY_GPIO_PIN GPIO_PIN_0
#define GTIM_TIMX_CNT_CHY_GPIO_AF GPIO_AF1_TIM2 /* AF功能选择 */
#define GTIM_TIMX_CNT_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define GTIM_TIMX_CNT TIM2
#define GTIM_TIMX_CNT_IRQn TIM2_IRQn
#define GTIM_TIMX_CNT_IRQHandler TIM2_IRQHandler
#define GTIM_TIMX_CNT_CHY TIM_CHANNEL_1 /* 通道Y, 1<= Y <=2 */
#define GTIM_TIMX_CNT_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM2_CLK_ENABLE(); }while(0) /* TIM2 时钟使能 */
/****************************************************************************************************/
void gtim_timx_cnt_chy_init(uint16_t psc); /* 通用定时器 脉冲计数初始化函数 */
uint32_t gtim_timx_cnt_chy_get_count(void); /* 通用定时器 获取脉冲计数 */
void gtim_timx_cnt_chy_restart(void); /* 通用定时器 重启计数器 */
#endif
四、高级定时器
4.1、高级定时器简介
高级定时器为 TIM1 到 TIM8,主要特性有:
16 位递增、递减、中心对齐计数器(计数值:0~65535) |
16 位预分频器(分频系数:1~65536) |
可用于触发 DAC、ADC |
在更新事件、触发事件、输入捕获、输出比较时,会产生 中断/DMA 请求 |
4 个独立通道,可用于:输入捕获、输出比较、输出 PWM、单脉冲模式 |
使用外部信号控制定时器且可实现多个定时器互连的同步电路 |
支持编码器和霍尔传感器电路等 |
重复计数器 |
死区时间带可编程的互补输出 |
断路输入,用于将定时器的输出信号置于用户可选的安全配置中 |
4.2、高级定时器框图
4.3、高级定时器输出指定个数PWM实验
4.3.1、重复计数器特性
计数器每次上溢或下溢都能使重复计数器减 1,减到 0 时,再发生一次溢出就会产生更新事件
如果设置 RCR 为 N,更新事件将在 N+1 次溢出时发生
4.3.2、高级定时器输出指定个数PWM实验原理
1,配置边沿对齐模式输出 PWM
2,指定输出 N 个 PWM,则把 N-1 写入 RCR
3,在更新中断内,关闭计数器
注意:高级定时器通道输出必须把 MOE 位置 1
4.3.3、高级定时器输出指定个数PWM实验配置步骤
1、配置定时器基础工作参数:使用 HAL_TIM_PWM_Init()
2、定时器 PWM 输出 MSP 初始化:使用 HAL_TIM_PWM_MspInit(),配置 NVIC、CLOCK、GPIO 等
3、配置 PWM 模式/比较值等:使用 HAL_TIM_PWM_ConfigChannel()
4、设置优先级、使能中断:使用 HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ()
5、使能定时器更新中断:使用 __HAL_TIM_ENABLE_IT()
6、使能输出、主输出、计数器:使用 HAL_TIM_PWM_Start()
7、编写中断服务函数:使用 TIMx_IRQHandler() 等 —> HAL_TIM_IRQHandler()
8、编写更新中断回调函数:HAL_TIM_PeriodElapsedCallback()
相关 HAL 库函数介绍
关键结构体介绍
typedef struct
{
uint32_t OCMode; /* 输出比较模式选择 */
uint32_t Pulse; /* 设置比较值 */
uint32_t OCPolarity; /* 设置输出比较极性 */
uint32_t OCNPolarity; /* 设置互补输出比较极性 */
uint32_t OCFastMode; /* 使能或失能输出比较快速模式 */
uint32_t OCIdleState; /* 空闲状态下OC1输出 */
uint32_t OCNIdleState; /* 空闲状态下OC1N输出 */
} TIM_OC_InitTypeDef;
4.3.4、编程实战:高级定时器输出指定个数PWM实验
通过定时器 8 通道 1(PC6)实现指定个数 PWM 输出,用于控制 LED1 的亮灭
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/atim.h"
int main(void)
{
uint8_t key = 0;
uint8_t t = 0;
GPIO_InitTypeDef gpio_init_struct;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
/* 将 LED1 引脚设置为输入模式, 避免和 PF10 冲突 */
gpio_init_struct.Pin = LED1_GPIO_PIN; /* LED1引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 设置为输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速模式 */
HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct); /* 初始化LED1引脚 */
atim_timx_npwm_chy_init(10000 - 1, 8400 - 1); /* 20Khz的计数频率,2Hz的PWM频率. */
ATIM_TIMX_NPWM_CHY_CCRX = 5000; /* 设置PWM占空比,50%,这样可以控制每一个PWM周期,LED1(GREEN)
* 有一半时间是亮的,一半时间是灭的,LED1亮灭一次,表示一个PWM波
*/
atim_timx_npwm_chy_set(5); /* 输出5个PWM波(控制LED1(GREEN)闪烁5次) */
while (1)
{
key = key_scan(0);
if (key == KEY0_PRES) /* KEY0按下 */
{
atim_timx_npwm_chy_set(5); /* 输出5个PWM波(控制TIM8_CH1, 即PC6输出5个脉冲) */
}
t++;
delay_ms(10);
if (t > 50) /* 控制LED0闪烁, 提示程序运行状态 */
{
t = 0;
LED0_TOGGLE();
}
}
}
atim.c
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef g_timx_npwm_chy_handle; /* 定时器x句柄 */
/* g_npwm_remain表示当前还剩下多少个脉冲要发送
* 每次最多发送256个脉冲
*/
static uint32_t g_npwm_remain = 0;
/**
* @brief 高级定时器TIMX 通道Y 输出指定个数PWM 初始化函数
* @note
* 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
* 高级定时器时钟 = 168Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值
* @param psc: 预分频系数
* @retval 无
*/
void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
TIM_OC_InitTypeDef timx_oc_npwm_chy = {0}; /* 定时器输出 */
ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE(); /* TIMX 通道IO口时钟使能 */
ATIM_TIMX_NPWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
g_timx_npwm_chy_handle.Instance = ATIM_TIMX_NPWM; /* 定时器x */
g_timx_npwm_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_npwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_npwm_chy_handle.Init.Period = arr; /* 自动重装载值 */
g_timx_npwm_chy_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能TIMx_ARR进行缓冲 */
g_timx_npwm_chy_handle.Init.RepetitionCounter = 0; /* 重复计数器初始值 */
HAL_TIM_PWM_Init(&g_timx_npwm_chy_handle); /* 初始化PWM */
gpio_init_struct.Pin = ATIM_TIMX_NPWM_CHY_GPIO_PIN; /* 通道y的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推完输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = ATIM_TIMX_NPWM_CHY_GPIO_AF; /* 端口复用 */
HAL_GPIO_Init(ATIM_TIMX_NPWM_CHY_GPIO_PORT, &gpio_init_struct);
timx_oc_npwm_chy.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM 1 */
timx_oc_npwm_chy.Pulse = arr / 2; /* 设置比较值,此值用来确定占空比 */
/* 这里默认设置比较值为自动重装载值的一半,即占空比为50% */
timx_oc_npwm_chy.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
HAL_TIM_PWM_ConfigChannel(&g_timx_npwm_chy_handle, &timx_oc_npwm_chy, ATIM_TIMX_NPWM_CHY); /* 配置TIMx通道y */
HAL_NVIC_SetPriority(ATIM_TIMX_NPWM_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(ATIM_TIMX_NPWM_IRQn); /* 开启ITMx中断 */
__HAL_TIM_ENABLE_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE); /* 允许更新中断 */
HAL_TIM_PWM_Start(&g_timx_npwm_chy_handle, ATIM_TIMX_NPWM_CHY); /* 开启对应PWM通道 */
}
/* 高级定时器TIMX NPWM设置PWM个数, 1~2^32次方个 */
void atim_timx_npwm_chy_set(uint32_t npwm)
{
if (npwm == 0)
return;
g_npwm_remain = npwm; /* 保存脉冲个数 */
HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE); /* 产生一次更新事件,在中断里面处理脉冲输出 */
__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); /* 使能定时器TIMX */
}
/* 高级定时器TIMX NPWM中断服务函数 */
void ATIM_TIMX_NPWM_IRQHandler(void)
{
uint16_t npwm = 0;
/* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
if (__HAL_TIM_GET_FLAG(&g_timx_npwm_chy_handle, TIM_FLAG_UPDATE) != RESET)
{
if (g_npwm_remain >= 256) /* 还有大于256个脉冲需要发送 */
{
g_npwm_remain = g_npwm_remain - 256;
npwm = 256;
}
else if (g_npwm_remain % 256) /* 还有位数(不到256)个脉冲要发送 */
{
npwm = g_npwm_remain % 256;
g_npwm_remain = 0; /* 没有脉冲了 */
}
if (npwm) /* 有脉冲要发送 */
{
ATIM_TIMX_NPWM->RCR = npwm - 1; /* 设置重复计数寄存器值为npwm-1, 即npwm个脉冲 */
HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE); /* 产生一次更新事件,在中断里面处理脉冲输出 */
__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); /* 使能定时器TIMX */
}
else
{
ATIM_TIMX_NPWM->CR1 &= ~(1 << 0); /* 关闭定时器TIMX,使用HAL Disable会清除PWM通道信息,此处不用 */
}
__HAL_TIM_CLEAR_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE); /* 清除定时器溢出中断标志位 */
}
}
atim.h
#ifndef __ATIM_H
#define __ATIM_H
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
/******************************************************************************************/
/* 高级定时器 定义 */
/* TIMX 输出指定个数PWM 定义
* 这里输出的PWM通过PC6(TIM8_CH1)输出, 我们用杜邦线连接PC6和PF10, 然后在程序里面将PF10设置成浮空输入
* 就可以 看到TIM8_CH1控制LED1(GREEN)的亮灭, 亮灭一次表示一个PWM波
* 默认使用的是TIM8_CH1.
* 注意: 通过修改这几个宏定义, 可以支持TIM1/TIM8定时器, 任意一个IO口输出指定个数的PWM
*/
#define ATIM_TIMX_NPWM_CHY_GPIO_PORT GPIOC
#define ATIM_TIMX_NPWM_CHY_GPIO_PIN GPIO_PIN_6
#define ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_NPWM_CHY_GPIO_AF GPIO_AF3_TIM8
#define ATIM_TIMX_NPWM TIM8
#define ATIM_TIMX_NPWM_IRQn TIM8_UP_TIM13_IRQn
#define ATIM_TIMX_NPWM_IRQHandler TIM8_UP_TIM13_IRQHandler
#define ATIM_TIMX_NPWM_CHY TIM_CHANNEL_1 /* 通道Y, 1<= Y <=4 */
#define ATIM_TIMX_NPWM_CHY_CCRX TIM8->CCR1 /* 通道Y的输出比较寄存器 */
#define ATIM_TIMX_NPWM_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0) /* TIM8 时钟使能 */
/******************************************************************************************/
void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc); /* 高级定时器 输出指定个数PWM初始化函数 */
void atim_timx_npwm_chy_set(uint32_t npwm); /* 高级定时器 设置输出PWM的个数 */
#endif
4.4、高级定时器输出比较模式实验
4.4.1、高级定时器输出比较模式实验原理
输出比较模式:翻转
当 CNT = CCRx,OCxREF 电平翻转
总结:PWM 波周期或频率由 ARR 决定,占空比固定 50%,相位由 CCRx 决定
4.4.2、高级定时器输出比较模式实验配置步骤
1、配置定时器基础工作参数:使用 HAL_TIM_OC_Init()
2、定时器输出比较 MSP 初始化:使用 HAL_TIM_OC_MspInit(),配置 NVIC、CLOCK、GPIO 等
3、配置输出比较模式等:使用 HAL_TIM_OC_ConfigChannel()
4、使能通道预装载:使用 __HAL_TIM_ENABLE_OCxPRELOAD()
5、使能输出、主输出、计数器:使用 HAL_TIM_OC_Start()
6、修改捕获/比较寄存器的值:使用 __HAL_TIM_SET_COMPARE()
相关 HAL 库函数介绍
关键结构体介绍
typedef struct
{
uint32_t OCMode; /* 输出比较模式选择 */
uint32_t Pulse; /* 设置比较值 */
uint32_t OCPolarity; /* 设置输出比较极性 */
uint32_t OCNPolarity; /* 设置互补输出比较极性 */
uint32_t OCFastMode; /* 使能或失能输出比较快速模式 */
uint32_t OCIdleState; /* 空闲状态下OC1输出 */
uint32_t OCNIdleState; /* 空闲状态下OC1N输出 */
} TIM_OC_InitTypeDef;
4.4.3、编程实战:高级定时器输出比较模式实验
通过定时器 8 通道 1/2/3/4 输出相位分别为 25%、50%、75%、100% 的 PWM
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/atim.h"
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
atim_timx_comp_pwm_init(1000 - 1, 168 - 1); /* 1Mhz的计数频率, 1Khz的周期. */
ATIM_TIMX_COMP_CH1_CCRX = 250 - 1; /* 通道1 相位25% */
ATIM_TIMX_COMP_CH2_CCRX = 500 - 1; /* 通道2 相位50% */
ATIM_TIMX_COMP_CH3_CCRX = 750 - 1; /* 通道3 相位75% */
ATIM_TIMX_COMP_CH4_CCRX = 1000 - 1; /* 通道4 相位100% */
while (1)
{
t++;
delay_ms(10);
if (t > 50) /* 控制LED0闪烁, 提示程序运行状态 */
{
t = 0;
LED0_TOGGLE();
}
}
}
atim.c
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef g_timx_comp_pwm_handle; /* 定时器x句柄 */
/**
* @brief 高级定时器TIMX 输出比较模式 初始化函数(使用输出比较模式)
* @note
* 配置高级定时器TIMX 4路输出比较模式PWM输出,实现50%占空比,不同相位控制
* 注意,本例程输出比较模式,每2个计数周期才能完成一个PWM输出,因此输出频率减半
* 另外,我们还可以开启中断在中断里面修改CCRx,从而实现不同频率/不同相位的控制
* 但是我们不推荐这么使用,因为这可能导致非常频繁的中断,从而占用大量CPU资源
*
* 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
* 高级定时器时钟 = 168Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值。
* @param psc: 预分频系数
* @retval 无
*/
void atim_timx_comp_pwm_init(uint16_t arr, uint16_t psc)
{
TIM_OC_InitTypeDef timx_oc_comp_pwm = {0};
g_timx_comp_pwm_handle.Instance = ATIM_TIMX_COMP; /* 定时器8 */
g_timx_comp_pwm_handle.Init.Prescaler = psc; /* 预分频系数 */
g_timx_comp_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
g_timx_comp_pwm_handle.Init.Period = arr; /* 自动重装载值 */
g_timx_comp_pwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 不使能影子寄存器TIMx_ARR */
HAL_TIM_OC_Init(&g_timx_comp_pwm_handle); /* 输出比较模式初始化 */
timx_oc_comp_pwm.OCMode = TIM_OCMODE_TOGGLE; /* 比较输出模式翻转功能 */
timx_oc_comp_pwm.Pulse = 250 - 1; /* 设置输出比较寄存器的值 */
timx_oc_comp_pwm.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_1); /* 初始化定时器的输出比较通道1 */
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_1); /* 通道1 预装载使能 */
timx_oc_comp_pwm.Pulse = 500 - 1;
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_2); /* 初始化定时器的输出比较通道2 */
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_2); /* 通道2 预装载使能 */
timx_oc_comp_pwm.Pulse = 750 - 1;
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_3); /* 初始化定时器的输出比较通道3 */
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_3); /* 通道3 预装载使能 */
timx_oc_comp_pwm.Pulse = 1000 - 1;
timx_oc_comp_pwm.OCIdleState = TIM_OCIDLESTATE_RESET;
HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_4); /* 初始化定时器的输出比较通道4 */
__HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_4); /* 通道4 预装载使能 */
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
}
/* 定时器底层驱动,时钟使能,引脚配置 */
void HAL_TIM_OC_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == ATIM_TIMX_COMP)
{
GPIO_InitTypeDef gpio_init_struct;
ATIM_TIMX_COMP_CLK_ENABLE(); /* 使能定时器时钟 */
ATIM_TIMX_COMP_CH1_GPIO_CLK_ENABLE();
ATIM_TIMX_COMP_CH2_GPIO_CLK_ENABLE();
ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE();
ATIM_TIMX_COMP_CH4_GPIO_CLK_ENABLE();
gpio_init_struct.Pin = ATIM_TIMX_COMP_CH1_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_NOPULL;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init_struct.Alternate = ATIM_TIMX_COMP_GPIO_AF;
HAL_GPIO_Init(ATIM_TIMX_COMP_CH1_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_COMP_CH2_GPIO_PIN;
HAL_GPIO_Init(ATIM_TIMX_COMP_CH2_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_COMP_CH3_GPIO_PIN;
HAL_GPIO_Init(ATIM_TIMX_COMP_CH3_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_COMP_CH4_GPIO_PIN;
HAL_GPIO_Init(ATIM_TIMX_COMP_CH4_GPIO_PORT, &gpio_init_struct);
}
}
atim.h
#ifndef __ATIM_H
#define __ATIM_H
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
/******************************************************************************************/
/* TIMX 输出比较模式 定义
* 这里通过TIM8的输出比较模式,控制PC6,PC7,PC8,PC9输出4路PWM,占空比50%,并且每一路PWM之间的相位差为25%
* 修改CCRx可以修改相位.
* 默认是针对TIM8
* 注意: 通过修改这些宏定义,可以支持TIM1/TIM8任意一个定时器,任意一个IO口使用输出比较模式,输出PWM
*/
#define ATIM_TIMX_COMP_CH1_GPIO_PORT GPIOC
#define ATIM_TIMX_COMP_CH1_GPIO_PIN GPIO_PIN_6
#define ATIM_TIMX_COMP_CH1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_COMP_CH2_GPIO_PORT GPIOC
#define ATIM_TIMX_COMP_CH2_GPIO_PIN GPIO_PIN_7
#define ATIM_TIMX_COMP_CH2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_COMP_CH3_GPIO_PORT GPIOC
#define ATIM_TIMX_COMP_CH3_GPIO_PIN GPIO_PIN_8
#define ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_COMP_CH4_GPIO_PORT GPIOC
#define ATIM_TIMX_COMP_CH4_GPIO_PIN GPIO_PIN_9
#define ATIM_TIMX_COMP_CH4_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_COMP_GPIO_AF GPIO_AF3_TIM8
#define ATIM_TIMX_COMP TIM8
#define ATIM_TIMX_COMP_CH1_CCRX ATIM_TIMX_COMP->CCR1 /* 通道1的输出比较寄存器 */
#define ATIM_TIMX_COMP_CH2_CCRX ATIM_TIMX_COMP->CCR2 /* 通道2的输出比较寄存器 */
#define ATIM_TIMX_COMP_CH3_CCRX ATIM_TIMX_COMP->CCR3 /* 通道3的输出比较寄存器 */
#define ATIM_TIMX_COMP_CH4_CCRX ATIM_TIMX_COMP->CCR4 /* 通道4的输出比较寄存器 */
#define ATIM_TIMX_COMP_CLK_ENABLE() do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0) /* TIM8 时钟使能 */
/******************************************************************************************/
void atim_timx_comp_pwm_init(uint16_t arr, uint16_t psc); /* 高级定时器 输出比较模式输出PWM 初始化函数 */
#endif
4.5、高级定时器互补输出带死区控制实验
4.5.1、互补输出,还带死区控制,什么意思?
互补输出 带死区控制的互补输出
互补输出:是指通过两个输出引脚同时输出相反电平信号。其中一个引脚输出高电平时,另一个引脚输出低电平;反之亦然。这种互补输出的设计可以用于控制电机驱动、PWM信号生成等应用场景
死区控制:PWM的上下桥臂的三极管是不能同时导通的。如果同时导通就会是电源两端短路。所以,两路触发信号要在一段时间内都是使三极管断开的。这个区域就叫做“死区”
4.5.2、带死区控制的互补输出应用之H桥
由于元器件是有延迟特性,所以需要加上死区时间控制
4.5.3、捕获/比较通道的输出部分(通道1至3)
4.5.4、死区时间计算
1、确定
的值:
2、判断
,选择计算公式
3、代入选择的公式计算
控制寄存器1 TIMx_CR1
断路和死区寄存器 TIMx_BDTR
举个栗子(F4为例):DTG[7:0]=250
250,即二进制:1111 1010,选第四条
DT = (32+26)*16*23.81ns=22.09568us
4.5.5、刹车(断路)功能
使能刹车功能:将 TIMx_BDTR的BKE 位置 1,刹车输入信号极性由 BKP 位设置
使能刹车功能后:由 TIMx_BDTR 的 MOE、OSSI、OSSR 位,TIMx_CR2 的 OISx、OISxN 位,TIMx_CCER 的 CCxE、CCxNE 位控制 OCx 和 OCxN 输出状态
无论何时,OCx 和 OCxN 输出都不能同时处在有效电平
在发生刹车后
1、MOE 位被清零,OCx 和 OCxN 为无效、空闲或复位状态(OSSI 位选择)
2、 OCx 和 OCxN 的状态:由相关控制位状态决定。当使用互补输出时:根据情况自动控制输出电平,参考参考手册使用刹车(断路)功能小节
3、BIF 位置 1,如果使能了 BIE 位,还会产生刹车中断;如果使能了 TDE 位,会产生 DMA 请求
4、如果 AOE 位置 1,在下一个 更新事件 UEV 时,MOE 位被自动置 1
4.5.6、高级定时器互补输出带死区控制实验配置步骤
1、配置定时器基础工作参数:使用 HAL_TIM_PWM_Init()
2、定时器 PWM 输出 MSP 初始化:使用 HAL_TIM_PWM_MspInit(),配置 NVIC、CLOCK、GPIO 等
3、配置 PWM 模式/比较值等:使用 HAL_TIM_PWM_ConfigChannel()
4、配置刹车功能、死区时间等:使用 HAL_TIMEx_ConfigBreakDeadTime()
5、使能输出、主输出、计数器:使用 HAL_TIM_PWM_Start()
6、使能互补输出、主输出、计数器:使用 HAL_TIMEx_PWMN_Start()
相关 HAL 库函数介绍
关键结构体介绍
typedef struct
{
uint32_t OCMode; /* 输出比较模式选择 */
uint32_t Pulse; /* 设置比较值 */
uint32_t OCPolarity; /* 设置输出比较极性 */
uint32_t OCNPolarity; /* 设置互补输出比较极性 */
uint32_t OCFastMode; /* 使能或失能输出比较快速模式 */
uint32_t OCIdleState; /* 空闲状态下OCx输出 */
uint32_t OCNIdleState; /* 空闲状态下OCxN输出 */
} TIM_OC_InitTypeDef;
typedef struct
{
uint32_t OffStateRunMode; /* 运行模式下的关闭状态选择 */
uint32_t OffStateIDLEMode; /* 空闲模式下的关闭状态选择 */
uint32_t LockLevel; /* 寄存器锁定设置 */
uint32_t DeadTime; /* 死区时间设置 */
uint32_t BreakState; /* 是否使能刹车功能 */
uint32_t BreakPolarity; /* 刹车输入极性 */
uint32_t BreakFilter; /* 刹车输入滤波器(F1/F4系列没有) */
uint32_t AutomaticOutput; /* 自动恢复输出使能,即使能AOE位 */
} TIM_BreakDeadTimeConfigTypeDef;
4.5.7、编程实战:高级定时器互补输出带死区控制实验
通过定时器 1 通道 1 输出频率为 1KHz,占空比为 70% 的 PWM,使用 PWM 模式 1
使能互补输出并设置死区时间控制:设置 DTG 为 100(2.38us),进行验证死区时间是否正确
使能刹车功能:刹车输入信号高电平有效,配置输出空闲状态等,最后用示波器验证
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/atim.h"
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
atim_timx_cplm_pwm_init(1000 - 1, 168 - 1); /* 1Mhz的计数频率 1Khz波形輸出. */
atim_timx_cplm_pwm_set(300, 100); /* 占空比:7:3, 死区时间 100 * tDTS */
while (1)
{
t++;
delay_ms(10);
if (t > 50) /* 控制LED0闪烁, 提示程序运行状态 */
{
t = 0;
LED0_TOGGLE();
}
}
}
atim.c
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef g_timx_cplm_pwm_handle; /* 定时器x句柄 */
TIM_BreakDeadTimeConfigTypeDef g_sbreak_dead_time_config = {0}; /* 死区时间设置 */
/**
* @brief 高级定时器TIMX 互补输出 初始化函数(使用PWM模式1)
* @note
* 配置高级定时器TIMX 互补输出, 一路OCy 一路OCyN, 并且可以设置死区时间
*
* 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
* 高级定时器时钟 = 168Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率, 单位 : Mhz
*
* @param arr: 自动重装值。
* @param psc: 预分频系数
* @retval 无
*/
void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct = {0};
TIM_OC_InitTypeDef tim_oc_cplm_pwm = {0};
ATIM_TIMX_CPLM_CLK_ENABLE(); /* TIMx 时钟使能 */
ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE(); /* 通道X对应IO口时钟使能 */
ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE(); /* 通道X互补通道对应IO口时钟使能 */
ATIM_TIMX_CPLM_BKIN_GPIO_CLK_ENABLE(); /* 通道X刹车输入对应IO口时钟使能 */
gpio_init_struct.Pin = ATIM_TIMX_CPLM_BKIN_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLDOWN;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init_struct.Alternate = ATIM_TIMX_CPLM_CHY_GPIO_AF; /* 端口复用 */
HAL_GPIO_Init(ATIM_TIMX_CPLM_BKIN_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHY_GPIO_PIN;
HAL_GPIO_Init(ATIM_TIMX_CPLM_CHY_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHYN_GPIO_PIN;
HAL_GPIO_Init(ATIM_TIMX_CPLM_CHYN_GPIO_PORT, &gpio_init_struct);
g_timx_cplm_pwm_handle.Instance = ATIM_TIMX_CPLM; /* 定时器x */
g_timx_cplm_pwm_handle.Init.Prescaler = psc; /* 预分频系数 */
g_timx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */
g_timx_cplm_pwm_handle.Init.Period = arr; /* 自动重装载值 */
g_timx_cplm_pwm_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4; /* CKD[1:0] = 10, tDTS = 4 * tCK_INT = Ft / 4 = 42Mhz*/
g_timx_cplm_pwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能影子寄存器TIMx_ARR */
HAL_TIM_PWM_Init(&g_timx_cplm_pwm_handle);
tim_oc_cplm_pwm.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */
tim_oc_cplm_pwm.OCPolarity = TIM_OCPOLARITY_HIGH; /* OCy 高电平有效 */
tim_oc_cplm_pwm.OCNPolarity = TIM_OCPOLARITY_HIGH; /* OCyN 高电平有效 */
tim_oc_cplm_pwm.OCIdleState = TIM_OCIDLESTATE_RESET; /* 当MOE=0,OCx=0 */
tim_oc_cplm_pwm.OCNIdleState = TIM_OCNIDLESTATE_RESET; /* 当MOE=0,OCxN=0 */
HAL_TIM_PWM_ConfigChannel(&g_timx_cplm_pwm_handle, &tim_oc_cplm_pwm, ATIM_TIMX_CPLM_CHY);
/* 设置死区参数,开启死区中断 */
g_sbreak_dead_time_config.OffStateRunMode = TIM_OSSR_DISABLE; /* 运行模式的关闭输出状态 */
g_sbreak_dead_time_config.OffStateIDLEMode = TIM_OSSI_DISABLE; /* 空闲模式的关闭输出状态 */
g_sbreak_dead_time_config.LockLevel = TIM_LOCKLEVEL_OFF; /* 不用寄存器锁功能 */
g_sbreak_dead_time_config.BreakState = TIM_BREAK_ENABLE; /* 使能刹车输入 */
g_sbreak_dead_time_config.BreakPolarity = TIM_BREAKPOLARITY_HIGH; /* 刹车输入有效信号极性为高 */
g_sbreak_dead_time_config.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE; /* 使能AOE位,允许刹车结束后自动恢复输出 */
HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, &g_sbreak_dead_time_config);
HAL_TIM_PWM_Start(&g_timx_cplm_pwm_handle, ATIM_TIMX_CPLM_CHY); /* OCy 输出使能 */
HAL_TIMEx_PWMN_Start(&g_timx_cplm_pwm_handle, ATIM_TIMX_CPLM_CHY); /* OCyN 输出使能 */
}
/**
* @brief 定时器TIMX 设置输出比较值 & 死区时间
* @param ccr: 输出比较值
* @param dtg: 死区时间
* @arg dtg[7:5]=0xx时, 死区时间 = dtg[7:0] * tDTS
* @arg dtg[7:5]=10x时, 死区时间 = (64 + dtg[6:0]) * 2 * tDTS
* @arg dtg[7:5]=110时, 死区时间 = (32 + dtg[5:0]) * 8 * tDTS
* @arg dtg[7:5]=111时, 死区时间 = (32 + dtg[5:0]) * 16 * tDTS
* @note tDTS = 1 / (Ft / CKD[1:0]) = 1 / 42M = 23.8ns
* @retval 无
*/
void atim_timx_cplm_pwm_set(uint16_t ccr, uint8_t dtg)
{
g_sbreak_dead_time_config.DeadTime = dtg;
HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, &g_sbreak_dead_time_config); /*重设死区时间*/
__HAL_TIM_MOE_ENABLE(&g_timx_cplm_pwm_handle); /* MOE=1,使能主输出 */
ATIM_TIMX_CPLM_CHY_CCRY = ccr; /* 设置比较寄存器 */
}
atim.h
#ifndef __ATIM_H
#define __ATIM_H
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
/******************************************************************************************/
/* TIMX 互补输出模式 定义
* 这里设置互补输出相关硬件配置, CHY即正常输出, CHYN即互补输出
* 修改CCRx可以修改占空比.
* 默认是针对TIM1
* 注意: 通过修改这些宏定义,可以支持TIM1/TIM8定时器, 任意一个IO口输出互补PWM(前提是必须有互补输出功能)
*/
/* 输出通道引脚 */
#define ATIM_TIMX_CPLM_CHY_GPIO_PORT GPIOE
#define ATIM_TIMX_CPLM_CHY_GPIO_PIN GPIO_PIN_9
#define ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
/* 互补输出通道引脚 */
#define ATIM_TIMX_CPLM_CHYN_GPIO_PORT GPIOE
#define ATIM_TIMX_CPLM_CHYN_GPIO_PIN GPIO_PIN_8
#define ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
/* 刹车输入引脚 */
#define ATIM_TIMX_CPLM_BKIN_GPIO_PORT GPIOE
#define ATIM_TIMX_CPLM_BKIN_GPIO_PIN GPIO_PIN_15
#define ATIM_TIMX_CPLM_BKIN_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
/* TIMX REMAP设置
* 因为PE8/PE9/PE15, 默认并不是TIM1的复用功能脚, 必须开启完全重映射, 才可以将: TIM1_CH1->PE9; TIM1_CH1N->PE8; TIM1_BKIN->PE15;
* 这样, PE8/PE9/PE15, 才能用作TIM1的CH1N/CH1/BKIN功能.
* 因此, 必须实现ATIM_TIMX_CPLM_CHYN_GPIO_AF,
* 如果我们使用默认的复用功能输出, 则不用设置重映射, 是可以不需要该函数的! 根据具体需要来实现.
*/
#define ATIM_TIMX_CPLM_CHY_GPIO_AF GPIO_AF1_TIM1
/* 互补输出使用的定时器 */
#define ATIM_TIMX_CPLM TIM1
#define ATIM_TIMX_CPLM_CHY TIM_CHANNEL_1
#define ATIM_TIMX_CPLM_CHY_CCRY ATIM_TIMX_CPLM->CCR1
#define ATIM_TIMX_CPLM_CLK_ENABLE() do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0) /* TIM1 时钟使能 */
/******************************************************************************************/
void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc); /* 高级定时器 互补输出 初始化函数 */
void atim_timx_cplm_pwm_set(uint16_t ccr, uint8_t dtg); /* 高级定时器 互补输出 设置输出比较值 & 死区时间 */
#endif
4.6、高级定时器PWM输入模式实验
4.6.1、PWM输入模式工作原理
4.6.2、PWM输入模式时序
4.6.3、高级定时器PWM输入模式实验配置步骤
1、配置定时器基础工作参数:使用 HAL_TIM_IC_Init()
2、定时器输入捕获 MSP 初始化:使用 HAL_TIM_IC_MspInit(),配置 NVIC、CLOCK、GPIO 等
3、配置 IC1/2 映射、捕获边沿等:使用 HAL_TIM_IC_ConfigChannel()
4、配置从模式,触发源等:使用 HAL_TIM_SlaveConfigSynchro()
5、设置优先级、使能中断:使用 HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ()
6、使能捕获、捕获中断及计数器:使用 HAL_TIM_IC_Start_IT()、HAL_TIM_IC_Start()
7、编写中断服务函数:使用 TIMx_IRQHandler() 等 —> HAL_TIM_IRQHandler()
8、编写输入捕获回调函数:HAL_TIM_IC_CaptureCallback()
相关 HAL 库函数介绍
关键结构体介绍
typedef struct
{
uint32_t ICPolarity; /* 输入捕获触发方式选择,比如上升、下降沿捕获 */
uint32_t ICSelection; /* 输入捕获选择,用于设置映射关系 */
uint32_t ICPrescaler; /* 输入捕获分频系数 */
uint32_t ICFilter; /* 输入捕获滤波器设置 */
} TIM_IC_InitTypeDef;
typedef struct
{
uint32_t SlaveMode; /* 从模式选择 */
uint32_t InputTrigger; /* 输入触发源选择 */
uint32_t TriggerPolarity; /* 输入触发极性 */
uint32_t TriggerPrescaler; /* 输入触发预分频 */
uint32_t TriggerFilter; /* 输入滤波器设置 */
} TIM_SlaveConfigTypeDef;
4.6.4、编程实战:高级定时器PWM输入模式实验
通过定时器 14 通道 1(PF9)输出 PWM
将 PWM 输入到定时器 8 通道 1(PC6),测量 PWM 的频率/周期、占空比等信息
168MHz 采样频率(精度约5.95ns),PSC=0,ARR=65535
不考虑溢出情况下,测量的最长 PWM 周期为 389.9us
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/atim.h"
#include "./BSP/TIMER/gtim.h"
extern uint16_t g_timxchy_pwmin_psc; /* PWM输入状态 */
extern uint16_t g_timxchy_pwmin_sta; /* PWM输入状态 */
extern uint32_t g_timxchy_pwmin_hval; /* PWM的高电平脉宽 */
extern uint32_t g_timxchy_pwmin_cval; /* PWM的周期宽度 */
int main(void)
{
uint8_t t = 0;
double ht, ct, f, tpsc;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
gtim_timx_pwm_chy_init(10 - 1, 84 - 1); /* 1Mhz的计数频率, 100Khz PWM */
atim_timx_pwmin_chy_init(); /* 初始化PWM输入捕获 */
GTIM_TIMX_PWM_CHY_CCRX = 2; /* 低电平宽度20,高电平宽度80 */
while (1)
{
delay_ms(10);
t++;
if (t >= 20) /* 每200ms输出一次结果,并闪烁LED0,提示程序运行 */
{
if (g_timxchy_pwmin_sta) /* 捕获了一次数据 */
{
printf("\r\n"); /* 输出空,另起一行 */
printf("PWM PSC :%d\r\n", g_timxchy_pwmin_psc); /* 打印分频系数 */
printf("PWM Hight:%d\r\n", g_timxchy_pwmin_hval); /* 打印高电平脉宽 */
printf("PWM Cycle:%d\r\n", g_timxchy_pwmin_cval); /* 打印周期 */
tpsc = ((double)g_timxchy_pwmin_psc + 1) / 168; /* 得到PWM采样时钟周期时间 */
ht = g_timxchy_pwmin_hval * tpsc; /* 计算高电平时间 */
ct = g_timxchy_pwmin_cval * tpsc; /* 计算周期长度 */
f = (1 / ct) * 1000000; /* 计算频率 */
printf("PWM Hight time:%.3fus\r\n", ht); /* 打印高电平脉宽长度 */
printf("PWM Cycle time:%.3fus\r\n", ct); /* 打印周期时间长度 */
printf("PWM Frequency :%.3fHz\r\n", f); /* 打印频率 */
atim_timx_pwmin_chy_restart(); /* 重启PWM输入检测 */
}
LED1_TOGGLE(); /* DS1闪烁 */
t = 0;
}
}
}
atim.c
#include "./BSP/TIMER/atim.h"
TIM_HandleTypeDef g_timx_pwmin_chy_handle; /* 定时器x句柄 */
/* PWM输入状态(g_timxchy_cap_sta)
* 0,没有成功捕获.
* 1,已经成功捕获了
*/
uint8_t g_timxchy_pwmin_sta = 0; /* PWM输入状态 */
uint16_t g_timxchy_pwmin_psc = 0; /* PWM输入分频系数 */
uint32_t g_timxchy_pwmin_hval = 0; /* PWM的高电平脉宽 */
uint32_t g_timxchy_pwmin_cval = 0; /* PWM的周期宽度 */
/**
* @brief 定时器TIMX 通道Y PWM输入模式 初始化函数
* @note
* 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
* 高级定时器时钟 = 168Mhz
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
* Ft=定时器工作频率,单位:Mhz
*
* @param 无
* @retval 无
*/
void atim_timx_pwmin_chy_init(void)
{
GPIO_InitTypeDef gpio_init_struct = {0};
TIM_SlaveConfigTypeDef slave_config = {0};
TIM_IC_InitTypeDef tim_ic_pwmin_chy = {0};
ATIM_TIMX_PWMIN_CHY_CLK_ENABLE();
ATIM_TIMX_PWMIN_CHY_GPIO_CLK_ENABLE();
gpio_init_struct.Pin = ATIM_TIMX_PWMIN_CHY_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLDOWN;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init_struct.Alternate = ATIM_TIMX_PWMIN_CHY_GPIO_AF;
HAL_GPIO_Init(ATIM_TIMX_PWMIN_CHY_GPIO_PORT, &gpio_init_struct);
g_timx_pwmin_chy_handle.Instance = ATIM_TIMX_PWMIN; /* 定时器8 */
g_timx_pwmin_chy_handle.Init.Prescaler = 0; /* 定时器预分频系数 */
g_timx_pwmin_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_pwmin_chy_handle.Init.Period = 65535; /* 自动重装载值 */
HAL_TIM_IC_Init(&g_timx_pwmin_chy_handle);
/* 从模式配置,IT1触发更新 */
slave_config.SlaveMode = TIM_SLAVEMODE_RESET; /* 从模式:复位模式 */
slave_config.InputTrigger = TIM_TS_TI1FP1; /* 定时器输入触发源:TI1FP1 */
slave_config.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING; /* 上升沿检测 */
slave_config.TriggerFilter = 0; /* 不滤波 */
HAL_TIM_SlaveConfigSynchro(&g_timx_pwmin_chy_handle, &slave_config);
/* IC1捕获:上升沿触发TI1FP1 */
tim_ic_pwmin_chy.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; /* 上升沿检测 */
tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_DIRECTTI; /* 选择输入端IC1映射到TI1 */
tim_ic_pwmin_chy.ICPrescaler = TIM_ICPSC_DIV1; /* 不分频 */
tim_ic_pwmin_chy.ICFilter = 0; /* 不滤波 */
HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_1);
/* IC2捕获:上升沿触发TI1FP2 */
tim_ic_pwmin_chy.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; /* 下降沿检测 */
tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_INDIRECTTI; /* 选择输入端IC2映射到TI1 */
HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_2);
HAL_NVIC_SetPriority(ATIM_TIMX_PWMIN_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(ATIM_TIMX_PWMIN_IRQn); /* 开启TIMx中断 */
/* TIM1/TIM8有独立的输入捕获中断服务函数 */
if (ATIM_TIMX_PWMIN == TIM1 || ATIM_TIMX_PWMIN == TIM8)
{
HAL_NVIC_SetPriority(ATIM_TIMX_PWMIN_CC_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(ATIM_TIMX_PWMIN_CC_IRQn); /* 开启TIMx中断 */
}
__HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE);
HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2);
}
/* 定时器TIMX PWM输入模式 重新启动捕获 */
void atim_timx_pwmin_chy_restart(void)
{
sys_intx_disable(); /* 关闭中断 */
g_timxchy_pwmin_sta = 0; /* 清零状态,重新开始检测 */
g_timxchy_pwmin_psc = 0; /* 分频系数清零 */
__HAL_TIM_SET_PRESCALER(&g_timx_pwmin_chy_handle, 0); /* 以最大的计数频率采集,以得到最好的精度 */
__HAL_TIM_SET_COUNTER(&g_timx_pwmin_chy_handle, 0); /* 计数器清零 */
__HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_CC1); /* 使能通道1捕获中断 */
__HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE); /* 使能溢出中断 */
__HAL_TIM_ENABLE(&g_timx_pwmin_chy_handle); /* 使能定时器TIMX */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1); /* 清零捕获/比较1中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2); /* 清零捕获/比较2中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清零更新中断标志 */
sys_intx_enable(); /* 打开中断 */
}
/* 定时器TIMX 通道Y PWM输入模式 中断处理函数 */
static void atim_timx_pwmin_chy_process(void)
{
static uint8_t sflag = 0; /* 启动PWMIN输入检测标志 */
if (g_timxchy_pwmin_sta)
{
g_timxchy_pwmin_psc = 0;
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1); /* 清零捕获/比较1中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2); /* 清零捕获/比较2中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清零更新中断标志 */
__HAL_TIM_SET_COUNTER(&g_timx_pwmin_chy_handle, 0); /* 计数器清零 */
return;
}
if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE)) /* 发生了溢出中断 */
{
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清除溢出中断标记 */
if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1) == 0) /* 没有发生周期捕获中断,且捕获未完成 */
{
sflag = 0;
if (g_timxchy_pwmin_psc == 0) /* 从0 到 1 */
{
g_timxchy_pwmin_psc++;
}
else
{
if (g_timxchy_pwmin_psc == 65535) /* 已经最大了,可能是无输入状态 */
{
g_timxchy_pwmin_psc = 0; /* 重新恢复不分频 */
}
else if (g_timxchy_pwmin_psc > 32767) /* 不能倍增了 */
{
g_timxchy_pwmin_psc = 65535; /* 直接等于最大分频系数 */
}
else
{
g_timxchy_pwmin_psc += g_timxchy_pwmin_psc; /* 倍增 */
}
}
__HAL_TIM_SET_PRESCALER(&g_timx_pwmin_chy_handle, g_timxchy_pwmin_psc); /* 设置定时器预分频系数 */
__HAL_TIM_SET_COUNTER(&g_timx_pwmin_chy_handle, 0); /* 计数器清零 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1); /* 清零捕获/比较1中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2); /* 清零捕获/比较2中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清零更新中断标志 */
return;
}
}
if (sflag == 0) /* 第一次采集到捕获中断 */
{
if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1)) /* 检测到了第一次周期捕获中断 */
{
sflag = 1; /* 标记第一次周期已经捕获, 第二次周期捕获可以开始了 */
}
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1); /* 清零捕获/比较1中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2); /* 清零捕获/比较2中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清零更新中断标志 */
return; /* 完成此次操作 */
}
if (g_timxchy_pwmin_sta == 0) /* 还没有成功捕获 */
{
if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1)) /* 检测到了周期捕获中断 */
{
g_timxchy_pwmin_hval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2) + 1; /* 高定平脉宽捕获值 */
g_timxchy_pwmin_cval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1) + 1; /* 周期捕获值 */
if (g_timxchy_pwmin_hval < g_timxchy_pwmin_cval) /* 高电平脉宽必定小于周期长度 */
{
g_timxchy_pwmin_sta = 1; /* 标记捕获成功 */
g_timxchy_pwmin_psc = ATIM_TIMX_PWMIN->PSC; /* 获取PWM输入分频系数 */
if (g_timxchy_pwmin_psc == 0) /* 分频系数为0的时候, 修正读取数据 */
{
g_timxchy_pwmin_hval++; /* 修正系数为1, 加1 */
g_timxchy_pwmin_cval++; /* 修正系数为1, 加1 */
}
sflag = 0;
/* 每次捕获PWM输入成功后, 停止捕获, 避免频繁中断影响系统正常代码运行 */
ATIM_TIMX_PWMIN->CR1 &= ~(1 << 0); /* 关闭定时器TIMX */
__HAL_TIM_DISABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_CC1); /* 使能通道1捕获中断 */
__HAL_TIM_DISABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_CC2); /* 使能通道2捕获中断 */
__HAL_TIM_DISABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE); /* 使能溢出中断 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1); /* 清零捕获/比较1中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2); /* 清零捕获/比较2中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清零更新中断标志 */
}
else
{
atim_timx_pwmin_chy_restart();
}
}
}
/* 清除捕获/比较1中断标志\捕获/比较2中断标志/更新中断标志 */
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1);
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2);
__HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE);
}
/* 定时器TIMX 更新/溢出 中断服务函数 */
void ATIM_TIMX_PWMIN_IRQHandler(void)
{
atim_timx_pwmin_chy_process();
}
/* 定时器TIMX 输入捕获 中断服务函数 */
void ATIM_TIMX_PWMIN_CC_IRQHandler(void)
{
atim_timx_pwmin_chy_process();
}
atim.h
#ifndef __ATIM_H
#define __ATIM_H
#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"
/******************************************************************************************/
/* TIMX PWM输入模式 定义
* 这里的输入捕获使用定时器TIM1_CH1,捕获WK_UP按键的输入
* 默认是针对TIM1/TIM8等高级定时器
* 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器的通道1/通道2
*/
#define ATIM_TIMX_PWMIN_CHY_GPIO_PORT GPIOC
#define ATIM_TIMX_PWMIN_CHY_GPIO_PIN GPIO_PIN_6
#define ATIM_TIMX_PWMIN_CHY_GPIO_AF GPIO_AF3_TIM8
#define ATIM_TIMX_PWMIN_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_PWMIN TIM8
#define ATIM_TIMX_PWMIN_IRQn TIM8_UP_TIM13_IRQn
#define ATIM_TIMX_PWMIN_IRQHandler TIM8_UP_TIM13_IRQHandler
#define ATIM_TIMX_PWMIN_CHY TIM_CHANNEL_1 /* 通道Y, 1<= Y <=2*/
#define ATIM_TIMX_PWMIN_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0) /* TIM8 时钟使能 */
/* TIM1 / TIM8 有独立的捕获中断服务函数,需要单独定义,对于TIM2~5等,则不需要以下定义 */
#define ATIM_TIMX_PWMIN_CC_IRQn TIM8_CC_IRQn
#define ATIM_TIMX_PWMIN_CC_IRQHandler TIM8_CC_IRQHandler
/******************************************************************************************/
void atim_timx_pwmin_chy_init(void); /* 高级定时器 PWM输入模式初始化 */
void atim_timx_pwmin_chy_restart(void); /* 高级定时器 重启PWM输入模式检测 */
#endif
作者:HZU_Puzzle