☰
AI8051U三相SPWM变频驱动实战:从PWMA配置到死区踩坑
2026/10/3 14:56:38 网站建设 项目流程

最近在调AI8051U的PWM外设,把三相SPWM变频驱动整套流程跑通了。这个例程用PWMA的3个通道做三相互补输出,相位依次差120°,配合硬件死区时间直接驱动三相全桥,异步电机和PMSM都能拖起来转。网上关于AI8051U做SPWM的资料还不多,正好把寄存器配置、正弦表生成、相位累加和实测踩坑一起整理出来,给准备做变频器、逆变电源或者伺服驱动方向的朋友一个可以直接抄作业的底子。整个方案的核心就三件事:用PWMA_CH1/CH2/CH3输出三路PWM,各配一个互补通道,再把三路占空比按照相位差120°的正弦规律刷新。但越简单的方案坑越藏在细节里——死区时间、中心对齐模式下的更新时机、正弦表精度,哪一样没处理好,波形都会直接给你脸色看。

1. 三相SPWM变频驱动:先弄明白这几件事再动手

1.1 电机要的是正弦电压,但功率电路只会开关

三相异步电机或者永磁同步电机要转起来,定子绕组上必须有相位差120°的正弦电压。真正的正弦电源当然好办,但变频器要调速,得把直流母线电压变成频率可调、幅值可调的三相交流。功率电路里我们手里只有MOS管或IGBT,这些器件只会干两件事:导通或者关断。所以唯一的办法就是高频开关,用PWM脉冲序列去等效正弦波。

电机绕组本身是个大电感,电流是连续的,相当于一个天然的低通滤波器。只要PWM载波频率足够高,绕组里的电流就基本是平滑正弦。这就是SPWM的第一个要点:PWM的占空比不是随手给的,必须按正弦规律变化。把这个规律数字化,就是一张正弦表,表的点数代表一个输出周期内的刷新次数,表中每个值对应当时刻的占空比。

1.2 三相全桥为什么必须互补输出加死区

每一相就是一个半桥,上下两个管子串联在母线上。上管导通时桥臂中点接正母线,下管导通时接负母线(或者地)。上下管绝对不能同时导通,否则母线直接短路,瞬间电流能把管子炸飞。理论上上管关断、下管导通可以无缝衔接,但实际管子有延迟:关断需要几十到几百纳秒,开通往往更快。如果下管在上管还没完全关断时就开通,就是直通。

互补输出就是这个需求来的:硬件上让两路输出电平互反,中间插入一段可编程的死区——上管关断后等一段时间,再让下管导通。PWMA这类高级定时器把死区发生器做在硬件里,软件只需要写一个寄存器值,不用在中断里折腾延迟。

1.3 普通定时器和IO翻转为什么做不了这件事

有人问用普通定时器加IO翻转能不能做SPWM。能做,但上限很低。三路PWM要各自独立、还要互补、还要死区、还要在中断里高频更新占空比——普通定时器只有一路比较通道,IO翻转软件开销巨大,载波频率稍微一高CPU就忙不过来,示波器上一看波形全是抖的。

PWMA有3个带互补输出的通道、16位计数器、死区寄存器、刹车输入,这些就是为电机驱动准备的。它的结构和STM32的TIM1高级定时器师出同门,寄存器命名和配置思路都很接近,玩过STM32的人上手会非常快。

2. SPWM背后的关键参数:调制比、86.6%和死区时间

2.1 查表法怎么做SPWM

SPWM实现方式主流有两种。一种是实时比较法,用正弦参考波和三角载波实时比较生成PWM;另一种是查表法,把正弦波一个周期采样成N个点存成表,每个载波周期取出对应点的值作为占空比。单片机方案九成用查表法,因为在8051上实时算正弦函数代价太高了。

表怎么生成?以256点为例,点索引i从0到255,对应角度0°到360°,表中值用这个公式:

tab[i] = (0.5 + 0.5 * sin(2 * π * i / 256)) * 8192

乘8192是为了放大成整数。注意那个0.5偏移量:半桥电路没有负压输出,正弦波必须抬一个直流分量,让占空比在0到100%之间摆动而不是在正负之间摆动。这个直流分量经过电机绕组会被电感滤掉,不会影响输出线电压,放心用。

2.2 86.6%的线电压利用率是怎么算出来的

这个86.6%经常有人问,我把推导写清楚。

设直流母线电压为Vdc,每相桥臂中点相对负极的电压在0和Vdc之间脉动。SPWM线性调制时,调制比m(正弦参考幅值除以载波幅值)不能超过1。此时相电压基波的峰值为:

  • Uph_peak = m × Vdc / 2,m=1时最大为Vdc/2

为什么是Vdc/2?因为桥臂输出平均电压在0.5×Vdc上下摆动,正弦偏移的峰峰值是m×Vdc,取一半才是幅值。

三相线电压是两相电压之差,幅值关系是√3倍:

  • Uline_peak = √3 × Uph_peak ≤ √3 × Vdc / 2 ≈ 0.866 × Vdc

这就是86.6%的来历:同样一个直流母线,SPWM线性调制能送出去的最大线电压峰值只有母线电压的86.6%。如果电机额定电压是380V,那么直流母线至少要440V左右才够用。

那剩下14%怎么抠回来?SVPWM(空间矢量调制)通过在每个扇区重新分配零矢量作用时间,等效调制比可以做到SPWM的2/√3≈1.1547倍,线电压峰值能顶到Vdc,利用率100%。做变频器想榨干母线电压,下一步就往SVPWM走。我这个例程先用SPWM把整条链路跑通,后面换SVPWM只需要改占空比刷新算法,硬件不用动。

2.3 死区时间选多大

死区设置原则:大于最坏情况下从“关断指令发出”到“管子真正关断”的总延迟,再留一点余量,但也不能太大。死区过大,输出电压波形会削顶,低次谐波增加,电机能听到明显的“沙沙”声;死区过小,直通风险直接拉满。

常见经验值参考:

功率器件典型死区备注
低压小功率MOSFET(几十W)0.3~1μs开关快,死区可以小
中功率MOSFET(百W到kW级)1~2μs常见1.5μs
IGBT(kW级以上)2~5μs拖尾电流长,死区要大

以PWMA_DTR寄存器为例,它的结构和STM32 TIM1的DTG寄存器接近,低7位直接乘定时器时钟周期。24MHz主频下1个计数周期约41.7ns,想设2μs就写48,也就是0x30。具体换算表要看数据手册,记住“死区时间=寄存器值×定时器时钟周期”这个关系就不会算错。

3. AI8051U的PWMA初始化细节

3.1 通道和引脚分配

PWMA有4个通道,但只有CH1~CH3带互补输出,正好对应三相。通道分配如下:

通道输出互补输出对应相
CH1PWMA_CH1PPWMA_CH1NU相,0°
CH2PWMA_CH2PPWMA_CH2NV相,120°
CH3PWMA_CH3PPWMA_CH3NW相,240°

CH1P和CH1N这一对接U相半桥的上下管栅极驱动输入,CH2、CH3同理。引脚映射要查数据手册的引脚切换寄存器,不同封装的可用引脚不一样。我这次把三对输出放在P1口和P2口的部分引脚,旁边就是半桥驱动芯片的输入,走线尽量短,栅极驱动回路面积越小越好。

3.2 载波频率怎么定

载波频率(PWM频率)是SPWM最基础的设计参数,选择时主要权衡三点:

  • 听觉噪声:低于20kHz人耳能听到,电机和电感会啸叫。要么跑到20kHz以上,要么接受可闻噪声
  • 开关损耗:频率越高,每个周期MOS管开关损耗越大,散热压力越大
  • 分辨率:占空比分辨率取决于计数器最大值,ARR越大分辨率越高,但ARR大了载波频率就降了

具体计算,中央对齐模式:

f_pwm = 主频 / (预分频+1) / (ARR+1) / 2

主频24MHz,预分频为1,ARR=1499时:

f_pwm = 24MHz / 1 / 1500 / 2 = 8kHz

占空比分辨率是1500级,约10.5位,对一般电机驱动足够了。想上20kHz听不到啸叫,ARR就得压到599以下,分辨率降到约9.2位,SPWM谐波会大一些。这就要看你更在意噪声还是波形质量了。我这例程用8kHz,波形质量和散热都比较均衡,电机有一点高频声但完全能接受。

3.3 初始化代码与顺序

初始化顺序有讲究:先配预分频和重载值,再配通道模式,再配死区,最后开主输出和计数器。千万别先把MOE(主输出使能)打开再去配通道,那样上电瞬间输出引脚可能是不可控电平,直接炸管。

#define FOSC 24000000UL #define ARR_VAL 1499 // 8kHz载波 void PWMA_Init(void) { // 1. 预分频: 1分频 PWMA_PSCRH = 0x00; PWMA_PSCRL = 0x00; // 2. 自动重载值: 决定载波频率 PWMA_ARRH = (unsigned char)(ARR_VAL >> 8); PWMA_ARRL = (unsigned char)(ARR_VAL & 0xFF); // 3. CH1~CH3 配成PWM模式1, 使能预装载 // OC1M[2:0]=110, OC1PE=1, 所以CCMR=0x68 // 注意: 部分型号的CCMR寄存器把CH1/CH2拆成两个字节, 以头文件为准 PWMA_CCMR1 = 0x68; // CH1 PWM模式1 + 预装载 PWMA_CCMR2 = 0x68; // CH2 // CH3 同样处理, 寄存器名称可能是PWMA_CCMR3或CCMR2高字节 // 4. 使能3路输出及其互补输出, 高电平有效 PWMA_CCER1 = 0x55; // CH1/CH1N + CH2/CH2N PWMA_CCER2 = 0x05; // CH3/CH3N // 5. 死区: 24MHz下每步41.7ns, 0x30约2us PWMA_DTR = 0x30; // 6. 开主输出, 刹车配置按需扩展 PWMA_BKR = 0x80; // MOE=1 // 7. 中央对齐模式1 + 自动重载预装载 + 计数器使能 // CR1: ARPE=1(0x80), CMS=01(0x20), CEN=1(0x01) PWMA_CR1 = 0xA1; // 8. 更新中断使能 PWMA_IER = 0x01; }

这段代码里的寄存器名称在不同型号上可能有细微差别,比如CCMR1到底是同时管CH1/CH2还是每个通道一个寄存器。写代码前先把官方头文件打开对一遍,别直接抄。原理一样,只是寄存器拆分方式不同。

4. 三相120°相位差的软件实现:正弦表+相位累加器

4.1 先用Python把正弦表生成出来

手算正弦表不现实,用脚本生成最稳。下面这段Python输出256点、13位精度的表,值域0到8192,正好对应占空比0到100%:

import math N = 256 SCALE = 8192 for i in range(N): s = math.sin(2 * math.pi * i / N) v = int(round((0.5 + 0.5 * s) * SCALE)) v = max(0, min(SCALE, v)) print(f"0x{v:04X},", end="") if (i + 1) % 8 == 0: print()

生成的256个值粘进Keil,定义成code数组:

unsigned int code sin_tab[256] = { 0x1000, 0x1047, 0x108F, 0x10D6, 0x111D, 0x1164, 0x11AB, 0x11F2, // ... 共256个 };

4.2 120°相位差:一个小技巧,不用第二张表

三相互差120°,最笨的办法是做三张正弦表,没必要。256个点对应360°,120°就是256/3≈85.33个点,240°就是170.67个点。取整:

#define PHASE_B 85 // 120° #define PHASE_C 171 // 240°

在中断里取表时直接给索引加这两个偏移,再按0xFF截断,就能得到120°和240°的占空比。因为256是2的8次方,&0xFF这个操作比取模快得多,在中断里这点开销值得抠。索引差85.33取整为85,带来的相位偏差只有0.47°,对电机驱动完全可以忽略。

4.3 中断服务函数:占空比刷新

#define ARR_VAL 1499 #define SCALE_BITS 13 #define SCALE_VAL (1 << SCALE_BITS) // 8192 unsigned long phase_acc; // 相位累加器, 高16位作为表索引 unsigned long phase_step; bit half_cycle_flag; void PWMA_ISR(void) interrupt 13 { unsigned int idx; unsigned int arr = (unsigned int)(ARR_VAL + 1); if (PWMA_SR1 & 0x01) { PWMA_SR1 = 0x01; // 中央对齐模式下, 计数器上溢和下溢各产生一次更新事件 // 一个PWM周期会有两次中断, 这里只在第二次进入时刷新, // 保证载波周期内三相占空比一致 if (half_cycle_flag == 0) { half_cycle_flag = 1; return; } half_cycle_flag = 0; idx = (unsigned int)(phase_acc >> 16); PWMA_CCR1 = (unsigned int)(((unsigned long)sin_tab[idx] * (unsigned long)arr) >> SCALE_BITS); PWMA_CCR2 = (unsigned int)(((unsigned long)sin_tab[(idx + PHASE_B) & 0xFF] * (unsigned long)arr) >> SCALE_BITS); PWMA_CCR3 = (unsigned int)(((unsigned long)sin_tab[(idx + PHASE_C) & 0xFF] * (unsigned long)arr) >> SCALE_BITS); phase_acc += phase_step; } }

为什么用“高16位作索引”而不是单独一个递增变量?因为相位累加器天然支持变频。输出频率不是靠改变表内跳进步长,而是靠改变phase_step这个累加步进。累加器溢出一次就是完整一圈,溢出周期等于2^32除以phase_step个中断周期,输出频率就出来了。

4.4 变频控制和V/F曲线

输出频率f_out和相位步进的关系:

phase_step = f_out × 2^32 / f_update

其中f_update是实际刷新占空比的频率,本例是载波8kHz。50Hz时:

phase_step = 50 × 4294967296 / 8000 ≈ 26843546

代码里freq_x10单位是0.1Hz,用32位乘法近似:

void SetOutputFreq(unsigned int freq_x10) { // freq_x10 × 4294967296 / 8000 = freq_x10 × 536870.912 // 近似为 ×536871, 频率范围0.1~400Hz内误差小于0.1% phase_step = (unsigned long)freq_x10 * 536871UL; }

只调频率不调电压不行。三相电机定子绕组是电感,电压不变频率往下调,电流会暴涨,磁路饱和、绕组发热。所以变频器都有V/F曲线:基频以下保持电压和频率的比值恒定。最简单的实现就是让调制深度和频率一起变——比如10Hz时调制深度给0.2,50Hz时给到1.0。例程里留一个全局变量mod_depth,在中断里做一次乘法缩放占空比,后面想加恒压频比控制直接改这个变量就行。注意调制深度超过1.0就是过调制,波形会削顶,这里先不涉及。

5. 实测踩坑:从死区直通到MOS管发热的排查链路

5.1 坑一:死区没生效,上电就直通

第一次上电,我犯了个典型错误:初始化代码里DTR写的是0x00,想着先把波形跑出来再说。结果母线电流限制电阻直接烫手,不到10秒就闻到糊味。用示波器同时测同一桥臂的CH1和CH1N栅极波形,看到上下管驱动信号存在重叠区域——上管还没关完下管就开了,这就是直通。

排查链路我建议按这个顺序走,别跳步:

  1. 先把母线断开,只给控制电路供电,测6路栅极驱动波形
  2. 看同一桥臂两路信号有没有“同时为高”的时刻
  3. 如果有,检查死区寄存器值;如果没有但母线仍然过流,重点检查驱动芯片逻辑和MOS管是否已经损坏
  4. 确认死区正常后,串一个限流电阻或者白炽灯再上母线,逐级加压

把DTR改成0x30,约2μs之后,波形上出现了干净的死区窗口,也就是两路都为低的平台,母线电流恢复正常。这个坑基本每个人都会踩一次,好在排查也不难,就怕上来就直接满电压跑。

5.2 坑二:中央对齐模式的中断次数没算清楚

这是我调试时最隐蔽的一个问题。第一次写ISR,我只在更新中断里“每次进来就刷新占空比并累加相位”,结果输出频率比设定值高了一倍。原因在于中央对齐模式一个PWM周期有两次更新事件,上溢和下溢各一次,中断频率实际是16kHz,不是我以为的8kHz。相位步进按8kHz算,实际以16kHz累加,频率自然翻倍。

解决办法就是4.3代码里的half_cycle_flag:两次中断只做一次刷新。这个坑也提醒我,写软件前一定要先把硬件的更新事件触发条件读透,寄存器手册里关于更新事件那几行字值得反复看。

5.3 坑三:MOS管发热,但波形看着没问题

死区正常、没有直通,带个小电机跑了一会儿,MOS管还是烫。从前往后排查下来,有三个隐藏点:

  • 栅极驱动电压不足。自举电容太小,100nF根本不够,上管导通后期栅极电压跌落,管子没完全导通,进入线性区发热。换成1μF低ESR电容后明显改善。
  • 栅极电阻太大。最早用100Ω想抑制振铃,结果开关过程被拉长,开关损耗剧增。降到22Ω,同时给关断回路并联一个二极管加速泄放,发热才正常。
  • 载波频率过高。测试时为了静音试过32kHz,开关损耗直接翻倍。这个功率级别还是回到8kHz,加个小风扇,稳定跑半小时温升在可接受范围。

MOS管发热总结下来就三条线:没完全导通是导通损耗,开关过程太长是开关损耗,直通会快速炸管。按这三条线查,基本都能找到原因。

5.4 故障保护别省:刹车输入和逐波限流

电机驱动必须要考虑故障:过流、过压、堵转。PWMA的刹车输入(BKIN)可以在硬件层面直接关闭所有输出,响应是纳秒级的,比软件快得多。我把母线的过流比较器输出接到BKIN,一旦电流超过阈值,立即封波。软件里也要在过流中断里清MOE并置故障标志,进入保护状态后需要人工复位才能重新启动,避免反复触发把管子折磨坏。

还有一个容易被忽略的细节:PWM输出引脚到栅极驱动芯片之间串个几十欧电阻,除了抑制振铃,还能在驱动芯片异常时保护单片机IO。这个电阻不是随便加的,它直接影响开关速度,要和死区时间一起考虑,改了一边另一边也要重新验证。

6. 验证测试:示波器、逻辑分析仪和带电机前的检查清单

6.1 示波器和逻辑分析仪各看什么

调SPWM,逻辑分析仪和示波器分工不同。逻辑分析仪便宜,8通道的几十块,6路PWM加上过流信号、母线欠压信号全都能抓到,适合看时序关系:三相占空比是否按正弦变化、死区是否始终存在、故障封波是否及时。

示波器看模拟量:测相电压波形要注意探头地线要短,最好用隔离通道;用电流探头看相电流正弦度;看母线电压是否稳定。直接测高压侧的相电压容易引入干扰,也危险。

看三相互差120°最直观的办法:把三相PWM输出各自接一个RC低通滤波器,比如10kΩ加100nF,截止频率约160Hz,然后示波器三通道同时看滤波后的包络——三个正弦波清晰可见,彼此错开120°,幅值一致。这个土办法没有探头隔离问题,调试特别好用。

6.2 带电机前的安全检查清单

拿我的实际顺序作参考:

  1. 不装MOS管,只测控制板和驱动板的逻辑波形,确认6路PWM频率、死区、相位关系正确
  2. 装MOS管,不接母线,测栅极驱动波形,确认死区依然存在
  3. 串白炽灯或限流电阻接母线,给很低的输出频率10Hz和低调制深度,观察母线电流
  4. 确认无异常后直接上母线,空载运行听声音、看电流
  5. 再接电机负载,观察启动电流和稳态电流

每次改动代码后至少过一遍第3步,不要直接全电压跑。这个习惯能省下不少管子钱。

6.3 从SPWM到SVPWM、FOC的路还很长

这套底层跑通之后,往上加的东西很多。最直接的是换SVPWM算法,把线电压利用率从86.6%提到接近100%,电机在同样母线电压下能跑更高转速、输出更大转矩。再进一步就是电流环和速度环闭环,那就需要电流采样——中心对齐PWM在这里的优势很明显:计数器到达谷底0时,三相下管刚好都导通,母线电流等于某一相电流,这个时刻采样电流噪声最小,正好配合ADC同步触发。这是后面做FOC的基础。

配套工程文件我按“正弦表生成脚本、PWMA初始化、三相查表刷新、V/F频率控制”四部分整理好了,开发板到手就能直接对照跑。最后说点个人体会:SPWM这套东西,原理不复杂,但每个坑都藏在时序细节里——死区、中心对齐的更新事件、自举电容、栅极电阻,随便一个没处理到位,波形图上都很难一眼看出来,得靠电流、温度、声音这些物理量反馈才能发现。调试时别只盯示波器,多用手背感觉一下管子的温度,多听听电机的动静,它们往往比波形更早告诉你问题出在哪。

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

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

立即咨询