我们这一次学习采用控制器是STM32F407作为控制。

电机控制与 STM32 定时器的关系

电机的控制与 STM32 定时器有着密不可分的关系,举两个例子

  • 在直流有刷电机的控制中,我们常用脉冲宽度调制技术(PWM)来控制电压的大小,以此改变直流有刷电机的转速。
  • 对于步进电机而言,接收的脉冲个数决定了它的旋转位置,脉冲频率决定了它的旋转速度。

电机的控制本质上就是脉冲的控制,所以通过 STM32定时器对脉冲信号实现更高效的控制。学习 STM32 定时器的功能,包括定时器中断PWM 输出互补输出`带死区刹车输入捕获,等等。

基本定时器

STM32F407 有众多的定时器,其中包括 :

  • 2 个基本定时器(TIM6 和 TIM7)
  • 10 个通用定时器(TIM2 ---TIM5,TIM9~ TIM14)
  • 2 个高级控制定时器(TIM1 和 TIM8)

这些定时器彼此完全独立,不共享任何资源。下面学习使用 STM32F407 的基本定时器中断。使用 TIM6 的定时器中断来控制 LED 的翻转,在主函数用 LED0 的翻转来提示程序正在运行。

基本定时器简介

STM32F407 有两个基本定时器TIM6TIM7,它们的功能完全相同,资源是完全独立的,可以同时使用。其主要特性如下:16 位自动重载递增计数器,16 位可编程预分频器,预分频系数 1~65536,用于对计数器时钟频率进行分频,还可以触发 DAC 的同步电路,以及生成中断/DMA 请求。

基本定时器框图

时钟源

定时器的核心就是计算器,要实现计数功能,首先要给它一个时钟源。基本定时器时钟挂载在 APB1 总线,所以它的时钟来自于 APB1 总线,但是基本定时器时钟不是直接由 APB1 总线直接提供,而是先经过一个倍频器。当 APB1 的预分频器系数为 1 时,这个倍频器系数为1,即定时器的时钟频率等于 APB1 的频率;当 APB1 的预分频器系数≥2 分频时,这个倍频器系数就为 2,即定时器的时钟频率等于 APB1 频率的两倍。我们在 sys_stm32_clock_init 时钟设置函数已经设置 APB1 总线时钟频率为 42M,,APB1 总线的预分频器分频系数是 4,所以挂载在 APB1 总线的定时器时钟频率为 84Mhz。

控制器

定时器复位使能计数等功能触发 DAC 转换

时基单元

时基单元包括计数器寄存器(TIMx_CNT)预分频器寄存器(TIMx_PSC)自动重载寄存器(TIMx_ARR)。基本定时器的这三个寄存器都是 16 位有效数字,即可设置值范围是 0~65535。时基单元中的预分频器 PSC,它有一个输入和一个输出。输入 CK_PSC 来源于控制器部分,实际上就是来自于内部时钟(CK_INT),即 2 倍的 APB1 总线时钟频率(84MHz)。输出CK_CNT 是分频后的时钟,它是计数器实际的计数时钟,通过设置预分频器寄存器(TIMx_PSC)的值可以得到不同频率 CK_CNT,计算公式如下:

fCK_CNT= fCK_PSC / (PSC[15:0]+1)
  • PSC[15:0]是写入预分频器寄存器(TIMx_PSC)的值。预分频器寄存器(TIMx_PSC)可以在运行过程中修改它的数值,新的预分频数值将在下一个更新事件时起作用。因为更新事件发生时,会把 TIMx_PSC 寄存器值更新到其影子寄存器中,这才会起作用。

基本定时器的计数器(CNT)是一个递增的计数器,当寄存器(TIMx_CR1)的 CEN 位置 1,即使能定时器,每来一个 CK_CNT 脉冲,TIMx_CNT 的值就会递增加 1。当 TIMx_CNT值与 TIMx_ARR 的设定值相等时,TIMx_CNT 的值就会被自动清零并且会生成更新事件(如果开启相应的功能,就会产生 DMA 请求、产生中断信号或者触发 DAC 同步电路),然后下一个 CK_CNT 脉冲到来,TIMx_CNT 的值就会递增加 1,如此循环。在此过程中,TIMx_CNT等于 TIMx_ARR 时,我们称之为定时器溢出,因为是递增计数,故而又称为定时器上溢。定时器溢出就伴随着更新事件的发生。

比如我们需要一个 500ms 周期的定时器中断,一般思路是先设置预分频寄存器,然后才是自动重载寄存器。考虑到我们设置的 CK_INT 为 84MHZ,我们把 预 分 频 系 数 设 置 为 8400 , 即 写 入 预 分 频 寄 存 器 的 值 为 8399 , 那 么fCK_CNT=84MHZ/8400=10KHZ。这样就得到计数器的计数频率为 10KHZ,即计数器 1 秒钟可以计 10000 个数。我们需要 500ms 的中断周期,所以我们让计数器计数 5000 个数就能满足要求,即需要设置自动重载寄存器的值为 4999,另外还要把定时器更新中断使能位 UIE 置 1,CEN 位也要置 1。

TIM6/TIM7 寄存器

==控制寄存器 1(TIMx_CR1)==

该寄存器,我们需要注意的是:位 0(CEN)用于使能或者禁止计数器,该位置 1 计数器开始工作,置 0 则停止。还有位 7(APRE)用于控制自动重载寄存器 ARR 是否具有缓冲作用,如果 ARPE 位置 1,ARR 起缓冲作用,即只有在更新事件发生时才会把 ARR 的值写入其影子寄存器里;如果 ARPE 位置 0,那么修改自动重载寄存器的值时,该值会马上被写入其影子寄存器中,从而立即生效。

==DMA/中断使能寄存器(TIMx_DIER)==

该寄存器位 0(UIE)用于使能或者禁止更新中断,因为本实验用到了定时器的更新中断,所以配置时需要把该位置 1。位 8(UDE)用于使能或者禁止更新 DMA 请求。

==状态寄存器(TIMx_SR)==

该寄存器位 0(UIF)是中断更新的标志位,当发生中断时由硬件置 1,然后就会执行到中断服务函数,但是需要软件去清零,所以我们必须在中断服务函数里把该位清零。

==计数器寄存器(TIMx_CNT)==

该寄存器位[15:0]就是计数器的实时的计数值。

==预分频寄存器(TIMx_PSC==)

该寄存器是 TIM6/TIM7 的预分频寄存器,比如我们要 8200 分频,就往该寄存器写入 8199。

==自动重载寄存器(TIMx_ARR==

该寄存器可以由 APRE 位设置是否进行缓冲。计数器的值会和 ARR 寄存器影子寄存器进行比较,当两者相等,定时器就会溢出,从而发生更新事件,如果打开更新中断,还会发生更新中断。

定时器的 HAL 库驱动

定时器在 HAL 库中的驱动代码在stm32f4xx_hal_tim.cstm32f4xx_hal_tim_ex.c 文件(及它们的头文件)中。

HAL_TIM_Base_Init 函数

==定时器的初始化函数==

HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
  • 形参 1 是 TIM_HandleTypeDef
{
    TIM_TypeDef *Instance; /* 外设寄存器基地址 */
    TIM_Base_InitTypeDef Init; /* 定时器初始化结构体*/
    HAL_TIM_ActiveChannel Channel; /* 定时器通道 */
    DMA_HandleTypeDef *hdma[7]; /* DMA 管理结构体 */
    HAL_LockTypeDef Lock; /* 锁定资源 */
    __IO HAL_TIM_StateTypeDef State; /* 定时器状态 */
    __IO HAL_TIM_ChannelStateTypeDef ChannelState [4]; /* 定时器通道状态 */
    __IO HAL_TIM_ChannelStateTypeDef ChannelNState [4]; /* 定时器互补通道状态 */
    __IO HAL_TIM_DMABurstStateTypeDef DMABurstState; /* DMA 溢出状态 */
}TIM_HandleTypeDef;
    • Instance:指向定时器寄存器基地址。

    • Init:定时器初始化结构体,用于配置定时器的相关参数。

    • Channel:定时器的通道选择,基本定时器没有该功能。

    • hdma[7]:用于配置定时器的 DMA 请求。

    • Lock:ADC 锁资源。

    • State:定时器工作状态。

    • ChannelState:定时器通道状态。

    • ChannelNState:定时器互补通道状态。

    • DMABurstState:DMA 溢出状态。

  • TIM_Base_InitTypeDef

typedef struct
{
 uint32_t Prescaler; /* 预分频系数 */ 
 uint32_t CounterMode; /* 计数模式 */
 uint32_t Period; /* 自动重载值 ARR */
 uint32_t ClockDivision; /* 时钟分频因子 */ 
 uint32_t RepetitionCounter; /* 重复计数器 */
 uint32_t AutoReloadPreload; /* 自动重载预装载使能 */
} TIM_Base_InitTypeDef;
    • Prescaler:预分频系数,即写入预分频寄存器的值,范围 0 到 65535。
    • CounterMode:计数器计数模式,这里基本定时器只能向上计数。
    • Period:自动重载值,即写入自动重载寄存器的值,范围 0 到 65535。
    • ClockDivision:时钟分频因子,也就是定时器时钟频率 CK_INT 与数字滤波器所使用的采样时钟之间的分频比,基本定时器没有此功能。
    • RepetitionCounter:设置重复计数器寄存器的值,用在高级定时器中。
    • AutoReloadPreload:自动重载预装载使能,即控制寄存器 1 (TIMx_CR1)的 ARPE 位。

HAL_TIM_Base_Start_IT 函数

HAL_TIM_Base_Start_IT 函数是更新定时器中断和使能定时器的函数。

HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);

单独使能/关闭定时器中断和使能/关闭定时器方法。

__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE); /* 使能句柄指定的定时器更新中断 */
__HAL_TIM_DISABLE_IT (htim, TIM_IT_UPDATE); /* 关闭句柄指定的定时器更新中断 */
__HAL_TIM_ENABLE(htim); /* 使能句柄 htim 指定的定时器 */
__HAL_TIM_DISABLE(htim); /* 关闭句柄 htim 指定的定时器 */

定时器中断配置步骤

使能定时器时钟

HAL 中定时器使能是通过宏定义标识符来实现对相关寄存器操作

__HAL_RCC_TIMx_CLK_ENABLE(); /* x=1~14 */

初始化定时器参数,设置自动重装值,分频系数,计数方式等

定时器的初始化参数是通过定时器初始化函数 HAL_TIM_Base_Init 实现的。

使能定时器更新中断,开启定时器计数,配置定时器中断优先级

  • 通过 HAL_TIM_Base_Start_IT 函数使能定时器更新中断和开启定时器计数。
  • 通过 HAL_NVIC_EnableIRQ 函数使能定时器中断,通过 HAL_NVIC_SetPriority 函数设置中断优先级。

编写中断服务函数

定时器中断服务函数为:TIMx_IRQHandler,当发生中断的时候,程序就会执行中断服务函数。HAL 库为了使用方便,提供了一个定时器中断通用处理函数 HAL_TIM_IRQHandler,该函数又会调用 HAL_TIM_PeriodElapsedCallback 等一些回调函数,需要用户根据中断类型选择重定义对应的中断回调函数来处理中断程序。

高级定时器

高级定时器简介

STM32F407的TIM2TIM5计数器是 32 位的,其他的都是 16 位的。通用定时器和高级定时器其实也就是在基本定时器的基础上,添加了一些其他功能,如:输入捕获、输出比较、输出 PWM 和单脉冲模式等,其特性有一些的差异,但是基本原理都一样。互补输出则是高级定时器特有的功能。

高级定时器的框图

① 时钟源

高级定时器时钟可由下列的时钟源提供:

  • 内部时钟 (CK_INT)
  • 外部时钟模式 1:外部输入引脚 (TIx),x=1,2,3,4
  • 外部时钟模式 2:外部触发输入 (ETR)
  • 内部触发输入 (ITRx):使用一个定时器作为另一定时器的预分频器

②控制器

控制器包括:从模式控制器、编码器接口和触发控制器(TRGO)。从模式控制器可以控制计数器复位、启动、递增/递减、计数。编码器接口针对编码器计数。触发控制器用来提供触发信号给别的外设,比如为其他定时器提供时钟或者为 DAC/ADC 的触发转换提供信号。

③时基单元

高级定时器时基单元包括:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR)、重复计数器寄存器(TIMx_RCR)。

高级定时器的计数模式有三种:递增计数模式、递减计数模式和中心对齐模式。

④输入捕获

输入捕获包括:4 个输入捕获通道、输入滤波和边沿检测以及预分频器等部分。IO 端口通过复用功能与这些通道相连。配置好 IO 端口的复用功能后,将需要测量的信号输入到相应的 IO 端口,输入捕获部分可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常见的测量有:测量输入信号的脉冲宽度、测量 PWM 输入信号的频率和占空比等。

⑤输入捕获和输出比较公用部分

⑥输出比较

输出比较包括:4 个输出比较通道、3 个互补通道、死区发生器以及输出控制器,用于输出比较模式或 PWM 输出模式。高级定时器输出比较部分和通用定时器相比,多了带死区控制的互补输出功能。图 4.3.1.1第⑥部分的 TIMx_CH1N、TIMx_CH2N 和 TIMx_CH3N 分别是定时器通道 1、通道 2 和通道

3 的互补输出通道,通道 4 是没有互补输出通道的。DTG 是死区发生器,死区时间由 DTG[7:0]位来配置。如果不使用互补通道和死区时间控制,那么高级定时器 TIM1 和 TIM8 和通用定时器的输出比较部分使用方法基本一样,只是要注意 MOE 位得置 1 定时器才能输出。

⑦断路功能

断路功能也称刹车功能,一般用于电机控制的刹车。F4 系列有一个断路通道,断路源可以是刹车输入引脚(TIMx_BKIN),也可以是一个时钟失败事件。时钟失败事件由复位时钟控制器中的时钟安全系统产生。系统复位后,断路功能默认被禁止,MOE 位为低。使能断路功能的方法:将 TIMx_BDTR 的位 BKE 置 1。断路输入引脚 TIMx_BKIN 的输入有效电平可通过 TIMx_BDTR 寄存器的位 BKP 设置。使能刹车功能后:由 TIMx_BDTR 的 MOE、OSSI、OSSR 位,TIMx_CR2 的 OISx、OISxN位,TIMx_CCER 的 CCxE、CCxNE 位控制 OCx 和 OCxN 输出状态。无论何时,OCx 和 OCxN输出都不能同时处在有效电平。

高级定时器 PWM 输出

脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。我们可以让定时器产生PWM,在计数器频率固定时,PWM 频率或者周期由自动重载寄存器(TIMx_ARR)的值决定,其占空比由捕获/比较寄存器(TIMx_CCRx)的值决定。

定时器工作在递增计数模式,纵轴是计数器的计数值 CNT,横轴表示时。当CNT=CCRx 时,IO 输出高电平(逻辑 1);当CNT=ARR 时,定时器溢出,CNT 的值被清零,然后继续递增,依次循环。在这个循环中,改变 CCRx 的值,就可以改变 PWM 的占空比,改变 ARR 的值,就可以改变 PWM 的频率,这就是 PWM 输出的原理。

定时器产生 PWM 的方式有许多种,下面我们以边沿对齐模式(即递增计数模式/递减计数模式)为例,PWM 模式 1 或者 PWM 模式 2 产生 PWM

TIM1/TIM8 寄存器

STM32F407 的高级定时器输出 PWM,除了需要定时器的寄存器外,我们还会用到另外 5 个寄存器,包括:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)、重复计数器寄存器(TIMx_RCR)、断路和死区寄存器(TIMx_BDTR)。

==捕获/比较模式寄存器 1/2(TIMx_CCMR1/2)==

TIM1/TIM8 的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有 2 个:TIMx _CCMR1 和 TIMx _CCMR2。TIMx_CCMR1 控制 CH1 和 CH2,而 TIMx_CCMR2 控制 CH3和 CH4。

该寄存器的有些位在不同模式下,功能不一样,我们前面已经说过。比如我们要让 TIM8的 CH1 输出 PWM 波为例,该寄存器的模式设置位 OC1M[2:0]就是对应着通道 1 的模式设置,此部分由 3 位组成,总共可以配置成 8 种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110 或者 111,分别对应 PWM 模式 1 和 PWM 模式 2。这两种 PWM 模式的区别就是输出有效电平的极性相反,这里我们设置为 PWM 模式 2。位 3 OC1PE 是输出比较通道 1 的预装使能,该位需要置 1,另外 CC1S[1:0]用于设置通道 1 的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。

==捕获/比较使能寄存器(TIMx_CCER)==

==捕获/比较寄存器 1/2/3/4(TIMx_CCR1/2/3/4)==

捕获/比较寄存器(TIMx_CCR1/2/3/4),该寄存器总共有 4 个,对应 4 个通道 CH1~CH4。

==重复计数器寄存器(TIMx_RCR)==

重复计数器寄存器仅高级定时器拥有,此寄存器用于设置重复计数器值,因为它具有影子寄存器,所以它本身只是起缓冲作用。

该寄存器的 REP[7:0]位是低 8 位有效,即最大值 255。因为这个寄存器只是起缓冲作用,如果大家对该寄存器写入值后,想要立即生效,可以通过对 UG 位写 1,产生软件更新事件。注意:如果是通用定时器,则没有重复计数器寄存器。

==TIM1/TIM8 断路和死区寄存器(TIMx_BDTR)==

该寄存器,我们只需要关注位 15(MOE),要想高级定时器的 PWM 正常输出,则必须设置 MOE 位为 1,否则不会有输出。注意:如果是通用定时器,则没有断路和死区寄存器。