一、概述
STM32内部集成了多个定时/计数器,根据型号不同,STM32系列芯片最多包含8个定时/计数器。其中,TIM6、TIM7为基本定时器,TIM2~TIM5为通用定时器,TIM1、TIM8为高级控制定时器。
1.定时器的类型
- 基本定时器
- 通用定时器
- 高级控制定时器
- 窗口看门狗定时器
- 独立看门狗定时器
- 系统滴答定时器
2.计数模式
-
向上计数模式:计数器从0计数到自动加载值(ARR),并产生向上溢出事件。
-
向下计数模式:计数器从自动加载值(ARR)向下计数到0,并产生向下溢出事件。
-
中央对齐模式:计数器从0开始计数到自动加载值-1,产生向上溢出事件,然后向下计数到1,产生向下溢出事件,最后再从0开始重新计数。
3.主要功能
- 基本定时功能
- 输出比较
- 输入捕获
- 编码器接口模式
- 单脉冲模式
- 死区控制和刹车功能
注:本文将介绍前四种常见的功能。
4.通用定时器的结构
STM32通用定时器主要包括1个外部触发引脚(TIMx_ETR),4个输入/输出通道(TIMx_CH1、 TIMx_CH2、TIMx_CH3和TIMx_CH4),1个内部时钟,1个触发控制器,1个时钟单元(由预分频器PSC、自动重装载寄存器ARR和计数器CNT组成)。如图所示:
5.时钟源
定时/计数器时钟可由下列时钟源(如上图所示)提供:
- 内部时钟(CK_INT)
- 外部时钟模式1(TIMx_CH1~4)
- 外部时钟模式2(TIMx_ETR)
- 内部触发输入(ITR,使用一个定时器作为另一个定时器的预分频器)
当时钟源来自内部时,可实现定时功能;当时钟源来自外部时,可实现计数功能。
6.功能寄存器
略。
二、基本定时功能
下面介绍TIM定时器最基础的功能:基本定时功能。这种功能常用于周期性事件的触发。使用流程和步骤如下:
- 选择时钟源
- 配置时基单元
- 配置NVIC
- 编写中断服务函数
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//开启TIM2的时钟
TIM_InternalClockConfig(TIM2);
//选择时钟源为内部时钟,若不调用此函数,TIM2默认也为内部时钟
/*配置时基单元*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
//计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
//预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
//重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//清除定时器更新标志位,TIM_TimeBaseInit函数末尾,手动产生了更新事件,
//若不清除此标志位,则开启中断后,会立刻进入一次中断,
//如果不介意此问题,则不清除此标志位也可。
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC分组
/*配置NVIC*/
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //设置抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //设置响应优先级为1
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE); //使能TIM2,运行TIM2
}
void TIM2_IRQHandler(void) //定时器中断函数
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
//此处编写要周期实现的功能
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
三、输出比较功能
当定时器的计数器值(CNT)与捕获比较寄存器(CCR)的值相等时,产生比较事件,并根据配置对输出管脚进行相应的操作,如翻转或置位。其应用场景如下:
- PWM(脉宽调制)信号的产生:输出占空比可调的PWM信号,用于电机控制、LED调光等。
- 定时脉冲:在特定时间产生一个脉冲信号,用于精确事件触发。
- DAC触发:精确触发模拟信号输出。
下面介绍产生PWM波的使用流程:
- 配置GPIO,用于输出PWM,根据引脚定义表配置
- 选择时钟源
- 配置时基单元
- 配置输出比较模式
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*配置GPIO*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//受外设控制的引脚,均需要配置为复用模式
TIM_InternalClockConfig(TIM2);
//选择时钟源为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*配置时基单元*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;
//计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;
//预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
//重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
/*配置输出比较模式*/
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
//结构体初始化,若结构体没有完整赋值,则最好执行此函数,给结构体所有成员都赋一个默认值,避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//输出使能
TIM_OCInitStructure.TIM_Pulse = 0;
//初始的CCR值,也可以不定为0,直接定为想要的占空比所需的CCR值
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
//使能TIM2,运行TIM2
}
TIM_SetCompare3(TIM2, Compare);
//设置CCR的值,设置CCR几的值取决于使用哪个引脚,PA2对应的是CCR3
//该行代码用于改变占空比,一般放在主函数或者中断服务函数
另外,PWM的三项重要数据的计算方法如下:
- 占空比:CCR/(ARR+1)
- 分辨率:1/(ARR+1)
- 频率:CK_PSC/(PSC+1)/(ARR+1),CK_PSC一般为72MHz
四、输入捕获功能
输入捕获模式用于测量外部信号的时间特性,例如周期、频率、脉宽等。它通过将外部输入信号的某个边沿(上升沿或下降沿)捕获并保存计数器的值,从而实现时间测量。
下面介绍通过输入捕获功能实现频率测量的步骤:
- 配置GPIO,用于接收需要测频率的信号,根据引脚定义表配置
- 选择时钟源
- 配置时基单元
- 配置输入捕获功能
- 编写频率计算函数
void IC_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*配置GPIO*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM3);
//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*配置时基单元*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;
//计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
//预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
//重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
/*配置输入捕获功能*/
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
//选择配置定时器通道1
TIM_ICInitStructure.TIM_ICFilter = 0xF;
//输入滤波器参数,可以过滤信号抖动
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
//极性,选择为上升沿触发捕获
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
//捕获预分频,选择不分频,每次信号都触发捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
//输入信号交叉,选择直通,不交叉
TIM_ICInit(TIM3, &TIM_ICInitStructure);
/*选择触发源及从模式*/
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
//触发源选择TI1FP1
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
//从模式选择复位,即TI1产生上升沿时,会触发CNT归零
TIM_Cmd(TIM3, ENABLE); //使能TIM3,运行TIM3
}
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
//测周法得到频率fx = fc / N,这里不执行+1的操作也可
}
频率测量方法有两种,一种是适用于测量高频信号的测频法,一种是适用于测量低频信号的测周法。其原理如下图所示:
五、编码器模式
编码器接口模式用于解码旋转编码器的信号。它可以直接连接增量型旋转编码器的A相和B相信号,并解码出编码器的旋转方向和位置。 每个高级定时器和通用定时器都拥有1个编码器接口,两个输入引脚借用了输入捕获的通道1和通道2。
下面介绍使用编码器模式来测量电机转速的步骤,硬件上将带编码器的电机的编码输出连接到STM32的PA6和PA7,具体如下:
- 配置GPIO,用于接收正交编码,根据引脚定义表配置
- 配置时基单元
- 配置输入捕获模式
- 配置编码器模式
- 配置另一个定时器,编写速度计算函数
void Encoder_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*配置GPIO*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //配置PA6和PA7引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*配置时基单元*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //使用TIM3
/*配置输入捕获模式*/
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0x10;
//输入滤波器参数,可以过滤信号抖动
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0x10;
//输入滤波器参数,可以过滤信号抖动
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Falling, TIM_ICPolarity_Rising);
//配置编码器模式以及两个输入通道是否反相
TIM_ClearFlag(TIM3, TIM_FLAG_Update);
TIM_SetCounter(TIM3, 0);
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
//使用二、基本定时功能,在中断服务函数中编写计算速度的代码。
//这里需要另外配置一个定时器,相关代码参考第二点,这里不再赘述。
//先计算电机转一圈,STM32收到的n个编码;这取决于电机本身的参数。
//再每隔T时间输出STM32一共接收到的N个编码;则N/n即这段时间T里电机转过的圈数。
//最后用N/n/T即可求出转速。其中:
int n=xxx //根据电机参数计算
int N=TIM_GetCounter(TIM3); //STM32接收到的编码数
TIM_SetCounter(TIM3, 0); //拿到T时间内的编码数后,计数清零,重新计数
float Speed=N/n/T; //T为定时周期