参考江科大的讲解,记录了TIM通用定时器的常用功能,包括定时器中断,以及后面文章的输入捕获测量频率、输出比较实现PWM波、编码器测速等。

TIM定时器

TIM(Timer)是单片机最常用的片内外设之一,能够完成很多复杂任务,stm32系列的定时器资源分为高级定时器、通用定时器、基本定时器,最常用的是通用寄存器,高级定时器会在三相电机驱动用得较多: TIM定时器分类 其中STM32F103C8T6定时器资源包含TIM1、TIM2、TIM3、TIM4,即一个高级定时器+三个通用定时器

基本定时器

基本定时器的任务主要是产生定时中断主模式触发DAC基本定时器结构 基本定时器结构包含RCC内部时钟(c8t6主频72M)触发控制器只能选择内部时钟、主模式DAC),以及时基单元

时基单元包括预分频器PSC自动重载寄存器CNT计数器PSC是一个16位寄存器对内部72M时钟进行分频最大65536分频。自动重载寄存器存储了计数的目标值,计数器从0开始向上向重载值计数,一旦达到重载值,就会产生定时器中断并且清零计数值重新计数,产生的中断既可以通向NVIC触发CPU中断处理也可以作为事件中断连接其他片内外设

基本定时器只支持向上计数,在通用、高级定时器中还支持向下计数、中央对齐模式向下计数计数器每次从重载值开始递减,直到计数器减为0触发中断,并且重新载入重载值计数中央对齐模式计数器从0开始计数至重载值触发中断(不会清零),再计数递减至0触发定时器中断定时器中断是针对内核的术语,实际上产生的是一种更新中断,在计数值==重载寄存器/计数值==0时,下一个时钟到来就会发生计数溢出,产生更新中断,也是定时器中断

主模式DAC允许定时器中断(事件中断)直接驱动DAC模块输出波形,减轻了反复中断CPU的开销,主要原理是将定时中断通过TRGO(Trigger Out,触发输出)接到DAC触发引脚。(注:c8t6无DAC外设)

通用定时器

除了扩展了计数方式之外,通用定时器实现了更加复杂的定时器中断事件中断功能,还扩展了其他功能包括输入捕获输出比较编码器测速等。 通用定时器结构 本文集中讨论的是定时器中断

可以选择外部时钟:包括外部引脚时钟片内其他定时器的时钟:

  • 外部引脚时钟可以经过滤波电路,由触发控制器进行选择作为定时器中断的时钟信号,可以完成一些电平计数任务(红外计数、光敏等);

  • 此外可以和片内其他定时器级联的时钟:假设采用内部72MHz作为计数时钟,那么最长的计数间隔为:

计数时间

计数器同样是一个16位寄存器,最大计数值为65535,即

单级定时器最大计数时间不超过一分钟

因此,如果采用级联的方式,那么两个定时器的计数时间为59.65s×65535(分频)×65535(计数)约八千多年三个级联就能长达三十几万亿年,远超出宇宙寿命,stm32定义了定时器级联连接: 定时器级联

  • 最后是TIMx_CHx引脚捕获的时钟信号,信号的双边沿都能够作为计数的时钟。

高级定时器

计数单元增加了16位的重复计数寄存器,允许重复计数多次满(65535)后再触发中断,因此单个计数器的计数时间59.65s×65535约45天;其余输出相反相位PWM波死区寄存器刹车输出主要用于三相无刷电机的驱动(没用过,鸽高级定时器

分频细节

  • PSC分频本身也是一个16位计数器,当选择2分频时,实际上PSC寄存器的重载值是1,说明上层时钟上升沿到来(假设为内部时钟,记为CK_PSC),PSC寄存器反复在0和1之间计数,导致二分频。因此实际上的分频数满足: 因此设置分频值时PSC应该设置为1而不是2

  • PSC分频值的更新是更新信号发生后有效,每次时基单元计数完毕才会发出更新事件信号

影子寄存器(了解)

在图中自带阴影的寄存器,被称为影子寄存器(shadow register);影子寄存器是时序变化的背后推手,在分频时要对预分频控制寄存器赋值,在重载计数预装值时要对自动重载预装载寄存器赋值,但是实际上还存在另外一种寄存器,分别是预分频缓冲区(预分频影子寄存器)、自动重载影子寄存器它们会在更新事件信号到来时将前面的寄存器值送到影子寄存器新的分频值、重载值才真正生效,这种机制避免了计数机制的一些错误,例如当当前计数超过新预装值时,较小的新预装值影响原来的计数时序,否则计数必须计到16位溢出才能判断异常。

通用定时器中断配置

通用定时器中断配置 通用定时器中断配置包含几个方面:

1. 选择时钟源

时钟源总共有四种:内部时钟、外部引脚时钟、TIM引脚通道以及其他定时器时钟,其中外部模式1和外部模式2结合通用寄存器结构图看,外部模式2特指外部引脚不经过TRGI触发直接使滤波信号ETRF作为时钟信号,一般用于完全依赖外界电平条件的计数,外部模式1则比较广泛,TIM捕获的边沿、外部引脚信号、编码器接口、其他定时器时钟都经过TRGI。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//默认不写也可:内部时钟
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);

//ITRx代表来自其他定时器时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

//TIMx捕获时钟
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);
//外部模式1
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);

//外部模式2
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

2. 时基电路配置

和GPIO、EXTI中断配置一样,TIM的时基电路采用结构体配置,其中TIM_ClockDivision是滤波电路的分频,可以额外再选择1、2、4分频;

1
2
3
4
5
6
7
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
- TIM_TimeBaseInitStruct:
-- TIM_ClockDivision=TIM_CKD_DIV1; //1分频
TIM_CounterMode; //向上、向下、中央对齐
TIM_RepetitionCounter=0; //仅高级定时器:16位重复计数寄存器
TIM_Prescaler=7200-1; //16位预分频值PSC
TIM_Period=10000-1; //16位计算值ARR
分频、计数意义:72M/7200 = 10k,用10kHz频率计10k个数,即定时一秒。

3. 开启定时器中断

开启TIMx定时器,并且指定中断源TIM_IT;中断源来源根据场景不同选择,例如内部定时可以直接选择更新事件(UEV)中断,输出PWM波应该选择TIMx捕获中断、多相PWM输出应该选择相变中断等,结合实例看。

1
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

4. 配置NVIC

中断CPU的都应该配置NVIC分组+初始化:

1
2
3
4
5
6
7
8
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //抢占优先级与响应优先级分组

NVIC_InitTypeDef NVIC_InitStruct; //NVIC初始化,NVIC管理中断分配
NVIC_InitStruct.NVIC_IRQChannel=TIMx_IRQn; //1打开TIMx的中断,参考stm32f10x.h以及对应密度宏定义
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //2 NVIC使能
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1; //3 设置先占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1; //4 设置响应优先级
NVIC_Init(&NVIC_InitStruct);

5. 使能定时器

1
TIM_Cmd(TIMx, ENABLE);        //计数器使能 

内部时钟+OLED时序每隔1s输出

采用内部72M时钟,定时1s,每隔1s OLED计数+1;

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
void Timer_InternalInit(void)          //内部时钟
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //配置RCC时钟,TIM2挂载在APB1总线
TIM_InternalClockConfig(TIM2); //采用内部时钟

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //时基电路初始化
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //1 选择分频
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //2 选择计数方式(向上、向下、交替)
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0; //3 分频输出
TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1; //4 预分频 stm32内部时钟72MHz,预分频7200-1代表每10k记一次
TIM_TimeBaseInitStruct.TIM_Period=10000-1; //5 计数 计10000个数,即10kHz计10000个数,时间为1s,1秒触发一次中断
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);

TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清除TIM_TimeBaseInit的更新标志,防止上电时就产生一次更新中断。
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //更新响应 更新响应的中断是与cpu,事件响应是向其他外设

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //抢占优先级与响应优先级分组
NVIC_InitTypeDef NVIC_InitStruct; //NVIC初始化,NVIC管理中断分配
NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn; //1打开TIM2的中断
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //2 NVIC使能
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1; //3 设置先占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1; //4 设置响应优先级
NVIC_Init(&NVIC_InitStruct);

TIM_Cmd(TIM2, ENABLE); //计数器使能
}

//定时器处理中断
int16_t Num=0;

void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}

//main.c
Timer_InternalInit();
OLED_Init();
OLED_ShowString(1,1,"Hello Mo");
extern int16_t Num;
while(1){
OLED_ShowNum(4,1,Num,4);
}

外部引脚红外计数

这个功能在EXTI也实现过,EXTI中断实现计数的原理是对外部信号的计数,TIM中断实现计数的原理是把外设看成是时钟的计数,二者几乎是等效的。如果是一些突发事件,例如按键按下等,选择EXTI,如果是一些周期性的计数事件,例如生产线的计件,选TIM。

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
51
52
53
54
55
56
57
58
59
60
61
62
void Timer_ExternalInit(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //定时器
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //GPIO

GPIO_InitTypeDef GPIO_InitStructure; //GPIO初始化
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,不分频、极性不反转(上升沿计数)、采用频率分频0(默认频率采样)
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0X00);

//时基电路
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //1分频 = 不分频
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInitStruct.TIM_Period=100-1; //100个数才会产生更新事件
TIM_TimeBaseInitStruct.TIM_Prescaler=2-1; //1Hz,符合遮挡手速
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);

TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清除TIM_TimeBaseInit的更新标志,防止上电时就产生一次更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启定时器中断:中断源为计数的更新事件,即满100个数

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC配置
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);

TIM_Cmd(TIM2, ENABLE); //开启定时器
}

//打印目前计数值
uint16_t Counter(void)
{
return TIM_GetCounter(TIM2);
}

//每满100,Num才会+1,进位计数
int16_t Num=0;
void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}

//main.c
Timer_ExternalInit();
extern int16_t Num;
OLED_Init();
OLED_ShowString(1,1,"NUM:");
OLED_ShowString(2,1,"Count:");
while(1)
{
OLED_ShowNum(2,7,Num,5);
OLED_ShowNum(3,7,Counter(),5);
}