在上一篇 STM32外部中断的理解 中,我们讲述了stm32的外部中断,它是通过外部的一个中断信号作为中断源,对 CPU 进行申请中断处理的;此篇,我们再讲述下 stm32 的内部定时器中断。
1. 综述
stm32 定时器中断,是通过设置内部的定时器相关寄存器,然后定时器进行自增(自减)到某一个数之后,产生一个中断信号,由 cpu 进行处理。如下图:
其中,时基单元部分相关的寄存器就是我们需要设置的。
下图是基本定时器框图:
2. 名词解析
CK_PSC : 定时器的预分频器时钟源。这个值为系统时钟频率,在手册中,虽然 TIM2 是属于 APB1 外设,APB1 外设的时钟频率为36MHz,但是我们使用的库中,在 SystemInit
函数中,将所有的定时器都倍频到了72MHz,所以这个值为72MHz。
PSC : 定时器的预分频器寄存器(Prescaler Register)。在 stm32 中,预分频器用于将定时器的时钟源分频,以降低定时器的时钟频率。
CK_CNT : 定时器计数器的时钟源。CK_CNT = CK_PSC / (PSC + 1)
CNT : 定时器的计数器寄存器。用于记录定时器的计数值,根据定时器的时钟源逐渐递增或递减,计数器增加达到最大值时会自动重置为0,并触发相应的中断事件。CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)
ARR: 自动重装载寄存器(Auto-Reload Register)
3. 源码解析
我们这个实例演示了,通过定时器每隔1秒产生一个中断,并在 OLED 屏幕上进行显示。要达到这个功能,主要的核心是设置好 PSC
和 ARR
。
源码:
#include "Timer.h"
uint16_t num = 0;
void Timer_Init(void)
{
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;
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 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_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM2, ENABLE);
}
uint16_t Timer_Get(void)
{
return num;
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
3.1 使能 TIM2 外设时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM2 属于 APB1 外设。
3.2 配置时钟源
TIM_InternalClockConfig(TIM2);
TIM2 作为内部时钟源,并且时钟频率是72MHz。若不调用此函数,TIM默认也为内部时钟
3.3 配置时基单元
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; // 预分频器,即 PSC 的值
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // 重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
ARR: 10000 - 1, PSC:7200 - 1, 此时我们可以计算出计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)
:
CK_CNT_OV = 72MHz/(7200 - 1 + 1)/(10000 - 1 + 1) = 1 Hz
即:1秒产生1次溢出
预分频器确定好后,我们就可以通过设置 ARR 值,来产生不同时间的定时器。
3.4 开启TIM2的更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
3.5 配置 NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
具体的解释参考:STM32外部中断的理解
3.6 TIM 使能
TIM_Cmd(TIM2, ENABLE);
使能TIM2,定时器开始运行
3.7 中断处理
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
至此,基本上整个定时器中断的流程就解析完成了。