在嵌入式终端开发中,PWM(Pulse Width Modulation,脉宽调制)是一项基础且核心的技术,贯穿于 LED 调光、电机调速、电源稳压、蜂鸣器发声等绝大多数场景。无论是入门级的 51 单片机、主流的 STM32,还是轻量化的 ESP32、嵌入式 Linux 终端,PWM 都扮演着“数字与模拟之间的桥梁”角色——用数字信号的高频切换,等效实现模拟信号的连续输出,兼顾了数字控制的精准性与模拟控制的灵活性。
很多嵌入式开发者入门时,只知道“调占空比就能控亮度/转速”,却不清楚 PWM 的底层逻辑、参数权衡,以及不同嵌入式平台的实现差异,导致实际开发中频繁遇到波形抖动、精度不足、资源占用过高等问题。本文将从基础概念、核心原理、实现方式、实战案例、常见问题五个维度,全面拆解嵌入式终端 PWM 技术,帮你从“会用”升级到“活用、用好”。
PWM 的本质是一组周期性的方波信号,其核心是通过控制“高电平持续时间”与“整个周期时长”的比例,来调节输出的等效电压(或功率)。理解 PWM,首先要吃透三个不可分割的核心参数:频率、占空比、分辨率,三者共同决定了 PWM 信号的性能和适用场景。
占空比是 PWM 最核心的参数,定义为一个完整周期内,高电平持续时间(T)与整个周期时长(T)的百分比,公式如下:
$$ \text{Duty Cycle} = \frac{T {on}}{T{on} + T_{off}} \times 100\% $$
其中 T 是低电平持续时间,T = T + T。比如 1ms 周期的 PWM 信号,若高电平持续 0.5ms、低电平持续 0.5ms,占空比就是 50%;高电平持续 0.3ms,占空比就是 30%。
在嵌入式终端中,占空比直接决定了输出的等效效果:对 LED 而言,占空比越高,等效亮度越强(50% 占空比的 3.3V PWM,等效电压约 1.65V);对直流电机而言,占空比越高,电机获得的平均功率越大,转速越快;对电源模块而言,占空比决定了输出电压的高低。占空比的调节范围通常是 0%\~100%,0% 对应完全低电平(无输出),100% 对应完全高电平(持续输出)。
频率是指每秒内 PWM 信号完成的周期数,单位为 Hz,与周期 T 互为倒数:
$$ f = \frac{1}{T} $$
。比如频率 1kHz,意味着每秒产生 1000 个方波周期,每个周期时长 1ms;频率 10kHz,每个周期时长仅 100μs。频率的选择是嵌入式 PWM 开发的关键权衡点,直接影响输出稳定性和系统损耗,不同场景的频率选择有明确讲究(结合嵌入式终端实际应用场景整理):
分辨率反映了占空比可调节的最小精细程度,即“占空比能精确到多少百分比”,通常由嵌入式终端的定时器位数决定。假设定时器为 N 位(计数范围 0\~2-1),则 PWM 的最大可调节级数为 2 级,分辨率公式如下:
$$ \text{分辨率} = \frac{1}{2^N - 1} \times 100\% $$
举个直观例子:8 位定时器(0\~255)的分辨率为 1/255≈0.39%,意味着占空比只能以 0.39% 为步长调节(如 0%、0.39%、0.78%...);10 位定时器(0\~1023)的分辨率为 1/1023≈0.1%,调节精度提升 3 倍以上;16 位定时器(0\~65535)的分辨率可达 1/65535≈0.0015%,适合高精度场景(如精密伺服控制、恒光强调节)。
需要注意:分辨率与频率存在相互制约关系——当系统时钟固定时,要提高频率,需减小定时器的自动重载值(ARR),这会间接降低分辨率;反之,追求高分辨率,需增大 ARR,会导致频率下降。嵌入式开发中,需根据场景平衡两者(如普通 LED 调光用 8 位分辨率即可,精密调速需 10 位以上)。
嵌入式终端生成 PWM 信号的核心逻辑,本质是“定时器计数 + 比较输出”,无论硬件实现还是软件模拟,核心思路一致——通过定时器设定周期,通过比较寄存器设定高电平时长,当定时器计数达到比较值时,翻转 IO 口电平,循环往复形成方波。
以最常用的“向上计数模式 +PWM 模式 1”为例(主流嵌入式定时器默认模式),底层工作流程可简化为 4 步,通俗易懂:
补充说明:PWM 模式 1 和模式 2 的区别仅在于“电平翻转逻辑”——模式 1 是“计数<1ccr 输出高电平”,模式="" 2="" 是“计数="" <1ccr="" 输出低电平”,实际开发中模式="" 1="" 更常用(高电平有效)。
这里给出核心计算公式(适用于所有主流嵌入式平台,如 STM32、ESP32、51 单片机),掌握这些公式,可自由配置 PWM 参数:
示例:STM32F103 系统时钟为 72MHz,需生成 1kHz、50% 占空比的 PWM 信号,计算过程如下:
嵌入式终端中,PWM 的实现方式主要分为两种:硬件 PWM(定时器自带 PWM 模块)和软件 PWM(通过延时/中断模拟)。两者各有优劣,适用场景不同,核心选择原则是: 能用硬件,绝不靠软件(硬件 PWM 更稳定、省 CPU 资源),软件 PWM 仅作为硬件资源不足时的妥协方案。
硬件 PWM 是利用嵌入式终端内部的定时器 + 专用 PWM 模块实现,无需 CPU 持续干预,一旦配置完成,定时器会自动生成稳定的 PWM 信号,是嵌入式开发的首选方式。主流嵌入式芯片(STM32、ESP32、Arduino、增强型 51、PIC)均支持硬件 PWM,不同芯片的 PWM 模块差异主要在于“定时器数量、通道数、分辨率、高级功能(如互补输出、死区控制)”。
以下示例覆盖 3 种最常用嵌入式平台,重点展示核心配置步骤,省略冗余的时钟、GPIO 初始化代码(完整代码可结合芯片手册补充)。
#include "stm32f1xx_hal.h"
TIM_HandleTypeDef htim3; // 定时器句柄
// PWM初始化:1kHz频率,10位分辨率(ARR=1023)
void MX_TIM3_PWM_Init(void) {
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
// 定时器基础配置
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71; // 72MHz分频为1MHz(72/72=1)
htim3.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数
htim3.Init.Period = 1023; // ARR=1023,10位分辨率
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // 启用自动重载
HAL_TIM_Base_Init(&htim3);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);
HAL_TIM_PWM_Init(&htim3);
// PWM通道配置(模式1,高电平有效)
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 512; // 初始占空比50%(512/1023≈50%)
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
TIM_MasterCtrlSignalConfig(&htim3, TIM_MASTERCTRLSIGNAL_RESET, TIM_MASTERCTRLMODE_DISABLE);
}
// 启动PWM输出
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
// 动态修改占空比(例如修改为30%:30%×1023≈307)
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 307);
#define LED_PIN 2 // PWM输出引脚
#define PWM_CHANNEL 0 // PWM通道(ESP32支持16个通道)
#define PWM_FREQ 5000 // 5kHz频率
#define PWM_RESOLUTION 10// 10位分辨率(0~1023)
void pwm_init(void) {
// 初始化PWM通道:频率5kHz,10位分辨率
ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);
// 将引脚绑定到PWM通道
ledcAttachPin(LED_PIN, PWM_CHANNEL);
}
void main(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
pwm_init();
while (1) {
// 输出50%占空比(512/1023≈50%)
ledcWrite(PWM_CHANNEL, 512);
vTaskDelay(1000 / portTICK_PERIOD_MS);
// 输出30%占空比(307/1023≈30%)
ledcWrite(PWM_CHANNEL, 307);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
传统 51 单片机(如 STC89C52)无专用 PWM 模块,需用软件模拟;增强型 51(STC12、STC15)内置 PCA 模块,可实现硬件 PWM:
#include <STC15F2K60S2.h>
// PCA模块初始化,PWM输出(P1.3引脚,1kHz频率)
void pwm_init(void) {
CMOD = 0x00; // PCA时钟 = 系统时钟/12(假设系统时钟11.0592MHz)
CL = 0x00; // PCA计数器低8位
CH = 0x00; // PCA计数器高8位
CCAPM0 = 0x42;// PCA模块0工作在PWM模式1,允许比较匹配输出
CCAP0L = 0x7F;// 比较寄存器低8位(占空比50%)
CCAP0H = 0x7F;// 比较寄存器高8位
CR = 1; // 启动PCA计数器
}
void main(void) {
pwm_init();
while (1); // 硬件自动输出PWM,无需CPU干预
}
软件 PWM 是通过 CPU 执行延时函数或中断服务函数,手动翻转 IO 口电平,模拟 PWM 方波。适用于无硬件 PWM 资源的芯片(如传统 51 单片机),或硬件 PWM 通道全部占用的场景,核心实现思路有两种:延时翻转(简单但精度低)、定时器中断翻转(精度较高)。
#include <reg52.h>
sbit PWM_OUT = P1^0; // PWM输出引脚
unsigned int count = 0; // 周期计数
unsigned int high_time = 50; // 高电平计数(50×10μs=500μs)
unsigned int period = 100; // 周期计数(100×10μs=1ms,对应1kHz频率)
// 定时器0初始化:10μs中断一次(12MHz晶振)
void timer0_init(void) {
TMOD |= 0x01; // 定时器0工作模式1(16位)
TH0 = (65536 - 10)/256; // 初值高8位
TL0 = (65536 - 10)%256; // 初值低8位
ET0 = 1; // 开启定时器0中断
TR0 = 1; // 启动定时器0
EA = 1; // 开启总中断
}
// 定时器0中断服务函数:模拟PWM波形
void Timer0_ISR() interrupt 1 {
TH0 = (65536 - 10)/256; // 重装初值
TL0 = (65536 - 10)%256;
count++;
if (count == high_time) {
PWM_OUT = 0; // 高电平结束,翻转为低电平
}
if (count >= period) {
PWM_OUT = 1; // 周期结束,翻转为高电平
count = 0; // 重置周期计数
}
}
void main(void) {
PWM_OUT = 1;
timer0_init();
while(1) {
// 可在主循环中动态修改high_time,调节占空比
// 例如:high_time = 30; // 30%占空比
}
}
掌握 PWM 的原理和实现方式后,核心是结合嵌入式终端的实际应用场景,灵活配置参数、优化实现方案。以下是 4 个最常见的实战场景,涵盖基础入门到工业级应用,附关键设计要点和参数建议。
LED 调光是 PWM 最基础的应用,核心是通过调节占空比,改变 LED 的平均电流(从而改变亮度)。由于 LED 是电流驱动器件,PWM 调光时需串联限流电阻(220Ω\~1kΩ),避免电流过大烧毁 LED。
关键设计要点:
直流电机调速是嵌入式终端 PWM 的核心应用之一(如智能小车、风扇调速),核心是通过调节 PWM 占空比,改变电机两端的平均电压,从而改变电机转速(占空比与转速近似线性关系)。
关键设计要点:
蜂鸣器分为有源蜂鸣器和无源蜂鸣器,PWM 的应用方式不同:有源蜂鸣器只需固定频率的高电平即可发声(PWM 占空比 50%,频率匹配蜂鸣器谐振频率);无源蜂鸣器需通过 PWM 调节频率,实现不同音调(频率越高,音调越高)。
关键设计要点:
在嵌入式终端的电源模块中(如 DC-DC 降压),PWM 用于调节开关管的导通时间,实现输出电压的稳定(如将 12V 输入转换为 3.3V 输出,供 CPU、传感器等外设使用)。这种场景对 PWM 的精度、稳定性要求极高,属于工业级应用。
关键设计要点:
实际开发中,很多开发者会遇到“PWM 无输出、波形抖动、精度不足、噪音过大”等问题,大多是参数配置、硬件设计或实现方式不当导致。以下是 6 个最常见的问题,附原因分析和解决方案,帮你快速避坑。
核心原因(按概率排序):
调试技巧:用示波器测量 PWM 引脚,若无波形,先检查 GPIO 复用配置和引脚映射(参考芯片 datasheet 的“引脚复用表”),再检查定时器初始化和启动代码,最后核对 ARR、CCR 参数。
核心原因:
解决方案:优先替换为硬件 PWM;提高 PWM 频率至推荐范围;增加电源滤波电容(0.1μF+10μF),稳定供电;为 PWM 定时器分配独立通道,避免与其他中断冲突。
核心原因:分辨率过低(如 8 位分辨率用于精密调速);频率与分辨率配置不当(频率过高导致分辨率下降);占空比计算时取整误差过大。
解决方案:提高定时器位数(如 8 位改为 10 位、16 位);平衡频率与分辨率(如降低频率,增大 ARR 值,提升分辨率);占空比计算时采用四舍五入,而非直接截断,减小取整误差。
核心原因:PWM 频率过低(<1kHz),导致电机绕组电流纹波过大,产生机械振动和噪音;驱动芯片与电机之间未接续流二极管,绕组反电动势损坏芯片,同时产生噪音。
解决方案:将 PWM 频率提升至 5kHz\~10kHz;在电机两端并联续流二极管(1N4007),吸收反电动势;检查电机接线,避免接触不良。
核心原因:多路 PWM 共用一个定时器,修改其中一路的 CCR/ARR 值,会影响其他路的波形;软件 PWM 多路实现时,中断冲突导致波形紊乱。
解决方案:多路 PWM 尽量使用独立的定时器通道(如 STM32 TIM2、TIM3、TIM4 分别驱动不同路 PWM);若共用一个定时器,确保多路 PWM 的周期一致,仅修改各自的 CCR 值;避免用软件 PWM 实现多路输出。
核心原因:频率过高(如超过 1MHz),导致驱动芯片、开关管的开关损耗急剧增加,产生大量热量;PWM 输出引脚未优化,驱动能力不足,导致功耗浪费。
解决方案:降低 PWM 频率至合理范围(结合场景选择,如电源稳压可降至 100kHz\~500kHz);选用低开关损耗的驱动芯片和 MOS 管;优化 GPIO 输出模式,降低驱动电流,避免无效功耗。
PWM 技术看似简单,实则是嵌入式终端开发的“基本功”,其核心不在于“会配置代码”,而在于“理解参数权衡、匹配应用场景”。总结几点核心心法,帮你快速掌握 PWM 的精髓:
从入门级的 LED 调光,到工业级的电源稳压、电机伺服,PWM 技术贯穿了嵌入式终端开发的各个场景。掌握本文的原理、实现方式和实战技巧,你可以轻松应对绝大多数嵌入式 PWM 开发需求,从“入门”升级到“进阶”。如果在实际开发中遇到具体问题,可结合芯片型号和场景,针对性优化参数和代码——实践,才是掌握 PWM 的最佳方式。
关注我的CSDN: https://blog.csdn.net/qq30095907?spm=1011.2266.3001.5343
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70049487/viewspace-3110338/ ,如需转载,请注明出处,否则将追究法律责任。