通用定时器定时中断
定时器介绍
通用定时器TIMx是带预分频器的十六位定时器,内部预分频器(PSC),计数器(CNT),自动重装载寄存器(ARR)均是十六位的,CNT定时计数方式可以是向上,向下,双向计数。
结构:
由时钟源选择,时基单元,输入捕获,比较输出四大功能单元构成
主要功能:
● 16位向上、向下、向上/向下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意
数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
通用寄存器:TIM2,TIM3,TIM4,TIM5
高级定时器:TIM1,TIM8
基本定时器:TIM6,TIM7
二. 定时时钟源选择
TIMx的定时时钟来源有内部时钟CK_INT,外部触发沿时钟ETR,内部触发时钟ITRx,外部时钟源TIx。作为定时应用时,需要选择一个时钟频率固定,已知的时钟源作为定时器的定时时钟。在上面四种时钟上,只有CK_INT的时钟频率是固定且已知的,所以一般选择CK_INT作为定时用的时钟源。
注意:
STM32定时器是作为片内外设挂接在外设总线APB1和APB2上,其内部时钟CK_INT来自定时器所挂接的总线APB1或APB2上。但是不能直接来自外设总线APB1或APB2,而是来自输入为APB1或APB2外设总线时钟的一个倍频器。如图,在时钟树中可以清晰的体现。(补充: 预分频系数为1时,频率不变,否则x2).
三. 时基单元
时基单元由计数器寄存器(TIMx_CNT),预分频器寄存器 (TIMx_PSC),自动装载寄存器 (TIMx_ARR)组成。PSC和ARR均带有影子寄存器,其控制作用的主要是影子寄存器。
- CK_CNT=CK_PSC/(PSC+1).分频系数由PSC的值决定,PSC取值0~65535.
- CNT是十六位寄存器,可选择向上,向下,双向计数
- ARR是自动重装寄存器,用于保存定时器定时初值。
补充:
什么是影子寄存器?
影子寄存器的引入是ARM的一个特点(X86,PowerPC都没有)。我们知道,ARM有16个通用寄存器,这16个通用寄存器在指令中使用4个bit来标识,但是在不同的模式下,同样的4个bit 指向不同的物理寄存器,这些不同的物理寄存器就被称之为影子寄存器。不同的通用寄存器的影子寄存器个数也不相同,有的没有,有的只有1个,有的多达5个。要记住一点:所有的影子寄存器都是一个实际存在的物理寄存器。
四. 计数模式
向上计数模式: 计数器是从0开始向上加1计数到自动重装载寄存器ARR的值,在经过一个计数时钟,CNT的值溢出回到0再重新计数,并产生一个计数上溢更新事件。
向下计数模式: 计数器从自动重装载寄存器ARR的值开始向下减1计数到0,在经过1个计数时钟,CNT的值溢出回归到ARR开始重新计数,并产生一个计数下溢更新事件。
(向上计数模式和向下计数模式总共经历(ARR+1)次时钟)
双向计数模式: 计数器首先从0开始加1计数到ARR的值,同时产生上溢更新事件,然后向下减一计数直到0,同时产生下溢更新事件,随后进入下一次定时计数。在产生下溢更新事件的同时,若使能了更新更新中断允许,将产生更新中断请求。总共经历(ARR+ARR)个时钟。
五. 定时时间计算
六. 代码分析
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t num;
int main(void)
{
OLED_Init();
Init_Timer();
OLED_ShowString(1, 1, "Num:");
while(1)
{
OLED_ShowSignedNum(1, 5, num,5);
}
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
主函数代码主要是使用定时器实现计数。
①. 函数Init_Timer()
void Init_Timer(void)
{
NVIC_InitTypeDef NVIC_InitTypeStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //定时器2挂载在APB1上
TIM_InternalClockConfig(TIM2); //选择内部时钟源
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_Period和TIM_Prescaler都减一
//因为预分频器与计数器都有一个数的偏差,所有都需要减一
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; //重复计数器的值(高级TIM才有)
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2,TIM_FLAG_Update); //手动把更新中断的标志位清除
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeStructure.NVIC_IRQChannel=TIM2_IRQn ;
NVIC_InitTypeStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitTypeStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitTypeStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitTypeStructure);
TIM_Cmd(TIM2,ENABLE);
}
函数的主要逻辑是:先将定时器挂载在APB2总线上,然后打开内部时钟源,再配置定时器各参数并进行初始化。完成之后,再配置NVIC进行初始化,最后使能TIM时钟。
Ⅰ. 函数TIM_InternalClockConfig(TIM2)
/**
* @brief Configures the TIMx internal Clock
* @param TIMx: where x can be 1, 2, 3, 4, 5, 8, 9, 12 or 15
* to select the TIM peripheral.
* @retval None
*/
void TIM_InternalClockConfig(TIM_TypeDef* TIMx)
{
/* Check the parameters */
assert_param(IS_TIM_LIST6_PERIPH(TIMx));
/* Disable slave mode to clock the presc-aler directly with the internal clock */
//关闭从模式,使预分频器直接与内部时钟同步
TIMx->SMCR &= (uint16_t)(~((uint16_t)TIM_SMCR_SMS));
}
在从模式控制寄存器SMCR中说明
定时器参数配置
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_Period和TIM_Prescaler都减一
//因为预分频器与计数器都有一个数的偏差,所有都需要减一
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
//重复计数器的值(高级TIM才有)
现在突然悟出很重要的一点
一般在宏定义的时候,他会直接根据控制寄存器相应的要求来定义具体的值是多少。
例如:
#define TIM_CKD_DIV1 ((uint16_t)0x0000)
#define TIM_CKD_DIV2 ((uint16_t)0x0100)
#define TIM_CKD_DIV4 ((uint16_t)0x0200)
#define TIM_CounterMode_Up ((uint16_t)0x0000)
#define TIM_CounterMode_Down ((uint16_t)0x0010)
#define TIM_CounterMode_CenterAligned1 ((uint16_t)0x0020)
TIM_CKD_DIV1表示不分频,根据寄存器的要求位9-8的值为00;TIM_CKD_DIV2表示二分频,位9-8的值为01,十六进制码为0x0100。TIM_CounterMode_Up表示向上计数,根据寄存器要求,位6-5值需要是00,表示边沿计数,然后根据位4(DIR)的值决定向上还是向下,最后向上计数模式定义为0x0000。其他宏定义的参数也是如此。
如何计算周期与频率:
CK_CNT=CK_PSC/(PSC+1);
T=(ARR+1)/CK_CNT;
T=(ARR+1)*(PSC+1)/CK_PSC;
CK_PSC选择的时钟源是CK_INT.
Ⅱ. 函数TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
/**
* @brief Initializes the TIMx Time Base Unit peripheral according to
* the specified parameters in the TIM_TimeBaseInitStruct.
* @param TIMx: where x can be 1 to 17 to select the TIM peripheral.
* @param TIM_TimeBaseInitStruct: pointer to a TIM_TimeBaseInitTypeDef
* structure that contains the configuration information for the
* specified TIM peripheral.
* @retval None
*/
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
{
uint16_t tmpcr1 = 0;
/* Check the parameters */
assert_param(IS_TIM_ALL_PERIPH(TIMx));
assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));
tmpcr1 = TIMx->CR1;
//下面两个if语句中都是配置通用寄存器和高级寄存器的模式选择
if((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM2) || (TIMx == TIM3)||
(TIMx == TIM4) || (TIMx == TIM5))
{
/* Select the Counter Mode */
tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS)));
tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
}
if((TIMx != TIM6) && (TIMx != TIM7))
{
/* Set the clock division */
tmpcr1 &= (uint16_t)(~((uint16_t)TIM_CR1_CKD));
tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;
}
//将tmpcr1值写入CR1中,配置对应模式
TIMx->CR1 = tmpcr1;
/* Set the Autoreload value */
TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
/* Set the Prescaler value */
TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
//配置到高级定时器和TIM15,TIM16,TIM17定时器的重复计数寄存器。因为这部分功能只有这几个定时器有
if ((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM15)|| (TIMx == TIM16) || (TIMx == TIM17))
{
/* Set the Repetition Counter value */
TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
}
/* Generate an update event to reload the Prescaler and the Repetition counter
values immediately */
//重新初始化计数器,并产生一个更新事件。
TIMx->EGR = TIM_PSCReloadMode_Immediate;
}
函数TIM_ClearFlag(TIM2,TIM_FLAG_Update)和TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE)和void TIM_Cmd(TIM_TypeDef TIMx, FunctionalState NewState)*
TIM_ClearFlag(TIM2,TIM_FLAG_Update)清除中断更新标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE)使能中断
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)使能中断计数器
代码比较简单就不过多分析,参考DMA/中断寄存器,和状态寄存器。
注:其余为分析的函数可以参考https://blog.csdn.net/qq_74215816/article/details/139041453?spm=1001.2014.3001.5502