本帖最后由 正点原子运营 于 2023-7-17 12:01 编辑
1)实验平台:正点原子探索者STM32F407开发板
2) 章节摘自【正点原子】STM32F407开发指南 V1.1
6)STM32技术交流QQ群:151941872
本章我们主要来学习高级定时器,STM32F407有2个高级定时器(TIM1和TIM8)。我们将通过四个实验来学习高级定时器的各个功能,分别是高级定时器输出指定个数PWM实验、高级定时器输出比较模式实验、高级定时器互补输出带死区控制实验和高级定时器PWM输入模式实验。 本章分为如下几个小节: 22.1 高级定时器简介 22.2 高级定时器输出指定个数PWM实验 22.3 高级定时器输出比较模式实验 22.4 高级定时器互补输出带死区控制实验 22.5 高级定时器PWM输入模式实验
22.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 控制寄存器 1(TIMx_CR1) TIM1/TIM8的控制寄存器1描述如图22.2.1.1所示: 上图中我们只列出了本章需要用的一些位,其中:位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所示: 该寄存器的有些位在不同模式下,功能不一样,我们前面已经说过。比如我们要让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所示: 该寄存器比较简单,要让TIM1的CH1输出PWM波,这里我们要使能CC1E位,该位是通道1输入/输出使能位,要想PWM从IO口输出,这个位必须设置为1。CC1P位是设置通道1的输出极性,我们设置0,即OC1高电平有效。
l 事件产生寄存器(TIMx_EGR) TIM1/TIM8的事件产生寄存器,该寄存器作用是让用户用软件方式产生各类事件。TIMx_EGR寄存器描述如图22.2.1.4所示: UG位是更新事件的控制位,作用和定时器溢出时产生的更新事件一样,区别是这里是通过软件产生的,而定时器溢出是硬件自己完成的。只有开启了更新中断,这两种方式都可以产更新中断。本实验用到该位去产生软件更新器事件,在需要的时候把UG位置1即可,会由硬件自动清零。
l 重复计数器寄存器(TIMx_RCR) 重复计数器寄存器用于设置重复计数器值,因为它具有影子寄存器,所以它本身只是起缓冲作用。当更新事件发生时,该寄存器的值会转移到其影子寄存器中,从而真正起作用。TIMx_ RCR寄存器描述如图22.2.1.5所示: 该寄存器的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所示: 在输出模式下,捕获/比较寄存器影子寄存器的值与CNT的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制PWM的占空比了。
l 断路和死区寄存器(TIMx_BDTR) 高级定时器TIM1/8的通道用作输出时,还必须配置断路和死区寄存器(TIMx_BDTR)的位MOE,该寄存器各位描述如图22.3.1.7所示: 本实验,我们只需要关注该寄存器的位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,它们的时钟开启方法如下: - __HAL_RCC_TIM8_CLK_ENABLE(); /* 使能定时器8 */
- __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模式,根据需求设置输出比较的极性,设置比较值(控制占空比)等。 本实验我们设置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 程序流程图 图22.2.3.2.1 高级定时器输出指定个数PWM实验程序流程图 22.2.3.2 程序解析 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。高级定时器驱动源码包括两个文件:atim.c和atim.h。本章节的四个实验源码都是存放在atim.c和atim.h中,源码中也有明确的注释。 首先看atim.h头文件的几个宏定义: - /* TIMX 输出指定个数PWM 定义
- * 这里输出的PWM通过PC6(TIM8_CH1)输出, 我们用杜邦线连接PC6和PF10, 然后在程序里面将PE5
- * 设置成浮空输入就可以 看到TIM8_CH1控制LED1(GREEN)的亮灭, 亮灭一次表示一个PWM波
- * 默认使用的是TIM8_CH1.
- * 注意: 通过修改这几个宏定义, 可以支持TIM1/TIM8定时器, 任意一个IO口输出指定个数的PWM
- */
- #define ATIM_TIMX_NPWM_CHY_GPIO_PORT GPIOC
- #define ATIM_TIMX_NPWM_CHY_GPIO_PIN GPIO_PIN_6
- #define ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE()do{__HAL_RCC_GPIOC_CLK_ENABLE();\
- }while(0) /* PC口时钟使能 */
- #define ATIM_TIMX_NPWM_CHY_GPIO_AF GPIO_AF3_TIM8
- #define ATIM_TIMX_NPWM TIM8
- #define ATIM_TIMX_NPWM_IRQn TIM8_UP_IRQn
- #define ATIM_TIMX_NPWM_IRQHandler TIM8_UP_IRQHandler
- #define ATIM_TIMX_NPWM_CHY TIM_CHANNEL_1/* 通道Y, 1<= Y <=4*/
- #define ATIM_TIMX_NPWM_CHY_CCRX TIM8->CCR1/* 通道Y的输出比较寄存器 */
- #define ATIM_TIMX_NPWM_CHY_CLK_ENABLE() do{__HAL_RCC_TIM8_CLK_ENABLE(); \
- }while(0)
复制代码可以把上面的宏定义分成两部分,第一部分是定时器8输入通道1对应的IO口的宏定义,第二部分则是定时器8输入通道1的相应宏定义。
下面看gtim.c的程序,首先是输出指定个数PWM初始化函数,其定义如下: - /**
- *@brief 高级定时器TIMX 通道Y 输出指定个数PWM 初始化函数
- *@note
- * 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
- * 高级定时器时钟 = 168Mhz
- * 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
- * Ft=定时器工作频率,单位:Mhz
- * @param arr: 自动重装值
- *@param psc: 时钟预分频数
- *@retval 无
- */
- void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc)
- {
- GPIO_InitTypeDef gpio_init_struct;
- TIM_OC_InitTypeDef timx_oc_npwm_chy = {0}; /* 定时器输出 */
- ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE(); /* TIMX 通道IO口时钟使能 */
- ATIM_TIMX_NPWM_CHY_CLK_ENABLE(); /* TIMX 时钟使能 */
- g_timx_npwm_chy_handle.Instance = ATIM_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; /* 自动重装载值 */
- g_timx_npwm_chy_handle.Init.AutoReloadPreload =
- TIM_AUTORELOAD_PRELOAD_ENABLE; /*使能TIMx_ARR进行缓冲 */
- g_timx_npwm_chy_handle.Init.RepetitionCounter = 0; /* 重复计数器初始值 */
- HAL_TIM_PWM_Init(&g_timx_npwm_chy_handle); /* 初始化PWM */
- gpio_init_struct.Pin = ATIM_TIMX_NPWM_CHY_GPIO_PIN;/* 通道y的CPIO口 */
- 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_NPWM_CHY_GPIO_AF; /* 端口复用 */
- HAL_GPIO_Init(ATIM_TIMX_NPWM_CHY_GPIO_PORT, &gpio_init_struct);
- timx_oc_npwm_chy.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM 1*/
- timx_oc_npwm_chy.Pulse = arr / 2; /* 设置比较值,此值用来确定占空比 */
- timx_oc_npwm_chy.OCPolarity =TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
- HAL_TIM_PWM_ConfigChannel(&g_timx_npwm_chy_handle, &timx_oc_npwm_chy,
- ATIM_TIMX_NPWM_CHY); /* 配置TIMx通道y */
- /* 设置中断优先级,抢占优先级1,子优先级3 */
- HAL_NVIC_SetPriority(ATIM_TIMX_NPWM_IRQn, 1, 3);
- HAL_NVIC_EnableIRQ(ATIM_TIMX_NPWM_IRQn); /* 开启ITMx中断 */
- __HAL_TIM_ENABLE_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE);/* 允许更新中断 */
- HAL_TIM_PWM_Start(&g_timx_npwm_chy_handle, ATIM_TIMX_NPWM_CHY);/* 使能输出 */
- }
复制代码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个数的函数,其定义如下: - /* g_npwm_remain表示当前还剩下多少个脉冲要发送
- * 每次最多发送256个脉冲
- */
- static uint32_t g_npwm_remain = 0;
- /**
- * @brief 高级定时器TIMX NPWM设置PWM个数
- * @param rcr: PWM的个数, 1~2^32次方个
- * @retval 无
- */
- voidgtim_timx_npwm_chy_set(uint32_t npwm)
- {
- if (npwm == 0)return ;
- g_npwm_remain = npwm; /* 保存脉冲个数 */
- /* 产生一次更新事件,在中断里面处理脉冲输出 */
- HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE);
- __HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); /* 使能定时器TIMX */
- }
复制代码我们要输出多少个周期的PWM就用这个函数来设置。该函数作用是把我们设置输出的PWM个数的值赋值给静态全局变量g_npwm_remain,该变量会在更新中断服务函数回调函数中发挥作用。最后对TIMx_EGR寄存器UG位写1,产生一次更新事件,并使能定时器。 下面来介绍定时器中断服务函数,其定义如下: - /**
- * @brief 定时器中断服务函数
- * @param 无
- * @retval 无
- */
- voidGTIM_TIMX_NPWM_IRQHandler(void)
- {
- uint16_t npwm = 0;
- /* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
- if(__HAL_TIM_GET_FLAG(&g_timx_npwm_chy_handle, TIM_FLAG_UPDATE) != RESET)
- {
- if (g_npwm_remain >= 256) /* 还有大于等于256个脉冲需要发送 */
- {
- g_npwm_remain=g_npwm_remain - 256;
- npwm = 256;
- }
- else if (g_npwm_remain % 256) /* 还有位数(不到256)个脉冲要发送 */
- {
- npwm = g_npwm_remain % 256;
- g_npwm_remain = 0; /* 没有脉冲了 */
- }
- if (npwm) /* 有脉冲要发送 */
- {
- GTIM_TIMX_NPWM->RCR = npwm - 1; /* 设置RCR值为npwm-1, 即npwm个脉冲 */
- HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle,
- TIM_EVENTSOURCE_UPDATE); /* 产生一次更新事件,以更新RCR寄存器 */
- __HAL_TIM_ENABLE(&g_timx_npwm_chy_handle); /* 使能定时器TIMX */
- }
- else
- {
- /* 关闭定时器TIMX,使用__HAL_TIM_DISABLE需要失能通道输出,所以不用 */
- GTIM_TIMX_NPWM->CR1 &= ~(1 << 0);
- }
- /* 清除定时器更新中断标志位 */
- __HAL_TIM_CLEAR_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE);
- }
- }
复制代码这里我们没有使用HAL库的中断回调机制,而是想寄存器操作一样,直接通过判断中断标志位处理中断。通过__HAL_TIM_GET_FLAG函数宏判断是否发生更新中断,然后进行更新中断的代码处理,最后通过__HAL_TIM_CLEAR_IT函数宏清除更新中断标志位。
因为重复计数器寄存器 (TIM8_RCR)是8位有效的,所以在定时器中断服务函数中首先对全局变量g_npwm_remain(即我们要输出的PWM个数)进行判断,是否大于256,如果大于256,那就得分次写入重复计数器寄存器。写入重复计数寄存器后,需要产生软件更新事件把RCR寄存器的值更新到RCR影子寄存器中,最后一定不要忘记清除定时器更新中断标志位。
在main函数里面编写如下代码: - int main(void)
- {
- uint8_t key = 0;
- uint8_t t = 0;
- GPIO_InitTypeDefgpio_init_struct;
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- led_init(); /* 初始化LED */
- key_init(); /* 初始化按键 */
- /* 将 LED1 引脚设置为输入模式, 避免和PC6冲突 */
- gpio_init_struct.Pin = LED1_GPIO_PIN; /* LED1 引脚 */
- gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 设置输入状态 */
- gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
- gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;/* 高速模式 */
- HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct); /* 初始化LED1引脚 */
- atim_timx_npwm_chy_init(10000 - 1, 8400 - 1);/*20Khz的计数频率,2hz的PWM频率*/
- /* 设置PWM占空比,50%,这样可以控制每一个PWM周期,LED1(BLUE) 有一半时间是亮的,
- * 一半时间是灭的,LED1亮灭一次,表示一个PWM波 */
- ATIM_TIMX_NPWM_CHY_CCRX = 5000;
- atim_timx_npwm_chy_set(5); /* 输出5个PWM波(控制LED1)闪烁5次) */
- while (1)
- {
- key = key_scan(0);
- if (key == KEY0_PRES) /* KEY0按下 */
- {
- gtim_timx_npwm_chy_set(5); /* 输出5个PWM波(控制LED1闪烁5次) */
- }
- t++;
- delay_ms(10);
- if (t > 50) /* 控制LED1闪烁, 提示程序运行状态 */
- {
- t = 0;
- LED0_TOGGLE();
- }
- }
- }
复制代码先看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引脚的波形截获,具体如下: 由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所示: 本实验就是根据图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 控制寄存器 1(TIMx_CR1) TIM1/TIM8的控制寄存器1描述如图22.3.1.1所示。 上图中我们只列出了本实验需要用的一些位,其中:位7(APRE)用于控制自动重载寄存器是否具有缓冲作用,在基本定时器的时候已经讲过,请回顾。本实验中,我们把该位置1。
位4(DIR)用于配置计数器的计数方向,本实验默认置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.3.1.2所示: 该寄存器的有些位在不同模式下,功能不一样,我们现在用到输出比较模式。关于该寄存器的详细说明,请参考《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所示: 该寄存器比较简单,要让TIM8的4个通道都输出,我们需要把对应的捕获/比较1输出使能位置1。通道1到通道4的使能位分别是:CC1E、CC2E、CC3E、CC4E,我们把这4个位置1,使能通道输出。
l 捕获/比较寄存器1/2/3/4(TIMx_CCR1/2/3/4) 捕获/比较寄存器(TIMx_CCR1/2/3/4),该寄存器总共有4个,对应4个通道CH1~CH4。本实验4个通道都要使用到,以通道1对应的TIMx_ CCR1寄存器为例,其描述如下图所示: 这里,我们通过改变TIMx_CCR1/2/3/4寄存器的值来改变4个通道输出的PWM的相位。
l TIM1/TIM8断路和死区寄存器(TIMx_BDTR) 本实验用的是高级定时器,我们还需要配置:断路和死区寄存器(TIMx_BDTR),该寄存器各位描述如图22.3.1.5所示。 该寄存器,我们只需要关注位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函数 定时器的输出比较模式初始化函数,其声明如下: - HAL_StatusTypeDef HAL_TIM_OC_Init(TIM_HandleTypeDef*htim);
复制代码l 函数描述: 用于初始化定时器的输出比较模式。
l 函数形参: 形参1是TIM_HandleTypeDef结构体类型指针变量,基本定时器的时候已经介绍。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
2. HAL_TIM_OC_ConfigChannel函数 定时器的输出比较通道设置初始化函数。其声明如下: - HAL_StatusTypeDefHAL_TIM_OC_ConfigChannel(TIM_HandleTypeDef *htim,
- 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函数 定时器的输出比较启动函数,其声明如下: - 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库也同样提供了单独使能定时器的输出通道函数,函数为: - 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,它们的时钟开启方法如下: - __HAL_RCC_TIM8_CLK_ENABLE(); /* 使能定时器8 */
- __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函数使能定时器并开启输出。
22.3.3.2 程序流程图 图22.3.3.2.1 高级定时器输出比较模式实验程序流程图 22.3.3.3 程序解析 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。高级定时器驱动源码包括两个文件:atim.c和atim.h。 首先看atim.h头文件的几个宏定义: - /* TIMX 输出比较模式 定义
- * 这里通过TIM8的输出比较模式,控制PC6,PC7,PC8,PC9输出4路PWM,占空比50%,并且每一路PWM
- * 之间的相位差为25%,修改CCRx可以修改相位. 默认是针对TIM8
- * 注意: 通过修改这些宏定义,可以支持TIM1/TIM8任意一个定时器,任意一个IO口使用输出比较模式,
- * 输出PWM
- */
- #define ATIM_TIMX_COMP_CH1_GPIO_PORT GPIOC
- #define ATIM_TIMX_COMP_CH1_GPIO_PIN GPIO_PIN_6
- #define ATIM_TIMX_COMP_CH1_GPIO_CLK_ENABLE()
- do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
- #define ATIM_TIMX_COMP_CH2_GPIO_PORT GPIOC
- #define ATIM_TIMX_COMP_CH2_GPIO_PIN GPIO_PIN_7
- #define ATIM_TIMX_COMP_CH2_GPIO_CLK_ENABLE()
- do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
- #define ATIM_TIMX_COMP_CH3_GPIO_PORT GPIOC
- #define ATIM_TIMX_COMP_CH3_GPIO_PIN GPIO_PIN_8
- #define ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE()
- do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
- #define ATIM_TIMX_COMP_CH4_GPIO_PORT GPIOC
- #define ATIM_TIMX_COMP_CH4_GPIO_PIN GPIO_PIN_9
- #define ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE()
- do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
- #define ATIM_TIMX_COMP_GPIO_AF GPIO_AF3_TIM8
- #define ATIM_TIMX_COMP TIM8
- #define ATIM_TIMX_COMP_CH1_CCRX ATIM_TIMX_COMP->CCR1 /* 通道1的输出比较寄存器 */
- #define ATIM_TIMX_COMP_CH2_CCRX ATIM_TIMX_COMP->CCR2 /* 通道2的输出比较寄存器 */
- #define ATIM_TIMX_COMP_CH3_CCRX ATIM_TIMX_COMP->CCR3 /* 通道3的输出比较寄存器 */
- #define ATIM_TIMX_COMP_CH4_CCRX ATIM_TIMX_COMP->CCR4 /* 通道4的输出比较寄存器 */
- #define ATIM_TIMX_COMP_CLK_ENABLE()
- do{ __HAL_RCC_TIM8_CLK_ENABLE();}while(0) /* TIM8 时钟使能 */
复制代码可以把上面的宏定义分成两部分,第一部分是定时器1输出通道1~通道4对应的IO口的宏定义。第二部分则是定时器8的相应宏定义。
下面来看到atim.c文件的程序,首先是高级定时器输出比较模式初始化函数,其定义如下: - /**
- * @brief 高级定时器TIMX 输出比较模式 初始化函数(使用输出比较模式)
- * @note
- * 配置高级定时器TIMX 4路输出比较模式PWM输出,实现50%占空比,不同相位控制
- * 注意,本例程输出比较模式,每2个计数周期才能完成一个PWM输出,因此输出频率减半
- * 另外,我们还可以开启中断在中断里面修改CCRx,从而实现不同频率/不同相位的控制
- * 但是我们不推荐这么使用,因为这可能导致非常频繁的中断,从而占用大量CPU资源
- *
- * 高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
- * 高级定时器时钟 = 168Mhz
- * 定时器溢出时间计算方法: Tout = ((arr +1) * (psc + 1)) / Ft us.
- * Ft=定时器工作频率,单位:Mhz
- *
- * @param arr: 自动重装值。
- * @param psc: 时钟预分频数
- * @retval 无
- */
- voidatim_timx_comp_pwm_init(uint16_t arr, uint16_t psc)
- {
- TIM_OC_InitTypeDef timx_oc_comp_pwm = {0};
- g_timx_comp_pwm_handle.Instance = ATIM_TIMX_COMP; /* 定时器x */
- g_timx_comp_pwm_handle.Init.Prescaler = psc; /* 定时器分频 */
- g_timx_comp_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;/* 递增计数 */
- g_timx_comp_pwm_handle.Init.Period = arr; /* 自动重装载值 */
- g_timx_comp_pwm_handle.Init.AutoReloadPreload =
- TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能影子寄存器*/
- HAL_TIM_OC_Init(&g_timx_comp_pwm_handle); /* 输出比较模式初始化 */
- timx_oc_comp_pwm.OCMode = TIM_OCMODE_TOGGLE; /* 比较输出模式翻转功能 */
- timx_oc_comp_pwm.Pulse = 250 - 1; /* 设置输出比较寄存器的值 */
- timx_oc_comp_pwm.OCPolarity =TIM_OCPOLARITY_HIGH; /* 输出比较极性为高 */
- HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm,
- TIM_CHANNEL_1); /* 初始化定时器的输出比较通道1 */
- /* CCR1寄存器预装载使能 */
- __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
- tim_oc_handle.Pulse = 500;
- HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &tim_oc_handle,
- TIM_CHANNEL_2); /* 初始化定时器的输出比较通道2 */
- /* CCR2寄存器预装载使能 */
- __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
- tim_oc_handle.Pulse = 750;
- HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &tim_oc_handle,
- TIM_CHANNEL_3); /* 初始化定时器的输出比较通道3 */
- /* CCR3寄存器预装载使能 */
- __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
- tim_oc_handle.Pulse = 1000;
- HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &tim_oc_handle,
- TIM_CHANNEL_4); /* 初始化定时器的输出比较通道4 */
- /* CCR4寄存器预装载使能 */
- __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
- HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
- HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
- HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
- HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
- }
复制代码在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初始化的代码存放到该函数里,其定义如下: - /**
- * @brief 定时器底层驱动,时钟使能,引脚配置
- 此函数会被HAL_TIM_OC_Init()调用
- * @param htim:定时器句柄
- * @retval 无
- */
- void HAL_TIM_OC_MspInit(TIM_HandleTypeDef *htim)
- {
- if (htim->Instance == ATIM_TIMX_COMP)
- {
- GPIO_InitTypeDef gpio_init_struct;
- ATIM_TIMX_COMP_CLK_ENABLE();
- ATIM_TIMX_COMP_CH1_GPIO_CLK_ENABLE();
- ATIM_TIMX_COMP_CH2_GPIO_CLK_ENABLE();
- ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE();
- ATIM_TIMX_COMP_CH4_GPIO_CLK_ENABLE();
- gpio_init_struct.Pin =ATIM_TIMX_COMP_CH1_GPIO_PIN;
- 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_GPIO_AF;
- HAL_GPIO_Init(ATIM_TIMX_COMP_CH1_GPIO_PORT, &gpio_init_struct);
- gpio_init_struct.Pin =ATIM_TIMX_COMP_CH2_GPIO_PIN;
- HAL_GPIO_Init(ATIM_TIMX_COMP_CH2_GPIO_PORT, &gpio_init_struct);
- gpio_init_struct.Pin =ATIM_TIMX_COMP_CH3_GPIO_PIN;
- HAL_GPIO_Init(ATIM_TIMX_COMP_CH3_GPIO_PORT, &gpio_init_struct);
- gpio_init_struct.Pin =ATIM_TIMX_COMP_CH4_GPIO_PIN;
- HAL_GPIO_Init(ATIM_TIMX_COMP_CH4_GPIO_PORT, &gpio_init_struct);
- }
- }
复制代码该函数主要是使能定时器和通道对应的IO时钟,初始化IO口。
在main.c里面编写如下代码: - int main(void)
- {
- uint8_t t = 0;
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- led_init(); /* 初始化LED */
- atim_timx_comp_pwm_init(1000 - 1, 168 - 1); /* 1Mhz的计数频率 1Khz的周期. */
- ATIM_TIMX_COMP_CH1_CCRX = 250 - 1; /* 通道1 相位25% */
- ATIM_TIMX_COMP_CH2_CCRX = 500 - 1; /* 通道2 相位50% */
- ATIM_TIMX_COMP_CH3_CCRX = 750 - 1; /* 通道3 相位75% */
- ATIM_TIMX_COMP_CH4_CCRX = 999 - 1; /* 通道4 相位99% */
- while (1)
- {
- delay_ms(10);
- t++;
- if (t >= 20)
- {
- LED0_TOGGLE(); /* LED0(RED)闪烁 */
- t = 0;
- }
- }
- }
复制代码本小节开头我们讲解了输出比较模式翻转功能如何产生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输出的情况,如下图所示: 图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%。 |