PWM是什么?
在STM32微控制器中,PWM代表脉冲宽度调制(Pulse Width Modulation)。PWM是一种用于控制电子设备的技术,通过调整信号的脉冲宽度和周期,可以模拟出不同的电压或功率级别。
在STM32中,PWM功能常用于控制电机速度、调节LED亮度、产生音频信号等应用。通过调整PWM的占空比(高电平时间占总周期的比例),可以控制输出信号的平均电压或功率。例如,如果PWM信号的占空比为50%,即高电平时间等于总周期的一半,那么输出信号的平均电压或功率也将为输入电压或功率的一半。
STM32微控制器提供了多个PWM通道,每个通道可以配置为不同的输出引脚,并具有灵活的配置选项,例如频率、占空比、极性等。开发者可以使用STM32的PWM功能来实现精确的电子设备控制。
定义
PWM是脉冲宽度调制(Pulse Width Modulation)的缩写,它是一种调制技术,用于控制模拟信号的平均值。通过调整脉冲的宽度和周期,PWM可以模拟出不同的电压或功率级别。
在PWM中,信号由一系列固定周期的脉冲组成。脉冲的宽度表示信号的高电平时间,而周期表示脉冲的重复时间。通过改变脉冲的宽度与周期之间的比例,可以控制信号的平均电压或功率。
例如,如果脉冲的宽度占周期的一半,即50%的占空比,那么输出信号的平均电压或功率也将为输入电压或功率的一半。通过改变占空比,可以实现对输出信号的精确控制。占空比:高电平占整个电平周期的持续时长
PWM广泛应用于各种领域,包括电机控制、LED亮度调节、音频信号生成等。它是一种高效、精确的控制技术,常用于模拟信号的数字化处理和电子设备的调节与控制。
若要实现PWM输出,我们需要用到定时器的输出比较功能。
我们在查看《stm32f4xx中文参考手册》时,发现这4个通道的功能似乎有所重复:
实际上这两者是有区别的,输出比较模式和PWM模式都可以用来输出PWM波,在功能上两者有相同之处,对于一个定时器这两种方式都可以做到四路输出PWM,每一路PWM占空比都可调。
也有不同之处,输出比较模式可以方便的调节每一路PWM波的频率,可以输出四路频率不同,占空比不同的PWM。但是PWM模式如果想要调节PWM波的频率,那么就只能重新设置预分频系数或者自动重装载寄存器ARR,并且输出的四路PWM频率必定一致。
PWM模式是输出比较模式的子集。
代码参数
(1) TIM_OCMode:比较输出模式选择,总共有八种,常用的为PWM1/PWM2。它设定CCMRx 寄存器OCxM[2:0] 位的值。
(2) TIM_OutputState:比较输出使能,决定最终的输出比较信号OCx 是否通过外部引脚输出。它设定TIMx_CCER 寄存器CCxE/CCxNE 位的值。
(3) TIM_OutputNState: 比较互补输出使能,决定OCx 的互补信号OCxN 是否通过外部引脚输出。它设定CCER 寄存器CCxNE 位的值。
(4) TIM_Pulse:比较输出脉冲宽度,实际设定比较寄存器CCR 的值,决定脉冲宽度。可设置范围为0 至65535。
(5) TIM_OCPolarity:比较输出极性,可选OCx 为高电平有效或低电平有效。它决定着定时器通道有效电平。它设定CCER 寄存器的CCxP 位的值。
(6) TIM_OCNPolarity:比较互补输出极性,可选OCxN 为高电平有效或低电平有效。它设定TIMx_CCER 寄存器的CCxNP 位的值。
(7) TIM_OCIdleState:空闲状态时通道输出电平设置,可选输出1 或输出0,即在空闲状态(BDTR_MOE 位为0) 时,经过死区时间后定时器通道输出高电平或低电平。它设定CR2 寄存器的OISx 位的值。
(8) TIM_OCNIdleState:空闲状态时互补通道输出电平设置,可选输出1 或输出0,即在空闲状态(BDTR_MOE 位为0) 时,经过死区时间后定时器互补通道输出高电平或低电平,设定值必须与TIM_OCIdleState 相反。它设定是CR2 寄存器的OISxN 位的值。
分频系数:84-1
arr值:2000-1
比较值:480-1
可知:
输送给定时器4的时钟频率为:APB1时钟频率2/84=482/84=1Mhz
PWM周期为:在1Mhz的时钟频率下。数2000个数 1/1Mhz*2000=2ms
占空比为:高电平时间/PWM周期=数480个数/数2000个数=480/2000=24%
pwm频率为:1/pwm周期 = 1/2ms=500Hz
工作原理
PWM的工作原理基于对脉冲的宽度和周期进行调制。下面是PWM的基本工作原理:
设定目标数值:首先,确定需要控制的目标数值,例如调节电机的速度或LED的亮度。这个目标数值通常以一个百分比或占空比的形式表示。
确定频率:选择PWM信号的频率,即脉冲的周期。频率决定了脉冲的重复速率,通常以赫兹(Hz)表示。常见的频率范围是几百赫兹到几十千赫兹。
计算占空比:根据目标数值和所选频率,计算所需的占空比。占空比表示高电平时间占周期的比例。例如,如果目标是50%的亮度或速度,则占空比为50%。
生成PWM信号:使用计时器和计数器来生成PWM信号。计时器根据所选频率生成一个固定周期的计时事件,并从0开始计数。计数器在每个计时事件中递增,当计数值小于占空比所对应的计数阈值时,输出为高电平;否则,输出为低电平。
输出控制:根据计数器的值,控制输出引脚的电平状态。在计数值小于阈值时,输出为高电平;在计数值大于等于阈值时,输出为低电平。这样就形成了一系列固定周期、宽度可变的脉冲信号。
通过调整占空比,可以控制输出信号的平均电压或功率。占空比越高,输出信号的平均电压或功率就越高,而占空比越低,输出信号的平均电压或功率就越低。
使用PWM,可以实现精确的控制,例如精确调节电机的速度或改变LED的亮度级别。PWM技术的优点包括高效率、精度高以及对输出设备影响小等。
计数器寄存器 (TIMx_CNT) 自动装载寄存器 (TIMx_ARR) 捕获/比较寄存器(TIMx_CCRx)
向上计数模式:
输出过程: 当0-t1这段时间,计数器寄存器的CNT的值是小于CCR,输出高电平。 当t1-t2这段时间,计数器寄存器的CNT的值是大于CCR且小于ARR的,输出低电平。 当CNT的值达到ARR里的值时,产生溢出事件,自动清零再次从0开始向上计数。
应用
通过PWM实现呼吸灯的效果
代码
#include "pwm.h"
void tim14_pwm_led0(uint16_t psc, uint32_t arr)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//通电还是断电
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE); //时钟使能
GPIO_PinAFConfig(GPIOF, GPIO_PinSource9, GPIO_AF_TIM14); //复用为定时器14
//开始配置F端口下的9号跟10号引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; //led0 led1
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF; //复用模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP; //上拉模式
GPIO_InitStructure.GPIO_Speed=GPIO_High_Speed; //引脚响应速度
GPIO_Init(GPIOF, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseStructure.TIM_Prescaler = psc-1; //PSC值
TIM_TimeBaseStructure.TIM_Period = arr-1; //ARR值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM14, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //pwm模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性低
TIM_OC1Init(TIM14, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable); //使能TIM14在OC1上的CCR预装载寄存器
TIM_ARRPreloadConfig(TIM14, ENABLE); //定时器14 ARR寄存器
TIM_Cmd(TIM14, ENABLE); //定时器使能
}
//重写TIM_SetCompare1 - 固定定时器TIM14 - 方便传参只传一个
void PWM_SetCompare1(uint32_t Compare)
{
TIM_SetCompare1(TIM14,Compare);
}
#include "stm32f4xx.h"
#include "usart.h"
#include "led.h"
#include "stdio.h"
#include "timer.h"
#include "pwm.h"
#include "delay.h"
int main(void)
{
SysTick_Init();//必须要初始化滴答定时器的函数,这样延时函数才能正常用
//f = 84Mhz/ (83+1) / (999+1) = 1Khz
//pwm频率 = 时钟频率/(分频值+1)/(自动重装载值+1)
tim14_pwm_led0(84, 1000);
uint32_t cmp;
u8 flag = 1;
u8 direction = 1;
while (1)
{
if (direction)
{
for (cmp=0; cmp < 800; cmp += 1) // 渐亮
{
PWM_SetCompare1(cmp);
delay_ms(1); // 根据期望的渐变速度调整延时
}
direction = 0; // 切换方向
}
else
{
for (cmp=800; cmp > 0; cmp -= 1) // 渐暗
{
PWM_SetCompare1(cmp);
delay_ms(1); // 根据期望的渐变速度调整延时
}
direction = 1; // 切换方向
}
}
}
通过PWM控制蜂鸣器唱歌
代码
#include "stm32f4xx.h"
#include "uart.h"
#include "led.h"
#include "stdio.h"
#include "timer.h"
#include "pwm.h"
#include "delay.h"
#define L1 262-1//低调 do 的频率
#define L2 294-1//低调 re 的频率
#define L3 330-1//低调 mi 的频率
#define L4 350-1//低调 fa 的频率
#define L5 392-1//低调 sol 的频率
#define L6 440-1//低调 la 的频率
#define L7 494-1//低调 si 的频率
#define M1 524-1//中调 do 的频率
#define M2 588-1//中调 re 的频率
#define M3 660-1//中调 mi 的频率
#define M4 700-1//中调 fa 的频率
#define M5 784-1//中调 sol 的频率
#define M6 880-1//中调 la 的频率
#define M7 988-1//中调 si 的频率
#define H1 1048-1//高调 do 的频率
#define H2 1176-1//高调 re 的频率
#define H3 1320-1//高调 mi 的频率
#define H4 1480-1//高调 fa 的频率
#define H5 1640-1//高调 sol 的频率
#define H6 1760-1//高调 la 的频率
#define H7 1976-1//高调 si 的频率
#define S 0//不发音
int main(void)
{
SysTick_Init(); //系统延时
//f = 84Mhz/ (83+1) / (999+1) = 1Khz
//pwm频率 = 时钟频率/(分频值+1)/(自动重装载值+1)
tim13_pwm_led0(84, 3816);
//PSC值 84MHz/84 = 1M = 1000000
//ARR值 1000000/216低音=3816最大 计数值范围0~3816
const uint16_t tone_low[7] = {L1,L2,L3,L4,L5,L6,L7};
const uint16_t tone_mid[7] = {M1,M2,M3,M4,M5,M6,M7};
const uint16_t tone_high[7] = {H1,H2,H3,H4,H5,H6,H7};
int wind_rise1[]={H1,M7,H1,H2,M7,H1,H1,S, //爱你不跪的模样
H1,H2,H3,H2,H3,H2,H3,H3,H2,H3,H5,H3,S, //爱你对峙过绝望不肯哭一场
M6,M7,H1,H2,M7,H1,H1,H1,M7,H1,H2,M7,H1,H1,S, //爱你破烂的衣裳却敢堵命运的枪
H1,H2,H3,H2,H3,H2,H3,H3,H2,H3,H5,H3,S, //爱你和我那么像缺口一样
H5,H3, //去吗
H5,H3,S, //配吗
H5,H3,H5,H6,H3,H5,S, //这褴褛的披风
H5,H3,//战吗
H5,H3,S, //战啊
H5,H3,H5,H6,H3,H5,H5,H5,H3,H2,H2,H2,H1,H3,H3,H2,H2,H2,H1,H1,M6,M6,S,S, //以最卑微的梦致那黑夜中的呜咽与怒吼
H5,H5,H3,H2,H2,H2,H1,H3,H3,H2,H2,H2,H1,H1,M6,M6,S,S, //谁说站在光里才算英雄
};
uint8_t i;
int length1 = sizeof(wind_rise1)/sizeof(wind_rise1[0]);
while(1)
{
for(i=0;i<(length1/2);i++)
{
set_beep(wind_rise1[i]);
delay_ms(250);
}
}
}
#include "pwm.h"
void tim13_pwm_led0(uint16_t psc, uint32_t arr)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//通电还是断电
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM13, ENABLE); //时钟使能
GPIO_PinAFConfig(GPIOF, GPIO_PinSource8, GPIO_AF_TIM13); //复用为定时器14
//开始配置F端口下的9号跟10号引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8; //led0 led1
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF; //复用模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽输出
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP; //上拉模式
GPIO_InitStructure.GPIO_Speed=GPIO_High_Speed; //引脚响应速度
GPIO_Init(GPIOF, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseStructure.TIM_Prescaler = psc-1; // PSC值 84MHz/84 = 1M = 1000000
TIM_TimeBaseStructure.TIM_Period = arr-1; //ARR值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM13, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //pwm模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//输出使能
TIM_OCInitStructure.TIM_Pulse = 3816 / 2;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性低
TIM_OC1Init(TIM13, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM13, TIM_OCPreload_Enable); //使能TIM14在OC1上的CCR预装载寄存器
TIM_ARRPreloadConfig(TIM13, ENABLE); //定时器14 ARR寄存器
TIM_Cmd(TIM13, ENABLE); //定时器使能
}
//重写TIM_SetCompare1 - 固定定时器TIM14 - 方便传参只传一个
void PWM_SetCompare1(uint16_t loud) //period计数值,pulse比较值 占空比 - 计数值的一半
{
uint16_t period = 1000000 / loud;
TIM_SetAutoreload(TIM13,period);
TIM_SetCompare1(TIM13,period/2);
}
void set_beep(uint16_t f)
{
if(f==0){ //如果f=0则不发出声音
TIM_SetAutoreload(TIM13,1);
TIM_SetCompare1(TIM13,0);
}else{ //发出指定频率的声音
TIM_SetAutoreload(TIM13,(1000000/f));
TIM_SetCompare1(TIM13,(1000000/f)/15);//这个是为了改变蜂鸣器的音量,音量小一点音调的变化更清晰,听起来更清楚
}
}