本文为野火学习笔记。
定时器分类
stm32f1系列。除互联型设备,共有8个定时器,分为基本,通用,高级3种定时器。不同的定时器有不同的功能。
基本定时器为 TIM6,TIM7 。其只能定时,没有外部IO,且16位计数器只能向上计数。
通用定时器为 TIM2/3/4/5 , 可以定时;也可输出比较和输入捕捉,每个定时器有4个IO,16位计数器可上下计数。
高级定时器为 TIM1/8 ,在通用的基础上多了互补输出信号的功能,每个定时器有8个IO口。
功能框图
下图为基本定时器的结构框图:
- 时钟源
定时器的时钟源名为 TIMxCLK,可以在时钟树中找到

可见 TIMxCLK 时钟是来自于APB1 的,而 APB1 的预分频器一般选择2分频,使得 APB1 总线的时钟为36M;因此,TIMExCLK 的时钟为72M.
-
计数器时钟
时钟接入触发控制器,经过 PSC 预分频器分频,驱动CNT计数器。 PSC 为16位分配器,可对时钟信号进行1~65536分频。最终计数器的时钟计算公式如下。
C K _ C N T = T I M x C L K P S C + 1 CK\_CNT ~= ~\frac{TIMxCLK}{PSC+1} CK_CNT = PSC+1TIMxCLK
公式中分母+1的操作是官方规定的。 -
计数器
CNT 为16位计数器,只能向上计数,计数达到指定数时,更新事件,清零从头计数。 -
自动重载寄存器
寄存器中储存着计数最大值,计到此数,若配置了中断,会产生中断。 -
影子寄存器
自动重载寄存器ARR和预分频器PSC下有阴影,表示其有影子寄存器。影子寄存器起到缓冲的作用,若在计数器仍在计数时,修改ARR或PSC的值,如果直接写入,则计数器直接清零,重新开始计数。若向影子寄存器写入,则会在当前计数周期结束后,修改值,计数器不会被打断。
定时器时间计算
每记一次耗费时间为
1
C
K
_
C
L
K
\frac{1}{CK\_CLK}
CK_CLK1,计数器计满一次所需时间为
A
R
R
C
K
_
C
L
K
\frac{ARR}{CK\_CLK}
CK_CLKARR,即为:
A
R
R
P
S
C
+
1
T
I
M
x
C
L
K
ARR\frac{PSC+1}{TIMxCLK}
ARRTIMxCLKPSC+1
当计时器时钟为 72M,PSC预分频器为71,自动重装载ARR为1000时,计满一次的时间即为
1000
72
72
M
=
1
m
s
1000\frac{72}{72M}~=1ms
100072M72 =1ms.
中断
基本定时器只在溢出时才会产生中断,溢出时也可产生DMA请求。TIMx_DIER寄存器控制DMA和中断的使能,TIMx_EGR 寄存器记录溢出事件的产生。
固件库编程
定时器初始化结构体
typedef struct
{
uint16_t TIM_Prescaler; //预分频器
uint16_t TIM_CounterMode; //计数模式
uint16_t TIM_Period; //ARR
uint16_t TIM_ClockDivision; //时钟分频
uint8_t TIM_RepetitionCounter; //重复计数器
} TIM_TimeBaseInitTypeDef;
在这里,计数模式表示的是向上还是向下计数。最后两个参数是不是基本定时器的功能,不用配置。
开始编程
基本寄存器十分简单,只能计时。这里编写一个使得LED以1秒间隔闪烁的程序。
初始化TIM6
void BASIC_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
//BASIC_TIM_NVIC_Config(); //后面加上这句
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);
TIM_TimeBaseStruct.TIM_Period = 1000;
TIM_TimeBaseStruct.TIM_Prescaler = 71;
TIM_TimeBaseInit(TIM6,&TIM_TimeBaseStruct);
//清除中断标志位
TIM_ClearFlag(TIM6,TIM_FLAG_Update);
//使能中断请求
TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);
//使能计数器
TIM_Cmd(TIM6,ENABLE);
//暂时关闭时钟,等待使用
}
基本定时器只能向上计数,因此,初始化结果体中只需配置 PSC 和ARR。配置PSC=71,ARR=1000。即计数一次为1ms。
接下来我们希望在计数完成发生中断,按照 中断应用总结 中的步骤,使能外设中断。
使用 TIM_ClearFlag
清除中断标志位,计数溢出的事件被称为更新事件,对应固件库中的标志位叫 TIM_FLAG_Update
。然后使用 TIM_ITConfig
使能更新事件的中断。
最后使用 TIM_Cmd
使能定时器。这里我们完成了TIM6的初始化和外设中断使能,下面继续配置中断。
配置中断优先级分组
因程序只有这一个中断,优先级分组并不重要,这里配置中断优先级分组为0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
配置NVIC
static void BASIC_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStruct.NVIC_IRQChannel = TIM6_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&NVIC_InitStruct);
}
配置主优先级为0,子优先级为3。在初始化 TIM6 中使用这个函数。
中断服务函数
在 startup_stm32f10x_hd.s 中找到TIM6中断的函数名,在 stm32f10x_it.c 编写中断服务函数。
extern volatile uint16_t time;
void TIM6_IRQHandler(void)
{
if(TIM_GetITStatus(TIM6,TIM_IT_Update) != RESET)
{
time++;
TIM_ClearITPendingBit(TIM6,TIM_IT_Update);//清除中断标志位
}
}
进入中断后,检查是否真的中断,并设一个全局变量 time, 用来完成1s的记录。最后使用 TIM_ClearITPendingBit
清除中断标志位。
main函数
#define LED_G_GPIO_PIN GPIO_Pin_0
#define LED_G_GPIO_PORT GPIOB
#define LED_G_TEOOGLE {LED_G_PPIO_RORT->ODR ^= LED_G_GPIO_PIN;}
volatile uint16_t time=0;
int main(void)
{
LED_GPIO_Config();
BASIC_TIM_Config();
while(1)
{
if (time ==1000)//1s一进
{
time = 0;//计时归零
LED_G_TEOOGLE;//led反转
}
}
}