超级版主
 
- 积分
- 5579
- 金钱
- 5579
- 注册时间
- 2019-5-8
- 在线时间
- 1482 小时
|
本帖最后由 正点原子运营 于 2026-4-13 10:09 编辑
第二十一章 高级定时器实验
1)实验平台:正点原子STM32H7R7开发板
2)章节摘自【正点原子】STM32H7R7开发指南 V1.1
3)购买链接: https://detail.tmall.com/item.htm?id=820823382459
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32开发板技术交流群:756580169
本章我们主要来学习高级定时器, STM32H7R7有1个高级定时器(TIM1)。我们将通过四个实验来学习高级定时器的各个功能,分别是高级定时器输出指定个数PWM实验、高级定时器输出比较模式实验、高级定时器互补输出带死区控制实验和高级定时器PWM输入模式实验。
本章分为如下几个小节:
21.1 高级定时器简介
21.2 高级定时器输出指定个数PWM实验
21.3 高级定时器输出比较模式实验
21.4 高级定时器互补输出带死区控制实验
21.5 高级定时器PWM输入模式实验
21.1 高级定时器简介
高级定时器的框图和通用定时器框图很类似,只是添加了其它的一些功能,如:重复计数器、带死区控制的互补输出通道、断路输入等。这些功能在高级定时器框图的位置如下:
图21.1.1 高级定时器框图
上图中,框出来三个部分,这是和通用定时器不同的地方,下面来分别介绍它们。
①重复计数器
在H7R7系列中,除了高级定时器TIM1之外,通用定时器TIM15、TIM16和TIM17也有重复计数器。通过查看手册可以知道TIM15、TIM16和TIM17虽为通用定时器,但是高级定时器有的功能,它们也基本都有,所以它们是很特殊的。
下面来介绍一下重复计数器有什么作用?在学习基本定时器和通用定时器的时候,我们知道定时器发生上溢或者下溢时,会直接生成更新事件。但是有重复计数器的定时器并不完全是这样的,定时器每次发生上溢或下溢时,重复计数器的值会减一,当重复计数器的值为0时,再发生一次上溢或者下溢才会生成定时器更新事件。如果我们设置重复计数器寄存器RCR的值为N,那么更新事件将在定时器发生N+1次上溢或下溢时发生。
这里需要注意的是重复计数器寄存器是具有影子寄存器的,所以RCR寄存器只是起缓冲的作用。RCR寄存器的值会在更新事件发生时,被转移至其影子寄存器中,从而真正生效。
重复计数器的特性,在控制生成PWM信号时很有用,后面会有相应的实验。
②输出比较
高级定时器输出比较部分和通用定时器相比,多了带死区控制的互补输出功能。图21.1.1第②部分的TIMx_CH1N、TIMx_CH2N和TIMx_CH3N分别是定时器通道1、通道2和通道3的互补输出通道,通道4是没有互补输出通道的。DTG是死区发生器,死区时间由DTG[7:0]位来配置如果不使用互补通道和死区时间控制,那么高级定时器TIM1,以及TIM15/16/17和其它通用定时器的输出比较部分使用方法基本一样,只是要注意MOE位得置1定时器才能输出。
如果使用互补通道,那么就有一定的区别了,具体我们在高级定时器互补输出带死区控制实验小节再来介绍。
③断路功能
断路功能也称刹车功能,一般用于电机控制的刹车。H7R7系列有两个断路通道。断路1通道可以收集系统级故障(时钟失效和奇偶校验错误等)和应用故障(来自输入引脚:TIMx_BKIN或者TIMx_BKIN2,还可以来自内置比较器),可以在死区持续时间后将输出强制为预定义的电平(有效或无效)。断路2通道只包含应用故障,能够将输出强制为无效状态。系统复位后,断路功能默认被禁止,MOE位为低。
使能断路功能的方法:将TIMx_BDTR的位BKE(或BK2E)置1。断路输入引脚TIMx_BKIN或者TIMx_BKIN2的输入有效电平可通过TIMx_BDTR寄存器的位BKP(或BK2P)设置。
使能刹车功能后:由TIMx_BDTR的MOE、OSSI、OSSR位,TIMx_CR2的OISx、OISxN位,TIMx_CCER的CCxE、CCxNE位控制OCx和OCxN输出状态。无论何时,OCx和OCxN输出都不能同时处在有效电平。
当发生断路输入后,会怎么样?
1,MOE位被异步地清零,OCx和OCxN为无效、空闲或复位状态(由OSSI位选择)。
2,OCx和OCxN的状态:由相关控制位状态决定,当使用互补输出时:根据情况自动控制输出电平。
3,BIF位置1,如果使能了BIE位,还会产生刹车中断;如果使能了TDE位,会产生DMA请求。
4,如果AOE位置 1,在下一个 更新事件UEV时,MOE位被自动置 1。
高级定时器框图部分就简单介绍到这里,下面通过实际的实验来学习高级定时器。
21.2 高级定时器输出指定个数PWM实验
要实现定时器输出指定个数PWM,只需要掌握下面几点内容:
第一,如果大家还不清楚定时器是如何输出PWM的,请回顾通用定时器PWM输出实验的内容,这部分的知识是一样的。但是需要注意的是:本实验,我们需要把MOE位置1,这样定时器8的通道才能输出。
第二,要清楚重复计数器特性,设置重复计数器寄存器RCR的值为N,那么更新事件将在定时器发生N+1次上溢或下溢时发生。换句话来说就是,想要指定输出N个PWM,只需要把N-1写入RCR寄存器。因为在边沿对齐模式下,定时器溢出周期对应着PWM周期,我们只要在更新事件发生时,停止输出PWM就行。
第三,为了保证定时器输出指定个数的PWM后,定时器马上停止继续输出,我们使能更新中断,并在定时器中断里关闭计数器。
原理部分我们就讲到这里,下面直接开始寄存器的介绍。
21.2.1 TIM1寄存器
本实验用到定时器1,在《STM32H7Rx参考手册_V6(英文版).pdf》手册的41.2小节(1630页)可以找到TIM1的寄存器描述。这里我们将介绍TIM1寄存器里我们用到的几个重要的寄存器,具体如下:
控制寄存器 1(TIM1_CR1)
TIM1的控制寄存器1描述如图21.2.1.1所示:
图21.2.1.1 TIM1_CR1寄存器
位7(APRE)用于控制自动重载寄存器是否具有缓冲作用,在基本定时器的时候已经讲过,请回顾。在本实验中我们把该位要置1,这样就算改变ARR寄存器的值,该值也不会马上生效,而是等待之前设置的PWM完整输出后(发生更新事件)才生效。
CEN位,用于使能计数器的工作,必须要设置该位为1,才可以开始计数。
捕获/比较模式寄存器1(TIM1_CCMR1)
TIM1的捕获/比较模式寄存器:TIM1_CCMR1。TIM1_CCMR1控制CH1和CH2。 TIM1_CCMR1寄存器描述如图21.2.1.2所示:
图21.2.1.2 TIM1_CCMR1寄存器
该寄存器的有些位在不同模式下,功能不一样。比如我们要让TIM1的CH1输出PWM波为例,该寄存器的模式设置位OC1M[3:0]就是对应着通道1的模式设置,此部分由4位组成。TIM1总共可以配置成14种模式,我们使用的是PWM模式,所以这4位必须设置为0110或者0111,分别对应PWM模式1和PWM模式2。这两种PWM模式的区别就是输出有效电平的极性相反,这里我们设置为PWM模式1。位3 OC1PE是输出比较通道1的预装使能,该位需要置1,另外CC1S[1:0]用于设置通道1的方向(输入/输出)默认设置为0,就是设置通道作为输出使用。
捕获/比较使能寄存器(TIM1_ CCER)
TIM1的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关。TIM1_CCER寄存器描述如图21.2.1.3所示:
图21.2.1.3 TIM1_CCER寄存器
该寄存器比较简单,要让TIM1的CH1输出PWM波,这里我们要使能CC1E位,该位是通道1输入/输出使能位,要想PWM从IO口输出,这个位必须设置为1。CC1P位是设置通道1的输出极性,我们默认设置0,即OC1 高电平有效。
事件产生寄存器(TIM1_ EGR)
TIM1的事件产生寄存器,该寄存器是让用户用软件方式产生各类事件。如下图所示:
图21.2.1.4 TIM1_EGR寄存器
UG位是更新事件的控制位,作用和定时器溢出时产生的更新事件一样,区别是这里是通过软件产生的,而定时器溢出是硬件自己完成的。只有开启了更新中断,这两种方式都可以产更新中断。本实验用到该位去产生软件更新器事件,在需要的时候把UG位置1即可,会由硬件自动清零。
重复计数器寄存器(TIM1_ RCR)
重复计数器寄存器用于设置重复计数器值,因为它具有影子寄存器,所以它本身只是起缓冲作用。当更新事件发生时,该寄存器的值会转移到其影子寄存器中,从而真正起作用。TIM1_ RCR寄存器描述如图21.2.1.5所示:
图21.2.1.5 TIM1_ RCR寄存器
该寄存器的REP[7:0]位是低8位有效,即最大值255。因为这个寄存器只是起缓冲作用,如果大家对该寄存器写入值后,想要立即生效,可以通过对UG位写1,产生软件更新事件。
捕获/比较寄存器1/2(TIM1_ CCR1/2)
捕获/比较寄存器(TIM1_ CCR1/2),该寄存器总共有2个,对应TIM1的2个通道CH1和CH2。我们使用的是通道1,所以来看看TIM1_ CCR1寄存器描述如图21.2.1.6所示:
图21.2.1.6 TIM1_ CCR1寄存器
在输出模式下,捕获/比较寄存器影子寄存器的值与CNT的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制PWM的占空比了。
断路和死区寄存器(TIM1_ BDTR)
高级定时器TIM1在输出模式下,还需要配置:断路和死区寄存器(TIM1_BDTR),该寄存器各位描述如图21.2.1.7所示:
图21.2.1.7 TIM1_ BDTR寄存器
本实验,我们只需要关注该寄存器的位15(MOE),要想高级定时器的PWM正常输出,则必须设置MOE位为1,否则不会有输出。
21.2.2 硬件设计
1. 例程功能
通过TIM1_CH1(由PC2复用)输出PWM,按一下按键KEY0,就会输出5个PWM信号,可以用DS100示波器辅助查看。
2. 硬件资源
1)LED灯
LED1 – PC0
2)独立按键
KEY0 – PE9
3)定时器1,使用TIM1通道1,由PC2复用。
3. 原理图
定时器属于STM32H7R7的内部资源,只需要软件设置好即可正常工作。我们通过示波器来看STM32H7R7的定时器的PWM输出情况,同时还可以用按键KEY0进行控制。
21.2.3 程序设计
本实验用到的HAL库函数介绍请回顾通用定时器PWM输出实验。下面介绍一下定时器输出指定个数PWM的配置步骤。
定时器输出指定个数PWM配置步骤
1)开启TIMx和通道输出的GPIO时钟,配置该IO口的复用功能输出
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器1通道1,对应IO是PC2,它们的时钟开启方法如下:
- __HAL_RCC_TIM1_CLK_ENABLE(); /* 使能定时器1 */
- __HAL_RCC_GPIOC_CLK_ENABLE(); /* 开启GPIOC时钟 */
复制代码 IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化TIMx,设置TIMx的ARR和PSC等参数
使用定时器的PWM模式功能时,我们调用的是HAL_TIM_PWM_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用:HAL_TIM_PWM_MspInit函数,我们可以通过后者存放定时器和GPIO时钟使能、GPIO初始化、中断使能以及优先级设置等代码。
3)设置定时器为PWM模式,输出比较极性,比较值等参数
在HAL库中,通过HAL_TIM_PWM_ConfigChannel函数来设置定时器为PWM1模式或者PWM2模式,根据需求设置输出比较的极性,设置比较值(控制占空比)等。
4)使能定时器更新中断,开启定时器并输出PWM,配置定时器中断优先级
通过__HAL_TIM_ENABLE_IT函数使能定时器更新中断。
通过HAL_TIM_PWM_Start函数使能定时器并开启输出PWM。
通过HAL_NVIC_EnableIRQ函数使能定时器中断。
通过HAL_NVIC_SetPriority函数设置中断优先级。
5)编写中断服务函数
定时器中断服务函数为:TIMx_IRQHandler等,当发生中断的时候,程序就会执行中断服务函数。HAL库提供了一个定时器中断公共处理函数HAL_TIM_IRQHandler,该函数会根据中断类型调用相关的中断回调函数。用户根据自己的需要重定义这些中断回调函数来处理中断程序。本实验我们不使用HAL库的中断回调机制,而是把中断程序写在定时器中断服务函数里。详见本章例程源码。
21.2.3.1 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。本实验使用到TIM8,所以相关的BSP驱动代码还是存放到atim.c和atim.h文件中。
首先看atim.h头文件的几个宏定义:
- #define ATIM_TIMX TIM1
- #define ATIM_TIMX_IRQn TIM1_UP_IRQn
- #define ATIM_TIMX_IRQHandler TIM1_UP_IRQHandler
- #define ATIM_TIMX_CLK_ENABLE() do { __HAL_RCC_TIM1_CLK_ENABLE(); } while (0)
- #define ATIM_TIMX_CHY TIM_CHANNEL_1
- #define ATIM_TIMX_CHY_GPIO_PORT GPIOC
- #define ATIM_TIMX_CHY_GPIO_PIN GPIO_PIN_2
- #define ATIM_TIMX_CHY_GPIO_AF GPIO_AF1_TIM1
- #define ATIM_TIMX_CHY_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOC_CLK_ENABLE(); } while (0)
复制代码 可以把上面的宏定义分成两部分,第一部分是定时器1输入通道1对应的IO口的宏定义,第二部分则是定时器1输入通道1的相应宏定义。
下面看atim.c的程序,首先是输出指定个数PWM初始化函数,其定义如下:
- /**
- * @brief 初始化高级定时器输出指定个数PWM
- * @param arr: 自动重装载值
- * @param psc: 预分频系数
- * @retval 无
- */
- void atim_timx_chy_npwm_init(uint16_t arr, uint16_t psc)
- {
- TIM_OC_InitTypeDef timx_oc_init_struct = {0};
-
- /* 初始化TIM PWM */
- g_atimx_handle.Instance = ATIM_TIMX;
- g_atimx_handle.Init.Prescaler = psc; /* 预分频系数 */
- g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 计数模式 */
- g_atimx_handle.Init.Period = arr; /* 重装载值 */
- g_atimx_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频 */
- g_atimx_handle.Init.RepetitionCounter = 0; /* 重复计数数值 */
- g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
- /* 自动重装载预加载模式 */
- HAL_TIM_PWM_Init(&g_atimx_handle);
-
- /* 配置TIM PWM通道 */
- timx_oc_init_struct.OCMode = TIM_OCMODE_PWM1; /* PWM模式 */
- timx_oc_init_struct.Pulse = (arr + 1) >> 1; /* 占空比 */
- timx_oc_init_struct.OCPolarity = TIM_OCPOLARITY_HIGH; /* 有效电平 */
- HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &timx_oc_init_struct,
- ATIM_TIMX_CHY);
-
- /* 使能TIM更新中断 */
- __HAL_TIM_ENABLE_IT(&g_atimx_handle, TIM_IT_UPDATE);
-
- /* 开启TIM PWM输出 */
- HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_CHY);
-
- /* 配置中断优先级并使能中断 */
- HAL_NVIC_SetPriority(ATIM_TIMX_IRQn, 0, 0);
- HAL_NVIC_EnableIRQ(ATIM_TIMX_IRQn);
- }
复制代码 atim_timx_chy_npwm_init函数包含了输出通道对应IO的初始代码、NVIC、使能时钟、定时器基础工作参数和输出模式配置的所有代码。下面来看看该函数的代码内容。
第一部分使能定时器和GPIO的时钟。
第二部分是定时器输出通道对应的IO的初始化。
第三部分是NVIC的初始化,配置抢占优先级、响应优先级和开启NVIC定时器中断。
第四部分调用HAL_TIM_PWM_Init函数初始化定时器基础工作参数,如:ARR和PSC等。
第五部分调用HAL_TIM_PWM_ConfigChannel设置PWM模式以及比较值等参数。
最后是使能更新中断和使能通道输出。
为了方便代码的管理和移植性等,这里就没有使用HAL_TIM_PWM_MspInit这个函数来存放使能时钟、GPIO、NVIC相关的代码,而是全部存放在atim_timx_npwm_chy_init函数中。
下面我们看设置PWM个数的函数,其定义如下:
- /* 输出指定个数PWM定时器相关变量 */
- uint32_t g_timx_chy_npwm_remain = 0; /* 输出指定个数PWM定时器剩余个数计数器 */
- /**
- * @brief 设置高级定时器输出指定个数PWM
- * @param npwm: 指定PWM个数
- * @retval 无
- */
- void atim_timx_chy_npwm_set(uint32_t npwm)
- {
- if (npwm == 0)
- {
- return;
- }
-
- g_timx_chy_npwm_remain = npwm;
- HAL_TIM_GenerateEvent(&g_atimx_handle, TIM_EVENTSOURCE_UPDATE);
- }
复制代码 我们要输出多少个周期的PWM就用这个函数来设置。该函数作用是把我们设置输出的PWM个数的值赋值给全局变量g_timx_chy_npwm_remain,该变量会在更新中断服务函数中发挥作用。最后对TIM1_EGR寄存器UG位写1,产生一次更新事件,并使能定时器。
下面来介绍定时器中断服务函数,其定义如下:
- /**
- * @brief TIM中断服务函数
- * @param 无
- * @retval 无
- */
- void ATIM_TIMX_IRQHandler(void)
- {
- HAL_TIM_IRQHandler(&g_atimx_handle);
- }
复制代码 这里我们没有使用HAL库的中断回调机制,而是像寄存器操作一样,直接通过判断中断标志位处理中断。通过__HAL_TIM_GET_FLAG函数宏判断是否发生更新中断,然后进行更新中断的代码处理,最后通过__HAL_TIM_CLEAR_IT函数宏清除更新中断标志位。
因为重复计数器寄存器 (TIM1_RCR)是8位有效的,所以在定时器中断服务函数中首先对全局变量g_timx_chy_npwm_remain(即我们要输出的PWM个数)进行判断,是否大于256,如果大于256,那就得分次写入重复计数器寄存器。写入重复计数寄存器后,需要产生软件更新事件把RCR寄存器的值更新到RCR影子寄存器中,最后一定不要忘记清除定时器更新中断标志位。
在main函数里面编写如下代码:
- int main(void)
- {
- uint8_t key;
- uint8_t t = 0;
-
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(240, 5, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- key_init(); /* 初始化按键 */
- atim_timx_chy_npwm_init(5000 - 1, 30000 - 1);
- /* 初始化高级定时器输出指定个数PWM,PWM频率为2Hz */
-
- while (1)
- {
- key = key_scan(0);
- if (key == KEY0_PRES)
- {
- atim_timx_chy_npwm_set(5); /* 设置高级定时器输出5个PWM */
- }
-
- if (++t == 20)
- {
- t = 0;
- LED1_TOGGLE();
- }
-
- delay_ms(10);
- }
- }
复制代码 先看atim_timx_chy_npwm_init (5000 - 1, 30000 - 1);这个语句,这两个形参分别设置自动重载寄存器的值为4999,以及预分频器寄存器的值为29999。按照sys_stm32_clock_init函数的配置,定时器1的时钟频率为2倍的APB2总线时钟频率,即300MHz,可以得到计数器的计数频率是10KHz。自动重载寄存器的值决定的是PWM周期或频率(请回顾20.3小节的内容),计数器计5000个数所用的时间是PWM的周期。在边沿对齐模式下,定时器的溢出周期等于PWM的周期。根据定时器溢出时间计算公式,可得:
Tout= ((arr+1)*(psc+1))/Tclk= ((4999+1)*(29999+1))/300000000=0.5s 再由频率是周期的倒数关系得到PWM的频率为2Hz。
占空比则由捕获/比较寄存器(TIMx_CCRx)的值决定,这里就是由TIM1_CCR1寄存器决定。
21.2.4 下载验证
下载代码后,我们每按一下按键KEY0,就会输出5个PWM。下面我们使用正点原子DS100手持数字示波器,把PC2引脚的波形截获,具体如下:
图21.2.4 PC2引脚波形图
由图可知频率为2Hz,大家可以自行测量。
21.3 高级定时器输出比较模式实验
本小节我们来学习使用高级定时器输出比较模式下翻转功能,通过定时器2个通道分别输出2个50%占空比、不同相位的PWM。
输出比较模式下翻转功能作用是:当计数器的值等于捕获/比较寄存器影子寄存器的值时,OC1REF 发生翻转,进而控制通道输出(OCx)翻转。通过翻转功能实现输出PWM的具体原理如下:PWM频率由自动重载寄存器(TIMx_ARR)的值决定,在这个过程中,只要自动重载寄存器的值不变,那么PWM占空比就固定为50%。我们可以通过捕获/比较寄存器(TIMx_CCRx)的值改变PWM的相位。生成PWM的原理如图21.3.1所示:
图21.3.1 翻转功能输出PWM原理示意图
本实验就是根据图21.3.1的原理来设计的,具体实验是:我们设置固定的ARR值为999,那么PWM占空比固定为50%,通过改变2个通道的捕获/比较寄存器(TIMx_CCRx)的值使得每个通道输出的PWM的相位都不一样,注意捕获/比较寄存器的值设置范围是:0 ~ ARR。比如:TIMx_CCR1=250-1,TIMx_CCR2=500-1,那么可以得到通道1~通道2输出的PWM的相位分别是:25%和50%。翻转功能输出的PWM周期,这里用T表示,其计算公式如下:
T= 2*(arr+1)*((psc+1)/ Tclk) 其中:
T:翻转功能输出的PWM周期(单位为s)。
Tclk:定时器的时钟源频率(单位为MHz)。
arr:自动重装寄存器(TIMx_ARR)的值。
psc:预分频器寄存器(TIMx_PSC)的值
21.3.1 TIM1寄存器
高级定时器输出比较模式除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 之外。主要还用到以下这些寄存器:
控制寄存器 1(TIMx_CR1)
TIM1的控制寄存器1描述如图21.3.1.1所示:
图21.3.1.1 TIMx_CR1寄存器
位7(APRE)用于控制自动重载寄存器是否具有缓冲作用,在基本定时器的时候已经讲过,请回顾。本实验中,我们把该位置1。
CEN位,用于使能计数器的工作,必须要设置该位为1,才可以开始计数。
其它位保持复位值即可。
捕获/比较模式寄存器1/2(TIMx_CCMR1/2)
TIM1的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx_CCMR1和TIMx _CCMR2。TIMx_CCMR1控制CH1和CH2,而TIMx_CCMR2控制CH3和CH4。TIMx_CCMR1寄存器描述如图21.3.1.2所示:
图21.3.1.2 TIMx_CCMR1寄存器
该寄存器的有些位在不同模式下,功能不一样,我们现在用到输出比较模式。关于该寄存器的详细说明,请参考《STM32H7Rx参考手册_V6(英文版).pdf》第1733页,41.6.7节。
本实验我们用到了定时器1输出比较的2个通道,所以我们需要配置TIM1_CCMR1和TIM1_CCMR2。以TIM1_CCMR1寄存器为例,模式设置位OC1M[3:0]就是对应着通道1的模式设置,此部分由4位组成,总共可以配置成14种模式,我们使用的是翻转功能,所以这4位必须设置为0011。通道2也是如此,将位OC2M[3:0] 设置为0011。通道3和通道4就要设置TIM1_CCMR2寄存器的位OC3M[3:0]和位OC4M[3:0]。除此之外,我们还要设置输出比较的预装载使能位,如通道1对应输出比较的预装载使能位OC1PE置1,其他通道也要把相应位置1。
捕获/比较使能寄存器(TIMx_ CCER)
TIM1的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关和极性。TIMx_CCER寄存器描述如图21.3.1.3所示:
图21.3.1.3 TIMx_CCER寄存器
该寄存器比较简单,要让TIM1的的2个通道都输出,我们需要把对应的捕获/比较1输出使能位置1。通道1到通道2的使能位分别是:CC1E和CC2E,我们把这2个位置1,使能通道输出。
捕获/比较寄存器1/2(TIMx_ CCR1/2)
捕获/比较寄存器(TIMx_ CCR1/2),该寄存器总共有4个,对应4个通道CH1~CH4。本实验只用2个通道,以通道1对应的TIMx_ CCR1寄存器为例,其描述如下图所示:
图21.3.1.4 TIMx_ CCR1寄存器
在本实验中,我们通过改变TIMx_ CCR1/2寄存器的值来改变这2个通道输出的PWM的相位。
断路和死区寄存器(TIMx_ BDTR)
本实验用的是高级定时器,所以我们还需要配置:断路和死区寄存器(TIMx_BDTR),该寄存器各位描述如图21.3.1.5所示:
图21.3.1.5 TIMx_ BDTR寄存器
该寄存器,我们只需要关注位15(MOE),要想高级定时器的通道正常输出,则必须设置MOE位为1,否则不会有输出。
21.3.2 硬件设计
1. 例程功能
使用输出比较模式的翻转功能,通过定时器1的2路通道输出占空比固定为50%、相位分别是25%和50%的PWM。
2. 硬件资源
1)LED灯
LED:LED1 – PC0
2)PC2复用为TIM1_CH1
PC3复用为TIM1_CH2
3. 原理图
定时器属于STM32H7R7的内部资源,只需要软件设置好即可正常工作。我们需要通过示波器观察PC2和PC3引脚PWM输出的情况。
21.3.3 程序设计
21.3.3.1 定时器的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)开启TIMx和通道输出的GPIO时钟,配置该IO口的复用功能输出
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器1通道1\2,对应IO是PC2\PC3,它们的时钟开启方法如下:
- __HAL_RCC_TIM1_CLK_ENABLE(); /* 使能定时器1 */
- __HAL_RCC_GPIOC_CLK_ENABLE(); /* 开启GPIOC时钟 */
复制代码 IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化TIMx,设置TIMx的ARR和PSC等参数
使用定时器的输出比较模式时,我们调用的是HAL_TIM_OC_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用HAL_TIM_OC_MspInit函数,我们可以通过后者存放定时器和GPIO时钟使能、GPIO初始化、中断使能以及优先级设置等代码。
3)设置定时器为输出比较模式,输出比较极性,输出比较值、翻转功能等参数
在HAL库中,通过HAL_TIM_OC_ConfigChannel函数来设置定时器为输出比较模式,根据需求设置输出比较的极性,设置输出比较值、翻转功能等。
最后我们通过HAL_TIM_ENABLE_OCxPRELOAD函数使能通道的预装载。
4)开启定时器并输出PWM
通过HAL_TIM_OC_Start函数使能定时器并开启输出。
21.3.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。高级定时器驱动源码包括两个文件:atim.c和atim.h。
首先看atim.h头文件的几个宏定义:
- #define ATIM_TIMX TIM1
- #define ATIM_TIMX_CLK_ENABLE() do { __HAL_RCC_TIM1_CLK_ENABLE(); } while (0)
- #define ATIM_TIMX_CH1_GPIO_PORT GPIOC
- #define ATIM_TIMX_CH1_GPIO_PIN GPIO_PIN_2
- #define ATIM_TIMX_CH1_GPIO_AF GPIO_AF1_TIM1
- #define ATIM_TIMX_CH1_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOC_CLK_ENABLE(); } while (0)
- #define ATIM_TIMX_CH2_GPIO_PORT GPIOC
- #define ATIM_TIMX_CH2_GPIO_PIN GPIO_PIN_3
- #define ATIM_TIMX_CH2_GPIO_AF GPIO_AF1_TIM1
- #define ATIM_TIMX_CH2_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOC_CLK_ENABLE(); } while (0)
复制代码 可以把上面的宏定义分成两部分,第一部分是定时器1的相应宏定义。第二部分则是定时器1输出通道1和通道2对应的IO口的宏定义。
下面来看到atim.c文件的程序,首先是高级定时器输出比较模式初始化函数,其定义如下:
- /**
- * @brief 初始化高级定时器输出比较
- * @param arr: 自动重装载值
- * @param psc: 预分频系数
- * @retval 无
- */
- void atim_timx_comp_init(uint16_t arr, uint16_t psc)
- {
- TIM_OC_InitTypeDef timx_oc_init_struct = {0};
-
- /* 初始化TIM输出比较 */
- g_atimx_handle.Instance = ATIM_TIMX;
- g_atimx_handle.Init.Prescaler = psc; /* 预分频系数 */
- g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 计数模式 */
- g_atimx_handle.Init.Period = arr; /* 重装载值 */
- g_atimx_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频 */
- g_atimx_handle.Init.RepetitionCounter = 0; /* 重复计数数值 */
- g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
- /* 自动重装载预加载模式 */
- HAL_TIM_OC_Init(&g_atimx_handle);
-
- /* 配置TIM输出比较通道 */
- timx_oc_init_struct.OCMode = TIM_OCMODE_TOGGLE; /* 输出比较模式 */
- timx_oc_init_struct.Pulse = (((arr + 1) >> 2) * 1) - 1; /* 比较值 */
- timx_oc_init_struct.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出比较极性 */
- HAL_TIM_OC_ConfigChannel(&g_atimx_handle, &timx_oc_init_struct,
- TIM_CHANNEL_1);
-
- /* 配置TIM输出比较通道 */
- timx_oc_init_struct.OCMode = TIM_OCMODE_TOGGLE; /* 输出比较模式 */
- timx_oc_init_struct.Pulse = (((arr + 1) >> 2) * 3) - 1; /* 比较值 */
- timx_oc_init_struct.OCPolarity = TIM_OCPOLARITY_HIGH; /* 输出比较极性 */
- HAL_TIM_OC_ConfigChannel(&g_atimx_handle, &timx_oc_init_struct,
- TIM_CHANNEL_2);
-
- /* 开启TIM输出比较输出 */
- HAL_TIM_OC_Start(&g_atimx_handle, TIM_CHANNEL_1);
- HAL_TIM_OC_Start(&g_atimx_handle, TIM_CHANNEL_2);
- }
复制代码 在atim_timx_comp_init 函数中,首先是使能定时器和通道对应的IO时钟,初始化IO口。然后是调用HAL_TIM_OC_Init函数初始化定时器的ARR和PSC等参数。接着通过调用函数HAL_TIM_OC_ConfigChannel设置通道1和通道2的工作参数,包括:输出比较模式功能、输出比较寄存器的值,输出极性等。最后通过调用函数HAL_TIM_OC_Start来使能TIM1通道1和通道2输出。
在main.c里面编写如下代码:
- int main(void)
- {
- uint8_t t = 0;
-
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(240, 5, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- atim_timx_comp_init(1000 - 1, 300 - 1); /* 初始化高级定时器输出比较 */
-
- while (1)
- {
- if (++t == 20)
- {
- t = 0;
- LED1_TOGGLE();
- }
-
- delay_ms(10);
- }
- }
复制代码 本小节开头我们讲解了输出比较模式翻转功能如何产生PWM,下面结合程序一起计算出PWM的周期,频率等参数。
定时器1时钟源的时钟频率为2倍的APB2总线时钟频率,即300MHz,而调用atim_timx_comp_init (1000 - 1, 300 - 1)初始化函数之后,就相当于写入预分频寄存器的值为299,写入自动重载寄存器的值为999。将这些参数代入本小节介绍的翻转功能输出的PWM周期计算公式,可得:
T =2* ((arr+1)*(psc+1))/ Tclk = 2*((999+1)*(299+1))/ 300000000 = 0.002s 由上述式子得到PWM周期为2ms,频率为500Hz。ARR值为固定为1000,所以占空比则固定为50%。定时器1通道1~通道2输出的PWM波的相位分别是:25%和50%。
21.3.4 下载验证
下载代码后,可以看到LED1在闪烁,说明程序已经正常在跑了。 我们需要借助示波器观察PC6和PC7引脚PWM输出的情况,我们已经把示波器的显示内容截图出来,如图21.3.4.1所示:
图21.3.4.1 相位为25%和50%的PWM波
图21.3.4.1中,由上到下分别是引脚PC2和PC3输出的PWM,即分别对应的是TIM1_CH1和TIM1_CH2输出的相位为25%和50%的PWM。大家可以把其中一个通道的捕获/比较寄存器的值设置为0,那么就可以得到PWM初相位的波形,即相位为0%。
21.4 高级定时器互补输出带死区控制实验
本小节我们来学习使用高级定时器的互补输出带死区控制功能。对于刚接触这个知识的朋友可能会问:什么是互补输出?还带死区控制?What?下面给大家简单说一下。
图21.4.1 互补输出
上图中,CH1输出黄色的PWM,它的互补通道CH1N输出绿色的PWM。通过对比,可以知道这两个PWM刚好是反过来的,CH1的PWM为高电平期间,CH1N的PWM则是低电平,反之亦然,这就是互补输出。
下面来看一下什么是带死区控制的互补输出?
图21.4.2 带死区控制的互补输出
上图中,CH1输出的PWM和CH1N输出的PWM在高低电平转换间,插入了一段时间才实现互补输出。这段时间称为死区时间,可以通过DTG[7:0]位配置控制死区时间的长度,后面会详细讲解如何配置死区时间。上图中,箭头指出的两段死区时间的长度是一样的,因为都是由同一个死区发生器产生。
理解了互补输出和带死区控制的互补输出,下面来看一下带死区控制的互补输出有什么用?带死区控制的互补输出经常被用于控制电机的H桥中,下面给大家画了一个H桥的简图:
图21.4.3 H桥简图
图21.4.3是H桥的简图,实际控制电机正反转的H桥会根据复杂些,而且更多的是使用MOS管,这里只是为了解释带死区控制的互补输出在H桥中的控制逻辑原理,大家理解原理就行。上图的H桥搭建全部使用的是NPN,并且导通逻辑都是基极为高电平时导通。如果Q1和Q4三极管导通,那么电机的电流方向是从左到右(假设电机正转),如果Q2和Q3三极管导通,那么电机的电流方向是从右到左(假设电机反转)。上述就是H桥控制电机正反转的逻辑原理。但是同一侧的三极管是不可以同时导通的,否则会短路,比如:Q1和Q2同时导通或者Q3和Q4同时导通,这都是不可取的。
下面大家想一下图21.4.1的OC1(CH1)和OC1N(CH1N)输出的PWM输入到图21.4.3的H桥中,会怎样?按理来说应该是OC1N输出高电平的时候,OC1输出就是低电平,刚好Q2和Q3导通,电机的电流方向是从右到左(假设电机反转);反之,OC1输出高电平的时候,OC1N输出就是低电平,刚好Q1和Q4导通,电机的电流方向是从左到右(假设电机正转),这似乎已经完美解决电机正反转问题了。实际上,元器件是有延迟特性的,比如:控制信号从OC1传导至电机,是要经过一定的时间的,复杂的H桥电路更是如此。由于元器件特性,就会导致直接使用互补输出信号驱动H桥时存在短路现象。为了避免这种情况,于是就有了带死区控制的互补输出来驱动H桥电路。如图21.4.2的死区时间就是为了解决元器件延迟特性的。用户必须根据与输出相连接的器件及其特性(电平转换器的固有延迟、开关器件产生的延迟)来调整死区时间。
死区时间计算
下面来看一下定时器的死区时间是怎么计算并设置的?死区时间是由TIMx_CR1寄存器的CKD[1:0]位和TIMx_BDTR寄存器的DTG[7:0]位来设置,如下图所示:
图21.4.4 CKD[1:0]和DTG[7:0]位
死区时间计算分三步走:
第一步:通过CKD[1:0]位确定tDTS。根据CKD[1:0]位的描述,可以得到下面的式子:
其中:
CKD[1:0]:CKD[1:0]位设置的值。
Tclk:定时器的时钟源频率(单位为MHz)。
假设定时器时钟源频率是300MHz,我们设置CKD[1:0]位的值为2,代入上面的式子可得:
通过上式可得tDTS约等于13.33ns,本实验例程中我们也是这样设置的。
第二步:根据DTG[7:5]选择计算公式。
第三步:代入选择的公式计算。
下面给大家举个例子,假设定时器时钟源频率是300MHz,我们设置CKD[1:0]位的值为2,DTG[7:0]位的值为250。从上面的例子知道CKD[1:0]位的值为2,得到的tDTS=13.33ns。下面来看一下DTG[7:0]位的值为250,应该选择DTG[7:0]位描述中哪条公式?250的二进制数为11111010,即DTG[7:5]为111,所以选择第四条公式:DT=(32+ DTG[4:0]) * t dtg,其中t dtg = 16 * tDTS。可以看到手册上的式子符号大小写乱乱的,这里大小写不敏感。由手册的公式可以得到DT = (32+ DTG[4:0]) * 16 * tDTS = (32+ 26) * 16 * 13.33ns = 12370.24ns = 12.37us,即死区时间为12.37us。死区时间计算方法就给大家介绍到这里。
关于互补输出和死区插入的更多内容请看《STM32H7Rx参考手册_V6(英文版).pdf》手册的41.3.19小节(1682页),下面我们介绍相关的寄存器。
21.4.1 TIM1寄存器
高级定时器互补输出带死区控制除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 之外。主要还用到以下这些寄存器:
控制寄存器 1(TIMx_CR1)
TIM1的控制寄存器1描述如图21.4.1.1所示:
图21.4.1.1 TIMx_CR1寄存器
位7(APRE)用于控制自动重载寄存器是否进行缓冲,在基本定时器的时候已经讲过,请回顾。本实验中,我们把该位置1。
CEN位,用于使能计数器的工作,必须要设置该位为1,才可以开始计数。
捕获/比较模式寄存器1/2(TIMx_CCMR1/2)
TIM1的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx_CCMR1和TIMx _CCMR2。TIMx_CCMR1控制CH1和CH2,而TIMx_CCMR2控制CH3和CH4。TIMx_CCMR1寄存器描述如图21.4.1.2所示:
图21.4.1.2 TIMx_CCMR1寄存器
该寄存器的有些位在不同模式下,功能不一样,我们现在用到输出比较模式。关于该寄存器的详细说明,请参考《STM32H7Rx参考手册_V6(英文版).pdf》第1732页,41.6.7节。
本实验我们用到了定时器1输出比较的通道1,所以我们需要配置TIM1_CCMR1模式设置位OC1M[3:0],我们使用的是PWM模式1,所以这4位必须设置为0110。
捕获/比较使能寄存器(TIMx_ CCER)
TIM1的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关和极性。TIMx_CCER寄存器描述如图21.4.1.3所示:
图21.4.1.3 TIMx_CCER寄存器
该寄存器比较简单,要让TIM1的通道1输出,我们需要把对应的捕获/比较1输出使能位CC1E置1。因为本实验中,我们需要实现互补输出,所以还需要把CC1NE位置1,使能互补通道输出。CC1P和CC1NP分别是通道1输出和通道1互补输出的极性设置位。这里我们把CC1P和CC1NP位都置1,即输出极性为低,就可以得到互补的PWM。
捕获/比较寄存器1/2/3/4(TIMx_CCR1/2/3/4)
捕获/比较寄存器(TIMx_ CCR1/2/3/4),该寄存器总共有4个,对应4个通道CH1~CH4。我们使用的是通道1,所以来看看TIMx_ CCR1寄存器描述如图21.4.1.4所示:
图21.4.1.4 TIMx_ CCR1寄存器
对于TIM1来说,该寄存器16位有效位,本实验中可以通过改变该寄存器的值来改变PWM的占空比。
断路和死区寄存器(TIMx_ BDTR)
TIM1断路和死区寄存器,该寄存器各位描述如图21.4.1.5所示:
图21.4.1.5 TIMx_ BDTR寄存器
该寄存器控制定时器的断路和死区控制的功能。我们先看断路控制,高级定时器TIM1有两个断路通道,本实验我们用到断路1(断路输入引脚为PC2)。要使用断路1就要使能位BKE,置1即可。
位BKP选择断路输入信号有效电平。本实验中,我们选择高电平有效,即BKP置1。
位AOE是自动输出使能位,如果使能AOE位,那么在我们输入刹车信号后再断开了刹车信号,互补的PWM会自动恢复输出,如果失能AOE位,那么在输入刹车信号后再断开了刹车信号,互补的PWM就不会恢复输出,而是一直保持刹车信号输入时的状态。为了方便观察,我们使能该位,即置1。
BKF[3:0]是断路输入信号的滤波器,这里我们不需要用滤波器,所以这4个位置0。
要使用断路2功能可以参考断路1的配置即可。
位MOE是使能主输出,想要高级定时器的通道正常输出,则必须设置MOE位为1。
最后是DTG[7:0]位,用于设置死区时间,前面已经教过大家怎么设置了。这里以我们例程的设置为例,CKD[1:0] 设置为10,定时器时钟源频率是300MHz,所以tDTS = 13.33ns。
本例程的DTG[7:0]位的值设置为十进制100,即二进制数0110 0100。DTG[7:5]=011,符合第一条式子:DT=DTG[7:0] * t dtg,其中 t dtg = tDTS。DT是死区时间,可以得到DT = 100*13.33ns = 1.33us。到后面下载验证小节,我们通过示波器验证一下这个死区时间计算的理论值和实际值是否一样。
21.4.2 硬件设计
1. 例程功能
1,利用TIM1_CH1(PC2)输出70%占空比的PWM,它的互补输出通道(PE8)则是输出30%占空比的PWM。
2,刹车功能,当给刹车输入引脚(PA6)输入高电平时,进行刹车,即PC2和PE8停止输出PWM。
3,LED0闪烁指示程序运行。
2. 硬件资源
1)LED灯
LED:LED1 – PC0
2)定时器1
TIM1正常输出通道 PC2
TIM1互补输出通道 PE8
TIM1刹车输入通道 PA6
3. 原理图
定时器属于STM32H7R7的内部资源,只需要软件设置好即可正常工作。我们需要通过示波器观察PC2和PE8引脚PWM输出的情况。还可以通过给PA6引脚接入高电平进行刹车。
21.4.3 程序设计
21.4.3.1 定时器的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 BreakAFMode; /* 断路输入的备用功能模式 */
- uint32_t Break2State; /* 断路2输入使能控制 */
- uint32_t Break2Polarity; /* 断路2输入极性 */
- uint32_t Break2Filter; /* 断路2输入滤波器 */
- uint32_t Break2AFMode; /* 断路2输入的备用功能模式 */
- uint32_t AutomaticOutput; /* 自动恢复输出使能控制 */
- } TIM_BreakDeadTimeConfigTypeDef;
复制代码 函数返回值:
HAL_StatusTypeDef枚举类型的值。
2. HAL_TIMEx_PWMN_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枚举类型的值。
定时器互补输出带死区控制配置步骤
1)开启TIMx和通道输出以及刹车输入的GPIO时钟,配置该IO口的复用功能输出
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器1通道1,对应IO是PC2,互补输出通道引脚是PE8,刹车输入引脚是PA6,它们的时钟开启方法如下:
- __HAL_RCC_TIM1_CLK_ENABLE(); /* 使能定时器1 */
- __HAL_RCC_GPIOC_CLK_ENABLE(); /* 开启GPIOC时钟 */
复制代码 IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化TIMx,设置TIMx的ARR和PSC等参数
这里我们要使用定时器的PWM模式功能,所以调用的是HAL_TIM_PWM_Init函数来初始化定时器ARR和PSC等参数。注意:本实验要使用该函数配置TIMx_CR1寄存器的CKD[1:0]位,从而确定t DTS,方便后续设置死区时间。
注意:该函数会调用:HAL_TIM_PWM_MspInit函数,但是为不跟前面的实验共用该回调函数,提高独立性,我们就直接在atim_timx_cplm_init函数中,使能定时器时钟和GPIO时钟,初始化通道对应IO引脚等。
3)设置定时器为PWM模式,输出比较极性,互补输出极性等参数
通过HAL_TIM_PWM_ConfigChannel函数来设置定时器为PWM1模式,根据需求设置OCy输出极性和OCyN互补输出极性等。
4)设置死区参数
通过HAL_TIMEx_ConfigBreakDeadTime函数来设置死区参数,比如:设置死区时间、运行模式的关闭输出状态、空闲模式的关闭输出状态、刹车输入有效信号极性、刹车输入信号滤波设置、是否使用断路2和是否允许刹车后自动恢复输出等。
5)启动Ocy输出以及OCyN互补输出
通过HAL_TIM_PWM_Start函数启动OCy输出,通过HAL_TIMEx_PWMN_Start函数启动启动OCyN互补输出。
21.4.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。高级定时器驱动源码包括两个文件:atim.c和atim.h。
首先看atim.h头文件的几个宏定义:
- #define ATIM_TIMX TIM1
- #define ATIM_TIMX_CLK_ENABLE() do { __HAL_RCC_TIM1_CLK_ENABLE();} while (0)
- #define ATIM_TIMX_CHY TIM_CHANNEL_1
- #define ATIM_TIMX_CHY_GPIO_PORT GPIOC
- #define ATIM_TIMX_CHY_GPIO_PIN GPIO_PIN_2
- #define ATIM_TIMX_CHY_GPIO_AF GPIO_AF1_TIM1
- #define ATIM_TIMX_CHY_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOC_CLK_ENABLE();} while (0)
- #define ATIM_TIMX_CHYN_GPIO_PORT GPIOE
- #define ATIM_TIMX_CHYN_GPIO_PIN GPIO_PIN_8
- #define ATIM_TIMX_CHYN_GPIO_AF GPIO_AF1_TIM1
- #define ATIM_TIMX_CHYN_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOE_CLK_ENABLE();} while (0)
- #define ATIM_TIMX_BKIN_GPIO_PORT GPIOA
- #define ATIM_TIMX_BKIN_GPIO_PIN GPIO_PIN_6
- #define ATIM_TIMX_BKIN_GPIO_AF GPIO_AF1_TIM1
- #define ATIM_TIMX_BKIN_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOA_CLK_ENABLE();} while (0)
复制代码 可以把上面的宏定义分成两部分,第一部分包括是定时器1输出、互补输出和刹车输入通道对应的IO口的宏定义,第二部分则是定时器1的相应宏定义。
下面来看atim.c文件的程序,首先是高级定时器互补输出初始化函数,其定义如下:
- /**
- * @brief 初始化高级定时器互补输出带死区控制
- * @param arr: 自动重装载值
- * @param psc: 预分频系数
- * @retval 无
- */
- void atim_timx_cplm_init(uint16_t arr, uint16_t psc)
- {
- TIM_OC_InitTypeDef timx_oc_struct = {0};
-
- /* 初始化TIM PWM */
- g_atimx_handle.Instance = ATIM_TIMX;
- g_atimx_handle.Init.Prescaler = psc; /* 预分频系数 */
- g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 计数模式 */
- g_atimx_handle.Init.Period = arr; /* 重装载值 */
- g_atimx_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4; /* 时钟分频 */
- g_atimx_handle.Init.RepetitionCounter = 0; /* 重复计数数值 */
- g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
- /* 自动重装载预加载模式 */
- HAL_TIM_PWM_Init(&g_atimx_handle);
-
- /* 配置TIM PWM通道 */
- timx_oc_struct.OCMode = TIM_OCMODE_PWM1; /* PWM模式 */
- timx_oc_struct.Pulse = (arr + 1) >> 1; /* 占空比 */
- timx_oc_struct.OCPolarity = TIM_OCPOLARITY_LOW; /* 有效电平 */
- timx_oc_struct.OCNPolarity = TIM_OCNPOLARITY_LOW; /* 互补通道有效电平 */
- timx_oc_struct.OCIdleState = TIM_OCIDLESTATE_SET; /* 空闲有效电平 */
- timx_oc_struct.OCNIdleState = TIM_OCNIDLESTATE_SET; /* 互补通道空闲有效电平 */
- HAL_TIM_PWM_ConfigChannel(&g_atimx_handle, &timx_oc_struct, ATIM_TIMX_CHY);
-
- /* 配置TIM死区控制参数 */
- g_break_dead_time_config_struct.OffStateRunMode = TIM_OSSR_DISABLE;
- /* 运行模式的关闭输出状态 */
- g_break_dead_time_config_struct.OffStateIDLEMode = TIM_OSSI_DISABLE;
- /* 空闲模式的关闭输出状态 */
- g_break_dead_time_config_struct.LockLevel = TIM_LOCKLEVEL_OFF;
- /* 寄存器锁 */
- g_break_dead_time_config_struct.BreakState = TIM_BREAK_ENABLE;
- /* 刹车输入使能 */
- g_break_dead_time_config_struct.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
- /* 刹车输入有效极性 */
- g_break_dead_time_config_struct.AutomaticOutput =
- TIM_AUTOMATICOUTPUT_ENABLE; /* 自动使能输出 */
- HAL_TIMEx_ConfigBreakDeadTime(&g_atimx_handle,
- &g_break_dead_time_config_struct);
-
- /* 开启TIM PWM输出 */
- HAL_TIM_PWM_Start(&g_atimx_handle, ATIM_TIMX_CHY);
- HAL_TIMEx_PWMN_Start(&g_atimx_handle, ATIM_TIMX_CHY);
- }
复制代码 在atim_timx_cplm_init函数中,没有使用HAL库的MSP回调,而是把相关的初始化都写到该函数里面。
第一部分,使能定时器和相关通道对应的GPIO时钟,以及初始化相关IO引脚。
第二部分,通过HAL_TIM_PWM_Init函数初始化定时器的ARR和PSC等参数。
第三部分,通过HAL_TIM_PWM_ConfigChannel函数设置PWM模式1、输出极性,以及输出空闲状态等。
第四部分,通过HAL_TIMEx_ConfigBreakDeadTime函数配置断路功能。
最后一定记得要调用HAL_TIM_PWM_Start函数和HAL_TIMEx_PWMN_Start函数启动通道输出和互补通道输出。
为了方便,我们还定义了设置输出比较值和死区时间的函数,其定义如下:
- /**
- * @brief 设置高级定时器互补输出带死区控制
- * @param ccr: 输出比较值
- * @param dtg: 死区时间
- * @retval 无
- */
- void atim_timx_cplm_set(uint16_t ccr, uint8_t dtg)
- {
- g_break_dead_time_config_struct.DeadTime = dtg; /* 死区时间 */
- HAL_TIMEx_ConfigBreakDeadTime(&g_atimx_handle,
- &g_break_dead_time_config_struct); /* 配置死区时间参数 */
- __HAL_TIM_MOE_ENABLE(&g_atimx_handle); /* 使能主输出 */
- __HAL_TIM_SET_COMPARE(&g_atimx_handle, ATIM_TIMX_CHY, ccr);
- /* 设置比较寄存器 */
- }
复制代码 通过重新调用HAL_TIMEx_ConfigBreakDeadTime函数设置死区时间,注意这里的g_break_dead_time_config是全局结构体变量,在atim_timx_cplm_init函数已经初始化其他结构体成员了,这里只是对DeadTime成员(死区时间)配置。死区时间的计算方法前面已经讲解过,这里只要把要设置的DTG[7:0]值,通过dtg形参赋值给DeadTime结构体成员就行。另外一个形参是ccr,用于设置捕获/比较寄存器的值,即控制PWM的占空比。
在main.c里面编写如下代码:
- int main(void)
- {
- uint8_t t = 0;
-
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(240, 5, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- atim_timx_cplm_init(1000 - 1, 300 - 1);
- /* 初始化高级定时器互补输出带死区控制,PWM频率为1KHz */
- atim_timx_cplm_set(300 - 1, 100);
- /* 设置高级定时器互补输出带死区控制,占空比为30%,死区时间为100*tDTS */
-
- while (1)
- {
- if (++t == 20)
- {
- t = 0;
- LED1_TOGGLE();
- }
-
- delay_ms(10);
- }
- }
复制代码 先看atim_timx_cplm_init (1000 - 1, 300 - 1)这个语句,这两个形参分别设置自动重载寄存器的值为999,以及定时器预分频器寄存器的值为299。先看预分频系数,我们设置为300分频,定时器1的时钟源频率是2倍的APB2总线时钟频率,即300MHz,可以得到计数器的计数频率是1MHz,即每1us计数一次。再到自动重载寄存器的值为999决定的是PWM的频率(周期),可以得到PWM的周期为(999+1)*1us = 1000us。边沿对齐模式下,使用PWM模式1或者PWM模式2,得到的PWM周期是定时器溢出时间。这里的1000us,也可以直接通过定时器溢出时间计算公式Tout= ((arr+1)*(psc+1))/Tclk得到。
调用atim_timx_cplm_set (300, 100) 这个语句,相当于设置捕获/比较寄存器的值为300,DTG[7:0]的值为100。通过计算可以得到PWM的占空比为70%,死区时间为1.333us。根据PWM生成原理分析,再结合图21.3.2产生PWM示意图,以及我们在atim_timx_cplm_init函数配置PWM模式1、OCy输出极性为低,占空比的计算很简单,可以由(1000-300)/1000得到。
关于死区时间的计算方法,前面已经讲解过,这里以DTG[7:0]的值为100为例,再来讲解一遍计算过程。由前面讲解的内容知道,我们例程配置CKD[1:0]位的值为0,可以得到tDTS= 13.33ns。基于这个前提,通过改变DTG[7:0]的值,可以得到不同的死区时间。这里我们配置DTG[7:0]的值为100,即二进制数0110 0100,符合第一种情况dtg[7:5]=0xx时,死区时间DT = DTG [7:0] * tDTS。可以得到死区时间DT = 100*13.33 ns = 1.33us。
下面我们下载到开发板验证一下。
21.4.4 下载验证
下载代码后,可以看到LED1在闪烁,说明程序已经正常在跑了。 我们需要借助示波器观察PC2正常输出和PE8互补输出PWM的情况,示波器显示截图如图21.4.4.1所示:
图21.4.4.1 PC2正常输出和PE8互补输出PWM的情况
图21.4.4.1中的由上到下分别是PC2输出70%占空比的PWM波和PE8互补输出30%占空比的PWM波。互补输出的PWM波的正脉宽减去正常的PWM的负脉宽的值除以2就是死区时间,也可以是正常的PWM的正脉宽减去互补输出的PWM波的负脉宽的值除以2。我们使用第一种方法得到:死区时间 =(300–298)/2 us= 1us。与我们理论计算得到的值1.33us差不多,这样的误差是正常的。
要是不相信,我们再举个例子,我们把调用的函数改为atim_timx_cplm_set(300, 250),即配置DTG[7:0]的值为250,这个例子的计算过程在本实验前面死区时间计算的内容讲过,这里就不再赘述。经过计算得到死区时间DT =12.37us。修改好后,示波器显示截图如下图所示:
图21.4.4.2 PC2正常输出和PE8互补输出PWM的情况
由图21.4.4.2可得到,死区时间DT =(311–287)/2 us= 12us。与我们理论计算得到的值12.37us也是差不多的,误差在正常范围。由此证明我们的死区时间设置是没有问题。
刹车功能验证:当给刹车输入引脚(PA6)接入高电平(这里直接用杜邦线把PA6连接到3.3V)时,就会进行刹车,MOE位被硬件清零,如图21.4.4.3所示:
图21.4.4.3 刹车后的输出情况
从上图可以看到PC2和PE8输出的都是高电平,符合我们预期的设置。
另外因为我们使能了AOE位(即把该位置1),如果刹车输入为无效极性时,MOE位在发生下一个更新事件时自动置1,恢复运行模式(即继续输出PWM)。因此当停止给PA6接入高电平(拔掉之前连接的杜邦线),PWM会自动恢复输出。
21.5 高级定时器PWM输入模式实验
本小节我们来学习使用高级定时器PWM输入模式,此模式是输入捕获模式的一个特例。PWM输入模式经常被应用于测量PWM脉宽和频率。PWM输入模式在《STM32H7Rx参考手册_V6(英文版).pdf》手册1659页有详细的文字描述。下面我们结合这些文字,配合高级定时器框图给大家介绍PWM输入的工作原理。
图21.5.1 PWM输入模式工作原理示意图
第一,确定定时器时钟源。本实验中我们使用内部时钟(tim_ker_ck),高级定时器挂载在APB2总线上,按照sys_stm32_clock_init函数的配置,定时器时钟频率为2倍APB2总线时钟频率,即300MHz。计数器的计数频率确定了测量的精度。
第二,确定PWM输入的通道。PWM输入模式下测量PWM,PWM信号输入只能从通道1(CH1)或者通道2(CH2)输入。
第三,确定tim_ic1和tim_ic2的捕获边沿。这里以通道1(CH1)输入PWM为例,一般我们习惯设置IC1捕获边沿为上升沿捕获,IC2捕获边沿为下降沿捕获。
第四,选择触发输入信号(TRGI)。这里也是以通道1(CH1)输入PWM为例,那么我们就应该选择tim_ti1fp1为触发输入信号。如果是通道2(CH2)输入PWM,那就选择tim_ti2fp2为触发输入信号。可以看到这里并没有对应通道3(CH3)或者通道4(CH4)的触发输入信号,所以我们只选择通道1或者通道2作为PWM输入的通道。
第五,从模式选择:复位模式。复位模式的作用是:在出现所选触发输入 (TRGI) 上升沿时,重新初始化计数器并生成一个寄存器更新事件。
第六,读取一个PWM周期内计数器的计数个数,以及高电平期间的计数个数,再结合计数器的计数周期(即计一个数的时间),最终通过计算得到输入的PWM周期和占空比等参数。以通道1(CH1)输入PWM,设置tim_ic1捕获边沿为上升沿捕获,tim_ic2捕获边沿为下降沿捕获为例,那么CCR1寄存器的值+1就是PWM周期内计数器的计数个数,CCR2寄存器的值+1就是PWM高电平期间计数器的计数个数。通过这两个值就可以计算出PWM的周期或者占空比等参数。
再举个例子,以通道1(CH1)输入PWM,设置tim_ic1捕获边沿为下降沿捕获,tim_ic2捕获边沿为上升沿捕获为例,那么CCR1寄存器的值+1依然是PWM周期内计数器的计数个数,但是CCR2寄存器的值+1就是PWM低电平期间计数器的计数个数。通过这两个得到的参数依然可以计算出PWM的其它参数。这个大家了解一下就可以了,一般我们使用第六介绍的例子。
通过上面的描述,如果大家还不理解,下面我们结合PWM输入模式时序来分析一下。PWM输入模式时序图如图21.5.2所示:
图21.5.2 PWM输入模式时序图
图21.5.2是以通道1(CH1)输入PWM,设置tim_ic1捕获边沿为上升沿捕获,tim_ic2捕获边沿为下降沿捕获为例的PWM输入模式时序图。
从时序图可以看出,计数器的计数模式是递增计数模式。从左边开始看,当tim_ti1来了上升沿时,计数器的值被复位为0(原因是从模式选择为复位模式),tim_ic1和tim_ic2都发生捕获事件。然后计数器的值计数到2的时候,tim_ic2发生了下降沿捕获,捕获事件会导致这时候的计数器的值被锁存到CCR2寄存器中,该值+1就是高电平期间计数器的计数个数。最后计数器的值计数到4的时候,tim_ic1发生了上升沿捕获,捕获事件会导致这时候的计数器的值被锁存到CCR1寄存器中,该值+1就是PWM周期内计数器的计数个数。
假设计数器的计数频率是300MHz,那我们就可以计算出这个PWM的周期、频率和占空比等参数了。下面就以这个为例给大家计算一下。由计数器的计数频率为300MHz,可以得到计数器计一个数的时间是3.33ns(即测量的精度是3.33ns)。知道了测量精度,再来计算PWM的周期,PWM周期 =(4+1)*(1/300000000) = 16.67ns,那么PWM的频率就是48MHz。占空比 = (2+1)/(4+1) =3/5(即占空比为60%)。
21.5.1 TIM1寄存器
高级定时器PWM输入模式实验除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR) 之外。主要还用到以下这些寄存器:
从模式控制寄存器(TIMx_SMCR)
TIM1的从模式控制寄存器描述如图21.5.1.1所示:
图21.5.1.1 TIMx_SMCR寄存器
该寄存器的SMS[3:0]位,用于从模式选择。比如本实验中我们要用复位模式,所以设置SMS[3:0]=0100。TS[2:0]位是触发选择,我们设置为tim_ti1fp1,即TS[4:0]=00101。
捕获/比较模式寄存器1/2(TIMx_CCMR1/2)
TIM1的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx_CCMR1和TIMx _CCMR2。TIMx_CCMR1控制CH1和CH2,而TIMx_CCMR2控制CH3和CH4。TIMx_CCMR1寄存器描述如图21.5.1.2所示:
图21.5.1.2 TIMx_CCMR1寄存器
该寄存器的有些位在不同模式下,功能不一样,我们现在用到输入捕获模式。关于该寄存器的详细说明,请参考《STM32H7Rx参考手册_V6(英文版).pdf》第1458页,38.4.7节。
本实验我们通过定时器1通道1输入PWM信号,所以tim_ic1和tim_ic2都映射到tim_ti1。配置CC1S[1:0]=01、CC2S [1:0]=10,其他位不用设置,默认为0即可。
捕获/比较使能寄存器(TIMx_ CCER)
TIM1的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关和极性。TIMx_CCER寄存器描述如图21.5.1.3所示:
图21.5.1.3 TIMx_CCER寄存器
IC1捕获上升沿,所以CC1P位置0,即捕获发生在tim_ti1的上升沿。tim_ic2捕获下降沿,所以CC2P位置1,即捕获发生在tim_ti1的下降沿。设置好捕获边沿后,还需要使能这两个通道捕获,即CC1E和CC2E位置1。
捕获/比较寄存器1/2/3/4(TIMx_CCR1/2/3/4)
捕获/比较寄存器(TIMx_ CCR1/2/3/4),该寄存器总共有4个,对应4个通道CH1~CH4。我们使用的是通道1,所以来看看TIMx_ CCR1寄存器描述如图21.5.1.4所示:
图21.5.1.4 TIMx_ CCR1寄存器
本实验中,CCR1寄存器用于获取PWM周期内计数器的计数个数。CCR2寄存器用于获取PWM高电平期间计数器的计数个数。
DMA/中断使能寄存器(TIMx_DIER)
DMA/中断使能寄存器描述如图21.5.1.5所示:
图21.5.1.5 TIMx_DIER寄存器
该寄存器位0(UIE)用于使能或者禁止更新中断,因为本实验我们用到更新中断,所以该位需要置1。位1(CC1IE)用于使能或者禁止捕获/比较1中断,我们用到捕获中断,所以该位需要置1。
21.5.2 硬件设计
1. 例程功能
首先通过TIM4_CH3(PD14)输出PWM波。然后把PD14输出的PWM波用杜邦线接入PC2(定时器1通道1),最后通过串口打印PWM波的脉宽和频率等信息。LED1闪烁来提示程序正在运行。
2. 硬件资源
1)LED灯
LED: LED1– PC0
2)定时器4通道3(PD14)输出PWM波
定时器1通道1(PC2)输入PWM波
3. 原理图
定时器属于STM32H7R7的内部资源,只需要软件设置好即可正常工作。我们把PD14引脚输出的PWM波用杜邦线接入PC2引脚,然后通过电脑串口上位机软件观察打印出来的信息。
21.5.3 程序设计
定时器PWM输入模式实验用到的HAL库中的驱动代码在前面实验都有介绍过了。 我们在程序解析再详细讲解应用到的函数,下面介绍一下高级定时器PWM输入模式的配置步骤。
高级定时器PWM输入模式配置步骤
1)开启TIMx和输入通道的GPIO时钟,配置该IO口的复用功能输入
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器1通道1,对应IO是PC2,它们的时钟开启方法如下:
- __HAL_RCC_TIM1_CLK_ENABLE(); /* 使能定时器1 */
- __HAL_RCC_GPIOC_CLK_ENABLE(); /* 开启GPIOC时钟 */
复制代码 IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化TIMx,设置TIMx的ARR和PSC等参数
使用定时器的输入捕获功能时,我们调用的是HAL_TIM_IC_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用:HAL_TIM_IC_MspInit函数,但是为不跟前面的实验共用该回调函数,提高独立性,我们就直接在atim_timx_pwmin_chy_init函数中,使能定时器时钟和GPIO时钟,初始化通道对应IO引脚等。
3)从模式配置,IT1触发更新
通过HAL_TIM_SlaveConfigSynchro函数,配置从模式:复位模式、定时器输入触发源、边缘检测、是否滤波等。
4)设置IC1捕获相关参数
通过HAL_TIM_IC_ConfigChannel函数来设置定时器捕获通道1的工作方式,包括边缘检测极性、映射关系,输入滤波和输入分频等。
5)设置IC2捕获相关参数
通过HAL_TIM_IC_ConfigChannel函数来设置定时器捕获通道2的工作方式,包括边缘检测极性、映射关系,输入滤波和输入分频等。
6)使能定时器更新中断,开启捕获功能,配置定时器中断优先级
通过__HAL_TIM_ENABLE_IT函数使能定时器更新中断。
通过HAL_TIM_IC_Start_IT函数使能定时器并开启通道1或者通道2的捕获功能,使能捕获中断。
通过HAL_NVIC_EnableIRQ函数使能定时器中断。
通过HAL_NVIC_SetPriority函数设置中断优先级。
7)编写中断服务函数
H7R7系列的TIM1 有独立的输入捕获中断服务函数,分别是TIM1_CC_IRQHandler,其他定时器则没有,所以如果是TIM1可以直接使用输入捕获中断服务函数来处理输入捕获中断。在使用TIM1的时候,如果要考虑定时器1溢出,可以重定义更新中断服务函数TIM1_UP_IRQHandler。如果使用HAL库的中断回调机制,可以在相关中断服务函数中直接调用定时器中断公共处理函数HAL_TIM_IRQHandler,然后我们直接重定义相关的中断回调函数来编写中断程序即可。本实验中我们直接使用中断处理函数TIM1_CC_IRQHandler,里面同时处理捕获到的上升沿和下降沿触发的中断,具体看源码。
21.5.3.1 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。高级定时器驱动源码包括两个文件:atim.c和atim.h。
首先看atim.h头文件的几个宏定义:
- #define ATIM_TIMX TIM1
- #define ATIM_TIMX_IRQn TIM1_CC_IRQn
- #define ATIM_TIMX_IRQHandler TIM1_CC_IRQHandler
- #define ATIM_TIMX_CLK_ENABLE() do { __HAL_RCC_TIM1_CLK_ENABLE(); } while (0)
- #define ATIM_TIMX_CHY TIM_CHANNEL_1
- #define ATIM_TIMX_CHY_GPIO_PORT GPIOC
- #define ATIM_TIMX_CHY_GPIO_PIN GPIO_PIN_2
- #define ATIM_TIMX_CHY_GPIO_AF GPIO_AF1_TIM1
- #define ATIM_TIMX_CHY_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOC_CLK_ENABLE(); } while (0)
复制代码 可以把上面的宏定义分成两部分,第一部分针对TIM1有独立的捕获中断服务函数,需要单独定义,第二部分包括是定时器1通道1对应的IO口的宏定义。
下面看atim.c的程序,首先是高级定时器PWM输入模式初始化函数,其定义如下:
- /**
- * @brief 初始化高级定时器PWM输入
- * @note TIM1时钟源为timg2_ck
- * @param psc: 预分频系数
- * @retval 无
- */
- void atim_timx_pwmin_chy_init(uint16_t psc)
- {
- TIM_SlaveConfigTypeDef tim_slave_config_struct = {0};
- TIM_IC_InitTypeDef tim_ic_init_struct = {0};
-
- /* 初始化TIM输入捕获 */
- g_atimx_handle.Instance = ATIM_TIMX;
- g_atimx_handle.Init.Prescaler = psc; /* 预分频器系数 */
- g_atimx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 计数模式 */
- g_atimx_handle.Init.Period = 0xFFFF; /* 自动重装载值 */
- g_atimx_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;/* 时钟分频 */
- g_atimx_handle.Init.RepetitionCounter = 0; /* 重复计数数值 */
- g_atimx_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
- /* 自动重装载预加载模式 */
- HAL_TIM_IC_Init(&g_atimx_handle);
-
- /* 配置TIM从模式 */
- tim_slave_config_struct.SlaveMode = TIM_SLAVEMODE_RESET; /* 从模式 */
- tim_slave_config_struct.InputTrigger = TIM_TS_TI1FP1; /* 触发源 */
- tim_slave_config_struct.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
- /* 触发极性 */
- tim_slave_config_struct.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1;
- /* 触发分频 */
- tim_slave_config_struct.TriggerFilter = 0; /* 触发滤波 */
- HAL_TIM_SlaveConfigSynchro(&g_atimx_handle, &tim_slave_config_struct);
-
- /* 配置TIM输入捕获通道1 */
- tim_ic_init_struct.ICPolarity = TIM_ICPOLARITY_RISING; /* 极性 */
- tim_ic_init_struct.ICSelection = TIM_ICSELECTION_DIRECTTI; /* 输入选择 */
- tim_ic_init_struct.ICPrescaler = TIM_ICPSC_DIV1; /* 输入分频 */
- tim_ic_init_struct.ICFilter = 0; /* 输入滤波 */
- HAL_TIM_IC_ConfigChannel(&g_atimx_handle, &tim_ic_init_struct,
- TIM_CHANNEL_1);
-
- /* 配置TIM输入捕获通道2 */
- tim_ic_init_struct.ICPolarity = TIM_ICPOLARITY_FALLING; /* 极性 */
- tim_ic_init_struct.ICSelection = TIM_ICSELECTION_INDIRECTTI; /* 输入选择 */
- tim_ic_init_struct.ICPrescaler = TIM_ICPSC_DIV1; /* 输入分频 */
- tim_ic_init_struct.ICFilter = 0; /* 输入滤波 */
- HAL_TIM_IC_ConfigChannel(&g_atimx_handle, &tim_ic_init_struct,
- TIM_CHANNEL_2);
-
- /* 开启TIM中断模式输入捕获 */
- HAL_TIM_IC_Start_IT(&g_atimx_handle, TIM_CHANNEL_1);
- HAL_TIM_IC_Start_IT(&g_atimx_handle, TIM_CHANNEL_2);
- }
复制代码 在atim_timx_pwmin_chy_init函数中,没有使用HAL库的MSP回调,而是把相关的初始化都写到该函数里面。
第一部分,使能定时器和相关通道对应的GPIO时钟,以及初始化相关IO引脚。
第二部分,通过HAL_TIM_IC_Init函数初始化定时器的ARR和PSC等参数。
第三部分,通过HAL_TIM_SlaveConfigSynchro函数配置从模式,复位模式等。
第四部分,通过HAL_TIM_IC_ConfigChannel函数分别配置IC1和IC2。
第五部分,配置NVIC,如使能定时器中断,配置抢占优先级和响应优先级。
最后,通过调用HAL_TIM_IC_Start_IT函数和__HAL_TIM_ENABLE_IT函数宏使能捕获中断和更新中断,并且使能定时器。
最后要介绍的是中断服务函数,在定时器1的输入捕 获中断服务函数TIM1_CC_IRQHandler。我们使用ATIM_TIMX_IRQHandler函数作为输入捕获中断服务函数的宏定义的,这个函数定义如下:
- /**
- * @brief TIM中断服务函数
- * @param 无
- * @retval 无
- */
- void ATIM_TIMX_IRQHandler(void)
- {
- HAL_TIM_IRQHandler(&g_atimx_handle);
- }
复制代码 ATIM_TIMX_IRQHandler输入捕获中断服务函数中同时输入捕获通道1和输入捕获通道2的中断,当通道1捕获到上升沿时,调用HAL_TIM_ReadCapturedValue函数获取捕获的PWM周期。当通道2捕获到下降沿时,调用HAL_TIM_ReadCapturedValue函数获取捕获的PWM的高电平脉宽,再把这两个数值赋值给两个全局变量,就能够在main函数中被使用了。
下面介绍一下待测试的PWM怎么得到。在实验9-2通用定时器PWM输出实验我们已经编写了PWM输出的程序,但是那个PWM的输出引脚是LED0的引脚。这里我们只需在gtim.h头文件添加一些宏定义就可以使PWM输出到PD14引脚。这些宏定义如下:
- #define GTIM_TIMX TIM4
- #define GTIM_TIMX_CLK_ENABLE() do { __HAL_RCC_TIM4_CLK_ENABLE(); } while (0)
- #define GTIM_TIMX_CHY TIM_CHANNEL_3
- #define GTIM_TIMX_CHY_GPIO_PORT GPIOD
- #define GTIM_TIMX_CHY_GPIO_PIN GPIO_PIN_14
- #define GTIM_TIMX_CHY_GPIO_AF GPIO_AF2_TIM4
- #define GTIM_TIMX_CHY_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOD_CLK_ENABLE(); } while (0)
复制代码 添加好这些宏定义后,要把PWM控制LED0的宏定义屏蔽掉。然后在main函数调用PWM输出初始化函数就可以使得PD14输出PWM波。然后我们用杜邦线把PD14和PC2连接起来。这样PD14输出的PWM就可以输入到PC2(定时器1 通道1)进行测量。
在main.c里面编写如下代码:
- int main(void)
- {
- uint8_t t = 0;
-
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(300, 6, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- gtim_timx_pwm_chy_init(100 - 1, 300 - 1);
- /* 初始化通用定时器PWM,PWM频率为10KHz */
- atim_timx_pwmin_chy_init(300 - 1); /* 初始化高级定时器PWM输入 */
-
- while (1)
- {
- /* 捕获成功 */
- if (g_timx_chy_pwmin_sta == 1)
- {
- g_timx_chy_pwmin_sta = 0;
-
- printf("高电平时间:%d us\r\n", g_timx_chy_pwmin_hval);
- printf("PWM周期:%d us\r\n", g_timx_chy_pwmin_cval);
- printf("PWM频率:%d Hz\r\n", 1000000 / g_timx_chy_pwmin_cval);
- printf("\r\n");
- }
-
- if (++t == 20)
- {
- t = 0;
- LED1_TOGGLE();
- }
-
- delay_ms(10);
- }
- }
复制代码 先看gtim_timx_pwm_chy_init(100 - 1, 300 - 1)这个语句,这两个形参分别设置自动重载寄存器的值为99,以及定时器预分频寄存器的值为299。先看预分频系数,我们设置为300分频,定时器4的时钟频率是2倍的APB1总线时钟频率,即300MHz,可以得到计数器的计数频率是1MHz,即1us计数一次。再到自动重载寄存器的值为99决定的是PWM波的频率(周期),可以得到PWM的周期为100*1us = 100us。然后在gtim_timx_pwm_chy_init这个函数中可知,占空比为(arr + 1) >> 1,即重载值100的二进制右移一位,也就是除以2,高低电平宽度各占一半,占空比为50%等于50us。即产生的PWM波高电平时间为50us,周期为100us,频率为10000Hz。下载验证的时候验证一下捕获到的与输出的是否一致。
atim_timx_pwmin_chy_init这个语句,就初始化PWM输入捕获。然后在无限循环中判断g_timx_chy_pwmin_sta标志变量是否捕获到数据,捕获到就打印和计数相关信息。
下面我们下载到开发板验证一下。
21.5.4 下载验证
下载代码后,可以看到LED1在闪烁,说明程序已经正常在跑了,我们再打开串口调试助手,选择对应的串口端口。然后用杜邦线把PD14引脚连接到PC2引脚,就可以看到串口助手不断打印PWM波的信息,如图21.5.4.1所示:
图21.5.4.1 打印高电平脉冲次数
可以看到打印出来的PWM波信息为:高电平时间是50us,周期是100us,频率是10000Hz和我们的预想结果一样。
大家可以通过gtim_timx_pwm_chy_init函数的形参设置其他参数的PWM波,以及GTIM_TIMX_CHY设置占空比。这里的测试的PWM波有一定的范围,不是全范围的PWM都可以进行准确的测试,大家可以进行验证。 |
|