
1. 为什么STC8H的高级PWM值得单独拿出来讲如果你之前只用过STC89C52或者STC15系列做过PWM调光、调速第一次接触STC8H的高级PWM模块大概率会有点懵。STC8H系列里带“高级PWM”外设的型号比如STC8H8K64U、STC8H1K28等它的PWM模块跟传统单片机那种“一个定时器配一个比较寄存器”完全不是一个量级的东西。它更像是一台小型电机控制专用外设支持互补输出、死区插入、多通道同步、刹车保护、编码器接口甚至能做三相无刷电机的六路驱动。我最初拿STC8H做的是一个直流有刷电机的调速项目本来想着随便配个PWM就完事了结果发现客户要求半桥驱动上下管不能直通必须加死区。用普通PWM靠软件延时插死区波形抖得厉害后来换成高级PWM的硬件死区波形干净得像示波器自带的标准信号。从那以后我基本把STC8H的高级PWM当成了一个独立课题来研究。这篇内容适合三类人看第一类是从STC15/STC8A迁移过来发现寄存器对不上的老玩家第二类是做电机驱动、电源变换、逆变器需要互补和死区的工程师第三类是想用一颗几块钱的国产MCU替代专用PWM控制器做多路输出的朋友。我会把互补输出、死区控制、多通道配置这三块拆开讲透寄存器怎么算、坑在哪里、示波器上看到什么波形算对全部按我实际调试的过程来写。2. 高级PWM模块的整体架构与设计思路2.1 它和普通PWM到底差在哪普通PWM的本质就是一个计数器加一个比较值计数值小于比较值时输出高大于时输出低。STC8H的高级PWM保留了这套逻辑但把它扩展成了一个“时基多通道互补死区刹车”的完整系统。你可以把它想象成一个交响乐团时基单元是指挥负责打拍子各个PWM通道是乐手按指挥的节奏演奏互补输出是成对的乐手一个出声另一个就必须闭嘴死区就是两个乐手切换时留出的那一点点静默时间防止声音撞在一起。具体到寄存器层面STC8H高级PWM的核心模块包括PWMA时基单元包含ARR自动重装载寄存器、CNTR计数器、PSCR预分频器决定PWM的频率。PWMA通道单元每个通道有CCR捕获比较寄存器、CCMR模式寄存器、CCER使能寄存器决定占空比和输出极性。死区发生器DTR寄存器为互补通道对插入死区时间。刹车单元BRK寄存器外部故障信号触发时快速封锁输出。自动重装载预装载ARPE位决定ARR写入是立即生效还是等更新事件。这套架构跟STM32的高级定时器TIM1/TIM8非常相似但STC8H的寄存器命名和位定义有自己的风格不能直接照搬STM32的代码。我见过有人把STM32的PWM初始化代码改个名字就往STC8H上套结果波形死活出不来原因就是STC8H的使能位和极性控制位分布不一样。2.2 为什么选高级PWM而不是软件模拟有人会问我用定时器中断翻转IO口也能出PWM为什么要用高级PWM这个问题我在早期也纠结过。软件模拟PWM的优点是灵活任何IO都能用缺点是占CPU、频率上不去、占空比精度受中断响应影响。我实测过用STC8H的24MHz主频定时器中断翻转IOPWM频率做到10kHz时CPU已经占了将近40%的时间而且占空比最小步进只能到1/100左右再细就抖了。高级PWM是纯硬件输出CPU只需要初始化一次之后完全不参与。频率可以做到几百kHz占空比精度取决于ARR的位数16位ARR能做到1/65536的调节精度。更重要的是互补输出和死区控制是硬件完成的软件模拟根本做不到纳秒级的死区精度。所以只要你的项目涉及半桥、全桥、三相逆变高级PWM是唯一靠谱的选择。2.3 多通道配置的两种典型场景STC8H的高级PWM通常有4个通道PWMA的PWM1P/PWM1N到PWM4P/PWM4N每个通道都可以独立配置。多通道配置有两种典型用法第一种是多路独立输出比如同时控制四路LED调光或者四路风扇调速每个通道频率相同但占空比独立。这种场景下四个通道共用一个时基ARR决定频率各自的CCR决定占空比。第二种是互补对输出比如H桥的两个上管和下管或者三相逆变器的三对桥臂。这种场景下通道1P和1N是一对2P和2N是一对硬件自动保证两者不会同时导通并且可以插入死区。我做的项目里三相无刷电机驱动用的是第二种三对互补通道加中心对齐模式波形对称性很好。而一个多路温控风扇的项目用的是第一种四路独立PWM频率统一设在25kHz避开了风扇的音频噪声频段。3. 互补输出与死区控制的核心细节3.1 互补输出的硬件逻辑互补输出的意思很简单PWM1P输出高的时候PWM1N输出低PWM1P输出低的时候PWM1N输出高。这两个信号在硬件上是互斥的不会同时为高。但“互斥”不等于“安全”因为MOS管关断需要时间如果PWM1P刚变低、PWM1N立刻变高上管还没完全关断下管就导通了电源直接短路这就是常说的“直通”或“ Shoot-through”。STC8H的互补输出通过CCER寄存器里的CC1P和CC1NP位分别控制PWM1P和PWM1N的极性。注意这两个位是独立控制的不是简单的取反关系。你可以把两个都设成高有效也可以设成一个高有效一个低有效具体取决于你的驱动电路是高端驱动还是低端驱动。我一般用IR2104这类半桥驱动芯片输入信号是高有效所以PWM1P和PWM1N都设成高有效芯片内部自己会做互斥。这里有个容易踩的坑STC8H的互补输出使能位是CCER寄存器里的CC1E和CC1NE。CC1E控制PWM1PCC1NE控制PWM1N。如果你只使能了CC1E没使能CC1NE那PWM1N根本不会输出示波器上只有一路波形。我刚开始调的时候以为互补是自动的结果找了半天才发现是使能位没开。3.2 死区时间的计算与寄存器配置死区时间是互补输出里最关键的参数。太短了起不到防直通的作用太长了会损失有效占空比、增加谐波失真。死区时间的具体数值取决于MOS管的关断时间和驱动电路的延时一般取100ns到1us之间。我用的IRF3205加IR2104组合实测死区设在300ns左右比较合适。STC8H的死区时间由DTR寄存器配置它把死区时间分成几个档位通过DTCFG位选择。具体计算公式是死区时间 (DTCFG值 1) × Tpwm_clk其中Tpwm_clk是PWM时钟周期。假设PWM时钟是24MHz那一个时钟周期约41.7ns。如果DTCFG设为6死区时间就是(61)×41.7≈292ns接近300ns。但这里有个细节STC8H的DTR寄存器还分死区预分频可以进一步放大死区时间。如果你的PWM时钟很快但需要较长的死区就要用预分频。我一般先把PWM时钟设在24MHz然后根据需要的死区时间反推DTCFG值。比如需要500ns死区500/41.7≈12DTCFG设11即可。实测的时候一定要用示波器看两路波形的死区不能只看代码算出来的值。因为驱动芯片本身也有延时PCB走线也有延时最终到MOS管栅极的死区可能跟寄存器算出来的差几十纳秒。我习惯在示波器上同时抓PWM1P和PWM1N看两个下降沿和上升沿之间的时间差调到既不直通又不明显损失占空比为止。3.3 死区插入的时机与波形分析死区插入的时机有两种一种是在互补对的切换沿都插入另一种是只在其中一个沿插入。STC8H默认是在两个沿都插入死区这样最安全。但有些应用里比如LLC谐振变换器可能只需要在特定沿插入死区这时候就要仔细看寄存器的手册说明。我用示波器抓过一组典型的互补波形PWM1P从高变低后PWM1N延迟约300ns才从低变高PWM1N从高变低后PWM1P也延迟约300ns才从低变高。两路波形在切换点都有一个明显的“死区平台”两路都是低电平。这个平台就是死区它保证了上下管不会同时导通。如果示波器上看到两路波形有重叠也就是一路还没完全变低另一路就开始变高那说明死区没生效或者太短。这时候要检查DTR寄存器是否真的写进去了以及CCER的互补使能位是否打开。我遇到过一次死区不生效的情况查了半天发现是DTR寄存器写在了PWM使能之后而STC8H要求死区配置必须在PWM输出使能之前完成。这个顺序问题手册里写得不显眼但实际调试中很容易忽略。3.4 刹车保护与故障封锁刹车功能是高级PWM的另一个安全特性。当外部故障信号比如过流比较器输出触发时刹车单元会立即封锁PWM输出让所有通道进入安全状态。STC8H的刹车输入可以映射到特定引脚触发后PWM输出被强制拉低或拉高具体取决于寄存器配置。我在电机驱动项目里一定会用刹车功能。过流信号从比较器出来接到刹车引脚一旦电流超过阈值PWM立刻封锁比软件中断快得多。软件中断从触发到执行封锁至少要几个微秒而硬件刹车是纳秒级的。对于MOS管来说几微秒的过流就可能炸管所以硬件刹车是保命的东西。刹车触发后PWM输出会被封锁但计数器还在跑。要恢复输出需要清除刹车标志并重新使能输出。我一般会在刹车中断里做故障记录然后等故障排除后手动恢复。注意刹车标志的清除顺序先清标志再重新使能否则可能清不掉。4. 多通道配置的实操过程4.1 时基单元的参数计算多通道配置的第一步是确定时基参数。STC8H高级PWM的PWM频率计算公式是PWM频率 系统时钟 / (PSCR预分频 1) / (ARR 1)假设系统时钟24MHz我想要20kHz的PWM频率ARR设成16位最大值65535那预分频就是24000000 / 20000 / 65536 ≈ 0.018这个值小于1说明不需要预分频PSCR设0即可。然后反算ARRARR 24000000 / 20000 - 1 1199所以PSCR0ARR1199PWM频率正好20kHz。ARR是16位的1199远小于65535说明占空比精度还有很大余量。如果我要更高的占空比精度可以把PSCR设大让ARR更大。比如PSCR设11ARR就是(24000000/12/20000)-199精度反而降低了。所以预分频和ARR的取舍要看具体需求要频率准就用小预分频要占空比细就用大ARR。我一般习惯把ARR设在1000到4000之间这样占空比调节比较直观比如ARR1199时CCR600就是50%占空比。如果ARR太大算占空比的时候容易算错。4.2 四个通道的独立占空比配置时基配好之后四个通道的占空比分别由CCR1到CCR4决定。每个通道的CCR可以独立写互不影响。但要注意CCR的预装载功能如果CCMR里的OCxPE位使能了预装载那CCR写入后不会立即生效要等更新事件UEV才生效。这样做的目的是防止PWM周期中间改占空比导致波形不完整。我一般会打开预装载然后在更新中断里统一更新四个CCR。这样四个通道的占空比在同一时刻生效不会出现某个通道先变另一个通道后变的情况。对于多路LED调光这种应用人眼对同步性不敏感不开预装载也没事。但对于三相电机驱动三个通道的占空比必须同步更新否则会产生转矩脉动。配置四个通道的代码大概长这样// 使能PWM1P到PWM4P输出 PWMA_CCER1 0x55; // 通道1和2使能高有效 PWMA_CCER2 0x55; // 通道3和4使能高有效 // 设置四个通道的占空比 PWMA_CCR1 300; // 通道1占空比25% PWMA_CCR2 600; // 通道2占空比50% PWMA_CCR3 900; // 通道3占空比75% PWMA_CCR4 1199; // 通道4占空比100% // 使能预装载 PWMA_CCMR1 | 0x08; PWMA_CCMR2 | 0x08; PWMA_CCMR3 | 0x08; PWMA_CCMR4 | 0x08; // 使能主输出 PWMA_BKR 0x80;这段代码里CCER1和CCER2的0x55是二进制01010101表示通道1到4的PWMxP都使能且高有效。BKR的0x80是使能主输出这个位不置1的话所有通道都不会输出。我见过有人配了半天通道就是不输出最后发现是BKR没开。4.3 互补通道对的配置差异如果要用互补输出配置就跟独立输出不一样了。以通道1为例需要同时使能CC1E和CC1NE并且配置死区。代码大概是这样// 通道1互补输出 PWMA_CCER1 | 0x01; // 使能PWM1P PWMA_CCER1 | 0x04; // 使能PWM1N PWMA_CCER1 ~0x02; // PWM1P高有效 PWMA_CCER1 ~0x08; // PWM1N高有效 // 死区配置 PWMA_DTR 0x06; // 死区时间约292ns PWMA_BKR 0x80; // 使能主输出注意CCER1的位定义bit0是CC1Ebit1是CC1Pbit2是CC1NEbit3是CC1NP。所以0x01是使能PWM1P0x04是使能PWM1N0x02和0x08分别是极性位清零表示高有效。互补输出和独立输出不能混用在同一个通道上。如果你把通道1配成互补通道2配成独立这是可以的因为每个通道的CCER位是独立的。但时基是共用的所以所有通道的频率必须相同。如果需要不同频率的PWM就得用不同的定时器STC8H的高级PWM只有一个时基做不到多频率输出。4.4 中心对齐模式与边沿对齐模式的选择STC8H高级PWM支持边沿对齐和中心对齐两种模式。边沿对齐就是计数器从0数到ARR然后归零PWM波形是左对齐的。中心对齐是计数器从0数到ARR再数回0PWM波形是对称的。边沿对齐适合普通调光调速配置简单频率计算直观。中心对齐适合电机驱动和逆变器因为对称波形的高次谐波更少对电机的噪声和损耗都更小。我用中心对齐做三相驱动时相电压波形明显比边沿对齐干净电流纹波也小一些。中心对齐模式下PWM频率的计算公式变成PWM频率 系统时钟 / (PSCR 1) / (2 × ARR)因为计数器要上下数两次才是一个完整周期。所以同样的ARR中心对齐的频率是边沿对齐的一半。配置的时候要注意这个2倍关系别算错了频率。中心对齐模式通过CR1寄存器的CMS位选择。CMS00是边沿对齐CMS01是中心对齐模式1CMS10是中心对齐模式2CMS11是中心对齐模式3。模式1/2/3的区别在于比较中断的触发时机一般用模式1就行。5. 常见问题与排查技巧实录5.1 PWM没有输出的排查顺序PWM配好了但示波器上没波形这是最常见的问题。我总结了一个排查顺序按这个顺序查基本能解决90%的情况排查项检查内容常见问题主输出使能BKR寄存器的MOE位忘记置1所有通道都不输出通道使能CCER的CCxE位只配了CCMR没配CCER引脚映射端口配置寄存器PWM引脚没设成复用功能时基使能CR1的CEN位计数器没启动预装载CCMR的OCxPE位预装载开了但没触发更新刹车状态BKR的刹车标志刹车触发后没清除我遇到最多的是两个一个是BKR的MOE没开一个是引脚没设成复用。STC8H的PWM输出引脚默认是普通IO必须通过PxM1/PxM0寄存器设成复用推挽输出。比如PWM1P在P1.0就要把P1M1的bit0清零、P1M0的bit0置1。这个配置在手册的端口章节不在PWM章节所以容易漏。5.2 死区不生效或波形异常的排查死区不生效的表现是两路互补波形有重叠或者死区时间跟设定值差很多。排查思路先确认DTR寄存器是否真的写进去了。STC8H的DTR寄存器有写保护需要先解锁才能写。我一开始不知道直接写DTR发现没反应后来查手册才发现要先写0x55和0xAA到保护寄存器。再确认死区配置的顺序。STC8H要求死区配置在PWM输出使能之前完成如果先使能了输出再配死区死区可能不生效。我现在的习惯是初始化的时候先配时基、再配通道、再配死区、最后开主输出这个顺序最稳。最后用示波器实测死区时间。如果实测值跟计算值差很多可能是PWM时钟跟预期不一致。检查PSCR和系统时钟频率确认PWM时钟到底是多少。我有一次系统时钟设的24MHz但PSCR算错了实际PWM时钟只有12MHz死区时间翻倍了。5.3 多通道同步更新的问题四个通道独立输出时如果占空比在运行中改变可能会出现通道间不同步。比如通道1的CCR先写通道2的CCR后写两个通道的占空比变化不在同一时刻。对于LED调光无所谓但对于电机驱动就会有问题。解决办法是打开CCR预装载然后在更新中断里统一更新。更新中断由UEV事件触发发生在计数器归零边沿对齐或上下数到中间中心对齐的时候。在中断里一次性写四个CCR下一个周期四个通道同时生效。void PWMA_ISR() interrupt 26 { if (PWMA_SR1 0x01) { // 更新中断 PWMA_SR1 ~0x01; // 清标志 PWMA_CCR1 new_duty1; PWMA_CCR2 new_duty2; PWMA_CCR3 new_duty3; PWMA_CCR4 new_duty4; } }注意中断号STC8H的PWMA中断号是26不是STM32的TIM1_UP。这个中断号在头文件里有定义用的时候直接写数字或者用宏都行。5.4 刹车误触发与恢复刹车功能虽然安全但也容易误触发。如果刹车引脚悬空或者受到干扰可能会随机触发导致PWM突然封锁。我在一个电机项目里遇到过刹车随机触发的问题查了半天发现是刹车引脚没有上拉环境噪声耦合进来触发了刹车。解决办法是给刹车引脚加一个上拉电阻或者在寄存器里使能内部上拉。另外刹车触发后要检查刹车标志确认是真实故障还是误触发。如果是误触发可以在软件里做滤波连续检测到多次刹车才认为是真实故障。刹车恢复的流程是先排除故障源然后清除刹车标志再重新使能主输出。注意清除刹车标志和重新使能的顺序先清标志再使能否则可能清不掉。我一般会在刹车中断里置一个故障标志主循环检测到故障标志后做处理处理完再恢复PWM。5.5 高频PWM下的MOS管发热问题PWM频率越高MOS管的开关损耗越大发热越严重。我做过一个测试同样的电机和负载PWM频率从10kHz升到20kHzMOS管温度从40度升到65度。所以PWM频率不是越高越好要权衡噪声和损耗。对于有刷电机10kHz到16kHz比较合适高于16kHz人耳听不到啸叫但损耗也可接受。对于无刷电机20kHz到30kHz比较常见因为无刷电机的电感小电流纹波大需要更高的频率来平滑电流。但频率越高死区时间占的比例越大有效占空比损失越多。我一般会把死区设在200ns到500ns之间根据MOS管的开关速度调整。另外MOS管的栅极驱动电阻也会影响发热。电阻太大开关速度慢开关损耗大电阻太小开关速度快但可能引起振荡。我一般先用10欧姆试然后用示波器看栅极波形如果上升沿有振荡就加大电阻如果上升沿太慢就减小电阻。6. 从寄存器到波形的完整调试记录6.1 一个三相驱动的实际配置案例我拿一个实际的三相无刷电机驱动项目来完整走一遍配置流程。硬件是STC8H8K64U系统时钟24MHzMOS管用IRF3205驱动芯片IR2104目标PWM频率20kHz中心对齐模式死区300ns。第一步配置时基。中心对齐模式下PWM频率24MHz/(PSCR1)/(2×ARR)。设PSCR0ARR599则频率24M/1/1198≈20.03kHz接近20kHz。ARR599意味着占空比精度是1/600对于电机驱动够用了。第二步配置三个互补通道。通道1、2、3分别对应三相的U、V、W。每个通道都要使能P和N配置死区。CCER1配置通道1和2CCER2配置通道3。PWMA_CCER1 0x11; // 通道1和2的P使能高有效 PWMA_CCER1 | 0x44; // 通道1和2的N使能高有效 PWMA_CCER2 0x01; // 通道3的P使能 PWMA_CCER2 | 0x04; // 通道3的N使能第三步配置死区。DTR寄存器设0x06死区约292ns。第四步配置中心对齐模式。CR1寄存器的CMS位设01。PWMA_CR1 | 0x20; // CMS01中心对齐模式1第五步使能主输出和计数器。PWMA_BKR 0x80; // 使能主输出 PWMA_CR1 | 0x01; // 启动计数器第六步在更新中断里更新三个CCR实现SVPWM算法。这套配置跑下来示波器上三对互补波形很干净死区稳定在300ns左右电机运转平稳电流纹波比边沿对齐模式小很多。6.2 示波器实测波形分析调试的时候我习惯用示波器抓几组关键波形。第一组是单对互补波形看死区是否正常。第二组是三对波形的相位关系看是否相差120度。第三组是相电压波形看中心对齐的对称性。单对互补波形上PWM1P和PWM1N在切换点都有死区平台两路都是低电平。死区时间用光标测量跟设定值误差在10%以内算正常。如果误差太大检查PWM时钟和DTR配置。三对波形的相位关系通道1超前通道2约120度通道2超前通道3约120度。如果相位不对检查CCR的初始值和更新时机。SVPWM算法里三个CCR是实时计算的相位关系由算法保证。相电压波形是PWM1P减去PWM1N的等效波形中心对齐模式下应该是对称的梯形波。如果波形不对称检查CMS模式是否配错或者ARR值是否在上下数的时候被改了。6.3 性能实测与优化方向这套配置在24MHz主频下PWM分辨率是1/600频率20kHz死区300ns。实测电机空载电流约0.3A满载电流约2AMOS管温度在满载运行10分钟后约55度可以接受。优化方向有几个一是提高PWM频率到30kHz进一步降低电流纹波但MOS管温度会上升二是减小死区到200ns提高有效占空比但需要确认MOS管不会直通三是用DMA自动更新CCR减轻CPU负担但STC8H的DMA能不能触发PWM更新我还没试过手册里没明确写。我个人经验是对于大多数中小功率电机驱动20kHz频率加300ns死区是一个比较稳妥的起点。先在这个参数下把系统跑通然后再根据实测的电流纹波和MOS管温度做微调。不要一上来就追求高频和窄死区那样容易炸管。7. 几个容易忽略的寄存器细节7.1 预装载的双缓冲机制STC8H的ARR和CCR都有预装载功能但两者的预装载行为不一样。ARR的预装载由ARPE位控制CCR的预装载由OCxPE位控制。ARPE使能后写ARR不会立即生效要等更新事件。OCxPE使能后写CCR也不会立即生效也要等更新事件。这个双缓冲机制的好处是防止PWM周期中间改参数导致波形异常。但坏处是如果你不知道这个机制改了ARR发现频率没变会以为寄存器坏了。我一开始就踩过这个坑后来养成了习惯改ARR或CCR之后手动触发一次更新事件或者等下一个更新中断。手动触发更新事件的方法是置CR1的UG位硬件会自动清零。这样不用等计数器跑完一个周期参数立即生效。调试的时候很方便。7.2 输出极性与驱动电路的匹配PWM输出的极性要跟驱动电路匹配。IR2104这类半桥驱动芯片输入高电平导通上管输入低电平导通下管。所以PWM1P和PWM1N都设成高有效芯片内部会做互斥。但有些驱动芯片是低有效的比如某些光耦隔离驱动输入低电平才导通。这时候就要把极性位设成低有效。极性配错的后果是上下管同时导通直接炸管。所以第一次上电的时候一定要用示波器看波形确认极性对了再接功率级。我一般会先用低压小电流测试确认波形没问题再上高压。7.3 中断标志的清除时机PWM中断有多个标志位更新中断、捕获中断、刹车中断等。每个标志位都要在中断服务程序里手动清除否则会反复进中断。清除方法是写1到对应的标志位不是写0。这个跟很多单片机不一样STC8H的中断标志是写1清零。我见过有人写0清标志结果中断一直进程序跑飞了。所以清标志的时候一定要看手册确认是写1清零还是写0清零。STC8H的PWM中断标志是写1清零这个要记住。8. 写在最后的实操体会STC8H的高级PWM模块功能很强但寄存器比较多刚开始接触容易晕。我的建议是不要一上来就配三相六路先从单路PWM输出开始把时基和通道配通看到波形了再加互补再加死区再加多通道。一步一步来每步都用示波器验证这样出了问题容易定位。另外STC8H的官方例程里有一些PWM的配置代码但例程往往只覆盖基本功能互补和死区的例子不多。我当初是照着STM32的高级定时器思路去理解STC8H的寄存器发现很多概念是相通的只是位定义和寄存器名字不同。如果你有STM32的基础迁移过来会快很多。最后分享一个我常用的调试技巧把PWM输出接到一个RC低通滤波器上用万用表测电容上的电压这个电压就是PWM的平均值也就是占空比乘以电源电压。这样不用示波器也能快速验证占空比对不对。RC的时间常数要远大于PWM周期比如PWM频率20kHzRC取10k加1uF时间常数10ms远大于50us的周期滤波效果很好。这个方法在我没有示波器的时候帮了大忙简单粗暴但有效。