第二十五章高级定时器实验
本章我们主要来学习高级定时器,STM32MP157有2个高级定时器(TIM1和TIM8)。我们将通过四个实验来学习高级定时器的各个功能,分别是高级定时器输出指定个数PWM实验、高级定时器输出比较模式实验、高级定时器互补输出带死区控制实验和高级定时器PWM输入模式实验。
本章分为如下几个小节:
25.1、高级定时器简介;
25.2、高级定时器输出指定个数PWM实验;
25.3、高级定时器输出比较模式实验;
25.4、高级定时器互补输出带死区控制实验;
25.5、高级定时器PWM输入模式实验;
25.1 高级定时器简介
高级定时器的框图在通用定时器框图的基础上加了一些功能,前面如果学习了通用定时器的框图和实验,再看高级定时器的框图时就很好理解了。高级定时器的框图和通用定时器的框图十分相似,但仍有微弱差异,如下是高级定时器的框图:
图25.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概况。
图25.1. 2短路源是或运算关系
系统复位后默认禁止刹车电路,即默认关闭了断路功能(TIMx_BDTR寄存器中的MOE位为0)。设置TIMx_BDTR寄存器中的BKE位和BKE2 位可以使能或者禁止断路功能。断路输入信号的极性可以通过配置BKP位和BKP2位来选择。
了解完这两个差异之后,我们直接通过实际的实验来学习高级定时器。
25.2 高级定时器输出指定个数PWM实验
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验14-1 高级定时器-输出指定个数PWM实验。
本小节我们来学习使用高级定时器输出指定个数PWM,本实验以高级定时器8为例TIM1操作也类似。关于定时器如何输出PWM波的知识,请大家回顾通用定时器PWM输出实验的介绍。下面下面我们先来了解本实验会涉及的寄存器。
25.2.1 TIM1/TIM8寄存器
1. 控制寄存器 1(TIMx_CR1)
图25.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)
图25.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)
图25.2.1. 3寄存器
该寄存器主要是用户用软件更新各类事件和某些寄存器位,这里我们只介绍第0位:
位0[UG]是更新定时器事件的控制位,作用类似定时器溢出更新中断,区别是这里是通过软件去更新,而定时器溢出更新中断是硬件自己完成。本实验就是用到该位去更新定时器事件,对该位写1就可以重新初始化计数器并生成寄存器更新事件,由硬件自动清零。
4. 重复计数器寄存器(TIMx_ RCR)
图25.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:
图25.2.1. 5寄存器
该寄存器有16位,对于输出模式,CCR2[15:0] 为预装载值,该值与CNT的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制PWM的输出脉宽。
6. TIM1/TIM8断路和死区寄存器(TIMx_ BDTR)
图25.2.1. 6寄存器
对于通用定时器,只需要配置以上提到的寄存器就够了,如果是高级定时器,我们还需要配置TIMx_ BDTR,该寄存器我们只关注位15[MOE],对此位清0,则禁止禁止 OC 和 OCN 输出,如果要想高级定时器的PWM正常输出,则必须设置MOE位为1,即使能 OC 和 OCN 输出。
25.2.2 定时器的HAL库驱动
本节实验涉及到的HAL库驱动在前面通用定时器章节已经讲解,如HAL_TIM_PWM_Init和HAL_TIM_PWM_ConfigChannel函数都已经在前面章节介绍过,这里就不再重复介绍了。
25.2.3 硬件设计
1. 例程功能
用TIM8_CH2输出指定个数PWM,按键KEY0每按下一次,就输出5个PWM,输出的PWM控制BEEP的开和关,开关一次表示一个周期的PWM波形。LED0用于指示程序在运行。
2. 硬件资源
1)LED0、KEY0按键和蜂鸣器
BEEP | KEY0 | LED0 |
PC7 | PG3 | PI0 |
图25.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次。
图25.2.2. 2引脚部分原理图
从《STM32MP157A&D数据手册》中也可以查阅PC7的复用关系:
图25.2.2. 3 《STM32MP157A&D数据手册》部分截图
25.2.4 程序设计
1. 程序流程图
图25.2.3. 1实验程序流程图
2. 添加用户代码
(1)atim.h文件代码
/* PC7引脚定义 */
#define GTIM_TIMX_NPWM_CHY_GPIO_PORT GPIOC
#define GTIM_TIMX_NPWM_CHY_GPIO_PIN GPIO_PIN_7
#define GTIM_TIMX_NPWM_CHY_GPIO_AF GPIO_AF3_TIM8 /* AF功能选择 */
/* PC口时钟使能 */
#define GTIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE();}while(0)
#define GTIM_TIMX_NPWM TIM8 /* TIM8 */
#define GTIM_TIMX_NPWM_IRQn TIM8_UP_IRQn/* TIM8的中断号 */
#define GTIM_TIMX_NPWM_IRQHandler TIM8_UP_IRQHandler/* 中断服务函数 */
#define GTIM_TIMX_NPWM_CHY TIM_CHANNEL_2 /* 通道Y, 1<= Y <=4 */
#define GTIM_TIMX_NPWM_CHY_CCRX TIM8->CCR2 /* 通道Y的输出比较寄存器 */
/* TIMX 时钟使能 */
#define GTIM_TIMX_NPWM_CHY_CLK_ENABLE() do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0)
可以把上面的宏定义分成两部分,第一部分是PC7引脚定义、复用功能配置、使能GPIOC时钟。第二部分是TIM8通道2相关定义,同时使能TIM8时钟。
(2)atim.c文件
下面看atim.c的程序,首先是初始化函数。
TIM_HandleTypeDef g_timx_npwm_chy_handle; /* 定时器x句柄 */
TIM_OC_InitTypeDef g_timx_oc_npwm_chy_handle; /* 定时器输出句柄 */
/**
* @brief通用定时器TIMX 通道Y 输出指定个数PWM 初始化函数
* @note
通用定时器的时钟来自APB1,当D2PPRE1≥2分频的时候
通用定时器的时钟为APB1时钟的2倍, 而APB1为120M, 所以定时器时钟 = 240Mhz
定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
定时器工作频率,单位:Mhz
*
* @param自动重装值
* @param时钟预分频数
* @retval无
*/
void gtim_timx_npwm_chy_init(uint16_t arr,uint16_t psc)
{
g_timx_npwm_chy_handle.Instance = GTIM_TIMX_NPWM; /* 定时器x */
g_timx_npwm_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
/*向上计数模式*/
g_timx_npwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
g_timx_npwm_chy_handle.Init.Period = arr; /* 自动重装载值 */
/* 1分频 */
g_timx_npwm_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
/* 使能TIMx_ARR 寄存器进行缓冲 */
g_timx_npwm_chy_handle.Init.AutoReloadPreload = \ TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_PWM_Init(&g_timx_npwm_chy_handle);/* 初始化PWM */
g_timx_oc_npwm_chy_handle.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1 */
/* 设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为50% */
g_timx_oc_npwm_chy_handle.Pulse = arr/2;
/*输出比较极性为高*/
g_timx_oc_npwm_chy_handle.OCPolarity = TIM_OCPOLARITY_HIGH;
/* 配置TIM8通道2 */
HAL_TIM_PWM_ConfigChannel(&g_timx_npwm_chy_handle, \ &g_timx_oc_npwm_chy_handle, GTIM_TIMX_NPWM_CHY);
/*开启TIM8通道2*/
HAL_TIM_PWM_Start(&g_timx_npwm_chy_handle, GTIM_TIMX_NPWM_CHY);
__HAL_TIM_ENABLE_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE);/* 使能更新中断 */
HAL_NVIC_SetPriority(GTIM_TIMX_NPWM_IRQn, 1, 3);/*抢占优先级1,子优先级3 */
HAL_NVIC_EnableIRQ(GTIM_TIMX_NPWM_IRQn); /* 开启ITMx中断 */
}
/**
* @brief定时器底层驱动,时钟使能,引脚配置
此函数会被HAL_TIM_PWM_Init()调用
* @param定时器句柄
* @retval无
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == GTIM_TIMX_NPWM)
{
GPIO_InitTypeDef gpio_init_struct;
GTIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE();/* 开启通道y的GPIO时钟 */
GTIM_TIMX_NPWM_CHY_CLK_ENABLE();
gpio_init_struct.Pin = GTIM_TIMX_NPWM_CHY_GPIO_PIN;/* 通道y的GPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
/* 定时器x通道y的GPIO口复用 */
gpio_init_struct.Alternate = GTIM_TIMX_NPWM_CHY_GPIO_AF;
/* 初始化GPIO */
HAL_GPIO_Init(GTIM_TIMX_NPWM_CHY_GPIO_PORT, &gpio_init_struct);
}
}
HAL_TIM_PWM_Init初始化TIM8并设置TIM8的ARR和PSC等参数,然后通过调用函数HAL_TIM_PWM_ConfigChannel设置TIM8的通道2的PWM模式以及比较值等参数,最后通过调用函数HAL_TIM_PWM_Start来使能TIM8以及使能PWM通道TIM_CHANNEL_2输出。 因为我们用到定时器的溢出中断,所以还要通过__HAL_TIM_ENABLE_IT宏定义使能更新中断并设置中断优先级和开启中断。和通用定时器PWM输出实验一样,我们把通道的GPIO口初始化和定时器使能等程序放到回调函数HAL_TIM_PWM_MspInit中。
HAL_TIM_PWM_MspInit函数主要是初始化定时器输出通道的GPIO口,并且开启时钟,使能定时器。其中要注意IO口复用功能的选择一定要选对了。
下面我们看设置PWM个数的函数,其定义如下:
/* g_npwm_remain表示当前还剩下多少个脉冲要发送
每次最多发送256个脉冲
*/
static uint32_t g_npwm_remain = 0;
/**
* @brief通用定时器TIMX NPWM设置PWM个数
* @param的个数, 1~2^32次方个
* @retval无
*/
void gtim_timx_npwm_chy_set(uint32_t npwm)
{
if (npwm == 0)return ;
g_npwm_remain = npwm; /* 保存脉冲个数 */
GTIM_TIMX_NPWM->EGR |= 1 << 0; /* 产生一次更新事件,在中断里面处理脉冲输出 */
GTIM_TIMX_NPWM->CR1 |= 1 << 0;/* 使能定时器TIMX */
// __HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); /* 使能定时器TIMX */
}
gtim_timx_npwm_chy_set函数用于用户设置要发送的PWM波个数,每次进入此函数的时候都要先检查发送的脉冲个数npwm是否是0,是0的话则直接退出。g_npwm_remain用于保存用户设置的要发送的脉冲个数,此外,设置TIMx_EGR 寄存器的UG位为1,以使能更新中断,然后通过将TIMx_CR1寄存器的第0位CEN置1,即开启定时器,当然,开启定时器也可以使用HAL库的API函数来实现:
__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle);
下面我们来看看中断服务函数和回调函数:
1 /**
2 * @brief定时器中断服务函数
3 * @param无
4 * @retval无
5 */
6 void GTIM_TIMX_NPWM_IRQHandler(void)
7 {
8 HAL_TIM_IRQHandler(&g_timx_npwm_chy_handle);
9 }
10 /**
11 * @brief定时器更新中断回调函数
12 * @param定时器句柄指针
13 * @note此函数会被定时器中断函数共同调用的
14 * @retval无
15 */
16 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
17 {
18 if (htim == (&g_timx_npwm_chy_handle))
19 {
20 uint16_t npwm = 0;
21 if (g_npwm_remain > 256) /* 还有大于256个脉冲需要发送 */
22 {
23 g_npwm_remain=g_npwm_remain - 256;
24 npwm = 256;
25 }
26 else if (g_npwm_remain % 256) /* 还有位数(不到256)个脉冲要发送 */
27 {
28 npwm = g_npwm_remain % 256;
29 g_npwm_remain = 0; /* 没有脉冲了 */
30 }
31 if (npwm) /* 有脉冲要发送 */
32 {
33 /* 设置重复计数寄存器值为npwm-1, 即npwm个脉冲 */
34 GTIM_TIMX_NPWM->RCR = npwm - 1;
35 /* 产生一次更新事件,以更新RCR寄存器 */
36 GTIM_TIMX_NPWM->EGR |= 1 << 0;
37 GTIM_TIMX_NPWM->CR1 |= 1 << 0; /* 使能定时器TIMX */
38 /* 使能定时器TIMX */
39 //__HAL_TIM_ENABLE(&g_timx_npwm_chy_handle);
40 }
41 else
42 {
43 GTIM_TIMX_NPWM->CR1 &= ~(1 << 0); /* 关闭定时器TIMX */
44 }
45 /* 清除定时器溢出中断标志位 */
46 __HAL_TIM_CLEAR_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE);
47 }
48 }
第6~9行,当TIMx_RCR递减为0时,会产生更新中断,发生中断时,会执行中断服务函数GTIM_TIMX_NPWM_IRQHandler,中断服务函数会调用中断请求函数HAL_TIM_IRQHandler,中断请求函数HAL_TIM_IRQHandler中会判断中断的类型,判断是更新(溢出)中断,则调用更新中断回调函数HAL_TIM_PeriodElapsedCallback,此函数需要用户自行编写。我们来分析上面的代码:
第20行,npwm用于存放用户要设置的PWM波个数,每次进入此函数都先将其清零。
第21~25行,将PWM波个数分次写入重复计数器寄存器,g_npwm_remain是用户设置的PWM波个数,如果波形大于256个(也就是TIMx_RCR的值,这里设置为8位,如果想设置16位,则将256改为65536),则g_npwm_remain减去256(为剩下的要发送的个数),npwm被赋值256,也就是先将这256个PWM波发送出去,只要g_npwm_remain大于256,这段代码就会被执行。
第26~30,当剩下要发送的PWM波个数小于256时,直接将剩余的PWM波个数赋值给npwm,然后发送出去。
第31~40行,npwm表示有几个PWM波要发送,如果npwm大于0,表示有脉冲要发送。将npwm-1赋值给TIMx_RCR,当RCR寄存器从npwm-1递减为0的时候才会发生中断。在生成下一个重复更新事件之前,无论向TIMx_RCR寄存器写入何值都无影响,所以我们写完该寄存器后,如果要想马上生效就得手动更新事件,即对TIMx_EGR寄存器的UG位写1生成更新事件,并启动定时器。
第41~44行,如果没有脉冲要发送,则关闭TIM8。这里是直接将TIMx_CR1的第0位CEN清零,即禁止计数器。这里注意的是,不能使用__HAL_TIM_DISABLE来关闭TIM8,因为此宏会将TIM8的通道2的CC2E、CC2NE和CEN清零,程序运行后,在没有按下按键时,蜂鸣器就发声了。
第46行,清除定时器溢出中断标志位,每次进入中断都要清除此标志位,避免程序一直卡在重复进入中断中。
(3)main.c文件
1 #include "./SYSTEM/sys/sys.h"
2 #include "./SYSTEM/delay/delay.h"
3 #include "./SYSTEM/usart/usart.h"
4 #include "./BSP/LED/led.h"
5 #include "./BSP/BEEP/beep.h"
6 #include "./BSP/KEY/key.h"
7 #include "./BSP/TIMER/atim.h"
8 /**
9 * @brief主函数
10 * @param无
11 * @retval无
12 */
13 int main(void)
14 {
15 uint8_t key = 0;
16
17 HAL_Init(); /* 初始化HAL库 */
18 /* 初始化M4内核时钟,209M */
19 if(IS_ENGINEERING_BOOT_MODE())
20 {
21 sys_stm32_clock_init(34, 2, 2, 17, 6826);
22 }
23 usart_init(115200); /* 串口初始化为115200 */
24 delay_init(209); /* 延时初始化 */
25 led_init(); /* 初始化LED */
26 key_init(); /* 初始化按键 */
27 /* 10Khz的计数频率,2hz的PWM频率. */
28 gtim_timx_npwm_chy_init(5000 - 1, 20900 - 1);
29 /* 设置PWM占空比,50%,LED1有一半时间亮,一半时间灭,LED1亮灭一次,表示一个PWM波 */
30 GTIM_TIMX_NPWM_CHY_CCRX = 2500;
31 gtim_timx_npwm_chy_set(5); /* 输出5个PWM波(控制LED1闪烁5次) */
32
33 while(1)
34 {
35 key = key_scan(0);
36
37 if (key == KEY0_PRES) /* KEY0按下 */
38 {
39 gtim_timx_npwm_chy_set(5); /* 输出5个PWM波(控制LED2(BLUE)闪烁5次) */
40 }
41 delay_ms(10);
42 }
43 }
main.c文件中,第28行,开启PWM通道,使能定时器更新中断,并设置自动重载值为5000-1,频率为20900,即10KHz的计数频率,2Hz的PWM频率;
第30行,设置PWM的占空比为50%,或者直接使用HAL库的API函数来修改占空比:
__HAL_TIM_SET_COMPARE (&g_timx_npwm_chy_handle, TIM_CHANNEL_2, 3000);
第31行,输出5个PWM波形,所以进入main函数后,可以听到蜂鸣器发声5次;
第33~41行,按键扫描,如果是KEY0按下,则蜂鸣器发声5次。
25.2.5 编译和测试
进入Debug模式,点击运行按钮来运行调试,可以看到开发板底板的LED0灯在闪烁,说明程序已经再运行了。同时蜂鸣器响5次就停止了,按下底板的KEY0按键,蜂鸣器响5次以后就停止。
25.3 高级定时器输出比较模式实验
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验14-2 高级定时器-输出比较模式实验。
25.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。
图25.3.1. 1输出比较模式,翻转OC1时序图
可以通过翻转功能实现输出PWM波,PWM波频率由自动重载寄存器(TIMx_ARR)的值决定,其占空比也是可以由自动重载寄存器(TIMx_ARR)的值来决定,不过就得不断改变该寄存器的值,如果该寄存器的值一直都不改变,那占空比就为50%。此外我们还可以通过捕获/比较寄存器(TIMx_CCRx)的值改变PWM波的相位。它们生成PWM的原理如下图所示:
图25.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计数器所用的计数器时间。
25.3.2. 输出比较和PWM模式的差异
前面我们学习了PWM模式,与本节的输出比较模式,它俩有什么差别呢。
我们知道,在PWM模式下,输出的PWM波形频率由TIMx_ARR确定,高电平的时长由各个通道的TIMx_CCRx确定(即占空比),4个通道的频率是一致的,只有占空比是各自独立的;在输出比较模式下,频率由预装载寄存器(TIMx_ARR)的大小决定,此值越大,输出的频率越低,通道的初相位可以通过各通道的TIMx_CCRx来确定,通道工作在输出比较模式时,一个TIM至多可以提供4个不同的定时周期,每个通道的PWM波的频率、占空比互不干扰,完全独立。可以理解PWM模式是输出比较模式的特例。
25.3.3 TIM1/TIM8寄存器
高级定时器输出比较模式除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 之外。主要还用到以下这些寄存器,这些寄存器我们在前面有做过部分介绍:
1. 控制寄存器 1(TIMx_CR1)
TIM1/TIM8的控制寄存器1描述如图下图所示:
图25.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寄存器描述如图下图所示:
图25.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寄存器描述如下图所示:
图25.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寄存器为例,如下图:
图25.3.3. 4寄存器
对于高级定时器该寄存器16位有效位,在本实验中,我们通过改变该寄存器的值来改变PWM波的相位。在翻转功能模式下,当计数器的值等于该寄存器的值时,OC1REF会发生翻转,即输出的电平发生翻转。
5. TIM1/TIM8断路和死区寄存器(TIMx_ BDTR)
本实验用的是高级定时器,我们还需要配置:断路和死区寄存器(TIMx_BDTR),该寄存器各位描述如下图所示:
图25.3.3. 5寄存器
对于通用定时器,只需要配置以上提到的寄存器就够了,如果是高级定时器,我们还需要配置TIMx_ BDTR,该寄存器我们只关注位15[MOE],对此位清0,则禁止禁止 OC 和 OCN 输出,如果要想高级定时器的PWM正常输出,则必须设置MOE位为1,即使能 OC 和 OCN 输出。
25.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{
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}
25.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 |
表25.3.5. 1硬件资源
3. 原理图
如下图,底板的JP1排针上引出PE13和PE14,这两个引脚分别对应TIM1_CH3和TIM1_CH4。程序中通过配置这两个IO口输出不同相位的正弦波,通过示波器测试波形进行验证。
图25.3.5. 1原理图部分截图
25.3.6 程序设计
1. 程序流程图
图18.3.6. 1程序流程图
2. 添加用户代码
(1)atim.h文件代码
/* TIMX 输出比较模式 定义
这里通过TIM1的输出比较模式,控制PE13,PE14输出2路PWM,占空比50%,并且每一路PWM之间的相位差为25%
修改CCRx可以修改相位.
默认是针对TIM1/TIM8
注意: 通过修改这些宏定义,可以支持TIM1~TIM17任意一个定时器,任意一个IO口使用输出比较模式,输出PWM
*/
#define ATIM_TIMX_COMP_CH3_GPIO_PORT GPIOE
#define ATIM_TIMX_COMP_CH3_GPIO_PIN GPIO_PIN_13
#define ATIM_TIMX_COMP_CH3_GPIO_AF GPIO_AF1_TIM1 /* AF功能选择 */
#define ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE() \ do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define ATIM_TIMX_COMP_CH4_GPIO_PORT GPIOE
#define ATIM_TIMX_COMP_CH4_GPIO_PIN GPIO_PIN_14
#define ATIM_TIMX_COMP_CH4_GPIO_AF GPIO_AF1_TIM1 /* AF功能选择 */
#define ATIM_TIMX_COMP_CH4_GPIO_CLK_ENABLE() \ do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define ATIM_TIMX_COMP TIM1
#define ATIM_TIMX_COMP_CH3_CCRX TIM_CHANNEL_3 /* 通道3的输出比较寄存器 */
#define ATIM_TIMX_COMP_CH4_CCRX TIM_CHANNEL_4 /* 通道4的输出比较寄存器 */
/* TIM1 时钟使能 */
#define ATIM_TIMX_COMP_CLK_ENABLE() do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0)
/***********************************函数声明*******************************/
/* 高级定时器 输出比较模式输出PWM 初始化函数 */
void atim_timx_comp_pwm_init(uint16_t arr, uint16_t psc);
void atim_timx_comp_pwm_gpio_init(void); /* 定时器通道引脚初始化函数 */
以上宏定义主要是定时器1输出通道3和通道4对应的IO口的宏定义,包括引脚定义、AF功能选择和GPIO时钟使能以及通道3和通道4的输出比较寄存器宏定义和TIM1时钟使能。
下面看atim.c的程序。
(2)atim.c文件代码
1 TIM_HandleTypeDef g_timx_comp_pwm_handle; /* 定时器x句柄 */
2 /**
3 * @brief高级定时器TIMX 输出比较模式 初始化函数(使用输出比较模式)
4 * @note
5 配置高级定时器TIMX 2路输出比较模式PWM输出,实现50%占空比,不同相位控制
6注意,本例程输出比较模式,每2个计数周期才能完成一个PWM输出,因此输出频率减半
7另外,我们还可以开启中断在中断里面修改CCRx,从而实现不同频率/不同相位的控制
8 但是我们不推荐这么使用,因为这可能导致非常频繁的中断,从而占用大量CPU资源
9 *
10高级定时器的时钟来自APB2,当APB2DIV≥2分频的时候
11 高级定时器的时钟为APB2时钟的2倍, 而APB2为104.5M, 所以定时器时钟 = 209Mhz
12 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
13 定时器工作频率,单位:Mhz
14 *
15 * @param自动重装值。
16 * @param时钟预分频数
17 * @retval无
18 */
19 void atim_timx_comp_pwm_init(uint16_t arr, uint16_t psc)
20 {
21 TIM_ClockConfigTypeDef sClockSourceConfig = {0};
22 TIM_MasterConfigTypeDef sMasterConfig = {0};
23 TIM_OC_InitTypeDef sConfigOC = {0};
24 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
25
26 atim_timx_comp_pwm_gpio_init();
27 ATIM_TIMX_COMP_CLK_ENABLE();
28
29 g_timx_comp_pwm_handle.Instance = ATIM_TIMX_COMP; /* 定时器1 */
30 g_timx_comp_pwm_handle.Init.Prescaler = psc ; /* 定时器分频 */
31 g_timx_comp_pwm_handle.Init.CounterMode=TIM_COUNTERMODE_UP;/* 向上计数 */
32 g_timx_comp_pwm_handle.Init.Period = arr; /* 自动重装载值 */
33 g_timx_comp_pwm_handle.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;/*不分频*/
34 g_timx_comp_pwm_handle.Init.RepetitionCounter = 0; /* 重复计数器寄存器为0 */
35 /* 不使能影子寄存器TIMx_ARR */
36 g_timx_comp_pwm_handle.Init.AutoReloadPreload = \ TIM_AUTORELOAD_PRELOAD_DISABLE;
37 HAL_TIM_Base_Init(&g_timx_comp_pwm_handle); /* 初始化定时器时基 */
38 /* 使用内部时钟 */
39 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
40 /* 时钟源配置 */
41 HAL_TIM_ConfigClockSource(&g_timx_comp_pwm_handle, &sClockSourceConfig);
42
43 HAL_TIM_OC_Init(&g_timx_comp_pwm_handle); /* 输出比较模式初始化 */
44 /* 定时器主从模式配置,这里我们没有用到主从模式 */
45 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
46 sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
47 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
48 HAL_TIMEx_MasterConfigSynchronization(&g_timx_comp_pwm_handle, \ &sMasterConfig);
49 /* 定时器通道3和通道4的配置 */
50 sConfigOC.OCMode = TIM_OCMODE_TOGGLE; /* 输出比较模式翻转功能 */
51 sConfigOC.Pulse = 500-1; /* 设置通道3输出比较寄存器的值为500-1 */
52 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
53 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; /* 互补输出比较极性位高 */
54 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; /* 失能输出比较快速模式 */
55 /* 选择空闲状态下非工作状态 */
56 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
57 /* 设置空闲状态下非工作状态 */
58 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
59 /* 初始化定时器的输出比较通道3 */
60 HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &sConfigOC, \ TIM_CHANNEL_3);
61 /* 通道3预装载使能 */
62 __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
63
64 sConfigOC.Pulse = 250-1; /* 设置通道4输出比较寄存器的值为250-1 */
65 /* 初始化定时器的输出比较通道4 */
66 HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &sConfigOC, \ TIM_CHANNEL_4);
67 /* 通道4预装载使能 */
68 __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
69 /* 设置运行模式下非工作状态选项 */
70 sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
71 sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
72 sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; /* 锁电平参数 */
73 sBreakDeadTimeConfig.DeadTime = 0; /* 死区时间设置为0 */
74 /* 失能TIMx刹车输入 */
75 sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
76 /* TIM1刹车输入管脚极性为高电平有效 */
77 sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
78 sBreakDeadTimeConfig.BreakFilter = 0; /* TIM1刹车输入滤波为0 */
79 /* TIM1刹车输入BRK2失能 */
80 sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
81 /* 刹车输入BRK2高电平有效 */
82 sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
83 sBreakDeadTimeConfig.Break2Filter = 0; /* TIM1刹车输入2滤波为0 */
84 /* 失能自动输出功能,只能后期手动通过软件设置MOE */
85 sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
86 /* 配置间隔功能,停滞时间,锁定级别 */
87 HAL_TIMEx_ConfigBreakDeadTime(&g_timx_comp_pwm_handle, \ &sBreakDeadTimeConfig);
88 }
atim_timx_comp_pwm_init函数用于配置TIM1的时基参数和通道3以及通道4的参数,时基配置中,配置定时器预分频值为psc、向上计数模式、自动重装载值为arr、时钟不分频、高级定时器的TIMx_RCR值为0,定时器1的时钟为2倍APB2,即频率为209MHZ,如果配置psc的值为999。由前面基本定时器讲解的定时器溢出公式得定时器溢出的周期:
结合前面讲的PWM波的周期是2倍的ARR计数器所用的计数器时间,所以得到PWM波的周期是2ms,频率为500HZ。
通道3输出比较寄存器的值为500-1,初相位为50%;通道4输出比较寄存器的值为250-1,初相位为25%。
相位=
第62和68行,通道3和4的输出比较预加载项选择Enable,即在定时器工作时是否能修改Pulse的值,如果禁用此项,表示定时器工作时不能进行修改,只能等到更新事件到来的时候才能进行修改,所以这里选择使能。第26行,调用atim_timx_comp_pwm_gpio_init函数完成GPIO相关初始化,我们来看看此函数:
/**
* @brief定时器输出比较模式通道引脚初始化函数
* @param无
* @note此函数会被atim_timx_comp_pwm_init()函数调用
* @retval无
*/
void atim_timx_comp_pwm_gpio_init(void)
{
GPIO_InitTypeDef gpio_init_struct = {0};
ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE(); /* 使能GPIOE时钟 */
ATIM_TIMX_COMP_CH4_GPIO_CLK_ENABLE();
gpio_init_struct.Pin = ATIM_TIMX_COMP_CH3_GPIO_PIN; /* PE13 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_NOPULL; /* 无上/下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 速度等级为高 */
gpio_init_struct.Alternate = ATIM_TIMX_COMP_CH3_GPIO_AF;/* 复用选择为AF1 */
/* 初始化GPIOE */
HAL_GPIO_Init(ATIM_TIMX_COMP_CH3_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_COMP_CH4_GPIO_PIN; /* PE14 */
gpio_init_struct.Alternate = ATIM_TIMX_COMP_CH4_GPIO_AF;/* 复用选择为AF1 */
/* 初始化GPIOE */
HAL_GPIO_Init(ATIM_TIMX_COMP_CH4_GPIO_PORT, &gpio_init_struct);
}
以上函数比较简单,主要完成GPIO相关引脚的初始化。下面我们看看main.c文件的代码。
(3)main.c文件代码
1 extern TIM_HandleTypeDef g_timx_comp_pwm_handle;
2
3 /**
4 * @brief主函数
5 * @param无
6 * @retval无
7 */
8 int main(void)
9 {
10 HAL_Init(); /* 初始化HAL库 */
11 /* 初始化M4内核时钟,209M */
12 if(IS_ENGINEERING_BOOT_MODE())
13 {
14 sys_stm32_clock_init(34, 2, 2, 17, 6826);
15 }
16 delay_init(209); /* 延时初始化 */
17 led_init(); /* 初始化LED */
18 atim_timx_comp_pwm_init(1000-1, 209-1);/* 1Mhz的计数频率1Khz的周期 */
19 /* 启动定时器1通道3 */
20 HAL_TIM_OC_Start(&g_timx_comp_pwm_handle,TIM_CHANNEL_3);
21 /* 启动定时器1通道4 */
22 HAL_TIM_OC_Start(&g_timx_comp_pwm_handle,TIM_CHANNEL_4);
23
24 while(1)
25 {
26 LED0_TOGGLE(); /* LED0(RED)闪烁 */
27 delay_ms(500); /* 延时500ms */
28 //HAL_Delay(500); /* 延时500ms */
29 }
30 }
第18行,配置TIM1的自动重装载值为1000-1,配置定时器预分频值为209-1;
第20和22行,启动定时器1的通道3和通道4;
第26~27行,LED0每隔500ms闪烁一次,也可以使用第28行的HAL_Delay延时函数实现毫秒级别的延时。
25.3.7 编译和测试
本次实验需要使用示波器来验证。找4根一边是公头一边是母头的杜邦线,两根杜邦线分别接在JP1排针引出的PE13和PE14上,两根杜邦线接在板子引出的地线上,例如底板的JP7和JP8排针处有引出地线,如下图:
图25.3.7.1连接好PWM输出排针
将引出的线接入到示波器的两个通道,用于测量TIM1的通道3和通道4的波形。本实验笔者使用正点原子自主研发的DS100 Mini数字示波器,此示波器支持两个通道,屏幕分辨率为480*320,采样率为250MSa/S,可以保存波形:
图25.3.7.2正点原子DS100 Mini数字示波器
开发板接好线,拨码开关拨成MCU启动方式,然后开发板上电。进入Debug模式,点击运行按钮来运行调试,可以看到开发板底板的LED0灯在闪烁,说明程序已经在运行了。如下图,测试出波形周期为2ms,频率为500Hz,和我们前面计算的一致:
图25.3.7.3波形周期
ARR值为固定为1000-1,所以占空比则固定为50%。通道3和通道4我们分别配置为500-1和250-1,即两者的相位分别为50%和25%,两者下降沿或者上升沿相位差为25%(即250us),如下图所示,绿色波形是PE13引脚输出的,黄色波形是PE14引脚输出的:
图25.3.7.4两波形相位差
25.4 高级定时器互补输出带死区控制实验
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验14-3 高级定时器-互补输出带死区控制实验。
25.4.1 互补输出和死区时间
本小节我们来学习如何使用高级定时器的互补输出和死区插入功能。
1. 互补输出
定时器的PWM互补输出很好理解,也就是两个PWM波形是互补的状态,例如,如果CHx输出高电平,则CHxN输出低电平,两者的PWM波形是互补输出的特性。如下图,OCXREF是参考基准信号,OCx和OCxN是两个通道CHx和CHxN的输出,即主输出OCx或互补输出OCxN,OCxN与OCXREF时序相位同步,OCXN信号与OCXREF时序反相同步,两个输出波形互补:
图25.4.1. 1定时器的PWM互补输出波形
2. 死区时间
说到互补输出,还会涉及到一个死区时间的概念,死区时间可以理解为某个处于相对无效状态的时间或空间。死区时间在步进电机、伺服电机、逆变器和开关电源中使用的比较多,例如下图是一个逆变器电机驱动原理示意图红框的部分是两组桥,每组桥由功率器件组成,每组桥的上半部分是上桥(S1和S3),下半部分是下桥(S2和S4),每组桥的上半桥和下半桥是不能同时导通的,否则电源会通过上下两个桥形成短路,烧毁功率器件。
图25.4.1. 2逆变器电机驱动原理示意图
在高速的PWM驱动信号在达到功率元件的控制极时,会由于各种各样的原因产生延迟,从而造成某个半桥不能立马关闭,会延迟一段时间才真正关闭,这个时候就可能会与另外一个半桥处于同时开启的状态,从而造成短路。死区时间就是在上半桥关断后,延迟一段时间再打开下半桥,或者在下半桥关断后,延迟一段时间再打开上半桥,从而避免两个桥同时开启的状态。所延迟的这段延迟时间就是死区时间,即上、下半桥的元件都是关断的状态。关于桥大家可不必深究,我们引出桥只是为了说明死区时间。
如下图,OCXREF是参考基准信号,OCx和OCxN是两个通道CHx和CHxN的输出,OCxN与OCXREF时序相位同步,只是其上升沿相对OCXREF上升沿存在延迟,OCXN信号与OCXREF时序反相同步,并且其上升沿相对OCXREF下降沿存在延迟,这个延迟时间就是插入的死区时间:
图25.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) |
表25.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,到后面下载验证小节,我们通过示波器验证一下这个死区时间和我们这里的计算值是否正确。
关于此寄存器的各位我们会在下面的寄存器小节介绍。
25.4.2 TIM1/TIM8寄存器
高级定时器互补输出带死区控制除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 之外。主要还用到以下这些寄存器:
1. 控制寄存器 1(TIMx_CR1)
TIM1/TIM8的控制寄存器1描述如下图所示:
图25.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寄存器描述如下图所示:
图25.4.2. 2寄存器
该寄存器的有些位在不同模式下,功能不一样,我们现在用到输出比较模式。
本实验我们用到了定时器1输出比较的通道3,所以我们需要配置TIMx_CCMR2模式设置位OC3M[3:0],我们使用的是PWM模式1,所以这4位必须设置为0110。除此之外,我们还要设置输出比较的预装载使能位,通道3对应输出比较的预装载使能位OC3PE置1。
3. 捕获/比较使能寄存器(TIMx_ CCER)
TIM1/TIM8的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关和极性。TIMx_CCER寄存器描述如下图所示:
图25.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寄存器描述如下图所示:
图25.4.2. 4寄存器
对于高级定时器该寄存器16位有效位,和通用定时器PWM输出实验一样,在PWM 模式1的情况下,我们通过改变该寄存器的值来改变PWM波的占空比。
5. TIM1/TIM8断路和死区寄存器(TIMx_ BDTR)
TIM1/TIM8断路和死区寄存器,该寄存器各位描述如下图所示:
图25.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,到后面下载验证小节,我们通过示波器验证一下这个死区时间和我们这里的计算值是否正确。
25.4.3 定时器的HAL库驱动
定时器在HAL库中的驱动代码在前面已经介绍了部分,这里我们再介绍几个本实验用到的函数。
1. HAL_TIMEx_ConfigBreakDeadTime函数
定时器的断路和死区时间配置初始化函数,其声明如下:
HAL_StatusTypeDef HAL_TIMEx_ConfigBreakDeadTime(TIM_HandleTypeDef *htim,
TIM_BreakDeadTimeConfigTypeDef *sBreakDeadTimeConfig);
- 函数描述:用于初始化定时器的断路(即刹车)和死区时间。
- 函数形参:形参1是TIM_HandleTypeDef结构体类型指针变量,基本定时器的时候已经介绍。形参2是TIM_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);
- 函数描述:该函数用于启动定时器的互补输出。
- 函数形参:形参1是TIM_HandleTypeDef结构体类型指针变量,用于配置定时器基本参数。
形参2是定时器通道,范围:TIM_CHANNEL_1到TIM_CHANNEL_6。 - 函数返回值:
HAL_StatusTypeDef枚举类型的值。
25.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 |
表25.4.4. 1硬件资源
3. 原理图
TIM1的两个通道如下:
图25.4.4. 1TIM1两个通道部分原理图
对于PA6用做断路输入,我们会配置PA6为低电平时就会进行刹车,即停止输出PWM波。PA6引脚在底板的CAMERA接口有用到,我们需要用杜邦线,将低电平引入DCMI_PIXCLK就可以给PA6提供低电平了:
图25.4.4. 2断路输入部分原理图
25.4.5 程序设计
1. 程序流程图
图25.4.5. 1程序流程图
2. 添加用户代码
(1)atim文件代码
/* TIMX 互补输出模式 定义
这里设置互补输出相关硬件配置, CHY即正常输出, CHYN即互补输出
修改CCRx可以修改占空比.
默认是针对TIM1/TIM8
*/
/* 输出通道引脚 */
#define ATIM_TIMX_CPLM_CHY_GPIO_PORT GPIOE
#define ATIM_TIMX_CPLM_CHY_GPIO_PIN GPIO_PIN_12
#define ATIM_TIMX_CPLM_CHY_GPIO_AF GPIO_AF1_TIM1 /* AF功能选择 */
/* GPIOA口时钟使能 */
#define ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE() \ do{__HAL_RCC_GPIOE_CLK_ENABLE();}while(0)
/* 互补输出通道引脚 */
#define ATIM_TIMX_CPLM_CHYN_GPIO_PORT GPIOE
#define ATIM_TIMX_CPLM_CHYN_GPIO_PIN GPIO_PIN_13
#define ATIM_TIMX_CPLM_CHYN_GPIO_AF GPIO_AF1_TIM1 /* AF功能选择 */
/* GPIOA口时钟使能 */
#define ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE() \ do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)
/* 刹车输入引脚 */
#define ATIM_TIMX_CPLM_BKIN_GPIO_PORT GPIOA
#define ATIM_TIMX_CPLM_BKIN_GPIO_PIN GPIO_PIN_6
#define ATIM_TIMX_CPLM_BKIN_GPIO_AF GPIO_AF1_TIM1 /* AF功能选择 */
/* GPIOA口时钟使能 */
#define ATIM_TIMX_CPLM_BKIN_GPIO_CLK_ENABLE() \ do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
/* 互补输出使用的定时器 */
#define ATIM_TIMX_CPLM TIM1
#define ATIM_TIMX_CPLM_CHY GPIO_AF1_TIM1
#define ATIM_TIMX_CPLM_CHY_CCRY TIM_CHANNEL_3
/* TIM1 时钟使能 */
#define ATIM_TIMX_CPLM_CLK_ENABLE() \ do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0)
/* 高级定时器 互补输出 初始化函数 */
void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc);
/* 定时器互补输出模式通道引脚初始化函数 */
void atim_timx_cplm_pwm_gpio_init(void);
以上宏定义中,定义PE13、PE12和PA6相关宏,并定义PE13、PE12的复用功能选择为TIM1_CH3和TIM1_CH3N,同时定义TIM1和GPIO相关时钟使能宏定义。
(2)atim.c文件代码
1 TIM_HandleTypeDef g_timx_cplm_pwm_handle; /* 定时器x句柄 */
2 /**
3 * @brief高级定时器TIMX 互补输出 初始化函数(使用PWM模式1)
4 * @note
5配置高级定时器TIMX 互补输出, 一路OC3 一路OC3N, 并且可以设置死区时间
6高级定时器的时钟来自APB2,当APB2DIV≥2分频的时候
7 高级定时器的时钟为APB2时钟的2倍, 而APB2为104.5M, 所以定时器时钟 = 209Mhz
8 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
9 定时器工作频率,单位:Mhz
10 *
11 * @param自动重装值。
12 * @param时钟预分频数
13 * @retval无
14 */
15 void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc)
16 {
17 TIM_ClockConfigTypeDef sClockSourceConfig = {0};
18 TIM_MasterConfigTypeDef sMasterConfig = {0};
19 TIM_OC_InitTypeDef sConfigOC = {0};
20 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
21
22 atim_timx_cplm_pwm_gpio_init(); /* GPIO初始化 */
23 ATIM_TIMX_CPLM_CLK_ENABLE(); /* TIM1 时钟使能 */
24
25 g_timx_cplm_pwm_handle.Instance = TIM1; /* 定时器1 */
26 g_timx_cplm_pwm_handle.Init.Prescaler = psc; /* 定时器分频 */
27 /* 向上计数模式*/
28 g_timx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
29 g_timx_cplm_pwm_handle.Init.Period = arr; /* 自动重装载值 */
30 /* 4分频
31 g_timx_cplm_pwm_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;
32 g_timx_cplm_pwm_handle.Init.RepetitionCounter = 0; /* 重复计数器寄存器为0 */
33 /* 使能影子寄存器TIMx_ARR */
34 g_timx_cplm_pwm_handle.Init.AutoReloadPreload = \ TIM_AUTORELOAD_PRELOAD_ENABLE;
35 HAL_TIM_Base_Init(&g_timx_cplm_pwm_handle); /* 初始化定时器时基 */
36
37 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;/* 使用内部时钟 */
38 /* 时钟源配置 */
39 HAL_TIM_ConfigClockSource(&g_timx_cplm_pwm_handle, &sClockSourceConfig);
40 HAL_TIM_PWM_Init(&g_timx_cplm_pwm_handle) ; /* PWM模式初始化 */
41 /* 定时器主从模式配置,这里我们没有用到主从模式 */
42 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
43 sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
44 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
45 HAL_TIMEx_MasterConfigSynchronization(&g_timx_cplm_pwm_handle, \ &sMasterConfig);
46
47 sConfigOC.OCMode = TIM_OCMODE_PWM1; /* PWM模式1 */
48 sConfigOC.Pulse = 300; /* 设置通道3的占空比为30% */
49 sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW; /* 通道3输出极性为低 */
50 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW; /*互补输出极性为低 */
51 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; /* 不使用快速模式 */
52 /* 设置通道3的空闲状态下为低电平 */
53 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
54 /* 设置互补输出通道的空闲状态下为高电平 */
55 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_SET;
56 /* 初始化定时器的通道3 PWM输出 */
57 HAL_TIM_PWM_ConfigChannel(&g_timx_cplm_pwm_handle, &sConfigOC, \ ATIM_TIMX_CPLM_CHY_CCRY);
58 /* 运行模式下“关闭状态”选择,这里选择Disable,即关闭 */
59 sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
60 /* 即空闲模式下“关闭状态”选择,这里选择Disable,即关闭 */
61 sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
62 /* 锁定设置选择Off,即关闭 */
63 sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
64 /* 配置死区延时时间,这里配置死区发生器为十进制数的100 */
65 sBreakDeadTimeConfig.DeadTime = 100;
66 /* 用于配置刹车状态,这里配置为Enable,即打开刹车 */
67 sBreakDeadTimeConfig.BreakState = TIM_BREAK_ENABLE;
68 /* 用于配置刹车极性,这里配置为Low,即刹车电平为低 */
69 sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_LOW;
70 sBreakDeadTimeConfig.BreakFilter = 0;/* 不使用滤波 */
71 /* 没有使用到刹车输入2,这里处于关闭状态 */
72 sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
73 sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_LOW;
74 sBreakDeadTimeConfig.Break2Filter = 0;
75 /* 用于配置自动输出状态,这里选择使能Enable */
76 sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE;
77 /* 配置刹车功能 */
78 HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, \ &sBreakDeadTimeConfig);
79 /* 配置通道3开始生成PWM信号 */
80 HAL_TIM_PWM_Start(&g_timx_cplm_pwm_handle, ATIM_TIMX_CPLM_CHY_CCRY);
81 /* 在互补输出上开始PWM信号生成 */
82 HAL_TIMEx_PWMN_Start(&g_timx_cplm_pwm_handle,ATIM_TIMX_CPLM_CHY_CCRY);
83 }
函数atim_timx_cplm_pwm_init可通过配置参数arr和psc完成自动重载值和时钟预分频数的配置,main函数中通过调用sys_stm32_clock_init函数配置APB2的时钟为209MHz,即TIM1的时钟为209MHz。代码中我们已经附上了详细的注释,可以很快理解代码。
第25~35行,主要是TIM1的时基初始化部分;
第31行,配置4分频,所以f DTS = f CK_INT;
第47行,配置为PWM模式1;
第48行,配置通道3占用比为30%,所以互补输出通道的占空比为70%;
第65行,设置死区发生器为十进制数的100;
第67行,打开刹车;
第69行,设置刹车电平为低电平有效,也就是给断路输入引脚为低电平时开始刹车,通道不输出PWM波。
第80行,HAL_TIM_PWM_Start是定时器的PWM输出启动函数,此API函数我前面就有介绍到,这里是开启通道3的PWM输出;
第82行,调用HAL_TIMEx_PWMN_Start函数启动互补输出通道生成PWM波;
第22行,调用atim_timx_cplm_pwm_gpio_init函数完成相关引脚化,我们来看看该函数:
/**
* @brief定时器互补输出模式通道引脚初始化函数
* @param无
* @note此函数会被atim_timx_cplm_pwm_init()函数调用
* @retval无
*/
void atim_timx_cplm_pwm_gpio_init(void)
{
GPIO_InitTypeDef gpio_init_struct = {0};
ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE(); /* 通道X对应IO口时钟使能 */
ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE(); /* 通道X互补通道对应IO口时钟使能 */
ATIM_TIMX_CPLM_BKIN_GPIO_CLK_ENABLE(); /* 通道X刹车输入对应IO口时钟使能 */
gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHY_GPIO_PIN; /* 指定引脚为12 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH ; /* 速度等级为高 */
gpio_init_struct.Alternate = ATIM_TIMX_CPLM_CHY_GPIO_AF; /* 复用选择为AF1 */
/* 初始化GPIOE */
HAL_GPIO_Init(ATIM_TIMX_CPLM_CHY_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHYN_GPIO_PIN; /* 指定引脚为13 */
gpio_init_struct.Alternate = ATIM_TIMX_CPLM_CHYN_GPIO_AF;/* 复用选择为AF1 */
/* 初始化GPIOE */
HAL_GPIO_Init(ATIM_TIMX_CPLM_CHYN_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = ATIM_TIMX_CPLM_BKIN_GPIO_PIN; /* 指定引脚为6 */
gpio_init_struct.Alternate = ATIM_TIMX_CPLM_BKIN_GPIO_AF;/* 复用选择为AF1 */
/* 初始化GPIOE */
HAL_GPIO_Init(ATIM_TIMX_CPLM_BKIN_GPIO_PORT, &gpio_init_struct);
}
(3)main.c文件代码
main.c文件的部分代码如下:
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
/* 初始化M4内核时钟,209M */
if(IS_ENGINEERING_BOOT_MODE())
{
sys_stm32_clock_init(34, 2, 2, 17, 6826);
}
delay_init(209); /* 延时初始化 */
led_init(); /* 初始化LED */
/* 10Mhz的计数频率 1Khz的周期 */
atim_timx_cplm_pwm_init(1000 - 1, 209 - 1);
while(1)
{
LED0_TOGGLE(); /* LED0翻转 */
delay_ms(500); /* 延时500ms */
//HAL_Delay(500); /* 延时500ms */
}
}
25.4.6 编译和运行
本次实验需要使用示波器来验证。找4根一边是公头一边是母头的杜邦线,两根杜邦线分别接在JP1排针引出的PE13和PE12上,两根杜邦线接在板子引出的地线上,例如底板的JP7和JP8排针处有引出地线(注意,是地线,不要接错5V或者3.3V),如下图:
图25.4.5. 2连接好PWM输出排针
将引出的线接入到示波器的两个通道,用于测量TIM1的通道3和互补输出通道的波形。开发板接好线,拨码开关拨成MCU启动方式,然后开发板上电。进入Debug模式,点击运行按钮
来运行调试,可以看到开发板底板的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相近,有误差是正常的。
图25.4.5. 3PWM互补输出波形
下面我们验证刹车功能。找一根一边是公头一边是母头的杜邦线,将底板上的JP7的地线(注意,是地线,不要接错5V或者3.3V)接到CAMERA接口的pclk口(即PA),如下图所示:
图25.4.5. 4刹车输入引脚接线
接好线以后,PA6输入了低电平,因为我们在刹车功能配置中设置BRK Polarity为Low,即刹车电平为低,可以发现通道3和互补通道停止输出PWM波:
图25.4.5. 5执行刹车后的波形
因为我们程序中配置自动输出状态为Enable,使能了AOE位,即允许刹车后自动恢复输出,所以当停止给PA6接入低电平(拔掉之前连接在CAMERA上的杜邦线),PWM波会恢复输出。
25.5 高级定时器PWM输入模式实验
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验14-4 高级定时器-PWM输入模式实验。
25.5.1 PWM输入模式
本小节我们来学习如何使用高级定时器PWM输入模式,此模式是输入捕获模式的一个特例,PWM输入模式经常被应用于测量PWM波脉宽和频率。
在前面的通用定时器输入捕获实验中只用到了一个捕获寄存器来测量脉宽,实验中是先捕获到上升沿,将计数器TIMx_CNT的值锁存到捕获寄存器TIMx_CCRx中,然后设置为下降沿捕获,当捕获到下降沿时,再将TIMx_CNT的值记录到TIMx_CCRx中,然后设置为上升沿捕获,如此反复,上升沿和下降沿两次得到的捕获值之差可以用于计算高电平脉冲,根据占空比也可以计算出脉冲的周期。
图25.5.1. 1通用定时器输入捕获实验捕获原理示意图
本章节的PWM输入模式需要用到两个捕获寄存器进行测量,两个捕获通道ICx会被映射至同一个 TIx 输入,这两个 ICx 信号在边沿处有效,但极性相反。输入通道和捕获通道的映射关系如下:
图25.5.1. 2输入通道和捕获通道的映射关系
注意:当我们使用 PWM 输入模式的时候,因为一个输入通道(TIx)会占用两个捕获通道(ICx),即捕获通道IC1和IC2为一组,捕获通道IC3和IC4为一组。因此一个定时器在使用 PWM 输入的时候最多只能使用两个输入通道(TIx)。
本实验配置定时器8在PWM输入模式下工作,然后向通道1输入PWM波,并对其进行测量脉宽和频率。下面我们用一个时序图来描述PWM输入模式的工作原理。
图25.5.1. 3输入模式时序
实验过程原理如下:首先将从模式控制器配置为复位模式,即向TIMx_SMCR寄存器中的SMS[3:0]位写入0100。复位模式下,当我们启动触发信号开始进行捕获的时候,在出现所选触发极性检测信号时,计数器 CNT复位清零并生成一个寄存器更新事件。
我们定义一个PWM波的起点是上升沿,结束点是下一个上升沿(该上升沿也是下一个PWM波的起点),从这个上升沿到下一个上升沿所经过的时间就是该PWM波的周期;PWM波的上升沿到下一个下降沿所经过的时间就是此PWM波的正脉宽长度,下降沿再到下一个上升沿所经过的时间就是负脉宽时间长度。根据这些信息就可以计算出PWM波的周期和频率以及占空比。
PWM波输入到定时器1通道1(TI1)后,信号会被检测是上升沿还是下降沿,比如IC1被设置为上升沿检测,IC2被设置为下降沿检测,那么TI1FP1这一路通道是测量周期,另外一路通道TI1FP2则是测量占空比。
25.5.2 TIM1/TIM8寄存器
高级定时器PWM输入模式实验除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 之外。主要还用到以下这些寄存器:
1. 从模式控制寄存器(TIMx_SMCR)
TIM1/TIM8的从模式控制寄存器描述如下图所示:
图25.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寄存器描述如下图所示:
图25.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寄存器描述如下图所示:
图25.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寄存器:
图25.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/中断使能寄存器描述如下图所示:
图25.5.2. 5寄存器
该寄存器位0(UIE)用于使能或者禁止更新中断,因为本实验我们用到中断,所以该位需要置1。位1(CC1IE)用于使能或者禁止捕获/比较1中断,我们也要用到,所以该位需要置1。
25.5.3 硬件设计
1. 例程功能
首先通过TIM5_CH4 (PI0)输出PWM波。然后把PB5输出的PWM波用杜邦线接入PI5(定时器8通道1),最后通过串口打印PWM波的脉宽和频率等信息。和前面程序一样,我们使LED0闪烁来提示程序正在运行。
2. 硬件设计
1)
LED0 | TIM5_CH4 | UART4_TX | UART4_RX | TIM8_CH1 |
PI0 | PI0 | PG11 | PB2 | PI5 |
表25.5.3. 1硬件资源
2)定时器8输出通道1(TIM8_CH1)
定时器属于STM32MP157的内部资源,只需要软件设置好即可正常工作。
3. 原理图
开发板底板的JP1排针引出了PI0和PI5引脚,这两个引脚可分别当做TIM5_CH4和TIM8_CH1:
图25.5.3. 1引脚部分原理图
25.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. 程序流程图
下面看看本实验的程序流程图:
图25.5.4. 1高级定时器PWM输入模式实验程序流程图
2. 添加用户代码
高级定时器驱动代码我们都把放在atim.c和atim.h文件中,所以高级定时器PWM输入模式实验的代码在高级定时器互补输出带死区控制实验的代码后面追加。
首先看atim.h头文件的几个宏定义:
(1)atim.h头文件代码
/* TIMX PWM输入模式 定义
这里的输入捕获使用定时器TIM8_CH1,
默认是针对TIM1/TIM8等高级定时器
*/
#define ATIM_TIMX_PWMIN_CHY_GPIO_PORT GPIOI
#define ATIM_TIMX_PWMIN_CHY_GPIO_PIN GPIO_PIN_5
#define ATIM_TIMX_PWMIN_CHY_GPIO_AF GPIO_AF3_TIM8 /* AF功能选择 */
/* PI口时钟使能 */
#define ATIM_TIMX_PWMIN_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOI_CLK_ENABLE(); }while(0)
#define ATIM_TIMX_PWMIN TIM8
#define ATIM_TIMX_PWMIN_IRQn TIM8_UP_IRQn
#define ATIM_TIMX_PWMIN_IRQHandler TIM8_UP_IRQHandler
#define ATIM_TIMX_PWMIN_CHY TIM_CHANNEL_1 /* 通道1 */
/* TIM8 时钟使能 */
#define ATIM_TIMX_PWMIN_CHY_CLK_ENABLE() \ do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0)
/* TIM1 / TIM8 有独立的捕获中断服务函数,需要单独定义,对于TIM2~5/TIM12/TIM15等,则不需要以下定义 */
#define ATIM_TIMX_PWMIN_CC_IRQn TIM8_CC_IRQn
#define ATIM_TIMX_PWMIN_CC_IRQHandler TIM8_CC_IRQHandler
以上是是定时器1通道1对应的IO口的宏定义和定时器1的相应宏定义,另外针对TIM1/ TIM8有独立的捕获中断服务函数,我们后面会单独定义。
(2)atim.c文件代码
关于定时器5的初始化代码在gtim.c文件中,具体代码我们就不列出来了,和前面通用定时器章节的PWM输出实验的初始化代码相似。下面我们列出TIM8的初始化代码,代码中附上详细的注释:
1 /**
2 * @brief 定时器TIMX 通道Y PWM输入模式 初始化函数
3 * @note
4通用定时器的时钟来自APB2,当APB2DIV≥2分频的时候
5 通用定时器的时钟为APB2时钟的2倍, 而APB2为104.5M, 所以定时器时钟 = 209Mhz
6定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
7定时器工作频率,单位:Mhz
8 *
9 本函数初始化的时候: 使用psc=0, arr固定为65535.
10得到采样时钟频率为240Mhz,精度4.485ns
11 *
12 * @param无
13 * @retval无
14 */
15 void atim_timx_pwmin_chy_init(void)
16 {
17 TIM_ClockConfigTypeDef sClockSourceConfig = {0};
18 TIM_SlaveConfigTypeDef sSlaveConfig = {0};
19 TIM_MasterConfigTypeDef sMasterConfig = {0};
20 TIM_IC_InitTypeDef sConfigIC = {0};
21
22 atim_timx_pwmin_chy_gpio_init(); /* 定时器相关引脚初始化 */
23 ATIM_TIMX_PWMIN_CHY_CLK_ENABLE(); /* 使能TIM8时钟 */
24
25 g_timx_pwmin_chy_handle.Instance = ATIM_TIMX_PWMIN; /* 定时器8 */
26 /* 定时器预分频系数为0,不分频 */
27 g_timx_pwmin_chy_handle.Init.Prescaler = 0;
28 /* 向上计数模式 */
29 g_timx_pwmin_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
30 g_timx_pwmin_chy_handle.Init.Period = 65535; /* 自动重装载值 */
31 /* 内部时钟不分频*/
32 g_timx_pwmin_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
33 g_timx_pwmin_chy_handle.Init.RepetitionCounter = 0; /* 重复计数为0 */
34 /* 自动重载预装载使能 */
35 g_timx_pwmin_chy_handle.Init.AutoReloadPreload = \ TIM_AUTORELOAD_PRELOAD_ENABLE;
36 HAL_TIM_Base_Init(&g_timx_pwmin_chy_handle); /* 时基初始化 */
37 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;/* 内部时钟 */
38 /* 时钟初始化 */
39 HAL_TIM_ConfigClockSource(&g_timx_pwmin_chy_handle, &sClockSourceConfig);
40 HAL_TIM_IC_Init(&g_timx_pwmin_chy_handle); /* 捕获通道初始化 */
41
42 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET; /* 复位模式 */
43 sSlaveConfig.InputTrigger = TIM_TS_TI1FP1; /* 触发源为TI1FP1 */
44 /* 触发模式:上升沿触发 */
45 sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
46 sSlaveConfig.TriggerFilter = 0; /* 不滤波 */
47 /* 从模式配置 */
48 HAL_TIM_SlaveConfigSynchro(&g_timx_pwmin_chy_handle,&sSlaveConfig);
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 HAL_TIMEx_MasterConfigSynchronization(&g_timx_pwmin_chy_handle, \ &sMasterConfig);
57
58 sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; /* 上升沿检测 */
59 /*选择输入端 IC1映射到TI1上*/
60 sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
61 sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; /* 不分频 */
62 sConfigIC.ICFilter = 0; /* 不滤波 */
63 /* 捕获通道1配置 */
64 HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &sConfigIC,\ TIM_CHANNEL_1);
65
66 sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;/* 下降沿检测 */
67 /*选择输入端 IC2映射到TI1上*/
68 sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
69 /* 捕获通道2配置 */
70 HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &sConfigIC, \ TIM_CHANNEL_2);
71 /* 设置中断优先级,抢占优先级1,子优先级3 */
72 HAL_NVIC_SetPriority(ATIM_TIMX_PWMIN_IRQn,1,3);
73 HAL_NVIC_EnableIRQ(ATIM_TIMX_PWMIN_IRQn); /* 开启ITMx中断 */
74
75 /* TIM1/TIM8 有独立的输入捕获中断服务函数 */
76 if (ATIM_TIMX_PWMIN == TIM1 || ATIM_TIMX_PWMIN == TIM8)
77 {
78 /* 设置中断优先级,抢占优先级1,子优先级3 */
79 HAL_NVIC_SetPriority(ATIM_TIMX_PWMIN_CC_IRQn,1,3);
80 HAL_NVIC_EnableIRQ(ATIM_TIMX_PWMIN_CC_IRQn); /* 开启ITMx中断 */
81 }
82 /* 启动TIM8通道1输入捕获模式 */
83 HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle,TIM_CHANNEL_1);
84 /* 启动TIM8通道2输入捕获模式 */
85 HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle,TIM_CHANNEL_2);
86 /* 使能更新中断 */
87 __HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE);
88 }
函数atim_timx_pwmin_chy_init可通过配置参数arr和psc完成自动重载值和时钟预分频数的配置,main函数中通过调用sys_stm32_clock_init函数配置APB2的时钟为209MHz,即TIM1的时钟为209MHz。代码中我们已经附上了详细的注释,可以很快理解代码。
第25~36行,主要是TIM1的时基初始化部分:配置定时器预分频值为psc、向上计数模式、自动重装载值为arr、时钟不分频、使能自动重载。
第42行,配置从模式为复位模式,TIM8做从定时器,TIM5是主定时器。从模式定时器收到主模式定时器的信号后,当触发输入产生变化时,从模式定时器的计数器被复位,并更新所有的寄存器。此外,我们还可以了解其它模式,如:
External Clock Mode1——外部时钟:使用外部时钟源作为定时器时钟;
Gated Mode——门控模式:使用门控的功能决定时钟是否工作;
Trigger Mode——触发模式:由主定时器信号变化触发是否工作。
第43行,触发源为TI1FP1;
第45和46行,配置触发模式为上升沿触发,无滤波;
第48行,根据第42~46行的参数完成从模式配置;
第51~56行,配置定时器的配置主模式;
第58~64行,配置定时器的捕获通道1,其中,选择输入端 IC1映射到TI1上;
第66~70行,配置定时器的捕获通道2,其中,选择输入端 IC2映射到TI1上;
第72和73行,配置和使能TIM8的更新中断,并配置抢占优先级为1,子优先级3;
第79和80行,配置和使能捕获中断,并配置抢占优先级为1,子优先级3;
第83和85行,启动TIM8通道1和TIM8通道2的输入捕获模式;
第87行,使能更新中断。
第22行,调用atim_timx_pwmin_chy_gpio_init函数完成相关引脚化,我们来看看该函数:
/**
* @brief定时器PWM输入模式通道引脚初始化函数
* @param无
* @note此函数会被atim_timx_cplm_pwm_init()函数调用
* @retval无
*/
void atim_timx_pwmin_chy_gpio_init(void)
{
GPIO_InitTypeDef gpio_init_struct = {0};
ATIM_TIMX_PWMIN_CHY_GPIO_CLK_ENABLE(); /* TIM8通道IO口(PI0)时钟使能 */
gpio_init_struct.Pin = ATIM_TIMX_PWMIN_CHY_GPIO_PIN; /* 引脚5 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉模式 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH ; /* 高速模式 */
gpio_init_struct.Alternate = ATIM_TIMX_PWMIN_CHY_GPIO_AF;/* AF功能选择 */
/* GPIO引脚初始化 */
HAL_GPIO_Init(ATIM_TIMX_PWMIN_CHY_GPIO_PORT, &gpio_init_struct);
}
该函数比较简单,主要完成定时器相关引脚的初始化。下面我们来编写中断服务函数和回调函数,如下:
/**
* @brief定时器TIMX 更新/溢出 中断服务函数
* @note的这个函数仅用于更新/溢出中断服务,捕获在另外一个函数!
其他普通定时器则更新/溢出/捕获,都在这个函数里面处理!
* @param无
* @retval无
*/
void ATIM_TIMX_PWMIN_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_pwmin_chy_handle); /* 定时器共用处理函数 */
}
/**
* @brief定时器TIMX 输入捕获 中断服务函数
* @note仅TIM1/TIM8有这个函数,其他普通定时器没有这个中断服务函数!
* @param无
* @retval无
*/
void ATIM_TIMX_PWMIN_CC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_pwmin_chy_handle); /* 定时器共用处理函数 */
}
本实验我们开启了定时器8的溢出(更新)中断和输入输入捕获中断,所以要正确编写中断服务函数。
下面,我们来介绍两个极为重要的函数。先看定时器TIM8 PWM输入模式重新启动捕获函数:
1 /* PWM输入状态(g_timxchy_cap_sta)
2没有成功捕获.
3已经成功捕获了
4 */
5 uint8_t g_timxchy_pwmin_sta = 0; /* PWM输入状态 */
6 uint16_t g_timxchy_pwmin_psc = 0; /* PWM输入分频系数 */
7 uint32_t g_timxchy_pwmin_hval = 0 ; /* PWM的高电平脉宽 */
8 uint32_t g_timxchy_pwmin_cval = 0 ; /* PWM的周期宽度 */
9 /**
10 * @brief定时器TIM8 PWM输入模式 重新启动捕获
11 * @param无
12 * @retval无
13 */
14 void atim_timx_pwmin_chy_restart(void)
15 {
16 INTX_DISABLE(); /* 关闭中断 */
17 //TIM8->DIER=0x0000; /* 关闭中断 */
18 g_timxchy_pwmin_sta = 0; /* 清零状态,重新开始检测 */
19 g_timxchy_pwmin_hval=0;
20 g_timxchy_pwmin_cval=0;
21 INTX_ENABLE(); /* 打开中断 */
22 //TIM8->DIER=0x07; /* 打开中断 */
23 }
第5~8行,定义几个我们会用到的变量。
第14~23行,定义定时器8 PWM输入模式重新启动捕获函数,先将中断关闭,再把上面用到的变量清零,然后再开启中断。之所以关闭中断是避免中断的影响,这些变量就不是0了。这里关闭和开启中断也可以通过配置寄存器来实现,如第17和22行的代码,是将TIMx_DIER寄存器设置为复位值0x0000,即不开启中断,这里注意,第22行,我们操作的是TIMx_DIER寄存器的第0和第1位,其中第0位UIE用于使能更新中断,第1位CC1IE用于使能捕获/比较 1 中断,我们将其置1即可。
下面,我们再来看看定时器8输入捕获中断处理回调函数,如下:
1 /**
2 * @brief定时器输入捕获中断处理回调函数
3 * @param定时器句柄指针
4 * @note该函数在HAL_TIM_IRQHandler中会被调用
5 此函数是定时器共同调用的回调函数,为了防止重定义错误,
6我们把gtim.c的HAL_TIM_IC_CaptureCallback()函数先屏蔽掉.
7 * @retval无
8 */
9 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
10 {
11
12 if (g_timxchy_pwmin_sta == 0) /* 还没有成功捕获 */
13 {
14 if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
15 {
16 /* 修正系数为2, 加2 */
17 g_timxchy_pwmin_hval = \ HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle,TIM_CHANNEL_2)+2;
18 /* 修正系数为2, 加2 */
19 g_timxchy_pwmin_cval = \ HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle,TIM_CHANNEL_1)+2;
20 g_timxchy_pwmin_sta = 1; /* 标记捕获成功 */
21 }
22 }
23 }
第9~23行是定时器输入捕获中断处理回调函数。如果发生中断,会进入ATIM_TIMX_PWMIN_CC_IRQHandler中断服务函数,该中断服务函数会调用定时器中断请求函数HAL_TIM_IRQHandler。在定时器中断请求函数中,会根据中断类型执行对应的中断回调函数,本实验是输入捕获中断,所以会执行HAL_TIM_IC_CaptureCallback回调函数。我们直接在回调函数中做文章,实现读取TIM8通道1的捕获值(即寄存器TIMx_CCRx的值)。
第12行,标志位atimx_pwmin_sta用于判断是否成功捕获,如果没有捕获,表示0;
第14~21行,如果通道1在使用中,则读取捕获通道1和捕获通道2的捕获值,通道1的捕获值用于计算PWM周期,通道2的捕获值用于计算PWM的正脉宽。这里的值要加上2,才等于用于计算的捕获值。
第20行,获取到捕获值以后,则标记已经成功捕获。
(3)main.c文件代码
main.c文件的部分代码如下:
1 extern uint16_t g_timxchy_pwmin_psc; /* PWM输入状态 */
2 extern uint16_t g_timxchy_pwmin_sta; /* PWM输入状态 */
3 extern uint32_t g_timxchy_pwmin_hval; /* PWM的高电平脉宽 */
4 extern uint32_t g_timxchy_pwmin_cval; /* PWM的周期宽度 */
5 /**
6 * @brief主函数
7 * @param无
8 * @retval无
9 */
10 int main(void)
11 {
12 uint8_t t = 0;
13 double ht, ct, f, tpsc;
14 HAL_Init(); /* 初始化HAL库 */
15 /* 初始化M4内核时钟,209M */
16 if(IS_ENGINEERING_BOOT_MODE())
17 {
18 sys_stm32_clock_init(34, 2, 2, 17, 6826);
19 }
20 usart_init(115200); /* 串口初始化为115200 */
21 delay_init(209); /* 延时初始化 */
22 led_init(); /* 初始化LED */
23 /* 1Mhz的计数频率, 10Khz PWM */
24 gtim_timx_pwm_chy_init(100 - 1, 209 - 1);
25 atim_timx_pwmin_chy_init(); /* 初始化PWM输入捕获 */
26 GTIM_TIMX_PWM_CHY_CCRX = 20; /* 低电平宽度20,高电平宽度80 */
27 while(1)
28 {
29 delay_ms(10);
30 t++;
31 if (t >= 20) /* 每200ms输出一次结果,并闪烁LED0,提示程序运行 */
32 {
33 if (g_timxchy_pwmin_sta) /* 捕获了一次数据 */
34 {
35 printf("\r\n"); /* 输出空,另起一行 */
36 printf("PWM PSC :%d\r\n", g_timxchy_pwmin_psc); /* 打印分频系数 */
37 printf("TIMx_CCR2:%d\r\n", g_timxchy_pwmin_hval); /* 打印高电平脉宽 */
38 printf("TIMx_CCR1:%d\r\n", g_timxchy_pwmin_cval); /* 打印周期 */
39 /* 得到PWM采样时钟周期时间 */
40 tpsc = ((double)g_timxchy_pwmin_psc + 1)/209;
41 ht = g_timxchy_pwmin_hval * tpsc; /* 计算高电平时间 */
42 ct = g_timxchy_pwmin_cval * tpsc; /* 计算周期长度 */
43 f = (1 / ct) * 1000000; /* 计算频率 */
44 printf("PWM Hight time:%.3fus\r\n", ht); /* 打印高电平脉宽长度 */
45 printf("PWM Cycle time:%.3fus\r\n", ct); /* 打印周期时间长度 */
46 printf("PWM Frequency :%.3fHz\r\n", f); /* 打印频率 */
47 atim_timx_pwmin_chy_restart(); /* 重启PWM输入检测 */
48 }
49 LED0_TOGGLE(); /* LED0(RED)闪烁 */
50 t = 0;
51 }
52 }
53 }
第18行,配置系统时钟,最终APB2的时钟为209MHz;
第20行,初始化串口,实验中我们要用串口打印信息;
第24行,初始化定时器5通道4,重装载值为100 – 1,预分频值为209-1,上述参数可以计算定时器的时钟频率为:
也就是计数器每计数一个节拍为1us,设置自动重载值为100-1,计数器计数100个节拍就是100us,所以PWM波的周期为100us,频率为10000Hz。
第25行,初始化定时器8通道1 PWM输入捕获;
第26行,配置定时器5通道4的输出比较寄存器的值为20,即低电平宽度20,高电平宽度80;
第31行,延时200ms,LED0每隔接近200ms闪烁一次,串口UART4接近每隔200ms打印一次;
第36行,打印分频系数,此分频系数是TIM8的分频系数,前面我们在程序中配置为了0,即不分频;
第37行,打印捕获通道2的捕获值,即TIMx_CCR2的值,通过此值计算出正脉冲宽度;
第38行,打印通道1的捕获值TIMx_CCR1,通过此值计算PWM波周期;
第40行,计算PWM采样时钟周期,即TIM8_CH1的周期,其实也就是TIM5_CH1信号的周期,周期等于频率的倒数
,这里TIM8_CH1的频率为209MHz。
第41和42行,计算高电平时间和周期,后面我们通过观察串口的数据实际计算一下。
第43行,计算频率,频率等于周期的倒数;
第44~46行,将计算出的PWM的波高电平脉冲宽度、周期和频率打印出来;
第47行,重启PWM输入检测。
25.5.5 编译和测试
以上代码添加完毕以后,保存修改,编译工程无报错以后,用Type-C线接在开发板的USB_TTL接口上,线的一端接在电脑的USB口上,按照前面的步骤连接好ST-Link,同时注意开发板上的JP11处的跳线帽是否已经接好,如果跳线帽没接,那么UART4则无法正常通信,拨码开关拨成001,即MCU启动模式。用两边是母头的杜邦线将JP1排针处的PI0和PI5连在一起。
图18.5.4. 2开发板接线
进入Debug以后,点击运行按钮,可以看到底板的LED0灯在闪烁,说明程序已经在跑了。串口每隔200ms打印信息如下:
图18.5.4. 3串口打印的数据
PWM PSC :0
TIMx_CCR2:16720
TIMx_CCR1:20900
PWM Hight time:80.000us
PWM Cycle time:100.000us
PWM Frequency :10000.000Hz
前面我们计算出TIM8_CH1信号的采样周期为
,也就是TIM8计数器每计数一个节拍用时
,TIMx_CCR2的值是16720,则PWM高脉冲宽度
。TIMx_CCR1的值是20900,则PWM周期是
。PWM脉冲频率等于周期的倒数
。和我们前面设置的TIM5_CH4的PWM输出结果一致。