PWM输出原理与配置
参考资料
STM32Fx开发板:
《STM32Fx开发指南-HAL库版本》-第13章 PWM输出实验
STM32Fxx官方资料:
《STM32Fxx中文参考手册》-第23章 通用定时器
笔记基于正点原子官方视频
视频连接https://www.bilibili.com/video/BV1Wx411d7wT?p=71&spm_id_from=333.1007.top_right_bar_window_history.content.click
如有侵权,联系删除
一、通用定时器PWM概述
1.STM32 PWM工作过程
ARR的值决定着PWM波的周期,CRRx决定着PWM的占空比
当曲线大于CCRx时输出高电平,小于CCRx输出低电平,即形成PWM波,调节CCRx的值
可以实现占空比的调节
2.STM32 PWM工作过程(通道1为例)
CCR1:捕获比较(值)寄存器(x=1,2,3,4):设置比较值。
CCMR1: OC1M[2:0]位:
对于PWM方式下,用于设置PWM模式1【110】或者PWM模式2【111】
CCER:CC1P位:输入/捕获1输出极性。0:高电平有效,1:低电平有效。
CCER:CC1E位:输入/捕获1输出使能。0:关闭,1:打开。
3.PWM模式1 & PWM模式2
寄存器TIMx_CCMR1的OC1M[2:0]位来分析:
4.STM32 PWM 总结
PWM模式
脉冲宽度调制模式可以生成一个信号,该信号频率由TIMx_ARR寄存器值决定,其占空比则由TIMx_CCRx寄存器值决定。
通过向TIMx_CCMRx寄存器中的OCxM位写入110 (PWM模式1)或111(PWM模式2),可以独立选择各通道(每个OCx输出对应一个PWM)的PWM模式。必须通过将TIMx_CCMRx寄存器中的OCxPE位置1使能相应预装载寄存器,最后通过将TIMx_CR1寄存器中的ARPE位置1使能自动重载预装载寄存器。
STM32 定时器输出通道引脚
在芯片的数据手册中查看
二、常用寄存器和库函数配置
1.通用定时器基本函数和定义所在文件
stm32fxxx_hal_tim.c
位置:工程文件 - HALLIB - stm32fxxx_hal_tim.c
stm32fxxx_hal_tim.h
位置:工程文件 - HALLIB - stm32fxxx_hal_tim.c - stm32fxxx_hal_tim.h
2.定时器PWM时基参数初始化函数
函数体:HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim)
位置:工程文件 - HALLIB - stm32fxxx_hal_tim.c
该函数和HAL_TIM_Base_Init函数作用一样,不同的是引导调用不同的回调函数
typedef struct
{
uint32_t Prescaler; //预分频系数
uint32_t CounterMode; //计数模式:向上/下
uint32_t Period; //自动装载值
uint32_t ClockDivision; //时钟分频因子:定时器时钟与数字滤波器分频比
uint32_t RepetitionCounter; //重复计数次数:高级定时器使用
} TIM_Base_InitTypeDef;
3.定时器PWM时基参数初始化回调函数:
函数体:void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim);
位置:工程文件 - HALLIB - stm32fxxx_hal_tim.c
主要用来编写定时器时钟使能等。
__HAL_RCC_TIM3_CLK_ENABLE();//定时器3时钟使能
4.PWM输出比较通道参数配置函数:
函数体:HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef* sConfig, uint32_t channel);
位置:工程文件 - HALLIB - stm32fxxx_hal_tim.c
typedef struct
{
uint32_t OCMode; //模式PMW1 OR PWM2
uint32_t Pulse; //设置比较值
uint32_t OCPolarity; //输出比较极性
uint32_t OCNPolarity;
uint32_t OCFastMode;
uint32_t OCIdleState;
uint32_t OCNIdleState;
} TIM_OC_InitTypeDef;
入门阶段配置前三个即可
5.使能定时器和PWM输出比较通道:
函数体:HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
函数体:HAL_StatusTypeDef HAL_TIM_PWM_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
此函数同时开启了定时器PWM通道的输出比较中断
6.PWM输出配置步骤
①使能定时器时钟和通道IO口时钟。
②配置IO口复用映射:
HAL_GPIO_Init();
③初始化PWM时基参数:
HAL_TIM_PWM_Init();
④初始化PWM通道参数:
HAL_TIM_PWM_ConfigChannel();
⑤使能定时器PWM
HAL_TIM_PWM_Start();
三、PWM输出实验
要求:
使用定时器PWM功能,输出占空比可变的PWM波,用来驱动LED灯,从而达到LED亮度由暗变亮,又从亮变暗,如此循环。
以前文按键输入实验为模板,在此基础上进行更改
1.清理main.c函数,引入相关库
如下图:
添加定时器下相关库文件
2.初始化PWM时基参数:
HAL_TIM_PWM_Init();
1)搭建定时器基本框架
2)调用HAL_TIM_PWM_Init();
位置:工程文件 - HALLIB - stm32f4xx_hal_tim.c
可见HAL_TIM_PWM_Init();中定义的变量时一个TIM_HandleTypeDef类型的结构体指针,这里我们需要在main.c函数里定义一个TIM3_PWM_Handler
TIM_HandleTypeDef TIM3_PWM_Handler;
3)对结构体指针的成员变量进行配置
下面对结构体指针的成员变量进行配置
Instance配置过程同上一小节
TIM3_PWM_Handler.Init配置
主要配置三个变量,跟上一小节类似
Prescaler 预分频系数(PSC)
CounterMode 计数模式
Period 自动装载值(ARR)
TIM3_Handler.Init.Prescaler = 9000 - 1;
TIM3_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM3_Handler.Init.Period = 5000 - 1;
即Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk
Tout(溢出时间)=(5000)(9000)/90x10^6=0.5s
这里PWM波的周期即是 1/Tout(溢出时间)
具体设置如下:
TIM3_PWM_Handler.Init.Prescaler = 90 - 1;
TIM3_PWM_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM3_PWM_Handler.Init.Period = 500 - 1;
最终代码如下
TIM_HandleTypeDef TIM3_PWM_Handler;
void TIM3_PWM_Init (void)
{
TIM3_PWM_Handler.Instance = TIM3; //初始化TIM3
TIM3_PWM_Handler.Init.Prescaler = 90 - 1; //预分频系数(PSC)
TIM3_PWM_Handler.Init.CounterMode = TIM_COUNTERMODE_UP; //计数模式
TIM3_PWM_Handler.Init.Period = 500 - 1; //自动装载值(ARR)
HAL_TIM_PWM_Init(&TIM3_PWM_Handler);
}
3.初始化PWM通道参数:
HAL_TIM_PWM_ConfigChannel();
位置:工程文件 - HALLIB - stm32f4xx_hal_tim.c
HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef*sConfig, uint32_t Channel)
可见函数里面有三个变量,根据形式进行配置
第一个参数是TIM的结构体指针,前面已经定义好了,这里直接用就行:&TIM3_PWM_Handler
第二个是TIM_OC的结构体指针,这里我们要自己定义一个
TIM_OC_InitTypeDef TIM3_OC_Init;
TIM3_OC_Init.OCMode //模式PMW1 OR PWM2
TIM3_OC_Init.Pulse //设置比较值
TIM3_OC_Init.OCPolarity //输出比较极性
查看OCMode定义如下,这里选择TIM_OCMODE_PWM1
查看OCPolarity定义如下,这里选择TIM_OCPOLARITY_LOW
下面设置Pulse,即设计比较值,前文提到过,ARR的值决定着PWM波的周期,CRRx决定着PWM的占空比,这里ARR是Period 自动装载值,CRRx是Pulse设置比较值
我们设置占空比为50%,前文ARR设置的是500,这里CRRx即要取250。
配置好代码如下:
TIM3_OC_Init.OCMode = TIM_OCMODE_PWM1; //模式PMW1 OR PWM2
TIM3_OC_Init.Pulse = 250; //设置比较值,和ARR相比即是占空比
TIM3_OC_Init.OCPolarity = TIM_OCPOLARITY_LOW; //输出比较极性
第三个是定时器通道,查看定义来进行设置,这里选择通道4
PWM通道参数设置完毕代码如下
TIM_OC_InitTypeDef TIM3_OC_Init;
void TIM3_PWM_Init (void)
{
TIM3_OC_Init.OCMode = TIM_OCMODE_PWM1; //模式PMW1 OR PWM2
TIM3_OC_Init.Pulse = 250; //设置比较值,和ARR相比即是占空比
TIM3_OC_Init.OCPolarity = TIM_OCPOLARITY_LOW; //输出比较极性
HAL_TIM_PWM_ConfigChannel(&TIM3_PWM_Handler,&TIM3_OC_Init,TIM_CHANNEL_4); //初始化PWM通道参数
}
4.使能定时器时钟和通道IO口时钟、配置IO口复用映射
这一部分跟上一小节一样,稍微修改成自己所需的口即可,下面直接看代码
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim -> Instance == TIM3) //判断定时器3是否上报中断
{
GPIO_InitTypeDef GPIO_Initure; //初始化GPIO口
__HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器TIM3时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能IO口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_FAST; //高速
GPIO_Initure.Alternate=GPIO_AF2_TIM3; //复用为TIM3
HAL_GPIO_Init(GPIOB,&GPIO_Initure); //初始化PB1
}
}
5.使能定时器PWM
HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
位置:工程文件 - HALLIB - stm32f4xx_hal_tim.c - stm32f4xx_hal_tim.h
查看uint32_t Channel定义,这里选择TIM3的TIM_CHANNEL_4
代码如下:
HAL_TIM_PWM_Start(&TIM3_PWM_Handler,TIM_CHANNEL_4); //选择TIM3的TIM_CHANNEL_4,定时器3的通道4
6.编写中断服务函数
我们定义一个可以返回变量compare的新的函数体,让CCR4指向compare,这样改变compare的值即可改变CCR4的值,即改变占空比
代码如下:
void TIM_SetTIM3Compare4(u32 compare)
{
TIM3->CCR4 = compare; //将compare指向CCR4,操控compare的值来控制占空比
}
7.编写main函数
首先在main中初始化定时器
TIM3_PWM_Init(); //初始化TIM3定时器
然后在while循环中按照要求编写可以改变占空比的代码
代码如下:
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
KEY_Init(); //初始化按键
TIM3_PWM_Init(); //初始化TIM3定时器
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); //调用中断服务函数,并将led0pwmval的值返回过去
}
}
至此,所有代码编写完毕,main.c代码如下
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
TIM_HandleTypeDef TIM3_PWM_Handler;
TIM_OC_InitTypeDef TIM3_OC_Init;
void TIM3_PWM_Init (void)
{
TIM3_PWM_Handler.Instance = TIM3; //初始化TIM3
TIM3_PWM_Handler.Init.Prescaler = 90 - 1; //预分频系数(PSC)
TIM3_PWM_Handler.Init.CounterMode = TIM_COUNTERMODE_UP; //计数模式
TIM3_PWM_Handler.Init.Period = 500 - 1; //自动装载值(ARR)
HAL_TIM_PWM_Init(&TIM3_PWM_Handler);
TIM3_OC_Init.OCMode = TIM_OCMODE_PWM1; //模式PMW1 OR PWM2
TIM3_OC_Init.Pulse = 250; //设置比较值,和ARR相比即是占空比
TIM3_OC_Init.OCPolarity = TIM_OCPOLARITY_LOW; //输出比较极性
HAL_TIM_PWM_ConfigChannel(&TIM3_PWM_Handler,&TIM3_OC_Init,TIM_CHANNEL_4); //初始化PWM通道参数
HAL_TIM_PWM_Start(&TIM3_PWM_Handler,TIM_CHANNEL_4); //选择TIM3的TIM_CHANNEL_4,定时器3的通道4
}
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM3) //判断定时器3是否上报中断
{
GPIO_InitTypeDef GPIO_Initure; //初始化GPIO口
__HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器TIM3时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能IO口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_FAST; //高速
GPIO_Initure.Alternate=GPIO_AF2_TIM3; //复用为TIM3
HAL_GPIO_Init(GPIOB,&GPIO_Initure); //初始化PB1
}
}
void TIM_SetTIM3Compare4(u32 compare)
{
TIM3->CCR4 = compare; //将compare指向CCR4,操控compare的值来控制占空比
}
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
KEY_Init(); //初始化按键
TIM3_PWM_Init(); //初始化TIM3定时器
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); //调用中断服务函数,并将led0pwmval的值返回过去
}
}
四、在STM32CubeMX中配置PWM
详细可参考:H:\2技术书籍\STM32F429开发指南 - STM32F429开发指南-HAL库版本_V1.1 - 13.5 STM32CubeMX 配置定时器 PWM 输出功能