完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:ALIENTEK NANO STM32F411 V1开发板
2)摘自《正点原子STM32F4 开发指南(HAL 库版》关注官方微信号公众号,获取更多资料:正点原子 第十四章 PWM 输出实验 上一章,我们介绍了 STM32F4 的通用定时器 tiM3,用该定时器的中断来控制 DS1 的闪烁,这一章,我们将向大家介绍如何使用 STM32F4 的 TIM3 来产生 PWM 输出。在本章中,我们将使用 TIM3 的通道 1,把通道 1 重映射到 PC6,产生 PWM 来控制 DS6 的亮度。本章分为如下几个部分: 14.1 PWM 简介 14.2 硬件设计 14.3 软件设计 14.4 下载验证 14.5 STM32CubeMX 配置定时器 PWM 输出功能 14.1 PWM 简介 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用 微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制,PWM 原理如图 14.1.1 所示: 图 14.1.1 PWM 原理示意图 图 14.1.1 就是一个简单的 PWM 原理示意图。图中,我们假定定时器工作在向上计数 PWM模式,且当 CNT 示意图:当 CNT 值小于 CCRx 的时候,IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的 频率,这就是 PWM 输出的原理。 STM32F411 定时器可以用来产生 PWM 输出,其中高级定时器 TIM1 可以输出 4 路。而通用定时器可同时产生多达 4 路的 PWM 输出!这里我们仅使用 TIM3 的 CH1 产生一路 PWM 输出。如果要产生多路输出,大家可以根据我们的代码稍作修改即可。要使 STM32F4 的通用定时器 TIMx 产生 PWM 输出,除了上一章介绍的寄存器外,我们还会用到 3 个寄存器,来控制 PWM 的。这三个寄存器分别是:捕获/比较模式寄存器 (TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。 接下来我们简单介绍一下这三个寄存器。 首先是捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器总共有 2 个,TIMx _CCMR1 和 TIMx _CCMR2。TIMx_CCMR1 控制 CH1 和 2,而 TIMx_CCMR2 控制 CH3 和 4。该寄存器 的各位描述如图 14.1.2 所示: 图 14.1.2 TIMx_CCMR1 寄存器各位描述 该寄存器的有些位在不同模式下,功能不一样,所以在图 14.1.2 中,我们把寄存器分了 2 层 , 上 面 一 层 对 应 输 出 而 下 面 的 则 对 应 输 入 。 关 于 该 寄 存 器 的 详 细 说 明 , 请 参 考 《STM32F411xC/E 参考手册》第 361 页, 13.4.7 一节。这里我们需要说明的是模式设置位 OC1M, 此部分由 3 位组成。总共可以配置成 7 种模式,我们使用的是 PWM 模式,所以这 3 位必须设 置为 110/111。这两种 PWM 模式的区别就是输出电平的极性相反。另外 CC1S 用于设置通道的 方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。 接下来,我们介绍捕获/比较使能寄存器(TIMx_CCER),该寄存器控制着各个输入输出 通道的开关。该寄存器的各位描述如图 14.1.3 所示: 图 14.1.3 TIMx_ CCER 寄存器各位描述 该寄存器比较简单,我们这里只用到了 CC1E 位,该位是输入/捕获 1 输出使能位,要想 PWM 从 IO 口输出,这个位必须设置为 1,所以我们需要设置该位为 1。该寄存器更详细的介绍了,请参考《STM32F411xC/E 参考手册》第 365 页,13.4.9 这一节。 最后,我们介绍一下捕获/比较寄存器(TIMx_CCR1~4),该寄存器总共有 4 个,对应 4个输通道 CH1~4。因为这 4 个寄存器都差不多,我们仅以 TIMx_CCR1 为例介绍,该寄存器的 各位描述如图 14.1.4 所示: 图 14.1.4 寄存器 TIMx_ CCR1 各位描述 我们可以看到该寄存器是 32 位,其中 16~31bit 是 TIM2 和 TIM5 才会用到(因为 TIM2 和 TIM5 是 32 位),所以这里我们只用到 0~15bit。 在输出模式下,该寄存器的值与 CNT 的值比较,根据比较结果产生相应动作。利用这点, 我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。 如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配 置:刹车和死区寄存器(TIMx_BDTR),该寄存器各位描述如图 14.1.5 所示: 图 14.1.5 寄存器 TIMx_BDTR 各位描述 该寄存器,我们只需要关注最高位:MOE 位,要想高级定时器的 PWM 正常输出,则必须 设置 MOE 位为 1,否则不会有输出。注意:通用定时器不需要配置这个。其他位我们这里就不 详细介绍了,请参考《STM32F411xC/E 参考手册》第 309 页,12.4.18 这一节。 至此,我们把本章要用的几个相关寄存器都介绍完了,本章要实现通过 TIM3_CH1 输出 PWM 来控制 DS6 的亮度。下面我们介绍通过库函数来配置该功能的步骤。 首先要提到的是,PWM 实际跟上一章节一样使用的时定时器的功能,所以相关的函数设 置同样在库函数文件 stm32f4xx_hal_tim.h 和 stm32f4xx_hal_tim.c 文件中。 1)开启 TIM3 和 GPIO 时钟,配置 PC6 选择复用功能 AF2(TIM3)输出。 要使用 TIM3,我们必须先开启 TIM3 的时钟,这点相信大家看了这么多代码,应该明白了。 这里我们还要配置 PC6 为复用(AF2)输出,才可以实现 TIM3_CH1 的 PWM 经过 PC6 输出。 HAL 库使能 TIM3 时钟和 GPIO 时钟方法是: __HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器 3 __HAL_RCC_GPIOC_CLK_ENABLE(); //开启 GPIOC 时钟 接下来便是要配置 PC6 复用映射为 TIM3 的 PWM 输出引脚。关于 IO 复用映射,在串口通 信实验有详细详解,主要是通过函数 HAL_GPIO_Init 来实现的: GPIO_InitTypeDef GPIO_Initure; GPIO_Initure.Pin=GPIO_PIN_6; //PC6 GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速 GPIO_Initure.Alternate= GPIO_AF2_TIM3; //PC6 复用为 TIM3_CH1 HAL_GPIO_Init(GPIOC,&GPIO_Initure); 在 IO 口初始化配置中,我们只需要将成员变量 Mode 配置为复用推挽输出,同时成员变量 Alternate 配置为 GPIO_AF2_TIM3,即可实现 PC6 映射为定时器 3 通道 1 的 PWM 输出引脚。 这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看 STM32F4 对应的数据 手册,比如我们 PWM 实验,我们使用的是定时器 3 的通道 4,对应的引脚 PC6 可以从数据手 册表中查看: 2)初始化 TIM3,设置 TIM3 的 ARR 和 PSC 等参数。 根据前面的讲解,初始化定时器的 ARR 和 PSC 等参数时通过函数 HAL_TIM_Base_Init 来 实现的,但是这里大家要注意,对于我们使用定时器的 PWM 输出功能时,HAL 库为我们提供 了一个独立的定时器初始化函数 HAL_TIM_PWM_Init,该函数声明为: HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim); 该函数实现的功能以及使用方法和 HAL_TIM_Base_Init 都是类似的,作用都是初始化定时 器 的 ARR 和 PSC 等 参 数 。 为 什 么 HAL 库 要 提 供 这 个 函 数 而 不 直 接 让 我 们 使 用 HAL_TIM_Base_Init 函数呢? 这 是 因 为 HAL 库 为 定 时 器 的 PWM 输 出 定 义 了 单 独 的 MSP 回 调 函 数 HAL_TIM_PWM_MspInit,也就是说,当我们调用 HAL_TIM_PWM_Init 进行 PWM 初始化之后, 该函数内部会调用 MSP 回调函数 HAL_TIM_PWM_MspInit。而当我们使用 HAL_TIM_Base_Inir 初始化定时器参数的时候,它内部调用的回调函数为 HAL_TIM_Base_MspInit,这里大家注意 区分。 所以大家一定要注意,使用 HAL_TIM_PWM_Init 初始化定时器时,回调函数为: HAL_TIM_Base_MspInit,该函数声明为: void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim); 一般情况下,上面步骤 1 的时钟施恩那个和 IO 初始化都编写在回调函数内部。 3)设置 TIM3_CH1 的 PWM 模式,输出比较极性,比较值等参数。 接下来,我们要设置 TIM3_CH1 为 PWM 模式(默认时冻结的),因为我们的 DS0 是低电 平亮,而我们希望当 CCR1 的值小的时候,DS6 就暗,CCR1 值大的时候,DS6 就亮,所以我 们要通过配置 TIM3_CCMR1 的相关位来控制 TIM3_CH1 的模式。 在 HAL 库函数中,PWM 通道设置是通过函数 HAL_TIM_PWM_ConfigChannel 来设置的: HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef* sConfig, uint32_t Channel); 第一个参数 htim 时定时器初始化句柄,也就是 TIM_HandleTypeDef 结构体指针类型,这 和 HAL_TIM_PWM_Init 函数调用时候参数保持一致即可。 第二个参数 sConfig 是 TIM_OC_IniTypeDef 结构体指针类型,这也是该函数最重要的参数。该 参数用来设置 PWM 输出模式,极性,比较值等重要参数。首先我们来看看结构体定义: typedef struct { uint32_t OCMode; //PWM 模式 uint32_t Pulse; //捕获比较值 uint32_t OCPolarity; //极性 uint32_t OCNPolarity; uint32_t OCFastMode; //快速模式 uint32_t OCIdleState; uint32_t OCNIdleState; }TIM_OC_InitTypeDef; 该结构体成员我们重点关注前三个。成员变量 OCMode 用来设置模式,也就是我们前面讲 解的 7 种模式,这里我们设置为 PWM 模式 1。成员变量 Plus 用来设置捕获比较值。成员变量 TIM_OCPolarity 用 来 设 置 输 出 极 性 是 高 还 是 低 。 其 他 的 参 数 TIM_OutputNState , TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 是高级定时器才用到的。 第 三 个 参 数 Channel 用 来 选 择 定 时 器 的 通 道 , 取 值 范 围 为 TIM_CHANNEL_1~TIM_CHANNEL_4 即可。 例如我们要初始化定时器 3 的通道 1 为 PWM 模式 1,输出极性为低,那么实例代码为: TIM_OC_InitTypeDef TIM3_CH1Handler; //定时器 3 通道 1 句柄 TIM3_CH1Handler.OCMode=TIM_OCMODE_PWM1; //模式选择 PWM1 TIM3_CH1Handler.Pulse=arr/2; //设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为 50% TIM3_CH1Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低 HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH1Handler,TIM_CHANNEL_1); //配置 TIM3 通道 1 4)使能 TIM3,使能 TIM3 的 CH4 输出。 在完成以上设置之后,我们需要使能 TIM3 并且使能 TIM3_CH1 输出。在 HAL 库中,函 数 HAL_TIM_PWM_Start 可以用来实现这两个功能,函数声明如下: HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel); 该函数第二个入口参数 Channel 是用来设置要使能的通道号。 对于单独使能定时器的方法,在上一章定时器实验我们已经讲解。实际上,HAL 库也同样 提供了单独使能定时器的输出通道函数,函数为: void TIM_CCxChannelCmd(TIM_TypeDef* TIMx, uint32_t Channel, uint32_t ChannelState); 5)修改 TIM3_CCR1 来控制占空比。 最后,在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定 的,而我们通过修改 TIM3_CCR1 则可以控制 CH1 的输出占空比。继而控制 DS6 的亮度。 HAL 库中并没有提供独立的修改占空比函数,这里我们可以编写这样一个函数如下: //设置 TIM3 通道 1 的占空比 //compare:比较值 void TIM_SetTIM3Compare1(u32 compare) { TIM3->CCR1=compare; } 实际上,因为调用函数 HAL_TIM_PWM_ConfigChance 进行 PWM 配置的时候可以设置比 较值,所以我们也可以直接使用该函数来达到修改占空比的目的: void TIM_SetTIM3Compare1(TIM_TypeDef*TIMX,u32 compare) { TIM3_CH1Handler.Plue=compare; HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH1Handler, TIM_CHANNEL_1); } 这种方法因为要调用 HAL_TIM_PWM_ConfigChannel 函数对各种初始化参数进行重新设 置,所以大家在使用中一定要注意,例如在实时系统中如果多个线程同时修改初始化结构体相 关参数,可能导致结果混乱。 14.2 硬件设计 本实验用到的硬件资源有: 1) 指示灯 DS6 2) 定时器 TIM3 这两个前面都有介绍,因为 TIM3_CH1 可以通过 PC6 输出 PWM,而通过前面的学习,我 们知道 PC6 和 DS6 是直接连接的,所以电路上并没有任何变化。 14.3 软件设计 打开 PWM 输出实验工程可以看到,我们相比上一节,并没有添加其他任何 HAL 库文件, 因为 PWM 是使用的定时器资源,所以跟上一讲使用的是同样的 HAL 库文件。同时我们修改了 timer.c 和 timer.h 的内容,删除了上一章实验源码,直接把 PWM 功能相关函数和定义放在了这 两个文件中。 timer.c 源文件代码如下: TIM_HandleTypeDef TIM3_Handler; //定时器句柄 TIM_OC_InitTypeDef TIM3_CH1Handler; //定时器 3 通道 1 句柄 //TIM3 PWM 部分初始化 //arr:自动重装值。 //psc:时钟预分频数 //定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us. //Ft=定时器工作频率,单位:Mhz void TIM3_PWM_Init(u16 arr,u16 psc) { TIM3_Handler.Instance=TIM3; //定时器 3 TIM3_Handler.Init.Prescaler=psc; //定时器分频 TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;//向上计数模式 TIM3_Handler.Init.Period=arr; //自动重装载值 TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&TIM3_Handler); //初始化 PWM TIM3_CH1Handler.OCMode=TIM_OCMODE_PWM1; //模式选择 PWM1 TIM3_CH1Handler.Pulse=arr/2; //设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为 50% TIM3_CH1Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低 HAL_TIM_PWM_ConfigChannel(&TIM3_Handler, &TIM3_CH1Handler,TIM_CHANNEL_1);//配置 TIM3 通道 1 HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_1);//开启 PWM 通道 1 } //定时器底层驱动,时钟使能,引脚配置 //此函数会被 HAL_TIM_PWM_Init()调用 //htim:定时器句柄 void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) { GPIO_InitTypeDef GPIO_Initure; if(htim->Instance==TIM3) { __HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器 3 __HAL_RCC_GPIOC_CLK_ENABLE(); //开启 GPIOC 时钟 GPIO_Initure.Pin=GPIO_PIN_6; //PC6 GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速 GPIO_Initure.Alternate= GPIO_AF2_TIM3; //PC6 复用为 TIM3_CH1 HAL_GPIO_Init(GPIOC,&GPIO_Initure); } } //设置 TIM3 通道 1 的占空比 //compare:比较值 void TIM_SetTIM3Compare1(u32 compare) { TIM3->CCR1=compare; } 此部分代码包含了三个函数,完全实现了前面 14.1 小节讲解的配置步骤。第一个函数 TIM3_PWM_Init 实现的是 14.1 小节讲解的步骤 3~4,搜先通过调用定时器 HAL 库函数 HAL_TIM_PWM_Init 初始化 TIM3 并设置 TIM3 的 ARR 和 PSC 等参数,其次通过调用函数 HAL_TIM_PWM_ConfigChannel 设置 TIM3_CH1 的 PWM 模式以及比较值等参数,最后通过调 用函数 HAL_TIM_PWM_Start 来使能 TIM3 以及使能 PWM 通道 TIM3_CH1 输出。第二个函数 HAL_TIM_PWM_MspInit 是 PWM 的 MSP 初始化回调函数,该函数实现的是 14.1 小节步骤 1~2, 主要是使能相应时钟、完全重映射以及初始化定时器通道 TIM3_CH1 对应的 IO 口模式。第三 个函数 TIM_SerTIM3Compare1 是用户自定义的设置比较值函数,这在我们 13.1 小节步骤 6 有 详细讲解。 接下来,我们看看 main 函数内容如下: int main(void) { u16 led6pwmval=0; u8 dir=1; HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(96,4,2,4); //设置时钟,96Mhz delay_init(96); //初始化延时函数 LED_Init(); //初始化 LED TIM3_PWM_Init(1200-1,0); //不分频。PWM 频率=96000/1200=80Khz while(1) { delay_ms(10); if(dir)led6pwmval++; //dir==1 led6pwmval 递增 else led6pwmval--; //dir==0 led6pwmval 递减 if(led6pwmval>300)dir=0; //led6pwmval 到到 300 后,方向改为递减 if(led6pwmval==0)dir=1; //led6pwmval 递减到 0 后,方向改为递增 TIM_SetTIM3Compare1(led6pwmval);//修改比较值,修改占空比 } } 这里,我们从死循环函数可以看出,我们将 led6pwmval 这个值设置为 PWM 比较值,也就 是通过 led6pwmval 来控制 PWM 的占空比,然后控制 led6pwmval 的值从 0 变到 300,然后又 从 300 变到 0,如此循环,因此 DS6 的亮度也会跟着从暗变到亮,然后又从亮变到暗。至于这 里的值,我们为什么取 300,是因为 PWM 的输出占空比达到这个值的时候,我们的 LED 亮度 变化就不大了(虽然最大值可以设置到 899),因此设计过大的值在这里是没必要的。至此, 我们的软件设计就完成了。 14.4 下载验证 在完成软件设计之后,将我们将编译好的文件下载到 NANO STM32F4 开发板上,观看其 运行结果是否与我们编写的一致。如果没有错误,我们将看 DS6 不停的由暗变到亮,然后又从 亮变到暗。每个过程持续时间大概为 3 秒钟左右。 实际运行结果如下图 14.4.1 所示: 图 14.4.1 PWM 控制 DS0 亮度 14.5 STM32CubeMX 配置定时器 PWM 输出功能 使用 STM32CubeMX 配置 PWM 输出的配置步骤和配置定时器中断的配置步骤非常接近, 步骤如下: 1 在 Pinout->TIM3 配置项中,配置 Channel1 的值为 PWM generation CH1,然后 Clock Source 为 Internal Clock。操作过程如下图 14.5.1 所示: 图 14.5.1 TIM3 配置 2 进入 TIMERS->TIM3 配置页,在弹出的界面中点击 Parameter Settings 选项卡,Counter Settings 配置栏下面的四个选项就是用来配置定时器的预分频系数,自动装载值,计数模式以及 时钟分频因子。在界面的 PWM Generation Channel1 配置栏配置 PWM 模式,比较值,极性等 参数,操作方法如下图 14.5.2 所示: 图 14.5.2 TIM3 参数设置界面 本章 PWM 输出实验,我们并没有使用到中断,所以我们不需要使能中断和配置 NVIC, 经过上面的配置就可以生成工程源码,大家可以和本实验工程对比参考学习。 |
|
相关推荐
|
|
1020 浏览 0 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
996 浏览 2 评论
2102 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1202 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1621 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-23 16:26 , Processed in 0.353672 second(s), Total 34, Slave 27 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号