
最近做电机变频项目被朋友问AI8051U能不能出个三相SPWM例程。我一开始也犹豫因为很多人觉得51内核跑SPWM太吃力实际上AI8051U这颗芯片的PWMA外设相当能打16位计数器、三对互补PWM输出、硬件死区插入、刹车保护寄存器一应俱全拿来做三相SPWM变频驱动绰绰有余。这篇文章就记录我自己基于AI8051U的PWMA实现三相互补SPWM的过程包括外设初始化、正弦表查表策略、载波频率和死区时间怎么定以及调试中踩过的坑。适合准备用AI8051U做电机驱动、逆变器、变频电源或者单纯想把PWMA玩明白的朋友参考我会尽量把关键思路讲透方便你直接移植到自己的板子上。1. 项目背景与整体设计思路1.1 为什么选择AI8051U跑三相SPWMAI8051U是8051内核但主频和片上外设已经完全不是传统89C52那个时代的水平。它工作在1T模式主频最高可以跑到几十MHz带USB下载、硬件乘除法、甚至TFPU浮点加速单元。最关键的还是PWM外设和STC8H/STC32G系列的PWMA一脉相承支持多路互补PWM、独立死区控制、刹车输入保护、比较输出、中心对齐模式这些功能对三相逆变器来说是刚需。跑SPWM的核心任务是在每一个PWM周期里刷新三路比较值让三个通道的占空比分别跟随正弦规律变化同时彼此之间相差120度。对于8051内核来说这要求中断响应够快、PWM外设支持高频率重载否则占空比刷新不及时波形就会失真。AI8051U的PWMA配合1T流水线实测在中等载波频率下刷新三路CCR完全没有压力这是它适合做这个例程的根本原因。很多朋友可能觉得“三相SPWM不是STM32的活吗”确实STM32生态成熟但AI8051U的优势在于成本低、Keil环境熟悉、STC-ISP下载方便。对于三相风扇、水泵、小功率逆变器这类产品一颗AI8051U就能搞定控制部分不需要上ARM。所以我们下面所有设计都以AI8051U的PWMA外设为主线展开。1.2 SPWM变频驱动到底在干什么SPWM叫正弦脉宽调制本质是“用一系列宽度按正弦规律变化的PWM脉冲去等效一个正弦波”。变频驱动就是通过改变PWM输出正弦波的频率和幅值来控制交流电机的转速和转矩。三相电机需要三相对称正弦电流所以我们需要生成三路频率相同、幅值相同、但相位互差120度的正弦调制波。放在PWM域里看每一路正弦调制波就是一路占空比按正弦变化的PWM。母线直流电压经过三相桥逆变之后在电机三相绕组上得到近似正弦的相电压从而产生旋转磁场。基波频率决定电机同步转速电压幅值按比例变化是为了维持磁通恒定这就是V/F控制思想。我们的例程先做开环SPWM不搞FOC重点把三路互补信号的产生和变频逻辑跑通。1.3 为什么非要互补输出死区与防直通设计三相逆变器的每一相都由上下两个功率管组成半桥上管和下管的驱动信号必须互补。以U相为例PWMA_CH1P驱动上管PWMA_CH1N驱动下管两者逻辑上不能同时导通否则直通短路瞬间烧管子。但功率管存在开关时间光靠软件保证“逻辑上互补”并不够。比如上管还没完全关断、下管就已经开通哪怕只有几百纳秒的重叠母线电压也可能直接穿过桥臂。这时候就需要硬件死区插入在上下管切换之间强制插入一段“两个管子都关断”的盲区时间确保安全。PWMA外设内部就有死区发生器只要配置好DTG寄存器它会在互补信号切换时自动插入死区不需要软件在每个边沿去处理这就避免了51单片机软件模拟PWM最容易出现的事故。2. SPWM生成的核心细节2.1 正弦表与相位累加器三相互差120度的两种实现生成SPWM的占空比数据最常用的做法是查表法。把正弦函数预先离散成一张表运行时按索引取数值换算成PWM比较值CCR。查表法比在中断里实时算sin快得多是51内核做SPWM的基本盘。相位差120度有两种实现方式。第一种最简单粗暴准备三张正弦表分别偏移1/3周期和2/3周期来存放数据运行时就各自查各自的表。这种方式思路直白但浪费存储空间而且调制度、频率变化时三张表都要同步处理。第二种是单张正弦表配合相位累加器。我实际用的是这种方法在中断里维护一个16位累加器变量每次中断累加一个步进值phase_step再分别加上120度和240度对应的偏移量得到三相的相位索引。因为360度对应65536120度正好是65536/3约等于21845240度是43690。这么做的好处是内存省、逻辑统一、频率可以连续调节只需要中断里改动phase_step这个累加步进即可。用代码表达就是u16 acc_U phase_step; u16 acc_V acc_U 21845; u16 acc_W acc_U 43690; u8 idx_U acc_U 8; u8 idx_V acc_V 8; u8 idx_W acc_W 8;这里的右移8位相当于对一个256点的正弦表取相位索引表正好一个周期对应256个点。而21845和43690这两个固定偏移正好让三相之间保持120度电气角这是整套代码里最核心的思路。2.2 载波频率、表点数和输出频率的换算很多初学者在这里卡壳我多说几句。PWM本身有个开关频率叫载波频率比如16kHz。正弦波有个目标频率比如电机要转50Hz。每个PWM周期我们从正弦表里取一个点去刷新占空比那么一秒内取点的次数是载波频率一个正弦周期分到的点数就是载波频率除以正弦频率。如果固定正弦表点数N比如256点那么输出正弦波最高能达到的频率就是载波频率除以N。16kHz载波下256点表最高只能出62.5Hz的正弦波。如果你的目标频率范围要到400Hz要么降低查表分辨率要么用相位累加器方式让索引不严格按1递增而是按phase_step的小数步进。相位累加器方式不受“每周期必须凑整数个点”的限制因为索引是从16位累加器右移来的即使每次累加0.6个点长期来看相位也是连续的。输出频率和步进的换算公式是phase_step (f_target * 65536) / f_pwm;比如目标50Hz、载波16kHz那么phase_step 50 * 65536 / 16000 204。这个数值不要求整除频率分辨率完全够用。2.3 调制度与86.6%的电压利用率问题正弦表的输出范围决定了调制度M也就是SPWM相电压峰值与母线电压一半的比值。调制度M等于1时相电压基波峰值最大为Ud/2其中Ud是直流母线电压。三相线电压基波峰值是相电压的根号3倍所以最大线电压峰值是Ud/2乘以根号3约等于0.866倍Ud这就是大家热议的“SPWM线电压利用率最高只有86.6%”。这个86.6%不是缺陷而是正弦调制本身的数学特点。六步方波逆变能到100%但谐波很大SVPWM能接近100%同时谐波更小。在我们这个例程里调制度M就是正弦表幅值相对半占空比的比例M1对应满调制。实际电机驱动时一般会留点余量设M在0.85到0.95之间避免过调制导致波形畸变、电流谐波增大。调制度在代码里体现为对正弦表数值的缩放。我建议把正弦表存成原始128幅值0到255128是中点运行时通过amp变量缩放s16 delta (s16)(sin_tab[idx] - 128); u16 ccr mid_value ((delta * amp) 7);amp默认128就对应M1amp减半就是M0.5。这样调速和调电压可以分开做V/F控制时改amp就行。2.4 死区时间怎么定计算公式与工程整定死区时间太小防不了直通死区时间太大输出波形畸变增加、电流谐波变大、还会让有效电压降低。工程上先看功率管的开关参数比如你用的MOS管开通延迟30ns、关断延迟100ns那死区至少大于两者之差再留50%安全余量常见家用电机驱动用1us到3us比较多。PWMA的死区时间是通过BKR寄存器里的DTG位配置的具体公式在STC8H/STC32G系列手册里写得很清楚DTG的取值不同段位对应不同的死区倍率。比如不预分频的情况下死区时间近似等于DTG数值乘以PWM时钟周期。假设PWM时钟是48MHz周期约20.8ns想要1us死区DTG大约取48。如果死区还嫌不够可以用DTG高位选择更大的倍率档位拉长到几微秒。我实际调试时习惯先设大一点比如2us用示波器看上下管的栅极波形有没有同时导通区域确认安全后再逐步减小直到输出电流波形和谐波都能接受。这个“从大到小逼近”的方法比一上来就按理论值计算要稳妥得多因为你还要考虑驱动芯片的传输延迟和PCB走线带来的误差。3. PWMA外设初始化与代码实现3.1 引脚分配与最小硬件系统AI8051U的PWMA输出脚一般是P2组以及P1组具体映射要看封装和引脚切换寄存器。以典型方案来规划PWMA_CH1P/CH1N驱动U相上下桥PWMA_CH2P/CH2N驱动V相PWMA_CH3P/CH3N驱动W相。三路互补PWM经过驱动芯片IR2101或EG3002再接到三相MOSFET桥臂。驱动芯片选型方面IR2101这种自举半桥驱动芯片很常见只需要一路供电用PWM互补信号直接驱动。需要注意的是6路PWM信号进入驱动芯片之前最好串100欧姆到220欧姆的栅极电阻抑制振铃。母线电容要在功率板就近放置否则开关瞬间的电压尖峰很容易击穿MOS管。我测试时用的是一块24V直流供电的三相无刷驱动板MOS管用低电压大电流的N-MOS驱动芯片用三片IR2101。AI8051U核心板和功率板分开信号线尽量短共地必须可靠不然PWM高电平噪声会影响内部ADC和复位。3.2 PWMA寄存器配置流程寄存器配置是整个例程的地基。下面以STC8H/STC32G系列兼容的寄存器命名方式来写AI8051U的寄存器基本沿用这套体系具体名称和中断号以官方数据手册和头文件为准。第一步配置时钟和预分频。假设系统时钟48MHz边沿对齐模式想要16kHz载波那么ARR加1乘上PSC加1应等于3000。比如PSCR0ARR2999。如果PWM时钟太高导致ARR太大不好处理也可以加大预分频。代码如下PWMA_PSCRH 0x00; PWMA_PSCRL 0x00; // 预分频为0PWM时钟系统时钟 PWMA_ARRH 0x0B; PWMA_ARRL 0xB7; // ARR2999载波48M/(29991)16kHz PWMA_CR1 0x01; // 向上计数模式计数器使能第二步配置三个通道为PWM模式1。比较寄存器CCR的计数值小于ARR时输出有效电平。具体要设置CCMR1/2/3里的OCxM位同时使能CCER1里的三路输出以及三路互补输出PWMA_CCMR1 0x68; // CH1为PWM模式1具体值参考手册 PWMA_CCMR2 0x68; // CH2为PWM模式1 PWMA_CCMR3 0x68; // CH3为PWM模式1 PWMA_CCER1 0x3F; // 使能CH1/CH2/CH3及其互补输出第三步配置死区和主输出使能。BKR寄存器里同时有MOE主输出使能和DTG死区位。我设定死区对应约1us把DTG设为48附近具体数值要按自己的PWM时钟验算PWMA_BKR 0xB0; // MOE1使能主输出DTG配置死区具体位段见手册 PWMA_CCR1H 0x05; PWMA_CCR1L 0xDC; // 初始CCR1设一个中点附近值 PWMA_CCR2H 0x05; PWMA_CCR2L 0xDC; PWMA_CCR3H 0x05; PWMA_CCR3L 0xDC;这样一个基础的三相互补PWM就起来了。此时用示波器看CH1P和CH1N应该能看到一对互补且带死区的方波把另外两个通道也打开能看到三路错开120度的PWM。如果到这里正常就可以进入SPWM刷新阶段。3.3 SPWM更新中断与三相信号生成代码在PWM更新中断里刷新三路CCR是SPWM的主战场。采用边沿对齐模式时计数器上溢产生更新中断中断频率等于载波频率16kHz这个频率下8051内核处理几次查表和乘法运算没有问题。如果使用中心对齐模式注意更新事件是PWM频率的两倍中断负荷会翻倍后续调谐时要注意。中断里的完整流程是更新三个相位累加器取三个索引查正弦表按当前调制度换算成CCR值写入三个比较寄存器。参考代码volatile u16 phase_acc; volatile u16 phase_step; volatile u8 amp; // 调制度128为满调制 void PWMA_ISR(void) interrupt 13 // 中断号以实际头文件为准 { u16 accU phase_acc; u16 accV accU 21845; u16 accW accU 43690; u8 idxU (u8)(accU 8); u8 idxV (u8)(accV 8); u8 idxW (u8)(accW 8); u16 mid 1500; // ARR2999时中点比较值 s16 d; d (s16)(sin_tab[idxU] - 128); PWMA_CCR1H (u8)((mid ((d * (s16)amp) 7)) 8); PWMA_CCR1L (u8)(mid ((d * (s16)amp) 7)); d (s16)(sin_tab[idxV] - 128); PWMA_CCR2H (u8)((mid ((d * (s16)amp) 7)) 8); PWMA_CCR2L (u8)(mid ((d * (s16)amp) 7)); d (s16)(sin_tab[idxW] - 128); PWMA_CCR3H (u8)((mid ((d * (s16)amp) 7)) 8); PWMA_CCR3L (u8)(mid ((d * (s16)amp) 7)); phase_acc accU phase_step; }正弦表可以提前用PC端生成256点第i点值为128 127 * sin(2 * π * i / 256)存成const unsigned char数组。这里我把相位累加和索引计算放在中断里而phase_step和amp由主循环按目标频率更新实现变速变调。这样做的好处是中断只做机械的查表更新逻辑简单不容易出错。写CCR的时候要先写高字节再写低字节避免更新过程中计数器匹配到中间值产生毛刺。这个顺序在STC的高位字节预装载机制下尤其重要我刚开始没注意偶尔会出现一两个异常窄脉冲排查了很久才发现是写入顺序问题。3.4 V/F曲线与基础调速逻辑三相异步电机的特点是磁场由电压和频率共同决定。如果只变频不变压频率拉高后磁通会下降转矩不够频率降低时电压太高又会饱和电流飙升。所以开环SPWM驱动异步电机必须做V/F控制输出电压幅值随目标频率近似线性变化低频时再适当补偿死区和线路压降。放在例程里主循环可以根据目标频率f_target计算phase_step同时计算对应的amp。一个简单的V/F系数比如额定50Hz对应满调制度amp12815Hz对应amp约38那么可以写成f_target 30; // 目标频率单位Hz phase_step (u16)((u32)f_target * 65536 / 16000); amp (u8)((u32)f_target * 128 / 50); if (amp 20) amp 20; // 最低励磁电压补偿低频补偿可以给一个最低占空比底值防止电机低速时电压太小无法建立磁场。实际调试时可以先用示波器看三相输出线电压波形再配合电流钳表观察相电流是否接近正弦。如果电流波形有明显尖峰多半是死区补偿不足或调制度配得不合适。4. 常见问题与排查技巧实录4.1 波形异常相位差错误和极性反了怎么查我遇到的第一个问题是三路PWM相位完全一致示波器上U、V、W三通道重叠成一条线。检查思路从最简单的开始先看相位累加的偏移量是否真的写进了中断代码。我在代码里设置了21845和43690偏移但编译优化和头文件里u16变量定义不当导致加偏移后溢出结果异常三相索引差为0自然就没有相位差。第二个常见问题是某一路的P和N信号反了。PWMA的CCER寄存器里有CCxNP位可以控制互补输出的有效极性。如果你发现上管和下管的死区是“上管导通结束后立即下管开通”按逻辑没错但实际驱动波形看起来像非互补那就要查CCxNP设置。最直观的办法是把CH1P和CH1N同时接到示波器两通道开余辉显示正常的互补波形应该像两把梳子互相咬合中间有清晰死区空隙。有时候还会出现上面相正常、下面相滞后半拍的诡异波形。这种多半不是PWM外设问题而是驱动芯片某一相的上管供电没接好自举电容失效导致上管驱动波形不对。我在DEBUG板上遇到过一次排查半天最后发现是IR2101的自举电容虚焊。建议检查PWM信号之前先确认驱动芯片供电和自举电路都正常。4.2 管子发烫甚至烧毁死区与驱动电路的坑功率管发烫第一个要怀疑的就是死区时间不够。在小电流下可能看不到明显问题一旦负载加大上下管微直通导致瞬间大电流管子内部结温迅速上升。我用示波器量过不下五次死区发现理论计算值1us在某些栅极电阻和MOS管组合下仍然偏小尤其是栅极电容大、关闭时间长的管子。判断死区是否足够的经验方法让母线电压升高到正常工作电压同时用电流探头观察母线电流。如果每个PWM周期都出现一个窄而高的电流尖峰并且尖峰位置正好在开关切换点附近那大概率是死区不足。把死区时间往大调一档比如从1us调到2us再观察尖峰是否明显减小。同时注意驱动电阻也会影响开关速度栅极电阻从10欧改成100欧开关变慢所需死区时间也要跟着加大。另一个让MOS管发烫的原因是互补极性配置反了导致上下管逻辑错位实际的“死区”出现在错误的边沿上。这个问题比死区不足更隐蔽因为波形看起来也有空隙但两个管子在某个切换瞬间仍然会直通。此时用示波器直接测上管源极到下管漏极之间的压降或者用差分探头量半桥中点波形会看到异常的下冲。检查顺序依然是先恢复CCR为固定值输出再用示波器确认每一相P/N电平极性。4.3 电机嗡嗡叫却不转启动参数设置不合理电机通电之后只嗡嗡响但不转在开环SPWM驱动异步电机时很常见。原因主要有三类输出基波频率太低导致启动转矩不足调制度太低导致电机端电压太低启动瞬间频率和电压不匹配导致磁场建立失败。我的处理方法是设置一个最低启动频率比如5Hz启动时直接以5Hz对应的电压和步进输出而不是从0Hz慢慢爬。主循环里判断当前频率小于5Hz就强制为5Hz等电机建立可靠转速后再逐步升频。另外启动阶段适当增大最低励磁电压就是前面提到的amp下限帮助电机更可靠地建立磁场。如果是相序错误电机也会振动但不转或者转向不对。调换任意两相PWM输出即可改变转向。比如把CCR1和CCR2对应的两相交换电机就会反转。注意不要在带载运行时直接换相最好停机后再改。4.4 程序跑飞与中断卡死优先级和重入问题SPWM中断频率设置在16kHz时AI8051U的CPU负载通常能承受但如果主循环里有大段其它任务或者中断处理代码写得太长容易触发中断重入问题。C51编译器默认的interrupt函数不会自动重入一旦主循环或更高优先级中断里调用了与中断相同的代码段就可能造成变量冲突。我建议把相位更新需要的临时变量全部放在中断函数内部定义不要和主循环共用局部变量。相位累加器、全局的标志位都要加volatile修饰防止编译器优化导致读取不到最新值。中断里避免调用自定义延时不释放CPU的函数也不要调用printf之类重量级库函数否则载波一高就崩。如果出现PWM停止输出现象先检查是否进了看门狗复位。开看门狗后主循环必须及时喂狗而喂狗操作不能放在中断里否则主循环死循环时看门狗形同虚设。我调试时经常在修改主循环逻辑后忘记喂狗导致偶发复位PWM重新初始化后波形看起来“抖动”甚至没有输出。排除方法是在主循环最开始关狗确认系统稳定再开狗。4.5 实测排查问题速查表我把自己调试过程中遇到的问题整理成一张表方便对照排查。现象可能原因排查方法三相PWM波形完全重叠相位累加偏移丢失检查21845/43690是否真正加到累加器直接打印acc值对比上下管直通烧管死区时间太小或极性反先调大DTG到2us以上再核对CCxNP极性电机不转但有嗡嗡声启动频率太低或电压不足设置最低启动频率5Hz调低V/F曲线起点输出基频不准phase_step计算错误用示波器测U相PWM周期反向计算phase_step核对PWM中断卡死中断重入或死循环临时关中断跑主循环定位检查是否有库函数调用载波频率似有拍频表点和频率不同步用相位累加器后不会有此问题确认step是小数累加某一路上下桥臂波形缺失驱动芯片自举供电不良检查自举电容和上管电源逐路独立测试另外还有一个容易忽略的坑AI8051U的引脚切换寄存器。默认引脚和PWM外设复用位置不一定是对应的必须阅读芯片手册的引脚说明正确配置PWMA对应的引脚切换位。我第一次画板子时默认以为PWM在P2口结果发现实际上需要额外使能浪费了整整一个晚上。这个点新手特别容易踩强烈建议画板前就核对官方封装引脚表。最后分享一个小经验三相SPWM看起来代码不多真正难的是把相位关系和死区逻辑想清楚。我最开始用固定三张正弦表代码笨重还容易错后来改成单表相位累加器之后整个项目清爽了一大截调频率调电压都变成了简单的数值更新。如果你只是想快速验证PWMA输出可以先不接功率管直接让PWM输出通过电阻分压后用示波器看波形等波形对了再接驱动板和电机这样能少烧很多管子。AI8051U这个例程后面还可以扩展成SVPWM、闭环速度控制甚至配合TFPU做实时运算先把开环SPWM跑稳再慢慢往深处走。