0
点赞
收藏
分享

微信扫一扫

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验

第十八章高级定时器实验​


本章我们主要来学习高级定时器,STM32MP157有2个高级定时器(TIM1和TIM8)。我们将通过四个实验来学习高级定时器的各个功能,分别是高级定时器输出指定个数PWM实验、高级定时器输出比较模式实验、高级定时器互补输出带死区控制实验和高级定时器PWM输入模式实验。

本章分为如下几个小节:

18.1、高级定时器简介

18.2、高级定时器输出指定个数PWM实验

18.3、高级定时器输出比较模式实验

18.4、高级定时器互补输出带死区控制实验

18.5、高级定时器PWM输入模式实验;

18.1 高级定时器简介

高级定时器的框图在通用定时器框图的基础上加了一些功能,前面如果学习了通用定时器的框图和实验,再看高级定时器的框图时就很好理解了。高级定时器的框图和通用定时器的框图十分相似,但仍有微弱差异,如下是高级定时器的框图:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图


图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概况。

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_02


图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. 控制寄存器 1TIMx_CR1

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_03


图18.2.1. 1寄存器

上图中我们这里仅介绍本章节会用到的一些位,其中:

  • 位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寄存器

该寄存器控制着各个输入/输出通道的开关,每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)

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_04


图18.2.1. 3寄存器

该寄存器主要是用户用软件更新各类事件和某些寄存器位,这里我们只介绍第0位:

位0[UG]是更新定时器事件的控制位,作用类似定时器溢出更新中断,区别是这里是通过软件去更新,而定时器溢出更新中断是硬件自己完成。本实验就是用到该位去更新定时器事件,对该位写1就可以重新初始化计数器并生成寄存器更新事件,由硬件自动清零。

4. 重复计数器寄存器(TIMx_ RCR)

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_05


图18.2.1. 4寄存器

用户可通过该寄存器设置从预装载寄存器向活动寄存器周期性传输数据。该该寄存器共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:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_06


图18.2.1. 5寄存器

该寄存器有16位,对于输出模式,CCR2[15:0] 为预装载值,该值与CNT的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制PWM的输出脉宽。

6. TIM1/TIM8断路和死区寄存器(TIMx_ BDTR)

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_07


图18.2.1. 6寄存器

对于通用定时器,只需要配置以上提到的寄存器就够了,如果是高级定时器,我们还需要配置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次。

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_08


图18.2.2. 2引脚部分原理图

从《STM32MP157A&D数据手册》中也可以查阅PC7的复用关系:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_09


图18.2.2. 3 《STM32MP157A&D数据手册》部分截图

18.2.4 软件设计

1. STM32CubeMX配置

(1)配置PI0复用为TIM2_CH1

新建一个工程ATIM_PWM_OUT,进入STM32CubeMX插件配置界面后,在Pinout & Configuration处配置PC7复用为TIM8_CH2,如下图所示:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_10


图18.2.4. 1配置PC7复用为TIM8_CH2

(2)配置TIM8时基等参数

在TimersàTIM8中配置TIM8_CH2参数,其中,模式配置如下:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_11


图18.2.4. 2配置模式为PWM输出

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

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_12


图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使能自动重载; 上述参数可以计算定时器的时钟频率为:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_13


  • 计数器的溢出时间:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_14


  • 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寄存器写入新的值来改变占空比,从而控制蜂鸣器声音的大小。占空比=
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_15


  • 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,如下图,先使能定时器更新中断:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_16

  • 图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 interruptTIM8 capture compare interrupt是捕获比较中断,这个就很好理解了,我们前面通用定时器的实验有介绍到。例如捕获到上升沿/下降沿时会发生捕获中断,计数器的值和比较寄存器的值相等时就会触发中断,关于输出比较我们后面的实验会讲解。
    接下来要配置中断优先级,这里我们选择中断优先级分组2,抢占优先级和子优先级均为3:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_17

  • 图18.2.4. 5设置中断优先级
    (4)配置GPIO
    本实验还会用到LED0和按键KEY0,前面的实验我们有配置过,为了方便,本节实验会拷贝上一章节实验的BSP文件夹到工程中使用,大家可以将三个按键(KEY0、KEY1、WK_UP)以及LDE0和LDE1的都一起配置了。这里就只配置LED0和按键KEY0:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_18

  • 图18.2.4. 6配置LED0和KEY0
    由于蜂鸣器接在PC7上,而PC7已经配置为TIM8_CH2,所以直接在GPIOàTIM处配置蜂鸣器。因为蜂鸣器是低电平有效,所以我们最好配置为上拉:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_19

  • 图18.2.4. 7配置PC7上拉
    (5)配置时钟
    本实验我们采用外部24MHz的时钟HSE(也可以采用内部时钟),配置时钟树,经过PLL3锁相环以后,APB1的时钟频率为最大209MHz(也可以配置其它频率):
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_20

  • 图18.2.4. 8配置HSE
    我们选择HSE,作为锁相环PLL3的时钟源,在MCU子系统时钟里输入209并回车,STM32CubeMX会自动为我们计算参数,然后再手动配置APB1DIV、APB2DIV和APB3DIV的分频值为2。当APB1DIV的分频数大于1的时候,基本定时器的倍频器倍频值始终为2,所以通用定时器TIM8的时钟频率为209MHz。
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_21

  • 图18.2.4. 9配置系统时钟
    (5)配置生成独立的文件
    配置生成独立的.c和.h头文件,如下图:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_22

  • 图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为用户设置的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定时器句柄指针​
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模式,点击运行按钮

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_23

来运行调试,可以看到开发板底板的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寄存器

上图中我们只列出了本实验需要用的一些位,其中:

位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寄存器

该寄存器的有些位在不同模式下,功能不一样,我们现在用到输出比较模式。

本实验我们用到了定时器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寄存器

该寄存器比较简单,要让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寄存器

对于高级定时器该寄存器16位有效位,在本实验中,我们通过改变该寄存器的值来改变PWM波的相位。在翻转功能模式下,当计数器的值等于该寄存器的值时,OC1REF会发生翻转,即输出的电平发生翻转。

5. TIM1/TIM8断路和死区寄存器(TIMx_ BDTR)

本实验用的是高级定时器,我们还需要配置:断路和死区寄存器(TIMx_BDTR),该寄存器各位描述如下图所示:

图18.3.3. 5寄存器

对于通用定时器,只需要配置以上提到的寄存器就够了,如果是高级定时器,我们还需要配置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);​

  • 函数描述:该函数用于初始化定时器的输出比较通道。
  • 函数形参:形参1TIM_HandleTypeDef结构体类型指针变量,用于配置定时器基本参数
    形参2TIM_OC_InitTypeDef结构体类型指针变量,用于配置定时器的输出比较参数。在通用定时器PWM输出实验已经介绍过TIM_OC_InitTypeDef结构体指针类型。
    形参3定时器通道,范围:TIM_CHANNEL_1TIM_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_1TIM_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{
3uint32_t tmpsmcr;
4 assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
5
6/* 使能输出比较通道1 */
7 TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
8if(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;
15if(!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
16{
17 __HAL_TIM_ENABLE(htim);/* 使能TIM1 */
18}
19return 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时基等参数

在TimersàTIM1中先配置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。由前面基本定时器讲解的定时器溢出公式得定时器溢出的周期:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_24

  • 结合前面讲的PWM波的周期是2倍的ARR计数器所用的计数器时间,所以得到PWM波的周期是2ms,频率为500HZ
    Output Compare Channel 3用于配置通道3PWM输出参数,其中:
  • Mode:用于配置PWM的模式,这里选择Toggle on match,即翻转
  • Pulse (16 bits value):是TIMx_CCRx的值,也就是有效电平的值,可以配置在0-1000之间,例如配置0。这里配置500,即初相位为50%相位=
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_25


  • Output compare preload:输出比较预加载项选择Enable,即在定时器工作时是否能修改Pulse的值,如果禁用此项,表示定时器工作时不能进行修改,只能等到更新事件到来的时候才能进行修改,所以这里选择使能。
  • CH Polarity:输出极性,这里默认选择High,即高电平有效修改为Low也是可以的,在本实验中无关紧要,只是想输出PWM波而已。
  • CH Idle State用于配置通道的空闲状态,也就是配置PWM不输出时的状态,这里默认选择Reset,即PWM关闭状态下默认为低电平。如果配置为Set就是PWM关闭状态下默认为高电平。3)配置GPIO
    本实验还会用到LED0,LED0配置和前面实验的一样:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_26

  • 图18.3.6. 6配置LED0
    4)配置时钟
    本实验我们采用外部24MHz的时钟HSE(也可以采用内部时钟),配置时钟树,经过PLL3锁相环以后,APB1的时钟频率为最大209MHz(也可以配置其它频率):
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_27

  • 图18.3.6. 7配置HSE
    我们选择HSE,作为锁相环PLL3的时钟源,在MCU子系统时钟里输入209并回车,STM32CubeMX会自动为我们计算参数,然后再手动配置APB1DIV、APB2DIV和APB3DIV的分频值为2。当APB1DIV的分频数大于1的时候,基本定时器的倍频器倍频值始终为2,所以通用定时器TIM8的时钟频率为209MHz。
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_28

  • 图18.3.6. 8配置系统时钟
    (5)配置生成独立的文件
    配置生成独立的.c和.h头文件,如下图:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_29

  • 图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通道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定时器工作频率,单位: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:定时器句柄​
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:定时器句柄​
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:定时器句柄​
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 */​

5. 修改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的输出比较信号的生成。

6. 编译运行

本次实验需要使用示波器来验证。找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模式,点击运行按钮

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_30

来运行调试,可以看到开发板底板的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 =2*tCK_INT

10:t DTS =4*tCK_INT

后面的实验中我们会默认配置CKD[1:0]位为10,即tDTS =4*tCK_INT。再结合定时器1的内部时钟为2倍APB2,即209MHZ,得到fDTS = fCK_INT。由fDTS得到采样时钟tDTS

2)确定DTG[7:0]位:

可以通过配置断路和死区寄存器(TIMx_ BDTR)来控制定时器的断路和死区功能,该寄存器的低8位DTG[7:0]位用于用于定义插入到互补输出之间的死区持续时间,死区时间DT 与该持续时间相对应,死区时间计算方法:

DTG[7:5]配置

死区时间DT

DTG[7:5]=0xx

DT=DTG[7:0]*tdtg tdtg =tDTS

DTG[7:5]=10x

DT=(64+DTG[5:0])*tdtg (其中 Tdtg =2*tDTS

DTG[7:5]=110

DT=(32+DTG[4:0])*tdtg (其中 Tdtg =8*tDTS

DTG[7:5]=111

DT=(32+DTG[4:0])*tdtg (其中 Tdtg =16*tDTS

表18.4.1. 1死区时间计算方法

本实验中我们会配置配置死区发生器为十进制数的100,即二进制数0110 0100,这是符合TIMx_BDTR寄存器所讲的DTG[7:0]位的第一种情况:

DT=DTG[7:0] * tdtg,其中dtg = 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寄存器

上图中我们只列出了本实验需要用的一些位,其中:

位7(APRE)用于控制自动重载寄存器的影子寄存器是否有效,在基本定时器的时候已经讲过,请回顾。本实验中,该位要置1。

CKD[1:0]位指示定时器时钟(CK_INT)频率与死区发生器以及数字滤波器(ETR、TIx)所使用的死区及采样时钟(t DTS)之间的分频比:

00:t DTS =tCK_INT

01:t DTS =2*tCK_INT

10:t DTS =4*tCK_INT

11:保留,不要设置成此值

我们设置CKD[1:0]位为10,t DTS =4*tCK_INT,即f DTS = fCK_INT,再结合定时器1的内部时钟为2倍APB2,即209MHZ,得到fDTS = fCK_INT

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寄存器

该寄存器的有些位在不同模式下,功能不一样,我们现在用到输出比较模式。

本实验我们用到了定时器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寄存器

该寄存器比较简单,要让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寄存器

对于高级定时器该寄存器16位有效位,和通用定时器PWM输出实验一样,在PWM 模式1的情况下,我们通过改变该寄存器的值来改变PWM波的占空比。

5. TIM1/TIM8断路和死区寄存器(TIMx_ BDTR)

TIM1/TIM8断路和死区寄存器,该寄存器各位描述如下图所示:

图18.4.2. 5寄存器

该寄存器控制定时器的断路和死区控制的功能,我们先看断路控制:

高级定时器TIM1/TIM8有两路断路输入通道(BRK和BRK),本实验我们用到断路1通道(PA6引脚)。断路也就是我们所说的“刹车输入”。要使用断路就要使能位12(BKE),将该位置1即可。

断路输入信号可以通过位13 (BKP)选择断路极性,即断路有效电平,我们选择低电平有效,即将BKP置1。

位14(AOE)是自动输出使能位,如果使能AOE位,那么在我们输入刹车信号后再断开了刹车信号,互补的PWM波会自动恢复输出,如果失能AOE位,那么在输入刹车信号后再断开了刹车信号,互补的PWM波就不会恢复输出,而是一直保持刹车信号输入时的状态。为了方便观察,我们使能该位,即置1。

BKF[3:0]是断路输入信号的滤波器,我们是需要手动接入断路输入信号,不需要用滤波器,所以这4个位我们置0。

位15(MOE) 我们同样需要使能主输出,因为要想高级定时器的通道正常输出,则必须设置MOE位为1,否则不会有输出。

位 7:0,DTG[7:0]用于配置死区发生器,此位域定义插入到互补输出之间的死区持续时间,死区时间DT 与该持续时间相对应,如果:

DTG[7:5]=0xx => DT=DTG[7:0]*tdtg,其中dtg =tDTS

DTG[7:5]=10x => DT=(64+DTG[5:0])*tdtg ,其中dtg =2*tDTS

DTG[7:5]=110 => DT=(32+DTG[4:0])*tdtg,其中dtg =8*tDTS

DTG[7:5]=111 => DT=(32+DTG[4:0])*tdtg,其中dtg =16*tDTS

要使用断路2功能参考断路1的配置即可。

下面再来看看死区时间计算:

在前面讲TIM1/TIM8 控制寄存器1的CKD[1:0] 时,我们知道该位指示定时器时钟(CK_INT)频率与死区发生器以及数字滤波器(ETR、TIx)所使用的死区及采样时钟(tDTS)之间的分频比。并经过配置得到fDTS = fCK_INT,下面就要根据这个前提,来学习计算死区时间。由fDTS得到tDTS。如果我们配置死区发生器为十进制数的100,即二进制数0110 0100,这是符合TIMx_BDTR寄存器所讲的DTG[7:0]位的第一种情况:

DT=DTG[7:0] * tdtg,其中dtg = tDTS,DT是死区时间,可以得到DT = 100*19.14 ns = 1.914us。本实验我们也是设置DTG[7:0]的值为100,到后面下载验证小节,我们通过示波器验证一下这个死区时间和我们这里的计算值是否正确。

18.4.3 定时器的HAL库驱动

定时器在HAL库中的驱动代码在前面已经介绍了部分,这里我们再介绍几个本实验用到的函数。

1. HAL_TIMEx_ConfigBreakDeadTime函数

定时器的断路和死区时间配置初始化函数,其声明如下:

HAL_StatusTypeDef HAL_TIMEx_ConfigBreakDeadTime(TIM_HandleTypeDef *htim,​
TIM_BreakDeadTimeConfigTypeDef *sBreakDeadTimeConfig);​

  • 函数描述:用于初始化定时器的断路(即刹车)和死区时间。
  • 函数形参:形参1是TIM_HandleTypeDef结构体类型指针变量,基本定时器的时候已经介绍。形参2TIM_BreakDeadTimeConfigTypeDef结构体类型指针变量,用于配置断路和死区参数,其定义如下:

typedef struct​
{​
uint32_t OffStateRunMode; /* 运行模式下的关闭状态选择 */​
uint32_t OffStateIDLEMode; /* 空闲模式下的关闭状态选择 */​
uint32_t LockLevel; /* 寄存器锁定配置 */​
uint32_t DeadTime; /* 死区时间设置 */​
uint32_t BreakState; /* 断路(即刹车)输入使能控制 */​
uint32_t BreakPolarity; /* 断路输入极性 */​
uint32_t BreakFilter; /* 断路输入滤波器 */​
uint32_t Break2State; /* 断路2输入使能控制 */​
uint32_t Break2Polarity; /* 断路2输入极性 */​
uint32_t Break2Filter; /* 断路2输入滤波器 */​
uint32_t AutomaticOutput; /* 自动恢复输出使能控制 */​
} TIM_BreakDeadTimeConfigTypeDef;​


  • 函数返回值:HAL_StatusTypeDef枚举类型的值。

2. HAL_TIMEx_PWMN_Start函数

前面我们学习了定时器通道输出启动函数HAL_TIM_PWM_Start这里我们来了解定时器的互补输出启动函数。其声明如下:

HAL_StatusTypeDef HAL_TIMEx_PWMN_Start(TIM_HandleTypeDef *htim, ​
uint32_t Channel);​

  • 函数描述:该函数用于启动定时器的互补输出。
  • 函数形参:形参1TIM_HandleTypeDef结构体类型指针变量,用于配置定时器基本参数
    形参2定时器通道,范围:TIM_CHANNEL_1TIM_CHANNEL_6
  • 函数返回值:

HAL_StatusTypeDef枚举类型的值。

18.4.4 硬件设计​

1. 例程功能​

1)利用TIM1_CH3(PE13)输出70%占空比的PWM波,它的互补输出通道(PE12)则是输出30%占空比的PWM波。

2)刹车功能,当给刹车输入引脚(PA6)输入低电平时,进行刹车,即PE13和PE12停止输出PWM波。

3)LED0闪烁指示程序运行。

2. 硬件资源​

定时器1输出通道3和互补输出通道(TIM1_CH3和TIM1_CH3N)以及LED0,LED0用于指示程序在运行,PA6用做断路输入,我们会测试断路功能:

BKIN

TIM1_CH3

TIM1_CH3N

LED0

PA6

PE13

PE12

PI0

表18.4.4. 1硬件资源

3. 原理图​

TIM1的两个通道如下:

图18.4.4. 1TIM1两个通道部分原理图

对于PA6用做断路输入,我们会配置PA6为低电平时就会进行刹车,即停止输出PWM波。PA6引脚在底板的CAMERA接口有用到,我们需要用杜邦线,将低电平引入DCMI_PIXCLK就可以给PA6提供低电平了:

图18.4.4. 2断路输入部分原理图

18.4.5 软件设计​

1. 程序流程图​

图18.4.5. 1程序流程图

2. STM32CubeMX配置​

(1)配置PE13和PE14复用为TIM1_CH3和TIM1_CH4

新建一个工程ATIM_PWM_DT,进入STM32CubeMX插件配置界面后,在Pinout & Configuration处配置PE13和PE12复用为TIM1_CH3和TIM1_CH3N,如下图所示:

图18.4.5. 2配置通道引脚

本实验我们会测试刹车功能,会用到PA6作为通道3刹车输入对应IO口,我们配置如下,选择PA6为TIM1_BKIN:

图18.4.5. 3配置TIM1通道3的刹车输入

本节实验我们会用到LED0来指示LED0亮,所以同时配置PI0:

图18.4.5. 4配置LED0

(2)配置TIM1时基等参数

在TimersàTIM1中先配置TIM1的模式,模式配置如下图所示,我们选择内部时钟,选择PWM Generation CH3 CH3N,即通道3和互补通道3产生PWM:

图18.4.5. 5配置模式为PWM互补输出

接下来我们配置TIM1的时基参数和通道3以及互补通道3的参数,如下红框部分需要我们手动配置,其它部分就保持默认配置:

图18.4.5. 6配置TIM1通道3和互补输出通道参数

Counter Settings参数我们前面已经分析过多次,这里就不再重复分析。要注意的是Internal Clock Division(CKD)我们选择Division by 4 ,即4分频,209/4=52.25MHz。

Break And Dead Time management-BRK Configuration用于配置刹车输入,参数配置:

  • BRK State用于配置刹车状态,这里配置为Enable,即打开刹车电平;
  • BRK Polarity用于配置刹车极性,这里配置为Low,即刹车电平为低;
  • BRK Filter (4 bits value)默认选择为0,即不使用滤波;
  • BRK Sources Configuration用于配置刹车源,其中:-Digital Input 即数字输入,这里选择Disable
    -DFSDM选择Disable。DFSDM是STM32产品内置的一个新的数字外设。
    Break And Dead Time management-BRK2 Configuration用于配置刹车输入2,参数配置:
  • BRK2 State选择 Disable,我们这里只用到一个刹车输入,没有用刹车输入2,所以有关刹车输入2部分都不需要配置,只要不开启就好。Break And Dead Time management-Output Configuration用于配置死区(停滞)时间
  • Automatic Output State 用于配置自动输出状态,这里选择使能Enable,即将TIMx_BDTR寄存器的AOE置1,所以只要断路输入(BRK 或 BRK2)为无效状态,使能主输出MOE位以后,OCx 和 OCxN 就有输出。
  • Off State Selection for Run Mode (OSSR)即运行模式下“关闭状态”选择,这里选 Disable,即关闭
  • Off State Selection for ldle Mode (OSSl) 即空闲模式下“关闭状态”选择,这里选择Disable,即关闭
  • Lock Configuration,锁定设置选择Off,即关闭
  • Dead Time用于配置死区延时时间,这里配置死区发生器为十进制数的100,前面我们已经计算出死区时间约为1.914us;PWM Generation Channel 3 and 3N
  • Mode 选择PWM mode 1,即PWM模式1;
  • Pulse(16 bits value)用于配置通道3的占空比,这里配置为300,即30%的占空比,那么通道3对应的互补输出通道的占空比为70%;
  • Output compare preload 输出比较预加载项选择Enable,即在定时器工作时是否能修改Pulse的值,如果禁用此项,表示定时器工作时不能进行修改,只能等到更新事件到来的时候才能进行修改,所以这里选择使能。
  • Fast Mode用于配置PWM脉冲快速模式,这里我们也不需要,可以不配置。
  • CHN Polarity用于配置互补输出通道电平极性,这里我们选择为Low,即低电平有效;
  • CH Polarity:输出极性,这里我们选择Low,即低电平有效;
  • CH Idle State用于配置通道的空闲状态的电平,这里默认选择Reset为低电平。
  • CHN Idle StateSet用于配置互补输出通道的空闲状态的电平,这里默认选择Set为高电平。(3)配置GPIO
    本实验还会用到LED0,LED0配置和前面实验的一样:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_31

  • 图18.4.5. 7配置LED0
    PA6引脚配置如下,我们开启上拉,因为前面设置其低电平有效,在没有没有操作此IO口的食谱,我们希望其默认在高电平,所以做了上拉:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_32

  • 图18.4.5. 8配置PA6上拉
    两个通道的IO口配置如下:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_33

  • 图18.4.5. 9配置通道3和互补输出通道上拉
    (4)配置时钟
    本实验我们采用外部24MHz的时钟HSE(也可以采用内部时钟),配置时钟树,经过PLL3锁相环以后,APB1的时钟频率为最大209MHz(也可以配置其它频率):
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_34

  • 图18.4.5. 10配置HSE
    我们选择HSE,作为锁相环PLL3的时钟源,在MCU子系统时钟里输入209并回车,STM32CubeMX会自动为我们计算参数,然后再手动配置APB1DIV、APB2DIV和APB3DIV的分频值为2。当APB1DIV的分频数大于1的时候,基本定时器的倍频器倍频值始终为2,所以通用定时器TIM8的时钟频率为209MHz。
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_35

  • 图18.4.5. 11配置系统时钟
    (5)配置生成独立的文件
    配置生成独立的.c和.h头文件,如下图:
  • 《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_36

  • 图18.4.5. 12配置生成独立的.c和.h文件

3. 生成工程并添加BSP文件夹​

配置好后,按下“Ctrl+S”保存修改配置,生成工程,将上一章节的BSP文件夹拷贝到本机工程实验中,因为我们会用到LED0的驱动,其它的LED和按键的驱动,如果不用的话就自行删除掉,只留下LED0部分。如下:

图18.4.5. 13生成工程

4. 分析tim.c文件​

tim.c的初始化代码如下,和我们前面在STM32CubeMX上配置的一致:

1 #include "tim.h"​
2 ​
3 TIM_HandleTypeDef htim1;​
4 ​
5 /**​
6 * @brief高级定时器TIM1互补输出初始化函数(使用PWM模式1)​
7 * @note​
8 配置高级定时器TIM1互补输出, 一路OC3 一路OC3N, 并且可以设置死区时间​
9级定时器的时钟来自APB2,当APB2DIV≥2分频的时候​
10高级定时器的时钟为APB2时钟的2倍, 而APB2为104.5M, 所以定时器时钟 = 209Mhz​
11定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.​
12定时器工作频率,单位:Mhz​
13 *​
14 * @param无​
15 * @retval无​
16 */​
17 void MX_TIM1_Init(void)​
18 {​
19 TIM_ClockConfigTypeDef sClockSourceConfig = {0};​
20 TIM_MasterConfigTypeDef sMasterConfig = {0};​
21 TIM_OC_InitTypeDef sConfigOC = {0};​
22 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};​
23 ​
24 htim1.Instance = TIM1; /* 定时器1 */​
25 htim1.Init.Prescaler = 209-1; /* 定时器分频 */​
26 htim1.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式*/​
27 htim1.Init.Period = 1000-1; /* 自动重装载值 */​
28 htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;/* 4分频​
29 htim1.Init.RepetitionCounter = 0; /* 重复计数器寄存器为0 */​
30 /* 使能影子寄存器TIMx_ARR */​
31 htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;​
32 if (HAL_TIM_Base_Init(&htim1) != HAL_OK) /* 初始化定时器时基 */​
33 {​
34 Error_Handler();​
35 }​
36 /* 使用内部时钟 */​
37 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;​
38 /* 时钟源配置 */​
39 if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)​
40 {​
41 Error_Handler();​
42 }​
43 if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)/* PWM模式初始化 */​
44 {​
45 Error_Handler();​
46 }​
47 /* 定时器主从模式配置,这里我们没有用到主从模式 */​
48 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;​
49 sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;​
50 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;​
51 if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)​
52 {​
53 Error_Handler();​
54 }​
55 sConfigOC.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */​
56 sConfigOC.Pulse = 300; /* 设置通道3的占空比为30% */​
57 sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; /* 通道3输出极性为低 */ ​
58 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW; /*互补输出极性为低 */ ​
59 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; /* 不使用快速模式 */​
60 /* 设置通道3的空闲状态下为低电平​
61 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;​
62 /* 设置互补输出通道的空闲状态下为高电平 */​
63 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_SET;​
64 /* 初始化定时器的通道3 PWM输出 */​
65 if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)​
66 {​
67 Error_Handler();​
68 }​
69 /* 运行模式下“关闭状态”选择,这里选择Disable,即关闭 */​
70 sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;​
71 /* 即空闲模式下“关闭状态”选择,这里选择Disable,即关闭 */​
72 sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;​
73 /* 锁定设置选择Off,即关闭 */​
74 sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;​
75 /* 配置死区延时时间,这里配置死区发生器为十进制数的100 */​
76 sBreakDeadTimeConfig.DeadTime = 100;​
77 /* 用于配置刹车状态,这里配置为Enable,即打开刹车 */​
78 sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE;​
79 /* 用于配置刹车极性,这里配置为Low,即刹车电平为低 */​
80 sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW;​
81 /* 不使用滤波 */​
82 sBreakDeadTimeConfig.BreakFilter = 0;​
83 sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;​
84 /* 没有使用到刹车输入2,这里处于关闭状态 */​
85 sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;​
86 sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_LOW;​
87 sBreakDeadTimeConfig.Break2Filter = 0;​
88 sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;​
89 /* 用于配置自动输出状态,这里选择使能Enable */​
90 sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE;​
91 /* 配置刹车功能 */​
92 if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)​
93 {​
94 Error_Handler();​
95 }​
96 HAL_TIM_MspPostInit(&htim1);​
97 }​
98 /**​
99 * @brief定时器互补输出模式时钟初始化函数​
100 * @param:定时器句柄​
101 * @note此函数会在MX_TIM1_Init中调用,如HAL_TIM_Base_Init函数中​
102 * @retval无​
103 */​
104 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)​
105 {​
106 GPIO_InitTypeDef GPIO_InitStruct = {0};​
107 if(tim_baseHandle->Instance==TIM1)​
108 {​
109 __HAL_RCC_TIM1_CLK_ENABLE(); /* 开启TIM1时钟 */​
110 __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */​
111 /**TIM1 GPIO Configuration​
112 PA6 ------> TIM1_BKIN​
113 */​
114 GPIO_InitStruct.Pin = GPIO_PIN_6; /* 指定引脚6 */​
115 GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; /* 复用开漏输出 */​
116 GPIO_InitStruct.Pull = GPIO_PULLUP; /* 上拉 */​
117 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; /* 低速模式 */​
118 GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;/* 复用为TIM1功能引脚 */​
119 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* 初始化PA6 */​
120 }​
121 }​
122 /**​
123 * @brief定时器互补输出通道引脚初始化函数​
124 * @param:定时器句柄​
125 * @note此函数会被MX_TIM1_Init函数调用​
126 * @retval无​
127 */​
128 void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)​
129 {​
130​
131 GPIO_InitTypeDef GPIO_InitStruct = {0};​
132 if(timHandle->Instance==TIM1)​
133 {​
134 __HAL_RCC_GPIOE_CLK_ENABLE(); /* 开启GPIOE的时钟 */​
135 /**TIM1 GPIO Configuration​
136 PE13 ------> TIM1_CH3​
137 PE12 ------> TIM1_CH3N​
138 */​
139 GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_12;/* 指定引脚为13和12 */​
140 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */​
141 GPIO_InitStruct.Pull = GPIO_PULLUP; /* 上拉 */​
142 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; /* 速度等级为高速 */​
143 GPIO_InitStruct.Alternate = GPIO_AF1_TIM1; /* 复用选择为AF1 */​
144 HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); /* 初始化GPIOE */​
145 }​
146 }​
147 /**​
148 * @brief定时器互补输出时基反初始化函数​
149 * @param:定时器句柄​
150 * @note如果需要,用户可以调用此函数来关闭TIM1​
151 * @retval无​
152 */​
153 void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)​
154 {​
155 if(tim_baseHandle->Instance==TIM1)​
156 {​
157 __HAL_RCC_TIM1_CLK_DISABLE();/* 关闭TIM1时钟 */​
158​
159 /**TIM1 GPIO Configuration​
160 PE13 ------> TIM1_CH3​
161 PE12 ------> TIM1_CH3N​
162 PA6 ------> TIM1_BKIN​
163 */​
164 /* 将GPIOx外设寄存器初始化为其默认复位值 */​
165 HAL_GPIO_DeInit(GPIOE, GPIO_PIN_13|GPIO_PIN_12);​
166 HAL_GPIO_DeInit(GPIOA, GPIO_PIN_6);​
167 }​
168 }​

第28行,配置4分频,所以f DTS = f CK_INT

第55行,配置为PWM模式1;

第56行,配置通道3占用比为30%,所以互补输出通道的占空比为70%;

第76行,设置死区发生器为十进制数的100;

第78行,打开刹车;

第80行,设置刹车电平为低电平有效,也就是给断路输入引脚为低电平时开始刹车,通道不输出PWM波。

5. 修改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 void SystemClock_Config(void);​
9 int main(void)​
10 {​
11 HAL_Init(); /* HAL库初始化 */​
12 if(IS_ENGINEERING_BOOT_MODE())​
13 {​
14 /* 系统时钟初始化 */​
15 SystemClock_Config();​
16 }​
17 MX_GPIO_Init(); /*GPIO初始化 */​
18 MX_TIM1_Init(); /* TIM1初始化 */​
19 /* USER CODE BEGIN 2 */​
20 led_init(); /* LED初始化 */​
21 /* 配置通道3开始生成PWM信号 */​
22 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);​
23 /* 在互补输出上开始PWM信号生成 */​
24 HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_3);​
25 /* USER CODE END 2 */​
26 while (1)​
27 {​
28 /* USER CODE BEGIN 3 */​
29 LED0_TOGGLE(); /* LED0翻转 */​
30 HAL_Delay(500); /* 延迟500ms */​
31 }​
32 /* USER CODE END 3 */​
33 }​

第22行,HAL_TIM_PWM_Start是定时器的PWM输出启动函数,此API函数我前面就有介绍到,这里是开启通道3的PWM输出。

第24行,调用HAL_TIMEx_PWMN_Start函数启动互补输出通道生成PWM波。

6. 编译运行​

本次实验需要使用示波器来验证。找4根一边是公头一边是母头的杜邦线,两根杜邦线分别接在JP1排针引出的PE13和PE12上,两根杜邦线接在板子引出的地线上,例如底板的JP7和JP8排针处有引出地线(注意,是地线,不要接错5V或者3.3V),如下图:

图18.4.5. 14连接好PWM输出排针

将引出的线接入到示波器的两个通道,用于测量TIM1的通道3和互补输出通道的波形。开发板接好线,拨码开关拨成MCU启动方式,然后开发板上电。STM32CubeIDE进入Debug模式,点击运行按钮

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_37

来运行调试,可以看到开发板底板的LED0灯在闪烁,说明程序已经在运行了。如下图,通道1的波形(黄色)是PE12输出的,是互补输出,其高电平脉宽为302.0us,通道2的波形(蓝色)是PE13输出的,是输出通道3的信号,其低电平脉宽为298.0us。

互补输出的PWM波的正脉宽减去正常的PWM的负脉宽的值除以2就是死区时间,也可以是正常的PWM的正脉宽减去互补输出的PWM波的负脉宽的值除以2:

死区时间 =(302.0 – 298)/2 us= 2us

或者:

死区时间=(702.0 – 698)/2 us= 2us

与我们前面计算的1.914相近,有误差是正常的。

图18.4.5. 15PWM互补输出波形

下面我们验证刹车功能。找一根一边是公头一边是母头的杜邦线,将底板上的JP7的地线(注意,是地线,不要接错5V或者3.3V)接到CAMERA接口的pclk口(即PA),如下图所示:

图18.4.5. 16刹车输入引脚接线

接好线以后,PA6输入了低电平,因为我们在刹车功能配置中设置BRK Polarity为Low,即刹车电平为低,可以发现通道3和互补通道停止输出PWM波:

图18.4.5. 17执行刹车后的波形

因为我们在STM32CubeMX上将Automatic Output State配置为Enable,使能了AOE位,即允许刹车后自动恢复输出,所以当停止给PA6接入低电平(拔掉之前连接在CAMERA上的杜邦线),PWM波会恢复输出。

18.5 高级定时器PWM输入模式实验​

本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 11-4 ATIM_PWM_IN

18.5.1 PWM输入模式​

本小节我们来学习如何使用高级定时器PWM输入模式,此模式是输入捕获模式的一个特例,PWM输入模式经常被应用于测量PWM波脉宽和频率。

在前面的通用定时器输入捕获实验中只用到了一个捕获寄存器来测量脉宽,实验中是先捕获到上升沿,将计数器TIMx_CNT的值锁存到捕获寄存器TIMx_CCRx中,然后设置为下降沿捕获,当捕获到下降沿时,再将TIMx_CNT的值记录到TIMx_CCRx中,然后设置为上升沿捕获,如此反复,上升沿和下降沿两次得到的捕获值之差可以用于计算高电平脉冲,根据占空比也可以计算出脉冲的周期。

图18.5.1. 1通用定时器输入捕获实验捕获原理示意图

本章节的PWM输入模式需要用到两个捕获寄存器进行测量,两个捕获通道ICx会被映射至同一个 TIx 输入,这两个 ICx 信号在边沿处有效,但极性相反。输入通道和捕获通道的映射关系如下:

图18.5.1. 2输入通道和捕获通道的映射关系

注意:当我们使用 PWM 输入模式的时候,因为一个输入通道(TIx)会占用两个捕获通道(ICx),即捕获通道IC1和IC2为一组,捕获通道IC3和IC4为一组。因此一个定时器在使用 PWM 输入的时候最多只能使用两个输入通道(TIx)。

本实验配置定时器8在PWM输入模式下工作,然后向通道1输入PWM波,并对其进行测量脉宽和频率。下面我们用一个时序图来描述PWM输入模式的工作原理。

图18.5.1. 3输入模式时序

实验过程原理如下:首先将从模式控制器配置为复位模式,即向TIMx_SMCR寄存器中的SMS[3:0]位写入0100。复位模式下,当我们启动触发信号开始进行捕获的时候,在出现所选触发极性检测信号时,计数器 CNT复位清零并生成一个寄存器更新事件。

我们定义一个PWM波的起点是上升沿,结束点是下一个上升沿(该上升沿也是下一个PWM波的起点),从这个上升沿到下一个上升沿所经过的时间就是该PWM波的周期;PWM波的上升沿到下一个下降沿所经过的时间就是此PWM波的正脉宽长度,下降沿再到下一个上升沿所经过的时间就是负脉宽时间长度。根据这些信息就可以计算出PWM波的周期和频率以及占空比。

PWM波输入到定时器1通道1(TI1)后,信号会被检测是上升沿还是下降沿,比如IC1被设置为上升沿检测,IC2被设置为下降沿检测,那么TI1FP1这一路通道是测量周期,另外一路通道TI1FP2则是测量占空比。

18.5.2 TIM1/TIM8寄存器

高级定时器PWM输入模式实验除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 之外。主要还用到以下这些寄存器:

1. 从模式控制寄存器(TIMx_SMCR)

TIM1/TIM8的从模式控制寄存器描述如下图所示:

图18.5.2. 1寄存器

该寄存器的SMS[3:0]位用于从模式选择。比如本实验中我们要用复位模式,所以设置SMS[3:0]=0100。TS[2:0]位是触发选择,我们设置为滤波后的定时器输入1 (TI1FP1),即TS[4:0]=00101。

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

TIM1/TIM8的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx_CCMR1和TIMx _CCMR2。TIMx_CCMR1控制CH1和CH2,而TIMx_CCMR2控制CH3和CH4。本章节我们会用到TIM8的通道1,我们要用到输入捕获模式,TIMx_CCMR1寄存器描述如下图所示:

图18.5.2. 2寄存器

位1:0 CC1S用于配置捕获/比较 1 选择:

00:CC1 通道配置为输出;

01:CC1 通道配置为输入,IC1 映射到 TI1 上;

10:CC1 通道配置为输入,IC1 映射到 TI2 上;

11:CC1 通道配置为输入,IC1 映射到 TRC 上。

位 9:8 CC2S用于配置捕获/比较 2 选择:

00:CC2 通道配置为输出;

01:CC2 通道配置为输入,IC2 映射到 TI2 上;

10:CC2 通道配置为输入,IC2 映射到 TI1 上;

11:CC2 通道配置为输入,IC2 映射到 TRC 上。

本实验我们用到了定时器8通道1的PWM输入模式功能,输入通道1(TI1)需要映射到两个捕获通道(IC1和IC2)。所以配置CC1S[1:0]=01,即CC1通道配置为输入,IC1映射到TI1上。配置CC2S [1:0]=10,即CC2通道配置为输入,IC2映射到TI1上。

3. 捕获/比较使能寄存器(TIMx_ CCER)

TIM1/TIM8的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关和极性。TIMx_CCER寄存器描述如下图所示:

图18.5.2. 3寄存器

CCxE位用于使能捕获/比较x输出,将该位置1则使能捕获/比较x输出;CCxP位用于配置捕获/比较 1 输出极性,0表示OCx高电平有效,1表示OCx低电平有效。

我们需要捕获通道1(IC1)测量PWM波的周期,所以CC1P位置0,即高电平有效。捕获通道2(IC2)测量PWM波的高电平脉宽,所以CC2P位置1,即低电平有效。设置好输入极性后,还需要能这两个捕获通道,即CC1E和CC2E位置1。

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

捕获/比较寄存器(TIMx_ CCR1/2/3/4),该寄存器总共有4个,对应4个通道CH1~CH4。我们使用的是通道1,所以来看看TIMx_ CCR1寄存器:

图18.5.2. 4寄存器

本实验中,当捕获通道1(IC1)捕获到上升沿后,捕获通道2(IC2)捕获到下一个下降沿,此时读取捕获/比较寄存器2(TIM1_ CCR2)的值就是PWM波高定平脉宽的计数值,而当捕获通道1(IC1)捕获到下一个上升沿时,此时读取捕获/比较寄存器1(TIM1_ CCR1)的值就是PWM波周期的计数值,然后结合计数频率就可以知道时间。TIM1_ CCR2和TIM1_ CCR1的配置类似。

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

DMA/中断使能寄存器描述如下图所示:

图18.5.2. 5寄存器

该寄存器位0(UIE)用于使能或者禁止更新中断,因为本实验我们用到中断,所以该位需要置1。位1(CC1IE)用于使能或者禁止捕获/比较1中断,我们也要用到,所以该位需要置1。

18.5.3 硬件设计​

1. 例程功能​

首先通过TIM5_CH1 (PA0)输出PWM波。然后把PA0输出的PWM波用杜邦线接入PI5(定时器8通道1),最后通过串口打印PWM波的脉宽和频率等信息。和前面程序一样,我们使LED0闪烁来提示程序正在运行。

2. 硬件设计​

1)

LED0

TIM5_CH1

UART4_TX

UART4_RX

TIM8_CH1

PI0

PA0

PG11

PB2

PI5

表18.5.3. 1硬件资源

2)定时器8输出通道1(TIM8_CH1)

定时器属于STM32MP157的内部资源,只需要软件设置好即可正常工作。

3. 原理图​

开发板底板的JP1排针引出了PA0和PI5引脚,这两个引脚可分别当做TIM5_CH1和TIM8_CH1:

图18.5.3. 1引脚部分原理图

18.5.4 软件设计​

定时器PWM输入模式实验用到的HAL库中的驱动代码在前面实验都有介绍过了。实验中我们会使用HAL_TIM_ReadCapturedValue函数来读取捕获寄存器的值,通过捕获值计算正脉冲的宽度以及PWM波的周期:

uint32_t HAL_TIM_ReadCapturedValue(TIM_HandleTypeDef *htim, uint32_t Channel)

通过HAL_TIM_IC_Start_IT函数启动定时器的输入捕获模式,并开启输入捕获中断。本节实验会用到捕获中断。

HAL_StatusTypeDef HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel)

实验中使用到的函数前面我们都已经接触过了我们在程序解析部分再详细讲解应用到的函数。下面先来看看程序流程图。

1. 程序流程图​

下面看看本实验的程序流程图:

图18.5.4. 1高级定时器PWM输入模式实验程序流程图

2. STM32CubeMX配置​

(1)配置引脚复用

新建一个工程ATIM_PWM_IN,进入STM32CubeMX插件配置界面后,在Pinout & Configuration处配置PI5复用为TIM8_CH1,配置PA0复用为TIM5_CH1,如下图所示:

图18.5.4. 2配置通道引脚

本实验我们会用到UART4打印信息,配置PG11复用为UART4_TX,配置PB2复用为UART4_RX,如下图:

图18.5.4. 3配置UART4引脚

本节实验我们会用到LED0来指示LED0亮,所以同时配置PI0:

图18.5.4. 4配置LED0

(2)配置TIM5时基等参数

本实验我们要用到TIM5_CH1产生PWM波给TIM8_CH1,在TimersàTIM5中先配置TIM5的模式,模式配置如下图所示,我们选择内部时钟,选择PWM Generation CH1,即通道1产生PWM:

图18.5.4. 5配置TIM5模式

接下来我们配置TIM5的时基参数和通道1的参数,如下红框部分需要我们手动配置,其它部分就保持默认配置:

图18.5.4. 6配置TIM5时基和通道参数

上图的配置参数,除了红框中的要配置以外,其它选项保持默认,配置部分的介绍如下:

Counter Settings(计数器配置)配置如下:

  • Prescaler用于配置定时器预分频值,这里配置为209-1;
  • Counter Mode用于配置计数模式,我们选择向上计数Up;
  • Counter Period用于配置定时器自动重装载值,我们设置为100-1;
  • Internal Clock Division (CKD)配置为No Division即时钟不分频;
  • auto-reload preload用于配置自动重载是否使能,我们选择 Enable使能自动重载;

上述参数可以计算定时器的时钟频率为:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_38


也就是计数器每计数一个节拍为1us,设置自动重载值为100-1,计数器计数100个节拍就是100us,所以PWM波的周期为100us,频率为10000Hz。下面我们会配置PWM的占空比为50%,即高电平脉冲是50us。

PWM Generation Channel 1用于配置通道1的参数,其中:

  • Mode:用于配置PWM的模式,这里选择PWM mode1,即PWM模式1。
  • Pulse (16 bits value):是占空比值,也可以说是脉冲宽度,即TIMx_CCR1的值,也就是有效电平的值,这里配置50,即占空比为50%。
  • Output compare preload:输出比较预加载项选择Enable,即在定时器工作时是否能修改Pulse的值,如果禁用此项,表示定时器工作时不能进行修改,只能等到更新事件到来的时候才能进行修改,这里选择使能。
  • Fast Mode用于配置PWM脉冲快速模式,这里我们也不需要,可以不配置。
  • CH Polarity:输出极性,这里我们就默认选择High,即高电平有效。因为前面选择PWM模式1,计数器向上计数,如果TIM5_CNT<TIM5_CCR1时,通道1为有效电平,否则为无效电平;

(2)配置TIM8时基等参数

本实验我们要用到TIM8_CH1捕获TIM5_CH1产生的PWM波,然后计算出PWM波的高电平脉宽、PWM波的周期和频率。这里配置两个捕获通道(IC1和IC2)。

在TimersàTIM8中先配置TIM8的模式,模式配置如下图所示,我们选择从模式为Reset Mode,触发源为TI1FP1,并配置两个捕获通道IC1和IC2。

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_39


图18.5.4. 7配置TIM8模式

这里的从模式为Reset Mode也就是复位模式,TIM8做从定时器,TIM5是主定时器。从模式定时器收到主模式定时器的信号后,当触发输入产生变化时,从模式定时器的计数器被复位,并更新所有的寄存器。此外还有其他的模式,如:

External Clock Mode1——外部时钟:使用外部时钟源作为定时器时钟;

Gated Mode——门控模式:使用门控的功能决定时钟是否工作;

Trigger Mode——触发模式:由主定时器信号变化触发是否工作。

接下来配置TIM8_CH1的时基和捕获通道参数:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_40


图18.5.4. 8配置TIM8时基和通道参数

Counter Settings配置:

这里配置TIM8的预分频值为0,即时钟频率就是PWM波输入的频率;计数模式为向上计数;定时器自动重装载值默认选择最大,为65536;内部时钟不分频;重复计数默认选择为0,高级定时器和基本定时器以及通用定时器不同,高级定时器的TIMx_RCR递减为0时才会发生更新事件,这里配置为0,表示当计数器计数到65536后就发生溢出;使能自动装载;从模式控制选择为复位模式。

Input Capture Channel 1参数配置如下:

  • Polarity Selection用于配置极性选择,这里配置上升沿;
  • IC Selection 配置为Direct,为直连模式,即配置IC1直接映射在TI1上;
  • Prescaler Division Ratio用于配置配置输入分频,这里选择为No division,即不分频。要注意的是,如果设置了分频比,例如设置为2,表示捕获到2个上升沿才产生一次中断;
  • Input Filter (4 bits value) 配置输入滤波器参数,这里配置为0,即不滤波。 Input Capture Channel 2参数配置如下:
  • Polarity Selection这里配置下降沿;
  • IC Selection 配置为Direct,为直连模式,即配置IC2直接映射在TI1上;
  • Prescaler Division Ratio用于配置配置输入分频,这里选择为No division,即不分频;

以上配置符合我们前面的设置也就是捕获通道1用于测试PWM波的周期,捕获通道2用于配置PWM波的高电平脉宽。

(3)配置UART4参数

进入System Coreà Connectivity àUART4中,UART4通信模式配置为异步通信,Hardware Flow Control(硬件流控制)就不用配置了,我们这里用不到。在Parameter Settings(参数设置)处可以看到系统已经自动为我们配置好了默认的参数:

波特率为115200Bit/s;字长为8位;无校验位;1位停止位;数据方向为发和收;16倍过采样;Clock Prescaler(时钟预分频器)分频值为1。

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_41


图18.5.4. 9配置UART4参数

(4)配置NVIC

本节实验UART4会用到串口接收中断,TIM8会用到更新中断,我们先开启两者的全局中断:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_42


图18.5.4. 10开启TIM8更新中断


《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_43


图18.5.4. 11开启UART4全局中断

开启全局中断后,中断优先级默认为0,我们回到NVIC处配置中断优先级分组和中断优先级。我们选择中断优先级分组为2,UART4的抢占优先级为3,子优先级为3,TIM8的更新中断抢占优先级为2,子优先级为0.

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_44


图18.5.4. 12配置TIM8和UART4中断优先级

(5)配置GPIO

本实验还会用到LED0,LED0配置和前面实验的一样:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_45


图18.5.4. 13配置LED0

TIM8和TIM5的两个通道的IO口配置如下:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_46


图18.5.4. 14配置TIM8和TIM5引脚上拉

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_47


图18.5.4. 15配置UART4两个引脚上拉

(6)配置时钟

本实验我们采用外部24MHz的时钟HSE(也可以采用内部时钟),配置时钟树,经过PLL3锁相环以后,APB1的时钟频率为最大209MHz(也可以配置其它频率):

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_48


图18.5.4. 16配置HSE

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

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_49


图18.5.4. 17配置系统时钟

(7)配置生成独立的文件

配置生成独立的.c和.h头文件,如下图:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_50


图18.5.4. 18配置生成独立的.c和.h文件

3. 生成工程并添加BSP文件夹

配置好后,按下“Ctrl+S”保存修改配置,生成工程,将上一章节的BSP文件夹拷贝到本机工程实验中,因为我们会用到LED0的驱动,如下:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_51


图18.5.4. 19生成工程

4. 分析tim.c文件

根据STM32CubeMX的配置,tim.c文件生成初始化代码,关于定时器5的初始化代码我们就不列出来了,和前面通用定时器章节的PWM输出实验的初始化代码相似。下面我们列出TIM8的初始化代码,代码中附上详细的注释:

1 #include "tim.h"​
2 ​
3 TIM_HandleTypeDef htim8;​
4 ​
5 /**​
6 * @brief定时器TIM8通道1 PWM输入模式 初始化函数​
7 * @note​
8 通用定时器的时钟来自APB2,当APB2DIV≥2分频的时候​
9通用定时器的时钟为APB2时钟的2倍, 而APB2为104.5M, 所以定时器时钟 = 209Mhz​
10定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.​
11定时器工作频率,单位:Mhz​
12 *​
13 本函数初始化的时候: 使用psc=0, arr固定为65535. 得到采样时钟频率为209Mhz​
14 * @param无​
15 * @retval无​
16 */​
17 void MX_TIM8_Init(void)​
18 {​
19 TIM_SlaveConfigTypeDef sSlaveConfig = {0};​
20 TIM_MasterConfigTypeDef sMasterConfig = {0};​
21 TIM_IC_InitTypeDef sConfigIC = {0};​
22 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};​
23 ​
24 htim8.Instance = TIM8; /* 定时器8 */​
25 htim8.Init.Prescaler = 0; /* 定时器预分频系数为0,不分频 */​
26 htim8.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数模式 */​
27 htim8.Init.Period = 65535; /* 自动重装载值 */​
28 htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;/*内部时钟不分频*/​
29 htim8.Init.RepetitionCounter = 0; /* 重复计数为0 */​
30 /* 自动重载预装载使能 */​
31 htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;​
32 if (HAL_TIM_Base_Init(&htim8) != HAL_OK) /* 时基初始化 */​
33 {​
34 Error_Handler();​
35 }​
36 if (HAL_TIM_IC_Init(&htim8) != HAL_OK) /* 捕获通道初始化 */​
37 {​
38 Error_Handler();​
39 }​
40 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; /* 复位模式 */​
41 sSlaveConfig.InputTrigger = TIM_TS_TI1FP1; /* 触发源为TI1FP1 */​
42 /* 触发模式:上升沿触发 */​
43 sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;​
44 sSlaveConfig.TriggerFilter = 0; /* 不滤波 */​
45 /* 从模式配置 */​
46 if (HAL_TIM_SlaveConfigSynchro(&htim8, &sSlaveConfig) != HAL_OK)​
47 {​
48 Error_Handler();​
49 }​
50 /* 定时器触发输出,TIMx_EGR寄存器中的UG位用作触发输出(TRGO) */​
51 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;​
52 sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;​
53 /* 主定时器的从模式失能 */​
54 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;​
55 /* 配置主模式 */​
56 if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK)​
57 {​
58 Error_Handler();​
59 }​
60 sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;/* 上升沿检测 */​
61 /*选择输入端 IC1映射到TI1上*/​
62 sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;​
63 sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; /* 不分频 */​
64 sConfigIC.ICFilter = 0; /* 不滤波 */​
65 /* 捕获通道1配置 */​
66 if (HAL_TIM_IC_ConfigChannel(&htim8, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)​
67 {​
68 Error_Handler();​
69 }​
70 sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;/* 下降沿检测 */​
71 /*选择输入端 IC2映射到TI1上*/​
72 sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;​
73 /* 捕获通道2配置 */​
74 if (HAL_TIM_IC_ConfigChannel(&htim8, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)​
75 {​
76 Error_Handler();​
77 }​
78 /* 在输入模式下中断输入BRK,本节实验我们 没有用到断路功能 */​
79 sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;​
80 sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;​
81 if (HAL_TIMEx_ConfigBreakDeadTime(&htim8, &sBreakDeadTimeConfig) != HAL_OK)​
82 {​
83 Error_Handler();​
84 }​
85 }​
86 /**​
87 * @brief和TIM8时钟初始化函数​
88 * @param:定时器句柄​
89 * @note此函数会在MX_TIM8_Init中调用,如HAL_TIM_Base_Init函数中​
90 * @retval无​
91 */​
92 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)​
93 {​
94 GPIO_InitTypeDef GPIO_InitStruct = {0};​
95 if(tim_baseHandle->Instance==TIM5)​
96 {​
97 __HAL_RCC_TIM5_CLK_ENABLE(); /* 使能TIM5的时钟 */​
98 }​
99 else if(tim_baseHandle->Instance==TIM8)​
100 {​
101 __HAL_RCC_TIM8_CLK_ENABLE(); /* 使能TIM8时钟 */​
102 __HAL_RCC_GPIOI_CLK_ENABLE(); /*使能GPIOI时钟 */​
103 /**TIM8 GPIO Configuration​
104 PI5 ------> TIM8_CH1​
105 */​
106 GPIO_InitStruct.Pin = GPIO_PIN_5; /* 指定引脚5 */​
107 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */​
108 GPIO_InitStruct.Pull = GPIO_PULLUP; /* 上拉 */​
109 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速模式 */​
110 GPIO_InitStruct.Alternate = GPIO_AF3_TIM8;/* 复用为TIM8功能引脚 */​
111 HAL_GPIO_Init(GPIOI, &GPIO_InitStruct); /* 初始化GPIO5 */​
112​
113 HAL_NVIC_SetPriority(TIM8_UP_IRQn, 2, 0);/* TIM8更新中断优先级设置 */​
114 HAL_NVIC_EnableIRQ(TIM8_UP_IRQn); /* 使能TIM8中断 */​
115 }​
116 }​
117 /**​
118 * @brief引脚初始化函数​
119 * @param:定时器句柄​
120 * @note此函数会被MX_TIM5_Init函数调用​
121 * @retval无​
122 */​
123 void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)​
124 {​
125 GPIO_InitTypeDef GPIO_InitStruct = {0};​
126 if(timHandle->Instance==TIM5)​
127 {​
128 __HAL_RCC_GPIOA_CLK_ENABLE();​
129 /**TIM5 GPIO Configuration​
130 PA0 ------> TIM5_CH1​
131 */​
132 GPIO_InitStruct.Pin = GPIO_PIN_0; /* 指定引脚0 */​
133 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */​
134 GPIO_InitStruct.Pull = GPIO_PULLUP; /* 上拉 */​
135 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; /* 低速模式 */​
136 GPIO_InitStruct.Alternate = GPIO_AF2_TIM5; /* 复用为TIM5功能引脚 */​
137 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* 初始化GPIOA */​
138 }​
139 }​
140 /**​
141 * @brief和TIM8时基反初始化函数​
142 * @param:定时器句柄​
143 * @note如果需要,用户可以调用此函数来关闭TIM5和TIM8​
144 * @retval无​
145 */​
146 void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)​
147 {​
148 if(tim_baseHandle->Instance==TIM5)​
149 {​
150 __HAL_RCC_TIM5_CLK_DISABLE(); /* 关闭TIM5时钟 */​
151 }​
152 else if(tim_baseHandle->Instance==TIM8)​
153 {​
154 __HAL_RCC_TIM8_CLK_DISABLE(); /* 关闭TIM8时钟 */​
155 /**TIM8 GPIO Configuration​
156 PI5 ------> TIM8_CH1​
157 */​
158 /* 将PI5外设寄存器初始化为其默认复位值 */​
159 HAL_GPIO_DeInit(GPIOI, GPIO_PIN_5);​
160 /* 将TIM8初始化为默认复位值 */​
161 HAL_NVIC_DisableIRQ(TIM8_UP_IRQn);​
162 }​
163 }​

5. 在tim.c中添加代码

在tim.h中添加实验中会用到的几个变量,同时声明函数:

/* USER CODE BEGIN Private defines */​
extern uint8_t atimx_pwmin_sta; /* PWM输入状态 */​
extern uint16_t atimx_pwmin_psc ; /* PWM输入分频系数 */​
extern uint32_t atimx_pwmin_hval; /* PWM的高电平脉宽 */​
extern uint32_t atimx_pwmin_cval; /* PWM的周期宽度 */​
void atim_pwmin_restart(void); /* 声明函数 */​
/* USER CODE END Private defines */​

我们在tim.c中添加定时器PWM输入模式重新启动捕获函数和定时器输入捕获中断处理回调函数。我们在标红的字体之间添加代码如下:

1 /* USER CODE BEGIN 1 */​
2 uint8_t atimx_pwmin_sta; /* PWM输入状态 */​
3 uint16_t atimx_pwmin_psc = 0; /* PWM输入分频系数 */​
4 uint32_t atimx_pwmin_hval = 0 ; /* PWM的高电平脉宽 */​
5 uint32_t atimx_pwmin_cval = 0 ; /* PWM的周期宽度 */​
6 /**​
7 * @brief定时器TIM8 PWM输入模式 重新启动捕获​
8 * @param无​
9 * @retval无​
10 */​
11 void atim_pwmin_restart(void)​
12 {​
13 TIM8->DIER=0x0000; /* 关闭中断 */​
14 atimx_pwmin_sta = 0; /* 清零状态,重新开始检测 */​
15 atimx_pwmin_hval=0;​
16 atimx_pwmin_cval=0;​
17 TIM8->DIER=0x07; /* 打开中断 */​
18 }​
19 /**​
20 * @brief定时器输入捕获中断处理回调函数​
21 * @param定时器句柄指针​
22 * @note该函数在HAL_TIM_IRQHandler中会被调用​
23 * @retval无​
24 */​
25 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)​
26 {​
27 if (atimx_pwmin_sta == 0) /* 还没有成功捕获 */​
28 {​
29 if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)​
30 {​
31 /* 修正系数为2, 加2 */​
32 atimx_pwmin_hval = HAL_TIM_ReadCapturedValue(&htim8,TIM_CHANNEL_2)+2; ​
33 /* 修正系数为2, 加2 */​
34 atimx_pwmin_cval = HAL_TIM_ReadCapturedValue(&htim8,TIM_CHANNEL_1)+2; ​
35 atimx_pwmin_sta = 1; /* 标记捕获成功 */​
36 }​
37 }​
38 }​
39 /* USER CODE END 1 */​

第2~5行,定义几个我们会用到的变量。

第11~18行,定义定时器8 PWM输入模式重新启动捕获函数,先将中断关闭,再把上面用到的变量清零,然后再开启中断。之所以关闭中断是避免中断的影响,这些变量就不是0了。这里清除中断是将TIMx_DIER寄存器设置为复位值0x0000,即不开启中断。

第17行开启中断,这里注意,我们操作的是TIMx_DIER寄存器的第0和第1位,其中第0位UIE用于使能更新中断,第1位CC1IE用于使能捕获/比较 1 中断,我们将其置1即可。

第25~38行是定时器输入捕获中断处理回调函数。如果发生中断,会进入stm32mp1xx_it.c文件中的TIM8_UP_IRQHandler中断服务函数,该中断服务函数会调用定时器中断请求函数HAL_TIM_IRQHandler。在定时器中断请求函数中,会根据中断类型执行对应的中断回调函数,本实验是输入捕获中断,所以会执行HAL_TIM_IC_CaptureCallback回调函数。我们直接在回调函数中做文章,实现读取TIM8通道1的捕获值(即寄存器TIMx_CCRx的值)。

第27行,标志位atimx_pwmin_sta用于判断是否成功捕获,如果没有捕获,表示0;

第29~34行,如果通道1在使用中,则读取捕获通道1和捕获通道2的捕获值,通道1的捕获值用于计算PWM周期,通道2的捕获值用于计算PWM的正脉宽。这里的值要加上2,才等于用于计算的捕获值。

第35行,获取到捕获值以后,则标记已经成功捕获。

6. 在main.c文件中添加代码

main.c文件代码如下,标红的字体之间的代码是我们手动添加的,其它部分的代码我们前面的实验有分析过,这里就不再重复分析了。

1 #include "main.h"​
2 #include "tim.h"​
3 #include "usart.h"​
4 #include "gpio.h"​
5 /* USER CODE BEGIN Includes */​
6 #include "./BSP/Include/led.h"​
7 /* USER CODE END Includes */​
8 void SystemClock_Config(void);​
9 ​
10 int main(void)​
11 {​
12 HAL_Init();​
13​
14 if(IS_ENGINEERING_BOOT_MODE())​
15 {​
16 SystemClock_Config();​
17 }​
18 MX_GPIO_Init();​
19 MX_TIM8_Init();​
20 MX_UART4_Init();​
21 MX_TIM5_Init();​
22 /* USER CODE BEGIN 2 */​
23 double ht, ct, f, tpsc;​
24 HAL_UART_Receive_IT(&huart4,&RxBuffer,1); /*打开对应的串口中断 */​
25 HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_1); /* 开启PWM通道 */​
26 /* 启动TIM8输入捕获模式,且开启输入捕获中断 */​
27 HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_1);​
28 HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_2);​
29 __HAL_TIM_ENABLE_IT(&htim8, TIM_IT_UPDATE); /* 使能更新中断 */​
30 /* USER CODE END 2 */​
31 while (1)​
32 {​
33 /* USER CODE BEGIN 3 */​
34 HAL_Delay(2000);​
35 if (atimx_pwmin_sta) /* 捕获了一次数据 */​
36 {​
37 printf("\r\n"); /* 输出空,另起一行 */​
38 printf("PWM PSC :%d\r\n", atimx_pwmin_psc); /* 打印分频系数 */​
39 printf("TIMx_CCR2:%ld\r\n", atimx_pwmin_hval); /* 打印CCR2的值 */​
40 printf("TIMx_CCR1:%ld\r\n", atimx_pwmin_cval); /* 打印CCR1的值 */​
41 tpsc = ((double)atimx_pwmin_psc + 1)/209;/* 得到PWM采样时钟周期时间 */ ​
42 ht = atimx_pwmin_hval * tpsc; /* 计算高电平时间 */​
43 ct = atimx_pwmin_cval * tpsc; /* 计算周期长度 */​
44 f = (1 / ct) * 1000000; /* 计算频率 */​
45 printf("PWM Hight time:%.3fus\r\n", ht); /* 打印高电平脉宽长度 */​
46 printf("PWM Cycle time:%.3fus\r\n", ct); /* 打印周期时间长度 */​
47 printf("PWM Frequency :%.3fHz\r\n", f); /* 打印频率 */ ​
48 atim_pwmin_restart(); /* 重启PWM输入检测 */​
49 }​
50 LED0_TOGGLE(); /* LED0闪烁 */​
51 }​
52 /* USER CODE END 3 */​
53 }​

下面我们来看我们手动添加的代码部分。

第24行,开启对应的串口中断;

第25行,开启PWM通道;

第27和28行,启动TIM8输入捕获模式,且开启输入捕获中断,实验中我们用到两个捕获通道。

第29行,使能更新中断;

下面我们看看whlie循环部分:

第34行,延时2s,LED0每隔接近2s闪烁一次,串口UART4接近每隔2s打印一次;

第38行,打印分频系数,此分频系数是TIM8的分频系数,前面我们在STM32CubeMX中配置为了0,即不分频;

第39行,打印捕获通道2的捕获值,即TIMx_CCR2的值,通过此值计算出正脉冲宽度;

第40行,打印通道1的捕获值TIMx_CCR1,通过此值计算PWM波周期;

第41行,计算PWM采样时钟周期,即TIM8_CH1的周期,其实也就是TIM5_CH1信号的周期,周期等于频率的倒数

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_52

,这里TIM8_CH1的频率为209MHz。

第42和43行,计算高电平时间和周期,后面我们通过观察串口的数据实际计算一下。

第44行,计算频率,频率等于周期的倒数;

第45~47行,将计算出的PWM的波高电平脉冲宽度、周期和频率打印出来;

第48行,重启PWM输入检测。

7. UART4相关代码

要UART4正常打印,我们需要手动添加UART4的代码,这些代码这里就不列举出来了,在前面的部分章节实验以及串口通信实验章节我们都有详细讲解过。

8. 编译和测试

以上代码添加完毕以后,保存修改,本实验要使用串口显示浮点运算的数据所以先配置M4工程,勾选如下两处,这两个选项通常是用于串口打印的时候设置的,设置这两项以后,串口支持浮点类型数据打印。

Use float with printf from newlib-nano (-u _printf_float)

Use float with scanf from newlib-nano(-u _scanf_float)

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_53


图18.5.4. 20配置串口支持浮点数据打印

编译工程无报错以后,用Type-C线接在开发板的USB_TTL接口上,线的一端接在电脑的USB口上,按照前面的步骤连接好ST-Link,同时注意开发板上的JP11处的跳线帽是否已经接好,如果跳线帽没接,那么UART4则无法正常通信,拨码开关拨成001,即MCU启动模式。用两边是母头的杜邦线将JP1排针处的PA0和PI5连在一起。

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_54


图18.5.4. 21开发板接线

进入Debug以后,点击运行按钮

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_55

,可以看到底板的LED0灯在闪烁,说明程序已经在跑了。串口每隔2s打印信息如下:

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_56


图18.5.4. 22串口打印的数据

PWM PSC :0

TIMx_CCR2:10450

TIMx_CCR1:20900

PWM Hight time:50.000us

PWM Cycle time:100.000us

PWM Frequency :10000.000Hz

前面我们计算出TIM8_CH1信号的采样周期为

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_57

,也就是TIM8计数器每计数一个节拍用时

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_寄存器_58

TIMx_CCR2的值是10450,则PWM高脉冲宽度

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_框图_59

=50us。TIMx_CCR1的值是20900,则PWM周期是

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_60

=100us。PWM脉冲频率等于周期的倒数

《STM32MP1 M4裸机CubeIDE开发指南》第十八章 高级定时器实验_复用_61

和我们前面设置的TIM5_CH1的PWM输出结果一致。


举报

相关推荐

0 条评论