PWM驱动LED呼吸灯与引脚重映射
PWM
-
什么是PWM?
PWM,全称脉冲宽度调制,是一种用数字信号来模拟模拟信号输出的技术。它通过调整固定频率的方波信号中高电平时间(脉冲宽度)的比例,来控制平均功率输出. -
PWM基本结构

-
PWM参数
①频率:指PWM信号在1秒内完成周期性变化的次数,周期(T)是频率(f)的倒数(T=1/f).
②占空比:一个脉冲周期内,高电平时间占总周期的比例,通常用百分比表示(0%-100%)。这是实现精确控制的关键,占空比是决定LED灯亮度的关键.由CCR和ARR一起决定.
③分辨率:占空比变化步距.
PWM参数如图:

-
PWM计算公式
CCR为输出/比较寄存器

-
工作原理与控制逻辑
你可以把它想象成一个高速开关的水龙头:
如果在一秒钟内,开关各占一半时间,那么流出的总水量就相当于一直开着半管水,PWM也是类似,通过极快地“开”和“关”,并调整“开”的时间比例(占空比),来控制负载上的平均电压或电流.
在单片机中,通常通过定时器实现硬件PWM。计数器从0开始向上计数,并与两个寄存器比较:
自动重装载寄存器:决定PWM的周期(频率).
捕获/比较寄存器:决定PWM的占空比。当计数器值小于比较值时输出一种电平,大于时输出另一种电平,从而直接控制脉冲宽度. -
为何PWM如此有用?
高效率:功率器件工作于“开”或“关”的开关状态,而非线性放大区,因此自身功耗很低.
精确控制:通过数字方式微调占空比,就能实现对模拟量(如电机速度、亮度)的平滑、精确控制.
抗干扰性强:PWM是数字信号,相比模拟电压信号,更不容易受噪声干扰. -
常见应用场景
LED调光/呼吸灯:通过改变占空比直接控制LED的亮度,而不是改变其工作电压.
电机调速:控制直流电机的平均电压,实现平滑调速,广泛应用于风扇、机器人、模型车等.
开关电源:通过调节占空比来稳定输出电压.
音频放大:用于D类音频放大器,效率远高于传统的线性放大器. -
详细PWM概念:PWM概念详解
PWM驱动LED呼吸灯
- 流程图1

- 接线图如图:

- 在Hardware文件夹下新建一个PWM.c文件和一个PWM.h文件
PWM.c文件代码:
#include "stm32f10x.h" // Device header
//初始化
void PWM_Init(void)
{
//开启TIM2的时钟,TIM1(高级时钟)对频率要求高,连接在APB2;TIM2、TIM3、TIM4(通用时钟)连接在APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
//使用复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//启用STM32自带的内部时钟,系统默认配置,可以不写
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//选择分频数,TIM_CKD_DIV1表示分频数为1,即不分频
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//选择计数器的计数模式,TIM_CounterMode_Up表示向上计数,即从0开始计数,到达预定值清零再次从零开始计数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
/*设置自动重装器(ARR),因为预分频器设置为将7200个脉冲分频为1个脉冲,那么设置自动重装器为1000-1,
即1000HZ,7200*1000正好等于内部定时器频率72MHZ,此时一个脉冲信号72MHZ全部输入完毕,
正好过去1秒,自动重装器计时器就会清零*/
TIM_TimeBaseInitStructure.TIM_Period = 100-1;
//设置预分频器(PSC),选择预分频数,7200-1表示对72MHZ进行7200次分频,即将7200个脉冲分频为1个脉冲
TIM_TimeBaseInitStructure.TIM_Prescaler = 720-1;
//重复计数器,高级计数器(TIM1)才有,本计数器TIM2用不到
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitTypeStructure;
/*
TIM_OCStructInit();将输出比较结构体函数赋一个默认值,因为在使用输出比较函数配置一个通用定时器(如TIM2),
TIM_OC1Init结构体函数中有很多参数用不到(这些用不到的参数在高级定时器配置中要用到),不赋一个默认值,会出
现一些莫名其妙的问题
*/
TIM_OCStructInit(&TIM_OCInitTypeStructure);
//输出比较模式
TIM_OCInitTypeStructure.TIM_OCMode = TIM_OCMode_PWM1;
//极性,TIM_OCPolarity_High表示有限电平为高电平
TIM_OCInitTypeStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitTypeStructure.TIM_OutputState = TIM_OutputState_Enable;
/*
设置CCR的值,要结合自动重装器(ARR)和预分频器(PSC)的值来使用,当ARR=100-1,PSC=720-1,TIM_Pulse=50,
可以输出一个频率是1000HZ,占空比为50%,分辨率为1%的PWM波形
*/
TIM_OCInitTypeStructure.TIM_Pulse = 0;
//初始化输出比较函数,由于使用的是PA0口,它对应的输出比较口是CH1(根据引脚定义图查得)
TIM_OC1Init(TIM2,&TIM_OCInitTypeStructure);
//启动定时器
TIM_Cmd(TIM2, ENABLE);
}
//独立设置CCR的值
void TIM_Compare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
需要注意的是:
①使用PWM要提前开启定时器时钟,这是因为PWM的波形是根据定时器的脉冲变化而来的.
②根据接线图,PA0口接有LED灯,所以还要配置GPIO参数,值得注意的是,GPIO的输出模式参数要配置为复用推挽输出模式GPIO_Mode_AF_PP,原因是:是将GPIO引脚的控制权从CPU移交给特定的定时器等外设,让硬件自动生成精确的PWM波形,从而解放CPU.普通推挽输出模式和复用推挽输出模式对比如图:
③使用的是STM32内部时钟,默认开启,可以不写其初始代码.
④时基单元是配置通用寄存器的配置模式,但是,PSC参数和ARR参数的具体值要结合CCR(输出/比较寄存器的值)和具体要求根据PWM相关公式来使用.
⑤初始化输出比较函数涉及结构体参数,在使用通用定时器的时候,并不是该结构体的所有参数都要配置,但是如果不配置的话,在将通用定时器转到高级定时器忘记配置往往会出现一些莫名其妙的问题,这就要使用结构体初始化函数TIM_OCStructInit(&TIM_OCInitTypeStructure);来先将该结构体参数赋一个默认值,避免上述弊端,以后只要结构体参数不全部用到的情况均可使用结构体初始化函数先赋一个默认值.
⑥初始化输出比较函数中有一个参数TIM_Pulse,这个是配置CCR值的,要结合PSC和ARR的相关公式使用,公式如图:
若想配置一个频率为1KHZ,占空比为50%,分辨率为1%的PWM波形,则根据公式算得ARR=99,CCR=50,PSC=719.
⑦为什么初始化输出/比较函数要使用TIM_OC1Init(TIM2,&TIM_OCInitTypeStructure);,这是根据引脚定义图得来的,如图
我将TIM2配置为PWM输出模式,那么TIM2_CH1(定时器2的通道1)就是这个PWM波的输出端口,这个端口查表得其对应的是PA0,所以LED灯接入的是PA0口,即使用CH1通道,对应的是OC1函数.
⑧记得最后配置完毕定时器启动它, TIM_Cmd(TIM2, ENABLE);
⑨在函数void TIM_Compare1(uint16_t Compare),我不再使用TIM_OCInitTypeStructure.TIM_Pulse = 0;来写死CCR,而是动态的传参来改变他,这是LED之所以"呼吸"的关键步骤,CCR值越低,LED小灯就越暗,反之,越亮.
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
PWM_Init();
while (1)
{
for(int i=0;i<100;i++)
{
TIM_Compare1(i);
Delay_ms(10);
}
for(int j =100;j>0;j--)
{
TIM_Compare1(j);
Delay_ms(10);
}
}
}
PWM驱动LED呼吸灯(引脚重映射)
- PWM.c文件代码:
#include "stm32f10x.h" // Device header
//初始化
void PWM_Init(void)
{
//开启TIM2的时钟,TIM1(高级时钟)对频率要求高,连接在APB2;TIM2、TIM3、TIM4(通用时钟)连接在APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
//引脚复用,将PA0口的引脚映射为PA15
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
//使用复用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//启用STM32自带的内部时钟,系统默认配置,可以不写
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//选择分频数,TIM_CKD_DIV1表示分频数为1,即不分频
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//选择计数器的计数模式,TIM_CounterMode_Up表示向上计数,即从0开始计数,到达预定值清零再次从零开始计数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
/*设置自动重装器(ARR),因为预分频器设置为将7200个脉冲分频为1个脉冲,那么设置自动重装器为1000-1,
即1000HZ,7200*1000正好等于内部定时器频率72MHZ,此时一个脉冲信号72MHZ全部输入完毕,
正好过去1秒,自动重装器计时器就会清零*/
TIM_TimeBaseInitStructure.TIM_Period = 100-1;
//设置预分频器(PSC),选择预分频数,7200-1表示对72MHZ进行7200次分频,即将7200个脉冲分频为1个脉冲
TIM_TimeBaseInitStructure.TIM_Prescaler = 720-1;
//重复计数器,高级计数器(TIM1)才有,本计数器TIM2用不到
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitTypeStructure;
/*
TIM_OCStructInit();将输出比较结构体函数赋一个默认值,因为在使用输出比较函数配置一个通用定时器(如TIM2),
TIM_OC1Init结构体函数中有很多参数用不到(这些用不到的参数在高级定时器配置中要用到),不赋一个默认值,会出
现一些莫名其妙的问题
*/
TIM_OCStructInit(&TIM_OCInitTypeStructure);
//输出比较模式
TIM_OCInitTypeStructure.TIM_OCMode = TIM_OCMode_PWM1;
//极性,TIM_OCPolarity_High表示有限电平为高电平
TIM_OCInitTypeStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitTypeStructure.TIM_OutputState = TIM_OutputState_Enable;
/*
设置CCR的值,要结合自动重装器(ARR)和预分频器(PSC)的值来使用,当ARR=100-1,PSC=720-1,TIM_Pulse=50,
可以输出一个频率是1000HZ,占空比为50%,分辨率为1%的PWM波形
*/
TIM_OCInitTypeStructure.TIM_Pulse = 0;
//初始化输出比较函数,由于使用的是PA0口,它对应的输出比较口是CH1(根据引脚定义图查得)
TIM_OC1Init(TIM2,&TIM_OCInitTypeStructure);
//启动定时器
TIM_Cmd(TIM2, ENABLE);
}
//独立设置CCR的值
void TIM_Compare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
值得注意的是:
①与没有引脚重映射的PWM驱动LED呼吸灯项目不同的是,这个项目加上了
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
三句代码,需要实现引脚重映射功能首先要打开AFIO时钟, 然后用重映射函数GPIO_PinRemapConfig();(参数GPIO_PartialRemap1_TIM2设置重映射函数为部分重映射,也可使用全部重映射参数GPIO_FullRemap_TIM2来达到目的)来配置需要映射的引脚.
②因为想要重映射的引脚PA15正好是调试端口,所以应该把其变为普通引脚,用函数GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);即可实现
③若想要重映射的端口不是调试端口,那么使用前两句即可:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
④记得将配置GPIO的引脚参数改为PA15引脚:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
此时将LED小灯插在PA15引脚,LED灯呼吸闪烁.



2508

被折叠的 条评论
为什么被折叠?



