PWM外设分辨率:被忽视的控制精度瓶颈
2026/9/11 23:11:51 网站建设 项目流程

我先说个真实经历。前阵子给一块基于STM32的LED RGB灯板做调光,固件逻辑很简单,就是把三路占空比写进比较寄存器。但实际效果特别诡异:红色通道从“几乎不亮”到“明显亮了一档”之间几乎找不到中间值,参数稍微动一点点,亮度就跳变,根本没法做平滑呼吸。我一开始怀疑是Gamma曲线没处理好,查了半天,最后无意间看了一眼定时器初始化,才发现PSC和ARR完全是随手填的,占空比调节档位被压到了不到9位。也就是从那一刻起我才意识到,很多人调PWM只关心频率够不够高、占空比算得对不对,却忽略了一个真正决定控制上限的东西——PWM外设分辨率。

这篇文章就把PWM外设分辨率这件事从头到尾说透:它到底是什么、怎么算、和频率时钟怎么博弈、又如何影响LED、舵机、电机、三相逆变这些最常见的控制场景。最后我也会把实际调板子遇到的精度坑和排查方法一并整理出来,希望能帮你在下一块板子上少走弯路。

1. PWM分辨率到底是什么,为什么它比频率更容易被忽视

1.1 用定时器模型把分辨率一次讲清楚

几乎所有MCU的PWM都是靠定时器生成的。一个定时器里有个计数器,从0一直数到重装值ARR,然后再回到0,如此循环。同时还有一个比较寄存器CCR,当计数值等于CCR的那一刻,输出引脚电平发生翻转。这样在计数器完成一个周期之后,就形成了一个占空比由CCR/(ARR+1)决定的方波信号。

所以一个PWM周期内,输出脉宽能改变的最小量,不是0.1%、0.01%这种抽象概念,而是“一个计数时钟”。你最多能把一个周期切成ARR+1份,占空比的档位数也就是ARR+1。每档之间的步进值就是1/(ARR+1)。这个档位数或者说步进量,就是PWM外设分辨率的本质。

举个最直观的例子。ARR=255时,计数器一个周期只有256个档位,这就是常说的“8位PWM”。ARR=999时,一个周期有1000份,约等于10位分辨率。ARR=65535时,也就是16位分辨率,占空比步进只有1/65536,大约0.0015%。

这里我要纠正一个常见错觉:很多人以为PWM分辨率取决于寄存器位宽,比如定时器比较寄存器是16位,那就自动拥有16位分辨率。实际上寄存器位宽只是上限,真正决定分辨率的,是你实际配置出来的ARR+1有多大。哪怕你用的是32位定时器,只要ARR设成了99,那分辨率就只有100档,相当于不到7位。寄存器位宽是“允许你切多细”,ARR才是“你实际切了多细”。

另一个容易混淆的是,分辨率不是频率。PWM频率决定的是输出波形每个周期有多长,分辨率决定的是这一个周期里能被切成多少份。两者一快一细,经常打架,后面我会专门讲。

1.2 分辨率决定“最后一档”能细微到什么程度

为什么说分辨率对控制精度影响这么大?因为它直接决定了系统输出的“最小调节量”。如果分辨率只有8位,那么你所有占空比都只能是0/256、1/256、2/256……一直到255/256这一组离散值。你想输出0.7%的占空比?对不起,离它最近的只有0.39%和0.78%,你只能在两个都不准确的值里选一个。

这个现象在LED调光里特别明显。人眼对低亮度的变化极其敏感,暗场下亮度从1/256升到2/256,看起来差不多是翻倍的亮度差,所以就会出现“怎么调都在跳变”的观感。同样的问题在舵机里更致命:舵机角度的映射关系通常要求脉宽精度在1微秒左右,如果分辨率不够,角度输出就只能在几个离散位置上“咔哒咔哒”地跳,完全做不到顺滑跟随。

电源、电机驱动这类场景也一样。分辨率不足会让PID控制器的输出产生量化台阶。控制量在小范围内反复横跳,反映到电机轴上就是怠速不稳、电流纹波大,甚至低速时一顿一顿。很多工程师遇到这类问题第一反应是调PID参数,实际根源往往在PWM外设分辨率这个更底层的地方。

所以,判断一颗芯片适不适合某个控制任务,不能只看主频和定时器数量,还得看“在目标PWM频率下,到底能跑到多少位有效分辨率”。这个数字才是控制精度的硬指标。

2. PWM频率、时钟与分辨率的三方博弈

2.1 一个公式把全部参数串起来

要让PWM外设跑起来,你需要配置三个关系密切的参数:外设时钟频率、预分频器PSC、自动重装值ARR。三者共同决定PWM频率,公式是:

f_PWM = f_clk / ((PSC + 1) × (ARR + 1))

而分辨率最小步进是:

占空比步进 = 1 / (ARR + 1)

这里有一个非常容易被忽视的点:占空比步进的分母里只有ARR,没有PSC。也就是说,在同一个PWM频率约束下,一旦你为了让频率更高而压缩了ARR,分辨率就会同步缩水;PSC只是把时钟降下来,没法帮你找回丢失的档位。

再看另一个表达:

最小脉宽时间颗粒 = (PSC + 1) / f_clk

这个值表示输出脉宽每档变化多少秒。比如外设时钟是72MHz,PSC设为71,那么时间颗粒就是72/72MHz=1微秒,即每个计数代表1us。在这个配置下,ARR设为19999,输出PWM频率正好是1kHz?不对,72MHz/(72×20000)=50Hz,这是舵机常见配置。可见ARR=19999时,一个周期内可参与调节的档位接近两万档,约14位分辨率,所以舵机脉冲宽度能按1us粒度调,非常顺滑。

如果把ARR改成9999,还是PSC=71,那PWM频率变成100Hz,占空比步进还是1/10000,时间颗粒仍是1us。注意,步进比例变大了,但脉宽时间颗粒没变。所以真正决定控制精细度的,是“时间颗粒”和“档位数”两个值的综合结果,不能只看其中一个。

2.2 实际算例:25kHz对应2048分辨率的代价

热搜词里有一条“25k 2048分辨率 240m时钟 对红值”,这种表述看着像碎片信息,但背后恰好是一个很典型的工程问题:想在25kHz的PWM频率下获得2048档分辨率,需要什么样的时钟条件?

先做一道简单的换算。假设不分频,即PSC=0,PWM频率由f_clk/(ARR+1)决定。要在240MHz时钟下得到25kHz,则:

ARR + 1 = 240MHz / 25kHz = 9600

换算成位数是log2(9600)约等于13.2位。也就是说,在PSC=0的情况下,240MHz的主频能够支撑约13位分辨率,超过2048档的要求,看起来很宽裕。

但假如你为了让主频更稳、降低外设功耗,加了一个预分频系数,比如PSC=2,那么计数时钟变成80MHz,ARR+1最多只能是80MHz/25kHz=3200,约11.6位。如果PSC=9,计数时钟24MHz,ARR+1只有960,约9.9位,这时候连2048档都扛不到了。

这个算例解释了一个高频现象:很多人在240MHz甚至更高主频的芯片上,明明跑着25kHz的PWM,调节占空比时却发现输出明显“阶梯化”。问题不一定出在算法,而是你在配置PSC时悄悄把分辨率卖了。尤其是PWM频率需求进一步提高到50kHz、100kHz时,如果是24MHz的定时器时钟,ARR+1可能只剩几百档,相当于连9位都不到。此时你再怎么做软件精修,硬件输出本身就已经量化得跟楼梯一样了。

2.3 频率与分辨率的真实对照表

我把常见的几个PWM频率在典型时钟下的分辨率列出来,方便你对照定位自己的问题。这里假设定时器时钟为72MHz,PSC按实际需要调整,保证频率正确。

PWM频率所需ARR+1(72MHz时钟,PSC=0)实际分辨率位数占空比步进
1kHz72000约16.1位0.0014%
10kHz7200约12.8位0.0139%
20kHz3600约11.8位0.0278%
25kHz2880约11.5位0.0347%
50kHz1440约10.5位0.0694%
100kHz720约9.5位0.1389%
1MHz72约6.2位1.39%

看到没有,1kHz PWM时你有16位分辨率,很漂亮;到100kHz PWM时,理论档位只剩720个,约9.5位。如果此时又加了一个预分频器,比如PSC=1,那么可用档位数会直接减半到360个,约8.5位。很多数字电源或电机驱动器要求PWM频率在100kHz以上,同时对占空比精细度要求又很高,这时候如果芯片本身定时器时钟不够快,算法再好也白搭。

这也是为什么有些设备会引入32位定时器来做PWM,不是为了那离谱的计数上限,而是因为它能提供更高的ARR空间。另外,不同定时器挂在不同的时钟总线上,像STM32的APB1定时器时钟可能是APB2的一半甚至更低,选错外设也会白白损失分辨率。这种差异在数据手册里不会用“分辨率”这种词强调,但它真实存在。

3. 影响控制精度的四个隐藏细节

3.1 边沿对齐、中心对齐和单脉冲模式选哪个

同样是ARR,边沿对齐和中心对齐模式下,最终分辨率和波形的行为并不一样。

边沿对齐模式是最常见的,计数器从0递增到ARR,然后立刻归零。逻辑简单,分辨率等于1/(ARR+1),占空比更新及时。缺点是在一个周期内所有比较事件都集中在一边,如果占空比在周期中途被修改,输出脉宽可能产生一个不完整的中间状态,对严格要求“每个周波波形一致”的场合不友好。

中心对齐模式则是计数器先从0加到ARR,再从ARR减到0,一个PWM周期包含两次完整计数。这种模式下,每个PWM周期里PWM频率等于f_clk/(2×(PSC+1)×(ARR+1)),所以相同计数频率下,要想获得同样的PWM输出频率,ARR需要设成边沿对齐的两倍,分辨率直接多出1位。同时比较事件分布在递增段和递减段,占空比波形关于周期中心对称,谐波含量更低,很适合电机控制。

中心对齐模式还有额外的好处:计数器的峰值和谷值点恰好对应PWM波形中心,很多高级定时器可以在这些点触发ADC同步采样。由于此时开关管的开关噪声最小,采样到的电流值最接近相电流真实平均值。这就是网上很多人问“高级定时器中心对齐模式下ADC采样时刻怎么设置”的原因。我在电机驱动项目里实测过,同样的硬件,从边沿对齐切到中心对齐,配合比较事件触发采样,电流环的噪声明显小一个量级。

如果只是为了生成单次脉冲,比如触发可控硅或做超声波发射,可以用单脉冲模式,计数器匹配一次就停止,避免多余波形。这个模式和分辨率关系不大,但在面试题里经常出现,顺带提一下。

3.2 预装载/影子寄存器:为什么修改占空比会抖

规格书里有个叫“预装载”或者“影子寄存器”的东西,很多人配置时觉得多此一举,直接关掉。但它的作用非常关键。

如果没有开启预装载,CPU写入CCR的那一瞬间,比较值立刻生效。如果写入动作发生在PWM周期中间,输出电平会立刻按新比较值翻转,导致当前这个周期出现一段奇怪的脉冲:前半段按旧值翻转,后半段按新值翻转。从波形上看,就是出现了一个毛刺或者异常窄的脉冲。这种情况在LED调光时表现为亮度偶发闪烁,在电机控制里则意味着某个PWM周期的有效电压矢量跟预期不符,可能引起电流尖峰。

开启预装载之后,CCR写入值先被放在影子寄存器里,不会立刻影响输出,而是要等到计数溢出事件(也就是周期结束)才会统一加载。这样每个周期的波形都是一个完整且符合预期的形状。代价是修改占空比的生效会延迟一个周期,但绝大多数控制场景完全可以接受,甚至这正是PID周期性更新所希望的行为。

我的习惯是:只要是会变化占空比的应用,一律开启预装载;只有那些需要在特定时刻立即改变输出的场景,才考虑用强制更新或者关闭预装载。遇到过好几次“波形里时不时冒出一个窄脉冲”的求助帖,最后查出来都是没开预装载。

3.3 死区:桥式驱动里被“吃掉”的脉宽

带互补输出的PWM,比如H桥或三相逆变桥,上下两个管子绝对不能同时导通,否则电源直接短路。所以高级定时器在生成互补PWM时,会在两个输出之间插入一段死区时间。这段死区是故意让两个管子都关断的区间,用来防止直通。

但死区是有代价的。如果死区时间配置得跟开关频率相比太大,或者占空比设置的脉宽比死区还窄,那这个PWM周期可能根本没有有效的导通区间。对带死区插入的互补通道来说,比较值设得越小,实际有效脉宽就越短,一旦低于死区时间,输出波形会异常甚至被截除,导致你看到的实际输出电压和寄存器里的值完全对不上。

早期我做BLDC方波控制时,在一个三相驱动板上把死区设成了2us,载波频率20kHz,一个周期50us,看起来没问题。但当我把占空比调低到接近0%测试时,发现相电流出现了诡异的平台期,用示波器抓输出波形才发现,比较值低于某个阈值后,互补输出根本没有产生足够宽的脉冲,电机在低速区直接失去线性响应。后来我把死区时间从2us砍到与功率管开关时间匹配的0.8us,重新跑了全占空比扫描,低速线性度才好起来。

所以,死区的配置必须结合功率管栅极电荷、驱动芯片传播延迟、PWM频率三者一起算。死区太小容易直通炸管,死区太大则吞掉小脉宽区域的线性度,等效于在小占空比区间把分辨率全抹平了。

3.4 时钟抖动、电源纹波与GPIO翻转速度都是隐藏精度杀手

即使定时器寄存器配置完全正确,物理层面的抖动也会吃掉一部分实际分辨率。

首先是时钟源。很多低端MCU用内部RC振荡器,温漂和电压漂移都很明显,可能导致PWM实际频率偏离设定值,周期抖动变大。对要求精密的场景,应该使用外部晶振或者高精度时钟源。不过,这里说的抖动是“周期之间的微小涨落”,不是分辨率不足那种“档位太粗”,两者叠加在一起,控制精度的感受会更差。

其次是电源纹波。PWM比较器的参考电压由MCU内部电源决定,如果电源纹波大,或者数字噪声耦合到模拟比较区域,比较时刻会出现几个纳秒级的不确定偏移。在低频PWM下这可能无所谓,但高频大电流开关场景,比如电源、电机驱动,这个偏移会被放大,表现为PWM输出沿抖动。解决的办法是加强电源去耦,在MCU供电引脚附近放足够的MLCC电容,并让功率地与数字地干净分开。

再次是GPIO的翻转速度。MCU引脚本身有压摆率限制,外部负载电容越大,上升沿越缓。当你追求非常小的脉宽调整量时,如果Arduino或者树莓派那种软件GPIO翻转,受系统中断、调度、任务切换影响,脉冲沿抖动可能达到微秒乃至数十微秒级别。相比之下,硬件定时器PWM从比较匹配到引脚翻转是硬件链路直达,确定性好得多,这就是为什么“外设PWM”精度远高于“软件PWM”的底层原因。

4. 典型应用场景下的分辨率实操对照

4.1 LED/RGB灯调光:最少需要多少位

LED调光是PWM分辨率问题最直观的展示场。8位PWM能不能用?答案是能亮,但体验很一般。256档里,低亮度区间每档相差接近一倍亮度,暗部过渡全是台阶。10位PWM也就是1024档,人眼在常规亮度下基本看不出跳变,是目前性价比最高的选择。12位及以上一般用于专业灯光、摄影补光这类需要极平滑调光的设备。

RGB混色对分辨率的要求更高。因为最终颜色是三路光的乘积效果,如果某一路在某个区间的步进太粗,色温会明显偏移。我之前做调光固件时,先计算目标亮度和目标色温,反解出RGB三路占空比,然后直接写CCR。看起来天经地义,实际效果却是低亮度区间偏色严重,原因是8位档位根本承载不了分解出来的小数占空比,量化误差在红色通道被放大了。

后来我改成了两件事:第一,把PWM档位从256提升到接近2000档,也就是把ARR从255改成1999,同时提高定时器时钟;第二,所有color算法先量化到“当前分辨率下可表示的值”,再去做Gamma校正,避免在浮点域算完、写入前被截断产生不可控的量化偏差。

还有一个特别容易犯的错:有人为了追求高分辨率,把ARR设成非常大的值,却忘了PWM频率会随之下降。LED调光如果低于20kHz,线圈电感或LED驱动会发出可闻噪声,人也可能感觉到频闪。所以LED的实际参数应该这样算:先定下25kHz这样无频闪的频率,再反推在目标主频下能拿到多少档。如果档位不够,再降频率或换更高主频芯片。

4.2 舵机控制:50Hz下你需要多少步

舵机控制是另一个经典案例。大多数舵机用50Hz的PWM信号,周期是20ms,其中1ms对应一个方向极限,2ms对应另一个方向极限,1.5ms是中位。

表面上看,50Hz频率很低,分辨率应该随便都很高。我们来算一下:假如定时器时钟是1MHz,PSC设成71对应72MHz主频,那么一个计数就是1us,一个20ms周期对应20000个档位。1000us到2000us的控制脉宽里,可调节的档位数是1000档,换算成角度,在180度行程下,每档大约0.18度。这对于绝大多数舵机来说已经足够平滑。

但如果你图省事,直接拿8位PWM去控制舵机,也就是周期被切成256档,那么每个档位大约是78us脉宽。映射到舵机角度,一个档位变化就是大约14度,舵机会在几个离散角度之间跳来跳去,完全没法用。就算用10位PWM,一个档位也有19.5us,约3.5度跳变,手感依然粗糙。

所以舵机应用对分辨率的需求不是“周期多少份”这种抽象数字,而是“每档脉宽变化要小于舵机本身的分辨率”。工业舵机控制一般要求脉冲宽度能按1us甚至更细调整,也就是时间颗粒必须等于或小于1us。这意味着计数时钟至少要在1MHz以上,同时PWM周期档位至少20000档。这个结论对选型有直接指导意义:如果你用的芯片定时器时钟连30MHz都不到,还要分频,那能得到的脉宽步进可能就有好几微秒,再好的舵机也被你调出顿挫感。

4.3 电机/PID调速:量化误差是如何伤到PID的

直流电机的PID调速,是PWM分辨率问题暴露最隐蔽的领域。从现象上看是转速不稳、低速顿挫,甚至PID输出一直在抖动,但算法换了一版又一版都没改善。

问题出在占空比档位太粗。假设PWM分辨率只有8位,那控制输出只有256个离散档位。PID计算出来的输出可能是0.7318这种小数占空比,但最终只能拿到0.73或0.74对应两档。于是PID输出在小数附近反复横跳,反映到电机上就是电压的小幅周期性波动。

尤其在低速和轻载工况下,电机需要的占空比很小,比如5%。在8位分辨率下,5%占空比对应的档位是12.8附近,你只能输出12/256或者13/256,两者差距约4%的相对电压变化,电机转速自然一颤一颤的。

解决思路有两个方向:一是提高PWM分辨率,让占空比档位更细,量化误差摊薄到可接受范围;二是在PID输出层加适当的死区或者滞回,避免让PID去追逐离散档位。但根本办法还是前者。我自己的经验是,电机速度环至少要确保占空比档位数在10000以上,折算下来约14位,这样量化噪声对速度环的影响才能被忽略。

另外要注意,高分辨率往往对应较低的PWM频率,而电机应用通常希望PWM频率高于人耳范围并且落在电感纹波可接受的区间。比如10kHz到25kHz很常见。这时候对定时器时钟的要求会非常高,因为你要在25kHz下还要保留10000档以上。拿72MHz主频来算,25kHz对应ARR+1=2880,只有约11.5位。想要14位,就需要接近250MHz的定时器时钟。所以你会发现,高性能电机控制平台的主频普遍很高,不是算力不够,而是外设分辨率在逼着主频往上走。

4.4 BLDC/FOC和三相六路PWM:分辨率与故障保护的关系

三相逆变控制是PWM外设能力最密集的战场。这里不光要PWM分辨率高,还要有互补输出、死区生成、故障刹车、ADC联动等一系列功能,一起配合才能安全稳定运行。

六路PWM由三组互补对组成,每组上下桥臂之间必须有死区。分辨率在这里的体现,除了控制精度本身,还影响死区时间的设置粒度。如果死区只能按几个微秒的粗粒度配置,要么太大浪费有效占空比,要么太小让功率管工作在危险边缘。高级定时器能按一个计数时钟的粒度来配置死区,这就是外设分辨率的另一种表现。

FOC控制里通常用中心对齐PWM,配合在PWM中点触发的ADC采样。因为中心对齐模式的比较事件在周期内是对称分布的,电流纹波在波峰和波谷处也对称,此时采样能抓到相电流的平均值,谐波误差最小。所以很多芯片手册里会把“中心和边沿对齐PWM+ADC同步触发”放在一起,不是偶然。

故障保护同样跟分辨率有关系。像STM32的高级定时器有刹车输入,能在外设检测到过流、过压时立即封锁PWM输出,把引脚变为安全电平。这个功能必须在初始化阶段就配好,并且要明确刹车后引脚输出的电平极性。我在现实项目里见过因为刹车极性配反,故障发生后输出反而变成导通电平,等于把保护机制变成了损坏机制。另一个常见问题是,有人把刹车中断放在中断服务函数里做软件封锁,结果中断响应延迟已经足够让功率管烧一次。正确做法一定是硬件级快速关断,软件只负责记录故障原因和做恢复策略。

如果你想深入分析H桥和三相逆变PWM的数学关系,核心其实就是叠加多个PWM信号形成不同的有效电压矢量,而每个矢量的精度都由PWM分辨率先天决定,整个控制环的精度上限从这一层就锁死了。

4.5 PWM DMA:用带宽换更细的波形控制

如果需要在每个PWM周期都更新一次占空比,CPU直接写CCR会占用太多时间,尤其在高分辨率、高频率下。这时候通常会使用PWM DMA,让DMA自动把比较值数组写入定时器CCR,实现逐周期更新,从而生成任意波形、软启动曲线或者更平滑的SPWM。

但DMA方案里有个容易被忽视的分辨率陷阱:DMA缓冲区里每个数据的位宽必须与定时器CCR寄存器位宽匹配。如果你的DMA配置成8位传输,而CCR是16位寄存器,那么高字节永远不会被写入,哪怕ARR很大,实际能表达的档位也只剩256个,分辨率直接从14位掉到8位。检查DMA数据宽度应该成为排查PWM精度问题的标准动作之一。

DMA的另一个限制是更新频率。假如PWM频率是25kHz,每个周期更新一次CCR,那么DMA每40us要搬移一次数据。如果每次搬移还要经过总线仲裁、中断等环节,数据可能在下一次周期开始前还没写入成功,结果就是某些周期用的还是旧值。高分辨率DMA通常要求定时器触发事件直接连到DMA请求,不经过CPU,否则延迟不可控。

我调过一款用DMA做LED渐变的产品,频率设成20kHz,三通道同时用DMA更新,结果低亮度区明显出现非预期频闪。排查到后面发现,DMA请求优先级和总线带宽被其他外设占用,导致三路CCR写入时间错开,一个颜色周期内RGB三路的更新发生了相位偏差。把DMA优先级提到最高,并将三路更新放在同一个DMA数据块里,问题才消失。这里的关键教训是,分辨率提升后,数据搬运的时序精度也会变成新的瓶颈。

5. 实际工程中的参数计算与调优记录

5.1 先定频率还是先定分辨率

很多新手拿到需求就直接打开CubeMX或者寄存器模板,随手填一个PSC和ARR,理由是“反正能出PWM就行”。这种做法在控制精度要求不高的场景可能无所谓,但一旦要求提上来,就得按下面这个顺序做前期的参数规划:

第一,根据应用场景确定PWM频率。LED和电源不希望有可闻噪声,一般取20kHz以上;电机PWM频率要结合电感和开关损耗,常在8kHz到25kHz区间;舵机一般就用50Hz左右。如果这个频率没定准,后续所有计算都会跑偏。

第二,再确定目标分辨率。LED调光至少10位,好的要12位;舵机控制最好脉宽步进不超过1us;电机速度环至少14位。得到目标位数后,换算成最小需要的ARR+1,比如10位是1024,12位是4096,14位是16384。

第三,联立两个条件求解所需定时器时钟:

f_clk_min = f_PWM × (ARR + 1)

如果你的定时器时钟低于f_clk_min,那就只能降低PWM频率、降低目标分辨率、换高主频芯片,或者用中心对齐模式多换1位分辨率,四选一。这里我再强调一次,这个公式里没有PSC的位置,因为PSC只会把计数时钟变小,不会提高分辨率。

我自己的习惯是把整个计算过程直接用注释写在代码里,这样半年后回来看项目还能想起当初为什么选这组参数。很多看似随机的初始化参数,其实背后都该有一笔清晰的计算账。

5.2 一个完整的RGB调光验收案例

下面用一个我实际做过的RGB灯调光固件来演示参数计算和问题处理。

项目需求是:三通道RGB调光,PWM频率25kHz,目标分辨率不低于10位,低亮度区平滑无肉眼可见跳变,支持Gamma校正。

确定参数:芯片定时器时钟84MHz,PSC设成0,这样时间颗粒是1/84MHz约11.9ns。为了满足25kHz频率,需要:

ARR + 1 = 84MHz / 25kHz = 3360

所以ARR=3359,对应log2(3360)约11.7位分辨率,满足10位要求,也留了一些裕量。这个配置下,最小占空比步进约0.03%,对LED调光来说足够。

初始化代码核心部分如下:

// 以STM32 HAL为例,假设使用TIM1,时钟84MHz TIM_HandleTypeDef htim1; TIM_OC_InitTypeDef sConfigOC; htim1.Instance = TIM1; htim1.Init.Prescaler = 0; // PSC = 0,时间颗粒约11.9ns htim1.Init.Period = 3359; // ARR = 3359,档位数3360 htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; // 中心对齐,每个周期两次计数 htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&htim1); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; // 初始占空比0 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); // 务必开启预装载,避免周期中间改CCR造成毛刺 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);

注意这里我用了中心对齐模式,实际PWM输出频率是84MHz/(2×3360)=12.5kHz,不是预期的25kHz。如果你想在中心对齐模式下得到25kHz输出,需要把ARR再缩小一半,约1679,分辨率掉到约10.7位。这个取舍必须根据实际需求确认,不要抄完代码才发现频率对不上。

调测过程中,我将ARGB8888颜色值映射到三通道PWM占空比,整个过程分成四步:颜色解算、伽马校正、范围映射、写入CCR。一开始直接按浮点运算再写入,低亮度区还是出现偏色,后来我在伽马校正后先按当前分辨率做量化,也就是把浮点值转成整数档位,再去做范围映射,偏色问题基本消失。原因是浮点运算结果如果不预先量化,每次刷新时映射到整数档位可能会有不同的舍入结果,累积起来就让三通道的平衡被打破。

实测下来的参数总结如下表:

项目原方案调整后方案
PWM频率约12kHZ(中心对齐)25kHz(边沿对齐)
ARR33593359
档位数33603360
实际分辨率约11.7位约11.7位
低亮度区肉眼台阶明显基本消失
三通道一致性低亮度偏色量化后一致

本来还想过上12位分辨率,但25kHz下12位意味着ARR+1=4096,需要的定时器时钟是4096×25k=102.4MHz,当前芯片84MHz不够。除非改用中心对齐模式并接受12.5kHz频率,或者换更高主频芯片,否则11.7位就是这个硬件平台在25kHz频率下的实际上限。

5.3 树莓派软件PWM和硬件外设的精度差距

树莓派的GPIO可以非常方便地输出PWM,社区里也有各种现成库,很多原型项目直接拿它去控制舵机、RGB灯带。但如果你认真测一下波形,就会发现软件PWM和硬件定时器PWM的差距远超想象。

树莓派自带硬件PWM的通道数量极其有限,通常只有两路。当你需要三路以上PWM,比如RGB全彩调光,很多人就只能用软件方式模拟,靠系统定时器切换GPIO高低电平。问题在于,Linux不是实时系统,线程调度、中断处理、系统负载都会影响GPIO翻转的时机。我实测过在树莓派上用软件PWM输出1kHz信号,脉冲宽度的抖动通常在几十微秒级别,换算成占空比误差,在最坏情况下能达到几个百分点。这还只是在轻载状态下,如果系统同时跑着网络、显示、存储任务,波形会更加惨不忍睹。

所以我的结论比较直接:树莓派这类平台,能用硬件PWM就用硬件PWM;如果通道数不够,要么接一片PWM驱动芯片,用I2C或者SPI控制,要么降低精度要求,只做指示类应用,别拿去做精密调光、舵机、电机控制。控制精度这种事,没有实时性的软件模拟永远干不过硬件外设。

5.4 上电瞬间和复位瞬间的外设输出电平

有一个情况比PWM分辨率更基础,但在实际项目中经常踩,就是上电瞬间引脚的电平状态。MCU复位期间,大部分引脚处于高阻输入状态,外部上拉或下拉会决定此时输出到后级电路的电平。如果这个电平恰好让功率管导通、LED点亮、舵机打到极限位置,就可能产生上电浪涌、机构冲击甚至烧毁风险。

我在一块驱动板上遇到过:单片机还没开始跑PWM,MOS管驱动输入端已经被外部上拉拉到了高电平,导致上电瞬间两个下管直接导通,电源被短路。后来在硬件上加了下拉电阻,同时配置定时器刹车功能,让PWM在初始化完成前保持输出禁止状态,问题才解决。

软件上能做的是把PWM初始化放在系统最前面,并且在正式开启输出前先把CCR设为0、刹车或者禁用主输出。如果你用的芯片支持PWM故障保护,建议把故障保护配置成上电默认使能,等一切就绪再解除故障状态。这虽然是“上电逻辑”问题,但它对“实际控制精度”的影响是决定性的,因为一旦上电瞬间烧了器件,后面什么分辨率都没有意义了。

6. 常见问题速查与避坑手记

我在不同平台上零零散散排过很多PWM相关的bug,这里整理成一张速查表,基本都是能直接对照排查的内容。

现象最可能的原因检查方向
占空比改了但输出完全不变CCR写入失败或DMA位宽不对检查寄存器地址、DMA数据宽度、预装载是否生效
低亮度区出现明显亮度台阶分辨率不足,档位数太少重新按f_clk/(f_PWM)计算ARR,提高ARR
呼吸灯过渡不平滑,像在跳变目标曲线未按分辨率量化,或者预装载未开启先量化到档位再写入CCR,开启预装载
电机低速一顿一顿占空比档位太粗,PID在量化台阶间反复提高PWM分辨率,必要时对PID输出加滞回
波形中间出现毛刺或异常窄脉冲周期中间修改CCR,且未开启预装载开启预装载,让CCR在溢出事件统一更新
互补PWM输出直通炸管死区时间太小甚至为0重算死区,匹配功率管开关参数
小占空比下输出非线性死区吞掉了过窄的有效脉宽降低死区,或者避免在接近0%占空比的区域工作
PWM频率正确但实际周期抖动明显使用了内部RC时钟或电源纹波过大换外部时钟,加强电源去耦
上电瞬间外设动作异常复位期间引脚电平不受控在硬件上加电阻确定复位电平,软件配置刹车保护
故障时PWM没有封锁刹车极性配置错误,或者只做了软件中断封锁使用硬件级刹车,核对极性电平

最后分享几点我多年调PWM攒下的经验,每一条都是真金白银换来的。

第一,拿到需求先把频率、ARR、PSC、时间颗粒、实际分辨率这五项算清楚,写在注释里。这套计算不复杂,但能省掉后面大把排错时间。

第二,所有动态更新占空比的场景,都开预装载。除非你真的需要周期中间立即改输出,否则预装载只有好处没有坏处。

第三,死区时间的配置一定要跟功率器件的真实开关参数挂钩,不要凭感觉填。而且死区测试要覆盖最小占空比和最大占空比两端,别只测中间值。

第四,PWM故障保护一定要在初始化阶段就配置好,并且确认刹车后的输出极性和安全电平。等出事故再想起来,往往就晚了。

第五,上电瞬间的引脚电平状态要在原理图阶段就考虑,软件能做的补救是有限的。

第六,永远先确认外设时钟来自哪条总线,有时定时器时钟并不等于主频,甚至相差一倍以上。选错时钟源,所有分辨率计算都会跟着错。

根据我自己的项目经验,PWM外设分辨率表面上只是初始化参数里的一个数字,但它决定了一个控制系统的底层精度天花板。调PID、调滤波、调算法,都是在努力逼近这层天花板。如果你的低层分辨率先天不足,上层做再多优化也只是在粗颗粒上找补。反过来,把PWM分辨率计算做扎实,很多看起来“玄学”的控制问题,其实都能从根源上消失。

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

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

立即咨询