参考江科大的讲解,记录了TIM通用定时器的常用功能,包括定时器中断,以及后面文章的输入捕获测量频率、输出比较实现PWM波、编码器测速等。
TIM定时器
TIM(Timer)是单片机最常用的片内外设之一,能够完成很多复杂任务,stm32系列的定时器资源分为高级定时器、通用定时器、基本定时器,最常用的是通用寄存器,高级定时器会在三相电机驱动用得较多:
其中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外设)
通用定时器
除了扩展了计数方式之外,通用定时器实现了更加复杂的定时器中断和事件中断功能,还扩展了其他功能包括输入捕获、输出比较、编码器测速等。
本文集中讨论的是定时器中断:
可以选择外部时钟:包括外部引脚时钟、片内其他定时器的时钟:
计数时间
计数器同样是一个16位寄存器,最大计数值为65535,即
即单级定时器最大计数时间不超过一分钟。
因此,如果采用级联的方式,那么两个定时器的计数时间为59.65s×65535(分频)×65535(计数)约八千多年,三个级联就能长达三十几万亿年,远超出宇宙寿命,stm32定义了定时器级联连接:

- 最后是TIMx_CHx引脚捕获的时钟信号,信号的双边沿都能够作为计数的时钟。
高级定时器
计数单元增加了16位的重复计数寄存器,允许重复计数多次满(65535)后再触发中断,因此单个计数器的计数时间59.65s×65535约45天;其余输出相反相位PWM波、死区寄存器、刹车输出主要用于三相无刷电机的驱动(没用过,鸽。

分频细节
影子寄存器(了解)
在图中自带阴影的寄存器,被称为影子寄存器(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);
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
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; TIM_CounterMode; TIM_RepetitionCounter=0; TIM_Prescaler=7200-1; TIM_Period=10000-1;
|
分频、计数意义: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_InitStruct.NVIC_IRQChannel=TIMx_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1; NVIC_InitStruct.NVIC_IRQChannelSubPriority=1; NVIC_Init(&NVIC_InitStruct);
|
5. 使能定时器
内部时钟+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); TIM_InternalClockConfig(TIM2); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0; TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1; TIM_TimeBaseInitStruct.TIM_Period=10000-1; 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=1; NVIC_InitStruct.NVIC_IRQChannelSubPriority=1; 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); }
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_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_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_Period=100-1; TIM_TimeBaseInitStruct.TIM_Prescaler=2-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_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); }
int16_t Num=0; void TIM2_IRQHandler(void){ if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) Num++; TIM_ClearITPendingBit(TIM2,TIM_IT_Update); }
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); }
|