目录
一、概述
二、定时器的类型
三、时序
四、定时器中断基本结构
五、定时器定时中断代码
六、定时器外部时钟代码
一、概述
TIM(Timer)定时器
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
- 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHZ计数时钟下可以实现最大559.65s的定时(stm32级联两个16位计数器)
- 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
二、定时器的类型
- 基本定时器:拥有定时中断、主模式触发DAC的功能
如上图,内部时钟的来自RCC的TIMxCLK,这里的频率值一般都是系统的主频72MHZ,也就是基准时钟。自动重装载寄存器、PSC预分频器和CNT计数器组成叫做时基单元,通向时基单元的计数基准频率就是72MHZ。
预分频器:如果预分频器写0,就是1分频,输出频率=输入频率=72MHZ;如果预分频器写1,就是2分频,输出频率=输入频率/2=36MHZ;依次类推,所以预分频器的值和实际的分频系数相差了1,预分频系数=预分频器值+1。这个预分频器是16位,最大值就是65535,也就是65536分频。
CNT计数器:对预分频器后的时钟进行计数,计数时钟每来一个上升沿,计数器的值加1。这个计数器也是16位的。计数器从0开始加,加到65535时或目标值时就会从0开始重新加。
自动重装载寄存器:也是16位的,它存的就是我们写入的计数目标值。自动重装值是固定的目标值,当计数值等于自动重装值时,也就是计时时间到了,会产生一个中断信号,并且清零计数器,计数器自动开始下一次的计数计时。
向上的箭头UI:当计数值等于自动重装值时,产生的一个中断信号,我们叫做它更新中断,之后就会通往NVIC,我们再配置好NVIC的定时器通道,那定时器的更新中断就能得到CPU的响应了。
向下的箭头UI:代表会产生一个事件,这里对应的事件就叫做 "更新事件",更新事件不会触发中断,但可以触发内部其他电路的工作
- 通用定时器:拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能。
如上图,通用定时器就比基本定时器复杂的多了。
基本定时器计数模式只有向上计数模式,通用和高级定时器计数模式:向上计数、向下计数、中央对齐。
以预分频器为分界线,最上面的部分就是内外时钟源选择和主从触发模式的结构了。内部时钟由主频72MHZ产生。
外部时钟输入:
TI1F_ED连接的是输入捕获单元的CH1引脚,ED(Edge)就是边沿的意思,上升沿和下降沿均有效。最后,这个时钟还能通过TI1FP1和TI2FP2获得。
编码器接口可以读取正交编码器的输出波形。
TRGO那部分电路,可以把内部的一些事件映射到这个TRGO引脚上,比如也可以把定时器内部的一些事件映射到这里来,用于触发其他定时器、DAC或ADC。
右下部分,捕获/比较寄存器、输出控制。
捕获/比较寄存器是输入捕获和输出比较共用的。
- 高级定时器暂时不用。
三、时序
- 预分频器时序
CNT_EN :计数器使能,高电平计数正常运行,低电平计数器停止。
CK_CNT:预分频器输出时钟,也是计数器时钟。
计数器寄存器:在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增。
预分频缓冲器:如果对正在计数的计数频率进行分频,会在计数完成之后,下一次计数才会改变。
计数器计数频率:CK_CNT=CK_PSC/(PSC+1);PSC是预分频的值。
- 计数器时序
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1);
时间 t=1/CK_CNT_OV=(PSC + 1)x (ARR + 1)/CK_PSC;
在配置寄存器时PSC一般设置为7200-1;
t=7200*(ARR+1)/72000000=(ARR+1)/10000=(ARR+1)x0.1ms
ARR为自动重装寄存器
- 计数器无预装时序
如图所示,原来目标值为FF,然后修改为36,然后到达36就会发生更新。
- 计数器有预装时序
如图所示,多了一个影子寄存器。原来目标值为F5,然后加了影子寄存器,不会到达36发生更新,而到达F5发生更新,而是在下一次更新中断或事件才开始生效。加入影子寄存器的目的是让值得变化和更新事件同步发生,防止在运行途中更改造成错误。
四、定时器中断基本结构
如下图:
五、定时器定时中断代码
- 配置时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
- 配置内部时钟
TIM_InternalClockConfig(TIM2);
- 配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //滤波频率
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //计数方式
TIM_TimeBaseInitStruct.TIM_Period=10000-1; //自动重装载寄存器ARR
TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1; //预分频器
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
- 配置中断输出控制
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //启动中断
- 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //从启动文件找后缀为md.s
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStructure);
- 启动定时器
TIM_Cmd(TIM2,ENABLE);
- 中断服务函数
void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //更新中断就是产生一个中断标志位{Num++;TIM_ClearITPendingBit(TIM2,TIM_IT_Update); }}
完整代码,如下:
Timer.c:
#include "stm32f10x.h" // Device headerextern uint16_t Num;
void Timer_Init(void)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;NVIC_InitTypeDef NVIC_InitStructure;//1.配置时钟,用那个外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//2.内部时钟配置TIM_InternalClockConfig(TIM2);//3.配置时基单元TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //滤波频率TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //计数方式TIM_TimeBaseInitStruct.TIM_Period=10000-1; //自动重装载寄存器ARR,定时1sTIM_TimeBaseInitStruct.TIM_Prescaler=7200-1; //预分频器TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0; //这个是高级定时器才用的,这里不用,给0TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);TIM_ClearFlag(TIM2,TIM_FLAG_Update);//4.配置中断输出控制,打开中断TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//5.NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStructure);//6.启动定时器TIM_Cmd(TIM2,ENABLE);
}void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){Num++;TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //更新中断就是产生一个中断标志位}}
Timer.h:
#ifndef _TIMER_H
#define _TIMER_Hvoid Timer_Init(void);#endif
main.c:
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "delay.h"
#include "Timer.h"uint16_t Num=0;int main(void)
{OLED_Init();Timer_Init();OLED_ShowString(1,1,"Num:");while(1) {OLED_ShowNum(1,5,Num,4);OLED_ShowNum(2,5,TIM_GetCounter(TIM2),4); //TIM_GetCounte()用来获得计数器的值}}
运行起来,会有一点问题,就是每次复位Num的值都是从1开始,而不是从0开始。这是因为啥呢?
可以查看时基单元函数TIM_TimeBaseInit(),最后有这么一句话,如下图:绿字意思位:会立即产生一个更新事件去重装载预分频器和重复计数器。
更新时事的同时,也会更新中断,产生中断让预分频器的值立即生效。这是由于预分频缓冲器的存在,我们写入的值不会立即生效,需要在下一计数开始才生效。而系统是想让预分频器的值立即生效,所以会产生一次中断,产生了一个中断标志位,我们需要在中断初始化之前要进行清除。需要用到清除标志位函数,在启动中断之前进行初始化:
TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清除中断标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //启动中断
这样每次复位Num的值都是从0开始。
六、定时器外部时钟代码
把内部时钟变为外部时钟输入,其他部分基本不变。然后利用对射式红外传感器模拟外部时钟输入。
- 配置时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
- GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; //PA0口
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);
- 外部时钟配置
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
- 配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //滤波频率
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //计数方式,向上计数
TIM_TimeBaseInitStruct.TIM_Period=10-1; //自动重装载寄存器ARR,需要手动模拟,值不要太大
TIM_TimeBaseInitStruct.TIM_Prescaler=1-1; //预分频器
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
- 配置中断输出控制,打开中断
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
- NVIC配置
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStructure);
- 启动定时器
TIM_Cmd(TIM2,ENABLE);
- 中断服务函数和内部时钟的服务函数共用
void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){Num++;TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //更新中断就是产生一个中断标志位}}
完整代码,如下:
Timer.c:
#include "stm32f10x.h" // Device headerextern uint16_t Num;
void Timer_Init(void)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;NVIC_InitTypeDef NVIC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;//1.配置时钟,用那个外设RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//2.外部时钟配置TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);//3.配置时基单元TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //滤波频率TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //计数方式TIM_TimeBaseInitStruct.TIM_Period=10-1; //自动重装载寄存器ARR,需要手动模拟,值不要太大TIM_TimeBaseInitStruct.TIM_Prescaler=1-1; //预分频器TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);TIM_ClearFlag(TIM2,TIM_FLAG_Update);//4.配置中断输出控制,打开中断TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//5.NVIC配置NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStructure);//6.启动定时器TIM_Cmd(TIM2,ENABLE);
}void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){Num++;TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //更新中断就是产生一个中断标志位}}
Timer.h:
#ifndef _TIMER_H
#define _TIMER_Hvoid Timer_Init(void);#endif
main.c:
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "delay.h"
#include "Timer.h"uint16_t Num=0;int main(void)
{OLED_Init();Timer_Init();OLED_ShowString(1,1,"Num:");OLED_ShowString(2,1,"CNT:");while(1) {OLED_ShowNum(1,5,Num,4);OLED_ShowNum(2,5,TIM_GetCounter(TIM2),4); //获取计数器CNT中计数值}}