完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第十四章 PWM 输出实验 上一章,我们介绍了STM32F4的通用定时器tiM3,用该定时器的中断来控制DS1的闪烁, 这一章,我们将向大家介绍如何使用 STM32F4 的 TIM3 来产生 PWM 输出。在本章中,我们将 使用 TIM14 的通道 1 来产生 PWM 来控制 DS0 的亮度。本章分为如下几个部分: 14.1 PWM 简介 14.2 硬件设计 14.3 软件设计 14.4 下载验证 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 输出的原理。 STM32F4 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级 定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出!这里我们仅使用 TIM14 的 CH1 产生一路 PWM 输出。 要使 STM32F4 的通用定时器 TIMx 产生 PWM 输出,除了上一章介绍的寄存器外,我们还 会用到 3 个寄存器,来控制 PWM 的。这三个寄存器分别是:捕获/比较模式寄存器 (TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。 接下来我们简单介绍一下这三个寄存器。 首先是捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有 2 个:TIMx _CCMR1 和 TIMx _CCMR2,不过 TIM14 只有一个。TIMx_CCMR1 控制 CH1 和 2,而 TIMx_CCMR2 控制 CH3 和 4。以下我们将以 TIM14 为例进行介绍。TIM14_CCMR1 寄存器各位描述如图 14.1.2 所示: 图 14.1.2 TIM14_CCMR1 寄存器各位描述 该寄存器的有些位在不同模式下,功能不一样,所以在图 14.1.2 中,我们把寄存器分了 2 层,上面一层对应输出而下面的则对应输入。关于该寄存器的详细说明,请参考《STM32F4xx 中文参考手册》第 476 页,16.6.4 节。这里我们需要说明的是模式设置位 OC1M,此部分由 3 位组成。总共可以配置成 7 种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110/111。 这两种 PWM 模式的区别就是输出电平的极性相反。另外 CC1S 用于设置通道的方向(输入/输 出)默认设置为 0,就是设置通道作为输出使用。注意:这里是因为我们的 TIM14 只有 1 个通 道,所以才只有第八位有效,高八位无效,其他有多个通道的定时器,高八位也是有效的,具 体请参考《STM32F4xx 中文参考手册》对应定时器的寄存器描述。 接下来,我们介绍 TIM14 的捕获/比较使能寄存器(TIM14_CCER),该寄存器控制着各个 输入输出通道的开关。该寄存器的各位描述如图 14.1.3 所示: 图 14.1.3 TIM14_ CCER 寄存器各位描述 该寄存器比较简单,我们这里只用到了 CC1E 位,该位是输入/捕获 1 输出使能位,要想 PWM 从 IO 口输出,这个位必须设置为 1,所以我们需要设置该位为 1。该寄存器更详细的介 绍了,请参考《STM32F4xx 中文参考手册》第 478 页,16.6.5 这一节。同样,因为 TIM14 只有 1 个通道,所以才只有低四位有效,如果是其他定时器,该寄存器的其他位也可能有效。 最后,我们介绍一下捕获/比较寄存器(TIMx_CCR1~4),该寄存器总共有 4 个,对应 4 个 通道 CH1~4。不过 TIM14 只有一个,即:TIM14_CCR1,该寄存器的各位描述如图 14.1.4 所示: 图 14.1.4 寄存器 TIM14_ CCR1 各位描述 在输出模式下,该寄存器的值与 CNT 的值比较,根据比较结果产生相应动作。利用这点, 我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。 如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配 置:刹车和死区寄存器(TIMx_BDTR),该寄存器各位描述如图 14.1.5 所示: 图 14.1.5 寄存器 TIMx_ BDTR 各位描述 该寄存器,我们只需要关注最高位:MOE 位,要想高级定时器的 PWM 正常输出,则必须 设置 MOE 位为 1,否则不会有输出。注意:通用定时器不需要配置这个。其他位我们这里就不 详细介绍了,请参考《STM32F4xx 中文参考手册》第 386 页,14.4.18 这一节。 本章,我们使用的是 TIM14 的通道 1,所以我们需要修改 TIM14_CCR1 以实现脉宽控制 DS0 的亮度。至此,我们把本章要用的几个相关寄存器都介绍完了,本章要实现通过 TIM14_CH1 输出 PWM 来控制 DS0 的亮度。下面我们介绍通过库函数来配置该功能的步骤。 首先要提到的是,PWM 实际跟上一章节一样使用的是定时器的功能,所以相关的函数设 置同样在库函数文件 stm32f4xx_hal_tim.h 和 stm32f4xx_hal_tim.c 文件中。 1)开启 TIM14 和 GPIO 时钟,配置 PF9 选择复用功能 AF9(TIM14)输出。 要使用 TIM14,我们必须先开启 TIM14 的时钟,这点相信大家看了这么多代码,应该明白 了。这里我们还要配置 PF9 为复用(AF9)输出,才可以实现 TIM14_CH1 的 PWM 经过 PF9 输出。 HAL 库使能 TIM14 时钟和 GPIO 时钟方法是: __HAL_RCC_TIM14_CLK_ENABLE();//使能定时器 14__HAL_RCC_GPIOF_CLK_ENABLE();//开启 GPIOB 时钟接下来便是要配置 PF9 复用映射为 TIM3 的 PWM 输出引脚。关于 IO 口复用映射,在串口 通信实验中有详细讲解,主要是通过函数 HAL_GPIO_Init 来实现的: GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_TIM14_CLK_ENABLE();//使能定时器 14__HAL_RCC_GPIOF_CLK_ENABLE();//开启 GPIOF 时钟GPIO_Initure.Pin=GPIO_PIN_9;//PF9GPIO_Initure.Mode=GPIO_MODE_AF_PP;//复用推挽输出GPIO_Initure.Pull=GPIO_PULLUP;//上拉GPIO_Initure.Speed=GPIO_SPEED_HIGH;//高速GPIO_Initure.Alternate= GPIO_AF9_TIM14;//PF9 复用为 TIM14_CH1HAL_GPIO_Init(GPIOF,&GPIO_Initure);这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看 STM32F4 对应的数据 手册,比如我们 PWM 实验,我们使用的是定时器 14 的通道 1,对应的引脚 PF9 可以从数据手 册表中查看: 2)初始化 TIM14,设置 TIM14 的 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_Init 初始化定时器参数的时候,它内部调用的回调函数为 HAL_TIM_Base_MspInit,这里大家注意 区分。 所以大家一定要注意,使用 HAL_TIM_PWM_Init 初始化定时器时,回调函数为: HAL_TIM_PWM_MspInit,该函数声明为: void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim);一般情况下,上面步骤 1 的时钟使能和 IO 口初始化映射都编写在回调函数内部。 3)设置 TIM14_CH1 的 PWM 模式,使能 TIM14 的 CH1 输出。 接下来,我们要设置 TIM14_CH1 为 PWM 模式(默认是冻结的),因为我们的 DS0 是低电 平亮,而我们希望当 CCR1 的值小的时候,DS0 就暗,CCR1 值大的时候,DS0 就亮,所以我 们要通过配置 TIM14_CCMR1 的相关位来控制 TIM14_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_InitTypeDef 结构体指针类型,这也是该函数最重要的参数。 该参数用来设置 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。成员变量 Pulse 用来设置捕获比较值。成员变量 TIM_OCPolarity 用 来 设 置 输 出 极 性 是 高 还 是 低 。 其 他 的 参 数 TIM_OutputNState , TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 是高级定时器才用到的。 第 三 个 参 数 Channel 用 来 选 择 定 时 器 的 通 道 , 取 值 范 围 为 TIM_CHANNEL_1~ TIM_CHANNEL_4。这里我们使用的是定时器 14 的通道 1,所以取值为 TIM_CHANNEL_1 即 可。 例如我们要初始化定时器 14 的通道 1 为 PWM 模式 1,输出极性为低,那么实例代码为: TIM_OC_InitTypeDef TIM14_CH1Handler; //定时器 14 通道 1 句柄TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; //模式选择 PWM1TIM3_CH4Handler.Pulse=arr/2; //设置比较值,此值用来确定占空比TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);4)使能 TIM14。 在完成以上设置了之后,我们需要使能 TIM14。使能 TIM14 的方法前面已经讲解过: 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)修改 TIM14_CCR1 来控制占空比。 最后,在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定 的,而我们通过修改比较值 TIM14_CCR1 则可以控制 CH1 的输出占空比。继而控制 DS0 的亮 度。HAL 库中并没有提供独立的修改占空比函数,这里我们可以编写这样一个函数如下: //设置 TIM 通道 4 的占空比//compare:比较值void TIM_SetTIM14Compare1(u32 compare){TIM14->CCR1=compare;}实际上,因为调用函数 HAL_TIM_PWM_ConfigChanne 进行 PWM 配置的时候可以设置比 较值,所以我们也可以直接使用该函数来达到修改占空比的目的: void TIM_SetCompare1(TIM_TypeDef *TIMx,u32 compare){TIM14_CH1Handler.Pulse=compare;HAL_TIM_PWM_ConfigChannel(&TIM14_Handler,&TIM14_CH1Handler,TIM_CHANNEL_1);}这种方法因为要调用 HAL_TIM_PWM_ConfigChannel 函数对各种初始化参数进行重新设 置,所以大家在使用中一定要注意,例如在实时系统中如果多个线程同时修改初始化结构体相 关参数,可能导致结果混乱。 14.2 硬件设计 本实验用到的硬件资源有: 1) 指示灯 DS0 2) 定时器 TIM14 这两个我们前面都已经介绍了,因为 TIM14_CH1 可以通过 PF9 输出 PWM,而 DS0 就是 直接节在 PF9 上面的,所以电路上并没有任何变化。 14.3 软件设计 打开 PWM 输出实验代码可以看到,我们相比上一节,并没有添加其他任何 HAL 库文件, 而是添加了几个函数 Timer.c 源文件代码如下://TIM14 PWM 部分初始化//arr:自动重装值。//psc:时钟预分频数//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.//Ft=定时器工作频率,单位:Mhzvoid TIM14_PWM_Init(u16 arr,u16 psc){ TIM14_Handler.Instance=TIM14;//定时器 14 TIM14_Handler.Init.Prescaler=psc;//定时器分频 TIM14_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;//向上计数模式 TIM14_Handler.Init.Period=arr;//自动重装载值 TIM14_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&TIM14_Handler);//初始化 PWM TIM14_CH1Handler.OCMode=TIM_OCMODE_PWM1;//模式选择 PWM1 TIM14_CH1Handler.Pulse=arr/2;//设置比较值,此值用来确定占空比,默认比较值为自动重装载值//的一半,即占空比为 50% TIM14_CH1Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低 HAL_TIM_PWM_ConfigChannel(&TIM14_Handler,&TIM14_CH1Handler,TIM_CHANNEL_1);//配置 TIM14 通道 1 HAL_TIM_PWM_Start(&TIM14_Handler,TIM_CHANNEL_1);//开启 PWM 通道 1}//定时器底层驱动,时钟使能,引脚配置//此函数会被 HAL_TIM_PWM_Init()调用//htim:定时器句柄void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim){GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_TIM14_CLK_ENABLE();//使能定时器 14__HAL_RCC_GPIOF_CLK_ENABLE();//开启 GPIOF 时钟GPIO_Initure.Pin=GPIO_PIN_9; //PF9GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出GPIO_Initure.Pull=GPIO_PULLUP; //上拉GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速GPIO_Initure.Alternate= GPIO_AF9_TIM14;//PF9 复用为 TIM14_CH1HAL_GPIO_Init(GPIOF,&GPIO_Initure);}//设置 TIM 通道 4 的占空比//compare:比较值void TIM_SetTIM14Compare1(u32 compare){TIM14->CCR1=compare;}此部分代码包含了上面介绍的 PWM 输出设置的前 5 个步骤。这里我们关于 TIM14 的设置 就不再说了。 接下来,我们看看主程序里面的 main 函数如下: int main(void){u8 dir=1; u16 led0pwmval=0; HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhzdelay_init(168); //初始化延时函数uart_init(115200); //初始化 USARTLED_Init();//初始化 LEDTIM3_Init(5000-1,8400-1); //定时器 3 初始化,周期为 500msTIM14_PWM_Init(500-1,84-1); //84M/84=1M 的计数频率,自动重装载为 500,//那么 PWM 频率为 1M/500=2kHZ while(1) {delay_ms(10);if(dir)led0pwmval++;//dir==1 led0pwmval 递增else led0pwmval--;//dir==0 led0pwmval 递减if(led0pwmval>300)dir=0;//led0pwmval 到达 300 后,方向为递减if(led0pwmval==0)dir=1;//led0pwmval 递减到 0 后,方向改为递增TIM_SetTIM14Compare1(led0pwmval);//修改比较值,修改占空比 }}这里,我们从死循环函数可以看出,我们将 led0pwmval 这个值设置为 PWM 比较值,也就 是通过 led0pwmval 来控制 PWM 的占空比,然后控制 led0pwmval 的值从 0 变到 300,然后又 从 300 变到 0,如此循环,因此 DS0 的亮度也会跟着信号的占空比变化从暗变到亮,然后又从 亮变到暗。至于这里的值,我们为什么取 300,是因为 PWM 的输出占空比达到这个值的时候, 我们的 LED 亮度变化就不大了(虽然最大值可以设置到 499),因此设计过大的值在这里是没 必要的。至此,我们的软件设计就完成了。 14.4 下载验证 在完成软件设计之后,将我们将编译好的文件下载到探索者 STM32F4 开发板上,观看其 运行结果是否与我们编写的一致。如果没有错误,我们将看 DS0 不停的由暗变到亮,然后又从 亮变到暗。每个过程持续时间大概为 3 秒钟左右。 实际运行结果如下图 14.4.1 所示: 图 14.4.1 PWM 控制 DS0 亮度 |
|
相关推荐
|
|
1187 浏览 0 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
1122 浏览 2 评论
2223 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1311 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1735 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-27 12:20 , Processed in 0.659316 second(s), Total 68, Slave 51 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号