STM32-通用定时器
目录
通用定时器
功能框图
输入捕获
测量频率
测量脉宽
PWM输入模式
输出比较
PWM边沿对齐模式
PWM中心对齐模式
初始化结构体
TIM_TimeBaseInitTypeDef:时基结构体
TIM_OC_InitTypeDef:输出比较结构体
TIM_IC_InitTypeDef:输入捕获结构体
通用定时器实验:输入捕获
常规配置
TIM5配置
测试环节
实验现象
通用定时器
通用定时器由一个可编程预分频器驱动的16位自动重新加载计数器组成。应用:测量输入的脉冲长度信号(输入捕获)、产生输出波形(输出比较和PWM)。
脉冲长度和波形周期可以从几微秒调制到几毫秒,使用定时器预分频器和RCC时钟控制器预分频器。
输入捕获应用场景:脉冲跳变沿时间测量 和 PWM输入测量。
输出比较应用场景:PWM8种模式(常用PWM边沿对齐模式、PWM中心对齐模式)
功能框图
16位向上、向下、向上/向下自动重装载计数器。
16位可编程预分频器,1~65536。
多达4个独立通道,用于:
输入捕获
输出比较
PWM产生(边沿对齐模式和中心对齐模式)
单脉冲模式输出
同步电路控制定时器和外部信号,并互连多个定时器。
中断/DMA生成以下事件:
更新:计数器溢出/下溢,计数器初始化(由软件或内部/外部触发)
触发事件(计数器启动、停止、初始化或由内部/外部触发计数)
输入捕获
输出比较
支持增量(正交)编码器和霍尔传感器电路的定位目的。
触发输入外部时钟或逐周期电流管理。
时基单元包括:
计数器寄存器(TIMx_CNT)
预分频寄存器(TIMx_PSC)
自动重装载寄存器(TIMx_ARR)
计数模式:
向上计数
向下计数
中心对齐:计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件。然后继续从0开始重新计数,以此循环。
TIx为输入通道,需要被测量的信号从定时器的外部引脚TIMx_CH1/2/3/4进入。
当输入的信号存在高频干扰时,需要对输入信号进行滤波,根据采样定律(采样频率必须大于或等于两倍的输入信号),比如输入信号为1M,存在高频信号干扰时就要进行滤波,可以设置采样频率为2M,这样可以在保证采样到有效信号的基础上把高于2M的高频干扰信号过滤掉。
输入滤波器的配置由TIMx_CR1:CKD[1:0]和TIMx_CCMR1/2:ICxF[3:0]控制。根据ICxF位的描述,采样频率Fsample可以由Fck_int(内部时钟)和Fdts(Fck_int经过分频后的频率,分频因子由CKD[1:0]决定,1/2/4分频)分频后的时钟提供。
边沿检测器用来设置信号在捕获时什么边沿有效(上升沿、下降沿、双边沿),具体由TIMx_CCER:CCxP、CCxNP决定。
捕获通道:IC1/2/3/4。每个捕获通道都有对应的捕获寄存器CCR1/2/3/4,当发生捕获时,计数器CNT的值就会被锁存到捕获寄存器中。
输入通道TIx是用来输入信号的,捕获通道ICx是用来捕获输入信号的通道。一个输入通道的信号可以同时输入给两个捕获通道。比如TI1的信号经过滤波和边沿检测器后TI1FP1和TI1FP2可以进入到捕获通道IC1和IC2。输入通道和捕获通道的映射关系具体由TIMx_CCMR:CCxS[1:0]配置。
ICx的输出型会经过预分频器,用于决定发生多少个事件时进行一次捕获。具体由TIMx_CCMR:ICxPSC配置。如果希望捕获信号的每一个边沿,则不分频。
经过预分频器的信号ICxPS是最终被捕获的信号,当发生捕获时(第一次),计数器CNT的值会被锁存到捕获寄存器TIMx_CCR中,还会产生CCxI中断,相应的中断位CCxIF(在SR寄存器中)会被置位,通过软件或读取CCR的值可以将CCxIF清0。如果发生第二次捕获(即重复捕获,CCR寄存器中已捕获到计数器值且CCxIF标志已置1),则捕获溢出标志位CCxOF(在SR寄存器中)会被置位,CCx_OF只能通过软件清零。
输入捕获
测量频率
当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器TIM_CCRx中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器的值读取到value1中。
当出现第二次上升沿时,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器TIM_CCRx中,并再次进入捕获中断,在中断服务程序中记录一次捕获,并把捕获寄存器的值读取到value2中,清除捕获记录标志。利用value2-value1的差值就可以算出信号的周期(频率)。
测量脉宽
当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器TIM_CCRx中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器的值读取到value1中。然后把捕获边沿改变为下降沿捕获,目的是捕获后面的下降沿。
当下降沿到来时,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器TIM_CCRx中,而且还会进入捕获中断,在中断服务程序中记录一次捕获,并把捕获寄存器的值读取到value2中,清除捕获记录标志。然后把捕获边沿设置为上升沿捕获。
在测量脉宽过程中需要来回切换捕获边沿的极性,如果测量的脉宽时间比较长,定时器就会发生溢出,溢出时会产生更新中断,我们可以在中断里对溢出进行记录处理。
PWM输入模式
测量脉宽和频率还有一个更简便的方法:PWM输入模式,该模式是输入捕获的特例,只能使用通道1和2,通道3和4不能使用。测量脉宽和频率的方法只使用一个捕获寄存器,PWM输入模式需要占用两个捕获寄存器。
当使用PWM输入模式时,因为一个输入通道(TIx)会占用两个捕获通道(ICx),所以一个定时器在使用PWM输入模式时最多只能使用两个输入通道(TIx)。
以输入通道TI1工作在PWM输入模式为例:
PWM信号由输入通道TI1进入,因为是PWM输入模式,所以信号被分为两路(TI1FP1、TI1FP2)。其中一路是周期,另一路是占空比,具体哪一路信号对应周期还是占空比,得从程序上设置哪一路信号作为触发输入(作为触发输入的那一路信号对应的是周期,另一路信号对应的是占空比)。作为触发输入的那一路信号还需要设置极性(上升沿捕获还是下降沿捕获),一旦设置好触发输入的极性,另外一路硬件就会自动配置为相反的极性捕获,无需软件配置。
概况:选定输入通道,确定触发信号,然后设置触发性的极性(因为是PWM输入模式,所以另一路由硬件配置,无需软件配置)。
当使用PWM输入模式时必须将从模式控制器配置为复位模式(配置寄存器TIMx_SMCR:SMS[2:0]实现),即当我们启动触发信号开始进行捕获时,同时把计数器CNT复位清零。
上图为例,PWM信号由输入通道TI1进入,配置TI1FP1为触发信号,上升沿捕获。
当上升沿到来时IC1和IC2同时捕获,计数器CNT清零;
到了下降沿时IC2捕获,计数器CNT的值被锁存在捕获寄存器TIMx_CCR2中;
到了下一个上升沿到来时IC1捕获,计数器CNT的值被锁存在捕获寄存器TIMx_CCR1中。
其中TIMx_CCR2 + 1测量的是脉宽,TIMx_CCR1 + 1测量的是周期。(注意:TIMx_CCR1和TIMx_CCR2都必须加1,因为计数器是从0开始计数的)
从软件上看,PWM输入模式测量脉宽和周期更容易,缺点是需要占用两个捕获寄存器。
输出比较
输出比较模式共8种,具体由TIMx_CCMR1/2:OCxM[2:0]配置。
PWM输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装载寄存器ARR的值决定,占空比由比较寄存器CCR的值决定。
PWM模式分两种:PWM1、PWM2。
以PWM1模式为例。以计数器CNT计数方向不同还分为边沿对齐模式和中心对齐模式。PWM信号主要都是用来控制电机,一般电机控制用的都是边沿对齐模式,FOC电机一般用的是中心对齐模式。
PWM边沿对齐模式
在递增计数模式下,计数器从0计数到自动重装载值(TIMx_ARR寄存器的内容),然后重新从0开始计数并生成计数器上溢事件。
在边沿对齐模式下,计数器CNT只工作在一种模式:递增或递减模式。
以CNT工作在递增模式为例,ARR=8,CCR=4,CNT从0开始计数。
当CNT < CCR的值时,OCXREF为有效的高电平,此时比较中断寄存器CCxIF置位。
当CCR <= CNT <= ARR时,OCXREF为无效的低电平。
然后CNT又从0开始计数并生成计数器上溢事件,以此循环往复。
PWM中心对齐模式
中心对齐模式下,计数器CNT是工作在递增/递减模式下。
开始时,计数器CNT从0开始计数到自动重装载值减1(ARR-1),生成计数器上溢事件;
然后从自动重装载值开始向下计数到1,生成计数器下溢事件。然后从0开始重新计数,以此往复。
以PWM1模式的中心对齐模式为例,ARR=8,CCR=4。
第一阶段计数器CNT工作在递增模式下从0开始计数。
当CNT < CCR的值时,OCXREF为有效的高电平。
当CCR <= CNT <= ARR时,OCXREF为无效的低电平。
第二阶段计数器CNT工作在递减模式下从ARR的值开始计数。
当CNT > CCR的值时,OCXREF为无效的低电平。
当1 <= CNT <= CRR时,OCXREF为有效的高电平。
在波形图中可以把波形分为两个阶段,
第一个阶段是计数器CNT工作在递增模式的波形,这个阶段又分为1和2两个阶段。
第一个阶段是计数器CNT工作在递减模式的波形,这个阶段又分为3和4两个阶段。
中心对齐模式下的波形特征是1和3的时间相等,2和4的时间相等。
中心对齐模式又分为中心对齐1/2/3三种,具体由寄存器TIMx_CR1:CMS[1:0]配置。具体的区别是比较中断标志位CCxIF在何时置1。
中心模式1在CNT递减计数时置1,中心模式2在CNT递增计数时置1,中心模式3在CNT递增和递减计数时都置1。
初始化结构体
TIM_TimeBaseInitTypeDef:时基结构体
typedef struct
{
uint16_t TIM_Prescaler; // 预分频器,基本定时器配置需要
uint16_t TIM_CounterMode; // 计数模式,通用定时器配置需要
uint16_t TIM_Period; // 定时周期,基本定时器配置需要
uint16_t TIM_ClockDivision; // 时钟分频,通用定时器配置需要
uint8_t TIM_RepetitionCounter; // 重复计数器
} TIM_TimeBaseInitTypeDef;
对于TIM_CounterMode,有。
#define TIM_CounterMode_Up ((uint16_t)0x0000) //边沿对齐模式,向上计数
#define TIM_CounterMode_Down ((uint16_t)0x0010) //边沿对齐模式,向下计数
#define TIM_CounterMode_CenterAligned1 ((uint16_t)0x0020) //中央对齐模式1,计数器交替向上向下计数。
//产生下溢中断,配置为输出的通道(TIMx_CCMRx寄存器中CCxS=00)的输出比较中断标志位,只在计数器向下计数时被设置。
#define TIM_CounterMode_CenterAligned2 ((uint16_t)0x0040) //中央对齐模式2,计数器交替向上向下计数。
//产生上溢中断,配置为输出的通道(TIMx_CCMRx寄存器中CCxS=00)的输出比较中断标志位,只在计数器向上计数时被设置。
#define TIM_CounterMode_CenterAligned3 ((uint16_t)0x0060) //中央对齐模式3,计数器交替向上向下计数。
//产生下溢和上溢中断,配置为输出的通道(TIMx_CCMRx寄存器中CCxS=00)的输出比较中断标志位,在计数器向上和向下计数时均被设置。
对于TIM_ClockDivision,为时钟分频,设置定时器时钟CK_INT频率与死区发生器以及数字滤波器采样时钟频率分频比,可选择1/2/4分频。
TIM_OC_InitTypeDef:输出比较结构体
与TIM_OCxInit函数配合使用完成指定定时器输出通道初始化配置。
typedef struct {
uint32_t OCMode; // 比较输出模式,8种,常用PWM1或PWM2
uint32_t Pulse; // 比较输出脉冲宽度。0~65535
uint32_t OCPolarity; // 比较输出极性。决定着定时器通道有效电平
uint32_t OCNPolarity; // 互补输出极性
uint32_t OCFastMode; // 比较输出模式快速使能
uint32_t OCIdleState; // 空闲状态下比较输出状态
uint32_t OCNIdleState; // 空闲状态下比较互补输出状态
} TIM_OCInitTypeDef;
TIM_IC_InitTypeDef:输入捕获结构体
与HAL_TIM_IC_ConfigChannel函数配合使用完成指定定时器输入通道初始化配置。
如果使用PWM输入模式需要与HAL_TIM_PWM_ConfigChannel函数配合使用完成指定定时器输入通道初始化配置。
typedef struct {
uint32_t ICPolarity; // 输入捕获通道的极性选择,上升沿/下降沿/边沿跳变触发捕获事件
uint32_t ICSelection; // 输入捕获通道的输入源,捕获通道ICx的信号可来自三个输入通道,分别为TIM_ICSELECTION_DIRECTTI、TIM_ICSELECTION_INDIRECTTI或TIM_ICSELECTION_TRC
uint32_t ICPrescaler; // 输入捕获预分频器,1/2/4/8,如果需要捕获输入信号的每个有效边沿,则设置1分频即可
uint32_t ICFilter; // 输入捕获滤波器,0x0~0xf。一般不使用滤波器,即设置为0
} TIM_IC_InitTypeDef;
ICSelection为输入捕获通道的输入源。
当为TIM_ICSELECTION_DIRECTTI时,TI1、TI2、TI3、TI4对应IC1、IC2、IC3、IC4。
当为TIM_ICSELECTION_DIRECTTI时,TI1、TI2、TI3、TI4对应IC2、IC1、IC4、IC3。
当为TIM_ICSELECTION_TRC时,TI1、TI2、TI3、TI4对应TRC。
通用定时器实验:输入捕获
常规配置
USART:115200-8-N-1,支持重定向输出printf函数,勾选使用C库。
TIM5配置
硬件原理图得知,PA0是KEY1的按键IO,也是TIM5_CH1的IO。
TIM_HandleTypeDef htim5;
void MX_TIM5_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
htim5.Instance = TIM5;
htim5.Init.Prescaler = 71;
htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
htim5.Init.Period = 65535;
htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_IC_Init(&htim5) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *tim_baseHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (tim_baseHandle->Instance == TIM5)
{
__HAL_RCC_TIM5_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**TIM5 GPIO Configuration
PA0-WKUP ------> TIM5_CH1
*/
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(TIM5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM5_IRQn);
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *tim_baseHandle)
{
if (tim_baseHandle->Instance == TIM5)
{
__HAL_RCC_TIM5_CLK_DISABLE();
/**TIM5 GPIO Configuration
PA0-WKUP ------> TIM5_CH1
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0);
/* TIM5 interrupt Deinit */
HAL_NVIC_DisableIRQ(TIM5_IRQn);
}
}
测试环节
typedef struct
{
uint8_t ucFinishFlag; // 捕获结束标志位
uint8_t ucStartFlag; // 捕获开始标志位
uint16_t usCtr; // 捕获寄存器的值
uint16_t usPeriod; // 自动重装载寄存器更新标志
}STRUCT_CAPTURE; // 定时器输入捕获用户自定义变量结构体声明
STRUCT_CAPTURE TIM_ICUserValueStructure = {0, 0, 0, 0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
TIM_ICUserValueStructure.usPeriod++;
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
TIM_IC_InitTypeDef IC_Config;
if (TIM_ICUserValueStructure.ucStartFlag == 0) // 上升沿捕获,开始捕获
{
__HAL_TIM_SET_COUNTER(htim, 0); // 清零定时器计数
TIM_ICUserValueStructure.usPeriod = 0;
TIM_ICUserValueStructure.usCtr = 0;
// 配置输入捕获参数,主要是修改触发电平
IC_Config.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
IC_Config.ICSelection = TIM_ICSELECTION_DIRECTTI;
IC_Config.ICPrescaler = TIM_ICPSC_DIV1;
IC_Config.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim5, &IC_Config, TIM_CHANNEL_1);
// 清除TIM捕获/比较1中断标志位
__HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
// 启动输入捕获并开启中断
HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
TIM_ICUserValueStructure.ucStartFlag = 1;
}
else // 下降沿捕获,结束捕获
{
// 获取定时器计数值
TIM_ICUserValueStructure.usCtr = HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_1);
// 配置输入捕获参数,主要是修改触发电平
IC_Config.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
IC_Config.ICSelection = TIM_ICSELECTION_DIRECTTI;
IC_Config.ICPrescaler = TIM_ICPSC_DIV1;
IC_Config.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim5, &IC_Config, TIM_CHANNEL_1);
// 清除中断标志位
__HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
// 启动输入捕获并开启中断
HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
TIM_ICUserValueStructure.ucStartFlag = 0;
TIM_ICUserValueStructure.ucFinishFlag = 1;
}
}
void test(void)
{
uint32_t time, TIM_PscCLK;
初始化
// TIM 计数器的驱动时钟
TIM_PscCLK = HAL_RCC_GetHCLKFreq() / 71;
/* 启动定时器 */
HAL_TIM_Base_Start_IT(&htim5);
/* 启动定时器通道输入捕获并开启中断 */
HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
while (1)
{
/* 完成测量高电平脉宽 */
if (TIM_ICUserValueStructure.ucFinishFlag == 1)
{
/* 计算高电平计数值 */
time = TIM_ICUserValueStructure.usPeriod * 65535 + TIM_ICUserValueStructure.usCtr;
/* 打印高电平脉宽时间 */
printf("测得高电平脉宽时间:%d.%d s\n", time / TIM_PscCLK, time % TIM_PscCLK);
TIM_ICUserValueStructure.ucFinishFlag = 0;
}
}
}