【正点原子MP157连载】第十八章 高级定时器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南

1)实验平台:正点原子STM32MP157开发板
2)购买链接:https://item.taobao.com/item.htm?&id=629270721801
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子STM32MP157技术交流群:691905614

第十八章 高级定时器实验

本章我们主要来学习高级定时器,STM32MP157有2个高级定时器(TIM1和TIM8)。我们将通过四个实验来学习高级定时器的各个功能,分别是高级定时器输出指定个数PWM实验、高级定时器输出比较模式实验、高级定时器互补输出带死区控制实验和高级定时器PWM输入模式实验。
本章分为如下几个小节:
18.1、高级定时器简介;
18.2、高级定时器输出指定个数PWM实验;
18.3、高级定时器输出比较模式实验;
18.4、高级定时器互补输出带死区控制实验;
18.5、高级定时器PWM输入模式实验;
18.1 高级定时器简介
高级定时器的框图在通用定时器框图的基础上加了一些功能,前面如果学习了通用定时器的框图和实验,再看高级定时器的框图时就很好理解了。高级定时器的框图和通用定时器的框图十分相似,但仍有微弱差异,如下是高级定时器的框图:
在这里插入图片描述

图18.1. 1高级定时器框图
框图中大部分的模块我们在基本定时器以及通用定时器的框图中都有详细的讲解,这里部分模块我们不再重复详细讲解。
①是时钟源部分;②是控制器部分(从模式控制器、编码器接口和触发控制器TRGO);③是时基单元;④是输入捕获部分;⑤是输入捕获和输出比较公用部分;⑥是输出比较部分;⑦是断路功能部分。下面我们介绍高级定时器相关的部分。
③时基单元
高级定时器时基单元功能包括四个寄存器,分别是计数器寄存器(CNT),预分频器寄存器(PSC),自动重载寄存器(ARR)和重复计数器寄存器(RCR)。其中重复计数器RCR是高级定时器独有的,这也是时基单元与通用定时器以及基本定时器的差别。前面三个寄存器都是16位有效,TIMx_RCR寄存器有16位,且16位可用。
重复计数器RCR
在基本/通用定时器发生上/下溢事件时,直接会产生中断然后生成更新事件,但对于高级定时器来说不是这样子,高级定时器在硬件结构上多出了重复计数器,在定时器发生上溢/下溢事件时递减重复计数器的值,只有当重复计数器的值递减为0的时候才会生成更新时间,所以当发生了RCR寄存器的值递减到0+1次的上/下溢时才会产生更新事件。
⑦断路功能
⑦中有两个断路输入(TIMx_BKIN和TIMx_BKIN2),用于将定时器的输出信号置于用户可选的安全配置中。断路功能就是电机控制的刹车功能,断路功能的目的是保护由 TIM1 和 TIM8 定时器产生的 PWM 信号所驱动的功率开关,高级定时器的PWM输出的功能特性就是用来控制电机的。使能相应的控制位(TIMx_BDTR寄存器中的MOE、OSSI和OSSR位,TIMx_CR2寄存器中的OISx和OISxN位)来控制是否输出信号以及输出信号的状态,但是无论何时,OCx和OCxN输出不能在同一时间同时处于有效电平上。
因为有两个断路输入,所以断路源可以分为断路 (BRK) 通道的源和断路 2 (BRK2) 通道的源。断路 (BRK) 通道的源可以是连接到 BKIN 引脚的外部源或者内部源(如系统中断、比较器的输出和时钟故障等),断路 2 (BRK2) 的源可以是连接到 BKIN 引脚的外部源或者来自比较器输出的内部源。断路源是或运算关系,如下图是断路BKIN概况。
在这里插入图片描述

图18.1. 2短路源是或运算关系
系统复位后默认禁止刹车电路,即默认关闭了断路功能(TIMx_BDTR寄存器中的MOE位为0)。设置TIMx_BDTR寄存器中的BKE位和BKE2 位可以使能或者禁止断路功能。断路输入信号的极性可以通过配置BKP位和BKP2位来选择。
了解完这两个差异之后,我们直接通过实际的实验来学习高级定时器。
18.2 高级定时器输出指定个数PWM实验
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 11-1 ATIM_PWM_OUT。
本小节我们来学习使用高级定时器输出指定个数PWM,本实验以高级定时器8为例TIM1操作也类似。关于定时器如何输出PWM波的知识,请大家回顾通用定时器PWM输出实验的介绍。下面下面我们先来了解本实验会涉及的寄存器。
18.2.1 TIM1/TIM8寄存器

  1. 控制寄存器 1(TIMx_CR1)
    在这里插入图片描述

图18.2.1. 1 TIMx_CR1寄存器
上图中我们这里仅介绍本章节会用到的一些位,其中:
位0 [CEN] 是计数器使能位,我们前面就遇见过,将该位写入:
0:禁止计数器;
1:使能计数器。
位4 [DIR]是计数器方向设置位,当定时器配置为中心对齐模式或编码器模式时,该位为只读状态,将该位:
0:计数器递增计数;
1:计数器递减计数。
位7 [ARPE]是自动重载预装载使能位(Auto-reload preload enable),该位用于控制自动重载寄存器的影子寄存器是否有效,在基本定时器的章节我们已经讲过,可以回顾前面的内容。将该位:
0:TIMx_ARR 寄存器不进行缓冲;
1:TIMx_ARR 寄存器进行缓冲。
2. 捕获/比较使能寄存器(TIMx_ CCER)
在这里插入图片描述

图18.2.1. 2 TIMx_ CCER寄存器
该寄存器控制着各个输入/输出通道的开关,每4个位控制一个通道,我们以通道1(CC1)为例介绍第0~3位。
位0[CC1E]是捕获/比较 1 输出使能位,如果CC1 通道配置为输出,将该位:
0:关闭输出,即OC1 未激活;
1:开启输出,即OC1 信号输出到相应的输出引脚上。
位1[CC1P]是捕获/比较 1 输出极性配置位,对于CC1 通道配置为输出的情况,将该位:
0:OC1 高电平有效
1:OC1 低电平有效
位2[CC1NE]是捕获/比较 1 互补输出使能位,将该位:
0:关闭捕获/比较 1 互补输出;
1:开启捕获/比较 1 互补输出。
位3[CC1NP]是捕获/比较 1 互补输出极性配置位,当CC1 通道配置为输出时,将该位:
0:OC1N 高电平有效;
1:OC1N 低电平有效。
以上的4位就介绍这么多,对于其它的通道也是类似的,直接参考通道1(CC1)的各位配置即可。
3. 事件产生寄存器(TIMx_ EGR)
在这里插入图片描述

图18.2.1. 3 TIMx_ EGR寄存器
该寄存器主要是用户用软件更新各类事件和某些寄存器位,这里我们只介绍第0位:
位0[UG]是更新定时器事件的控制位,作用类似定时器溢出更新中断,区别是这里是通过软件去更新,而定时器溢出更新中断是硬件自己完成。本实验就是用到该位去更新定时器事件,对该位写1就可以重新初始化计数器并生成寄存器更新事件,由硬件自动清零。
4. 重复计数器寄存器(TIMx_ RCR)
在这里插入图片描述

图18.2.1. 4 TIMx_ RCR寄存器
用户可通过该寄存器设置从预装载寄存器向活动寄存器周期性传输数据。该该寄存器共16位REP[15:0],在PWM模式下,(REP+1) 对应于边沿对齐模式下的 PWM 周期数或者中心对齐模式下的 PWM 半周期数,所以我们可以利用该寄存器来控制要输出PWM波的个数。生成下一重复更新事件之前,无论向TIMx_RCR寄存器写入何值都无影响,所以我们写完该寄存器后,如果要想马上生效就得手动更新事件,即对TIMx_EGR寄存器的UG位写1。
5. 捕获/比较寄存器2(TIMx_ CCR2)
捕获/比较寄存器(TIMx_ CCRx)总共有4个,分别对应4个通道。本小节的实验我们使用的是通道2(CC2),所以直接看TIMx_CCR2:
在这里插入图片描述

图18.2.1. 5 TIMx_ CCR2寄存器
该寄存器有16位,对于输出模式,CCR2[15:0] 为预装载值,该值与CNT的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制PWM的输出脉宽。
6. TIM1/TIM8断路和死区寄存器(TIMx_ BDTR)
在这里插入图片描述

图18.2.1. 6 TIMx_ BDTR寄存器
对于通用定时器,只需要配置以上提到的寄存器就够了,如果是高级定时器,我们还需要配置TIMx_ BDTR,该寄存器我们只关注位15[MOE],对此位清0,则禁止禁止 OC 和 OCN 输出,如果要想高级定时器的PWM正常输出,则必须设置MOE位为1,即使能 OC 和 OCN 输出。
18.2.2 定时器的HAL库驱动
本节实验涉及到的HAL库驱动在前面通用定时器章节已经讲解,如HAL_TIM_PWM_Init和HAL_TIM_PWM_ConfigChannel函数都已经在前面章节介绍过,这里就不再重复介绍了。
18.2.3 硬件设计

  1. 例程功能
    用TIM8_CH2输出指定个数PWM,按键KEY0每按下一次,就输出5个PWM,输出的PWM控制BEEP的开和关,开关一次表示一个周期的PWM波形。LED0用于指示程序在运行。
  2. 硬件资源
    1)LED0、KEY0按键和蜂鸣器
    BEEP KEY0 LED0
    PC7 PG3 PI0
    图18.2.2. 1硬件资源
    2)定时器8输出通道2(TIM8_CH2)
    定时器属于STM32MP157的内部资源,只需要软件设置好即可正常工作。
  3. 原理图
    按键KEY0是低电平有效,蜂鸣器接在PC7上,关于蜂鸣器的实验我们在前面的章节也有介绍过,当PC7输出低电平的时候,蜂鸣器发声,当PC7输出高电平的时候,蜂鸣器停止发声。从原理图看出PC7还可以复用为TIM8_CH2,程序上通过配置PC7复用为TIM8_CH2,然后按下KEY0按键后,TIM8_CH2就输出5个PWM波,从而蜂鸣器发声5次。
    在这里插入图片描述

图18.2.2. 2引脚部分原理图
从《STM32MP157A&D数据手册》中也可以查阅PC7的复用关系:
在这里插入图片描述

图18.2.2. 3 《STM32MP157A&D数据手册》部分截图
18.2.4 软件设计

  1. STM32CubeMX配置
    (1)配置PI0复用为TIM2_CH1
    新建一个工程ATIM_PWM_OUT,进入STM32CubeMX插件配置界面后,在Pinout & Configuration处配置PC7复用为TIM8_CH2,如下图所示:
    在这里插入图片描述

图18.2.4. 1配置PC7复用为TIM8_CH2
(2)配置TIM8时基等参数
在TimersTIM8中配置TIM8_CH2参数,其中,模式配置如下:
在这里插入图片描述

图18.2.4. 2配置模式为PWM输出
和第17.3小节通用定时器的配置类似,这里选择定时器8的通道2生成PWM波形。配置完TIM8_CH2的模式后,我们配置TIM8_CH2的时基等参数,如下红框部分需要我们手动配置,其它部分就不需要配置了:
在这里插入图片描述

图18.2.4. 3配置TIM8通道2参数
上图的配置参数,除了红框中的要配置以外,其它选项保持默认,本实验用不到刹车功能,所以就不会使能BRK和BRK2。配置部分的介绍如下:
Counter Settings(计数器配置)配置如下:
Prescaler用于配置定时器预分频值,这里配置为20900-1;
Counter Mode用于配置计数模式,我们选择向上计数Up;
Counter Period用于配置定时器自动重装载值,我们设置为5000-1;
Internal Clock Division (CKD)配置为No Division即时钟不分频;
auto-reload preload用于配置自动重载是否使能,我们选择 Enable使能自动重载;
上述参数可以计算定时器的时钟频率为:

计数器的溢出时间:

Repetition Counter(RCR-16 bits value)默认选择为0,这里注意,高级定时器和基本定时器以及通用定时器不同,高级定时器的TIMx_RCR递减为0时才会发生更新事件。
PWM Generation Channel 2用于配置通道2的参数,其中:
Mode:用于配置PWM的模式,这里选择PWM mode1,即PWM模式1。另外还有PWM模式2,可以理解PWM mode l是与PWM mode 2模式互补的波,PWM模式1为高电平时PWM模式2为低电平,反之亦然。
Pulse (16 bits value):是占空比值,也可以说是脉冲宽度,即TIMx_CCR2的值,也就是有效电平的值,可以配置在0-5000之间,例如配置0。这里配置2500,即占空比为50%。在后面的实验中,我们可以对TIMx_CCR2寄存器写入新的值来改变占空比,从而控制蜂鸣器声音的大小。
占空比=
Output compare preload:输出比较预加载项选择Enable,即在定时器工作时是否能修改Pulse的值,如果禁用此项,表示定时器工作时不能进行修改,只能等到更新事件到来的时候才能进行修改,所以这里选择使能。
Fast Mode用于配置PWM脉冲快速模式,这里我们也不需要,可以不配置。
CH Polarity:输出极性,这里我们选择High,即高电平有效(我们的蜂鸣器是低电平有效,即低电平时蜂鸣器发声)。
CH Idle State用于配置通道的空闲状态,也就是配置PWM不输出时的状态,这里默认选择Reset,即PWM关闭状态下默认为低电平。如果配置为Set就是PWM关闭状态下默认为高电平。
以上配置中要注意的是输出极性和PWM模式,其中TIMx_CNT为TIM8的计数寄存器,用于计数器计数,TIMx_CCR2为TIM8的比较寄存器,其值由用户设置,如上面设置为2500。
板子上的的蜂鸣器是低电平有效,如果配置输出极性为High:
在PWM模式1下当向上计数时,如果TIMx_CNT<TIMx_CCR2时,通道2为无效电平,否则为有效电平;在向下计数时,如果TIMx_CNT>TIMx_CCR2时通道2为有效电平,否则为无效电平。
如果在PWM模式2下,在向上计数时,如果TIMx_CNT<TIMx_CCR2时通道2为有效电平,否则为无效电平;在向下计数时,如果TIMx_CNT>TIMx_CCR2时通道2为无效电平,否则为有效电平。
板子上的蜂鸣器是低电平有效,如果配置输出极性为Low:
在PWM模式1下当向上计数时,如果TIMx_CNT<TIMx_CCR2时,通道2为有效电平,否则为无效电平;在向下计数时,如果TIMx_CNT>TIMx_CCR2时通道2为无效电平,否则为有效电平。
如果在PWM模式2下,在向上计数时,如果TIMx_CNT<TIMx_CCR2时通道2为无效电平,否则为有效电平;在向下计数时,如果TIMx_CNT>TIMx_CCR2时通道2为有效电平,否则为无效电平。
例如如果本实验输出极性为Low,配置为PWM模式2、向上计数、比较值始终为600、自动重装载值为500,如果这么配置的话,计数器最大值只能为500,永远小于600,即TIMx_CNT<TIMx_CCR2,通道2电平为无效值,所以蜂鸣器永远不会发声。
(3)配置NVIC
本节实验我们用到定时器的更新中断(溢出中断),所以要配置NVIC,如下图,先使能定时器更新中断:
在这里插入图片描述

图18.2.4. 4开启TIM8的更新中断
上图中看到TIM8有几个配置选项,其中:
TIM8 break interrupt
TIM8 break interrupt是刹车中断,当配置好刹车功能后,当出现刹车信号时可以进入相应的中断请求函数BRK_IRQHandler进行刹车后的动作。要注意,如果要使用刹车中断,则应使能刹车功能(BKE置1)、配置刹车输入极性(配置BKP)等。
TIM8 update interrupt
TIM8 update interupt是更新中断,当计数器上溢/下溢、计数器初始化是会发生更新事件,所以会产生更新中断。本小节实验我们用到此中断。
TIM8 trigger and commutation interrupt
TIM8 trigger and commutation interupt是触发事件引起的中断,如计数器启动、停止、初始化或通过内部/外部触发计数等。
TIM8 capture compare interrupt
TIM8 capture compare interrupt是捕获比较中断,这个就很好理解了,我们前面通用定时器的实验有介绍到。例如捕获到上升沿/下降沿时会发生捕获中断,计数器的值和比较寄存器的值相等时就会触发中断,关于输出比较我们后面的实验会讲解。
接下来要配置中断优先级,这里我们选择中断优先级分组2,抢占优先级和子优先级均为3:
在这里插入图片描述

图18.2.4. 5设置中断优先级
(4)配置GPIO
本实验还会用到LED0和按键KEY0,前面的实验我们有配置过,为了方便,本节实验会拷贝上一章节实验的BSP文件夹到工程中使用,大家可以将三个按键(KEY0、KEY1、WK_UP)以及LDE0和LDE1的都一起配置了。这里就只配置LED0和按键KEY0:
在这里插入图片描述

图18.2.4. 6配置LED0和KEY0
由于蜂鸣器接在PC7上,而PC7已经配置为TIM8_CH2,所以直接在GPIOTIM处配置蜂鸣器。因为蜂鸣器是低电平有效,所以我们最好配置为上拉:
在这里插入图片描述

图18.2.4. 7配置PC7上拉
(5)配置时钟
本实验我们采用外部24MHz的时钟HSE(也可以采用内部时钟),配置时钟树,经过PLL3锁相环以后,APB1的时钟频率为最大209MHz(也可以配置其它频率):
在这里插入图片描述

图18.2.4. 8配置HSE
我们选择HSE,作为锁相环PLL3的时钟源,在MCU子系统时钟里输入209并回车,STM32CubeMX会自动为我们计算参数,然后再手动配置APB1DIV、APB2DIV和APB3DIV的分频值为2。当APB1DIV的分频数大于1的时候,基本定时器的倍频器倍频值始终为2,所以通用定时器TIM8的时钟频率为209MHz。
在这里插入图片描述

图18.2.4. 9配置系统时钟
(5)配置生成独立的文件
配置生成独立的.c和.h头文件,如下图:
在这里插入图片描述

图18.2.4. 10配置生成独立的.c和.h文件
3. 生成工程
配置好后,按下“Ctrl+S”保存修改配置,生成工程,如下:
在这里插入图片描述

图18.2.4. 11生成工程
4. 添加LED和按键驱动代码
将按键输入实验的BSP文件夹拷贝到工程中,我们后面直接用此文件夹里的LED灯驱动和按键驱动:
在这里插入图片描述

图18.2.4. 12添加BSP文件夹
5. 修改tim.c文件
如下图,在标红的字体中间添加我们的代码:

1   /* USER CODE BEGIN 0 */
2   /* 表示当前还剩下多少个脉冲要发送,每次最多发送2^16个脉冲 */
3   static uint32_t atim_npwm = 0;
4   /* USER CODE END 0 */
5 
6   /* USER CODE BEGIN 1 */
7 
8   /**
9    * @brief    高级定时器TIM8设置脉冲个数 
10   * @param    npwm为用户设置的PWM个数,也就是RCR的值(1~2^16个)
11   * @retval   无 
12   */
13  void atim_npwm_set(uint32_t npwm)
14  {
15      if (npwm == 0) return ; 			/* 如果脉冲个数是0,则直接退出 */
16      atim_npwm = npwm;       			/* atim_npwm保存脉冲个数 */
17      TIM8->EGR |= 1 << 0;    			/* 设置UG位为1,使能更新中断 */
18      __HAL_TIM_ENABLE(&htim8);  		/* 使能定时器TIM8 */
19  }
20
21  /**
22   * @brief    定时器更新中断回调函数
23   * @param    htim:定时器句柄指针
24   * @note     此函数会被定时器中断函数共同调用
25   * @retval   无
26   */
27  void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
28  {
29      if (htim == (&htim8))
30      {
31          uint32_t npwm = 0; 			/* 将npwm清零 */
32          /* 还有大于65536个脉冲需要发 */
33          if (atim_npwm > 65536)  
34          {
35              atim_npwm=atim_npwm - 65536;
36              npwm = 65536;
37          }
38          /* 还有不到65536个脉冲要发 */
39          else if (atim_npwm % 65536)   
40          {
41              npwm = atim_npwm % 65536;
42              atim_npwm = 0;         	 	/* 没有脉冲要发了 */
43          }
44          if (npwm)   						/* 有脉冲要发 */
45          {
46              /* 设置重复计数寄存器值为npwm-1, 即npwm个脉冲 */
47              TIM8->RCR = npwm - 1;
48              TIM8->EGR |= 1 << 0;  		/* 设置UG位为1,使能更新中断 */
49              __HAL_TIM_ENABLE(&htim8);  	/* 使能定时器TIMX */
50          //GTIM_TIMX_NPWM->CR1 |= 1 << 0;/* 使能定时器TIMX */
51          }
52          else    							/* 没有脉冲要发 */
53          {
54            /* 关闭定时器TIM8。使用HAL关闭会清除PWM通道信息,所以不能用 */
55             TIM8->CR1 &= ~(1 << 0);
56          }
57          /* 清除定时器溢出中断标志位 */
58          __HAL_TIM_CLEAR_IT(&htim8, TIM_IT_UPDATE);
59      }
60  }
61
62  /* USER CODE END 1 */
第13~19行,atim_npwm_set函数用于用户设置要发送的PWM波个数:
第15行,每次进入此函数的时候都要先检查发送的脉冲个数npwm是否是0,是0的话则直接退出;
第16行,atim_npwm用于保存用户设置的要发送的脉冲个数;
第17行,设置TIMx_EGR 寄存器的UG位为1,以使能更新中断;
第18行,调用HAL库的宏__HAL_TIM_ENABLE来使能定时器,其实也就是将TIMx_CR1寄存器的第0位CEN置1,即开启定时器。该行也可以直接写成:

TIM8->CR1 |=1<<0; /* 使能定时器TIM8*/
第27~60行是定时器更新中断回调函数,我们在通用定时器章节也使用到此函数。我们在STM32CubeMX配置TIM8的时候,使能了“TIM8 update interupt”,即TIM8更新中断。当TIMx_RCR递减为0时,会产生更新中断,发生中断时,会执行stm32mp1xx_it.c文件里的中断服务函数TIM8_UP_IRQHandler,中断服务函数会调用中断请求函数HAL_TIM_IRQHandler:

/* 中断服务函数 */
void TIM8_UP_IRQHandler(void)
{
  /* 中断请求函数 */
  HAL_TIM_IRQHandler(&htim8);
}

中断请求函数HAL_TIM_IRQHandler中会判断中断的类型,判断是更新(溢出)中断,则调用更新中断回调函数HAL_TIM_PeriodElapsedCallback,此函数需要用户自行编写。我们来分析上面的代码:
第31行,npwm用于存放用户要设置的PWM波个数,每次进入此函数都先将其清零。
第33~37行,将PWM波个数分次写入重复计数器寄存器,atim_npwm是用户设置的PWM波个数,如果波形大于65536个(也就是TIMx_RCR的最大值),则atim_npwm减去65536(为剩下的要发送的个数),npwm被赋值65536,也就是先将这65536个PWM波发送出去,只要atim_npwm大于65536,这段代码就会被执行。
第39~43,当剩下要发送的PWM波个数小于65536时,直接将剩余的PWM波个数赋值给npwm,然后发送出去。
第44~51行,npwm表示有几个PWM波要发送,如果npwm大于0,表示有脉冲要发送。将npwm-1赋值给TIMx_RCR,当RCR寄存器从npwm-1递减为0的时候才会发生中断。在生成下一个重复更新事件之前,无论向TIMx_RCR寄存器写入何值都无影响,所以我们写完该寄存器后,如果要想马上生效就得手动更新事件,即对TIMx_EGR寄存器的UG位写1生成更新事件,并启动定时器。
第52~56行,如果没有脉冲要发送,则关闭TIM8。这里是直接将TIMx_CR1的第0位CEN清零,即禁止计数器。这里注意的是,不能使用__HAL_TIM_DISABLE来关闭TIM8,因为此宏会将TIM8的通道2的CC2E、CC2NE和CEN清零,程序运行后,在没有按下按键时,蜂鸣器就发声了。
第57行,清除定时器溢出中断标志位,每次进入中断都要清除此标志位,避免程序一直卡在重复进入中断中。
6. mian.c文件代码
main.c文件添加代码如下,红色字体之间的代码是我们手动添加的:

1   #include "main.h"
2   #include "tim.h"
3   #include "gpio.h"
4 
5   /* USER CODE BEGIN Includes */
6   #include "./BSP/Include/led.h"
7   #include "./BSP/Include/key.h"
8   /* USER CODE END Includes */
9   void SystemClock_Config(void);
10  int main(void)
11  {
12    
13    HAL_Init();
14    if(IS_ENGINEERING_BOOT_MODE())
15    {
16      SystemClock_Config();
17    }
18    MX_GPIO_Init();
19    MX_TIM8_Init();
20    /* USER CODE BEGIN 2 */
21      uint8_t key = 0;    		/* 设置键值key为0 */
22      led_init();         		/* 关闭LED0 */
23      HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2);	/* 启动定时器PWM输出 */
24      __HAL_TIM_ENABLE_IT(&htim8, TIM_IT_UPDATE);/* 使能更新中断 */
25    /* USER CODE END 2 */
26    while (1)
27    {
28      /* USER CODE BEGIN 3 */
29      LED0_TOGGLE();          			/* LED0闪烁 */
30      key = key_scan(0);      			/* 获取键值 */
31      if (key == KEY0_PRES)   			/* KEY0按下 */
32      {
33        //(TIM8->CCR2) = 3000;/* 可以手动设置占空比,控制蜂鸣器的声音大小 */
34        /* 也可以使用HAL库里的API函数 */
35        //__HAL_TIM_SET_COMPARE (&htim8, TIM_CHANNEL_2, 3000);
36        atim_npwm_set(5);  				/* 输出5个PWM波(控制蜂鸣器响5次) */
37      }
38      HAL_Delay(10);          			/* 延时10ms */
39    }
40    /* USER CODE END 3 */
41  }
第23行,开启PWM通道;
第24行,使能定时器更新中断;
第29行,程序运行后,LED0会闪烁,以指示程序在运行;
第30行,获取按键值;
第31~37行,如果是KEY0按下,则调用atim_npwm_set输出5个PWM波。也可以在此函数中添加如下代码,修改PWM的占空比以控制蜂鸣器声音的大小:

(TIM8->CCR2) = 3000;
或者直接使用HAL库的API函数来修改占空比:
__HAL_TIM_SET_COMPARE (&htim8, TIM_CHANNEL_2, 3000);
7. 编译运行
进入Debug模式,点击运行按钮来运行调试,可以看到开发板底板的LED0灯在闪烁,说明程序已经再运行了。按下底板的KEY0按键,蜂鸣器响5次以后就停止。
18.3 高级定时器输出比较模式实验
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 11-2 ATIM_PWM_CP。
18.3.1 输出比较模式
高级定时器的输出比较模式功能用于控制输出波形,或指示一段给定的时间已经到时。通用定时器和高级定时器都具有输入捕获和输出比较功能,不同的定时器可能通道数量上有差异,高级定时器的通道 1 到通道 4 可用作输出,而通道5 和通道 6 只能在单片机内部使用(例如,用于产生混合波形或触发 ADC)。本小节我们使用高级定时器的输出比较功能输出不同相位的多个PWM波。
通过控制输出比较模式(TIMx_CCMRx 寄存器中的 OCxM 位)和输出极性(TIMx_CCER 寄存器中的 CCxP 位),当捕获/比较寄存器(TIMx_CCMRx)与计数器计数值(CNT)相等时,输出引脚的电平可以:保持其电平 (OCxM=0000);有效电平 (OCxM=0001);无效电平(OCxM=0010);翻转 (OCxM=0011);PWM模式1(OCxM= 0110);PWM模式2(0111)。
在输出比较模式的翻转功能(OCxM = 0011)情况下,当计数器的值(CNT)等于捕获/比较寄存器的值(TIMx_CCMRx)时,OC1REF 发生翻转,进而控制通道输出(OCx)翻转。如下图,以TIM1的通道1为例,先将TIMx_CCER的CC1E位置1,以开启捕获 / 比较 1 输出,然后设置输出比较翻转功能,往TIM1_CCR1写入值B201h,当TIM1_CNT等于TIM1_CCR1的值时,OC1REFF翻转,输出的OC1等于OC1REFF。
在这里插入图片描述

图18.3.1. 1 输出比较模式,翻转OC1时序图
可以通过翻转功能实现输出PWM波,PWM波频率由自动重载寄存器(TIMx_ARR)的值决定,其占空比也是可以由自动重载寄存器(TIMx_ARR)的值来决定,不过就得不断改变该寄存器的值,如果该寄存器的值一直都不改变,那占空比就为50%。此外我们还可以通过捕获/比较寄存器(TIMx_CCRx)的值改变PWM波的相位。它们生成PWM的原理如下图所示:
在这里插入图片描述

图18.3.1. 2输出比较模式,翻转功能输出PWM波原理示意图
本实验就是根据上图的原理来设计的,具体实验是:我们设置固定的ARR值为1000,即占空比固定为50%,通过改变TIM1的两个通道的捕获/比较寄存器(TIMx_CCRx)的值使得每个通道输出的PWM波的相位都不一样。比如:TIMx_CCR3=500-1,TIMx_CCR4=250-1,那么可以得到通道3和通道4输出的PWM波的相位分别是:50%、25%。另外要注意的点是,这样的PWM波的周期是2倍的ARR计数器所用的计数器时间。
18.3.2. 输出比较和PWM模式的差异
前面我们学习了PWM模式,与本节的输出比较模式,它俩有什么差别呢。
我们知道,在PWM模式下,输出的PWM波形频率由TIMx_ARR确定,高电平的时长由各个通道的TIMx_CCRx确定(即占空比),4个通道的频率是一致的,只有占空比是各自独立的;在输出比较模式下,频率由预装载寄存器(TIMx_ARR)的大小决定,此值越大,输出的频率越低,通道的初相位可以通过各通道的TIMx_CCRx来确定,通道工作在输出比较模式时,一个TIM至多可以提供4个不同的定时周期,每个通道的PWM波的频率、占空比互不干扰,完全独立。可以理解PWM模式是输出比较模式的特例。
18.3.3 TIM1/TIM8寄存器
高级定时器输出比较模式除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 之外。主要还用到以下这些寄存器,这些寄存器我们在前面有做过部分介绍:

  1. 控制寄存器 1(TIMx_CR1)
    TIM1/TIM8的控制寄存器1描述如图下图所示:
    在这里插入图片描述

图18.3.3. 1 TIMx_CR1寄存器
上图中我们只列出了本实验需要用的一些位,其中:
位7(APRE)用于控制自动重载寄存器的影子寄存器是否有效,在基本定时器的时候已经讲过,请回顾。本实验中,该位要置1。
位0(CEN)位,用于使能计数器的工作,必须要设置该位为1,才可以开始计数。
其他位保持复位值即可。
2. 捕获/比较模式寄存器1/2(TIMx_CCMR1/2)
TIM1/TIM8的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx_CCMR1和TIMx _CCMR2。TIMx_CCMR1控制CH1和CH2,而TIMx_CCMR2控制CH3和CH4。本节实验我们会用到通道1和通道2,TIMx_CCMR1寄存器描述如图下图所示:
在这里插入图片描述

图18.3.3. 2 TIMx_CCMR1寄存器
该寄存器的有些位在不同模式下,功能不一样,我们现在用到输出比较模式。
本实验我们用到了定时器1输出比较的通道1和2,所以我们需要配置TIM1_CCMR1。模式设置位OC1M[3:0]就是对应着通道1的模式设置,此部分由4位组成,总共可以配置成14种模式,我们使用的是翻转功能,所以这4位必须设置为0011。通道2也是如此配置,将位OC2M[3:0] 设置为0011。除此之外,我们还要设置输出比较的预装载使能位,如通道1对应输出比较的预装载使能位OC1PE置1,其他通道亦如此。
3. 捕获/比较使能寄存器(TIMx_ CCER)
TIM1/TIM8的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关和极性。TIMx_CCER寄存器描述如下图所示:
在这里插入图片描述

图18.3.3. 3 TIMx_CCER寄存器
该寄存器比较简单,要让TIM1的4个通道都输出,主需要将对应的CCxE位置1即可。对于通道1和通道2,将CC1E和CC2E这2个位置1即可使能通道输出。
4. 捕获/比较寄存器1/2/3/4(TIMx_ CCR1/2/3/4)
捕获/比较寄存器(TIMx_CCR1/2/3/4),该寄存器总共有4个,对应4个通道CH1~CH4。我们使用的是通道1和通道2,以TIMx_ CCR1寄存器为例,如下图:
在这里插入图片描述

图18.3.3. 4 TIMx_ CCR1寄存器
对于高级定时器该寄存器16位有效位,在本实验中,我们通过改变该寄存器的值来改变PWM波的相位。在翻转功能模式下,当计数器的值等于该寄存器的值时,OC1REF会发生翻转,即输出的电平发生翻转。
5. TIM1/TIM8断路和死区寄存器(TIMx_ BDTR)
本实验用的是高级定时器,我们还需要配置:断路和死区寄存器(TIMx_BDTR),该寄存器各位描述如下图所示:
在这里插入图片描述

图18.3.3. 5 TIMx_ BDTR寄存器
对于通用定时器,只需要配置以上提到的寄存器就够了,如果是高级定时器,我们还需要配置TIMx_ BDTR,该寄存器我们只关注位15[MOE],对此位清0,则禁止禁止 OC 和 OCN 输出,如果要想高级定时器的PWM正常输出,则必须设置MOE位为1,即使能 OC 和 OCN 输出。
18.3.4 定时器的HAL库驱动
定时器在HAL库中的驱动代码在前面已经介绍了部分,请回顾,这里我们再介绍几个本实验用到的函数。

  1. HAL_TIM_OC_Init函数
    定时器的输出比较模式初始化函数,其声明如下:
    HAL_StatusTypeDef HAL_TIM_OC_Init(TIM_HandleTypeDef *htim);
    函数描述:
    用于初始化定时器的输出比较模式。
    函数形参:
    形参1是TIM_HandleTypeDef结构体类型指针变量,基本定时器的时候已经介绍。
    函数返回值:
    HAL_StatusTypeDef枚举类型的值。
  2. HAL_TIM_OC_ConfigChannel函数
    定时器的输出比较通道设置初始化函数。其声明如下:
    HAL_StatusTypeDef HAL_TIM_OC_ConfigChannel(TIM_HandleTypeDef *htim,
    TIM_OC_InitTypeDef *sConfig, uint32_t Channel);
    函数描述:
    该函数用于初始化定时器的输出比较通道。
    函数形参:
    形参1是TIM_HandleTypeDef结构体类型指针变量,用于配置定时器基本参数。
    形参2是TIM_OC_InitTypeDef结构体类型指针变量,用于配置定时器的输出比较参数。在通用定时器PWM输出实验已经介绍过TIM_OC_InitTypeDef结构体指针类型。
    形参3是定时器通道,范围:TIM_CHANNEL_1到TIM_CHANNEL_6。
    函数返回值:
    HAL_StatusTypeDef枚举类型的值。
  3. HAL_TIM_OC_Start函数
    定时器的输出比较启动函数,其声明如下:
    HAL_StatusTypeDef HAL_TIM_OC_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
    函数描述:
    用于启动定时器的输出比较模式。
    函数形参:
    形参1是TIM_HandleTypeDef结构体类型指针变量。
    形参2是定时器通道,范围:TIM_CHANNEL_1到TIM_CHANNEL_6。
    函数返回值:
    HAL_StatusTypeDef枚举类型的值。
    注意事项:
    HAL库也同样提供了单独使能定时器的输出通道函数,函数为:
    void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel,
    uint32_t ChannelState);
    HAL_TIM_OC_Start函数内部也调用了该函数。
1   HAL_StatusTypeDef HAL_TIM_OC_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
2   {
3     uint32_t tmpsmcr;
4     assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
5 
6     /* 使能输出比较通道1 */
7     TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
8     if (IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET)
9     {
10      /* 使能主输出 */
11      __HAL_TIM_MOE_ENABLE(htim);
12    }
13    /* 启用外围设备,但在触发模式下除外,在触发模式下使用触发自动启用 */
14    tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
15    if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
16    {
17      __HAL_TIM_ENABLE(htim);/* 使能TIM1 */
18    }
19    return HAL_OK;
20  }

18.3.5 硬件设计

  1. 例程功能
    使用定时器1的输出比较模式的翻转功能,让TIM1_CH1、TIM1_CH2和这2路通道输出50%占空比的PWM波,并且每个通道输出不同相位的PWM波,分别是相位25%、相位50%。
  2. 硬件资源
    定时器1输出通道3和4(TIM1_CH3和TIM1_CH4)以及LED0,LED0用于指示程序在运行。
    TIM1_CH3 TIM1_CH4 LED0
    PE13 PE14 PI0
    表18.3.5. 1硬件资源
  3. 原理图
    如下图,底板的JP1排针上引出PE13和PE14,这两个引脚分别对应TIM1_CH3和TIM1_CH4。程序中通过配置这两个IO口输出不同相位的正弦波,通过示波器测试波形进行验证。
    在这里插入图片描述

图18.3.5. 1原理图部分截图
18.3.6 软件设计

  1. 程序流程图
    在这里插入图片描述

图18.3.6. 1程序流程图
2. STM32CubeMX配置
(1)配置PE13和PE14复用为TIM1_CH3和TIM1_CH4
新建一个工程ATIM_PWM_CP,进入STM32CubeMX插件配置界面后,在Pinout & Configuration处配置PE13和PE14复用为TIM1_CH3和TIM1_CH4,如下图所示:
在这里插入图片描述

图18.3.6. 2配置TIM1的两个通道
本节实验我们会用到LED0来指示LED0亮,所以同时配置PI0:
在这里插入图片描述

图18.3.6. 3配置LED0
(2)配置TIM1时基等参数
在TimersTIM1中先配置TIM1的模式,模式配置如下图所示,我们选择内部时钟,通道3和通道4选择为输出比较功能:
在这里插入图片描述

图18.3.6. 4配置模式为输出比较
接下来我们配置TIM1的时基参数和通道3以及通道4的参数,如下红框部分需要我们手动配置,其它部分就保持默认配置:
在这里插入图片描述

图18.3.6. 5配置TIM1通道3和通道4的参数
上图的配置参数,除了红框中的要配置以外,其它选项保持默认,本实验用不到刹车功能,所以就不会使能BRK和BRK2。配置部分的介绍如下:
Counter Settings(计数器参数配置)配置如下:
Prescaler用于配置定时器预分频值,这里配置为209-1;
Counter Mode用于配置计数模式,我们选择向上计数Up;
Counter Period用于配置定时器自动重装载值,我们设置为1000-1;
Internal Clock Division (CKD)配置为No Division即时钟不分频;
Repetition Counter(RCR-16 bits value)用于配置高级定时器的TIMx_RCR值,这里注意,高级定时器和基本定时器以及通用定时器不同,高级定时器的TIMx_RCR递减为0时才会发生更新事件。我们这里配置为0,即计数器溢出即发生更新事件。
auto-reload preload用于配置自动重载是否使能,我们选择 Disable,即不使能自动重载;
定时器1的时钟为2倍APB2,即频率为209MHZ,写入自动重载寄存器的值为999。由前面基本定时器讲解的定时器溢出公式得定时器溢出的周期:

结合前面讲的PWM波的周期是2倍的ARR计数器所用的计数器时间,所以得到PWM波的周期是2ms,频率为500HZ。
Output Compare Channel 3用于配置通道3的PWM输出参数,其中:

Mode:用于配置PWM的模式,这里选择Toggle on match,即翻转。
Pulse (16 bits value):是TIMx_CCRx的值,也就是有效电平的值,可以配置在0-1000之间,例如配置0。这里配置500,即初相位为50%。
相位=
Output compare preload:输出比较预加载项选择Enable,即在定时器工作时是否能修改Pulse的值,如果禁用此项,表示定时器工作时不能进行修改,只能等到更新事件到来的时候才能进行修改,所以这里选择使能。
CH Polarity:输出极性,这里默认选择High,即高电平有效,修改为Low也是可以的,在本实验中无关紧要,只是想输出PWM波而已。
CH Idle State用于配置通道的空闲状态,也就是配置PWM不输出时的状态,这里默认选择Reset,即PWM关闭状态下默认为低电平。如果配置为Set就是PWM关闭状态下默认为高电平。
(3)配置GPIO
本实验还会用到LED0,LED0配置和前面实验的一样:
在这里插入图片描述

图18.3.6. 6配置LED0
(4)配置时钟
本实验我们采用外部24MHz的时钟HSE(也可以采用内部时钟),配置时钟树,经过PLL3锁相环以后,APB1的时钟频率为最大209MHz(也可以配置其它频率):
在这里插入图片描述

图18.3.6. 7配置HSE
我们选择HSE,作为锁相环PLL3的时钟源,在MCU子系统时钟里输入209并回车,STM32CubeMX会自动为我们计算参数,然后再手动配置APB1DIV、APB2DIV和APB3DIV的分频值为2。当APB1DIV的分频数大于1的时候,基本定时器的倍频器倍频值始终为2,所以通用定时器TIM8的时钟频率为209MHz。
在这里插入图片描述

图18.3.6. 8配置系统时钟
(5)配置生成独立的文件
配置生成独立的.c和.h头文件,如下图:
在这里插入图片描述

图18.3.6. 9配置生成独立的.c和.h文件
3. 生成工程并添加BSP文件夹
配置好后,按下“Ctrl+S”保存修改配置,生成工程,将上一章节的BSP文件夹拷贝到本机工程实验中,因为我们会用到LED0的驱动,其它的LED和按键的驱动,如果不用的话就自行删除掉,只留下LED0部分。如下:
在这里插入图片描述

图18.3.6. 10生成工程
4. 分析tim.c文件
tim.c文件代码如下,代码中已经给出详细注释,可以很容易看懂。本节实验我们没有用到刹车功能。

1   #include "tim.h"
2  
3   TIM_HandleTypeDef htim1; 				/* TIM1句柄 */
4  
5   /**
6    *			 TIM1时基以及TIM1通道3和通道4初始化函数
7    * @brief    高级定时器TIM1输出比较模式 初始化函数(使用输出比较模式)
8    * @note
9    * 配置高级定时器TIM1 2路输出比较模式PWM输出,实现50%占空比,不同相位控制
10   * 注意,本例程输出比较模式,每2个计数周期才能完成一个PWM输出,因此输出频率减半
11   * 另外,我们还可以开启中断在中断里面修改CCRx,从而实现不同频率/不同相位的控制
12   * 但是我们不推荐这么使用,因为这可能导致非常频繁的中断,从而占用大量CPU资源
13   *          
14   * 高级定时器的时钟来自APB2,当APB2DIV≥2分频的时候
15   * 高级定时器的时钟为APB2时钟的2倍, 而APB2为104.5M, 所以定时器时钟 = 209Mhz
16   * 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
17   * Ft=定时器工作频率,单位:Mhz
18   * 本例配置TIM1两个通道的PWM波周期为2ms,频率为500Hz
19   * 通道3相位为50%,通道4相位为25%
20   * @param   无
21   * @retval  无
22   */
23  void MX_TIM1_Init(void)
24  {
25    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
26    TIM_MasterConfigTypeDef sMasterConfig = {0};
27    TIM_OC_InitTypeDef sConfigOC = {0};
28    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
29 
30    htim1.Instance = TIM1;            					/* 定时器1 */
31    htim1.Init.Prescaler = 209-1;     				/* 定时器分频 */
32    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;	/* 向上计数模式*/
33    htim1.Init.Period = 1000-1;      					 /* 自动重装载值 */
34    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;/* 不分频 */
35    htim1.Init.RepetitionCounter = 0;				/* 重复计数器寄存器为0 */
36    /* 不使能影子寄存器TIMx_ARR */
37    htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
38    if (HAL_TIM_Base_Init(&htim1) != HAL_OK) 		/* 初始化定时器时基 */
39    {
40      Error_Handler();
41    }
42    /* 使用内部时钟 */
43    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
44    /* 时钟源配置 */
45    if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
46    {
47      Error_Handler();
48    }
49    if (HAL_TIM_OC_Init(&htim1) != HAL_OK)		/* 输出比较模式初始化 */
50    {
51      Error_Handler();
52    }
53    /* 定时器主从模式配置,这里我们没有用到主从模式 */
54    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
55    sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
56    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
57    if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
58    {
59      Error_Handler();
60    }
61    /* 定时器通道3和通道4的配置 */
62    sConfigOC.OCMode = TIM_OCMODE_TOGGLE;		/* 输出比较模式翻转功能 */
63    sConfigOC.Pulse = 500-1;  /* 设置通道3输出比较寄存器的值为500-1 */
64    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;/* 输出比较极性为高 */ 
65    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;/* 互补输出比较极性位高 */
66    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;/* 失能输出比较快速模式 */
67    /* 选择空闲状态下非工作状态 */
68    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
69    /* 设置空闲状态下非工作状态 */
70    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
71    /* 初始化定时器的输出比较通道3 */
72    if (HAL_TIM_OC_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
73    {
74      Error_Handler();
75    }
76    /* 通道3预装载使能 */
77    __HAL_TIM_ENABLE_OCxPRELOAD(&htim1, TIM_CHANNEL_3);
78    sConfigOC.Pulse = 250-1;/* 设置通道4输出比较寄存器的值为250-1 */
79    /* 初始化定时器的输出比较通道4 */
80    if (HAL_TIM_OC_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
81    {
82      Error_Handler();
83    }
84    /* 通道4预装载使能 */
85    __HAL_TIM_ENABLE_OCxPRELOAD(&htim1, TIM_CHANNEL_4);
86    /* 设置运行模式下非工作状态选项 */
87    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
88    /* 设置在空载下非工作状态选项 */
89    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
90    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;/* 锁电平参数 */
91    sBreakDeadTimeConfig.DeadTime = 0;				/* 死区时间设置为0 */
92    /* 失能TIMx刹车输入 */
93    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
94    /* TIM1刹车输入管脚极性为高电平有效 */
95    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
96    sBreakDeadTimeConfig.BreakFilter = 0;	/* TIM1刹车输入滤波为0 */
97    /* TIM1刹车输入BRK为输入模式 */
98    sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;
99    /* TIM1刹车输入BRK2失能 */
100   sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
101   /* 刹车输入BRK2高电平有效 */
102   sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
103   /* TIM1刹车输入2滤波为0 */
104   sBreakDeadTimeConfig.Break2Filter = 0;
105   sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
106   /* 失能自动输出功能,只能后期手动通过软件设置MOE */
107   sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
108   /* 配置间隔功能,停滞时间,锁定级别 */
109   if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
110   {
111     Error_Handler();
112   }
113   HAL_TIM_MspPostInit(&htim1); 				/* 定时器输出比较底层驱动 */
114 }
115 /**
116  * @brief    定时器输出比较模式时钟初始化函数
117  * @param    timHandle :定时器句柄
118  * @note     此函数会在MX_TIM1_Init中调用,如HAL_TIM_Base_Init函数中
119  * @retval   无
120  */
121 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
122 {
123   if(tim_baseHandle->Instance==TIM1)
124   {
125     __HAL_RCC_TIM1_CLK_ENABLE();				/* 开启TIM1时钟 */
126   }
127 }
128 /**
129  * @brief    定时器输出比较模式通道引脚初始化函数
130  * @param    timHandle :定时器句柄
131  * @note     此函数会被MX_TIM1_Init函数调用
132  * @retval   无
133  */
134 void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
135 {
136
137   GPIO_InitTypeDef GPIO_InitStruct = {0};
138   if(timHandle->Instance==TIM1)
139   {
140     __HAL_RCC_GPIOE_CLK_ENABLE();				/* 开启GPIOE的时钟 */
141     /**TIM1 GPIO Configuration
142     PE13     ------> TIM1_CH3
143     PE14     ------> TIM1_CH4
144     */
145     /* 指定引脚为13和14 */
146     GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14;
147     /* 指定模式为复用推挽输出 */
148     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 		/* 复用推挽输出 */
149     GPIO_InitStruct.Pull = GPIO_NOPULL;			/* 无上/下拉 */
150     /* 速度等级为低,也可以配置为其它等级 */
151     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
152     GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;	/* 复用选择为AF1 */
153     HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);		/* 初始化GPIOE */
154   }
155 }
156 /**
157  * @brief    定时器输出比较模式时基反初始化函数
158  * @param    timHandle :定时器句柄
159  * @note     如果需要,用户可以调用此函数来关闭TIM1
160  * @retval   无
161  */
162 void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
163 {
164   if(tim_baseHandle->Instance==TIM1)
165   {
166     __HAL_RCC_TIM1_CLK_DISABLE();				/* 关闭TIM1时钟 */
167   }
168 }
169 /* USER CODE BEGIN 1 */
  1. 修改main.c文件
    main.c文件中的部分代码如下所示,其中标红的字体之间的代码是我们手动添加的。
1   #include "main.h"
2   #include "tim.h"
3   #include "gpio.h"
4 
5   /* USER CODE BEGIN Includes */
6   #include "./BSP/Include/led.h"
7   /* USER CODE END Includes */
8 
9   void SystemClock_Config(void);
10
11  int main(void)
12  {
13    HAL_Init(); 									/* HAL库初始化 */
14
15    if(IS_ENGINEERING_BOOT_MODE())
16    {
17      SystemClock_Config();					/* 初始化系统时钟 */
18    }
19
20    MX_GPIO_Init();          					/* 初始化GPIO */
21    MX_TIM1_Init();          					/* 初始化TIM1 */
22    /* USER CODE BEGIN 2 */
23    led_init();              					/* 初始化led */
24    HAL_TIM_OC_Start(&htim1,TIM_CHANNEL_3); /* 启动定时器1通道3 */
25    HAL_TIM_OC_Start(&htim1,TIM_CHANNEL_4); /* 启动定时器1通道4 */
26    /* USER CODE END 2 */
27    while (1)
28    {
29      /* USER CODE BEGIN 3 */
30        LED0_TOGGLE();        					 /* LED0翻转 */
31        HAL_Delay(500);      					 /* 延时500ms */
32    }
33    /* USER CODE END 3 */
34  }
第24和第25行,使能TIM1通道3和通道4的输出比较信号的生成。
  1. 编译运行
    本次实验需要使用示波器来验证。找4根一边是公头一边是母头的杜邦线,两根杜邦线分别接在JP1排针引出的PE13和PE14上,两根杜邦线接在板子引出的地线上,例如底板的JP7和JP8排针处有引出地线,如下图:
    在这里插入图片描述

图18.3.6. 11连接好PWM输出排针
将引出的线接入到示波器的两个通道,用于测量TIM1的通道3和通道4的波形。本实验笔者使用正点原子自主研发的DS100 Mini数字示波器,此示波器支持两个通道,屏幕分辨率为480*320,采样率为250MSa/S,可以保存波形:
在这里插入图片描述

图18.3.6. 12正点原子DS100 Mini数字示波器
开发板接好线,拨码开关拨成MCU启动方式,然后开发板上电。STM32CubeIDE进入Debug模式,点击运行按钮来运行调试,可以看到开发板底板的LED0灯在闪烁,说明程序已经在运行了。如下图,测试出波形周期为2ms,频率为500Hz,和我们前面计算的一致:
在这里插入图片描述

图18.3.6. 13波形周期
ARR值为固定为1000,所以占空比则固定为50%。通道3和通道4我们分别配置为500和250,即两者的相位分别为50%和25%,两者下降沿或者上升沿相位差为25%(即250us),如下图所示,绿色波形是PE13引脚输出的,黄色波形是PE14引脚输出的:
在这里插入图片描述

图18.3.6. 14两波形相位差
18.4 高级定时器互补输出带死区控制实验
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 11-3 ATIM_PWM_DT。
18.4.1 互补输出和死区时间
本小节我们来学习如何使用高级定时器的互补输出和死区插入功能。

  1. 互补输出
    定时器的PWM互补输出很好理解,也就是两个PWM波形是互补的状态,例如,如果CHx输出高电平,则CHxN输出低电平,两者的PWM波形是互补输出的特性。如下图,OCXREF是参考基准信号,OCx和OCxN是两个通道CHx和CHxN的输出,即主输出OCx或互补输出OCxN,OCxN与OCXREF时序相位同步,OCXN信号与OCXREF时序反相同步,两个输出波形互补:
    在这里插入图片描述

图18.4.1. 1定时器的PWM互补输出波形
2. 死区时间
说到互补输出,还会涉及到一个死区时间的概念,死区时间可以理解为某个处于相对无效状态的时间或空间。死区时间在步进电机、伺服电机、逆变器和开关电源中使用的比较多,例如下图是一个逆变器电机驱动原理示意图红框的部分是两组桥,每组桥由功率器件组成,每组桥的上半部分是上桥(S1和S3),下半部分是下桥(S2和S4),每组桥的上半桥和下半桥是不能同时导通的,否则电源会通过上下两个桥形成短路,烧毁功率器件。
在这里插入图片描述

图18.4.1. 2逆变器电机驱动原理示意图
在高速的PWM驱动信号在达到功率元件的控制极时,会由于各种各样的原因产生延迟,从而造成某个半桥不能立马关闭,会延迟一段时间才真正关闭,这个时候就可能会与另外一个半桥处于同时开启的状态,从而造成短路。死区时间就是在上半桥关断后,延迟一段时间再打开下半桥,或者在下半桥关断后,延迟一段时间再打开上半桥,从而避免两个桥同时开启的状态。所延迟的这段延迟时间就是死区时间,即上、下半桥的元件都是关断的状态。关于桥大家可不必深究,我们引出桥只是为了说明死区时间。
如下图,OCXREF是参考基准信号,OCx和OCxN是两个通道CHx和CHxN的输出,OCxN与OCXREF时序相位同步,只是其上升沿相对OCXREF上升沿存在延迟,OCXN信号与OCXREF时序反相同步,并且其上升沿相对OCXREF下降沿存在延迟,这个延迟时间就是插入的死区时间:
在这里插入图片描述

图18.4.1. 3死区时间
高级控制定时器 (TIM1/TIM8) 可以输出两路互补信号,并管理死区时间,用户必须根据与输出相连接的器件及其特性(电平转换器的固有延迟、开关器件产生的延迟)来调整死区时间,每路输出可以独立选择输出极性(主输出OCx或互补输出OCxN),可通过对TIMx_CCER寄存器中的 CCxP 和 CCxNP 位执行写操作来完成极性选择。值得注意的是,如果延迟时间大于有效输出(OCx 或 OCxN)的宽度,否则相应通道的输出呈无效状态,即不会产生相应的脉冲。
3. 死区时间计算方法
1)先确死区及采样时钟频率:
通过配置TIMx_CRx寄存器的CKD[1:0]位指示定时器时钟(CK_INT)频率与死区发生器和数字滤波器(ETR、TIx)所使用的死区及采样时钟(tDTS)之间的分频比,当CKD[1:0]位配置为:
00:t DTS =tCK_INT
01:t DTS =2tCK_INT
10:t DTS =4
tCK_INT
后面的实验中我们会默认配置CKD[1:0]位为10,即tDTS =4*tCK_INT。再结合定时器1的内部时钟为2倍APB2,即209MHZ,得到fDTS = fCK_INT /4 = 209MHZ/4 = 52.25MHZ。由fDTS = 52.25MHZ得到采样时钟tDTS = 19.14 ns。
2)确定DTG[7:0]位:
可以通过配置断路和死区寄存器(TIMx_ BDTR)来控制定时器的断路和死区功能,该寄存器的低8位DTG[7:0]位用于用于定义插入到互补输出之间的死区持续时间,死区时间DT 与该持续时间相对应,死区时间计算方法:
在这里插入图片描述

表18.4.1. 1死区时间计算方法
本实验中我们会配置配置死区发生器为十进制数的100,即二进制数0110 0100,这是符合TIMx_BDTR寄存器所讲的DTG[7:0]位的第一种情况:
DT=DTG[7:0] * tdtg,其中 tdtg = tDTS,DT是死区时间,可以得到死区时间DT = 100*19.14 ns =1.914us。本实验我们也是设置DTG[7:0]的值为100,到后面下载验证小节,我们通过示波器验证一下这个死区时间和我们这里的计算值是否正确。
关于此寄存器的各位我们会在下面的寄存器小节介绍。
18.4.2 TIM1/TIM8寄存器
高级定时器互补输出带死区控制除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 之外。主要还用到以下这些寄存器:

  1. 控制寄存器 1(TIMx_CR1)
    TIM1/TIM8的控制寄存器1描述如下图所示:
    在这里插入图片描述

图18.4.2. 1 TIMx_CR1寄存器
上图中我们只列出了本实验需要用的一些位,其中:
位7(APRE)用于控制自动重载寄存器的影子寄存器是否有效,在基本定时器的时候已经讲过,请回顾。本实验中,该位要置1。
CKD[1:0]位指示定时器时钟(CK_INT)频率与死区发生器以及数字滤波器(ETR、TIx)所使用的死区及采样时钟(t DTS)之间的分频比:
00:t DTS =tCK_INT
01:t DTS =2tCK_INT
10:t DTS =4
tCK_INT
11:保留,不要设置成此值
我们设置CKD[1:0]位为10,t DTS =4*tCK_INT,即f DTS = fCK_INT /4,再结合定时器1的内部时钟为2倍APB2,即209MHZ,得到fDTS = fCK_INT /4 = 209MHZ/4 = 52.25MHZ。
CEN位,用于使能计数器的工作,必须要设置该位为1,才可以开始计数。
2. 捕获/比较模式寄存器1/2(TIMx_CCMR1/2)
TIM1/TIM8的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx_CCMR1和TIMx _CCMR2。TIMx_CCMR1控制CH1和CH2,而TIMx_CCMR2控制CH3和CH4。TIMx_CCMR2寄存器描述如下图所示:
在这里插入图片描述

图18.4.2. 2 TIMx_CCMR2寄存器
该寄存器的有些位在不同模式下,功能不一样,我们现在用到输出比较模式。
本实验我们用到了定时器1输出比较的通道3,所以我们需要配置TIMx_CCMR2模式设置位OC3M[3:0],我们使用的是PWM模式1,所以这4位必须设置为0110。除此之外,我们还要设置输出比较的预装载使能位,通道3对应输出比较的预装载使能位OC3PE置1。
3. 捕获/比较使能寄存器(TIMx_ CCER)
TIM1/TIM8的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关和极性。TIMx_CCER寄存器描述如下图所示:
在这里插入图片描述

图18.4.2. 3 TIMx_CCER寄存器
该寄存器比较简单,要让TIM1的通道3输出,我们需要把对应的捕获/比较3输出使能位CC3E置1。因为我们还需要通道3的互补输出通道也输出,因此还需要把CC3NE位置1。CC3P和CC3NP分别是通道3输出和通道3互补输出的极性配置位,因为我们需要的是互补的PWM波,所以通道3输出和通道3互补输出的极性要相同,这里都设置为低电平有效,即CC3P和CC3NP位都置1。
4. 捕获/比较寄存器1/2/3/4(TIMx_CCR1/2/3/4)
捕获/比较寄存器(TIMx_ CCR1/2/3/4),该寄存器总共有4个,对应4个通道CH1~CH4。我们使用的是通道3,所以来看看TIMx_ CCR3寄存器描述如下图所示:
在这里插入图片描述

图18.4.2. 4 TIMx_ CCR3寄存器
对于高级定时器该寄存器16位有效位,和通用定时器PWM输出实验一样,在PWM 模式1的情况下,我们通过改变该寄存器的值来改变PWM波的占空比。
5. TIM1/TIM8断路和死区寄存器(TIMx_ BDTR)
TIM1/TIM8断路和死区寄存器,该寄存器各位描述如下图所示:
在这里插入图片描述

图18.4.2. 5 TIMx_ BDTR寄存器
该寄存器控制定时器的断路和死区控制的功能,我们先看断路控制:
高级定时器TIM1/TIM8有两路断路输入通道(BRK和BRK),本实验我们用到断路1通道(PA6引脚)。断路也就是我们所说的“刹车输入”。要使用断路就要使能位12(BKE),将该位置1即可。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值