资讯动态

基于STM32的三相逆变器变频变压程序设计与实现

发布时间:2026/10/21 8:35:09 来源:尧图企业网站定制
拿到一块三相逆变器控制板直流母线电压已经稳定在540V六个功率管全部就位接下来的重头戏就是让程序把直流变成频率可调、电压跟得上频率的三相交流输出。很多人拿到这种板子第一件事是到处找能直接烧录的逆变程序可一旦涉及变频变压、二次开发、灵活设定输入输出工程牵扯到的东西远比想象中多PWM通道怎么分配正弦波怎么生成频率如何平滑变化母线电压怎么参与计算死区怎么设置哪一个环节没处理好上电就可能冒烟。这篇文章就围绕一套基于STM32主控、用C语言设计的三相逆变器逆变程序展开整理我在实际开发中对控制策略、软件架构、关键算法和调试细节的理解给正在做变频电源、电机驱动或电力电子实验的工程师一个可参考的落地方案。1. 做逆变程序先把控制策略想清楚为什么选V/F开环三相逆变器的硬件拓扑并不复杂直流母线正负极之间跨接三组半桥每组半桥由上下两个开关管串联构成六个开关管对应六路PWM。程序要做的事情本质上是控制这六个开关管的导通顺序和占空比让负载端在三相之间轮流获得不同的平均电压从而合成接近正弦的电压波形。这个过程专业说法叫PWM调制工程实现里有SPWM、SVPWM等不同路线但无论哪条路线输出频率由调制波形周期决定输出幅值由占空比大小决定这就是标题里“变频变压调节”四个字的物理基础。1.1 三相逆变器的核心任务把直流母线变成受控的三相交流电每一相半桥本质上就是一个斩波器开关管以十几kHz的频率动作把直流母线电压斩成高低变化的脉冲序列。由于负载通常是电机或电感元件电流不会跟着脉冲突变而是会取脉冲序列的平均值。只要控制占空比按正弦规律变化负载端就能得到一个等效的正弦电压这就是SPWM的面积等效原理。很多初学者会纠结“PWM明明是方波怎么叫正弦输出”。换个角度理解你不需要让电压波形本身光滑只需要让电压的“平均值包络”按正弦走。载波频率越高包络越精确但开关损耗也越大。所以逆变程序的第一步不是写代码而是先把控制频率、调制方式、死区时间这些基础参数定下来。我习惯把载波设在8kHz到16kHz之间太低听得见噪声太高管子发烫。1.2 V/F恒定控制为什么能覆盖大部分变频需求标题明确写了“变频变压调节”这四个字对应的经典控制策略就是恒压频比控制也就是V/F开环控制。异步电机的定子绕组通电后会产生旋转磁场磁场的旋转速度由频率决定而气隙磁通的大小由电压和频率的比值决定。如果只提频不提压电压不变频率升高磁场会被拉弱电机出力不足反过来频率降低时电压过高又会磁饱和、电流飙升。V/F恒定正是为了让气隙磁通保持稳定。对于大多数通用变频场景比如风机、水泵、普通异步电机调速开环V/F已经能跑得很稳。它的最大优点是不依赖电机参数不装编码器只要程序里有一条正确的V/F曲线就能转。缺点是不能精确控制位置和力矩动态响应也一般。但这不影响它成为二次开发的基础盘后面即使要上闭环控制V/F的框架仍然可以保留作为开环运行模式。低频时需要额外注意。电机定子绕组存在电阻低频时感抗很小电阻压降占了很大比重如果严格按照直线V/F比例给电压转矩会不足。所以工程上通常在低频段做电压补偿比如5Hz以下补偿一段固定电压5Hz以上再回归直线关系。这个细节我在后面算法部分还会展开。1.3 SPWM与SVPWM之间怎么选调制算法的选择直接影响代码结构。SPWM的思路是三相互相独立各生成一列与正弦波对应的PWM三相相位差120°实现直观。SVPWM的思路则是把六个开关管的状态组合看成空间矢量通过相邻矢量的时间合成来逼近目标旋转矢量电压利用率和谐波表现更好。对比项SPWMSVPWM算法复杂度低正弦查表即可高需要扇区判断和作用时间计算母线电压利用率较低约86%左右较高相比SPWM大约提升15%输出谐波5次、7次谐波相对明显谐波更小波形更接近正弦工程难度容易上手适合第一版需要理解矢量合成代码量更大适用场景通用变频、教学、快速验证电机控制、对波形质量要求较高的场合我推荐的做法是先按SPWM把整套程序打通包括频率斜坡、母线电压归一化、故障保护、通讯管理让电机能稳定转起来。等到功能架构稳定再把算法层的调制函数替换成SVPWM其余部分基本不用动。标题里说“支持二次开发”这种先简后繁的演进路径比一开始就堆SVPWM稳妥得多。2. STM32资源分配与程序分层先给代码划好边界确定控制策略之后就要把需求落到STM32这颗主控身上。三相逆变器对定时器、ADC、中断的依赖都非常重资源分配不合理后面调试会非常痛苦。我一般先把整个系统分成三个层次驱动层只负责操作寄存器算法层只负责算占空比和频率应用层只负责管参数和状态。二次开发时改任何一个层次都不会牵连另外两层。2.1 六路互补PWM如何映射到STM32高级定时器三相全桥需要六路PWM其中三路是上桥臂驱动另外三路是下桥臂驱动同一相的上下管必须互补而且互补之间要有死区时间防止直通。STM32里的高级定时器是最合适的选择因为高级定时器自带互补输出和硬件死区插入功能CH1/CH2/CH3输出上桥臂PWMCH1N/CH2N/CH3N输出下桥臂PWM一个定时器就能搞定六路信号。初始化时有几个点必须确认。第一是计数模式SPWM我倾向用中心对齐模式这样输出波形对称性更好谐波更小。第二是极性配置上管和下管的驱动IC输入极性可能不同配置不对会导致同一相上下管同时导通。第三是死区时间STM32通过BDTR寄存器设置不是任意值都能精确给到需要根据定时器时钟确认步进。下面是一段典型的HAL库配置骨架核心是开启自动重装载预装载和互补输出static void PWM_Init(void) { TIM_OC_InitTypeDef sConfigOC {0}; TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig {0}; sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 0; sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; sConfigOC.IdleState TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState TIM_OCNIDLESTATE_RESET; HAL_TIM_PWM_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_2); HAL_TIM_PWM_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_3); sBreakDeadTimeConfig.OffStateRunMode TIM_OSSR_DISABLE; sBreakDeadTimeConfig.OffStateIDLEMode TIM_OSSI_DISABLE; sBreakDeadTimeConfig.LockLevel TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime 0x50; sBreakDeadTimeConfig.BreakState TIM_BREAK_ENABLE; sBreakDeadTimeConfig.BreakPolarity TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.BreakFilter 0; sBreakDeadTimeConfig.AutomaticOutput TIM_AUTOMATICOUTPUT_DISABLE; HAL_TIM_OC_ConfigBreakDeadTime(htim1, sBreakDeadTimeConfig); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_2); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_3); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_2); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_3); }这里DeadTime的值不是随便填的必须换算成实际时间。假设定时器时钟为72MHzDTG步进一般是几个时钟周期0x50对应的时间大概在几微秒级别具体要看参考手册的寄存器分段。2.2 ADC采样通道分配与同步时机逆变器需要采样的模拟量不少母线电压、至少两相输出电流、散热器温度。STM32的ADC通道足够用但采样时机比通道分配更关键。功率管开关瞬间会产生很强的di/dt噪声如果ADC在这个时刻采样母线电压和电流读数都会被污染。正确做法是把采样时刻放在PWM周期的中间点此时上下管都已经稳定导通电流也接近一个PWM周期内的平均值。工程上比较省事的实现是让定时器的更新事件触发ADC采样再通过DMA把结果搬进内存。这样CPU不需要在采样上浪费时间中断里直接拿最新的ADC值参与计算即可。一个典型的设定是PWM周期中断里更新三相CCR同时把ADC转换结果同步到算法层变量。母线电压是必须参与调制深度计算的采样不准会导致输出电压幅值漂移这个在下一章详细说。2.3 驱动层、算法层、应用层三层边界很多自发写的逆变程序喜欢把所有逻辑堆在定时器中断里中断函数动辄几百行看似效率高实际改动一次风险翻倍。我建议从第一版就按三层划分驱动层只做端口封装比如PWM设置占空比、ADC读取通道值、GPIO控制继电器和指示灯不出现任何控制逻辑。算法层负责正弦表索引、相位累加、V/F曲线计算、调制深度限制输入是频率指令和母线电压输出是三个CCR值。应用层负责参数管理、串口或CAN命令解析、故障类型判断、启停状态机。分层之后有一个明显好处调试时可以用假数据代替ADC输入单独验证算法层的输出是否正确。比如先放一个固定的母线电压540V看不同频率下CCR变化是否符合V/F曲线再接入真实ADC值。这个验证顺序帮我排除掉大量“不知道是采样错还是算法错”的麻烦。3. 变频变压调节的C语言实现查表法SPWM核心思路这一章是程序的核心也是标题里“C语言实现变频变压调节”最集中的体现。三相SPWM的C语言实现有很多种写法我在实际项目中一直用的是“正弦查表表相位累加器”这套组合逻辑清晰计算量小二次开发时改调制深度或频率都非常直观。3.1 正弦表生成与调制比的计算SPWM的第一步是准备一个正弦表。如果你愿意可以在中断里实时调用sin函数但嵌入式MCU的浮点运算相对慢载波频率一次中断就要算三相信号代价不划算。更常见的做法是离线生成一张256点的int16_t正弦表峰值设为32767运行时只做查表和乘法。#define SIN_TABLE_SIZE 256 static int16_t sin_table[SIN_TABLE_SIZE]; void Build_Sin_Table(void) { for (uint16_t i 0; i SIN_TABLE_SIZE; i) { float angle 2.0f * 3.1415926f * i / SIN_TABLE_SIZE; sin_table[i] (int16_t)(32767.0f * sinf(angle)); } }如果你不想让目标平台在启动时生成表也可以直接在PC上算好一个const数组复制进工程。两种方式都行看项目习惯。表长度256点对于10kHz载波已经足够细腻低频时相位每次只挪一小格输出频率分辨率很高。有了正弦表之后每个PWM周期要更新三相的比较值。中心对齐模式下比较值CCR决定了占空比我一般先算一个中间值half_arr代表50%占空比对应的比较值然后叠加调制深度和正弦值。三相之间相位差120度对应256点正弦表就是偏移85个点和171个点这个偏移用8位索引自然溢出取模即可。核心代码类似void Update_SPWM_Compare(uint32_t phase_acc, int16_t modulation) { uint16_t idx (phase_acc 24) 0xFFu; int16_t sA sin_table[idx]; int16_t sB sin_table[(idx 85) 0xFFu]; int16_t sC sin_table[(idx 171) 0xFFu]; int32_t ccrA half_arr (int32_t)modulation * sA * half_arr / 32768; int32_t ccrB half_arr (int32_t)modulation * sB * half_arr / 32768; int32_t ccrC half_arr (int32_t)modulation * sC * half_arr / 32768; TIM1-CCR1 (uint32_t)ccrA; TIM1-CCR2 (uint32_t)ccrB; TIM1-CCR3 (uint32_t)ccrC; }modulation就是调制深度范围通常限制在0到0.95之间直接决定输出电压幅值。中心对齐模式下当CCR等于half_arr时输出占空比为50%输出相电压平均值为0CCR大于half_arr时上管导通时间变长输出电压往正方向走。公式里除以32768是为了把正弦表的满幅值归一化整个过程只用整数和少量乘法在中断里执行非常轻量。3.2 用相位累加器平滑调节输出频率变频的难点不在“频率变了”而在“怎么平滑地变”。如果直接改正弦表的查表步长跳变时波形会出现毛刺电流冲击大。我用一个32位相位累加器来解决这个问题把整个正弦波周期映射成0到2^32的整数范围。每个PWM周期到来时往累加器里加一个相位增量累加器的高8位正好对应256点正弦表的索引。相位增量的计算精度直接决定了输出频率的精度。假设载波频率是fpwm目标输出频率是fout那么每个PWM周期需要走过的相位比例就是fout/fpwm映射到32位定点就是phase_increment (uint32_t)(fout * 4294967296.0 / fpwm);比如载波10kHz想输出50Hz增量算下来大约是21474836一个很小的整数每次累加器加上这个数高低位配合得很自然。想输出49.99Hz增量只差一点点32位定点数完全能表达频率分辨率远超普通16位变量。频率改变时也不能直接跳增量而是通过加减速斜坡逐步靠近目标值。我在主循环里每1ms按设定的加减速时间调整当前增量比如设定2秒从0加速到50Hz那每毫秒就把当前增量往目标增量方向挪一小格。这样电机启动时不会因为频率突变产生过大的电流冲击这也是变频器行业里最基础的启动逻辑。3.3 低频补偿与母线电压归一化V/F控制里有两条曲线要处理好一条是V/F基准曲线另一条是低频补偿。基准曲线很简单就是设定“额定相电压峰值除以额定频率”得到每Hz对应多少伏。实时频率乘以这个系数再叠加上低频补偿电压就是当前时刻的目标相电压峰值。低频补偿的目的是抵消定子电阻压降一般只在几Hz以下的区间起作用补偿量从5Hz附近开始线性衰减到0。目标电压算出来之后还要除以实际母线电压才能得到调制深度。这就是标题里“灵活设定输入输出”在算法层的体现输入是目标频率和母线电压输出是随工况自动调整的调制深度。如果忽略这一步直接把目标电压和固定母线电压做比例一旦母线电压跌落输出电压幅值就会跟着掉重载时电机转速明显不稳。uint16_t v_peak_target VF_Slope_Calc(freq_out) LF_Comp_Calc(freq_out); int16_t modulation (int16_t)(v_peak_target * 32768 / vdc_measured); if (modulation MAX_MODULATION) modulation MAX_MODULATION;母线电压采样必须做低通滤波否则单个PWM周期内的采样噪声会让调制深度抖动。我一般用一阶滤波即可滤波时间常数取几毫秒既保留动态响应又滤掉开关噪声。调制深度上限我习惯设在0.95左右留一点余量给非线性空区和死区造成的电压损失。4. 支持二次开发的接口设计让代码不被动标题里“支持二次开发”不是一句空话它意味着代码必须能被别人接管围绕同一块主控改参数、换算法、加协议时不用推倒重来。我在设计这套程序时把可扩展性当成和功能同等重要的指标具体体现在状态封装、参数表和扩展接口三个方面。4.1 用结构体把逆变器运行时状态封装起来三相逆变器涉及的状态很多当前频率、目标频率、相位累加器、调制深度、加减速时间、各种保护阈值。如果这些变量全是全局量模块之间互相可见写起来很爽但对二次开发者来说是灾难根本分不清哪个变量被谁修改。我的做法是把所有运行状态集中到一个结构体里每个逆变器实例对应一个结构体变量。typedef struct { uint32_t carrier_freq_hz; uint32_t output_freq_hz; uint32_t target_freq_hz; uint32_t accel_ms; uint32_t decel_ms; uint16_t max_modulation; uint16_t low_freq_comp; volatile uint32_t phase_acc; int16_t modulation; int32_t phase_inc_current; int32_t phase_inc_target; uint16_t vdc_mv; uint16_t overcurrent_th; } InverterCtx_t;这种设计有一个很典型的好处如果某台机器需要做双电机驱动直接定义两个InverterCtx_t实例每个实例绑定各自的定时器和ADC通道代码不用复制一份再改变量名。算法函数只要接受结构体指针内部通过指针访问所有状态复用性比全局变量方案高一个量级。即使现在不需要多电机这个习惯也值得养成。4.2 参数表与通讯交互让输入输出可以灵活设定“灵活设定输入输出”落实到产品层面就是用户可以随时修改额定频率、加减速时间、保护阈值等参数而不是每次改代码重新烧录。我这边的方案是一张参数表表里每一项都有唯一的地址、取值范围、默认值和当前值存储在Flash中。上位机或通讯协议按地址读写这张表运行参数就能不中断地调整。typedef struct { uint16_t addr; uint16_t min_val; uint16_t max_val; uint16_t default_val; uint16_t cur_val; } ParamItem_t; static ParamItem_t param_table[] { {0x00, 10, 400, 50, 50}, /* 输出频率单位0.1Hz */ {0x01, 8, 16, 10, 10}, /* 载波频率单位kHz */ {0x02, 500, 2000, 1000, 1000},/* 加速时间单位ms */ {0x03, 500, 2000, 1000, 1000},/* 减速时间单位ms */ {0x04, 7800, 9400, 9000, 9000},/* 过压阈值 */ };运行指令也可以走同样的表目标频率写进去应用层状态机检测到变化后启动斜坡停机指令写入后程序先把输出频率斜降到0再封锁PWM。这套机制的好处是把“设定和操作”统一成读写参数协议层不用针对每个命令写单独的处理函数新功能上线时只需往参数表里加一行。4.3 预留扩展点从开环V/F走向闭环与多协议逆变程序的二次开发通常朝向几个方向加电流闭环、换SVPWM算法、接不同的通讯协议、升级故障保护逻辑。预留扩展点的关键是让软件模块之间通过函数或回调解耦。我的算法层向外暴露的接口就只有两个一是设置目标频率二是执行载波中断里的占空比更新。闭环PI、SVPWM扇区判断都只在这个接口内部做替换。故障保护也是一个典型的扩展场景。硬件比较器一旦触发过流会通过刹车输入直接封锁PWM这是硬保护软件层面同时通过中断记录故障类型。我在应用层留了一个故障上报函数不同故障码对应不同处理策略新的故障类型只要注册一个新处理回调即可。通讯方面串口解析函数本身独立成层将来想换成CAN或Modbus不需要碰算法层和驱动层。5. 从仿真到带载现场调试踩过的坑代码写出来是一回事能稳定跑起来是另一回事。三相逆变器是典型的“一上强电就容易炸管”的设备调试顺序如果不对吃亏的是钱包和经验。我的调试习惯是把整个过程拆成空载、低压带载、强电带载三个阶段每个阶段都有明确的验证目标。5.1 空载调试先把六路波形和相位关系调出来不要一上来就把540V母线电加进去先用控制板的低压电上电用示波器观察TIM1的六路输出。首先确认三相上桥臂CH1、CH2、CH3的PWM频率和占空比包络是否正常再切换通道看互补输出CH1N、CH2N、CH3N确认同一相的两路之间是否存在死区时间。死区如果不明显把示波器时间轴拉宽仔细观察上升沿和下降沿的间距。相位关系也必须在空载阶段确认。CH1与CH2应该差120度CH2与CH3再差120度。不要只凭代码里“idx85”就认为相位一定对因为某些驱动电路的信号延迟不同空载看波形是最直接的验证手段。如果发现CH2占空比变化的顺序反了通常是某一路比较值更新顺序错了或者三相接线相序标反这个在低压阶段改起来很容易强电阶段返工就很危险。空载阶段我还会做一个固定占空比测试屏蔽正弦表让三路都输出50%占空比确认CCR写入配置没问题然后让三路分别输出0%和100%确认比较值在边界情况不会有异常跳变。这些边界状态往往是异常电流的根源提前确认能省掉后面大量排查时间。5.2 死区设置与上下桥直通最容易冒烟的隐患三相逆变器最常见的炸管原因不是过压而是同一相上下桥臂直通。直通发生的原因通常有两个死区时间不够或者上下管的极性配置错误。死区时间不能只从IGBT/MOSFET数据手册的关断延迟里随便挑一个值必须把驱动芯片的传输延迟、功率管的关断延迟、下降时间都算进去。工程上常用估算式死区时间 ≥ 驱动延迟 功率管关断延迟 功率管下降时间 安全裕量我实际用的功率管关断延迟大约200ns下降时间约100ns驱动IC还有几百纳秒的传输延迟所以死区设在2微秒左右比较稳妥。死区设得越大越安全但输出电压谐波会增加低频时还会明显感受到波形“缺角”所以也不能无脑加大。调完死区后用示波器放大观察上下管导通沿确保一个关断完成之前另一个没有开始导通。另一个容易忽略的点是使能时序。PWM初始化时比较值默认是0合理但高级定时器的互补输出使能后如果刹车配置不对输出可能直接进入无效电平状态反而让某一相上下管状态异常。我建议在硬件上加强下拉在软件上先检查CCR和死区配置再启动PWM。如果板子上有硬件使能引脚把它和单片机复位时序配合好避免MCU复位期间功率管处于不确定状态。5.3 带载升压测试预充电、过流保护与频率斜坡空载波形确认后可以开始给母线加低压电并接入小功率电机比如先用50V直流母线跑10Hz看电机是否能平稳转起来。带载测试阶段最容易发现问题尤其是电流波形。用电流探头观察U相电流正常应该是正弦波如果在某个角度出现尖峰往往意味着死区补偿不足或者母线电压采样有毛刺。此时不要想着调PID掩盖问题先把现象定位到具体环节。上强电之前母线电容的预充电绝对不能省。大容量母线电容在首次上电瞬间近似短路不经过预充电电阻直接合闸整流桥、保险丝、电容都会承受巨大冲击。我习惯用主接触器加预充电阻的方案先通过电阻给电容充电到母线电压的90%以上再短接电阻完成上电。程序里也要在母线电压低于欠压阈值时禁止启动PWM。最后是过流保护和频率斜坡的配合。开环V/F下输出频率的斜坡时间直接决定启动电流冲击。如果把2秒的斜坡改到200ms启动瞬间电流可能翻几倍。我一般把保护阈值设为额定电流的1.5到2倍过流触发后立刻封锁全部PWM同时记录故障原因。硬件比较器触发的刹车通道优先级高于软件因为中断响应再快也要几十个周期硬件可以在几个纳秒内把PWM拉低这一点在强电环境里非常关键。做这套程序我最大的感受是三相逆变器的软件并没有想象中那么高不可攀但它对细节的要求远超普通单片机项目。如果你想快速上手建议先拿一块成熟的开发板在低压下按照“空载确认波形、轻载跑V/F、再上强电”的顺序逐步验证。每一个环节都留好测试点把示波器和电流探头当成自己的眼睛很多坑都能在冒烟之前被发现。等这套基础程序稳定了再去扩展SVPWM、闭环控制或者新通讯协议路就会顺很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑