【STM32】定时器

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

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

目录

[1 定时器类型](#1 定时器类型)

[2 计时器的几种溢出模式](#2 计时器的几种溢出模式)

[3 芯片定时器参数及性质查询](#3 芯片定时器参数及性质查询)

[4 关于计数器计数值的计算](#4 关于计数器计数值的计算)

[5 常用公共函数](#5 常用公共函数)

[5.1 定时器计数器清零](#5.1 定时器计数器清零)

[5.2 获取计数器当前值](#5.2 获取计数器当前值)

[5.3 开启中断](#5.3 开启中断)

[5.4 启用xx功能及其对应中断](#5.4 启用xx功能及其对应中断)

[5.5 启用xx功能但不启用其对应中断](#5.5 启用xx功能但不启用其对应中断)

[5.6 清除标志位](#5.6 清除标志位)

[6 基本定时器](#6 基本定时器)

[6.1 特性](#6.1 特性)

[6.2 原理](#6.2 原理)

[6.3 溢出时间计算方法](#6.3 溢出时间计算方法)

[6.4 配置步骤](#6.4 配置步骤)

[6.5 用到的函数](#6.5 用到的函数)

[6.6 注意事项⚠️](#6.6 注意事项⚠️)

[7 输出比较模式](#7 输出比较模式)

[7.1 PWM1/PWM2模式](#7.1 PWM1/PWM2模式)

[7.2 翻转模式](#7.2 翻转模式)

[8 通用定时器](#8 通用定时器)

[8.1 特性](#8.1 特性)

[8.2 原理](#8.2 原理)

[8.3 配置PWM输出模式](#8.3 配置PWM输出模式)

[8.4 配置PWM输出模式相关函数](#8.4 配置PWM输出模式相关函数)

[8.5 输入捕获](#8.5 输入捕获)

[8.6 输入捕获配置步骤](#8.6 输入捕获配置步骤)

[8.7 脉冲计数](#8.7 脉冲计数)

[8.8 脉冲计数配置步骤](#8.8 脉冲计数配置步骤)

[9 高级定时器](#9 高级定时器)

[9.1 特性](#9.1 特性)

[9.2 重复计数器](#9.2 重复计数器)

[9.3 输出指定个数的PWM](#9.3 输出指定个数的PWM)

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

[9.5 带死区控制的互补输出](#9.5 带死区控制的互补输出)

[9.6 带死区控制的互补输出配置步骤](#9.6 带死区控制的互补输出配置步骤)

[9.7 刹车(断路)功能](#9.7 刹车(断路)功能)

[9.8 PWM输入模式](#9.8 PWM输入模式)

[10 公共注意事项](#10 公共注意事项)

[10.1 BASE_Init和功能_Init之间的MsInit矛盾](#10.1 BASE_Init和功能_Init之间的MsInit矛盾)

[10.2 HAL_TIM_IC_Start和HAL_TIM_IC_Start_IT的区别](#10.2 HAL_TIM_IC_Start和HAL_TIM_IC_Start_IT的区别)

[10.3 while等待状态变化和中断回调不要同时使用](#10.3 while等待状态变化和中断回调不要同时使用)


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输出不同,输入捕获并不是很在意计数器溢出的时间(4.3),其更关注计数频率,计数频率越高得到的脉宽精度才会越高,4.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来进行超时判断。

相关推荐
MARIN_shen41 分钟前
Marin说PCB之POC电路layout设计仿真案例---06
网络·单片机·嵌入式硬件·硬件工程·pcb工艺
Asa3191 小时前
STM32-按键扫描配置
stm32·单片机·嵌入式硬件
南城花随雪。1 小时前
单片机:实现驱动超声波(附带源码)
单片机·嵌入式硬件
嵌入式科普1 小时前
十三、从0开始卷出一个新项目之瑞萨RZN2L串口DMA接收不定长
c语言·stm32·瑞萨·e2studio·rzn2l
yutian060610 小时前
Keil MDK下载程序后MCU自动重启设置
单片机·嵌入式硬件·keil
析木不会编程13 小时前
【小白51单片机专用教程】protues仿真独立按键控制LED
单片机·嵌入式硬件·51单片机
枯无穷肉17 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
不过四级不改名67717 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式科普17 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
嵌入式大圣17 小时前
单片机UDP数据透传
单片机·嵌入式硬件·udp