1.TIM简介
- TIM(Timer)定时器
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
- 16位计数器(59.65s的计时,1/72M/65536/65536)、预分频器(对计数器时钟进行分频,让计数更灵活)、自动重装寄存器(计数的目标值,记N个时钟后申请中断)的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
- 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
2. 定时器类型
- STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
2.1 基本定时器
- 下面三个叫做时基单元,预分频器之前,连接的就是基准计数时钟的输入(内部时钟CK_INT,来源自RCC的TIMxCLK,频率值一般都是系统的主频72MHz),预分频器可以对72MHz进行分频(1就是2分频),计数器可以对预分频后的计数时钟进行计数(每来一个上升沿,计数器就加一,0~65535),计数器的值会在计时过程中会不断地自增运行,当自增运行到目标值时,产生中断,那就完成了定时的任务,自动重装定时器存的就是写入的计数目标,在运行的过程中,计数值不断自增,自动重装值是固定的目标,当计数值等于自动重装值时,也就是计时时间到了,就会产生一个中断信号,并清零计数器,计数器自动开始下一次的计数计时
- 采用向上计数模式。UI:更新中断 U:更新事件
2.2 通用定时器
- 预分频器对时钟进行预分频,计数器自增计数,当计数值到自动重装值时,计数值清零同时产生更新终端和更新事件
- 通用定时器和高级定时器还支持向下计数模式和中央对齐模式。
向下计数模式就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断,然后继续下一轮,依次循环;
中央对齐的计数模式,就是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断,然后继续下一轮,依次循环。
- 上面一部分就是内外时钟源选择和主从触发模式的结构
内外时钟源选择:不止系统频率72MHZ,还可以选择外部时钟,
TIMx_ETR引脚上的外部时钟(PA0),可以接一个外部方波时钟,配置一下内部的极性选择、边沿检测和预分频器电路,再配置输入滤波电路,最后,滤波后的信号,兵分两路,上面一路ETRF进入触发控制器,紧跟着就可以选择作为时基单元的时钟【外部时钟模式2】; 下面这里还有一路可以提供时钟,就是TRGI(Trigger In),主要是用作触发输入来使用的【外部时钟模式1】。
外部时钟模式1:
第一个,就是ETR引脚的信号;
第二个,就是ITR信号,这一部分的时钟信号是来自其他定时器的,从右边可以看出,这个主模式的输出TRGO可以通向其他定时器,那通向其他定时器的时候,就接到了其他定时器的ITR引脚上来了,ITRO到ITR3分别来自其他4个定时器的TRGO输出
【比如可以先初始化TIM3,然后使用主模式把它的更新事件映射到TRGO上,接着再初始化TIM2,这里选择ITR2,对应的就是TIM3的TRGO,然后后面再选择时钟为外部时钟模式1,这样TIM3的更新事件就可以驱动TM2的时基单元,也就实现了定时器的级联】;
第三个,TI1F_ED,连接的是这里输入捕获单元的CH1引脚,也就是从CH1引脚获得时钟,这里后缀加一个ED(Edge)就是边沿的意思(也就是通过这一路输入的时钟,上升沿和下降沿均有效);
最后,这个时钟还能通过TI1FP1和TI2FP2获得,其中TI1FP1是连接到了CH1引脚的时钟,TI1FP2是连接到了CH2引脚的时钟。
总结一下就是,外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1引脚的边沿、CH1引脚和CH2引脚
- 下面这一部分主要包含了两块电路,
左边这一块是输入捕获电路,总共有四个通道,分别对应CH1到CH4的引脚,可以用于测输入方波的频率等
右边这一块是输出比较电路,总共有四个通道,分别对应CH1到CH4的引脚,可以用于输出PWM波形,驱动电机
中间这个寄存器是捕获/比较寄存器,是输入捕获和输出比较电路共用的,因为输入捕获和输出比较不能同时使用,所以这里的寄存器是共用的,引脚也是共用的
2.3 高级定时器
3.定时器
3.1定时中断基本结构
最重要的PSC(Prescaler) 预分频器、CNT (Counter)计数器、ARR(AutoReloadRegister)自动重装器这三个寄存器构成的时基单元,
- 下面这里是运行控制,就是控制寄存器的一些位(比如启动停止、向上或向下计数等等),
- 左边是为时基单元提供时钟的部分,这里可以选择RCC提供的内部时钟,也可以选择ETR引脚提供的外部时钟模式2,当然还可以选择这里的触发输入当做外部时钟,即外部时钟模式1,对应的有ETR外部时钟、ITRx其他定时器、T输入捕获通道,这些就是定时器的所有可选的时钟源了,
- 右边这里,就是计时时间到,产生更新中断后的信号去向(如果是高级定时器的话,还会多一个重复计数器),那这里中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NMIC申请中断,这个中断输出控制就是一个中断输出的允许位,如果要需要某个中断,就记得允许一下
初始化定时器
第一步:RCC开启时钟;
第二步:选择时基单元的时钟源。对于定时中断,我们就选择内部时钟源;
第三步:配置时基单元。包括预分频器、自动重装器、计数模式等等;
第四步:配置输出中断控制,允许更新中断输出到NVIC;
第五步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级;
第六步:运行控制
3.2 预分频时序
- CK_PSC,预分频器的输入时钟,选内部时钟的话一般是72MHz
- CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止
- 定时器时钟CK_CNT,既是预分频器的时钟输出,也是计数器的时钟输入
开始时,计数器未使能,计数器时钟不运行,然后使能后,前半段,预分频器系数为1,计数器的时钟等于预分频器前的时钟,后半段,预分频器系数变为2,计数器的时钟就也变为预分频器前时钟的一半了,
- 在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增,在中间的这个位置FC之后,计数值变为0了(可以推断出ARR自动重装值就是FC),当计数值计到和重装值相等,并且下一个时钟来临时,数值才清零
- 同时,下面这里产生一个更新事件,这就是一个计数周期的工作流程
- 后三行是预分频存器的一种缓冲机制,也就是这个预分频寄存器实际上是有两个,一个是预分频控制寄存器,供我们读写用的,它并不直接决定分频系数,
- 另外还有一个预分频缓冲器,缓冲寄存器才是真正起作用的寄存器,【比如我们在某个时刻,把预分频寄存器由0改成了1,如果在此时立刻改变时钟的分频系数,那么就会导致这里,在一个计数周期内,前半部分和后半部分的频率不一样,计数计到一半,计数频率突然就会改变了,改变了分频值,这个变化并不会立刻生效,而是会等到本次计数周期结束时,产生了更新事件,预分频奇存器的值才会被传递到缓冲寄存器里面去,才会生效】所以即使在计数中途改变了预分频值,计数频率仍然会保持为原来的频率,直到本轮计数完成,在下一轮计数时,改变后的分频值才会起作用,【不会被打断】
- 预分频计数器内部实际上也是靠计数来分频的,当预分频值为0时,计数器就一直为0,直接输出原频率,当预分频值为1时,计数器就0、1、0、1、0、1这样计数,在回到0的时候,输出一个脉冲,
- 这样输出频率就是输入频率的2分频,预分频器的值和实际的分频系数之间有一个数的偏移
- 计数器计数频率:CK_CNT = CK_PSC(72MHz) / (PSC + 1)
3.3 计数器时序
- CK_INT,内部时钟72MHz
- CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止
- 计数器时钟CK_CNT ,因为分频系数为2,所以这个频率是CK_INT除2,
- 计数器寄存器在这个时钟每个上升沿自增,当增到0036的时候,发生溢出,那计到36之后,再来一个上升沿,计数器清零
- 计数器溢出
- 产生一个更新事件脉冲
- 另外还会置一个更新中断标志位UIF,这个标志位只要置1了,就会去申请中断,然后中断响应后,需要在中断程序中手动清零
- 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)【如果想算溢出时间,只需再取个倒数】
3.4 计数器无预装时序
3.5 计数器有预装时序
3.6 RCC时钟树
参考:https://blog.csdn.net/m0_49941630/article/details/132689377
4.实操
TIM库函数
/*时基单元*/ void TIM_DeInit(TIM_TypeDef* TIMx); //恢复缺省配置 void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); //时基单元初始化 void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); //把结构体变量赋默认值 /*运行控制*/ void TIM_Cmd(TIM_TypeDef* TIMx(选择定时器), FunctionalState NewState(新的状态 使能/失能)); //使能计数器 /*中断输出控制*/ void TIM_ITConfig(TIM_TypeDef* TIMx(选择定时器), uint16_t TIM_IT(选择配置哪个终端输出), FunctionalState NewState(新的状态 使能/失能)); //使能中断输出信号 /*时基单元的时钟选择部分*/ void TIM_InternalClockConfig(TIM_TypeDef* TIMx); //选择RCC内部时钟 void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx(选择定时器), uint16_t TIM_InputTriggerSource(选择要接入哪个其他的定时器)); //选择ITRx其他定时器 void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx(选择定时器), uint16_t TIM_TIxExternalCLKSource(选择具体的某个引脚),uint16_t TIM_ICPolarity(输入的极性), uint16_t ICFilter(输入的滤波器)); //选择TIx捕获通道的时钟 void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler(外部触发预分频器 对ETR的外部时钟再提前做一个分频), uint16_t TIM_ExtTRGPolarity(输入的极性),uint16_t ExtTRGFilter(输入的滤波器)); //选择ETR外部时钟模式1输入的时钟 void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter); //选择ETR外部时钟模式2输入的时钟 void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter); //单独配置ETR引脚的预分频器、极性、滤波器 void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler(要写入的预分频值), uint16_t TIM_PSCReloadMode(写入的模式)); //单独写预分频值 void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode); //改变计数器的计数模式 void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState); //自动重装器预装功能配置 void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter); //给计数器写入一个值 void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload); //给自动重装器写入一个值 uint16_t TIM_GetCounter(TIM_TypeDef* TIMx); //获取当前计数器的值 uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx); //获取当前预分频器的值
初始化定时器
第一步:RCC开启时钟;
第二步:选择时基单元的时钟源。对于定时中断,我们就选择内部时钟源;
第三步:配置时基单元。包括预分频器、自动重装器、计数模式等等;
第四步:配置输出中断控制,允许更新中断输出到NVIC;
第五步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级;
第六步:运行控制
4.1 定时器定时中断
4.1.1 实验现象
4.1.2 代码
Timer.c
#include "stm32f10x.h" // Device header void Timer_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//第一步:RCC开启时钟 TIM_InternalClockConfig(TIM2);//第二步:选择时基单元的时钟源 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//不分频
//指定时钟分频,在一个固定的时钟频率进行采样,如果连续N个采样点都为相同的电平,那就代表输入信号稳定了,就把这个值输出出去,反之说明信号抖动
//采样频率f,可以是由内部时钟直接而来,也可以是由内部时钟加一个时钟分频而来,所以分频多少就是由TIM_ClockDivision决定的
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//ARR自动重装器的值 TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器的值 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_PriorityGroupConfig(NVIC_PriorityGroup_2);//2位抢占,2位响应 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//指定中断通道来开启或关闭,由于TIM2,选择TIM2_IRQn NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定中断通道是使能 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//指定抢占优先级 0~3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定响应优先级 0~3 NVIC_Init(&NVIC_InitStructure);//第五步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级; TIM_Cmd(TIM2, ENABLE);//第六步:运行控制 } /* void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)//检查中断标志位,更新中断 { TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除标志位 } } */
main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Timer.h" uint16_t Num; int main(void) { OLED_Init(); Timer_Init(); OLED_ShowString(1, 1, "Num:"); while (1) { OLED_ShowNum(1, 5, Num, 5); } } void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) { Num ++; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }
4.2 定时器外部时钟
4.2.1 实验现象
4.2.2 代码
Timer.c
#include "stm32f10x.h" // Device header void Timer_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//GPIO开启时钟 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, 0x0F); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); 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 = 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); } } */
main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Timer.h" uint16_t Num; int main(void) { OLED_Init(); Timer_Init(); OLED_ShowString(1, 1, "Num:"); OLED_ShowString(2, 1, "CNT:"); while (1) { OLED_ShowNum(1, 5, Num, 5); OLED_ShowNum(2, 5, Timer_GetCounter(), 5); } } void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) { Num ++; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }