向下计数模式(时钟分频因子=1)
向上计数模式(时钟分频因子=1)
中央对齐计数模式(时钟分频因子=1 ARR=6)
计数器当前值寄存器CNT 该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。
预分频寄存器TIMx_PSC 该寄存器用设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。 这里,定时器的时钟来源有4 个:
1) 内部时钟(CK_INT)
2) 外部时钟模式 1:外部输入脚(TIx)
3) 外部时钟模式 2:外部触发输入(ETR)
4) 内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。
这些时钟,具体选择哪个可以通过TIMx_SMCR 寄存器的相关位来设置。这里的CK_INT
时钟是从 APB1 倍频的来的,除非APB1 的时钟分频数设置为1,否则通用定时器TIMx 的时钟是APB1 时钟的2 倍,当APB1 的时钟不分频的时候,通用定时器TIMx 的时钟就等于APB1的时钟。
自动重装载寄存器(TIMx_ARR)
控制寄存器1(TIMx_CR1) 首先我们来看看TIMx_CR1 的最低位,也就是计数器使能位,该位必须置 1,才能让定时器开始计数。 从第 4 位 DIR 可以看出默认的计数方式是向上计数,同时也可以向下计数,第5,6位是设置计数对齐方式的。 从第 8 和第 9 位可以看出,我们还可以设置定时器的时钟分频因子为1,2,4。
DMA中断使能寄存器(TIMx_DIER) 这里我们同样仅关心它的第0 位,该位是更新中断允许位, 本章用到的是定时器的更新中断, 所以该位要设置为 1,来允许由于更新事件所产生的中断。
1) TIM3 时钟使能。
TIM3 是挂载在 APB1 之下,所以我们通过 APB1 总线下的使能使能函数来使能 TIM3。调用的函数是:
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
复制代码 2) 初始化定时器参数,设置自动重装值, 分频系数,计数方式等。
在库函数中,定时器的初始化参数是通过初始化函数 TIM_TimeBaseInit 实现的:
- voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,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 是用来设置分频系数的,刚才上面有讲解。第二个参数 TIM_CounterMode 是用来设置计数方式,上面讲解过,可以设置为向上计数,向下计数方式还有中央对齐计数方式, 比较常用的是向上计数模式 TIM_CounterMode_Up 和向下计数模式 TIM_CounterMode_Down。第三个参数是设置自动重载计数周期值,这在前面也已经讲解过。第四个参数是用来设置时钟分频因子。针对 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);
复制代码 3) 设置 TIM3_DIER 允许更新中断。因为我们要使用 TIM3 的更新中断, 寄存器的相应位便可使能更新中断。 在库函数里面定时器中断使能是通过 TIM_ITConfig 函数来实现的:
- void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
复制代码 第一个参数是选择定时器号,这个容易理解,取值为 TIM1~TIM17。
第二个参数非常关键,是用来指明我们使能的定时器中断的类型,定时器中断的类型有很多种,包括更新中断 TIM_IT_Update,触发中断 TIM_IT_Trigger,以及输入捕获中断等等。
第三个参数就很简单了, 就是失能还是使能。例如我们要使能 TIM3 的更新中断,格式为:
- TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
复制代码 4) TIM3 中断优先级设置。
在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器, 设置中断优先级。之前多次讲解到用 NVIC_Init 函数实现中断优先级的设置,这里就不重复讲解。
5) 允许 TIM3 工作,也就是使能 TIM3。
光配置好定时器还不行,没有开启定时器,照样不能用。我们在配置完后要开启定时器,通过 TIM3_CR1 的 CEN 位来设置。 在固件库里面使能定时器的函数是通过 TIM_Cmd 函数来实
现的:
- void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
复制代码 这个函数非常简单,比如我们要使能定时器 3, 方法为:
- TIM_Cmd(TIM3, ENABLE); //使能 TIMx 外设
复制代码 6) 编写中断服务函数。
- 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 );
复制代码
timer.h
- #ifndef __TIM_H
- #define __TIM_H
- #include "sys.h"
- void TIM3_Init(u16 arr,u16 psc);
- #endif
复制代码
timer.c
- #include "time.h"
- #include "led.h"
- void TIM3_Init(u16 arr,u16 psc)
- {
-
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitstruct;
- NVIC_InitTypeDef NVIC_InitStructure;
-
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
-
- TIM_TimeBaseInitstruct.TIM_ClockDivision=TIM_CKD_DIV1;
- TIM_TimeBaseInitstruct.TIM_CounterMode=TIM_CounterMode_Up;
- TIM_TimeBaseInitstruct.TIM_Period=arr;
- TIM_TimeBaseInitstruct.TIM_Prescaler=psc;
- TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitstruct);
-
- TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
-
- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //使能按键TIME3所在的中断通道
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级3
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能TIM3中断通道
- NVIC_Init(&NVIC_InitStructure);
- TIM_Cmd(TIM3, ENABLE);
-
-
- }
- void TIM3_IRQHandler()
- {
- if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
- {
- LED1=!LED1;
- TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
-
- }
-
- }
复制代码
main.c
- #include "stm32f10x.h"
- #include "led.h"
- #include "delay.h"
- #include "time.h"
- int main()
- {
- delay_init();
- LED_Init();
- TIM3_Init(4999,7199);//500ms 一开始有这个警告implicit declaration of function "TIM_Init"is invalid in c99,(也能编译成功)莫名其妙没有了
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- while(1)
- {
-
- LED0=!LED0;
- delay_ms(200);
-
- }
-
- }
复制代码
在前面时钟系统部分我们讲解过, 系统初始化的时候在默认的系统初始化函数 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)。
根据上面的公式,我们可以算出中断
溢出时间为 500ms,即 Tout= ((4999+1)*( 7199+1))/72=500000us=500ms。
|