TIM(Timer)定时器

定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断 。比如对72MHz计72个数,一个数就是1MHz就是1微秒的时间,如果记72000个数,就是1KHz就是1ms的时间。

16位计数器(每来一个数,计数器加1)、预分频器(对计数器进行分频,让计数更加灵活)、自动重装寄存器(技术的目标值)的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时(公式:f=72MHz/psc/ARR;三者都是16位,2的16次方是65536,如果预分频设置最大,自动重装也最大,那么定时器最大时间就是72MHz/65536/65536,得到的是中断频率,倒数就是中断时间) ,如果觉得不够大,还可以采取级联的办法,一个定时器的输出作为另一个定时器的输入,59.65s要再乘上两个65536。

不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。(后续会有所讲解)

根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型,高一级拥有第一季所有功能。

image.png

STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4(操作外设之前,应该查看有没有相关的外设,不然就寄了)

image.png

时基单元包含预分频器,计数器,自动重装寄存器

基本定时器只能选择内部时钟,向上计数。所以控制器旁边引出的这根线 可以相当于是接到了内部时钟这跟根线上。来自RCC的TIMxCLK的一般是主频72MHz。预分频器如果写0,就是不分频,写1就相当于72MHz/(1+1)=36MHz。写2就是除以3。预分频器是16位的,所以最大值是65535。计数时钟每来一个上升沿,计数器的值就加1,计数器也是16位的,最大值也是65535,再加就是从0开始。只能使用向上计数。自动重装寄存器储存的就是计数目标,当计数器加到目标值时就输出中断信号。向上箭头时更新中断,输送到NVIC触发中断,向下箭头就是产生事件,不会触发中断,一般触发内部其他外设的工作。

主模式触发DAC功能:能让单片机在不收软件控制的情况下实现自动运行,某些情境下极大的减轻cou的负担,如图144,产生的更新事件通过主模式映射到TRGO上,然后TRGO口接到DAC中,当事件过来的时候直接触发DAC,免去了重新设置一个定时器中断触发DAC,减轻了cpu的负担,同时防止主程序被频繁中断。

image.png

中间部分大同小异,但是计数方式除了向上计数还有向下计数以及所谓的中央对齐模式,中央对齐就是从开始向上计数到达重装值然后申请中断,之后再从重装值开始自减到达0再申请中断。向下计数就是重某个值向下减到0触发。

通用定时器不仅可以选择内部时钟,还可以选择外部时钟,就像上部一样。输入滤波是对输入的进行整形,避免毛刺的影响,极性选择就应该是上升沿下降沿那种,预分频器就是分频。选择ETR外部时钟的时候,应该看手册,看看具体的ETR对应哪个引脚,因为引脚是固定的。

image.png

ETRF那一路叫外部输入时钟模式2,TRGI那一路是外部时钟模式1 ,TRGO连接的是其他定时器ITR引脚,具体连接哪里可以查找手册(14.4.3),也就是表78,以第一行为例,表示定时器2作为从定时器的时候,ITR0连接的是TIM1的TRGO,这样可以实现级联的功能。TRGI还可以从ETR中获得信号。

TI1f_ED中的ed指的是edge,边沿的意思,意思就是从TIMx_CH1输入的上升沿以及下降沿都有效,也都可以传到这边。

TI1FP1连接的是IC1这里的引脚,TI1FP2连接的是IC2这里的引脚,具体连接到哪里看图。编码器接口的作用是可以读取正交编码器的波形。对于TRGO,我们可以把定时器内部的一些事件响应映射到上面,用来触发其他定时器,DAC和ADC。

下方右边指的是输出比较电路,左边是输入捕获电路,中间公用部分是捕获/比较寄存器,输入捕获跟输出比较不能同时使用,所以寄存器公共,引脚也公用。引脚指的是TIMX_CH1这种。

image.png

新的部分,右边,重复次数计数器,可以实现每隔几个计数周期才发生一次更新时间以及中断,之前是一个计数周期结束就发出一个中断信号。还可以乘上一个65536,提高了计数时间。

右边TIM每一个控制输出都输出两个互补的PWM信号,前三个是这样的,第四个就不是这样的,第四个是正常的,前三个共六路用来输出三相无刷电机。在开关的切换瞬间,由于器件的不理想,会出现短暂的直通现象。

image.png

DTG死区生成电路,防止直通现象,输出可以输出一对互补的PWM波,用以驱动三相电机。

 下面那部分就是刹车输入的功能,提供安全保障,外部输入刹车信号或者内部时钟失效,当发生故障的时候控制电路就会自动打断电机的输出,防止以外的发生。

image.png

 运行控制指的是向上向下计数啊,启动停止啊什么的。高级定时器还会在中断输出之前多一个重复计数寄存器。

单片机中有很多地方需要申请中断,在中断输出控制哪里可以控制需要那些中断,需要就允许不需要就不允许,其实就是中断允许位。

在中断信号发生的时候,会在状态寄存器中置一个中断标志位,这个标志位会通过中断输出控制道NVIC申请中断。

image.png

 FC就是ARR,计数计算到跟ARR相等,就重新计算。

预分频寄存器分为预分频控制寄存器以及预分频缓冲寄存器(影子寄存器),如果在一个计数周期内改变了预分频的值,还是不会立即改变,而是会等一个计算周期结束之后产生更新事件的时候,再进行改变,把改变的分频值传入1预分频缓冲寄存器,然后计数寄存器才会起作用,实则起作用的就是影子寄存器。

image.png

上图中有阴影的都是带有1影子寄存器的。目的是让更新事件跟值的变化同步,会出现一点小错误,比如已经加到8了,但是但是你重新设定的值是5,已经超过了,就只能加到FFFF之后再重置。

预分频计数器从0变1,也就是从一开始的000000,变成了0101010101没一次到0就会产生一次时钟脉冲。 

image.png

 无预装就是没有缓冲寄存器的意思,反之则有。

RCC时钟树

image.png

 外部石英振荡器比内部的RC振荡器更加稳定。高速晶振一般用来提供系统时钟,AHB,APB2,APB1等。

ST公司一半已经配备好了时钟的配置,在SystemInit中,他是这样配置的:

首先启动内部时钟,暂时以8MHz时钟运行,然后外部时钟经过PLLMUL锁相环进行倍频9倍得到72MHz,等到输出稳定后,由锁相环输出,把内部时钟8MHz换为外部晶振的72MHz。

如果外部晶振出问题了,时钟没办法切换成外部时钟72MHz了,时钟会慢了大概10倍。

CSS一旦外部时钟失效,换为内部时钟,保证系统时钟的运行,防止程序卡死。在高级定时器中也有这个CCS的存在,输送时钟输入信号的。

然后传送到AHB中!

在通过预分频的变化把传进来的时钟频率更改,按照一定的关系全部变成72MHz,方便实用。

无论哪种定时器,他的内部基准时钟都是72MHz,预分频器设置多少就除以多少。

输入滤波的工作原理:在固定的时钟频率下进行采样,如果连续n个采样点是相同的电平,代表输入信号就稳定了。如果采样值全都相同,就把采样值都输出出去,如果采样值不全都相同,说明信号有抖动,保持上一次的输出。频率越低,采样点越多,滤波效果就越好,但是信号延迟就越大。采样频率f可以由内部时钟直接而来,也可以由内部时钟加一个时钟分频而来。

而这个滤波所用的固定时钟频率来自内部时钟经过一个预分频器得到,这个预分频器就是配置世基单元初始化的一个参数:TIM_TimeBaseStruct.TIM_ClockDivision=TIM_CKD_DIV1;此处的一分频就是不分频。

配置定时器中断就是打通那个五颜六色的图。

image.png

第一步,RCC开启时钟,定时器要开启时钟,注意总线位置对应的定时器

第二步,选择时基单元的时钟源,对于定时中断,选择内部时钟源(选择内部时钟的时候可以不写,其实默认就是内部时钟,但是还是建议写一下)

第三步,配置时基单元

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;

    TIM_TimeBaseStruct.TIM_ClockDivision=TIM_CKD_DIV1;//看上面滤波那里

    TIM_TimeBaseStruct.TIM_CounterMode=TIM_CounterMode_Up;//选择计数器模式,此处为向上计数

    TIM_TimeBaseStruct.TIM_Period=7200-1;//周期,就是自动重装器的值,ARR

    TIM_TimeBaseStruct.TIM_Prescaler=10000-1;//预分频寄存器的值,输入频率除以的那个数

    TIM_TimeBaseStruct.TIM_RepetitionCounter//重复计数器的值

    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);

第四步,配置输出中断控制,允许更新中断输出到NVIC

第五步,配置NVIC,在NVIC中打开定时器中断的通道,并且分配一个优先级

第六步,运行控制,之后使能一下计数器,写中断函数,中断函数在启动文件Start中

void TIM_DeInit(TIM_TypeDef* TIMx);  //恢复配置

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);  //时基单元初始化

TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState); //对应运行控制

TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState); //对应使能中断输出控制

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

void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);  //单独配置ETR引脚的预分频器,极性,滤波器这些参数的

以上六个对应时基单元的时钟选择部分。

uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);    //获取当前计数器的值

uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);   //获取当前预分频的值

FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);  

void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

 TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);  //单独写预分频值,第一个参数是定时器,第二个参数预分频值,第三个参数是写入模式,就是缓冲寄存器那一套,立即手动产生更新事件生效数值还是等待更新事件,然后自动产生。

TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode); //改变计数器计数模式的函数

TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);  //选择有没有预装,就是选择有没有影子寄存器

TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);  //给计数器写一个值

TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);  //给自动重装器ARR写一个值

uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);    //获取当前计数器的值

uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);   //获取当前预分频的值

FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);  

void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

//这四个是用来获取中断标志位以及清除中断标志位的

跨越.c文件的变量,需要在其中一个的前面用extern定义说明。第二种就是把中断函数直接放到主函数中,让两个变量在一个.c文件中。

当外部输入的信号的功率比较小,上拉输入会影响信号的输入的电平的时候,就用浮空输入。

刚初始化完成就会进入中断,如果需要从头开始,就需要手动清除一下更新中断标志位。

TIM_ClearFlag(TIM2,TIM_FLAG_Update);

变量如果在不同的.c文件中,并且两者其实是同一个变量,需要在另一个没有定义的.c文件中添加exturn 定义变量。或者把带有该变量的文件直接移动到声明的第一个.c文件中。

在TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);的.c文件中,存在这么一句话,立即更新事件重新装载预分频器数值以及自动重装寄存器的值,所以tim还没申请中断的时候,他就更新了一个事件,因为缓冲寄存器的缘故,新写入的值需要一个更新事件才能起作用,这用的是手动生成一个更新事件。但是更新中断以及更新事件是同时发生的,所以以此同时会产生一次中断,也是说还没 TIM申请中断就有了个中断,所以会提前马上执行一次中断函数。解决方法就是在配置完这个东西之后,在中断开启的前面,清除一次中断标志位。

image.png

 配置外部时钟,第一个参数,定时器;第二个参数是配置时钟的预分频值,不是时基单元中的预分频值,一般不需要配置;第三个是电平极性,也就是说什么有效什么有效;第四个参数,指的是滤波选择,在手册14.4.2

image.png

  外部时钟的配置别忘了配置对应的GPIO引脚,8.1.11是相关GPIO在特定情况的推荐配置。一般选择浮空输入,但是对于信号比较强的,选择上下拉输入比较好,对于比较弱的,上下拉输入的电平太强会导致输入信号的影响。

————————————————

版权声明:本文为CSDN博主「笔下觅封侯」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/m0_63148816/article/details/126029475