文章目录
- 一、PWM 简介
- 二、硬件设计
- 三、软件设计
- 四、实验现象
- 五、STM32CubeMX 配置定时器 PWM 输出功能
上一章,我们介绍了
STM32F429 的通用定时器
TIM3,用该定时器的中断来控制
DS1 的闪烁,这一章,我们将向大家介绍如何使用
STM32F429 的
TIM3 来产生
PWM 输出。在本章中,我们将使用
TIM3 的通道 4 来产生
PWM 来控制
DS0 的亮度。
一、PWM 简介
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制,PWM 原理如图所示:

上图就是一个简单的 PWM 原理示意图。图中,我们假定定时器工作在向上计数 PWM模式,且当CNT<CCRx 时,输出 0,当 CNT>=CCRx 时输出 1。那么就可以得到如上的 PWM示意图:当 CNT 值小于 CCRx 的时候,IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理。
STM32F429 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出!这里我们仅使用 TIM3 的 CH4 产生一路 PWM 输出。
要使 STM32F429 的通用定时器 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。以TIM3为例,TIM3_CCMR2寄存器各位描述如图所示:

该寄存器的有些位在不同模式下,功能不一样,所以在图中,把寄存器分了 2 层,上面一层对应输出而下面的则对应输入。关于该寄存器的详细说明,请参考《STM32F4xx中文参考手册》第 435 页。这里我们需要说明的是模式设置位OC4M,此部分由 3 位组成。总共可以配置成 7 种模式,我们使用的是PWM模式,所以这 3 位必须设置为110/111。这两种PWM模式的区别就是输出电平的极性相反。另外CC4S用于设置通道的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。 -
捕获/比较使能寄存器:
TIMx_CCER
该寄存器控制着各个输入输出通道的开关。该寄存器的各位描述如图所示:

该寄存器比较简单,我们这里只用到了CC4E位,该位是输入/捕获 4 输出使能位,要想PWM从IO口输出,这个位必须设置为 1,所以我们需要设置该位为 1。该寄存器更详细的介绍了,请参考《STM32F4xx 中文参考手册》第 436 页。 -
捕获/比较寄存器:
TIMx_CCR1~4
该寄存器总共有 4 个,对应 4 个通道CH1~4。我们使用的是通道 4,TIM3_CCR4寄存器的各位描述如图所示:

在输出模式下,该寄存器的值与CNT的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制PWM的输出脉宽了。如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配置:刹车和死区寄存器(TIMx_BDTR),该寄存器各位描述如图所示:

该寄存器,我们只需要关注最高位:MOE位,要想高级定时器的PWM正常输出,则必须设置MOE位为 1,否则不会有输出。注意:通用定时器不需要配置这个。该寄存器更详细的介绍请参考《STM32F4xx 中文参考手册》第 386 页。
本章,我们使用的是 TIM3 的通道 4,所以我们需要修改 TIM3_CCR4 以实现脉宽控制 DS0的亮度。
下面介绍通过 HAL 库来配置该功能的步骤,相关的函数设置在库函数文件 stm32f4xx_tim.h 和stm32f4xx_tim.c 文件中。
-
开启
TIM3和GPIO时钟,配置PB1选择复用功能AF1(TIM3)输出
要使用TIM3,我们必须先开启TIM3的时钟。HAL库使能TIM3时钟和GPIO时钟方法是:__HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器 3 __HAL_RCC_GPIOB_CLK_ENABLE(); //开启 GPIOB 时钟配置
PB1为复用(AF1)输出,才可以实现TIM3_CH4的PWM经过PB1输出。接下来便是要配置PB1复用映射为TIM3的PWM输出引脚。关于IO口复用映射,在串口通信实验中有详细讲解,主要是通过函数HAL_GPIO_Init来实现的:GPIO_InitTypeDef GPIO_Initure; GPIO_Initure.Pin=GPIO_PIN_1; //PB1 GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速 GPIO_Initure.Alternate= GPIO_AF2_TIM3; //PB1 复用为 TIM3_CH4 HAL_GPIO_Init(GPIOB,&GPIO_Initure);在
IO口初始化配置中,我们只需要将成员变量Mode配置为复用推挽输出,同时成员变量Alternate配置为GPIO_AF2_TIM3,即可实现PB1映射为定时器 3 通道 4 的PWM输出引脚。
这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看STM32F4对应的数据手册,比如我们PWM实验,我们使用的是定时器 3 的通道 4,对应的引脚PB1可以从数据手册表中查看:

-
初始化
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_Init初始化定时器参数的时候,它内部调用的回调函数为HAL_TIM_Base_MspInit,这里大家注意区分。
使用HAL_TIM_PWM_Init初始化定时器时,回调函数为:HAL_TIM_PWM_MspInit,该函数声明为:void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim);一般情况下,上面步骤 1 的时钟使能和
IO口初始化映射都编写在回调函数内部。 -
设置
TIM3_CH4的PWM模式,输出比较极性,比较值等参数
接下来,我们要设置TIM3_CH4为PWM模式(默认是冻结的),因为我们的DS0是低电平亮,而我们希望当CCR4的值小的时候,DS0就暗,CCR4值大的时候,DS0就亮,所以我们要通过配置TIM3_CCMR2的相关位来控制TIM3_CH4的模式。
在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。这里我们使用的是定时器3的通道4,所以取值为TIM_CHANNEL_4即可。
例如我们要初始化定时器 3 的通道 4 为
PWM模式 1,输出极性为低,那么实例代码为:TIM_OC_InitTypeDef TIM3_CH4Handler; //定时器 3 通道 4 句柄 TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; //模式选择 PWM1 TIM3_CH4Handler.Pulse=arr/2; //设置比较值,此值用来确定占空比 TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低 HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4); - 第一个参数
-
使能
TIM3,使能TIM3的CH4输出
在完成以上设置了之后,我们需要使能TIM3并且使能TIM3_CH4输出。在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); -
修改 TIM3_CCR4 来控制占空比
在经过以上设置之后,PWM其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改比较值TIM3_CCR4则可以控制CH4的输出占空比,继而控制DS0的亮度。HAL库中并没有提供独立的修改占空比函数,这里编写这样一个函数如下://设置 TIM3 通道 4 的占空比 // compare:比较值 void TIM_SetTIM3Compare4(u32 compare) { TIM3->CCR4=compare; }实际上,因为调用函数
HAL_TIM_PWM_ConfigChanne进行PWM配置的时候可以设置比较值,所以我们也可以直接使用该函数来达到修改占空比的目的:void TIM_SetCompare4(TIM_TypeDef *TIMx,u32 compare) { TIM3_CH4Handler.Pulse=compare; HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4); }这种方法因为要调用
HAL_TIM_PWM_ConfigChannel函数对各种初始化参数进行重新设置,所以在使用中一定要注意,例如在实时系统中如果多个线程同时修改初始化结构体相关参数,可能导致结果混乱。
二、硬件设计
本实验用到的硬件资源有:
- 指示灯
DS0 - 定时器
TIM3
这两个我们前面都已经介绍了,因为 TIM3_CH4 可以通过 PB1 输出 PWM,而 DS0 就是直接接在PB1 上面的,所以电路上并没有任何变化。
三、软件设计
我们直接复制“定时器中断实验”的工程模板,将复制过来的模板文件夹重新命名为“8-PWM输出实验”。修改了timer.c 和 timer.h 的内容。
打开timer.c,代码如下:
#include "timer.h"
#include "led.h"
TIM_HandleTypeDef TIM3_Handler; //定时器3PWM句柄
TIM_OC_InitTypeDef TIM3_CH4Handler; //定时器3通道4句柄
//TIM3 PWM部分初始化
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{
//初始化TMI3
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_CH4的PWM模式,输出比较极性,比较值等参数
TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; //模式选择PWM1
TIM3_CH4Handler.Pulse=arr/2; //设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为50%
TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低
HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);//配置TIM3通道4
//使能 `TIM3`,使能 `TIM3` 的 `CH4` 输出
HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_4);//开启PWM通道4
}
//定时器底层驱动,时钟使能,引脚配置
//此函数会被HAL_TIM_PWM_Init()调用
//htim:定时器句柄
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器3
__HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB时钟
GPIO_Initure.Pin=GPIO_PIN_1; //PB1
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
GPIO_Initure.Alternate= GPIO_AF2_TIM3; //PB1复用为TIM3_CH4
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
}
//设置TIM通道4的占空比
//compare:比较值
void TIM_SetTIM3Compare4(u32 compare)
{
TIM3->CCR4=compare;
}
其头文件timer.h为
#ifndef __PWM_H
#define __PWM_H
#include "sys.h"
extern TIM_HandleTypeDef TIM3_Handler; //定时器3PWM句柄
extern TIM_OC_InitTypeDef TIM3_CH4Handler; //定时器3通道4句柄
void TIM3_PWM_Init(u16 arr,u16 psc);
void TIM_SetTIM3Compare4(u32 compare);
#endif
此部分代码包含三个函数,完全实现了前面的5 个配置步骤。
- 第一个函数
TIM3_PWM_Init实现的是步骤2-4,首先通过调用定时器HAL库函数HAL_TIM_PWM_Init初始化TIM3并设置TIM3的ARR和PSC等参数,其次通过调用函数HAL_TIM_PWM_ConfigChannel设置TIM3_CH4的PWM模式以及比较值等参数,最后通过调用函数HAL_TIM_PWM_Start来使能TIM3以及使能PWM通道TIM3_CH4输出。 - 第二个函数
HAL_TIM_PWM_MspInit是PWM的MSP初始化回调函数,该函数实现的是步骤 1,主要是使能相应时钟以及初始化定时器通道TIM3_CH4对应的IO口模式,同时设置复用映射关系。 - 第三个函数
TIM_SetTIM3Compare4是用户自定义的设置比较值函数,即步骤 5。
主函数main.c代码如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
int main(void)
{
u8 dir=1;
u16 led0pwmval=0;
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz
delay_init(180); //初始化延时函数
uart_init(115200); //初始化USART
LED_Init(); //初始化LED
TIM3_PWM_Init(500-1,90-1); //90M/90=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_SetTIM3Compare4(led0pwmval); //修改比较值,修改占空比
}
}
从死循环函数可以看出,我们控制 LED0_PWM_VAL 的值从 0 变到 300,然后又从 300 变到 0,如此循环,因此 DS0 的亮度也会跟着从暗变到亮,然后又从亮变到暗。至于这里的值,我们为什么取 300,是因为 PWM 的输出占空比达到这个值的时候,我们的 LED 亮度变化就不大了(虽然最大值可以设置到 499),因此设计过大的值在这里是没必要的。
四、实验现象
使用 USB 线将开发板和电脑连接成功后(电脑能识别开发板上 CH340 串口),把编译后产生的.hex 文件烧入到芯片内。可以看到:DS0 不停的由暗变到亮,然后又从亮变到暗。

五、STM32CubeMX 配置定时器 PWM 输出功能
使用 STM32CubeMX 配置 PWM 输出的配置步骤和配置定时器中断的配置步骤非常接近,步骤如下:
在TIM3 配置项中,配置 Channel4 的值为 PWM generation CH4,然后 Clock Source 为 Internal Clock。操作过程如下图所示:

进入 Configuration->TIM3 配置页,在弹出的界面中点击 Parameter Settings 选项卡,Counter Settings 配置栏下面的四个选项就是用来配置定时器的预分频系数,自动装载值,计数模式以及时钟分频因子。在界面的 PWM Generation Channel4 配置栏配置 PWM模式,比较值,极性等参数,操作方法如下图所示:

本章 PWM 输出实验,我们并没有使用到中断,所以我们不需要使能中断和配置 NVIC。经过上面的配置就可以生成工程源码。

















![[激光原理与应用-57]:激光器 - 光学 - 常见光学镜片介绍](https://img-blog.csdnimg.cn/img_convert/85b806b8338795b503438eb0e442a091.gif)

