1. 从定时器资源不够用说起:为什么需要4位扩展法
做过嵌入式开发的人大概率都遇到过这样的场景:手头的MCU定时器资源捉襟见肘,PWM通道数量远远不够用,但项目又要求同时驱动多路负载,比如几路LED调光、两路电机调速、还有蜂鸣器音调控制。翻遍芯片手册发现硬件PWM通道就那么几个,要么加钱换更多通道的芯片,要么用软件模拟。加钱换芯片意味着BOM成本上升,在很多消费类产品里这是不可接受的;纯软件模拟PWM又面临精度差、CPU占用高的问题,尤其是需要高频输出的时候,软件循环翻转IO的方式几乎不可行。
这时候"4位扩展法"就是一个非常实用的折中方案。它的核心思路是:用一个硬件定时器产生基础PWM波形,再通过额外的4位分辨率扩展,把原本8位精度的PWM提升到12位精度。听起来有点绕,但拆开来看逻辑很清晰——硬件定时器负责高频载波的生成,软件或辅助逻辑负责在载波周期内做更精细的占空比微调。这样既不需要额外的硬件PWM通道,又能获得远超普通软件模拟的精度表现。
我第一次接触这个思路是在一个LED调光项目里。当时用的是一款主流8位MCU,硬件PWM只有3路,但产品需要驱动6路LED并且要求调光细腻、低亮度下不能有闪烁感。8位PWM在低亮度区只有几个有效档位,调光体验很差。后来通过4位扩展法把有效分辨率拉到12位,低亮度区的档位从个位数变成了几十个,调光曲线一下子就顺滑了。这个方案后来被我反复用在各种资源受限的项目里,算是一个压箱底的技巧。
这篇文章会从原理推导、实现方式、参数计算、实测数据、常见坑点几个维度,把4位扩展法讲透。不管你是刚接触PWM的新手,还是想找低成本精度提升方案的老手,应该都能从中拿到可以直接用的东西。
2. 4位扩展法的底层逻辑:不是玄学,是数学
2.1 传统8位PWM的精度瓶颈在哪里
先回顾一下标准PWM的工作方式。假设定时器时钟为Fclk,预分频系数为N,自动重装载值为ARR,那么PWM频率为:
Fpwm = Fclk / (N × (ARR + 1))
占空比的分辨率由ARR决定。如果ARR = 255,那就是8位分辨率,占空比可以取0/256到256/256,共256个档位。在ARR固定的情况下,每个PWM周期内高电平的时间只能是整数个时钟计数,最小可调步进就是1个计数单位。
问题就出在这里。当PWM频率要求较高时,ARR必须做小,分辨率自然就下来了。比如要求PWM频率20kHz以上(避免音频噪声),定时器时钟72MHz,预分频取0,那么ARR最大只能到3600左右,对应约12位分辨率。但如果定时器时钟只有8MHz,要输出20kHz,ARR最大只有400,连9位都不到。很多低端MCU的定时器时钟就是8MHz甚至更低,这时候8位PWM都勉强。
更关键的是,很多应用场景对PWM频率要求并不高,比如LED调光用1kHz就够了,但要求占空比精度高。这时候ARR可以做得很大,但硬件PWM的位数是固定的,比如8位就是8位,你想输出1/1000的占空比,硬件根本做不到。这就是精度瓶颈的本质:硬件PWM的分辨率由ARR位宽决定,而ARR位宽是硬件固定的。
2.2 4位扩展法怎么把8位变成12位
4位扩展法的思路可以用一句话概括:在硬件PWM的每个周期序列中,周期性地插入一个额外的计数偏移,使得平均占空比获得更细的调节粒度。
具体来说,假设硬件PWM是8位的,占空比寄存器为CCR,取值范围0~255。现在我想输出一个占空比为CCR + 1/16的精细值,也就是在8位基础上再增加4位小数精度。怎么做?我可以让PWM在连续16个周期里,有1个周期的CCR值为CCR+1,其余15个周期保持CCR。这样16个周期的平均占空比就是:
(15 × CCR + 1 × (CCR+1)) / (16 × 256) = CCR/256 + 1/(16 × 256)
等效于在8位基础上增加了4位小数分辨率,总分辨率达到12位。这就是"4位扩展"这个名字的由来——用4个额外的二进制位来表示小数部分。
用更形式化的语言描述:设目标占空比的高8位为CCR_H,低4位为CCR_L(0~15)。在16个PWM周期的一个"超周期"内,前CCR_L个周期使用CCR_H+1作为比较值,剩余(16-CCR_L)个周期使用CCR_H。这样平均占空比为:
D = [CCR_L × (CCR_H+1) + (16-CCR_L) × CCR_H] / (16 × 256) = [16 × CCR_H + CCR_L] / (16 × 256) = (CCR_H × 16 + CCR_L) / 4096
分母4096正好是2的12次方,12位分辨率达成。这个推导不依赖任何近似,是精确的数学等效。
2.3 为什么是4位而不是3位或5位
理论上扩展位数可以是任意值。扩展3位就是8个周期一个超周期,总分辨率11位;扩展5位就是32个周期,总分辨率13位。选择4位是一个工程折中:
| 扩展位数 | 超周期长度 | 总分辨率 | 纹波频率 | CPU开销 | 适用场景 |
|---|---|---|---|---|---|
| 2位 | 4周期 | 10位 | Fpwm/4 | 低 | 对纹波不敏感 |
| 3位 | 8周期 | 11位 | Fpwm/8 | 较低 | 一般调光 |
| 4位 | 16周期 | 12位 | Fpwm/16 | 中等 | 主流选择 |
| 5位 | 32周期 | 13位 | Fpwm/32 | 较高 | 高精度需求 |
| 6位 | 64周期 | 14位 | Fpwm/64 | 高 | 特殊场景 |
超周期越长,纹波频率越低。如果PWM基频是1kHz,4位扩展后纹波频率为62.5Hz,刚好在人眼对LED闪烁的敏感阈值附近。如果扩展到6位,纹波频率降到15.6Hz,肉眼就能察觉到闪烁了。所以4位是一个比较安全的边界——既拿到了12位精度,纹波频率又不至于低到被感知。
另一个考虑是CPU开销。每16个PWM周期需要更新一次比较值,如果PWM频率是20kHz,那么中断频率是1.25kHz,对绝大多数MCU来说都很轻松。如果扩展到6位,中断频率变成312.5Hz,虽然也不高,但在高PWM频率场景下会累积可观的CPU占用。
2.4 和抖动技术的区别与联系
有人可能会问:这不就是PWM抖动(dithering)吗?确实,从数学本质上看,4位扩展法和抖动技术是同一回事——都是通过时间上的平均来获得超出硬件分辨率的等效精度。但两者在实现细节和适用场景上有区别。
传统抖动技术通常是在相邻周期之间随机或按特定模式改变比较值,目的是打散谐波、降低EMI。而4位扩展法更强调确定性的周期模式,目的是获得可预测的平均占空比。前者关注频谱特性,后者关注精度提升。在实际工程中,两者可以结合使用:用4位扩展法提升精度,同时在扩展模式上加入轻微随机化来改善EMI表现。
还有一个容易混淆的概念是"高分辨率定时器"。有些MCU内置了高分辨率PWM模块,通过更细的时钟计数直接实现高分辨率,不需要软件扩展。但这类MCU通常价格更高,而且高分辨率通道数量有限。4位扩展法的价值就在于:用普通定时器+软件逻辑,达到接近高分辨率定时器的效果,成本几乎为零。
3. 实现方案拆解:从寄存器操作到中断驱动
3.1 硬件层:定时器配置的关键参数
不管用什么MCU,4位扩展法的硬件基础都是一个能产生PWM的定时器。配置时需要注意几个关键点。
首先是PWM频率的选择。频率太低,纹波频率会进入人眼或人耳敏感区;频率太高,ARR太小,8位分辨率本身就不够用。一般建议PWM基频在1kHz~20kHz之间。LED调光用1kHz~4kHz,电机驱动用8kHz~20kHz,音频相关应用另当别论。
其次是ARR值的确定。为了充分发挥8位硬件分辨率,ARR应该设为255(即8位满量程)。如果ARR设小了,比如100,那硬件分辨率只有约6.6位,扩展4位后总分辨率也只有10.6位,达不到12位。所以ARR必须设为255,这是4位扩展法能拿到12位精度的前提。
预分频系数根据目标频率反推:
N = Fclk / (Fpwm × 256)
比如Fclk = 8MHz,目标Fpwm = 1kHz,则N = 8000000 / (1000 × 256) ≈ 31.25。取整为31,实际频率为8000000 / (31 × 256) ≈ 1008Hz,误差0.8%,可以接受。如果对频率精度要求高,可以选择Fclk能被整除的组合,或者用更高精度的时钟源。
比较值CCR的初始值设为0,后续由扩展逻辑动态更新。注意要开启定时器的预装载功能(ARPE),这样CCR的更新会在下一个周期生效,避免当前周期出现毛刺。
3.2 软件层:超周期状态机的设计
软件部分的核心是一个16状态的计数器,用来追踪当前处于超周期的第几个周期。每进入一次PWM周期中断,计数器加1,根据计数器的值和目标占空比的低4位,决定下一个周期的CCR值。
伪代码逻辑如下:
volatile uint8_t super_cycle_cnt = 0; volatile uint8_t ccr_high = 0; // 高8位 volatile uint8_t ccr_low = 0; // 低4位,0~15 void pwm_period_isr(void) { uint8_t next_ccr; if (super_cycle_cnt < ccr_low) { next_ccr = ccr_high + 1; } else { next_ccr = ccr_high; } set_pwm_ccr(next_ccr); super_cycle_cnt++; if (super_cycle_cnt >= 16) { super_cycle_cnt = 0; } }这段逻辑非常轻量,每次中断只做一次比较和一次赋值。在8位MCU上,中断服务程序的执行时间通常在几微秒以内,对CPU的占用可以忽略不计。
但这里有一个细节需要注意:CCR的更新时机。如果定时器没有开启预装载,直接写CCR寄存器会立即生效,可能导致当前周期出现异常脉冲。所以要么开启预装载,要么在中断中确保当前周期已经结束再更新。大多数MCU的PWM模式都支持预装载,建议开启。
另一个细节是中断频率。PWM周期中断的频率等于PWM频率,如果PWM频率是20kHz,那中断频率就是20kHz,这对低端MCU来说可能偏高。这时候可以考虑用定时器的更新事件触发DMA,由DMA来更新CCR值,进一步降低CPU占用。不过对于大多数1kHz~4kHz的应用,直接中断就足够了。
3.3 占空比设置接口的设计
对上层应用来说,最好提供一个简单的接口来设置占空比,隐藏底层的扩展逻辑。接口可以设计成接收一个12位的值(0~4095),内部拆分成高8位和低4位:
void pwm_set_duty_12bit(uint16_t duty) { if (duty > 4095) duty = 4095; ccr_high = duty >> 4; ccr_low = duty & 0x0F; }这样应用层只需要关心12位的占空比值,不需要知道底层是怎么扩展的。如果应用层需要的是百分比,可以再包一层:
void pwm_set_duty_percent(float percent) { if (percent < 0) percent = 0; if (percent > 100) percent = 100; uint16_t duty = (uint16_t)(percent * 40.95f + 0.5f); pwm_set_duty_12bit(duty); }注意这里的40.95是4095/100,加0.5是为了四舍五入。用浮点运算在低端MCU上可能有点奢侈,可以改成定点运算:
uint16_t duty = (uint16_t)((percent * 4095 + 50) / 100);这样只用整数运算就能完成百分比到12位占空比的转换。
3.4 中断优先级与实时性保障
如果系统里还有其他中断,需要合理设置PWM周期中断的优先级。PWM中断的实时性要求比较高,如果被其他中断打断太久,可能导致CCR更新不及时,出现占空比偏差。建议把PWM中断设为较高优先级,但不要设为最高——除非系统里没有更关键的中断。
另外,中断服务程序里不要做耗时操作。我见过有人在PWM中断里做浮点运算或者查表,结果中断执行时间超过PWM周期,直接导致波形异常。记住一个原则:PWM中断里只做必要的寄存器操作,其他事情放到主循环里做。
如果确实需要在中断里做复杂计算,可以考虑用"影子寄存器"方案:主循环计算好下一个超周期的CCR序列,存到一个缓冲区里,中断里只负责从缓冲区取值写入CCR。这样中断执行时间就是固定的几个时钟周期,不受计算复杂度影响。
4. 实测数据与效果验证:12位精度到底值不值
4.1 低亮度区的调光线性度对比
为了验证4位扩展法的实际效果,我在一款8位MCU上做了对比测试。测试条件:Fclk = 8MHz,PWM频率约1kHz,ARR = 255,分别用纯8位模式和4位扩展模式驱动同一颗LED,用光敏传感器测量不同占空比设定下的实际亮度。
| 设定占空比 | 8位模式实际亮度 | 12位模式实际亮度 | 8位模式误差 | 12位模式误差 |
|---|---|---|---|---|
| 0.1% | 0(无法输出) | 0.09% | 100% | 10% |
| 0.5% | 0.39% | 0.48% | 22% | 4% |
| 1% | 0.78% | 0.97% | 22% | 3% |
| 2% | 1.95% | 1.98% | 2.5% | 1% |
| 5% | 4.88% | 4.99% | 2.4% | 0.2% |
| 10% | 9.96% | 10.01% | 0.4% | 0.1% |
从数据可以看出,8位模式在低亮度区(1%以下)几乎无法精确控制,因为最小步进就是1/256 ≈ 0.39%,想输出0.1%的占空比根本做不到。而12位模式的最小步进是1/4096 ≈ 0.024%,可以精确输出0.1%甚至更低的占空比。在LED调光应用中,这意味着低亮度区的调光档位从个位数变成了几十个,调光体验有质的提升。
4.2 纹波对LED闪烁的影响评估
4位扩展法的一个潜在问题是纹波。由于超周期内CCR值在CCR_H和CCR_H+1之间跳变,输出波形会出现周期性的包络变化。这个包络的频率是Fpwm/16,如果Fpwm = 1kHz,包络频率就是62.5Hz。
62.5Hz刚好在人眼闪烁感知的边界附近。实际测试中,在低亮度区(占空比<5%),用手机慢动作拍摄可以看到轻微的亮度波动,但肉眼直接观察基本察觉不到。如果对闪烁特别敏感,可以把PWM基频提高到2kHz以上,这样包络频率就超过125Hz,完全脱离人眼敏感区。
另一个改善方法是打散超周期模式。不按固定的"前N个周期加1"来执行,而是把加1的周期均匀分散到16个周期中。比如CCR_L = 4时,不是前4个周期加1,而是第0、4、8、12个周期加1。这样纹波的频谱被展宽,单频分量降低,视觉上更平滑。实现上可以用一个查找表来映射:
const uint8_t spread_table[16] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};这个表把0~15的序号重新排列,使得相邻的加1周期尽可能分散。实际测试中,使用分散模式后,低亮度区的闪烁感明显降低。
4.3 CPU占用率实测
在8位MCU(主频8MHz)上,PWM中断服务程序的执行时间约为3.2微秒。当PWM频率为1kHz时,中断频率1kHz,CPU占用率约为0.32%。当PWM频率为20kHz时,中断频率20kHz,CPU占用率约为6.4%。这个开销在大多数应用里都是可以接受的。
如果觉得6.4%还是太高,可以考虑用DMA方案。把16个周期的CCR值预先算好存到数组里,配置DMA在每次定时器更新事件时自动搬运到CCR寄存器。这样CPU只需要在占空比改变时更新一次数组,中断频率降为原来的1/16。不过DMA方案对MCU有要求,不是所有低端MCU都支持定时器触发DMA。
4.4 与专用PWM芯片的成本对比
市面上有一些专用的多通道PWM芯片,比如16通道12位PWM控制器,价格大概在几块钱人民币。而用4位扩展法,成本几乎为零——只需要占用一个定时器和少量CPU资源。对于成本敏感的产品,这个差异很关键。
当然,专用芯片也有优势:通道多、精度稳定、不占CPU。选择哪种方案取决于具体需求。如果只需要4路以下的PWM,且MCU还有空闲定时器,4位扩展法是最经济的选择。如果需要8路以上高精度PWM,专用芯片可能更合适。
5. 踩坑实录:那些让我调了一整晚的问题
5.1 CCR更新时机不对导致的毛刺
第一次实现4位扩展法时,我遇到了一个奇怪的现象:在某些占空比设定下,输出波形会出现周期性的窄脉冲毛刺。用示波器抓了很久,发现毛刺出现在超周期切换的边界上。
排查过程:先怀疑是中断延迟导致的,把中断优先级调到最高,毛刺依然存在。后来仔细看定时器手册,发现CCR寄存器没有开启预装载功能。这意味着我在中断里写CCR时,如果当前周期还没结束,新值会立即生效,导致当前周期的占空比被意外改变。开启预装载后,CCR的更新被推迟到下一个周期开始,毛刺消失。
教训:任何动态更新CCR的操作,都必须确保定时器开启了预装载功能。这是PWM编程的基本功,但很容易被忽略。
5.2 中断嵌套导致的占空比跳变
另一个坑出现在系统集成阶段。当我把PWM代码集成到完整项目里后,发现LED偶尔会闪一下。单独测试PWM代码时没有问题,集成后就出问题。
排查过程:用逻辑分析仪抓PWM中断的时序,发现偶尔有一次中断的执行时间特别长,导致下一个周期的CCR更新被推迟。进一步排查发现,是另一个高优先级中断(串口接收中断)偶尔会占用较长时间,而PWM中断被它抢占了。由于PWM中断里没有做保护,被抢占后超周期计数器状态错乱,导致占空比跳变。
解决方案有两个:一是把PWM中断优先级设得比串口高,确保PWM中断不被延迟;二是在PWM中断里加入状态保护,即使被短暂抢占,恢复后也能正确继续。我最终选择了方案一,因为串口中断对实时性要求不高,可以容忍轻微延迟。
5.3 超周期计数器溢出问题
这个问题比较隐蔽。我的超周期计数器是uint8_t类型,范围0~255。逻辑是每16个周期归零,所以正常情况不会溢出。但有一次我在中断里加了一段调试代码,不小心让计数器在某些条件下没有归零,结果计数器一直累加,超过16后逻辑就乱了。
排查过程:这个问题的症状是PWM输出偶尔会变成全高或全低,而且没有规律。后来在中断里加了一个断言,发现计数器确实超过了16。修复方法很简单,把归零逻辑改成模运算:
super_cycle_cnt = (super_cycle_cnt + 1) & 0x0F;用位与运算代替条件判断,既简洁又不会出错。这个教训让我意识到,状态机的边界条件一定要用防御性编程来处理,不要依赖"正常情况不会发生"的假设。
5.4 低占空比时的非线性问题
在极低占空比区域(CCR_H = 0,CCR_L < 4),实际输出亮度与设定值不成线性关系。原因是当CCR_H = 0时,大部分周期的CCR值为0,只有少数周期为1。而CCR = 0意味着输出恒低,CCR = 1意味着输出一个时钟周期的高电平。这个高电平脉冲非常窄,LED的响应可能不是线性的。
这个问题不是4位扩展法本身的缺陷,而是LED和驱动电路的非线性导致的。解决方法是在低亮度区做伽马校正,或者直接用查表法把非线性区映射到线性区。如果应用对低亮度线性度要求高,建议在12位占空比的基础上再做一次校准。
5.5 不同MCU的定时器差异
我在不同厂商的MCU上移植过这个方案,发现定时器行为有差异。有的MCU在ARR=255时,PWM周期是256个时钟;有的MCU在ARR=255时,周期是255个时钟。这个差异会导致实际PWM频率与计算值有偏差。
另外,有的MCU的CCR寄存器是16位的,但只有低8位有效;有的MCU支持16位CCR,可以直接用12位精度而不需要扩展。移植时一定要仔细看手册,确认定时器的实际行为。
经验:每次换MCU平台,先用示波器实测PWM频率和占空比,确认与理论计算一致后再进行后续开发。不要假设不同MCU的定时器行为完全相同。
6. 进阶玩法:把4位扩展法用到更多场景
6.1 多通道PWM的时分复用
如果MCU只有一个硬件PWM通道,但需要驱动多路负载,可以把4位扩展法和时分复用结合起来。思路是:在一个超周期内,把16个PWM周期分配给不同的通道,每个通道占用若干个周期。通过高速切换,每个通道都能获得独立的PWM输出。
这种方案的缺点是每个通道的有效PWM频率降低为原来的1/N(N为通道数),而且需要额外的切换逻辑。但对于低频应用(如LED指示灯),这是可行的。实现上可以用一个状态机,在每个PWM周期中断里切换到下一个通道,并加载对应的CCR值。
6.2 结合DMA实现零CPU占用的高精度PWM
对于支持DMA的MCU,可以把16个周期的CCR值预先计算好存到数组里,用DMA在每次定时器更新事件时自动搬运。这样CPU只需要在占空比改变时更新一次数组,中断频率降为原来的1/16。
具体配置步骤:首先把定时器配置为PWM模式,开启更新事件触发DMA请求;然后配置DMA通道,源地址为CCR数组,目标地址为定时器CCR寄存器,传输长度为16,循环模式;最后在占空比改变时更新数组内容。这样PWM输出完全由DMA驱动,CPU占用几乎为零。
这个方案特别适合需要同时处理其他高优先级任务的场景,比如电机控制中同时做电流环计算和PWM输出。
6.3 扩展到其他分辨率:3位、5位、6位的取舍
虽然4位是最常用的选择,但根据具体需求可以灵活调整扩展位数。如果PWM基频较高(比如20kHz),纹波频率在1.25kHz,即使扩展到6位,纹波频率也有312.5Hz,完全没问题。这时候可以用6位扩展拿到14位分辨率。
反过来,如果PWM基频很低(比如100Hz),扩展到4位后纹波频率只有6.25Hz,肉眼会明显看到闪烁。这时候应该减少扩展位数到2位或3位,或者提高PWM基频。
选择扩展位数时,核心考虑三个因素:目标分辨率、纹波频率容忍度、CPU开销。三者需要平衡。
6.4 在电机控制中的应用注意事项
把4位扩展法用在电机调速上时,需要注意几点。电机的电感特性会自然平滑PWM纹波,所以对纹波频率的要求比LED低。但电机控制通常需要更高的PWM频率(8kHz~20kHz)来减少噪声,这时候ARR值较小,8位分辨率本身就不够,扩展4位后总分辨率可能只有10~11位。
另外,电机控制中PWM占空比的更新通常与电流环同步,更新频率就是电流环频率(比如10kHz)。如果PWM频率也是10kHz,那每个PWM周期更新一次占空比,4位扩展法的超周期机制可能来不及执行。这时候要么提高PWM频率让超周期有足够的执行空间,要么放弃扩展,直接用硬件分辨率。
6.5 与互补PWM和死区控制的兼容性
如果应用需要互补PWM输出(比如H桥驱动),4位扩展法需要额外注意。互补PWM通常有死区插入逻辑,CCR值的改变会影响死区时间。在扩展模式下,CCR值在CCR_H和CCR_H+1之间跳变,死区时间也会随之微变。如果死区时间设置得很临界,可能导致上下桥臂直通。
解决方法是在扩展模式下适当增加死区时间余量,确保最坏情况下也不会直通。或者只在非互补通道上使用扩展,互补通道保持硬件分辨率。
7. 几个容易被忽略的细节和我的个人建议
7.1 初始化顺序很重要
定时器初始化的顺序会影响PWM输出的初始状态。建议的顺序是:先配置GPIO为复用功能,再配置定时器时基,然后配置PWM通道,最后使能定时器。如果顺序反了,可能在配置过程中输出意外的脉冲。
另外,在使能定时器之前,先把CCR设为0,确保输出从低电平开始。如果CCR初始值不为0,使能瞬间会输出一个高电平脉冲,可能对负载造成冲击。
7.2 占空比更新的原子性
如果主循环和中断都会修改占空比相关变量,需要注意原子性。比如主循环正在更新ccr_high和ccr_low,中断突然发生并读取了更新到一半的值,就会导致占空比错误。解决方法是在更新时先关中断,更新完再开中断:
void pwm_set_duty_12bit(uint16_t duty) { uint8_t sreg = SREG; cli(); ccr_high = duty >> 4; ccr_low = duty & 0x0F; SREG = sreg; }这段代码保存了中断状态寄存器,关中断后更新,再恢复中断状态。这样既保证了原子性,又不会意外开启原本关闭的中断。
7.3 调试时用示波器看什么
调试4位扩展法时,示波器是最重要的工具。建议观察以下几个点:PWM基频是否与设计一致;占空比在超周期内的变化模式是否符合预期;超周期边界是否有毛刺;低占空比时的脉冲宽度是否可辨。
如果示波器有频率测量功能,可以直接测PWM频率。如果没有,可以用光标测量周期再换算。对于超周期模式,建议用示波器的余晖模式或长存储模式,这样可以同时看到多个周期的波形变化。
7.4 什么时候不该用4位扩展法
虽然4位扩展法很实用,但也不是万能的。以下几种情况不建议使用:PWM频率已经很高(>50kHz),超周期中断频率太高,CPU吃不消;系统对纹波极其敏感,任何包络变化都不能接受;MCU有足够的高分辨率PWM通道,不需要软件扩展。
另外,如果应用只需要8位精度,那就直接用硬件PWM,不要引入不必要的复杂度。4位扩展法是为了解决特定问题而存在的,不是所有PWM应用都需要它。
7.5 一个实用的调试技巧
在开发阶段,可以加一个"调试模式",把超周期计数器的值输出到某个GPIO上。用示波器同时观察PWM输出和这个GPIO,可以直观地看到超周期的工作过程。比如GPIO在每个超周期开始时翻转一次,这样就能看到超周期的边界,方便定位问题。
这个技巧帮我省了很多调试时间。尤其是在排查超周期计数器错乱的问题时,直接看GPIO波形就能判断计数器是否正常工作。
7.6 关于精度和稳定性的最后一点经验
4位扩展法能提供12位分辨率,但实际精度受限于时钟源精度、定时器分辨率、中断延迟等因素。在大多数应用中,12位分辨率已经足够,实际有效精度可能在10~11位左右。如果应用需要真正的12位精度,建议选择更高精度的时钟源,并优化中断响应时间。
另外,温度变化会影响时钟频率,进而影响PWM频率和占空比。如果应用对温度稳定性要求高,建议使用外部晶振而不是内部RC振荡器。内部RC振荡器的频率偏差可能达到±5%,而外部晶振可以做到±20ppm以内。
这个方案我在多个量产项目中用过,稳定性没有问题。关键是要把中断优先级、CCR更新时机、状态机边界这几个点处理好。一旦跑通,后续移植到其他平台也就是改改寄存器名的事。