【STM32】TIM定时器中断与通用计时器+内部时钟源代码

引子

STM32中的TIM能够让我们做一些定时操作,TIM就是定时器的简称,这次就来研究一下STM32中定时器是如何工作的。

基本定时器

基本定时器的结构是这样的:

通过来自RCC的时钟,输入到触发控制器。在一般情况下,触发控制器会输出一个计数的信号,然后让CNT计数器自增。当CNT计数器中的数字达到了自动重装载寄存器的值的时候,就产生一个中断或者事件。在这个结构中,预分频器、计数器和自动重装载寄存器都是16位的计数器。

这里,预分频器和自动重装载寄存器都是有阴影的,这表明它们各自是两个寄存器,一个寄存器叫做影子寄存器,我们没有办法修改,这是由内部电路使用的寄存器;另一个寄存器就是单纯供我们读写的寄存器,当满足一定条件的时候,这些寄存器的值会写入影子寄存器。

在这个结构中,预分频器的作用就如同字面意思,是对来自时钟的信号频率进行一个预先的分频,这是因为,来自时钟的信号频率通常都比较大,所以需要进行一个预先的分频来适应比较小的定时间隔。这里,预分频器通过不断地计数,当满足一个值的时候,就输出一个计数信号,让最后的计数器进行计数。

也就是说,每来一个时钟信号,那么预分频器就加1,如果到达了一个预先设定的值,就清零,然后输出一个计数信号,让CNT计数器计数。

那么最后的频率就非常好计算了。最后的频率就等于时钟频率除以预分频的系数,再除以自动重装载寄存器的系数。实际存储的过程中,预分频和自动重装载寄存器的值都要是系数减一。

通用定时器

通用定时器的结构如下图所示:

这里我们从左往右看,首先最左边展示了我们可以使用的时钟源,分别是内部时钟、TIMx_ETRTIMx_CHx

当使用内部时钟时,我们发现,如果忽略捕获/比较寄存器的话,整个流程并没有变化。

当使用TIMx_CHx,也就是定时器的输入通道的时候,信号首先经过了输入滤波器和边沿检测器,这是为了对输入信号进行初步的筛选和处理,随后输入会有两条路可以选,分别输入到不同的选择器中,进行不同的计数策略。

这部分的结构抽象起来就是如下所示的样子:

选择时钟源,然后进行基本定时器的计数(也叫做时基单元),随后输出中断,走向NVIC。

代码编写

代码的编写可以分为这么几个步骤:时钟使能->时钟源选择->时基单元的初始化->中断初始化->开启时钟->中断函数的编写。

这里我实现了一个LED小灯1s开1s关交替的代码。

时钟使能

根据不同的定时器选择不同的时钟开启,我这里是使用了APB1总线,所以要开启APB1总线的时钟:

1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

时钟源选择

随后选择目标时钟源:

1
TIM_InternalClockConfig(TIM2);

时基单元的初始化

这里我们可以设置定时的时间:

1
2
3
4
5
6
7
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

首先创建一个结构体,然后确认滤波器的分频系数(TIM_ClockDivision),我们这里没有应用,所以随便设置一个即可,随后设置计数的模式,是一直向上计数,向下计数,还是其它的模式,这里我选择了一直向上计数计数到目标值产生中断并且清零,随后确认定时器的自动重装载寄存器的值以及预分频器的值,这里我的目的是计数1s,所以最终的频率要为1Hz,所以我给了自动重装载寄存器的系数为10000,预分频器的系数设为了7200,然后因为实际的值中,要是系数减一,所以做出了这样的设置,对于重复计数器来说,通用寄存器没有这个功能,所以随便设为一个值即可。最后,将计时器进行初始化即可。

中断初始化

在这里,中断的初始化就和之前提到的一样,这里的代码如下所示:

1
2
3
4
5
6
NVIC_InitTypeDef NVIC_InitSturcture;
NVIC_InitSturcture.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitSturcture.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitSturcture.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitSturcture.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitSturcture);

这里,我们指定所需要的中断是来自TIM2的中断,随后给相关的总线使能,并且设置一个抢占优先级和响应优先级即可,最后初始化。

开启时钟

代码如下:

1
TIM_Cmd(TIM2, ENABLE);

中断函数的编写

对于中断函数来说,如下编写即可:

1
2
3
4
5
6
void TIM2_IRQHandler() {
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
is_led_on = ~is_led_on;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}

这里,我首先检查中断标志位,随后将一个全局变量进行变换,传递给主函数进行LED小灯的开关操作,最后将相关的标志位清零即可。

文章作者:
文章链接: https://www.coderlock.site/2025/12/18/【STM32】TIM定时器中断/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 寒夜雨