PWM 脉冲宽度调制原理
PWM 即脉冲宽度调制,是一种通过调节脉冲信号的占空比来控制输出平均功率的技术。它在单片机应用中非常广泛,可用于 LED 调光、电机调速等场景。以下是其相关原理知识:
核心参数:
- 占空比:是指一个完整周期内,高电平持续时间与整个周期时长的百分比。占空比直接决定了输出的等效效果,如对 LED 而言,占空比越高,等效亮度越强。

- 频率 :指每秒内 PWM 信号完成的周期数,单位为 Hz,与周期 T 互为倒数,即
f = 1/T。不同应用场景对频率要求不同,如直流电机调速,频率通常选择 1kHz-20kHz,过低会导致电机振动,过高会增加驱动芯片开关损耗。 - 分辨率 :反映了占空比可调节的最小精细程度,通常由嵌入式终端的定时器位数决定。假设定时器为 N 位,则 PWM 的最大可调节级数为 2^N 级,分辨率公式为 "分辨率 = 1/(2^N - 1)×100%"。--了解即可
以 STM32F10x 系列为例,手动编写 PWM 核心代码的部分。
时钟配置,GPIO 配置::
-
首先要使能定时器和相关 GPIO 的时钟。例如,对于 TIM3 和其使用的 PA6(TIM3_CH1)引脚
cs// 使能TIM3时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); -
将用于 PWM 输出的 GPIO 引脚配置为复用推挽输出模式。
csGPIO_InitTypeDef GPIO_InitStructure; // 配置PA6 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);
定时器基本配置:
补充相关概念,方便大家理解。
预分频器
- 概念:预分频器就像是一个 "减速齿轮",它能降低定时器的计数频率。STM32 的定时器时钟频率通常比较高,比如系统时钟频率可能是 72MHz 。如果直接用这么高的频率计数,定时器计数速度太快,不利于实现我们想要的定时时长。
- 作用:预分频器通过设置一个分频系数,让定时器实际计数的频率降低。例如,设置预分频器的值为 71 ,那么定时器的计数频率就变为系统时钟频率除以(71 + 1)。如果系统时钟是 72MHz ,经过预分频后,定时器的计数频率就是 72MHz / 72 = 1MHz 。这样定时器每计数一次的时间间隔就变长了,方便我们根据实际需求进行定时控制。
计数模式
- 向上计数模式:这是最常见的一种计数模式。在这种模式下,定时器从 0 开始,按照设定的计数频率,每次增加 1 进行计数,一直计到设定的周期值(比如 1000)。当计数值达到周期值时,会产生一个溢出事件,定时器会重新从 0 开始计数。就像一个跑步比赛,从起点 0 出发,跑到 1000 米处,然后又回到起点重新跑。
- 向下计数模式:与向上计数相反,它从设定的周期值开始,每次减 1 进行计数,一直减到 0。当计数值减到 0 时,产生溢出事件,然后又从周期值重新开始向下计数。
- 向上 / 向下计数模式(中央对齐模式):这种模式下,定时器先从 0 开始向上计数到周期值,然后再从周期值开始向下计数到 0,如此循环。这种模式常用于一些需要精确控制占空比且对波形对称性有要求的应用场景,比如一些音频信号处理等。
周期
-
概念:周期决定了定时器完成一次完整计数循环所需要的时间。在向上计数模式下,周期值就是定时器从 0 计数到的最大值。例如,我们把周期设为 999 ,定时器在向上计数模式下,就会从 0 开始,每次加 1,一直计到 999,这就是一个完整的计数周期。
-
与频率的关系:周期和频率是相互关联的。假设经过预分频后定时器的计数频率是 1MHz ,也就是 1 秒内计数 1000000 次。如果周期设为 999 ,那么完成一次完整计数循环(一个周期)所需要的时间就是 1000 / 1000000 秒 = 0.001 秒,对应的频率就是 1 / 0.001 秒 = 1kHz 。在 PWM 应用中,周期就决定了 PWM 信号的频率。
-
设置定时器的预分频器、计数模式、周期等参数。
csTIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 定时器预分频器设为71,这样定时器时钟频率为72MHz / (71 + 1) = 1MHz TIM_TimeBaseStructure.TIM_Period = 999; // 周期设为999,PWM频率为1kHz (1MHz / 1000) TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);补充知识:
TIM_ClockDivision (CKD):定时器时钟分频位,主要用于配置定时器的采样时钟(数字滤波器时钟)与定时器内核时钟(Tck_int)的分频关系,取值范围和对应含义如下:
*
| 取值 | 宏定义(推荐使用) | 实际分频效果 | 说明 |
|---|---|---|---|
| 0 | TIM_CKD_DIV1 | 不分频 | 采样时钟 = 定时器内核时钟(默认 / 常用) |
| 1 | TIM_CKD_DIV2 | 2 分频 | 采样时钟 = 定时器内核时钟 / 2 |
| 2 | TIM_CKD_DIV4 | 4 分频 | 采样时钟 = 定时器内核时钟 / 4 |
PWM 模式配置 :
选择 PWM 模式,并设置输出比较值(决定占空比)、极性
cs
TIM_OCInitTypeDef TIM_OCInitStructure;
// 选择PWM模式1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 初始占空比设为0,即输出比较值为0
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//当输出比较匹配时,对应 GPIO 口输出高电平
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
启动定时器:
- 完成上述配置后,启动定时器以开始输出 PWM 信号。
cs
TIM_Cmd(TIM3, ENABLE);
在程序运行过程中,可以通过修改TIM_OCInitStructure.TIM_Pulse的值来改变 PWM 的占空比。例如,若要将占空比设置为 50%,假设TIM_TimeBaseStructure.TIM_Period为 999 ,则可以这样做:
cs
TIM_SetCompare1(TIM3, 500);
这里TIM_SetCompare1函数用于设置 TIM3 通道 1 的比较值,从而改变占空比。整个编程核心部分就是通过这些步骤,配置好定时器和 GPIO ,让其输出符合要求的 PWM 信号。
cs
#include "stm32f10x.h"
// 函数声明
void TIM3_PWM_Init(void);
void GPIO_Configuration(void);
int main(void) {
// 初始化TIM3用于PWM输出
TIM3_PWM_Init();
// 配置GPIO
GPIO_Configuration();
while (1) {
// 这里可以通过改变占空比来无级调节LED亮度
// 假设通过一个变量来控制占空比,范围0 - 999
static int dutyCycle = 0;
// 逐渐增加占空比
TIM_SetCompare1(TIM3, dutyCycle);
for (volatile int i = 0; i < 1000000; i++); // 简单延时
dutyCycle++;
if (dutyCycle > 999) {
dutyCycle = 0;
}
}
}
void TIM3_PWM_Init(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 使能TIM3时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 定时器基本配置
TIM_TimeBaseStructure.TIM_Period = 999; // 周期设为999,PWM频率 = 72MHz / (71 + 1) / (999 + 1) = 1kHz
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频器设为71,得到1MHz计数频率
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// PWM模式配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比为0
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
// 启动TIM3
TIM_Cmd(TIM3, ENABLE);
}
void GPIO_Configuration(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA6为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
代码说明:
- TIM3_PWM_Init 函数 :
- 首先使能 TIM3 的时钟。
- 配置定时器基本参数,包括周期(
TIM_Period)和预分频器(TIM_Prescaler),从而确定 PWM 的频率。 - 选择 PWM 模式 1,并设置初始占空比(
TIM_Pulse)。 - 最后启动 TIM3。
- GPIO_Configuration 函数 :
- 使能 GPIOA 的时钟。
- 将 PA6 引脚配置为复用推挽输出模式,用于输出 TIM3 产生的 PWM 信号。
- main 函数 :
- 调用
TIM3_PWM_Init和GPIO_Configuration进行初始化。 - 在
while(1)循环中,通过改变TIM_SetCompare1(TIM3, dutyCycle)中的dutyCycle值来无级调节 PWM 的占空比,进而调节 LED 的亮度。这里通过一个简单的延时和占空比递增递减逻辑来演示占空比的变化。实际应用中,dutyCycle的值可以通过外部输入(如按键、传感器等)来动态改变。
- 调用
(拓展)单片机里的复用功能
在单片机里,复用功能就好比一个 "多功能插座"。
咱们知道单片机上有很多引脚,这些引脚一开始你可能觉得它们只能干一种事儿,比如单纯地作为输入或者输出引脚。但实际上,很多引脚都有复用功能,也就是它们除了最基本的输入输出功能外,还能有其他用途 。
就拿刚才说的 STM32 的 PA6 引脚举例,它最基础的功能可能是作为普通的 GPIO(通用输入输出端口),可以连接个小灯,控制灯的亮灭。但它还有复用功能,能作为 TIM3 定时器的通道 1 。当把它配置成 TIM3 定时器通道 1 的时候,这个引脚就能输出 PWM 信号,像控制电机转速或者调节 LED 亮度等,这和单纯控制小灯亮灭是不同的功能。
之所以要有复用功能,是为了在不增加芯片引脚数量的情况下,让芯片能实现更多更复杂的功能,提高芯片的使用效率。