STM32定时中断

继续跟着 江科大自化协 学习STM32。

注意

有的定时器使用APB1,有的定时器使用APB2

前置知识

TIM(Timer)定时器
16位计数器、预分频器(Pre-Scaler)、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
如果觉得定时器时间不够,可以用定时器级联,最大定时时间将会变为$59.65*65536^2$
还不够?还可以再级联一个……最大定时时常变为几亿年……

定时器分为高级定时器、通用定时器、基本定时器三中类型:
定时器类型
对于STM32F103C8T6而言,它拥有TIM1、TIM2、TIM3、TIM4
定时中断的基本结构

细谈定时器

如何看图

带有“影子”的寄存器,都是带有缓冲寄存器的寄存器。

缓冲寄存器?

缓冲寄存器又名影子寄存器
有了缓冲寄存器,只有当更新事件发生时,寄存器本体才会将自己的值更新为缓冲寄存器中写入的值。
用不用缓冲寄存器可以设置。
通过设置ARPE位,可以选择是否使用预装功能。

基本定时器

基本定时器
PSC、CNT、自动重装载寄存器构成了时基单元。时基单元由内部时钟(单片机上的晶振)提供信号,通过分频器不断输出高电平信号使得CNT自增加一。

实际分频系数=预分频器系数+1

CNT是16位的。
自动重装寄存器(ARR,Auto Reload Register):写入的计数目标。
当CNT=自动重装值时,CNT清零,并会产生一个中断,这个中断成为更新中断,发往NVIC。用带折线箭头的UI来表示这一中断信号。同时还会产生一个更新事件,可以触发内部其他电路的工作。
基本定时器的主从模式触发,能在不受程序的控制下运行。

例如,主模式触发DAC,可以利用更新事件,让更新信号传输到TRGO(Trigger Out)的位置,TRGO接到DAC的触发引脚上,进行转换。不需要软件参与,不干扰正常程序的运行,也不会影响到其它中断。

通用定时器

通用定时器
通用定时器的时基单元与基本定时器相同。
计数器的技术模式不止向上计数这一种。还有向下计数中央对齐模式

  • 向下计数:从自动重装寄存器的值开始,当高电平计数信号(时钟信号)输入时,计数器减一
  • 中央对齐模式:(计数信号/时钟信号不断输入时)CNT从0开始增加,到自动重装寄存器的值时触发中断和事件,然后开始减少,回到0时又会触发一次中断和事件,然后再开启下一轮循环。

定时中断、内外时钟源、触发输入

如果选择外部时钟,那么时钟信号需要通过ETR引脚输入。也可以通过CH1引脚的边沿(Edge)获得时钟。
如果需要触发输入,那么应该用TRGI。当然,时钟级联也可以走这条通道。
如果要时钟级联:
初始化TIM3为主模式,将它的更新事件映射到TRGO上;然后初始化TIM2,选择ITR2,选择外部时钟模式1,并让对应TIM3的TRGO。
始终还可以通过TI1FPn获得(如TI1FP1)。TI1FP1(2)可以读取正交编码器的输出波形。

复杂的电路主要是为了扩展和方便一些特殊的使用场景
外部时钟通过ETR引脚输出即可

输入捕获和输出比较寄存器

捕获/比较n寄存器左侧是输入捕获电路,右侧是输出比较电路。
有四个接口:CH1~CH4
输入捕获和输出比较不能同时使用,所以输出接口共用。

高级定时器

高级定时器
和通用寄存器大部分相似。
加了个重复次数计数器,可以实现每隔几个计数周期去发生一次更新或中断。相当于对计数器的输出分了一次频。
右侧添加了几个互补的输出接口,能输出相反的PWM波。三相无刷电机狂喜。

DTG(Dead Time Generate,死区生成器):为了防止直通现象,在CHn开关的一瞬间,产生一定时长的死区,使得这对互补的引脚都关闭,防止直通现象。
刹车输入:给电机驱动提供安全保障,若BKIN输入,或内部时钟失效,则会自动切断输出,保护电机驱动。

时序

预分频器时序

即使预分频制寄存器从0变成了1,STM32的分频器也要等到触发更新事件后才会改变分频系数。
也就是说,当触发更新事件后,改变后的分频值才会起作用。
预分频器时序

计数器技术频率:

就是前面的那条Warning

计时器时序

注意

更新中断会带带有更新中断标志(UIF),记得Clear一下

计时器时序
计数器溢出频率:

这就不得不要提一下计数器无预装时序和有预装时序了

无预装时序

咕咕咕,我就放个图片
无预装时序

有预装时序

咕咕咕,我再放个图片
有预装时序

RCC时钟树

来看看RCC时……我滴妈耶!
RCC时钟树
但是感谢ST,他们封装了函数SystemInit()

时钟安全系统

简称CSS(Clock Security System),负责切换时钟,监测外部时钟的运行状态,如果外部的失效,自动切换成内部的。
在刹车输入时,也有CSS的身影。

注意

如果你发现自己的计时器满了大概十倍左右,那么可能是外部72MHz晶振失效了,系统自动启用了内部的8MHz晶振

时钟分频电路

72MHz进入AHB总线,AHB有个预分频器,在SystemInit里配置的分配系数为1,则AHB的时钟就是72MHz,然后进入RPB1总线,这里配置的分频系数是2,则这里的频率是72/2=36MHz。
但是!下面有个支路——若干APB与分频系数=1,则频率不变,否则频率×2。这条支路单独为TIM2~7开通
因此,无论是何种计时器,频率都是72MHz(如果SystemInit中的默认配置没有改的话……)

RCC_APBxxxPeriphClockCmd

代码中的RCC_APB1/2PeriphClockCmd作用的地方就是图中的外设时钟使能

其他部分

还有给ADC、SDIO提供时钟的电路,在AHB后边那一堆的上部。

以下内容由原来的“STM32定时中断2”合并而来。

stm32f10x_tim.h

  • TIM_DeInit(TIM_TypeDef*)
  • TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef*)
  • TIM_TimeBaseInit(TIM_TypeDef*, TIM_TimeBaseInitTypeDef*):配置时基单元
  • TIM_Cmd(TIM_TypeDef*, FunctionalState):使能/失能计数器
  • TIM_ITConfig(TIM_TypeDef*, uint16_t, FunctionalState):中断输出控制
  • TIM_InternalClockConfig(TIM_ TypeDef*):选择内部时钟,
  • TIM_ITRXExternalClockConfig(TIM_ TypeDef* ,uint16_t):选择ITRx其他定时器
  • TIM_TIXExternalClockConfig(TIM_ TypeDef* ,uint16_t ,uint16_t ,uint16_t):选择TIx捕获通道的时钟
  • TIM_ETRClockMode1Config(TIM_ TypeDef* , uint16_t ,uint16_t, uint16_ t):选择外部时钟模式1输入
  • TIM_ETRC1ockMode2Config(TIM_ TypeDef* , uint16_t, uint16_t , uint16_t):选择外部时钟模式2输入
  • 一些用于清除中断标志位的函数

其它的一些函数都是用来单独修改某些设置的,例如TIM_PrescalerConfig()写预分频值。

示例一

我们首先来做一个简单的中断程序。
配置内部时钟->设置内部时钟模式->配置时基单元->中断输出控制->配置NVIC
定时器定时中断

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// #includes

uint16_t num;

void main(void){
OLED_Init();
Timer_Init();
while (1){
OLED_ShowNum(1, 1, num, 5);
OLED_ShowNum(2, 2, TIM_GetCounter(TIM2), 5); // 获取计数器的值
}
}

// 中断函数
void TIM2_IRQHandler(void){
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){
num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}

timer.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stm32f10x.h>
void Timer_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 定时器初始化
TIM_InternalClockConfig(TIM2); // 选择内部时钟(定时器上电后默认用内部时钟,可省略)
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 指定时钟分频 - 滤波器的参数 && 信号延迟 && 极性
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数器模式(向上计数)
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; // 自动重装器的值(“周期”),-1由公式得来
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; // 预分频器,-1由公式得来(在10KHz下记1w个数)
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

TIM_ClearFlag(TIM2, TIM_FLAG_Update); // 手动清除更新中断标志位

// 使能中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 开启更新中断到NVIC的通路
// NVIC
NVIC_PeriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 选择中断通道
NVIC_InitStructure.NVIC_IRQChanneCmd = 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){

TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/

这里有一些需要注意的问题

注意

TIM_TimeBaseInit()在调用的最后会生成一个更新事件,目的是更新我们初始化的值,但这样会导致上电后直接就进一次中断。
调用TIM_ClearFlag(TIMx, TIM_FLAG_Update);即可解决此问题

示例二

定时器外部时钟

main.c

1
2
3
4
5
6
7
8
9
10
11
// #includes

uint16_t num;

void main(void){
OLED_Init();
Timer_Init();
while (1){
OLED_ShowNum(1, 1, Timer_GetCounter(), 5);
}
}

timer.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stm32f10x.h>
void Timer_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
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);

// 定时器初始化
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x00); // 上升沿/高电平,不用滤波器
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 指定时钟分频 - 滤波器的参数 && 信号延迟 && 极性
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数器模式(向上计数)
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; // 自动重装器的值(“周期”),-1由公式得来
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; // 预分频器,-1由公式得来(在10KHz下记1w个数)
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

TIM_ClearFlag(TIM2, TIM_FLAG_Update); // 手动清除更新中断标志位

// 使能中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 开启更新中断到NVIC的通路
// NVIC
NVIC_PeriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 选择中断通道
NVIC_InitStructure.NVIC_IRQChanneCmd = ENABLE; // 中断通道是使能还是失能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级
NVIC_Init(&NVIC_InitStructure);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
}

uint16_t Timer_GetCounter(void){
return TIM_GetCounter(TIM2);
}

/*
// 中断函数
void TIM2_IRQHandler(void){
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){

TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/

作者

勇敢梧桐树

发布于

2022-12-03

更新于

2023-07-09

许可协议

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×