☰
PWM原理详解:占空比、频率、分辨率与STM32电机调速实战
2026/10/9 1:01:07 网站建设 项目流程

各位做嵌入式或者电子设计的朋友,第一次碰 PWM 这个概念,多半是在调 LED 亮度或者转电机的时候。我记得自己最早是在一个调光小夜灯项目里撞上 PWM 的,当时脑子里有个特别朴素的疑问:让灯快速开关,怎么就能做到“亮度连续变化”?这个疑问一直带到我后来做电机调速、做散热风扇控制,直到把 PWM 的本质彻底想明白,才发现很多配置里的坑——比如 PWM 模式 1 和模式 2 到底啥区别、频率选多少合适、PA8 为什么总被拿来当 PWM 引脚——全部迎刃而解。

这篇文章就是想把这些内容一次性讲透。我会从 PWM 的物理本质说起,把占空比、频率、分辨率这三个最核心的旋钮讲清楚,再结合电机控制、STM32 和国产单片机的 PWM 配置、故障保护、硬盘风扇温度监控这些高频场景,把原理和实操串成一条线。适合刚接触 PWM 的单片机初学者,也适合那些已经会配置、但一直没搞懂背后“为什么”的人。

1. 从一盏调光台灯说起:PWM 在时间维度上怎么“骗”过负载

1.1 占空比与周期:两个最基础的数字

PWM 的全称是 Pulse Width Modulation,脉冲宽度调制。一个周期 T 内,高电平持续的时间是 Ton,占空比 D = Ton / T × 100%。举个例子,一个 5V 的方波信号,如果高电平占了整个周期的 60%,那这个信号在时间维度上的平均电压就是 5V × 60% = 3V。用这样的信号去驱动一颗 LED,人眼看到的亮度就相当于 3V 直流驱动的效果。

很多人第一次听这个会觉得奇怪:明明灯的供电是一个不断在 5V 和 0V 之间跳变的方波,怎么亮度就变成 3V 了?这里面有个关键点——负载感知的从来不是某一瞬间的电压,而是单位时间内的能量累积值。LED 虽然本身响应速度极快,但人眼的视觉暂留效应会把高频闪烁“抹平”,所以只要开关频率够高,你看到的就不是闪动,而是一个稳定的、亮度介于全亮和熄灭之间的状态。

占空比和周期,就是描述 PWM 信号最核心的一对参数。占空比决定“平均效果”的强弱,周期(或者说频率)决定这个平均效果是否足够平滑。理解了这两个数的含义,后面所有关于 PWM 的配置逻辑都能串起来。

1.2 惯性系统是 PWM 成立的物理前提

为什么快速开关能让灯看起来稳定?核心原因在于:几乎所有 PWM 的应用对象,都是对高频变化不敏感的“惯性系统”。

人眼的视觉暂留是一种生物惯性,图像在人脑中会保留约几十毫秒,所以频率超过 50 到 60Hz 的闪烁就基本感知不到了。电机的转子有机械惯性,加上绕组本身的电感效应,电压虽然在一开一关地跳变,但电流和转矩是连续变化的,电机轴的转速不会跟着高频抖动。加热器有热惯性,热量积累和散失都需要时间,所以即使 PWM 频率低到几赫兹,加热体温度依然能保持平稳。说白了,PWM 就是利用这些系统“反应慢”的特点,把时间和能量切成小块,让负载在宏观上只感受到平均值。

但反过来想,如果一个负载完全没有惯性,比如一颗快响应光敏电阻直接接在 LED 旁边,它可能真的会检测到光的闪烁。这种情况就需要用外部 RC 低通滤波器把方波的高频分量滤掉,提取出干净的直流平均值。很多传感器供电或模拟信号调理电路里,就是这么处理 PWM 信号的。

理解这个前提之后,你就能明白为什么调 LED 和调电机要用不同的 PWM 频率,也明白为什么不能拿同一个配置去套所有场景。

2. 频率、占空比、分辨率:三个旋钮背后的物理与工程权衡

2.1 频率怎么选:从 LED 闪烁到电机啸叫

选 PWM 频率,本质上是找一个“让负载感觉不到开关动作”的合适范围。不同负载的惯性特征差异很大,所以合适频率也差得很远。

应用场景常用频率范围选频逻辑
LED 调光200Hz 到 2kHz 以上避开人眼闪烁感知,频率越高调光越平滑,但太高会引入开关损耗
直流电机调速10kHz 到 25kHz避开音频范围,也避免机械共振点,频率太高会显著增加 MOS 管开关损耗
加热器/电热丝1Hz 到 10Hz热惯性很大,极低的频率就能稳定控温,开关损耗几乎可以忽略
4 线风扇调速21kHz 到 25kHz避开音频范围,防止“啸叫”,和主板风扇调速规范一致

这里有个容易踩的坑:直流电机如果用几百赫兹的 PWM,往往会听到明显的“嗡嗡”声。这个声音的来源有两个,一是 PWM 频率本身落在音频范围内,线圈和转子受电磁力激励后发声;二是 PWM 频率刚好撞上电机或负载的机械共振频率。解决起来也不复杂,把频率提到 15kHz 以上,共振点避开了,音频范围内也没有能量,噪音自然消失。但频率不是越高越好,MOS 管每次开关都有损耗,频率翻倍开关损耗差不多也翻倍,所以实际工程里一般不会盲目追求超高频率。

LED 调光则要注意另一个问题:频率太低会让人明显感觉到闪烁。有人喜欢用 1kHz 以下,其实只要不处于 50 到 60Hz 附近问题不大。不过我在实际项目里,只要硬件条件允许,都会把 LED 的 PWM 频率放在 1kHz 以上,肉眼看起来更舒服,拍照也不会出现明显条纹。

2.2 分辨率与计数器位数:8 位、16 位与占空比精度

用单片机定时器产生 PWM 时,你配置的其实是一个计数器周期和一个比较值。以最常见的做法来说:定时器从 0 往上计数,数到自动重装载值 ARR 之后归零重新开始;比较寄存器 CCR 决定翻转点——计数值小于 CCR 时输出一个电平,大于等于 CCR 时输出另一个电平。这样占空比就是 CCR / (ARR + 1) × 100%。

注意这个“ARR + 1”的小细节,计数器是从 0 开始数的,所以 0 到 ARR 一共是 ARR + 1 个计数状态。如果 ARR 设为 255,那实际有 256 个分级,占空比最小步进约为 0.4%;如果 ARR 设为 65535,就有 65536 个分级,占空比精度约为 0.0015%。这就是常说的 8 位和 16 位分辨率。

分辨率不是越高越好,它和频率是互相牵制的,因为两者都受限于定时器时钟。同一个定时器时钟下,想要更高的分辨率(更大的 ARR),能得到的最高 PWM 频率就会下降。我见过有人非要在 72MHz 的系统时钟下同时要 100kHz 的 PWM 频率和 16 位分辨率,算术上根本做不到,只能退而求其次,降频率或者降分辨率。

对 LED 调光来说,8 位分辨率有时候在低亮度区会出现肉眼可见的跳变——比如占空比从 1% 变到 2%,亮度变化能看出来。如果对平滑度要求高,可以考虑用 10 位或 12 位分辨率,或者用对数曲线做软调光。对电机控制来说,10 位到 12 位分辨率已经完全够用,一味追求 16 位意义不大,因为电机本身的机械惯性和摩擦会让微小占空比变化“吸收”掉。

2.3 一个完整的频率和占空比计算案例

既然频率和分辨率互相牵制,那实际配置时怎么定参数?我用 STM32F103 的 72MHz 定时器时钟举个例子。

假设我要一个 20kHz 的 PWM 频率,同时希望分辨率能有 10 位左右。定时器 PWM 频率的公式是:

PWM 频率 = 定时器时钟 / ((PSC + 1) × (ARR + 1))

先给 ARR 留出 10 位分辨率空间,也就是 ARR + 1 = 1024。代入公式反推:

PSC + 1 = 72MHz / (20kHz × 1024) ≈ 3.515

PSC 取整数后要么是 2 要么是 3。取 PSC = 2 时,实际 PWM 频率 = 72MHz / (3 × 1024) ≈ 23.4kHz,分辨率仍然是 10 位;取 PSC = 3 时,实际频率 = 72MHz / (4 × 1024) = 17.6kHz,同样 10 位分辨率。这两组参数都能用,选哪个看你更在意频率还是更在意占空比调节范围。

如果你希望频率正好是 20kHz,那就得接受分辨率略低于 10 位:设 PSC = 1,ARR + 1 = 72MHz / (20kHz × 2) = 1800,此时 ARR = 1799,实际有效分级 1800 个,大约 10.8 位,还是很不错的。

这种手动计算能力一定要有,虽然 STM32CubeMX 这类工具会自动帮你算,但遇到工具不支持的芯片、或者需要精确匹配某个频率和分辨率时,自己心算一遍能少走很多弯路。我自己的习惯是:先定频率,再定分辨率,最后用公式去凑 PSC 和 ARR,而不是反过来。

3. 为什么 PWM 能控电机:感性负载的“记忆效应”与续流路径

3.1 线性调节为什么又热又费电

在讲 PWM 电机调速之前,先明白一个对比:为什么不用线性方式调电机速度?

线性调速的原理就是往电源和电机之间串联一个可变电阻或工作在线性区的三极管,通过消耗多余电压来降压。举个具体数据:12V 的电源驱动一个额定 12V 的直流电机,你想让它半速运行,于是把电机两端电压降到 6V。这时候理想情况下,电机消耗的功率是 6V 乘以电流,但串联的调整管上同样要承担 6V 乘以电流的功率损耗——也就是说有一半的能量白白变成了热量。

电机功率稍微大一点,这个热量就非常可观。我之前调试一个 24V、额定电流 3A 的电机,用线性方案降速到一半,调整管上要散掉将近 36W 的热量,散热片体积大得离谱。换成 PWM 之后,MOS 管只在开关瞬间有一点损耗,导通时压降接近零、截止时电流为零,整体效率立刻上到 90% 以上。这个差距,就是 PWM 能成为电机调速主流方案的根本原因。

3.2 电感电流的“惯性”与续流二极管

电机绕组本质上是一个大电感。电感有一个非常重要的特性:电流不能突变。也就是说,不管 PWM 信号怎么切换,流过电机绕组的电流都是连续变化的,不会瞬间从 5A 跳到 0A。

PWM 导通期间,电源给绕组充电,电流按电感的时间常数逐步上升;PWM 关断期间,绕组两端电压突然被切断,但电感会试图维持原来的电流方向,这时候必须给这个电流提供一条通路,否则绕组两端会产生极高的反向感应电动势,直接把 MOS 管或驱动芯片击穿。这条通路通常就是一个续流二极管,接在电机绕组和电源地之间。对 H 桥电路来说,还可以靠另一侧 MOS 管内部的体二极管或者主动导通下管来续流。

所以你观察电机驱动电路时,正极到电机正端、电机负端到地之间,一定会有续流二极管或者等效续流路径。这个二极管不是可选项,而是必须项。没有续流路径的 PWM 电机驱动,出问题是迟早的事。我在焊接测试板时遇到过几次 MOS 管烧毁,追查到最后都是续流二极管虚焊或者反接导致的。

3.3 调速背后的平均电压模型,不是整条故事

电机调速最简化的模型是:平均电压 Vavg = D × VCC,转速近似与 Vavg 成正比。这个模型用来理解 PWM 调速的大方向没问题,但实际工程里它只讲了一半故事。

另一半是电流脉动。PWM 导通时电流上升,关断时电流下降,两者之和才是电机的实际电流波形。如果 PWM 频率太低或者电机电感太小,电流脉动会很大,轻则产生转矩波动和噪音,重则导致电机发热增加。这也是为什么电机 PWM 频率一般不能低于几千赫兹——不是频率本身有什么魔法,而是电感滤波需要足够高的开关频率来维持电流的连续和平滑。

再往深走一步,如果你控制的是带 H 桥的直流电机,还会遇到“刹车”和“反接制动”这些更进阶的操作。比如通过给下桥臂短时间全部导通来实现再生刹车,本质上也是在 PWM 的开关逻辑里切换不同的续流和供电拓扑。初学者不需要一上来就搞懂这些,但知道 PWM 调速背后不只有“平均电压”这一个概念,以后调起来遇到非线性现象就不会太慌。

4. 从 CubeMX 到寄存器:STM32 上生成 PWM 的完整脉络与模式 1/模式 2 的区别

4.1 引脚、定时器与通道的关系:为什么 PA8 常被拿来做 PWM

单片机开发里最常见的问题之一就是:“为什么大家老用 PA8 做 PWM?”原因很简单:在 STM32F103 系列里,PA8 默认复用功能就是 TIM1_CH1,也就是高级定时器 TIM1 的第一个比较输出通道。

TIM1 属于高级定时器,它比普通定时器多了互补输出、死区生成、刹车输入这些功能。这些功能对有刷电机和半桥驱动特别有用,所以很多教程、开发板资源都会优先使用 TIM1 的通道做 PWM 演示,PA8 自然就成了“PWM 引脚”的代表。但这里必须提醒一句:不同型号、不同封装的芯片,同编号引脚的复用映射不一定相同。比如某些型号的 PA8 可能映射到别的定时器通道,或者同一引脚有多个复用功能。配置之前一定翻数据手册的“Alternate function mapping”表格,这是最可靠的依据。

还有个小细节:STM32 的引脚复用要记得开启对应 GPIO 的复用时钟,F1 系列还需要配置 AFIO 重映射。很多人配置完发现引脚没输出,最后查出来是 AFIO 时钟没开。CubeMX 会自动帮你把这些使能好,但裸寄存器开发时这一步很容易漏。

4.2 在 STM32CubeMX 里配出一路可用的 PWM

用 STM32CubeMX 配置 PWM 的完整步骤,我按自己常用的顺序整理一下,每一步背后都有实际考量:

  1. 先把 PA8 设置为复用功能输出,选择它的复用信号为 TIM1_CH1。
  2. 在 Timers 里使能 TIM1,在 Channel1 选项里选 PWM Generation CH1。
  3. 到 Clock Configuration 里确认定时器时钟。以 STM32F103 为例,APB2 总线上的定时器时钟通常是 72MHz,这里一旦看错,后面所有频率计算都会差。
  4. 在 TIM1 的参数配置页里设置 Prescaler(PSC)、Counter Period(ARR)和 Pulse(CCR)。Pulse 就是初始占空比对应值,和 ARR 的比例共同决定占空比大小。
  5. 设置 PWM 模式为 PWM Mode 1,极性为 High。如果你想让占空比在高电平时输出,极性 High 是最直观的选择。
  6. 生成代码后,在主程序里调用 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1),这步一漏,引脚就一直是低电平。

运行中要动态调整占空比,不需要重新初始化,直接修改比较寄存器就行。HAL 库里用 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, newCCR),底层就是往 CCR 寄存器写值。这个操作很快,可以在主循环里高频执行,也可以放到中断里做精准波形控制。

4.3 PWM 模式 1 与模式 2:比较逻辑的镜像关系

网上搜“PWM 模式 1 和模式 2 的区别”,能搜到一堆资料,但真正说清楚的没几个。其实逻辑非常简单:它们就是比较器输出逻辑的镜像关系。

以向上计数为例,定时器从 0 数到 ARR:

模式CNT < CCR 时CNT >= CCR 时占空比
PWM 模式 1输出有效电平输出无效电平CCR / (ARR + 1)
PWM 模式 2输出无效电平输出有效电平(ARR + 1 - CCR) / (ARR + 1)

所谓“有效电平”,就是你在极性配置里设定的电平。如果极性配置为 High,有效电平就是高电平;如果极性配置为 Low,有效电平就是低电平。所以“模式 1/2”和“极性高/低”这四组组合,组合出来的是四种不同的输出波形。实际调试时,与其背寄存器手册,不如接个示波器直接看波形,几秒钟就能明白怎么回事。

我在实际项目里 90% 的情况用 PWM 模式 1 + 极性 High,也就是计数器小于比较值时输出高电平,这样 CCR 越大占空比越大,直觉最顺畅。只有在需要反相控制外部电路,或者硬件上已经做了一级反相、需要在软件里补偿极性时,才会切到模式 2。

4.4 从 STM32 到 HC32F460、CCU6:换单片机时哪些经验可以平移

单片机从 STM32 换到国产或者 Infineon 等平台时,PWM 配置的“套路”是通用的。以 HC32F460 这类国产 M4 内核芯片为例,它的定时器 PWM 配置思路和 STM32 非常相似,基本都是:使能定时器时钟、配置计数周期和预分频、配置比较通道模式和比较值、使能输出。差别主要在库函数的命名和调用风格上。下面这段是思路示例,不是某一版 SDK 的直接可编译代码,因为各家库版本差异较大:

// HC32F460 配置 PWM 的思路性示例,具体 API 以官方库为准 static void pwm_timer_init(void) { // 1. 使能定时器外设时钟(对应 STM32 的 RCC 时钟使能) // 2. 配置计数模式:向上计数,设置周期寄存器(对应 ARR) // 和预分频寄存器(对应 PSC),注意计数从 0 开始 // 3. 配置比较通道:选择 PWM 模式、输出极性、设置比较值(对应 CCR) // 4. 使能定时器主输出和通道输出 // 有的芯片这步叫“使能 PWM 输出”,漏掉就没有波形 }

换平台时最容易出问题的就是这最后一步——使能主输出。STM32 高级定时器也有这个坑,TIM1 和 TIM8 在配置完后如果不调用类似 TIM_CtrlPWMOutputs 的接口开启主输出,PWM 波形根本出不来。不同芯片对这个机制的命名不一样,但存在感完全一样。

Infineon 的 CCU6 也是一回事,它是 XC2000 和 AURIX 系列里的捕获比较单元,功能上支持多通道 PWM、死区注入、故障保护输入和同步功能。别看名字叫“捕获比较单元”,原理上依然是定时器 + 比较器 + 翻转逻辑的组合。你只要在 STM32 上把 PWM 的本质吃透了,换到任何芯片上都可以快速上手,无非是查手册、找寄存器和例程的时间成本。

5. 实战排雷:噪音、抖动、故障保护与温度监控里的 PWM 细节

5.1 PWM 波形看起来正常,电机为什么还是嗡嗡响

示波器上波形方方正正,占空比也对,但电机一接上就嗡嗡响。这种情况我在项目里遇到不止一次,排查思路大概有三条。

第一,先查频率是不是落在音频范围或机械共振点。这是最常见的原因,解决方法是把 PWM 频率提到 20kHz 以上,虽然听感因人而异,但 20kHz 基本超过绝大多数人的听觉上限。第二,看占空比是否一直在大幅跳变。如果软件里直接从一个占空比跳变到另一个(比如 10% 直接切到 90%),电机会瞬间加速,机械冲击会诱发噪音和振动。解决办法是加斜坡——每几毫秒把占空比往目标值推进一小步。第三,查电源电压是否被拉垮。PWM 全速工作时电流波动大,如果电源内阻高或线径细,母线电压会同步波动,进而影响电机电流的平稳性。这种情况下波形实际已经变形,只是探头测到的是电源端的信号,没反应出真实情况。

遇到电机噪音,别急着改硬件,先用软件把频率和斜坡这两项优化掉,大概率能解决 80% 的问题。

5.2 占空比跳变时的抖动:死区、极性与上下桥臂

如果你用的是半桥或全桥驱动,还有一个特别容易忽略的细节:互补 PWM 的死区时间。

H 桥上下桥臂如果同时导通,等于电源直接短路。为了避免这种“直通”事故,必须在上下管切换之间插入一段死区时间——这段时间内两个管子都保持关断。STM32 高级定时器里有个很形象的名字叫“Dead Time”,配置后波形上能看到上升沿和下降沿之间插入了一个小缺口。

死区时间不是越大越好。太大,输出波形失真,电机电流畸变;太小,保护效果不足。典型值从几百纳秒到几微秒不等,取决于 MOS 管栅极电荷和驱动速度。测试时直接用示波器看上下管的栅极波形,确保“上管关断到下管开启”之间有明显间隔,这是最直观的验证方式。

5.3 故障保护:别只写软件,还有硬件刹车

PWM 驱动电机时,过流和过温是逃不掉的话题。很多人第一反应是“我在代码里写个 if 检测,超了就置占空比为 0”。这个思路没错,但响应时间可能不够。

代码从上到下执行,如果主循环忙于其他任务,故障发生到 PWM 关断之间可能有几十毫秒甚至更长的延迟。这个时间对感性负载来说,足够让电流冲到几倍额定值,烧毁 MOS 管。所以真正可靠的方案,是把 PWM 输出通道的硬件刹车功能用起来。STM32 高级定时器的 BRK(Break Input) 就是这么用的——外部比较器检测到过流信号后,直接拉断定时器输出,不经过 CPU,响应时间在微秒级。配置时再设定“刹车后输出到安全电平”,比如紧急情况下输出低电平关闭驱动。

我的经验是:软件保护用于常规工况限制和故障记录,硬件刹车用于真正的事故兜底。两者都要,但不要指望软件能在微秒级救命。类似地,CCU6 也有故障保护输入,功能一样,原理也一样。

5.4 硬盘温度监控里的 PWM 风扇:4 线风扇的工作原理与配置要点

很多玩 NAS、服务器或者台式机的人会搜索“PWM 怎么监控硬盘温度”,实际上这个需求通常是这样的:通过软件读取硬盘温度,然后自动调整风扇转速。这里面的风扇,绝大多数是 4 线 PWM 风扇。

4 线风扇的四根线分别对应:VCC(电源)、GND(地)、TACH(转速反馈)、PWM(调速控制)。TACH 脚会输出一个频率与转速成正比的脉冲信号,测量这个信号的频率就能反推风扇实际转速。PWM 脚则接收外部控制器发来的调速信号,常见规范是 25kHz 左右,占空比越高风扇转速越快。这个场景里 PWM 的使命很简单:根据温度传感器读数,按策略调整占空比,使风扇转速匹配散热需求。

举个典型的查表策略:硬盘温度低于 40℃ 时,占空比给个下限例如 30%,保持基本通风;40℃ 到 55℃ 之间,占空比随温度线性上升;超过 55℃ 直接 100% 全速。软件侧可以用下面的命令读取硬盘温度作为输入源:

# 读取 ATA 硬盘温度(Linux 环境下) smartctl -A /dev/sda | grep -i temperature # NVMe 硬盘则用 smartctl -A /dev/nvme0 | grep -i temperature

拿到温度后,在脚本或守护进程里计算目标占空比,再通过主板风扇接口或独立控制器输出 PWM 信号。这里有个实际经验:不要直接把占空比下限设成 0%。很多 4 线风扇在占空比过低时会直接停转,停转之后要么热保护、要么重新启动时卡死在低速状态。实际使用中,建议设一个 20% 到 30% 的转速下限,保证风扇始终在转。温度上来之后再平滑往上抬,不要等温度爆表了才从 20% 跳到 100%,那样只会带来噪音和机械冲击。

写在最后

PWM 看起来是一个特别基础的话题,但真正用起来,里面全是细节。我自己就曾经因为模式 1 和模式 2 的极性问题,写反了比较逻辑,导致风扇 PWM 信号反相,结果占空比越大转速反而越低,排查了整整一晚上才发现是软件里选错了模式。从那以后,凡是要配置 PWM,我一定先开示波器看一眼波形,再确认高电平持续时间是否随占空比增加而增加,最后才往下做控制逻辑。

给刚入坑的朋友一个建议:第一次调试 PWM 驱动时,从 10% 占空比开始,用手动方式缓慢往上调,同时用万用表测平均电压、用示波器看波形、用温度计摸 MOS 管温度。这个过程会让你对频率、占空比和负载响应之间的物理关系建立直接的体感。等这些都熟悉了,无论是 STM32、HC32F460 还是 CCU6,对你来说都只是“换一套函数名”而已。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询