当前位置: 首页 > article >正文

【STM32】定时器

定时器就像Qt的QTimer,还是硬件级的,超好用。不过有一说一,基本定时器更符合定时器的定义,通用定时器和高级定时器的作用已经不是“定时器”三个字可以概括的了。

大部分图片来源:正点原子HAL库课程

 专栏目录:记录自己的嵌入式学习之路-CSDN博客


目录

1    定时器类型

2    计时器的几种溢出模式

3    芯片定时器参数及性质查询

4    关于计数器计数值的计算

5    常用公共函数

5.1    定时器计数器清零

5.2    获取计数器当前值

5.3    开启中断

5.4    启用xx功能及其对应中断

5.5    启用xx功能但不启用其对应中断

5.6    清除标志位

6    基本定时器

6.1    特性

6.2    原理

6.3    溢出时间计算方法

6.4    配置步骤

6.5    用到的函数

6.6    注意事项⚠️

7    输出比较模式

7.1    PWM1/PWM2模式

7.2    翻转模式

8    通用定时器

8.1    特性

8.2    原理

8.3    配置PWM输出模式

8.4    配置PWM输出模式相关函数

8.5    输入捕获

8.6    输入捕获配置步骤

8.7    脉冲计数

8.8    脉冲计数配置步骤

9    高级定时器

9.1    特性

9.2    重复计数器

9.3    输出指定个数的PWM

9.4    输出比较模式配置步骤(输出翻转模式实验)

9.5    带死区控制的互补输出

9.6    带死区控制的互补输出配置步骤

9.7    刹车(断路)功能

9.8    PWM输入模式

10    公共注意事项

10.1    BASE_Init和功能_Init之间的MsInit矛盾

10.2    HAL_TIM_IC_Start和HAL_TIM_IC_Start_IT的区别

10.3    while等待状态变化和中断回调不要同时使用

10.4    上升沿和下降沿的监测和切换


1    定时器类型


2    计时器的几种溢出模式


3    芯片定时器参数及性质查询

芯片资料文档,如:《STM32F103ZET6.pdf》


4    关于计数器计数值的计算

  • 计数次数实际上是计数器值+1,因为计数器是从0开始计数的。我的理解是它计的第一次是0,后面是1,所以实际计数次数才是要加一的;
  • 关于PWM占空比的计算,由于占空比是由ARR和CCR共同决定的,而计算式子中ARR总是会被+1,同理计算时CCR应当也要加一,因此设置占空比计算一般都是这样的:
    • 占空比 = (CRR+1) / (ARR+1)

5    常用公共函数

5.1    定时器计数器清零

__HAL_TIM_SET_COUNTER(定时器句柄地址, 0);

5.2    获取计数器当前值

__HAL_TIM_GET_COUNTER(定时器句柄地址);

5.3    开启中断

__HAL_TIM_ENABLE_IT(定时器句柄地址, 中断类型);

5.4    启用xx功能及其对应中断

HAL_TIM_xx_Start_IT (定时器句柄, 通道);

5.5    启用xx功能但不启用其对应中断

HAL_TIM_xx_Start (定时器句柄, 通道);

5.6    清除标志位

__HAL_TIM_CLEAR_FLAG(定时器句柄, 标志位类型);

常见用于清除比较捕获的标志位:

__HAL_TIM_CLEAR_FLAG(&tim_ic_handle, TIM_FLAG_CC2);


6    基本定时器

        TIM6/TIM7

6.1    特性

  • 16位递增计数器(0~65535)
  • 16位预分频器(1~65536)
  • 可用于触发DAC
  • 在更新事件(计数器溢出)时,会产生中断/DMA请求
  • 并不与具体的IO绑定,只能用作普通的计时器(时基),就像Qt里面的QTimer

6.2    原理

  • 16位递增计数器
  • 时钟源:内部RCC时钟的TIMx_CLK
  • 当CNT计数器的值等于重载影子寄存器的值时,发生溢出。溢出后可产生:①更新事件(默认产生);②中断和DMA输出(默认不产生)。

        其具有一个影子寄存器的概念,即图上的影子,自动重装载寄存器和PSC寄存器都有其对应的影子寄存器。它实际上是真正起作用的寄存器,需要有更新事件后才会将其原生寄存器的值加载至影子寄存器中(除非将ARPE位设置为无缓冲,那就设置了原生就直接加载到ARR寄存器)。我个人觉得可以理解为:新的配置需要在原有定时器结束后才会生效

        影子寄存器开启缓冲的作用:假设需要LED灯先亮1秒再灭2秒,那要是等一秒后再让它灭,再修改ARR寄存器,那就会存在一定的误差。而如果开启ARR的缓冲,就可以在等待1秒期间就设置后面的2秒,等1秒完毕后它自己就自动配置了。

6.3    溢出时间计算方法

6.4    配置步骤

6.5    用到的函数

6.6    注意事项⚠️

  • TIM句柄中需要关注的结构体成员:TIM_TypeDef、TIM_Base_InitTypeDef
  • 对于基本定时器来说TIM_Base_InitTypeDef结构体中的计数模式CounterMode、时钟分频因子ClockDivision、重复计数器寄存器RepetitionCounter都是无效的
  • 其中AutoReloadPreload就是是否启用预装载,即是否使用影子寄存器的意思,启用了就有一个对ARR寄存器的缓冲作用,不启用就一修改就直接改变其影子寄存器;
  • 使用TIM时外设需要导入的除了stm32f1xx_hal_tim.c外,还有stm32f1xx_hal_tim_ex.c,否则会编译失败。

7    输出比较模式

PWM有八种模式

  • TIM_OCMODE_TIMING:冻结,输出比较不起作用
  • TIM_OCMODE_ACTIVE:当计数值为比较/捕获寄存器值相同时,强制输出为高电平
  • TIM_OCMODE_INACTIVE:当计数值为比较/捕获寄存器值相同时,强制输出为低电平
  • TIM_OCMODE_TOGGLE:当计数值与比较/捕获寄存器值相同时,翻转输出引脚的电平
  • TIM_OCMODE_PWM1:向上计数时,当TIMx_CNT < TIMx_CCR*时,输出电平有效,否则为无效;向下计数时,当TIMx_CNT > TIMx_CCR*时,输出电平无效,否则为有效
  • TIM_OCMODE_PWM2:与PWM1相反
  • TIM_OCMODE_FORCED_ACTIVE:强制为有效电平
  • TIM_OCMODE_FORCED_INACTIVE:强制为无效电平

7.1    PWM1/PWM2模式

  • PWM1和PWM2,其实就是什么时候输出有/无效电平的差别;
  • PWM的占空比由CCRx决定。
     

7.2    翻转模式

  • 关于周期
    • 从上图可看出一个周期计数为2*(ARR+1)次,而计数一次的时间为(PSC+1)/Ft,因此周期为Ft/(2×(ARR+1)×(PSC+1) ),也就是说PWM的周期是溢出周期的二分之一。
  • 关于占空比
    • 占空比是50%,只要是反转模式就是50%
  • 关于相位
    • 相位由CCR决定

8    通用定时器

        TIM2/TIM3/TIM4/TIM5

8.1    特性

  • 16位递增、递减、中心对齐计数器(0~65535)
  • 16位预分频器(1~65536)
  • 可用于触发DAC和ADC
  • 在更新事件、触发事件、输入捕获、输出比较时,会产生中断/DMA请求
  • 4个独立通道,可用于:输入捕获、输出比较、输出PWM、单脉冲模式
  • 使用外部信号控制定时器且可实现多个定时器互连的同步电路
  • 支持编码器和霍尔传感器电路等

8.2    原理

        16位递增计数器

  • 时钟源(4个):

        (1)    内部时钟:RCC时钟的TIMx_CLK(APB)

        (2)    外部时钟模式1:TIx(通道1和通道2的引脚信号)【注意⚠️!外部时钟模式1仅CH1和CH2可用】

        (3)    外部时钟模式2:外部IO口复用触发输入TIMx_ETR,每个定时器只有一个ETR引脚功能

        (4)    内部触发输入ITRx:与内部或外部其他定时器进行级联

        内部时钟模式意味着使用内部时钟频率做计数,就像基本定时器一样;外部时钟模式即使用外部的上升沿、下降沿信号来触发计数器的改变

  • 滤波器原理:

        要采样到N次相同的信号才输出一个信号

8.3    配置PWM输出模式

  • 时钟来源:内部时钟;
  • PWM的占空比由CCRx决定。


  • 其中使能通道预装载即启用CCR影子寄存器的缓冲功能,与ARR的预装载功能类似。

8.4    配置PWM输出模式相关函数

  • 需要关注的结构体:TIM_OC_InitTypeDef
     
    • 仅需要关注前三个结构体成员;
    • OCPolarity是用来定义有效电平是高电平还是低电平的;
    • 注意⚠️:赋值OCPolarity的时候千万别写成OCNPolarity

8.5    输入捕获

  • 作用:
    • 测量脉宽
  • 原理:
    • 四个通道皆可用于输入捕获
    • 时钟来源:内部时钟
    • 在检测到上升沿和下降沿时读到捕获寄存器的值,通过两次读取间捕获寄存器的差值(实际上是计数器的差值)以及记一次数所需时间,就可以知道脉宽。

  • 计一个数所花时间:(PSC+1) / Ft 【(分频系数+1)/定时器时钟源频率】

8.6    输入捕获配置步骤


  • 需要关注的结构体:TIM_IC_InitTypeDef

        分频系数:就是设置多少个上升沿/下降沿才产生一次计数;

  • 重点:与基本定时器及PWM输出不同,输入捕获并不是很在意计数器溢出的时间(6.3),其更关注计数频率,计数频率越高得到的脉宽精度才会越高,6.3这条式子就不是用来倒着用来求移出时间了,而是:
    • 先决定计数频率,再决定溢出条件ARR。其中我觉得ARR可以设置为最大值65535,因为其越大,计算脉宽是需要存储的溢出次数就越小;
  • 如果要捕获脉宽,就需要在捕获回调里面写切换的代码,在捕获到上升沿后,将捕获模式改成下降沿检测。
    • 注意⚠️!修改配置前为了保险可先关闭定时器,修改完毕后再打开
    • 注意⚠️!修改配置时必须先清除上一配置!
  • 获取捕获值的函数为:HAL_TIM_ReadCapturedValue

8.7    脉冲计数

  • 原理:
    • 时钟来源:外部时钟模式1,即通道1或通道2的输入。或TIMx_ETR引脚的输入,每个定时器只有一个ETR引脚功能;
    •  特性(外部1):来源于通道1或通道2的输入,有经边缘检测和不经边缘检测两种可选的信号作为时钟源,分别为TIxFPy和TI1F_ED。若为不经检测,则上升沿和下降沿都会分别触发一次计数,若经,就只会触发一个。
    • 特性(外部2):一定会经过边缘检测器,所以无需考虑上面的情况。

8.8    脉冲计数配置步骤


  • 需要关注的函数和结构体
    • HAL_TIM_SlaveConfigSynchro函数,用于配置从模式控制器,使得计数器使用外部时钟;
    • TIM_SlaveConfigTypeDef结构体,用于配置从模式
      • 从模式选择:用于选择外部时钟模式1;
      • 输入触发源选择:可选择TIxFP1(单边)、TIxFP2(单边)、TI1F_ED(双边);
      • 输入触发极性:上升、下降或者双边沿;
      • 预分频:外1模式没有,外2模式才有;
      • 滤波器:选择滤波;

9    高级定时器

        TIM1/TIM8

9.1    特性

  • 16位递增、递减、中心对齐计数器(0~65535)
  • 16位预分频器(1~65536)
  • 可用于触发DAC和ADC
  • 在更新事件、触发事件、输入捕获、输出比较时,会产生中断/DMA请求
  • 4个独立通道,可用于:输入捕获、输出比较、输出PWM、单脉冲模式
  • 使用外部信号控制定时器且可实现多个定时器互连的同步电路
  • 支持编码器和霍尔传感器电路等
  • 重复计数器(比通用定时器多的功能)
  • 死区时间带可编程的互补输出,但仅通道1到通道3有互补输出通道,通道4没有(比通用定时器多的功能)
  • 断路(刹车)输入,用于将定时器的输出信号置于用户可选的安全配置中(比通用定时器多的功能)
  • 使用高级定时器的输出功能时,必须将TIMx_BDTR寄存器的MOE位置1,否则无法输出

9.2    重复计数器

高级定时器与普通定时器不同,其可设置为单纯产生溢出时并不产生定时器更新事件,而是几次溢出后才产生一次更新事件。具体的更新事件发出条件由TIMx的RCR寄存器进行设置。如果设置RCR为N,则更新事件将在N+1次溢出时发生。

9.3    输出指定个数的PWM

  • 原理:
    • 因为在边沿对齐模式下,定时器溢出周期对应着PWM周期,我们只要在更新事件发生时,停止输出PWM就行。


  • 关键结构体
  • 需要注意的点
    • 高级定时器需要对其句柄.Init.RepetitinCounter(即重复计数器的初始值)进行设定

9.4    输出比较模式配置步骤(输出翻转模式实验)


  • 需要关注的结构体
    • 依然是只关注前三个就足够了

9.5    带死区控制的互补输出

  • 概念
    • 死区
      • 在死区中,输出通道和互补输出通道的电平都是无效电平;
      • 死区控制的存在是为了解决元器件传输电平过程中造成的延时。以电机控制为例,就是使用互补输出使得下图交叉导通分别实现正传和反转,但是正反转切换过程中由于元器件的延时特性,有可能会造成在某一时刻双边的三极管同时导通导致VCC直接连接到了GND,因此需要人为在电机切换正反转方向时创造一个死区,使得电机是先关闭再切换方向,从而避免了短路烧毁。
    • 注意⚠️
      • 任何时候输出和互补输出都不能同时处于有效电平,这个硬件上就决定了,若软件上同时输出有效电平,则都会变成无效电平;
  • 死区时间计算

9.6    带死区控制的互补输出配置步骤


  • 关键结构体1
    • 这次除了OCFastMode以外全都用上了;
    • 其中最后两个成员就是上文所说的刹车完毕后空闲状态的电平设置;
    • 注意⚠️:上述最后两个不能都是其自身的有效电平,否则会被硬件强制赋值为两个无效电平,就像上文说的一样:“任何时候输出和互补输出都不能同时处于有效电平,这个硬件上就决定了,若软件上同时输出有效电平,则都会变成无效电平”
  • 关键结构体2
    • 寄存器锁定:一般用不到
    • 刹车输入极性:指定高电平是刹车还是低电平是刹车
    • 自动恢复输出使能:允许刹车结束后自动恢复输出
    • 注意⚠️!死区时间DeadTime是DTG寄存器的值,并不是真正的死区时间,真正的死区时间需要通过死区时间计算公式算出来。
  • 值得注意的是,按照例程跑出来的死区时间是在输出信号的下降沿后上升沿前的,如下图,要考虑一下如果要实现上升沿后下降沿前,可能就不是这样了。按我的理解来说,是将输出和互补输出的极性都设置为低电平有效应该就可以实现了。但是也不是那么确定,毕竟没有示波器做实验。⚠️⚠️⚠️因此,后续要使用到死区时间的时候必须弄清楚这个问题才行!!!

9.7    刹车(断路)功能

  • 刹车发生后:
  • 关于刹车互补输出的设置可以看STM32F1XX参考手册的13.4.9,重点是表75,其中关于空闲状态的描述挺有意思,就是刹车后会进入空闲状态,而空闲状态的输出和互补输出电平由OISx和OISxN寄存器来决定。

9.8    PWM输入模式

  • 作用:测量输入PWM波的参数;
  • 测量精度:由计数器工作频率决定,要是计数器选择内部时钟源,而APB对计数器时钟又没有分频,那么测量精度就是1/72000000s(计一次数的时间数值是这样,但是单纯这样表示精度我不知道对不对,我瞎写的)
  • 原理:记录PWM信号的上升沿、下降沿、下一个上升沿的对应计数值,再用它们和计一次数的时间相乘,即可计算其占空比和周期了;
  • 例程中选用的方法:
    • 将IC1和IC2同时映射到TIMx_CH1上;
    • IC1使用上升沿触发,IC2使用下降沿触发;
    • 从模式控制器设置为复位模式,利用TI1FP1的上升沿信号触发。当从模式控制器为复位模式时,TI1FP1的信号会使计数器CNT重新初始化为0(递增计数)或者ARR(递减计数,一般用递增,因为好算);
  • 注意⚠️:
    • 只有通道1和通道2可以用于测量,因为通道3和通道4没有响应的TI1FP1信号用于触发CNT复位;
  • 配置方法:

  • 关键结构体
  • 仅需使用前两个成员;
    • 从模式选择:选复位模式
    • 触发源:TI1FP1或TI1FP2
    • 触发极性:就是用于配置TI1FP1或TI1FP2产生前的上升沿、下降沿检测器
    • 剩下的两个用不到;
  • 注意⚠️:
    • 需要使用定时器的捕获比较中断:TIMx_CC_IRQHandler
    • TIM_IC_InitTypeDef中的ICSelection,意思为选择ICx的映射关系,如IC1映射到TI1上,就选TIM_ICSELECTION_DIRECTTI,如IC2也映射到TI1上,就选TIM_ICSELECTION_INDIRECTTI,索引到其注释可以看到下图所述的内容,即映射关系,根据映射关系选就行了,也是一个分组选取的东西。
    • 在中断处理回调函数中,可通过htim->Channel来判断中断处理函数响应的通道,来区分TI1和TI2,如下图:
    • 获取捕获值:HAL_TIM_ReadCapturedValue

10    公共注意事项

10.1    BASE_Init和功能_Init之间的MsInit矛盾

当HAL_TIM_PWM_Init和HAL_TIM_Base_Init函数先后调用时,后调用的函数的MsInit函数将不被执行,因为外设Init函数中有对外设进行状态判断的代码,只要执行过一次Init,那么其State就不是Reset状态的了,因此就不会执行另一个Init函数的MsInit函数。

注意⚠️:

        对同一个外设执行多次不同层级的Init函数的话,只能在第一个Init函数的MsInit函数中写相关的初始化函数。

10.2    HAL_TIM_IC_Start和HAL_TIM_IC_Start_IT的区别

HAL_TIM_IC_Start是只启动输入捕获,不使能其捕获中断;而HAL_TIM_IC_Start_IT是在启动输入捕获的同时也启动输入捕获的中断,相当于在HAL_TIM_IC_Start后同时调用__HAL_TIM_ENABLE_IT(htim, TIM_IT_CCx)而已。

如果是使用HAL_TIM_IC_Start_IT,那就必须定义其中断处理函数以及回调处理函数了。不然中断产生后,程序会一直中断,无法执行下去,这俩任意少写一个都会这样。

10.3    while等待状态变化和中断回调不要同时使用

标题说可能说不太清楚,这里举一个例子:

假设需要输入捕获一个上升沿,如果你开启了输入捕获的中断,并编写了中断服务函数和输入捕获回调函数(哪怕没有内容,当然公共中断处理函数还是要调用的),而同时你又有一个while循环用来计算输入捕获的产生时间,那么这个while就是不准的了。如下图这样的while,在启用了IC中断后这个函数返回的就不是TPAD_TIMX_CAP_CHY_CCRX值了,而是中间那个CNT。

  • 原因分析:

        在中断产生后,中断服务中的外设公共中断服务函数会将中断的标志位清除,从而使得while循环多次直至满足超时条件。

  • 更严重的后果:

        若在所述的while循环体中,加入了一点耗时的函数,哪怕就是单纯的读取CNT,就有可能造成连超时条件都是难以达成。因为稍微耗时一点点的函数都有可能导致CNT溢出,从而重新计数,极大可能最终导致一直卡在循环中,一直无法满足超时条件而退出。

        因此,假设真的需要添加耗时函数在其中,最好是使能一下TIM的Update事件,从而记录下溢出的次数,再从而记录下真实的累积CNT来进行超时判断。 

10.4    上升沿和下降沿的监测和切换

  • 监测:
    • 方法一:使用标志位,自己有变量存储上升沿和下降沿的检测到与否的状态,那就知道现在来的是上升沿还是下降沿;
    • 方法二:在回调处理函数中,检测GPIO的值,要是为高电平,那肯定就是上升沿;要是为低电平,那就肯定为下降沿;
  • 切换:
    • 使用TIM_RESET_CAPTUREPOLARITY()函数先清除,再使用TIM_SET_CAPTUREPOLARITY()函数重新设定为另一个边沿检测;

 
 


http://www.kler.cn/news/282925.html

相关文章:

  • leetcode46:全排列
  • 自动化测试员的职业前景
  • 【考研数学】如何实现高效刷题?怎么刷题?
  • 【Pytorch】生成对抗网络实战
  • 切片上传记录
  • Centos 添加双网卡 (生产环境配置记录)
  • 【区块链 + 司法存证】印记区块链电子印章 | FISCO BCOS应用案例
  • BERT:Pre-training of Deep Bidirectional Transformers forLanguage Understanding
  • centOS安装R语言4.0及以上
  • 少走弯路,ESP32 读取Micro SD(TF)播放mp3的坑路历程。
  • QGraphicsView类介绍
  • MySQL迁移到ClickHouse
  • Docker 基本命令
  • [windows][软件]Windows平台MongoDB的安装
  • 【机器学习】线性回归正则化的概念、三种正则化方法的优缺点、使用场景以及在python中的实例
  • 关于武汉芯景科技有限公司的MCU监控芯片XJ809S开发指南(兼容MAX809S)
  • <数据集>手部识别数据集<目标检测>
  • Qt WebSocket
  • 【论文阅读】YOLOv10: Real-Time End-to-End Object Detection
  • SQL 简易建库和增删改查
  • 【Redis】主从复制
  • 操作系统 --王道计算机考研--学习笔记
  • 海信电视与《黑神话 :悟空》合作,大屏端流畅游戏体验如何保障?
  • 构建自闭症解决之道:多维度策略的探索与实践
  • YOLOv8 训练15种动物目标检测模型
  • day03-面向对象-内部类泛型常用API
  • [底层原理] C/C++获取时间(将时间戳转换为年月日)?
  • EmguCV学习笔记 VB.Net 7.2 特征点检测
  • Java之内部类
  • 浅谈Kafka(三)