ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SPWM三相六路互补输出与调频调压:死区、载波比与V/F控制详解

SPWM三相六路互补输出与调频调压:死区、载波比与V/F控制详解

简介:面向STM32F103C8T6三相电机驱动与变频控制场景,该资源提供基于高级定时器TIM1查表法生成三相六路互补SPWM的完整工程。代码生成相位互差120°的六路PWM,可配置死区时间,并通过恒压频比策略实现调频调压,适用于逆变器控制、变频调速、电机驱动等教学与工程验证。工程基于KEIL5编写,注释详尽,支持在线仿真直接观察输出波形;源码中spwm.c、timer.c等模块划分清晰,便于理解正弦查表、死区插入、压频调节的具体实现,同时附带的PDF文档可辅助原理梳理。资源包共148个文件,包含18个C源文件、26个头文件、KEIL工程配置、链接脚本、PDF参考文档、hex可执行文件等,压缩包大小46.65MB。目前已有1676人学习下载,对嵌入式及电机控制方向的开发者而言,可作为学习样例和移植基础,快速获取可运行的工程并对照调试。

1. SPWM三相六路互补输出:死区和调频调压是怎么凑到一起的

做三相电机驱动或三相逆变电源开发的人,迟早会撞上「SPWM三相六路互补输出」这个组合。它的本质是把三对桥臂的开关逻辑映射成六路PWM:每一相的上管和下管逻辑上必须反相,这就是“互补”;但物理器件关断需要时间,所以要在切换沿插入一小段死区,防止上下管直通烧模块;而输出频率和电压幅值又必须按恒压频比的规律一起动,这就是标题里“调频调压”要做的事。这篇文章适合手上已经有逆变主电路或驱动板、正准备写固件的人,我按自己调板子的顺序来讲:SPWM怎么生成、六路互补怎么配死区、调频调压的频率和电压从哪里算出来,以及几个最容易翻车的排查点。新手可以照着搭参数,熟手可以直接跳去第5章看坑。

2. SPWM生成原理:规则采样、定时器比较与载波比选择

2.1 自然采样与规则采样:数字控制器里跑的其实是规则采样

SPWM的数学原型是正弦波与三角波求交点:正弦瞬时值高于三角波时输出高电平,反之输出低电平,输出脉冲宽度按正弦规律变化。这种实时求交点的做法叫自然采样,硬件上由比较器电路直接完成,模拟SPWM发生器就是干这个的。

数字MCU里没有廉价的正弦-三角波比较器硬件,所以几乎所有固件方案走的是规则采样:在三角波的顶点或底点时刻,把正弦函数当时的数值采进来,保持一个载波周期不变,得到的是一串阶梯状的占空比指令。规则采样和自然采样的差别在于,脉冲边沿位置有一个周期量级的量化误差,但载波比(载波频率与输出频率之比)超过100之后,两者输出波形的THD差异已经小到工程上可以忽略。我一般直接查正弦表,因为查表法只做一次乘法就把占空比算出来了,中断里省时间。查表法的精度损失来自表长度和定点量化,256点表配合Q15格式的相位步进,对绝大多数电机驱动场景足够。

2.2 定时器比较通道怎么合成一路SPWM:预装载是容易忽略的一环

定时器生成PWM的核心机制是计数器数值与比较寄存器CCR相等时,输出引脚发生翻转。边沿对齐模式下计数器从0递增到ARR,匹配一次输出翻转一次;中心对齐模式下计数器先增后减,输出在周期中点对称,所以脉冲波形对称性更好,谐波里偶次分量更小。中心对齐模式还有另一个好处:上下桥臂的互补切换时刻在时间轴上对称,死区插入后每半个周期引入的误差方向一致,便于后期做死区补偿。我在电机驱动项目里默认选中心对齐,只有对开关损耗极其敏感的高频小功率场合才切回边沿对齐。

有个配置项特别容易踩坑:比较寄存器的预装载。如果关闭预装载,软件在计数周期中间某个时刻写入新CCR值,该值可能在一个周期内被比较器匹配多次,输出波形上就会出现不该有的毛刺。正确做法是使能预装载,让新值只在更新事件(周期顶点或底点)才被搬运到实际比较寄存器。中心对齐模式下更新事件一个周期出现两次,分别在顶点和底点,利用两次更新事件刷新三相CCR,软件上每个PWM周期有两次写入机会,时序余量比边沿对齐更宽松。

2.3 载波频率与载波比:先定这两个数,再谈波形质量

载波频率也就是开关频率,它直接决定四个相互制约的量:输出电流纹波、开关损耗、死区占比和音频噪声。开关频率越高,电流越平滑、波形越接近正弦,但IGBT或MOSFET的开关损耗线性上升,死区时间占一个周期的比例也在变大——死区占比过大时,死区引起的电压误差会比想象中明显得多。常见做法是MOSFET方案选10kHz到20kHz,IGBT方案选8kHz到12kHz,这也是通用变频器的典型区间。

载波比是载波频率除以输出频率。它决定一个正弦周期内能放下多少个PWM脉冲,载波比太低,输出波形就退化成少数几个阶梯,电流纹波和转矩脉动都很严重。我调参数时有一个经验下限:最低载波比不小于15,最好能到20以上。于是选择顺序就变成了:先按开关器件定好载波频率,再算最高输出频率下载波比是否够,不够就提高载波频率或者限制最高运行频率。

下表是我惯用的参数起点,不同功率段直接套用:

参数低压MOSFET方案三相380V IGBT方案
载波频率16kHz8kHz
最高输出频率400Hz100Hz
最低载波比4080
死区时间300ns–800ns2μs–3μs
调制比范围0–100%0–100%

载波比这个数字还需要和正弦表长度配合考虑。假如固定载波频率10kHz、输出频率50Hz,那么一个正弦周期正好有200个载波周期,相位步进就是256/200=1.28个表项每周期,这个小数必须用定点格式保存,否则频率会漂。这个计算在第4章展开。

3. 三相六路互补输出:桥臂映射、死区插入与寄存器配置

3.1 三对桥臂与六路PWM的映射:相位序错一根线就是事故

三相逆变器主电路是三个桥臂并联在母线上,每个桥臂上下两个开关管。六路PWM的命名通常是A+/A-、B+/B-、C+/C-,其中“+”表示上管,“-”表示下管,A、B、C三相相位互差120°。在带高级定时器的MCU里,定时器的三个比较通道各有互补输出,典型映射是:CH1和CH1N驱动A相上下管,CH2和CH2N驱动B相,CH3和CH3N驱动C相。这个映射关系要在硬件设计阶段就定死,画板的人和写固件的人必须共用同一张表格。

每相占空比的计算公式是:

dutyA = 0.5 + 0.5 * M * sin(theta); // A相,0.5为静止电平占空比 dutyB = 0.5 + 0.5 * M * sin(theta - 2.094); // 2.094 rad = 120° dutyC = 0.5 + 0.5 * M * sin(theta + 2.094);

M是调制比,范围0到1。当M=0时三相占空比全是50%,桥臂输出的是等宽方波,线电压为零;M=1时占空比在0到100%之间正弦摆动,此时SPWM线性区达到上限。这里有一个新手容易搞混的点:SPWM的相电压幅值最大是母线电压的一半(M=1时),线电压有效值约等于0.612倍母线电压,想继续提高电压利用率,靠的是SVPWM或三次谐波注入,而不是把M调到1以上。

3.2 为什么死区必须存在:从器件关断延迟算直通风险

逻辑上互补输出保证了上下管指令不会同时为高,但物理器件不是瞬间完成开关的。MOSFET有关断延迟,IGBT有拖尾电流,上管还没完全关断,下管就已经开始导通,母线就被直接短路,这就是直通。直通电流上升速率由母线电压和回路杂散电感决定,往往在几百纳秒内就超过器件承受能力,炸模块通常就发生在这种时刻。

死区的做法是在互补信号切换时,让目标管延迟一段时间再开通。这段时间内两个管子都处于关断状态,电流靠桥臂续流二极管续流。死区时间的选择要从器件数据手册的关断延迟出发:常见做法是取关断延迟最大值的1.5到2倍,同时留出驱动芯片的传播延迟余量。参考经验值如下:

开关器件类型典型死区范围说明
低压MOSFET(60V-100V)200ns–500ns开关速度快,死区可以很小
高压MOSFET(600V以上)500ns–1.5μs米勒平台拉长了关断时间
IGBT模块(600V/1200V)2μs–3μs拖尾电流明显,死区不能太短

注意死区不是越大越安全。死区期间相电压由电流续流方向决定,不受PWM指令控制,所以死区越长,实际输出电压与指令电压的偏差越大。这个偏差在低频段尤其明显,轻则电机噪声变大,重则波形削顶。把死区压到器件允许的下限,配合死区补偿,才是正路。

3.3 硬件死区配置:最小代码与死区时间换算

多数带高级定时器的MCU在硬件层面直接支持互补输出和死区插入,配置正确后,CPU不需要在中断里人为延迟,死区由定时器硬件自动插入。这比软件死区可靠得多,因为软件延时依赖中断响应时间,一旦中断被抢占,死区时间就跑偏了。我在所有正式方案里都用硬件死区。

硬件死区的核心配置有三项:输出极性、死区长度、空闲状态。下面是基于通用定时器外设的初始化伪代码,具体寄存器名称以实际芯片手册为准:

/* 定时器初始化:中心对齐,三通道互补PWM,硬件死区 */ #define TIM_CLK 90000000UL // 定时器时钟 90MHz #define PWM_FREQ 9000 // 载波频率 9kHz #define DEAD_TIME_NS 1200 // 目标死区 1200ns // 中心对齐模式下,ARR对应半个载波周期 uint16_t arr = TIM_CLK / PWM_FREQ / 2 - 1; timer_set_period(arr); // 死区计数值 = 时钟频率 × 时间 uint16_t dt_cnt = (uint16_t)(TIM_CLK / 1000000UL * DEAD_TIME_NS / 1000); // 使能三通道PWM输出与互补输出 timer_ch_enable(CH1, PWM_CENTER_ALIGNED, OUTPUT_ACTIVE_HIGH); timer_ch_enable(CH1N, OUTPUT_ACTIVE_HIGH, dt_cnt); // 互补通道带死区 timer_ch_enable(CH2, PWM_CENTER_ALIGNED, OUTPUT_ACTIVE_HIGH); timer_ch_enable(CH2N, OUTPUT_ACTIVE_HIGH, dt_cnt); timer_ch_enable(CH3, PWM_CENTER_ALIGNED, OUTPUT_ACTIVE_HIGH); timer_ch_enable(CH3N, OUTPUT_ACTIVE_HIGH, dt_cnt);

这段代码的关键点是中心对齐模式下ARR要按半个载波周期计算,死区时间要换算为定时器计数个数。不同MCU的死区寄存器编码方式差异很大,有的直接填计数值,有的是非线性编码,写驱动前务必查对应芯片的参考手册,别对着别家代码硬套。输出极性也要和驱动电路匹配:有的驱动芯片低电平有效,有的高电平有效,配反了会表现为一上电就炸管或电机抖动。我习惯把互补通道的极性和对应驱动芯片的输入逻辑列成一张表,画板、写固件各留一份,硬件和软件按同一张表核对,能省掉大量联调时互相甩锅的时间。

4. 调频调压:恒压频比、正弦步进法与低频补偿

4.1 恒压频比:为什么调频必须同时调压

交流异步电机的绕组阻抗随频率变化,如果只改变输出频率而不改变电压,低频时感抗很小,同样电压下电流会失控,磁路饱和,电机很快过热。恒压频比控制的本质是保持气隙磁通近似恒定:磁通与电压和频率的比值成正比,因此让电压幅值跟随频率按比例变化,电机在基频以下的任意频率都能输出额定转矩。

V/F曲线的基准工作点是额定频率和额定电压。在额定频率以下,调制比M按比例缩放:

M = (Fout / Frated) * 100%; // 例:50Hz额定,25Hz时M=50%

这个公式只适合纯理论情况。实际系统中还有两个电压损失项:死区时间带来的误差电压,以及开关管和续流二极管的导通压降。低频时这两项占的比重很大,如果不补偿,电机会出现低频抖动、带载无力。所以工程上的V/F曲线在低频段会加入一段起始电压boost,让频率接近0时电压不是0,而是某个小值。

4.2 正弦步进法:用一个相位累加器实现平滑调频

在整个调频调压方案里,调频的核心机构是相位累加器。载波频率固定不变,每个PWM周期更新一次三相占空比,更新时相位累加器前进一个步进值,步进大小决定输出频率。一个正弦周期由表长度N个点组成,每PWM周期走过step个点,那么输出频率为:

Fout = step * Fpwm / N

所以调频只需要改step这一个变量,非常优雅。step通常不是整数,以256点表、10kHz载波、50Hz输出为例,每个PWM周期需要走1.28个点,这个0.28的小数必须保留,否则输出频率会偏差很大。我的做法是把step按Q15格式存储,相位累加器用32位无符号整数,查表时取高8位作为正弦表索引。

如果输出频率从0.1Hz往上调,步进值跨度很大,但相位累加器可以连续工作,不会因为频率切换产生相位跳变,这对电机平滑启动很重要。启动瞬间从0Hz直接加速时,相位是从累加器当前值继续走的,不存在“从正弦表中间硬切”造成的冲击。

4.3 调压实现:V/F曲线查表、低频boost与占空比写入

下面是每个PWM周期更新中断里的完整逻辑。这段代码同时完成相位累加、查表、调压、三相占空比输出,中断里只做整数运算,不做浮点除法:

/* PWM更新中断:每个载波周期执行一次 */ void pwm_isr(void) { static uint32_t phase_acc = 0; // 1. 相位累加,step_q15 = Fout * N * 32768 / Fpwm phase_acc += step_q15; // 2. 查正弦表,取高8位作为256点表索引 uint16_t idx = (uint16_t)(phase_acc >> 8) & 0xFF; int16_t sa = sin_tbl[idx]; int16_t sb = sin_tbl[(idx + 85) & 0xFF]; // 120度对应256/3≈85.3 int16_t sc = sin_tbl[(idx + 171) & 0xFF]; // 240度 // 3. 从V/F曲线得到调制比,单位千分比,500表示0.5 uint16_t mod = vf_get_mod(fout_hz); // 4. 计算占空比,CENTER=ARR/2,对应50%占空比 int32_t duty_a = CENTER + ((int32_t)sa * mod) / 2000; int32_t duty_b = CENTER + ((int32_t)sb * mod) / 2000; int32_t duty_c = CENTER + ((int32_t)sc * mod) / 2000; // 5. 写入比较寄存器,预装载保证下一次更新生效 timer_write_ccr(CH1, (uint16_t)duty_a); timer_write_ccr(CH2, (uint16_t)duty_b); timer_write_ccr(CH3, (uint16_t)duty_c); }

代码里几个常数要解释清楚。sin_tbl表项若用int16_t表示正弦值,范围为0到32767,但这里存的是有符号值,sa的正负代表当前相位在半波的位置。调制比mod单位是千分比,1000对应M=1;duty_a的计算里除以2000,是因为sa的满幅是32768,乘以mod/1000后要映射到占空比的半幅范围(即0.5M对应到ARR的一半),所以系数是2×1000。这样算出来的duty在0到ARR之间,对应占空比0%到100%。偏移量85和171对应120度和240度,这是256点表的整数近似,实际相位误差约0.47度,对电机驱动没有影响。

vf_get_mod函数是V/F曲线的核心,它按分段线性实现。我的典型配置是:启动频率2Hz时输出电压为额定电压的3%到5%,从2Hz到额定频率之间按线性比例,额定频率以上保持最大调制比。低频boost的数值要谨慎,给多了电机在低频段会过励磁、电流偏大,给少了带载启动无力。这个值是在空载和满载两个极端下实测折中的,没有通用最优解。

5. 调频调压避坑:死区、过调制与启动冲击的排查手册

5.1 死区太大:波形削顶、电机噪声变大

现象:空载运行时光听声音就觉得不对,电机有持续的低频嗡嗡声;用示波器看相电压波形,发现正弦顶部不是光滑的圆弧,而是有台阶状缺口;母线电流波形上有毛刺。

原因:死区期间桥臂输出电压不受PWM指令控制,而是由续流电流方向决定,这相当于在每个开关周期注入一个误差电压脉冲。死区越长,误差电压越大,叠加到正弦波上就表现为顶部被削掉一块。调制比越接近1,这种削顶越明显。

解决:把死区时间压到器件允许的下限,前提是用示波器确认不会直通。我在MOSFET方案里曾经从1μs一路降到300ns,噪声问题基本消失,温升也没有明显变化。如果死区已经压到很低还是有削顶,就要考虑做第6章的死区补偿了。

5.2 死区太小:桥臂直通,炸管往往只在一瞬间

现象:上电瞬间或第一次给使能时,母线电流检测立刻报过流,驱动芯片的故障输出被拉低;拆开功率模块检查,上下管都短路了,甚至驱动电阻都烧黑了。

原因:死区时间小于开关管的关断延迟。IGBT的拖尾电流会导致实际关断时间比数据手册标称值更长,或者驱动电路本身存在不对称的传播延迟,上管驱动关断慢了几百纳秒,下管已经导通,直通电流瞬间形成。

解决:先把死区设到器件关断延迟最大值的2倍以上,跑通功能后再逐步缩小。每次缩小后带满载跑一段时间的温升和波形,确认没有异常再继续降。驱动芯片的传播延迟不对称问题容易被忽略,尤其使用光耦驱动的场合,上下通道的光耦延迟可能差几百纳秒,这部分也要算进死区余量里。我踩过一次,把死区从2μs降到1μs后满载运行一切正常,但把母线电压调高后就炸了,后来查出来是光耦开关延迟在高电压下变差,从此以后都会把死区余量多留一点。

5.3 过调制:把调制比超过1当成提压手段

现象:输出电压加到某个点就上不去了,继续提高调制比,输出电压几乎不变,但波形顶部被削平,输出电流变成尖顶波,谐波剧增,电机温升很快。

原因:SPWM线性区最大调制比就是1,M超过1后正弦波峰值部分进入削平状态,脉冲宽度饱和,基波电压不再随M线性增长,此时进入过调制区。过调制不是不能用,但电压谐波会急剧增大,电流畸变严重,对电机和滤波器都不友好。

解决:调制比限幅到100%。想提高电压利用率,正确的办法是换SVPWM算法,它的线性区调制比可以到1.15,比SPWM高出约15%。在不改主电路的前提下,SVPWM是提高母线电压利用率最直接的手段,代价是算法复杂度高一些,需要重新做扇区判断和矢量作用时间计算。如果只是想做简单验证,也可以先注入三次谐波,把正弦波峰值压下来,等效提高调制比上限,但电机零序电流会变大,需要谨慎评估。

5.4 启动瞬间过流:0Hz附近V/F曲线电压太低

现象:设了0Hz启动,一给使能就报过流跳闸,尤其是带载启动时几乎必跳;空载时能转起来,但电机在启动瞬间有明显抖动。

原因:0Hz时V/F曲线给出的是0V或极低电压,电机无法建立足够的转矩,转子不动,定子电流迅速增大;同时死区电压在低频段占比大,实际输出电压和指令电压方向不一致,电流波形畸变严重。

解决:设置启动频率,常见做法是2Hz到5Hz起调,V/F曲线在启动频率处给一个起始电压,通常为额定电压的3%到5%。启动相位从0开始,相位累加器在使能前清零,让电机从一开始就按确定的方向受力,避免初始相位不确定造成的冲击。如果是大惯量负载,还需要配合S曲线加减速,而不是直接设一个固定启动频率就完事。我的习惯是启动频率和boost电压做成两个可调参数,在调试软件里留出接口,上电试机时先空载调好,再带载微调。

5.5 载波比过小:高频段波形明显变差

现象:低频段电流波形很干净,把输出频率调到额定以上后,相电流变成明显的锯齿状,电机噪音变大,高速时转矩不足。

原因:载波频率固定,输出频率升高后载波比下降,一个正弦周期里只有十几个PWM脉冲,谐波分量大幅上升。这本质上是采样点数不足导致的波形失真,不是控制参数的问题。

解决:三种思路。一是限制最高运行频率,让载波比始终高于20;二是提高载波频率,但要注意死区占比和开关损耗同步上升,需要重新评估温升;三是分段切换载波频率,低速用低载波频率减少开关损耗,高速用高载波频率保证波形质量。第三种方案最灵活,但切换载波频率的瞬间要重新计算ARR和死区计数值,处理不好会产生频率跳变,建议在验证充分后再上。

6. 验证与进阶:示波器查死区、频压比校验与补偿方向

6.1 示波器看六路波形:死区用延迟放大来看

写完固件上电之前,先用示波器确认六路PWM的逻辑,这一步能避免炸管。把示波器两个通道分别接同一相的上管和下管驱动信号,用上管的关断沿做触发,时基放到200ns/div左右,观察下管开通沿是否比上管关断沿晚了一个死区时间。如果下管提前开通,说明死区配置没生效,立即断电检查寄存器。

测量时注意探头安全:上下管的驱动信号都以各自源极或发射极为参考,不能用普通探头跨接在母线之间测,最好用差分探头或隔离通道。我见过有人用两个探头分别接地的方式测上下管波形,结果探头地线把桥臂短路了,直接放炮。另外,测出来的死区时间要包含驱动芯片的传播延迟,实测值和寄存器设定值差几百纳秒是正常的,只要下管开通沿确实在上管关断沿之后就行。

6.2 频压比校验:从空载到满载的三段式调整

调频调压参数是否合理,最终要看输出电压和输出频率的关系是否符合预期。用电压表或功率分析仪测相电压有效值,记录不同频率点下的实测值,与理论V/F曲线对比。我习惯从空载开始,按5Hz、10Hz、25Hz、50Hz四个点记录,然后带额定负载再测一遍。低频段偏差大就调boost电压,高频段偏差大就查死区补偿和母线电压。

测试点空载实测满载实测理论值偏差处理
5Hz记录记录额定×10%偏差大调低频boost
25Hz记录记录额定×50%偏差大查死区补偿
50Hz记录记录额定值偏差大查母线电压

满载下如果5Hz电压偏低,电机容易堵转,要加boost;但boost加多了,空载电流会偏大,所以这个值要在两个极端之间折中。调好后用热成像仪看功率模块的温度,确认没有局部过热点。

6.3 方向:软件死区补偿在什么阶段值得做

死区补偿的原理是检测电流方向,在PWM占空比上反向叠加一个误差电压,抵消死区造成的电压损失。代码逻辑很简洁:

/* 死区补偿:按电流方向叠加补偿电压,comp单位为占空比计数 */ if (phase_current > 0) { duty_a += comp; // 电流为正,实际电压偏低,补上去 } else { duty_a -= comp; }

但工程实现有几个前提:必须有相电流方向信息,而且方向判断不能错,错了反而会加剧畸变;电流传感器带低通滤波时相位延迟会导致过零附近判断方向滞后,需要做滞回;三相都要独立补偿,计算量不大但调试量不小。我的观点是,先把死区压到器件允许下限,再评估要不要做软件补偿。如果电流波形仍然不理想,再上补偿不迟。硬件死区压缩能解决80%的问题,剩下20%才是死区补偿的发挥空间。

更高的方向是换SVPWM。同一套调频调压框架,把SPWM的占空比计算换成SVPWM的矢量作用时间计算,电压利用率提高15%,死区影响也会因为矢量合成方式的不同而减小。这个迁移不影响相位累加和V/F曲线的逻辑,只改占空比计算部分。

最后说一个习惯:每次初始化定时器,我都会把预装载、极性、死区、空闲电平四个配置项逐行核对一遍。之前有一次漏了预装载,波形上出现周期性的毛刺,电机抖动到怀疑人生,查了两天才发现是CCR在周期中间被改写导致的。从那以后,这四项成了我的固定检查清单。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进