完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:【正点原子】 NANO STM32F103 开发板
2)摘自《正点原子STM32 F1 开发指南(NANO 板-HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第十四章 PWM 输出实验 上一章,我们介绍了 STM32F1 的通用定时器 tiM3,用该定时器的中断来控制 DS1 的闪烁,这一章,我们将向大家介绍如何使用 STM32F1 的 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 输出的原理。 中容量的 STM32 定时器可以用来产生 PWM 输出,其中高级定时器 TIM1 可以 输出 4 路 的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出,这样,中容量的 STM32 最 多可以同时产生 20 路 PWM 输出!这里我们仅利用 TIM3 的 CH1 产生一路 PWM 输出。如果要 产生多路输出,大家可以根据我们的代码稍作修改即可。 同样,我们首先通过对 PWM 相关的寄存器进行讲解,大家了解了定时器 TIM3 的 PWM 原理之后,我们再讲解怎么使用库函数产生 PWM 输出。 要使 STM32 的通用定时器 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.1 中,我们把寄存器分了 2 层,上面一层对应输出而下面的则对应输入。关于该寄存器的详细说明,请参考《STM32 参考 手册》第 288 页,14.4.7 一节。这里我们需要说明的是模式设置位 OCxM,此部分由 3 位组成。 总共可以配置成 7 种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110/111。这两种 PWM 模式的区别就是输出电平的极性相反。 接下来,我们介绍捕获/比较使能寄存器(TIMx_CCER),该寄存器控制着各个输入输出 通道的开关。该寄存器的各位描述如图 14.1.3 所示: 图 14.1.3 TIMx_ CCER 寄存器各位描述 该寄存器比较简单,我们这里只用到了 CC1E 位,该位是输入/捕获 2 输出使能位,要想 PWM 从 IO 口输出,这个位必须设置为 1,所以我们需要设置该位为 1。该寄存器更详细的介 绍了,请参考《STM32 参考手册》第 292 页,14.4.9 这一节。 最后,我们介绍一下捕获/比较寄存器(TIMx_CCR1~4),该寄存器总共有 4 个,对应 4 个输通道 CH1~4。因为这 4 个寄存器都差不多,我们仅以 TIMx_CCR1 为例介绍,该寄存器的 各位描述如图 14.1.4 所示: 图 14.1.4 寄存器 TIMx_ CCR1 各位描述 在输出模式下,该寄存器的值与 CNT 的值比较,根据比较结果产生相应动作。利用这点, 我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。本章,我们使用的是 TIM3 的通道 1,所以我们需要修改 TIM3_CCR1 以实现脉宽控制 DS6 的亮度。 我们要利用 TIM3 的 CH1 输出 PWM 来控制 DS6 的亮度,但是 TIM3_CH1 默认是接在 PA6 上面的,而我们的 DS6 接在 PC6 上面,如果普通 MCU,可能就只能用飞线把 PA6 飞到 PC6 上来实现了,不过,我们用的是 STM32,它比较高级,可以通过重映射功能,把 TIM3_CH1 映射到 PC6 上。 STM32 的重映射控制是由复用重映射和调试 IO 配置寄存器(AFIO_MAPR)控制的,该 寄存器的各位描述如图 14.1.5 所示: 图 14.1.5 寄存器 AFIO_MAPR 各位描述 我们这里用到的是 TIM3 的重映射,从上图可以看出,TIM3_REMAP 是由[11:10]这 2 个位 控制的。TIM3_REMAP[1:0]重映射控制表如表 14.1.1 所示: 表 14.1.1 TIM3_REMAP 重映射控制表 默认条件下,TIM3_REMAP[1:0]为 00,是没有重映射的,所以 TIM3_CH1~TIM3_CH4 分 别是接在 PA6、PA7、PB0 和 PB1 上的,而我们想让 TIM3_CH1 映射到 PC6 上,则需要设置 TIM3_REMAP[1:0]=11,即部分重映射,这里需要注意,此时 TIM3_CH2 也被映射到 PC7 上了。 至此,我们把本章要用的几个相关寄存器都介绍完了,本章要实现通过重映射 TIM3_CH1 到 PC6 上,由 TIM3_CH1 输出 PWM 来控制 DS6 的亮度。下面我们介绍通过库函数来配置该 功能的步骤。 首先要提到的是,PWM 实际跟上一章节一样使用的时定时器的功能,所以相关的函数设 置同样在库函数文件 stm32f1xx_hal_tim.h 和 stm32f1xx_hal_tim.c 文件中。 1)开启 TIM3 和 GPIO 时钟,配置 TIM3 完全重映射(PC6)输出。 要使用 TIM3,我们必须先开启 TIM3 的时钟,这点相信大家看了这么多代码,应该明白了。 这里我们还要配置 PC6 为复用输出,这是因为 TIM3_CH1 通道将重映射到 PC6 上,此时,PC6 属于复用功能输出。HAL 库使能 TIM3 时钟和 GPIO 时钟方法是: __HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器 3 __HAL_RCC_GPIOC_CLK_ENABLE(); //开启 GPIOC 时钟 这两行代码很容易组织,这里不做过多重复的讲解。设置 PC6 为复用功能输出的方法在前面的 几个实验都有类似的讲解,相信大家很明白,这里简单列出 GPIO 初始化的一行代码即可: GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 2)设置 TIM3_CH1 重映射到 PC6 上。 因为 TIM3_CH1 默认是接在 PA6 上的,所以我们需要设置 TIM3_REMAP 为完全重映射(通 过 AFIO_MAPR 配置),让 TIM3_CH1 重映射到 PC6 上面。在 HAL 函数函数里面设置完全重 映射的函数是: __HAL_AFIO_REMAP_TIM3_ENABLE(); //TIM3 通道引脚完全重映射使能 3)初始化 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 初始化都编写在回调函数内部。 4)设置 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 5)使能 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); 6)修改 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 的完全重映射功能,把 TIM3_CH1 直接 映射到了 PC6 上,而通过前面的学习,我们知道 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; TIM3_Handler.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; //使能自动重载 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_AFIO_REMAP_TIM3_ENABLE(); //TIM3 通道引脚完全重映射使能 __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; //高速 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(RCC_PLL_MUL9); //设置时钟,72M delay_init(72); //初始化延时函数 uart_init(9600); //初始化串口 LED_Init(); //初始化 LED TIM3_PWM_Init(899,0); //不分频。PWM 频率=72000/(899+1)=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 STM32 开发板上,观看其运 行结果是否与我们编写的一致。如果没有错误,我们将看 DS6 不停的由暗变到亮,然后又从亮 变到暗。每个过程持续时间大概为 3 秒钟左右。 实际运行结果如下图 14.4.1 所示: 图 14.4.1 PWM 控制 DS0 亮度 14.5 STM32CubeMX 配置定时器 PWM 输出功能 使用 STM32CubeMX 配置 PWM 输出的配置步骤和配置定时器中断的配置步骤非常接近, 步骤如下: ① 在 Pinout->TIM3 配置项中,配置 Channel1 的值为 PWM generation CH1,然后 Clock Source 为 Internal Clock。操作过程如下图 14.5.1 所示: 图 14.5.1 TIM3 配置 ② 进入 Configuration->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 20:14 , Processed in 0.561542 second(s), Total 67, Slave 49 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号