STM32单相逆变SPWM代码实战:可调压调频全解析

STM32单相逆变SPWM代码实战:可调压调频全解析 简介基于STM32实现的单相逆变器控制工程代码面向嵌入式开发与电力电子方向的实践者覆盖直流转交流过程中的调压调频策略与PWM驱动控制。代码围绕V/F变压变频控制策略展开包含全桥逆变电路所需的PWM生成逻辑、定时器配置、故障检测与保护模块兼顾风机、水泵等感性负载的应用场景部分目录素材显示其源自电子设计竞赛训练项目具有一定实战参考价值。压缩包共270个文件约9.48MB主要包含C语言源码.c/.h、Keil工程配置文件.uvprojx/.uvoptx、编译固件.hex/.axf以及原理图与说明文档.pdf/.docx并保留PWM生成、控制器初始化等模块化文件。目前已有4266人学习下载。对于希望理解逆变器软件实现、调压调频算法落地或准备电子竞赛的读者这套代码提供了从工程框架到驱动层实现的完整路径可直接对照学习定时器配置、PWM占空比调节和故障保护流程减少从零搭建工程的时间成本。 做单相逆变这块有一阵子了最早是在网上东拼西凑了一堆代码调出来波形稀烂后来干脆沉下心把SPWM、定时器、死区这些底层逻辑彻底捋了一遍才算真正把“基于STM32的单相逆变代码可调压调频”这套东西跑通。这个项目最适合两类人一类是做电源、UPS、变频电源的嵌入式工程师另一类是毕设或者电子设计竞赛想做逆变器却不知道从哪下手的同学。核心价值很简单——给你一套能直接改、能直接烧录的代码思路配合硬件能把220V/50Hz或者任意频率、任意电压的正弦波输出调出来。1. 项目概述与整体方案设计1.1 需求拆解单相逆变到底在做什么先把目标说清楚。所谓单相逆变就是把直流电比如24V、48V、310V的母线电压变成单相交流电。标题里有两个关键词可调压、可调频。调压指的是输出正弦波的幅值可以改调频指的是输出正弦波的频率可以改比如从40Hz调到60Hz甚至调到400Hz中频电源。这个需求听起来简单但拆开来看有四个核心点开关管的PWM驱动波形必须是一对互补的、带死区的SPWM波输出要经过LC滤波把高频开关纹波滤掉得到光滑的正弦波调压的本质是调制比M的变化调频的本质是正弦波参考频率的变化整个系统要有保护逻辑过流、过压、母线欠压都要有响应根据我的经验用STM32做逆变首选带高级定时器的型号STM32F103系列、F334系列、F407系列都可以原因后面细说。我用的最多的是STM32F103C8T6成本低、资料多、性能也足够跑20kHz的SPWM。1.2 技术路线选择为什么是SPWMSTM32逆变器的PWM生成方式有几条路方波逆变效果太差谐波大、阶梯波逼近正弦但电路复杂、PWM调制。PWM里又分SPWM正弦脉宽调制和SVPWM空间矢量主要用于三相。对于单相逆变SPWM是最经典、最成熟的方案。SPWM的实现方式也有好几种硬件比较器生成用专门的PWM芯片如EG8010简单但灵活度低软件定时器中断翻转IO资源占用大频率做不高定时器硬件PWM查表更新比较值这是STM32方案里的最佳选择为什么选STM32而不是纯模拟方案因为STM32的高级定时器TIM1/TIM8自带互补PWM输出、死区插入、刹车保护功能。这三样恰好是逆变器驱动最核心的硬件支撑。你用普通定时器也能出两路PWM但互补和死区都要软件处理万一程序跑飞就有上下管直通炸MOS管的风险。高级定时器把这些功能硬件化安全性高了一个量级。1.3 调压调频的核心思路先说结论调压和调频在代码里是两件相对独立的事。调频改变PWM比较值查表的步进速度。比如原来正弦表有400个点载波频率20kHz每个载波周期更新一次比较值50Hz情况下每周期有400个点。如果你想让输出变成100Hz同样20kHz载波下每周期只剩200个点。可以改查表步进每次跳2个点也可以改载波频率看你的需求。调压改变调制比M。SPWM的占空比公式是 (D 0.5 0.5 \times M \times \sin(\theta))M从0到1变化时输出基波幅值线性变化。在代码里就是查表出来的正弦值乘以M。这两个逻辑理论上很清晰但实际联调时会踩不少坑后面第5部分我会专门讲。2. SPWM核心原理与STM32定时器配置2.1 单相SPWM原理通俗解读SPWM的核心思想是用一系列宽度按正弦规律变化的高频脉冲去等效一个低频正弦波。为什么这样能等效因为傅里叶变换告诉我们高频脉冲序列里包含了低频正弦分量只要把高频分量滤掉剩下的就是我们想要的低频正弦。用生活化的比喻你可以把SPWM理解成用砖块砌一面波浪墙。每块砖的宽度不同但平均高度趋近于正弦曲线。砖块越密载波频率越高波浪墙的轮廓就越光滑。LC滤波器的作用就是把砖块的棱角磨平。具体到定时器实现上STM32的TIM1工作在PWM模式ARR决定载波频率CCR决定占空比。我在代码里用中心对齐模式Center-aligned mode因为这种模式下输出的PWM对称性好谐波含量比边沿对齐模式低滤波也更容易。2.2 定时器与互补PWM输出配置先看关键的定时器配置参数。我的工程基于STM32F103C8T6主频72MHzTIM1挂载在APB2上时钟也是72MHz。目标是载波频率20kHz中心对齐模式下[ ARR \frac{72\text{MHz}}{2 \times 20\text{kHz}} - 1 1799 ]所以ARR设置为1799。这里的“除以2”是因为中心对齐模式下定时器计数要先从0加到ARR再从ARR减到0一个完整周期经历2×ARR个时钟节拍。关键配置代码如下static void TIM1_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // PA8: TIM1_CH1, PA9: TIM1_CH2N (互补通道) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); } static void TIM1_PWM_Config(uint16_t arr, uint16_t psc, uint16_t ccr) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_BDTRInitTypeDef TIM_BDTRInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_TimeBaseStructure.TIM_Prescaler psc; // 0, 72MHz TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_CenterAligned1; TIM_TimeBaseStructure.TIM_Period arr; // 1799 TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; 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 ccr; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCNPolarity TIM_OCNPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_BDTRInitStructure.TIM_OSSRState TIM_OSSRState_Enable; TIM_BDTRInitStructure.TIM_OSSIState TIM_OSSIState_Disable; TIM_BDTRInitStructure.TIM_LOCKLevel TIM_LOCKLevel_1; TIM_BDTRInitStructure.TIM_DeadTime 72; // 1us死区具体计算见下文 TIM_BDTRInitStructure.TIM_Break TIM_Break_Disable; TIM_BDTRInitStructure.TIM_BreakPolarity TIM_BreakPolarity_High; TIM_BDTRInitStructure.TIM_AutomaticOutput TIM_AutomaticOutput_Enable; TIM_BDTRConfig(TIM1, TIM_BDTRInitStructure); TIM_CtrlPWMOutputs(TIM1, ENABLE); // 高级定时器必须开启主输出 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }注意TIM_BDTRInitStructure里的TIM_DeadTime这个值需要精确计算。STM32F103的死区寄存器是8位的单位是t_dts。当TIM_ClockDivision为DIV1时t_dts 1/72MHz ≈ 13.89ns。我设置死区1us换算下来约72个tick。为什么要1us因为IR2104这类驱动芯片本身有传输延迟MOS管的关断延迟也通常大于开通延迟死区如果太小上管还没完全关断下管就导通了会直接炸管。1us对于低速MOS管如IRF540N是安全的高频应用可以适当减小。2.3 死区时间设置与选型死区时间不是拍脑袋定的。基本原则是死区 驱动芯片的传输延迟差异 功率管关断延迟 - 开通延迟。具体数值要看数据手册。从实际经验来说几个参考值IR2104 IRF540N死区建议0.5~1usIR2110 IRFP460死区建议1~2us自举式半桥驱动搭配SiC/GaN死区可以压到100ns以内死区也不是越大越好。死区大了输出波形会出现平顶畸变低次谐波增加空载电流畸变明显。我调试时习惯先用示波器看死区时间调完再测THD找到平衡点。3. 代码实现三大模块拆开讲3.1 正弦波表生成与载波比选择查表法是SPWM最实用的实现方式。把正弦曲线离散成N个点存在Flash里运行时定时器更新中断里取值更新CCR。生成正弦表可以用Python或者MATLAB我用Python一次生成400个点import math N 400 table [] for i in range(N): sin_val math.sin(2 * math.pi * i / N) ccr int(1800 * (0.5 0.5 * sin_val)) # 调制比M1.0时的满幅表 table.append(ccr) print(table)载波比N怎么选这里有个权衡N太小比如100每个正弦周期只有100个PWM脉冲谐波大THD高N太大比如2000每个正弦周期脉冲多THD低但CPU更新中断频率高占用资源大我的原则是载波频率20kHz下50Hz输出用N40060Hz输出用N333这样每个正弦周期的PWM脉冲数在333~400之间THD和CPU占用比较均衡。如果你做400Hz中频电源建议用N100左右否则更新中断频率太高。3.2 调压调制比调节与调频变频代码逻辑调压和调频在代码里是这样配合的#define TAB_SIZE 400 uint16_t sin_table[TAB_SIZE]; // 存储满幅CCR表 volatile float mod_ratio 0.85; // 调制比0.0~1.0 volatile uint16_t freq_step 1; // 查表步进控制频率 volatile uint16_t table_index 0; void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) ! RESET) { // 查表并乘调制比得到实际CCR uint16_t raw_value sin_table[table_index]; uint16_t ccr_value (uint16_t)((float)raw_value * mod_ratio); // 限幅保护 if (ccr_value 1799) ccr_value 1799; TIM_SetCompare1(TIM1, ccr_value); // 步进查表实现变频 table_index freq_step; if (table_index TAB_SIZE) table_index 0; } TIM_ClearITPendingBit(TIM1, TIM_IT_Update); }这里调压就是改变mod_ratio。因为CCR 1800 × (0.5 0.5×sinθ) × mod_ratio当M从0.85降到0.5输出电压幅值就从85%降到50%线性度很好。调频就是改变freq_step的值。freq_step1时400个点完整过一遍频率20kHz/40050Hz。freq_step2时相当于每次跳2个点频率20kHz/200100Hz。公式是[ f_{out} \frac{f_{carrier}}{TAB_SIZE} \times freq_step ]需要注意用固定载波频率跳步调频输出点数会变少低频时没问题频率高到一定程度比如400Hz点数降到50个THD会明显恶化。所以如果你的应用需要很宽的调频范围建议用同步调制调整载波比或者混合调制策略。3.3 按键/旋钮调节与闭环扩展实际项目中用户不会去改代码里的变量来调压调频而是通过按键或者旋钮操作。我在这个项目里用的是两个ADC通道采样电位器一个控制频率一个控制电压uint16_t freq_adc, volt_adc; float freq_set, volt_set; while(1) { // 读取电位器 HAL_ADC_Start(hadc1); freq_adc HAL_ADC_GetValue(hadc1); HAL_ADC_Start(hadc2); volt_adc HAL_ADC_GetValue(hadc2); // 映射到实际范围30Hz~100Hz调制度0.3~0.95 freq_set 30.0f (float)freq_adc / 4095.0f * 70.0f; volt_set 0.3f (float)volt_adc / 4095.0f * 0.65f; // 换算成查表步进带浮点步进精度更高 freq_step_float freq_set * TAB_SIZE / 20000.0f; // 更新调制比 mod_ratio volt_set; delay_ms(50); }这里有个小坑freq_set换算成freq_step后可能不是整数比如50.5Hz对应步进1.01。直接用整数跳会有频率误差累计下来波形会周期性抖动。我的处理方式是用浮点累加器float phase_accum 0.0f; float phase_step freq_set * TAB_SIZE / 20000.0f; void TIM1_UP_IRQHandler(void) { uint16_t idx (uint16_t)phase_accum; if (idx TAB_SIZE) idx 0; uint16_t ccr_value (uint16_t)((float)sin_table[idx] * mod_ratio); if (ccr_value 1799) ccr_value 1799; TIM_SetCompare1(TIM1, ccr_value); phase_accum phase_step; if (phase_accum TAB_SIZE) phase_accum - TAB_SIZE; }浮点累加器的好处是步进可以是任意实数频率分辨率只受浮点精度限制波形不会因为取整而抖动。这个细节是我在实际调波形时发现的——用整数步进时示波器能看到输出频率在目标值附近轻微漂移换成浮点累加器后问题立刻消失。4. 硬件设计要点与参数计算4.1 H桥主电路与驱动电路代码再好硬件不给力也白搭。单相逆变最常见的拓扑是单相全桥H桥由4个MOS管组成分成两组Q1/Q4一组Q2/Q3一组。Q1/Q2是上管Q3/Q4是下管。驱动方案我推荐用两片IR2104半桥驱动芯片。为什么不用IR2110因为IR2104自带死区逻辑SD引脚可以直接接单片机的刹车信号安全性高。IR2110虽然也能用但死区得自己在定时器里配调试时发现它会因为自举电容没选好导致上管驱动异常。IR2104的典型电路要点VCC接12VVB和VS之间接自举电容1uF~10uF钽电容或陶瓷电容并联HIN和LIN分别接STM32的TIM1_CH1和TIM1_CH1NHO和LO分别接上管和下管的栅极SD引脚接STM32的一个普通IO用于紧急关断自举电容的容量要特别留意。自举电容给上管栅极充电如果容量太小上管导通瞬间电压跌落会导致栅极驱动不足管子在线性区发热烧毁。经验值开关频率20kHz、MOS管栅极电荷Qg约60nCIRF540N级别时自举电容建议至少1uF我用的是10uF并联0.1uF的组合。4.2 LC滤波参数计算PWM输出波形里有大量载波频率20kHz及其边带谐波必须用LC低通滤波器滤掉。滤波器的截止频率要远低于载波频率又要远高于基波频率50Hz。截止频率公式[ f_c \frac{1}{2\pi\sqrt{LC}} ]我的实践经验是截止频率取载波频率的1/20到1/10比较合适也就是1kHz~2kHz。取L2mHC10uF[ f_c \frac{1}{2\pi\sqrt{2\times10^{-3} \times 10\times10^{-6}}} \approx 1125\text{Hz} ]1100Hz的截止频率对50Hz基波几乎没有衰减50Hz处衰减不到0.5dB对20kHz载波有约25dB的衰减效果不错。如果负载对纹波更敏感可以把C加大到20uF但要注意电容的耐压和纹波电流。这里有个滤波电感的实际问题电感不能直接用功率电感得用铁硅铝或铁氧体磁环绕制。我用的是铁硅铝磁环绕了大概30圈。绕完后实测电感量不够就加圈数多了就减别迷信计算值。4.3 母线电压与电流采样闭环调压或者过流保护都需要采样。对于单相逆变器最关键的采样有两个母线电压采样和输出电流采样。母线电压用电阻分压后接STM32的ADC。注意分压电阻的阻值要大到损耗可忽略同时并联一个小电容滤波。我会在ADC输入端加一个RC低通1kΩ100nF截止频率约1.6kHz能滤掉开关噪声。电流采样可以用电流互感器CT或者采样电阻隔离运放。低成本方案是康铜采样电阻比如10mΩ串联在H桥下管的源极和地之间用差分运放放大后送ADC。采样电阻要注意功率如果输出电流有效值5A采样电阻功耗是I²R25×0.010.25W选1W的电阻才安全。保护逻辑我放在定时器的Break引脚刹车功能上。一旦过流比较器输出高电平TIM1的BREAK输入触发PWM立即锁定在安全状态低电平比任何中断都快——这是硬件的实时性软件永远比不了。5. 调试实录常见问题与避坑指南5.1 PWM波形异常排查我调试这个项目时遇到的第一个问题就是TIM1的CH1和CH1N没有输出或者互补输出极性不对。检查顺序如下确认TIM_CtrlPWMOutputs(TIM1, ENABLE)被调用。STM32的高级定时器有个主输出开关不使能它PWM引脚永远是低电平这是新手最容易漏的一步确认MOE位Main Output Enable被置1。调试器里看TIM1-BDTR寄存器的值确认GPIO配置的是复用推挽输出GPIO_Mode_AF_PP不是普通推挽拿示波器直接量PA8和PA9引脚别量MOS管栅极——栅极的波形会受到驱动电路影响我踩过的一个具体坑用STM32CubeMX生成工程时PA9默认是USART1_TX我直接改了配置但忘了在CubeMX里重新生成导致PA9永远输出UART信号而不是PWM。这种情况就是代码和配置“两层皮”排查起来非常隐蔽。5.2 输出电压畸变与滤波效果输出波形如果带有明显的“平顶”或者“台阶”先看死区设置。死区时间太长会导致输出在过零点附近有台阶因为死区内两个管子都关断输出电压不由电源决定而是由续流二极管和负载决定形成所谓的“死区效应”。死区效应的典型表现是轻载时输出电压有削底THD偏高重载时不明显。解决思路有死区时间在保证安全的前提下尽量短采用死区补偿算法在电流过零附近调整占空比另外滤波器的地线布局很重要。我调试时发现输出波形上叠加了一个高频振铃后来发现是滤波电容的地和驱动电路的地共用了一段长走线开关电流在地线上产生压降。把功率地和控制地单点连接后振铃立刻消失。5.3 功率管发热与炸管防护炸管是逆变器调试的必修课我第一版硬件就炸过两对MOS管。总结下来炸管原因无非四种死区不够上下管直通母线直接短路栅极驱动电压不足MOS管没完全导通工作在线性区自举电容太小上管驱动异常输出短路或者过流没有及时关断调试时的安全操作习惯母线电压用可调电源从低电压开始加比如先12V再24V再48V不要直接上310V第一次上电务必串联一个几十瓦的灯泡限流短路时灯泡亮起成为限流电阻保护MOS管用示波器同时观察上管和下管的栅极波形确认死区存在且时间合适再加大母线电压5.4 调压调频联调的小技巧代码层面调压调频逻辑都正确但实际联调时会发现输出电压和设定的调制度不一定成线性关系。原因是母线电压波动、死区效应、滤波器阻抗都会影响输出。我的经验做法是用万用表或示波器测量实际输出电压在代码里做一维标定表。比如设定M0.8时实测输出205VM0.6时实测152V把这些点存成表格做线性插值补偿。精度要求高的话加一个输出交流电压采样闭环用PID调节调制比来稳定输出电压。调频时要注意输出电压不能变——频率变了但是L的阻抗变了如果负载是R-L型的比如电机低频时电流会变大阻抗变小容易过流。这时候最好加入V/F曲线控制电压频率协调控制频率降低时同时降低电压保证磁通恒定。这是一个比较进阶的玩法可以用查表实现。5.5 ST-Link连接失败的快速修复调试过程中经常会碰到ST-Link连不上板子报错信息大概是“error: no stm32 target found! if your product embeds debug authentication, pl...”这段。这个报错字面意思是找不到目标芯片我排查的顺序是量一下STM32的VDD有没有3.3VNRST引脚电压是不是高电平看SWDIO和SWCLK接线是否接反、接触不良确认芯片有没有被之前的程序锁死——如果芯片读保护被开启RDP Level 1ST-Link连不上是正常的先用ST-Link Utility做全片擦除如果以上都正常检查BOOT0引脚。如果BOOT0被拉高芯片会进入系统Bootloader而不是用户Flash调试器同样连不上我遇到最多的情况是第3种调试过程中程序跑飞碰巧把Flash配置改坏了芯片被锁。用ST-Link Utility连不上时试一下按住复位键再点Connect有时候能抢到芯片的调试访问权。6. 后续可以扩展的方向这个项目做成基础版之后有很多值得深入的方向。我个人觉得最有价值的是往闭环方向扩展——加交流电压采样构成闭环稳压加电流采样构成限流保护再加个通信接口比如Modbus或者CAN实现远程调压调频基本就是一台小型变频电源的控制核心了。如果你是在做毕业设计建议在开环SPWM的基础上把闭环控制比如PI调节器和上位机监控界面做出来这样整个项目的技术含量和完整性都会提升一个档次。我当时就是在这个基础上加了NRF24L01无线遥控答辩时演示远程调压调频效果非常好。本文还有配套的精品资源点击获取