OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2748|回复: 1

《STM32F407 探索者开发指南》第二十二章 高级定时器实验(上)

[复制链接]

1117

主题

1128

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4667
金钱
4667
注册时间
2019-5-8
在线时间
1224 小时
发表于 2023-7-17 12:01:29 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-7-17 12:01 编辑

第二十二章 高级定时器实验
1)实验平台:正点原子探索者STM32F407开发板

2) 章节摘自【正点原子】STM32F407开发指南 V1.1


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)STM32技术交流QQ群:151941872

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

本章我们主要来学习高级定时器,STM32F407有2个高级定时器(TIM1和TIM8)。我们将通过四个实验来学习高级定时器的各个功能,分别是高级定时器输出指定个数PWM实验、高级定时器输出比较模式实验、高级定时器互补输出带死区控制实验和高级定时器PWM输入模式实验。
本章分为如下几个小节:
22.1 高级定时器简介
22.2 高级定时器输出指定个数PWM实验
22.3 高级定时器输出比较模式实验
22.4 高级定时器互补输出带死区控制实验
22.5 高级定时器PWM输入模式实验

22.1 高级定时器简介
高级定时器的框图和通用定时器框图很类似,只是添加了其它的一些功能,如:重复计数器、带死区控制的互补输出通道、断路输入等。这些功能在高级定时器框图的位置如下:                                 
image001.png
图 22.1.1高级定时器框图

上图中,框出来三个部分,这是和通用定时器不同的地方,下面来分别介绍它们。

①   重复计数器
在F4系列中,高级定时器TIM1和TIM8都有重复计数器。下面来介绍一下重复计数器有什么作用?在学习基本定时器和通用定时器的时候,我们知道定时器发生上溢或者下溢时,会直接生成更新事件。但是有重复计数器的定时器并不完全是这样的,定时器每次发生上溢或下溢时,重复计数器的值会减一,当重复计数器的值为0时,再发生一次上溢或者下溢才会生成定时器更新事件。如果我们设置重复计数器寄存器RCR的值为N,那么更新事件将在定时器发生N+1次上溢或下溢时发生。

这里需要注意的是重复计数器寄存器是具有影子寄存器的,所以RCR寄存器只是起缓冲的作用。RCR寄存器的值会在更新事件发生时,被转移至其影子寄存器中,从而真正生效。

重复计数器的特性,在控制生成PWM信号时很有用,后面会有相应的实验。

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

如果使用互补通道,那么就有一定的区别了,具体我们在高级定时器互补输出带死区控制实验小节再来介绍。

③   断路功能
断路功能也称刹车功能,一般用于电机控制的刹车。F4系列有一个断路通道,断路源可以是刹车输入引脚(TIMx_BKIN),也可以是一个时钟失败事件。时钟失败事件由复位时钟控制器中的时钟安全系统产生。系统复位后,断路功能默认被禁止,MOE位为低。
使能断路功能的方法:将TIMx_BDTR的位BKE置1。断路输入引脚TIMx_BKIN的输入有效电平可通过TIMx_BDTR寄存器的位BKP设置。

使能刹车功能后:由TIMx_BDTR的MOE、OSSI、OSSR位,TIMx_CR2的OISx、OISxN位,TIMx_CCER的CCxE、CCxNE位控制OCx和OCxN输出状态。无论何时,OCx和OCxN输出都不能同时处在有效电平。

当发生断路输入后,会怎么样?
1,MOE位被异步地清零,OCx和OCxN为无效、空闲或复位状态(由OSSI位选择)。
2,OCx和OCxN的状态:由相关控制位状态决定,当使用互补输出时:根据情况自动控制输出电平,参考《STM32F4xx参考手册_V4(中文版).pdf》手册第382页的表73 具有断路功能的互补通道Ocx和OcxN的控制位。
3,BIF位置1,如果使能了BIE位,还会产生刹车中断;如果使能了TDE位,会产生DMA请求。
4,如果AOE位置1,在下一个更新事件UEV时,MOE位被自动置1。

高级定时器框图部分就简单介绍到这里,下面通过实际的实验来学习高级定时器。

22.2 高级定时器输出指定个数PWM实验
要实现定时器输出指定个数PWM,只需要掌握下面几点内容:
第一,如果大家还不清楚定时器是如何输出PWM的,请回顾通用定时器PWM输出实验的内容,这部分的知识是一样的。但是需要注意的是:我们需要把MOE位置1,这样高级定时器的通道才能输出。
第二,要清楚重复计数器特性,设置重复计数器寄存器RCR的值为N,那么更新事件将在定时器发生N+1次上溢或下溢时发生。换句话来说就是,想要指定输出N个PWM,只需要把N-1写入RCR寄存器。因为在边沿对齐模式下,定时器溢出周期对应着PWM周期,我们只要在更新事件发生时,停止输出PWM就行。
第三,为了保证定时器输出指定个数的PWM后,定时器马上停止继续输出,我们使能更新中断,并在定时器中断里关闭计数器。

原理部分我们就讲到这里,下面直接开始寄存器的介绍。

22.2.1 TIM1/TIM8寄存器
下面介绍TIM1/TIM8这些高级定时器中使用到的几个重要的寄存器,其他更多关于定时器的资料可以参考《STM32F4xx参考手册_V4(中文版).pdf》的第14章。

l  控制寄存器 1TIMx_CR1
TIM1/TIM8的控制寄存器1描述如图22.2.1.1所示:     
image003.png
图22.2.1.1 TIMx_CR1寄存器

上图中我们只列出了本章需要用的一些位,其中:位7(APRE)用于控制自动重载寄存器是否具有缓冲作用,在基本定时器的时候已经讲过,请回顾。在本实验中我们把该位要置1,这样就算改变ARR寄存器的值,该值也不会马上生效,而是等待之前设置的PWM完整输出后(发生更新事件)才生效。位4(DIR)用于配置计数器的计数方向,这里我们默认置0。位0(CEN),用于使能计数器的工作,必须要设置该位为1,才可以开始计数。

l 捕获/比较模式寄存器1/2(TIMx_CCMR1/2
TIM1/TIM8的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx _CCMR1和TIMx _CCMR2。TIMx_CCMR1控制CH1和CH2,而TIMx_CCMR2控制CH3和CH4。TIMx_CCMR1寄存器描述如图22.2.1.2所示:   
image005.png
图22.2.1.2 TIMx_CCMR1寄存器

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

l  捕获/比较使能寄存器(TIMx_CCER
TIM1/TIM8的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道的开关。TIMx_CCER寄存器描述如图22.2.1.3所示:   
image007.png
图22.2.1.3 TIMx_CCER寄存器

该寄存器比较简单,要让TIM1的CH1输出PWM波,这里我们要使能CC1E位,该位是通道1输入/输出使能位,要想PWM从IO口输出,这个位必须设置为1。CC1P位是设置通道1的输出极性,我们设置0,即OC1高电平有效。

l  事件产生寄存器(TIMx_EGR
TIM1/TIM8的事件产生寄存器,该寄存器作用是让用户用软件方式产生各类事件。TIMx_EGR寄存器描述如图22.2.1.4所示:   
image009.png
图22.2.1.4 TIMx_EGR寄存器

UG位是更新事件的控制位,作用和定时器溢出时产生的更新事件一样,区别是这里是通过软件产生的,而定时器溢出是硬件自己完成的。只有开启了更新中断,这两种方式都可以产更新中断。本实验用到该位去产生软件更新器事件,在需要的时候把UG位置1即可,会由硬件自动清零。

l  重复计数器寄存器(TIMx_RCR
重复计数器寄存器用于设置重复计数器值,因为它具有影子寄存器,所以它本身只是起缓冲作用。当更新事件发生时,该寄存器的值会转移到其影子寄存器中,从而真正起作用。TIMx_ RCR寄存器描述如图22.2.1.5所示:     
image011.png
图22.2.1.5 TIMx_RCR寄存器

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

l 捕获/比较寄存器1/2/3/4(TIMx_CCR1/2/3/4
捕获/比较寄存器(TIMx_CCR1/2/3/4),该寄存器总共有4个,对应4个通道CH1~CH4。我们使用的是通道1,所以来看看TIMx_CCR1寄存器的描述,如图22.2.1.6所示:   
image013.png
图22.2.1.6 TIMx_CCR1寄存器

在输出模式下,捕获/比较寄存器影子寄存器的值与CNT的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制PWM的占空比了。

l  断路和死区寄存器(TIMx_BDTR
高级定时器TIM1/8的通道用作输出时,还必须配置断路和死区寄存器(TIMx_BDTR)的位MOE,该寄存器各位描述如图22.3.1.7所示:     
image015.png
图22.3.1.7 TIMx_BDTR寄存器

本实验,我们只需要关注该寄存器的位15(MOE),要想高级定时器的PWM正常输出,则必须设置MOE位为1,否则不会有输出。

22.2.2 硬件设计
1. 例程功能
通过TIM8_CH1(由PC6复用)输出PWM,然后为了指示PWM的输出情况,我们用杜邦线将PC6和PF10引脚的排针连接起来,从而实现PWM输出控制LED1(硬件已连接在PF10引脚上)的亮灭。注意的点是:PF10要设置成浮空输入,避免引脚冲突,我们在main函数中设置好了,请看源码。上电默认输出5个PWM波,连接好杜邦线后可以看见LED1亮灭五次。之后按一下按键KEY0,就会输出5个PWM波控制LED1亮灭五次。LED0闪烁提示系统正在运行。

2. 硬件资源
1)LED灯:
LED0– PF9
LED1– PF10
2)独立按键:
KEY0– PE4
3)定时器8,使用TIM8通道1,由PC6复用。用杜邦线将PC6和PF10引脚连接起来。

3. 原理图
定时器属于STM32F407的内部资源,只需要软件设置好即可正常工作。我们通过LED1来指示STM32F407的定时器的PWM输出情况,所以需要用一根杜邦线连接PC6和PF10,同时还用按键KEY0进行控制。

22.2.3 程序设计
本实验用到的HAL库函数介绍请回顾通用定时器PWM输出实验。下面介绍一下定时器输出指定个数PWM的配置步骤。

定时器输出指定个数PWM配置步骤
1)开启TIMx和通道输出的GPIO时钟,配置该IO口的复用功能输出
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器8通道1,对应IO是PC6,它们的时钟开启方法如下:
  1. __HAL_RCC_TIM8_CLK_ENABLE();            /* 使能定时器8 */
  2. __HAL_RCC_GPIOC_CLK_ENABLE();           /* 开启GPIOC时钟 */
复制代码
IO口复用功能是通过函数HAL_GPIO_Init来配置的。

2)初始化TIMx设置TIMx的ARRPSC等参数
使用定时器的PWM模式功能时,我们调用的是HAL_TIM_PWM_Init函数来初始化定时器ARR和PSC等参数。
注意:该函数会调用:HAL_TIM_PWM_MspInit函数,我们可以通过后者存放定时器和GPIO时钟使能、GPIO初始化、中断使能以及优先级设置等代码。

3)设置定时器为PWM模式,输出比较极性,比较值等参数
在HAL库中,通过HAL_TIM_PWM_ConfigChannel函数来设置定时器为PWM1模式或者PWM2模式,根据需求设置输出比较的极性,设置比较值(控制占空比)等。
本实验我们设置TIM8的通道1为PWM1模式,使用杜邦线把PC6与PF10进行连接,因为我们的LED1(连接PF10)是低电平亮,而我们希望输出最后一个PWM波的时候,LED1就灭,所以我们设置输出比较极性为高。捕获/比较寄存器的值(即比较值)设置为自动重装载值的一半,即PWM占空比为50%。

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库的中断回调机制,而是把中断程序写在定时器中断服务函数里。详见本章例程源码。

22.2.3.1 程序流程图
QQ截图20230717115946.png
图22.2.3.2.1 高级定时器输出指定个数PWM实验程序流程图

22.2.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。高级定时器驱动源码包括两个文件:atim.c和atim.h。本章节的四个实验源码都是存放在atim.c和atim.h中,源码中也有明确的注释。
首先看atim.h头文件的几个宏定义:
  1. /* TIMX 输出指定个数PWM 定义
  2. * 这里输出的PWM通过PC6(TIM8_CH1)输出, 我们用杜邦线连接PC6和PF10, 然后在程序里面将PE5
  3. * 设置成浮空输入就可以 看到TIM8_CH1控制LED1(GREEN)的亮灭, 亮灭一次表示一个PWM波
  4. * 默认使用的是TIM8_CH1.
  5. * 注意: 通过修改这几个宏定义, 可以支持TIM1/TIM8定时器, 任意一个IO口输出指定个数的PWM
  6. */
  7. #define ATIM_TIMX_NPWM_CHY_GPIO_PORT           GPIOC
  8. #define ATIM_TIMX_NPWM_CHY_GPIO_PIN            GPIO_PIN_6
  9. #define ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE()do{__HAL_RCC_GPIOC_CLK_ENABLE();\
  10. }while(0)   /* PC口时钟使能 */
  11. #define ATIM_TIMX_NPWM_CHY_GPIO_AF             GPIO_AF3_TIM8
  12. #define ATIM_TIMX_NPWM                        TIM8
  13. #define ATIM_TIMX_NPWM_IRQn                   TIM8_UP_IRQn
  14. #define ATIM_TIMX_NPWM_IRQHandler           TIM8_UP_IRQHandler
  15. #define ATIM_TIMX_NPWM_CHY                    TIM_CHANNEL_1/* 通道Y,  1<= Y <=4*/
  16. #define ATIM_TIMX_NPWM_CHY_CCRX              TIM8->CCR1/* 通道Y的输出比较寄存器 */
  17. #define ATIM_TIMX_NPWM_CHY_CLK_ENABLE()    do{__HAL_RCC_TIM8_CLK_ENABLE(); \
  18.                                                   }while(0)
复制代码
可以把上面的宏定义分成两部分,第一部分是定时器8输入通道1对应的IO口的宏定义,第二部分则是定时器8输入通道1的相应宏定义。

下面看gtim.c的程序,首先是输出指定个数PWM初始化函数,其定义如下:
  1. /**
  2. *@brief       高级定时器TIMX 通道Y 输出指定个数PWM 初始化函数
  3. *@note
  4. *             高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
  5. *             高级定时器时钟 = 168Mhz
  6. *             定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
  7. *             Ft=定时器工作频率,单位:Mhz
  8. * @param       arr: 自动重装值
  9. *@param       psc: 时钟预分频数
  10. *@retval      无
  11. */
  12. void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc)
  13. {
  14.    GPIO_InitTypeDef gpio_init_struct;
  15.    TIM_OC_InitTypeDef timx_oc_npwm_chy = {0}; /* 定时器输出 */
  16.    ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE();       /* TIMX 通道IO口时钟使能 */
  17.    ATIM_TIMX_NPWM_CHY_CLK_ENABLE();             /* TIMX 时钟使能 */
  18.    g_timx_npwm_chy_handle.Instance = ATIM_TIMX_NPWM;  /* 定时器x */
  19.    g_timx_npwm_chy_handle.Init.Prescaler = psc;        /* 定时器分频 */
  20.    g_timx_npwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;  /* 递增计数 */
  21.    g_timx_npwm_chy_handle.Init.Period = arr;            /* 自动重装载值 */
  22. g_timx_npwm_chy_handle.Init.AutoReloadPreload =
  23. TIM_AUTORELOAD_PRELOAD_ENABLE;   /*使能TIMx_ARR进行缓冲 */
  24.    g_timx_npwm_chy_handle.Init.RepetitionCounter = 0; /* 重复计数器初始值 */
  25.    HAL_TIM_PWM_Init(&g_timx_npwm_chy_handle);           /* 初始化PWM */
  26.    gpio_init_struct.Pin = ATIM_TIMX_NPWM_CHY_GPIO_PIN;/* 通道y的CPIO口 */
  27.    gpio_init_struct.Mode = GPIO_MODE_AF_PP;             /* 复用推完输出 */
  28.    gpio_init_struct.Pull = GPIO_PULLUP;                  /* 上拉 */
  29.    gpio_init_struct.Speed =GPIO_SPEED_FREQ_HIGH;      /* 高速 */
  30.     gpio_init_struct.Alternate =ATIM_TIMX_NPWM_CHY_GPIO_AF;    /* 端口复用 */
  31.     HAL_GPIO_Init(ATIM_TIMX_NPWM_CHY_GPIO_PORT, &gpio_init_struct);
  32.    timx_oc_npwm_chy.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM 1*/
  33.    timx_oc_npwm_chy.Pulse = arr / 2;            /* 设置比较值,此值用来确定占空比 */
  34.    timx_oc_npwm_chy.OCPolarity =TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
  35. HAL_TIM_PWM_ConfigChannel(&g_timx_npwm_chy_handle, &timx_oc_npwm_chy,
  36. ATIM_TIMX_NPWM_CHY);     /* 配置TIMx通道y */
  37.     /* 设置中断优先级,抢占优先级1,子优先级3 */
  38.    HAL_NVIC_SetPriority(ATIM_TIMX_NPWM_IRQn, 1, 3);
  39.    HAL_NVIC_EnableIRQ(ATIM_TIMX_NPWM_IRQn);         /* 开启ITMx中断 */
  40.    __HAL_TIM_ENABLE_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE);/* 允许更新中断 */
  41.    HAL_TIM_PWM_Start(&g_timx_npwm_chy_handle, ATIM_TIMX_NPWM_CHY);/* 使能输出 */
  42. }
复制代码
gtim_timx_npwm_chy_init函数包含了输出通道对应IO的初始代码、NVIC、使能时钟、定时器基础工作参数和输出模式配置的所有代码。下面来看看该函数的代码内容。
第一部分使能定时器和GPIO的时钟。
第二部分调用HAL_TIM_PWM_Init函数初始化定时器基础工作参数,如:ARR和PSC等。
第三部分是定时器输出通道对应的IO的初始化。
第四部分调用HAL_TIM_PWM_ConfigChannel设置PWM模式以及比较值等参数。
第五部分是NVIC的初始化,配置抢占优先级、响应优先级和开启NVIC定时器中断。

最后是使能更新中断和使能通道输出。

为了方便代码的管理和移植性等,这里就没有使用HAL_TIM_PWM_MspInit这个函数来存放使能时钟、GPIO、NVIC相关的代码,而是全部存放在gtim_timx_npwm_chy_init函数中。

下面我们看设置PWM个数的函数,其定义如下:
  1. /* g_npwm_remain表示当前还剩下多少个脉冲要发送
  2. * 每次最多发送256个脉冲
  3. */
  4. static uint32_t g_npwm_remain = 0;
  5. /**
  6. * @brief       高级定时器TIMX NPWM设置PWM个数
  7. * @param       rcr: PWM的个数, 1~2^32次方个
  8. * @retval      无
  9. */
  10. voidgtim_timx_npwm_chy_set(uint32_t npwm)
  11. {
  12.     if (npwm == 0)return ;
  13. g_npwm_remain = npwm;                           /* 保存脉冲个数 */
  14. /* 产生一次更新事件,在中断里面处理脉冲输出 */
  15.     HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE);
  16.    __HAL_TIM_ENABLE(&g_timx_npwm_chy_handle);  /* 使能定时器TIMX */
  17. }
复制代码
我们要输出多少个周期的PWM就用这个函数来设置。该函数作用是把我们设置输出的PWM个数的值赋值给静态全局变量g_npwm_remain,该变量会在更新中断服务函数回调函数中发挥作用。最后对TIMx_EGR寄存器UG位写1,产生一次更新事件,并使能定时器。
下面来介绍定时器中断服务函数,其定义如下:
  1. /**
  2. * @brief       定时器中断服务函数
  3. * @param       无
  4. * @retval      无
  5. */
  6. voidGTIM_TIMX_NPWM_IRQHandler(void)
  7. {
  8.     uint16_t npwm = 0;
  9.     /* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
  10.     if(__HAL_TIM_GET_FLAG(&g_timx_npwm_chy_handle, TIM_FLAG_UPDATE) != RESET)
  11.     {
  12.         if (g_npwm_remain >= 256)       /* 还有大于等于256个脉冲需要发送 */
  13.         {
  14.            g_npwm_remain=g_npwm_remain - 256;
  15.             npwm = 256;
  16.         }
  17.         else if (g_npwm_remain % 256) /* 还有位数(不到256)个脉冲要发送 */
  18.         {
  19.             npwm = g_npwm_remain % 256;
  20.            g_npwm_remain = 0;          /* 没有脉冲了 */
  21.         }
  22.         if (npwm)                          /* 有脉冲要发送 */
  23.         {
  24.            GTIM_TIMX_NPWM->RCR = npwm - 1; /* 设置RCR值为npwm-1, 即npwm个脉冲 */
  25.            HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle,
  26. TIM_EVENTSOURCE_UPDATE); /* 产生一次更新事件,以更新RCR寄存器 */
  27.            __HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); /* 使能定时器TIMX */
  28.         }
  29.         else
  30.         {
  31. /* 关闭定时器TIMX,使用__HAL_TIM_DISABLE需要失能通道输出,所以不用 */
  32.            GTIM_TIMX_NPWM->CR1 &= ~(1 << 0);
  33.         }
  34. /* 清除定时器更新中断标志位 */
  35.        __HAL_TIM_CLEAR_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE);
  36.     }
  37. }
复制代码
这里我们没有使用HAL库的中断回调机制,而是想寄存器操作一样,直接通过判断中断标志位处理中断。通过__HAL_TIM_GET_FLAG函数宏判断是否发生更新中断,然后进行更新中断的代码处理,最后通过__HAL_TIM_CLEAR_IT函数宏清除更新中断标志位。

因为重复计数器寄存器 (TIM8_RCR)是8位有效的,所以在定时器中断服务函数中首先对全局变量g_npwm_remain(即我们要输出的PWM个数)进行判断,是否大于256,如果大于256,那就得分次写入重复计数器寄存器。写入重复计数寄存器后,需要产生软件更新事件把RCR寄存器的值更新到RCR影子寄存器中,最后一定不要忘记清除定时器更新中断标志位。

在main函数里面编写如下代码:
  1. int main(void)
  2. {
  3.     uint8_t key = 0;
  4. uint8_t t = 0;
  5. GPIO_InitTypeDefgpio_init_struct;
  6.     HAL_Init();                             /* 初始化HAL库 */
  7.    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
  8.     delay_init(168);                       /* 延时初始化 */
  9.     usart_init(115200);                    /* 串口初始化为115200 */
  10.     led_init();                             /* 初始化LED */
  11. key_init();                             /* 初始化按键 */
  12. /* 将 LED1 引脚设置为输入模式, 避免和PC6冲突 */
  13.    gpio_init_struct.Pin = LED1_GPIO_PIN;                 /* LED1 引脚 */
  14.    gpio_init_struct.Mode = GPIO_MODE_INPUT;             /* 设置输入状态 */
  15.    gpio_init_struct.Pull = GPIO_PULLUP;                  /* 上拉 */
  16.    gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;/* 高速模式 */
  17.    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);   /* 初始化LED1引脚 */
  18.    atim_timx_npwm_chy_init(10000 - 1, 8400 - 1);/*20Khz的计数频率,2hz的PWM频率*/
  19.     /* 设置PWM占空比,50%,这样可以控制每一个PWM周期,LED1(BLUE) 有一半时间是亮的,
  20.      * 一半时间是灭的,LED1亮灭一次,表示一个PWM波 */
  21. ATIM_TIMX_NPWM_CHY_CCRX = 5000;
  22. atim_timx_npwm_chy_set(5);    /* 输出5个PWM波(控制LED1)闪烁5次) */
  23.     while (1)
  24.     {
  25.         key = key_scan(0);
  26.         if (key == KEY0_PRES)   /* KEY0按下 */
  27.         {
  28.            gtim_timx_npwm_chy_set(5);   /* 输出5个PWM波(控制LED1闪烁5次) */
  29.         }
  30.         t++;
  31.         delay_ms(10);
  32.         if (t > 50)                         /* 控制LED1闪烁, 提示程序运行状态 */
  33.         {
  34.             t = 0;
  35.             LED0_TOGGLE();
  36.         }
  37.     }
  38. }
复制代码
先看gtim_timx_npwm_chy_init(10000- 1, 8400 - 1);这个语句,这两个形参分别设置自动重载寄存器的值为9999,以及预分频器寄存器的值为8399。按照sys_stm32_clock_init函数的配置,定时器8的时钟频率等于APB2总线时钟频率,即168MHz,可以得到计数器的计数频率是20KHz。自动重载寄存器的值决定的是PWM周期或频率(请回顾21.3小节的内容),计数器计5000个数所用的时间是PWM的周期。在边沿对齐模式下,定时器的溢出周期等于PWM的周期。根据定时器溢出时间计算公式,可得:
Tout= ((arr+1)*(psc+1))/Tclk= ((9999+1)*(8399+1))/168000000=0.5s
再由频率是周期的倒数关系得到PWM的频率为2Hz。

占空比则由捕获/比较寄存器(TIMx_CCRx)的值决定,这里就是由TIM8_CCR1寄存器决定。初始化定时器8时我们设置通道输出比较极性为高,GTIM_TIMX_NPWM_CHY_CCRX = 2500,就设置了占空比为50%。因为我们的LED灯是低电平点亮,所以正占空比期间LED灯熄灭,负占空比期间LED灯亮。

22.2.4 下载验证
首先用杜邦线连接好PF10和PC6引脚的排针。下载代码后,可以看到LED1亮灭五次,然后我们每按一下按键KEY0,LED1都会亮灭五次。

下面我们使用正点原子DS100手持数字示波器,把PC6引脚的波形截获,具体如下:   
image019.png
图22.2.4 PC6引脚波形图

由LED的原理图可以知道,PC6引脚输出低电平LED1亮、输出高电平LED1灭。图22.2.4中,从左往右看,可以知道,LED0一开始是熄灭的,然后经过5次亮灭,最后就是一直保持熄灭的状态。PWM频率是2Hz,占空比50%,请大家自行测量。

22.3 高级定时器输出比较模式实验
本小节我们来学习使用高级定时器输出比较模式下翻转功能,通过定时器4个通道分别输出4个50%占空比、不同相位的PWM。

输出比较模式下翻转功能作用是:当计数器的值等于捕获/比较寄存器影子寄存器的值时,OC1REF 发生翻转,进而控制通道输出(OCx)翻转。通过翻转功能实现输出PWM的具体原理如下:PWM频率由自动重载寄存器(TIMx_ARR)的值决定,在这个过程中,只要自动重载寄存器的值不变,那么PWM占空比就固定为50%。我们可以通过捕获/比较寄存器(TIMx_CCRx)的值改变PWM的相位。生成PWM的原理如图22.3.1所示:     
image021.png
图22.3.1 翻转功能输出PWM原理示意图

本实验就是根据图22.3.1的原理来设计的,具体实验是:我们设置固定的ARR值为999,那么PWM占空比固定为50%,通过改变4个通道的捕获/比较寄存器(TIMx_CCRx)的值使得每个通道输出的PWM的相位都不一样,注意捕获/比较寄存器的值设置范围是:0 ~ ARR。比如:TIMx_CCR1=250-1,TIMx_CCR2=500-1,TIMx_CCR3=750-1,TIMx_CCR4=1000-1,那么可以得到通道1~通道4输出的PWM的相位分别是:25%、50%、75%、100%。翻转功能输出的PWM周期,这里用T表示,其计算公式如下:
T = 2*(arr+1)*((psc+1)/ Tclk)
其中:
T:翻转功能输出的PWM周期(单位为s)。
Tclk:定时器的时钟源频率(单位为MHz)。
arr:自动重装寄存器(TIMx_ARR)的值。
psc:预分频器寄存器(TIMx_PSC)的值。

22.3.1 TIM1/TIM8寄存器
高级定时器输出比较模式除了用到定时器的时基单元:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR)之外。主要还用到以下这些寄存器:
l  控制寄存器 1TIMx_CR1
TIM1/TIM8的控制寄存器1描述如图22.3.1.1所示。     
image023.png
图22.3.1.1 TIMx_CR1寄存器

上图中我们只列出了本实验需要用的一些位,其中:位7(APRE)用于控制自动重载寄存器是否具有缓冲作用,在基本定时器的时候已经讲过,请回顾。本实验中,我们把该位置1。

位4(DIR)用于配置计数器的计数方向,本实验默认置0即可。

位CEN位,用于使能计数器的工作,必须要设置该位为1,才可以开始计数。

其它位保持复位值即可。

l  捕获/比较模式寄存器1/2TIMx_CCMR1/2
TIM1/TIM8的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx_CCMR1和TIMx_CCMR2。TIMx_CCMR1控制CH1和CH2,而TIMx_CCMR2控制CH3和CH4。TIMx_CCMR1寄存器描述如图22.3.1.2所示:   
image025.png
图22.3.1.2 TIMx_CCMR1寄存器

该寄存器的有些位在不同模式下,功能不一样,我们现在用到输出比较模式。关于该寄存器的详细说明,请参考《STM32F4xx参考手册_V4(中文版).pdf》第375页,14.4.7节。

本实验我们用到了定时器8输出比较的4个通道,所以我们需要配置TIM1_CCMR1和TIM1_CCMR2。以TIM1_CCMR1寄存器为例,模式设置位OC1M[2:0]就是对应着通道1的模式设置,此部分由3位组成,总共可以配置成8种模式,我们使用的是翻转功能,所以这3位必须设置为011。通道2也是如此,将位OC2M[2:0]设置为011。通道3和通道4就要设置TIM1_CCMR2寄存器的位OC3M[2:0]和位OC4M[2:0]。除此之外,我们还要设置输出比较的预装载使能位,如通道1对应输出比较的预装载使能位OC1PE置1,其他通道也要把相应位置1。

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

该寄存器比较简单,要让TIM8的4个通道都输出,我们需要把对应的捕获/比较1输出使能位置1。通道1到通道4的使能位分别是:CC1E、CC2E、CC3E、CC4E,我们把这4个位置1,使能通道输出。

l  捕获/比较寄存器1/2/3/4TIMx_CCR1/2/3/4
捕获/比较寄存器(TIMx_CCR1/2/3/4),该寄存器总共有4个,对应4个通道CH1~CH4。本实验4个通道都要使用到,以通道1对应的TIMx_ CCR1寄存器为例,其描述如下图所示:   
image029.png
图22.3.1.4 TIMx_CCR1寄存器

这里,我们通过改变TIMx_CCR1/2/3/4寄存器的值来改变4个通道输出的PWM的相位。

l TIM1/TIM8断路和死区寄存器(TIMx_BDTR
本实验用的是高级定时器,我们还需要配置:断路和死区寄存器(TIMx_BDTR),该寄存器各位描述如图22.3.1.5所示。   
image031.png
图22.3.1.5 TIMx_BDTR寄存器

该寄存器,我们只需要关注位15(MOE),要想高级定时器的通道正常输出,则必须设置MOE位为1,否则不会有输出。

22.3.2 硬件设计
1. 例程功能
使用输出比较模式的翻转功能,通过定时器8的4路通道输出占空比固定为50%、相位分别是25%、50%、75%和100%的PWM。

2. 硬件资源
1)LED灯
   LED0 – PF9
2)PC6复用为TIM8_CH1
  PC7复用为TIM8_CH2
   PC8复用为TIM8_CH3
  PC9复用为TIM8_CH4

3. 原理图
定时器属于STM32F407的内部资源,只需要软件设置好即可正常工作。我们需要通过示波器观察PC6、PC7、PC8和PC9引脚PWM输出的情况。

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

1. HAL_TIM_OC_Init函数
定时器的输出比较模式初始化函数,其声明如下:
  1. HAL_StatusTypeDef HAL_TIM_OC_Init(TIM_HandleTypeDef*htim);
复制代码
l  函数描述:
用于初始化定时器的输出比较模式。

l  函数形参:
形参1是TIM_HandleTypeDef结构体类型指针变量,基本定时器的时候已经介绍。

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

2. HAL_TIM_OC_ConfigChannel函数
定时器的输出比较通道设置初始化函数。其声明如下:
  1. HAL_StatusTypeDefHAL_TIM_OC_ConfigChannel(TIM_HandleTypeDef *htim,
  2. TIM_OC_InitTypeDef *sConfig, uint32_t Channel);
复制代码
l  函数描述:
该函数用于初始化定时器的输出比较通道。

l  函数形参:
形参1是TIM_HandleTypeDef结构体类型指针变量,用于配置定时器基本参数。
形参2是TIM_OC_InitTypeDef结构体类型指针变量,用于配置定时器的输出比较参数。
在通用定时器PWM输出实验已经介绍过TIM_OC_InitTypeDef结构体指针类型。
形参3是定时器通道,范围:TIM_CHANNEL_1到TIM_CHANNEL_4。

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

3. HAL_TIM_OC_Start函数
定时器的输出比较启动函数,其声明如下:
  1. HAL_StatusTypeDef HAL_TIM_OC_Start(TIM_HandleTypeDef*htim, uint32_t Channel);
复制代码
l  函数描述:
用于启动定时器的输出比较模式。

l  函数形参:
形参1是TIM_HandleTypeDef结构体类型指针变量。
形参2是定时器通道,范围:TIM_CHANNEL_1到TIM_CHANNEL_4。

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

l  注意事项:
HAL库也同样提供了单独使能定时器的输出通道函数,函数为:
  1. void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel,uint32_t ChannelState);
复制代码
HAL_TIM_OC_Start函数内部也调用了该函数。

定时器输出比较模式配置步骤
1)开启TIMx和通道输出的GPIO时钟,配置该IO口的复用功能输出。
首先开启TIMx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到定时器8通道1、2、3、4,对应IO是PC6\PC7\PC8\PC9,它们的时钟开启方法如下:
  1. __HAL_RCC_TIM8_CLK_ENABLE();             /* 使能定时器8 */
  2. __HAL_RCC_GPIOC_CLK_ENABLE();            /* 开启GPIOC时钟 */
复制代码
IO口复用功能是通过函数HAL_GPIO_Init来配置的。

2)初始化TIMx设置TIMx的ARRPSC等参数。
使用定时器的输出比较模式时,我们调用的是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函数使能定时器并开启输出。

22.3.3.2 程序流程图
QQ截图20230717120102.png
图22.3.3.2.1 高级定时器输出比较模式实验程序流程图

22.3.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。高级定时器驱动源码包括两个文件:atim.c和atim.h。
首先看atim.h头文件的几个宏定义:
  1. /* TIMX 输出比较模式 定义
  2. * 这里通过TIM8的输出比较模式,控制PC6,PC7,PC8,PC9输出4路PWM,占空比50%,并且每一路PWM
  3. * 之间的相位差为25%,修改CCRx可以修改相位. 默认是针对TIM8
  4. * 注意: 通过修改这些宏定义,可以支持TIM1/TIM8任意一个定时器,任意一个IO口使用输出比较模式,
  5. * 输出PWM
  6. */
  7. #define ATIM_TIMX_COMP_CH1_GPIO_PORT           GPIOC
  8. #define ATIM_TIMX_COMP_CH1_GPIO_PIN            GPIO_PIN_6
  9. #define ATIM_TIMX_COMP_CH1_GPIO_CLK_ENABLE()
  10. do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */
  11. #define ATIM_TIMX_COMP_CH2_GPIO_PORT           GPIOC
  12. #define ATIM_TIMX_COMP_CH2_GPIO_PIN            GPIO_PIN_7
  13. #define ATIM_TIMX_COMP_CH2_GPIO_CLK_ENABLE()
  14. do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */
  15. #define ATIM_TIMX_COMP_CH3_GPIO_PORT            GPIOC
  16. #define ATIM_TIMX_COMP_CH3_GPIO_PIN             GPIO_PIN_8
  17. #define ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE()
  18. do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */
  19. #define ATIM_TIMX_COMP_CH4_GPIO_PORT            GPIOC
  20. #define ATIM_TIMX_COMP_CH4_GPIO_PIN             GPIO_PIN_9
  21. #define ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE()  
  22. do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */
  23. #define ATIM_TIMX_COMP_GPIO_AF                  GPIO_AF3_TIM8
  24. #define ATIM_TIMX_COMP             TIM8
  25. #define ATIM_TIMX_COMP_CH1_CCRX  ATIM_TIMX_COMP->CCR1  /* 通道1的输出比较寄存器 */
  26. #define ATIM_TIMX_COMP_CH2_CCRX  ATIM_TIMX_COMP->CCR2  /* 通道2的输出比较寄存器 */
  27. #define ATIM_TIMX_COMP_CH3_CCRX  ATIM_TIMX_COMP->CCR3  /* 通道3的输出比较寄存器 */
  28. #define ATIM_TIMX_COMP_CH4_CCRX  ATIM_TIMX_COMP->CCR4  /* 通道4的输出比较寄存器 */
  29. #define ATIM_TIMX_COMP_CLK_ENABLE()     
  30. do{ __HAL_RCC_TIM8_CLK_ENABLE();}while(0)    /* TIM8 时钟使能 */
复制代码
可以把上面的宏定义分成两部分,第一部分是定时器1输出通道1~通道4对应的IO口的宏定义。第二部分则是定时器8的相应宏定义。

下面来看到atim.c文件的程序,首先是高级定时器输出比较模式初始化函数,其定义如下:
  1. /**
  2. * @brief      高级定时器TIMX 输出比较模式 初始化函数(使用输出比较模式)
  3. * @note
  4. *             配置高级定时器TIMX 4路输出比较模式PWM输出,实现50%占空比,不同相位控制
  5. *             注意,本例程输出比较模式,每2个计数周期才能完成一个PWM输出,因此输出频率减半
  6. *             另外,我们还可以开启中断在中断里面修改CCRx,从而实现不同频率/不同相位的控制
  7. *              但是我们不推荐这么使用,因为这可能导致非常频繁的中断,从而占用大量CPU资源
  8. *
  9. *             高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
  10. *             高级定时器时钟 = 168Mhz
  11. *             定时器溢出时间计算方法: Tout = ((arr +1) * (psc + 1)) / Ft us.
  12. *             Ft=定时器工作频率,单位:Mhz
  13. *
  14. * @param      arr: 自动重装值。
  15. * @param      psc: 时钟预分频数
  16. * @retval     无
  17. */
  18. voidatim_timx_comp_pwm_init(uint16_t arr, uint16_t psc)
  19. {
  20.     TIM_OC_InitTypeDef timx_oc_comp_pwm = {0};
  21.     g_timx_comp_pwm_handle.Instance = ATIM_TIMX_COMP;   /* 定时器x */
  22.     g_timx_comp_pwm_handle.Init.Prescaler = psc;         /* 定时器分频 */
  23.     g_timx_comp_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;/* 递增计数 */
  24.     g_timx_comp_pwm_handle.Init.Period = arr;             /* 自动重装载值 */
  25. g_timx_comp_pwm_handle.Init.AutoReloadPreload =
  26. TIM_AUTORELOAD_PRELOAD_ENABLE;                      /* 使能影子寄存器*/
  27.     HAL_TIM_OC_Init(&g_timx_comp_pwm_handle);             /* 输出比较模式初始化 */
  28.     timx_oc_comp_pwm.OCMode = TIM_OCMODE_TOGGLE;         /* 比较输出模式翻转功能 */
  29.     timx_oc_comp_pwm.Pulse = 250 - 1;                     /* 设置输出比较寄存器的值 */
  30.     timx_oc_comp_pwm.OCPolarity =TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
  31. HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm,
  32. TIM_CHANNEL_1);   /* 初始化定时器的输出比较通道1 */
  33. /* CCR1寄存器预装载使能 */
  34.     __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
  35.     tim_oc_handle.Pulse = 500;
  36. HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &tim_oc_handle,
  37. TIM_CHANNEL_2); /* 初始化定时器的输出比较通道2 */
  38. /* CCR2寄存器预装载使能 */
  39.     __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
  40.     tim_oc_handle.Pulse = 750;
  41. HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &tim_oc_handle,
  42. TIM_CHANNEL_3); /* 初始化定时器的输出比较通道3 */
  43. /* CCR3寄存器预装载使能 */
  44.     __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
  45.     tim_oc_handle.Pulse = 1000;
  46. HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &tim_oc_handle,
  47. TIM_CHANNEL_4); /* 初始化定时器的输出比较通道4 */
  48. /* CCR4寄存器预装载使能 */   
  49. __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
  50.     HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
  51.     HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
  52.     HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
  53.     HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
  54. }
复制代码
在atim_timx_comp_pwm_init 函数中,首先调用HAL_TIM_OC_Init函数初始化定时器的ARR和PSC等参数。然后通过调用函数HAL_TIM_OC_ConfigChannel设置通道1~通道4的工作参数,包括:输出比较模式功能、输出比较寄存器的值,输出极性等。接着调用__HAL_TIM_ENABLE_OCxPRELOAD函数宏使能CCR1/2/3/4寄存器的预装载。最后通过调用函数HAL_TIM_OC_Start来使能TIM8通道1~通道4输出。

HAL_TIM_OC_Init函数会调用HAL_TIM_OC_MspInit回调函数,我们把使能定时器和通道对应的IO时钟、IO初始化的代码存放到该函数里,其定义如下:
  1. /**
  2. * @brief      定时器底层驱动,时钟使能,引脚配置
  3.                  此函数会被HAL_TIM_OC_Init()调用
  4. * @param      htim:定时器句柄
  5. * @retval     无
  6. */
  7. void HAL_TIM_OC_MspInit(TIM_HandleTypeDef *htim)
  8. {
  9.     if (htim->Instance == ATIM_TIMX_COMP)
  10.     {
  11.         GPIO_InitTypeDef gpio_init_struct;
  12.         ATIM_TIMX_COMP_CLK_ENABLE();
  13.         ATIM_TIMX_COMP_CH1_GPIO_CLK_ENABLE();
  14.         ATIM_TIMX_COMP_CH2_GPIO_CLK_ENABLE();
  15.         ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE();
  16.         ATIM_TIMX_COMP_CH4_GPIO_CLK_ENABLE();
  17.         gpio_init_struct.Pin =ATIM_TIMX_COMP_CH1_GPIO_PIN;
  18.         gpio_init_struct.Mode = GPIO_MODE_AF_PP;
  19.         gpio_init_struct.Pull = GPIO_NOPULL;
  20.         gpio_init_struct.Speed =GPIO_SPEED_FREQ_HIGH;
  21.         gpio_init_struct.Alternate =ATIM_TIMX_COMP_GPIO_AF;
  22.         HAL_GPIO_Init(ATIM_TIMX_COMP_CH1_GPIO_PORT, &gpio_init_struct);
  23.         gpio_init_struct.Pin =ATIM_TIMX_COMP_CH2_GPIO_PIN;
  24.         HAL_GPIO_Init(ATIM_TIMX_COMP_CH2_GPIO_PORT, &gpio_init_struct);
  25.         gpio_init_struct.Pin =ATIM_TIMX_COMP_CH3_GPIO_PIN;
  26.         HAL_GPIO_Init(ATIM_TIMX_COMP_CH3_GPIO_PORT, &gpio_init_struct);
  27.         gpio_init_struct.Pin =ATIM_TIMX_COMP_CH4_GPIO_PIN;
  28.         HAL_GPIO_Init(ATIM_TIMX_COMP_CH4_GPIO_PORT, &gpio_init_struct);
  29.     }
  30. }
复制代码
该函数主要是使能定时器和通道对应的IO时钟,初始化IO口。

在main.c里面编写如下代码:
  1. int main(void)
  2. {
  3.     uint8_t t = 0;
  4.     HAL_Init();                                   /* 初始化HAL库 */
  5.    sys_stm32_clock_init(336, 8, 2, 7);          /* 设置时钟,168Mhz */
  6.    delay_init(168);                                /* 延时初始化 */
  7.     usart_init(115200);                            /* 串口初始化为115200 */
  8.     led_init();                                      /* 初始化LED */
  9.    atim_timx_comp_pwm_init(1000 - 1, 168 - 1); /* 1Mhz的计数频率 1Khz的周期. */
  10.    ATIM_TIMX_COMP_CH1_CCRX = 250 - 1;          /* 通道1 相位25% */
  11.    ATIM_TIMX_COMP_CH2_CCRX = 500 - 1;          /* 通道2 相位50% */
  12.    ATIM_TIMX_COMP_CH3_CCRX = 750 - 1;          /* 通道3 相位75% */
  13.    ATIM_TIMX_COMP_CH4_CCRX = 999 - 1;          /* 通道4 相位99% */
  14.     while (1)
  15.     {
  16.         delay_ms(10);
  17.         t++;
  18.         if (t >= 20)
  19.         {
  20.            LED0_TOGGLE();    /* LED0(RED)闪烁 */
  21.             t = 0;
  22.         }
  23.     }
  24. }
复制代码
本小节开头我们讲解了输出比较模式翻转功能如何产生PWM波,下面结合程序一起计算出PWM波的周期,频率等参数。

定时器8时钟源的时钟频率等于2倍的APB2总线时钟频率,即168MHz,而调用atim_timx_comp_pwm_init(1000 - 1, 168 - 1)初始化函数之后,就相当于写入预分频寄存器的值为167,写入自动重载寄存器的值为999。将这些参数代入本小节介绍的翻转功能输出的PWM周期计算公式,可得:
T = 2*(arr+1)*((psc+1)/ Tclk) = 2*(999+1)*((167+1)/168000000) = 0.002s
由上述式子得到PWM周期为2ms,频率为500Hz。ARR值为固定为1000,所以占空比则固定为50%。定时器1通道1~通道4输出的PWM波的相位分别是:25%、50%、75%、100%。

22.3.4 下载验证
下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了。 我们需要借助示波器观察PC6、PC7、PC8和PC9引脚PWM输出的情况,如下图所示:
image035.png
图22.3.4.1 相位为25%、50%、75%、100%的PWM波

图22.3.4.1中,由上到下分别是引脚PC6、PC7、PC8和PC9输出的PWM,即分别对应的是TIM1_CH1、TIM1_CH2、TIM1_CH3和TIM1_CH4输出的相位为25%、50%、75%和100%的PWM。大家可以把其中一个通道的捕获/比较寄存器的值设置为0,那么就可以得到PWM初相位的波形,即相位为0%。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

1

主题

3

帖子

0

精华

新手上路

积分
32
金钱
32
注册时间
2021-8-6
在线时间
9 小时
发表于 2023-10-13 16:28:08 | 显示全部楼层
改成定时器1就不能用了
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-23 07:50

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表