Stm32的定时器介绍
STM32F1 的定时器功能十分强大,有 TIME1 和 TIME8 高级定时器,也有 TIME2~TIME5 通用定时器,还有 TIME6 和 TIME7 等基本定时器。
STM32通用定时器
STM32F1 的通用定时器是通过可编程预分频器(PSC)驱动的 16 位自动装载计数器(CNT)构成。 STM32F1有TIM2、TIM3、TIM4 和 TIM5五个通用定时器 STM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。 使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。 STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
CH即CLOCK Hours
通用定时器的功能
1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。 2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数值。 3)4 个独立通道(TIMx_CH1~4),这些通道可以用来作为: A.输入捕获 B.输出比较 C.PWM 生成(边缘或中间对齐模式) D.单脉冲模式输出 4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。 5)如下事件发生时产生中断/DMA: A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发) B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) C.输入捕获 D.输出比较 E.支持针对定位的增量(正交)编码器和霍尔传感器电路 F.触发输入作为外部时钟或者按周期的电流管理
通用定时器的寄存器
控制寄存器 1(TIMx_CR1)
最低位,也就是计数器使能位,该位必须置 1,才能让定时器开始计数。 第 4 位 DIR 可以看出默认的计数方式是向上计数,同时也可以向下计数 第 5,6位是设置计数对齐方式的。 从第 8 和第 9 位可以看出,我们还可以设置定时器的时钟分频因子为 1,2,4
DMA/中断使能寄存器(TIMx_DIER)
DMA interrupt enable register 一个 16 位的寄存器
第 0 位是更新中断允许位,该位设置为 1允许由于更新事件所产生的中断。
预分频寄存器(TIMx_PSC)
Prescaler
预分频器的值由寄存器TIMx_PSC设定,是一个16位正整数值。 该寄存器用设置将定时器时钟源进行分频输出,然后提供给计数器,作为计数器的时钟。
这里,定时器的时钟来源有 4 个: 1)内部时钟(CK_INT) 2)外部时钟模式 1:外部输入脚(TIx) 3)外部时钟模式 2:外部触发输入(ETR) 4)内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。 这里的 CK_INT时钟是从 APB1 倍频的来的,除非 APB1 的时钟分频数设置为 1,否则通用定时器 TIMx 的时钟是 APB1 时钟的 2 倍,当 APB1 的时钟不分频的时候,通用定时器 TIMx 的时钟就等于 APB1的时钟。 这里还要注意的就是高级定时器的时钟不是来自 APB1,而是来自 APB2 的。
计数寄存器(TIMx_CNT)
Count 该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。
自动重装载寄存器(TIMx_ARR)
Automatic reload register 该寄存器在物理上实际对应着 2 个寄存器。 一个是程序员可以直接操作的,另外一个是程序员看不到的,这个看不到的寄存器在《STM32参考手册》里面被叫做影子寄存器。事实上真正起作用的是影子寄存器。 根据 TIMx_CR1 寄存器中 APRE 位的设置:APRE=0 时,预装载寄存器的内容可以随时传送到影子寄存器,此时 2者是连通的;而 APRE=1 时,在每一次更新事件(UEV)时,才把预装在寄存器的内容传送到影子寄存器。
状态寄存器(TIMx_SR)
State Register 该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。
最低位,中断是否发生。
系统定时器SysTick
-
系统定时器是属于 CM3 内核中的一个外设,内嵌在 NVIC 中。因为所有的 CM3 芯片都带有这个定时器,软件在不同 CM3 器件间的移植工作得以化简。 -
系统定时器是一个 24bit 的向下递减的计数器,计数器每计数一次的时间为 1/SYSCLK,一般我们设置系统时钟 SYSCLK 等于 72M。
- 该定时器的时钟源可以是内部时钟,或者是外部时钟。不过,STCLK 的具体来源则由芯片设计者决定
-
当重装载数值寄存器的值递减到 0 的时候,系统定时器就产生一次中断,以此循环往复。
- SysTick 设定初值并使能后,每经过 1 个系统时钟周期,计数值就减 1。计数到 0 时,SysTick 计数器自动重装初值并继续计数,同时内部的 COUNTFLAG 标志会置位,触发中断 (如果中断使能情况下)。
-
只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息。
滴答定时器寄存器
Systick 部分内容属于 NVIC 控制部分,一共有 4 个寄存器,名称和地址分别是:
STK_CSR 0xE000E010 -- 控制寄存器
CTRL
STK_LOAD 0xE000E014 -- 重载寄存器
STK_VAL 0xE000E018 -- 当前值寄存器
STK_CALRB 0xE000E01C -- 校准值寄存器
STK_CSR 控制寄存器CTRL?
寄存器内有 4 个位具有意义
- 第 0 位:ENABLE,Systick 使能位(0:关闭 Systick 功能;1:开启 Systick 功能)
- 第 1 位:TICKINT,Systick 中断使能位(0:关闭 Systick 中断;1:开启 Systick 中断)
- 第 2 位:CLKSOURCE,Systick 时钟源选择(0:使用 HCLK/8 作为 Systick 时钟;1:使用 HCLK 作为 Systick 时钟)
- 第 16 位:COUNTFLAG,Systick 计数比较标志,如果在上次读取本寄存器后,SysTick 已经数到了0,则该位为1。如果读取该位,该位将自动清零。
STK_LOAD 重载寄存器
Systick 是一个递减的定时器,当定时器递减至0 时,重载寄存器中的值就会被重装载,继续开始递减。STK_LOAD 重载寄存器是个24 位的寄存器最大计数0xFFFFFF。
STK_VAL 当前值寄存器
24 位的寄存器,读取时返回当前倒计数的值,写它则使之清零,同时还会清除在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志。
STK_CALRB 校准值寄存器
位31 NOREF:= 1 没有外部参考时钟(STCLK 不可用),= 0 外部参考时钟可用位30 SKEW:= 1 校准值不是准确的1ms,= 0校准值是准确的1ms
操作 Systick 定时器
只要配置 SysTick_Config() 即可实现。SysTick_Config 的参数是一个时钟次数,叫 systick 重装寄存器的值。意思就是我要多少个1/fosc 时间后中断一下。
static void BSP_CoreClockInit(void)
{
SysTick_Config(SystemCoreClock / 100);
}
SystemCoreClock / 100 代表的是时钟次数,即放入重装载寄存器中的值,每计时一个数需要 1/72000000s 的时间,那么计SystemCoreClock / 100 (SystemCoreClock 为 72M)个数的时间就是 10ms,通过在中断中设置标志位达到 systick 定时中断的功能。
滴答定时器实现延时
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD = 9*nus;
SysTick->VAL=0X00;
SysTick->CTRL=0X01;
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&(!(temp&(1<<16))));
SysTick->CTRL=0x00;
SysTick->VAL =0X00;
}
9*nus :假设外设频率为 9M,也就是经过 8 分频,那么计数 9 次是 1us,乘以 9 的意义就是参数的时间对应的次数,也就是重装载值
实现定时器中断
目的介绍
使用定时器产生中断,然后在中断服务函数里面翻转 DS1 上的电平,来指示定时器中断的产生。 接下来我们以通用定时器 TIM3 为实例,来说明要经过哪些步骤,才能达到这个要求,并产生中断。 这里我们就对每个步骤通过库函数的实现方式来描述。 定时器相关的库函数主要集中在固件库文件 stm32f10x_tim.h 和 stm32f10x_tim.c 文件中
TIM3 时钟使能
TIM3 是挂载在 APB1 之下,所以我们通过 APB1 总线下的使能使能函数来使能 TIM3。调用函数: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
初始化定时器参数
在库函数中,定时器的初始化参数是通过初始化函数 TIM_TimeBaseInit 实现的: voidTIM_TimeBaseInit(TIM_TypeDefTIMx,TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct); 第一个参数是确定是哪个定时器。 第二个参数是定时器初始化参数结构体指针,结构体类型为 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;
这个结构体一共有 5 个成员变量,对于通用定时器只有前面四个参数有用, 最后一个参数 TIM_RepetitionCounter 是高级定时器才有用的,这里不多解释。
TIM_Prescaler
设置分频系数的。prescaler 是用来分频来自APBx的时钟频率,然后提供给定时器,作为定时器的心跳。一个16位正整数值
- 预分频寄存器(TIMx_PSC)
TIM_CounterMode
设置计数方式,可以设置为向上计数,向下计数方式还有中央对齐计数方式。
#define TIM_CounterMode_Up
#define TIM_CounterMode_Down
#define TIM_CounterMode_CenterAligned1 ((uint16_t)0x0020)
#define TIM_CounterMode_CenterAligned2 ((uint16_t)0x0040)
#define TIM_CounterMode_CenterAligned3 ((uint16_t)0x0060)
#define IS_TIM_COUNTER_MODE(MODE) (((MODE) == TIM_CounterMode_Up) || \
((MODE) == TIM_CounterMode_Down) || \
((MODE) == TIM_CounterMode_CenterAligned1) || \
((MODE) == TIM_CounterMode_CenterAligned2) || \
((MODE) == TIM_CounterMode_CenterAligned3))
TIM_Period
第三个参数是设置自动重载计数周期值。
TIM_ClockDivision
设置时钟分频因子。ClockDivision是对于输入的分频,在输入捕获的时候要用到,决定数字滤波器采样频率的参数。
#define TIM_CKD_DIV1 ((uint16_t)0x0000)
#define TIM_CKD_DIV2 ((uint16_t)0x0100)
#define TIM_CKD_DIV4 ((uint16_t)0x0200)
#define IS_TIM_CKD_DIV(DIV) (((DIV) == TIM_CKD_DIV1) || \
((DIV) == TIM_CKD_DIV2) || \
((DIV) == TIM_CKD_DIV4))
TIM3 初始化范例代码格式:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 5000;
TIM_TimeBaseStructure.TIM_Prescaler =7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
中断时间
系统初始化的时候在默认的系统初始化函数 SystemInit函数里面已经初始化 APB1 的时钟为2 分频,所以 APB1 的时钟为 36M 从 STM32 的内部时钟树图得知:当 APB1 的时钟分频数为 1 的时候,TIM2~7 的时钟为 APB1 的时钟,而如果 APB1 的时钟分频数不为 1,那么 TIM2~7 的时钟频率将为 APB1 时钟的两倍。因此,TIM3 的时钟为 72M,再根据我们设计的 arr 和 psc 的值,就可以计算中断时间了。计算公式如下: Tout= ((arr+1)*(psc+1))/Tclk; 其中: Tclk:TIM3 的输入时钟频率(单位为 Mhz)。 Tout:TIM3 溢出时间(单位为 us)。 TIM3溢出中断,当 TIM3_CNT 的值等于 TIM3_ARR 的值的时候,就会产生 TIM3 的更新中断
设置 TIM3_DIER 允许更新中断
因为我们要使用 TIM3 的更新中断,寄存器的相应位便可使能更新中断。 在库函数里面定时器中断使能是通过 TIM_ITConfig 函数来实现的:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
例如我们要使能 TIM3 的更新中断,格式为:
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
TIMx
选择定时器号
TIM_IT
使能的定时器中断的类型,定时器中断的类型有很多种,包括更新中断 TIM_IT_Update,触发中断 TIM_IT_Trigger,以及输入捕获中断等等。
#define TIM_IT_Update ((uint16_t)0x0001)
#define TIM_IT_CC1 ((uint16_t)0x0002)
#define TIM_IT_CC2 ((uint16_t)0x0004)
#define TIM_IT_CC3 ((uint16_t)0x0008)
#define TIM_IT_CC4 ((uint16_t)0x0010)
#define TIM_IT_COM ((uint16_t)0x0020)
#define TIM_IT_Trigger ((uint16_t)0x0040)
#define TIM_IT_Break ((uint16_t)0x0080)
NewState
第三个参数就很简单了,就是失能还是使能。
TIM3 中断优先级设置
在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,设置中断优先级。之前多次讲解到用 NVIC_Init 函数实现中断优先级的设置,这里就不重复讲解。
使能 TIM3
我们在配置完后要开启定时器,通过 TIM3_CR1 的 CEN 位来设置。 在固件库里面使能定时器的函数是通过 TIM_Cmd 函数来实现的: void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState) 这个函数非常简单,比如我们要使能定时器 3,方法为: TIM_Cmd(TIM3, ENABLE); //使能 TIMx 外设
编写中断服务函数
最后,编写定时器中断服务函数,通过该函数来处理定时器产生的相关中断。 在中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。 然后执行相关的操作 这里使用的是更新(溢出)中断,所以在状态寄存器 SR 的最低位。 在处理完中断之后应该向 TIM3_SR 的最低位写 0,来清除该中断标志。
在固件库函数里面,用来读取中断状态寄存器的值判断中断类型的函数是: ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t) 该函数的作用是,判断定时器 TIMx 的中断类型 TIM_IT 是否发生中断。 比如,我们要判断定时器 3 是否发生更新(溢出)中断,方法为: if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){}
固件库中清除中断标志位的函数是: void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT) 该函数的作用是,清除定时器 TIMx 的中断 TIM_IT 标志位。 比如我们在TIM3 的溢出中断发生后,我们要清除中断标志位,方法是: TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); 这里需要说明一下,固件库还提供了两个函数用来判断定时器状态以及清除定时器状态标志位的函TIM_GetFlagStatus 和 TIM_ClearFlag,他们的作用和前面两个函数的作用类似。只是在 TIM_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而TIM_GetFlagStatus 直接用来判断状态标志位。
|