公司动态
基于STM32F103的双极性SPWM逆变器设计与调试指南
简介本资源是一套基于STM32F1系列单片机ARM Cortex-M3内核实现双极性正弦脉宽调制SPWM逆变控制的完整Keil MDK-ARM工程面向电力电子、嵌入式控制及电机驱动方向的中级以上开发者与高校实践者解决直流→高质量正弦交流电转换中的波形生成、载波调制与硬件协同控制问题。压缩包含266个文件主体为42个C源码含SPWM算法、GPIO/RCC/USART外设驱动、48个头文件h、44个编译中间文件d及41个目标文件o辅以uvprojx工程配置、hex烧录文件与调试配置dbgconf总大小10.83MB。已有113人学习下载工程结构规范预览可见stm32f10x_gpio.__i、system_stm32f10x.__i等标准外设库依赖说明其具备可移植性与工业级开发特征读者可直接编译运行深入理解双极性SPWM定时器配置逻辑、死区控制要点及逆变板保护机制实现路径。 前阵子帮朋友调一块基于STM32F103的双极性SPWM逆变板代码是在Keil5里打开的工程名就叫“XU程序-STM32-Code”。这块板子说起来也不算复杂就是把直流电通过全桥逆变、SPWM调制、LC滤波之后变成交流电但真正动手调起来踩坑的地方一点都不少。今天就把这个项目的完整设计思路、硬件电路要点、软件代码实现和调试过程整理出来给正在做或者准备做SPWM逆变器的朋友一个参考。这个项目适合几类人看一是正在学STM32高级定时器PWM输出的二是准备做逆变器、UPS、变频电源这类电力电子装置的三是手里有类似板子但不知道怎么下手调试的。内容我会按“整体设计 → 硬件电路 → 软件代码 → 调试排错”的顺序展开所有代码均基于STM32标准库工程文件用Keil5打开即可编译下载。1. 项目概览与整体设计思路1.1 为什么用STM32F1做SPWM逆变先聊一个很多人纠结的问题市面上做逆变器有专用芯片比如EG8010也有用DSP的比如TMS320F28035为什么这个项目偏偏选STM32F103我的答案是综合成本、资料丰富度和开发效率STM32F1是学习SPWM逆变的最佳入门平台没有之一。STM32F103主频72MHz虽然不算高但生成20kHz以内的SPWM载波完全够用。更重要的是F1系列内置了TIM1和TIM8两个高级定时器这两个定时器天生就是为电机控制和逆变器准备的——它们支持互补PWM输出、硬件死区插入、刹车输入、重复计数等功能。做全桥逆变你需要两路带死区的互补PWMTIM1的CH1和CH1N、CH2和CH2N刚好能输出两对正好驱动H桥的四个管子。有人可能会说用普通定时器软件延时也能做死区但软件延时的问题是中断响应不稳定一旦主循环里有其他任务死区时间就会忽长忽短轻则波形畸变重则上下管直通炸管。硬件死区就没有这个问题配置一次之后完全由定时器硬件控制CPU零干预可靠性高得多。再说开发环境。Keil5标准库几乎是国内STM32学习者的标配网上教程铺天盖地遇到问题一搜就有答案。相比之下DSP的开发门槛高EG8010虽然简单但灵活性差想调调载波频率、改改调制比都得看芯片脸色。所以这个项目选STM32F1本质上是选了一套“够用、好用、容易学”的方案。1.2 双极性SPWM与单极性SPWM的本质区别SPWM的全称是Sinusoidal Pulse Width Modulation正弦脉宽调制核心思想是把正弦波等效成一系列宽度按正弦规律变化的脉冲。但这个项目标题里特别强调了“双极性”这就有讲究了。单极性SPWM的特点是在正弦波的正半周期逆变桥只有一对管子高频开关输出只有Ud和0两种电平负半周期换另一对管子高频开关输出只有-Ud和0。也就是说单极性调制的输出电压幅值始终在“正母线电压/零/负母线电压”之间切换。双极性SPWM则是另一套逻辑在一个载波周期内H桥的对角管子同时导通、同时关断输出电压始终在Ud和-Ud之间切换不存在0电平。它的控制逻辑比较直观正弦波与三角波比较正弦大于三角波时输出Ud小于则输出-Ud。既然双极性SPWM输出电压跳变幅度大、开关损耗也略高为什么还要用主要有三个原因第一控制逻辑简单。双极性调制不需要区分正负半周期一组比较逻辑走到底程序实现最简洁。用STM32实现时只需要一路PWM和一路反相PWM两张表合一即可。第二输出谐波集中度高。双极性SPWM的谐波主要集中在载波频率及其倍频附近低次谐波含量少LC滤波器设计起来更容易。第三对负载的适应性好在非线性负载下表现比单极性更稳健。当然双极性也有它的短板同一桥臂上下管每个开关周期都要切换共模干扰相对大一些开关损耗也高一些但这些在低频小功率场景下影响不大。1.3 逆变系统整体架构这个项目的整体架构其实很清晰用一句话概括就是SPWM信号由STM32产生经过驱动电路功率放大推动H桥逆变再经过LC低通滤波还原成正弦交流电。完整信号链是STM32F103的TIM1输出两路带死区的互补SPWM → IR2104半桥驱动芯片做电平转换和推挽放大 → H桥四个MOSFET常见型号IRFP460或IRF540按SPWM规律导通 → 变压器如果做隔离升压或不带变压器直接输出 → LC低通滤波 → 交流输出。这里要特别提一下驱动电路。STM32的GPIO输出电压只有3.3V驱动能力也有限直接推MOSFET是不现实的。IR2104是高侧自举式半桥驱动器内部集成了电平转换和死区匹配逻辑一路PWM输入就能得到互补的两路驱动输出。用两片IR2104分别驱动H桥的左半桥和右半桥电路结构就会非常简洁。供电方面STM32用3.3VIR2104工作电压10~20V一般取12VH桥母线电压根据输出功率需求决定这个项目里取的是24V直流输入属于典型的低压小功率实验平台。2. 逆变板硬件电路核心设计2.1 H桥拓扑与MOSFET选型H桥是全桥逆变的核心功率级由四个开关管Q1~Q4组成负载或变压器初级接在桥臂中点A和B之间。对角管Q1Q4同时导通时负载电流从左往右流Q2Q3同时导通时电流从右往左流。双极性SPWM正是靠这种高频切换在输出端形成正负交替的脉冲序列。MOSFET选型上我建议按“电压余量2倍以上电流余量1.5倍以上”的原则来。24V母线电压选60V耐压的管子就够如果后续打算拉到48V或更高直接上100V的管子更省心。IRF540是经典件耐压100V、电流28A但它的输入电容Qg比较大驱动能力弱的时候开关速度会受影响。IRFP460耐压500V适合做高压后级低压下用有点浪费。这个项目里用的是IRF540N搭配IR2104驱动实测开关频率20kHz完全没压力。选管时需要关注的关键参数包括Rds(on)导通电阻决定导通损耗、Qg栅极总电荷决定驱动难度、tr/tf开关时间决定开关损耗。低压大电流场景优先看Rds(on)高压小电流场景优先看耐压和开关速度。参数IRF540NIRFP460说明耐压Vdss100V500V至少2倍母线电压最大电流Id33A20A按1.5倍额定电流估算Rds(on)44mΩ270mΩ低压下540N发热更小Qg49nC210nCQg小的更好驱动适合场景低压大电流高压小电流按母线电压选择2.2 驱动电路与自举电容计算IR2104的典型应用电路并不复杂HIN接STM32的PWM输出IN端SD引脚接使能信号LO和HO分别输出低侧和高侧驱动信号VB和VS之间接自举电容。自举电容的取值是个关键点。它的作用是在低侧管导通期间给高侧管的驱动电路充电从而保证高侧管有足够的栅极驱动电压。电容太小高侧驱动电压维持不住太大充电时间变长占空比高的时候跟不上。常用经验公式是C ≥ Qg / ΔV其中Qg是MOSFET的栅极总电荷ΔV是允许的电压跌落一般取2V左右。IRF540N的Qg约49nC算下来C ≥ 24.5nF实际工程上取10~47μF都常见。我实测用22μF/25V的钽电容配合1N4148快恢复二极管稳定可靠。还要注意IR2104的输入PWM信号要求高电平阈值最低2.7VSTM32的3.3V输出刚好卡在边界。如果板上3.3V纹波大建议加个100Ω串联电阻提升抗干扰能力或者用5V供电的逻辑门缓冲一下。这个项目里直接从STM32的PA8引脚接IR2104的HIN接线尽量短实测没有问题。2.3 死区对波形的影响所谓死区就是同一桥臂上下管切换时刻意插入的一小段“同时关断”的时间目的是防止上下管同时导通造成母线直通短路。直通一旦发生电流会瞬间飙升炸管几乎不可避免。STM32高级定时器对死区的处理非常优雅TIM1的CCER寄存器里配置互补输出极性BDTR寄存器里的DTG[7:0]位决定死区时间。最关键的是死区是硬件自动插入的——你只需要配置好死区时间定时器输出PWM时硬件会自动在同桥臂上下管之间插入这段“同步关断”时间CPU完全不用管。死区时间取多少合适我给出一个经验公式死区时间 ≥ MOSFET关断延迟td(off) - 开通延迟td(on) 裕量。IRF540N的典型关断延迟约50ns开通延迟约15ns理论死区35ns就够了。但实际电路中还要考虑IR2104的传输延迟、PCB走线寄生电容等因素工程上建议取1~3μs。这个项目取的是1μs在20kHz载波下占2%的周期对输出波形畸变的影响可以忽略同时安全性有充分保障。2.4 LC滤波器的设计与参数计算H桥输出的是一串正负脉冲里面既有50Hz或60Hz的基波成分也有大量载波频率及其倍频的高频谐波。LC低通滤波器的任务就是把这些高频成分滤掉只让基波通过在输出端得到光滑的正弦波。LC滤波器的截止频率需要远低于载波频率同时远高于输出频率才能有效衰减谐波。一般取载波频率的1/10~1/5。这个项目载波20kHz输出50Hz截止频率取2kHz左右比较合适。选定截止频率之后电感电容的取值有两条路一是先定电容按无功功率估算再算电感二是先定电感按纹波电流估算再算电容。我习惯用后者额定负载下电感纹波电流取额定电流的20%~30%L Ud / (4 × fsw × ΔI)电容则按截止频率反推C 1 / (2π × fc)² / L。我用24V母线、2A额定电流设计ΔI取0.5AL 24 / (4 × 20000 × 0.5) 600μH取标准值1mH再按截止频率1.6kHz反推C 1 / (2π × 1600)² / 0.001 ≈ 10μF取10μF/50V的CBB电容。这个参数实测输出波形干净THD约3%对一般负载完全够用。3. 软件实现SPWM波形生成与定时器配置3.1 SPWM生成方法选型对比SPWM波形生成有四种主流方法每种方法在STM32F1上的实现难度和效果差异很大。查表法预先计算好一个正弦周期内各点的占空比存成数组运行时按顺序查表输出。优点是CPU开销极小控制实时性好缺点是通过表格点数和载波频率共同决定输出频率动态调频比如变频器输出5~50Hz需要重新计算表格。实时计算法在定时器中断里实时计算正弦值并更新比较寄存器。优点是调频方便还能随时调整调制比缺点是占用CPU时间中断频率高比如20kHz时72MHz的主频下每个中断周期只有3600个时钟周期跑sin函数有点紧张。定时器DMA搬运法把整张SPWM表放进内存用DMA定时搬运到比较寄存器CPU几乎零负担。适合超高频载波和低功耗场景但F103的DMA配置稍微繁琐代码可读性差。更新中断查表结合查表为主更新中断只做索引递增和溢出判断。这是工程上最常用的折中方案兼顾了代码简洁和响应及时性。这个项目用的就是纯查表法。原因是逆变器输出频率固定50Hz调制比固定调压通过硬件或手动可调不需要动态变频。查表法代码最简单——中断里只需要“取数、赋值、索引加一”三步操作CPU占用率几乎可以忽略。3.2 SPWM表的离线计算SPWM占空比的计算公式是Duty(k) 0.5 0.5 × M × sin(2πk/N)其中M是调制比0M1它决定输出基波幅值N是一个正弦周期内的采样点数也就是载波比同步调制时等于载波频率除以输出频率k从0到N-1。关键参数选择上N取多少载波频率20kHz输出频率50HzN 20000/50 400。也就是说一个正弦周期被等分成400个点每个点对应一个PWM周期50μs相邻点的角度步进是0.9°等效出来的正弦波阶梯感很小谐波表现不错。这个表格可以用一段Python脚本离线生成也可以直接在STM32的启动时用代码计算。我建议先用Python算好把数组直接复制进工程这样烧录后立即工作调试阶段少一个变量。Python生成代码如下import math N 400 M 0.85 for k in range(N): duty 0.5 0.5 * M * math.sin(2 * math.pi * k / N) arr int(duty * 999) # 对应10位PWM分辨率 print(f{arr},, end) if (k 1) % 10 0: print()调制比M取0.85留了15%的余量防止过调制。如果M超过1正弦波峰值会被削平输出电压出现平顶谐波立刻恶化。这个坑我踩过一上来贪心设M1.0结果波形顶部平了THD飙升到8%。3.3 TIM1高级定时器配置与死区时间代码TIM1是STM32F1的高级定时器支持互补输出和死区配置这正是逆变器PWM生成的核心外设。配置代码分四步第一步初始化GPIO。PA8是TIM1_CH1PA7是TIM1_CH1N都要配置为复用推挽输出。注意互补通道必须开启复用功能否则引脚只是普通GPIO不会有PWM输出。第二步配置定时器时基。需要算清楚预分频器PSC和自动重载值ARR。STM32F103的TIM1挂在APB2总线上时钟72MHz。目标PWM频率20kHzARR 72MHz / (PSC1) / fPWM。若PSC0ARR 72e6/20000 3600。注意10位PWM分辨率意味着ARR最大1023但我们的ARR3600对应PWM分辨率超过10位。但如果直接算ARR3600则占空比更新比较寄存器的范围是0~3599。占空比表里换算公式就变成了CCR duty × ARR。第三步配置PWM模式、互补输出、死区时间和输出极性。死区时间通过BDTR寄存器的DTG位配置。DTG[7:0]的计算规则根据死区时间范围有两种公式时间大于3μs时用混合公式。这里1μs属于典型值直接查表或用公式都行。如果按“高8位对应128×Tdtg低7位对应64×Tdtg”的规则配置值DTG52对应1μs左右。实际代码中我习惯这样配置TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period 3600; // 20kHz TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OutputNState TIM_OutputNState_Enable; TIM_OCInitStructure.TIM_Pulse 1800; // 初始50%占空比 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCNPolarity TIM_OCNPolarity_High; TIM_OCInitStructure.TIM_OCIdleState TIM_OCIdleState_Set; TIM_OCInitStructure.TIM_OCNIdleState TIM_OCNIdleState_Reset; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_BDTRInitStructure.TIM_OSSRState TIM_OSSRState_Disable; TIM_BDTRInitStructure.TIM_OSSIState TIM_OSSIState_Disable; TIM_BDTRInitStructure.TIM_LOCKLevel TIM_LOCKLevel_OFF; TIM_BDTRInitStructure.TIM_DeadTime 52; // 约1us死区 TIM_BDTRInitStructure.TIM_Break TIM_Break_Disable; TIM_BDTRInitStructure.TIM_BreakPolarity TIM_BreakPolarity_High; TIM_BDTRInitStructure.TIM_AutomaticOutput TIM_AutomaticOutput_Enable; TIM_BDTRInit(TIM1, TIM_BDTRInitStructure);这里特别提醒一个新手必踩的坑TIM1配置完成后必须使能MOE主输出使能位MEM和MOE寄存器中的MOE位置1否则PWM根本不会输出。标准库中通过TIM_CtrlPWMOutputs(TIM1, ENABLE);实现。我见过不少人波形不出来折腾半天发现就是少这一行。3.4 主循环与中断服务函数双极性SPWM的软件框架很简单一个定时器更新中断一个SPWM表一个索引指针。中断服务函数的核心操作是查表更新比较寄存器。每个更新中断也就是每个载波周期50μs中把SPWM表中的当前值写入TIM1-CCR1然后索引加一到表尾就回卷。代码如下volatile uint16_t spwm_index 0; void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); TIM_SetCompare1(TIM1, spwm_table[spwm_index]); spwm_index; if (spwm_index SPWM_POINTS) spwm_index 0; } }main函数里只需要完成外设初始化和使能中断主循环可以做其他事情比如按键调压、OLED显示SPWM输出完全由中断驱动互不干扰int main(void) { SystemInit(); GPIO_Config(); TIM1_Config(); NVIC_Config(); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); while (1) { // 主循环可处理按键、显示等低实时性任务 } }3.5 Keil5工程搭建与烧录调试要点Keil5打开这个工程的体验取决于你的芯片包装没装对。第一次打开工程若提示“Device not found”或者编译报错找不到头文件多半是F1系列的DFP包Device Family Pack没装。打开Pack Installer搜索“STM32F1”安装对应版本即可。工程里要注意的地方标准库的宏定义USE_STDPERIPH_DRIVER和STM32F10X_HD必须加在C/C Compiler的Define里否则编译会报错说找不到stm32f10x_conf.h或者外设寄存器定义对不上。烧录调试用ST-Link最省心。接线方式ST-Link的SWDIO接PA13SWCLK接PA14GND共地3.3V接板子电源。如果连接失败先检查驱动有没有装好再确认板子供电是否正常。ST-Link Utility这个工具也值得安利一下它可以在不打开Keil的情况下独立烧录hex/bin文件还可以读取Flash内容和选项字节调试烧录器本身的问题时特别好用。4. 常见问题与调试实录4.1 输出波形异常没有波形、波形畸变调试逆变器第一件事就是看波形。把示波器探头夹在H桥输出端LC滤波前先看有没有PWM脉冲再看LC滤波后有没有正弦波。如果滤波前就没有PWM问题大概率出在三个地方一是MOE没使能TIM_CtrlPWMOutputs(TIM1, ENABLE)没调用二是定时器中断没有触发查NVIC配置和中断服务函数名是否匹配中断服务函数名在启动文件里定义必须严格一致三是GPIO复用功能没配置对PA7/PA8引脚没连出来。波形畸变的情况要分两类正弦波顶端削平说明调制比M太大出现过调制减小M即可正弦波某个区域明显凹陷或抖动多半是母线电压在重载下跌落或者SPWM表点数太少导致阶梯感太强。排查思路就是先空载看波形排除负载影响再逐步加负载观察变化。4.2 上下管直通与过流炸管这个问题最严重也最危险。出现直通轻则MOSFET发热严重重则直接冒烟。排查步骤依次是先确认死区配置有没有生效。把示波器两个通道分别夹在同一桥臂的上下管栅极信号上放大到微秒级别看两路信号之间是否有“同时为低”的间隙。如果没有间隙说明死区没生效检查BDTR寄存器配置和死区时间值。再看驱动芯片的供电和自举电路。IR2104的VB-VS间电压如果低于8V左右高侧管驱动不足管子会工作在半导通区发热陡增。用万用表量VB-VS间电压值低于8V就换大一点的自举电容或者换漏电流更小的自举二极管。最后检查母线电容。如果母线电容容量不足大电流冲击会让母线电压瞬间跌落干扰PWM控制逻辑间接导致异常导通。实测24V系统至少需要470μF以上的电解电容而且要靠近H桥放置。4.3 Keil5常见问题编译报错、点击调试闪退、左侧目录不显示这三个问题在Keil5里出现频率极高。编译报错90%是芯片包没装好或宏定义缺失按上一节的方法解决即可。点击调试按钮直接闪退一般是ST-Link的驱动版本太老和Keil版本不兼容更新ST-Link驱动到最新版就能解决。左侧目录不显示是因为Project窗口被收起了点击工具栏上的“Project”图标或按快捷键AltP调出来即可。还有一种情况是工程文件路径包含中文或空格Keil5会表现出一堆莫名其妙的怪问题比如编译时文件找不到、调试时无法加载程序。工程路径尽量用纯英文文件夹层级也不要太深。4.4 ST-Link连接失败的排查思路ST-Link连接失败是嵌入式调试最常见的开局问题。按这个顺序排查基本能解决第一確認接线。SWDIO、SWCLK、GND三条线是底线3.3V也最好接上但千万不能接反接反有烧板风险。第二在Keil的Options→Debug→Settings里点“Flash Download”选项卡如果设备ID显示一串0或FF说明连接不畅——查线、查驱动、查板子供电顺序。第三如果ST-Link之前被刷过固件或者升级失败用STM32 ST-LINK Utility的“Firmware Update”功能重新刷一遍固件。还有一个高级坑目标板APB2时钟配置错误导致SWD引脚被复用成普通功能。理论上SWD引脚有独立调试功能但如果程序里把PA13/PA14的复用功能配置错了调试口锁死连接不上。解决方法是按住复位按键在Keil里点击Connect的瞬间松开复位趁程序还没跑起来把Flash擦掉。5. 经验总结与调试心得这块双极性SPWM逆变板前后调了一个多星期踩了不少坑最后总结几条经验供大家参考。第一先硬件后软件先开环后闭环。板子到手先不写SPWM程序直接写一个50%占空比的固定PWM测试驱动电路确认H桥、驱动、供电都没问题再上SPWM。这样出了问题能快速定位到底是硬件故障还是软件逻辑问题。第二示波器是电力电子的眼睛。调试逆变器一定要养成“先看栅极波形再看输出波形”的习惯。栅极波形决定开关管工作状态输出波形决定控制算法效果。如果栅极波形有振铃、有过冲先解决驱动问题再谈控制效果。没有示波器的话至少要弄个逻辑分析仪看PWM信号别全靠万用表猜。第三安全永远是第一优先级。MOSFET炸管并不可怕可怕的是炸管时没有保护。调试时母线电源串一个限流电阻100Ω~1kΩ或者电子负载限流模式把故障电流限制在安全范围内。我调试时前两次炸管都是因为忘记串电阻后来养成习惯后一次都没再炸过。第四SPWM表的调制比别贪心。M0.85到0.9是安全区间一旦超过1.0输出波形严重畸变THD飙升LC滤波器也无能为力。如果你需要更高的输出电压优先提高母线电压而不是往死里压榨调制比。如果后续要扩展可以考虑的方向有加电压电流双闭环PID控制、把查表法改成实时计算实现变频输出30~100Hz可调、增加过流/过温保护逻辑、用DMA方式减轻CPU负担。这个项目做完之后我对STM32的定时器、中断系统和电力电子控制总算有了真刀真枪的理解比光看教程印象深得多。如果你也在做类似的项目卡住了欢迎对照这篇内容一步步排查相信很快就能让板子跑起来输出漂亮的正弦波。本文还有配套的精品资源点击获取