简介:这是一套基于STM32F103微控制器和IR2104半桥驱动芯片的三相逆变器系统设计方案,面向电力电子与嵌入式方向开发者,聚焦直流转三相交流过程中的SPWM波形生成、死区时间控制、三相桥式电路构建与反馈调节机制等关键问题。压缩包共一百五十六个文件,大小约二点四四兆字节,以Keil工程资源为主,包含STM32标准外设库头文件与C源码、汇编启动文件、工程配置及编译生成文件,并附有说明文档,便于对照源码理解设计细节。目前已有133人学习下载,适合正在学习逆变器控制算法、准备课程设计或项目开发的读者参考。资源内可查看SPWM调制与死区设置的软件逻辑,以及IR2104半桥驱动电路与反馈环路的配合实现,帮助快速上手三相逆变器开发,理解从算法到电路落地的完整流程。
1. 先把三相逆变器的控制难点说透
调试三相逆变器时,最先暴露问题的地方往往不是功率管烧毁,而是上下桥臂直通。STM32F103 主频 72MHz,做 SPWM 调制绰绰有余,但真正决定系统能否长期稳定运行的,是死区时间控制能不能在每个开关周期都可靠生效。IR2104 半桥驱动负责把 PWM 信号转换成高低边 MOS 管的驱动电压,但它自己不产生死区,死区必须由 MCU 的互补 PWM 输出保证。这个设计把 SPWM 波形生成、死区时间控制、三相桥式逆变电路和反馈调节机制串成一条完整链路,适合做电机驱动、变频电源、UPS 或三相 DC-AC 电源的工程师参考。下面按从调制算法到闭环控制的顺序拆开讲。
2. SPWM 生成算法与 STM32F103 定时器映射
2.1 对称规则采样 SPWM 比实时计算更适合嵌入式
SPWM 的核心是正弦波与三角载波比较。实时计算正弦值在 10kHz 载波下虽然来得及,但会占用大量 CPU 时间,留给反馈调节的余量就不够了。常见做法是离线生成一张正弦表,运行时查表并映射到定时器的比较寄存器。对称规则采样在三角载波顶点处采样调制波,计算量小,且输出脉冲关于载波周期中心对称,谐波性能比不对称采样略好,适合三相逆变器这种需要三路同步调制的场景。
载波频率一般选 10kHz 到 20kHz。频率太高,IR2104 自举电容的充电时间不足;频率太低,输出滤波器体积变大。我一般把载波设在 15kHz 左右,既在听觉范围之外,又留足裕量。正弦表长度取 768 点,不仅相位分辨率高,而且能被 3 整除,三相相位间隔天然准确。如果想进一步压低谐波,三电平 NPC 是另一个方向,但对 STM32F103 和驱动电路复杂度的要求都会上一个台阶。
2.2 TIM1 的互补通道映射关系与定时器初始化
STM32F103 里只有高级定时器能输出带死区的互补 PWM。以常见的 STM32F103C8T6 最小系统为例,TIM1 的 CH1/CH2/CH3 分别对应 U、V、W 相上桥臂,反相通道 CH1N/CH2N/CH3N 对应下桥臂。如果是高密度型号,TIM8 也可以作为第二组输出,但三相桥用一个 TIM1 就够了。通道映射关系如下:
| TIM1 通道 | 上桥臂输出 | 下桥臂输出 | 对应相 |
|---|---|---|---|
| CH1 / CH1N | CH1 | CH1N | U 相 |
| CH2 / CH2N | CH2 | CH2N | V 相 |
| CH3 / CH3N | CH3 | CH3N | W 相 |
初始化时要把主输出使能TIM_CtrlPWMOutputs(TIM1, ENABLE)打开,否则即使TIM_Cmd使能,引脚上也不会有波形。下面是基于标准外设库 V3.5.0 的 TIM1 基础配置:
TIM_TimeBaseInitTypeDef sTB; TIM_OCInitTypeDef sOC; TIM_BDTRInitTypeDef sBD; sTB.TIM_Prescaler = 72 - 1; // 72MHz / 72 = 1MHz 计数时钟 sTB.TIM_CounterMode = TIM_CounterMode_CenterAligned1; sTB.TIM_Period = 32; // 中心对齐:1MHz / (2*(32+1)) ≈ 15.15kHz sTB.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM1, &sTB); sOC.TIM_OCMode = TIM_OCMode_PWM1; sOC.TIM_Pulse = 0; // 运行时由 SPWM 更新 sOC.TIM_OutputState = TIM_OutputState_Enable; sOC.TIM_OutputNState = TIM_OutputNState_Enable; sOC.TIM_OCPolarity = TIM_OCPolarity_High; sOC.TIM_OCNPolarity = TIM_OCNPolarity_High; sOC.TIM_OCIdleState = TIM_OCIdleState_Reset; sOC.TIM_OCNIdleState = TIM_OCNIdleState_Reset; TIM_OC1Init(TIM1, &sOC); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);这里最容易弄错的是中心对齐模式下的频率公式。计数器先向上再向下,一个完整周期等于 2 倍的TIM_Period+1,所以 PWM 频率不是1MHz / (32+1),而是1MHz / (2*(32+1))。TIM_Prescaler=71只是把 72MHz 分频成 1MHz 计数时钟,不会改变死区时基,这一点到第 3 章死区配置时尤其重要。
2.3 三相互差 120° 的调制波生成方法
三相输出电压要求相位互差 120°。查表实现时,如果表长度取 256,间隔 85.33 无法整除,会引入固定相位误差。所以我把表长度取为 768,每相间隔正好 256 点。在定时器更新中断里按索引更新三个比较寄存器:
#define SPWM_TABLE_SIZE 768 #define TIM_PERIOD 32 // 初始化时生成正弦表,偏移到 0~1 范围 for (int i = 0; i < SPWM_TABLE_SIZE; i++) { float v = 0.5f + 0.5f * sinf(2.0f * 3.14159265f * i / SPWM_TABLE_SIZE); sin_table[i] = (uint16_t)(v * (TIM_PERIOD - 2)); } // 更新中断中,按调制度计算三相比较值 uint32_t u_idx = phase_acc; uint32_t v_idx = phase_acc + SPWM_TABLE_SIZE / 3; uint32_t w_idx = phase_acc + 2 * SPWM_TABLE_SIZE / 3; uint32_t cmp_u = (uint32_t)(sin_table[u_idx % SPWM_TABLE_SIZE] * modulation_index); uint32_t cmp_v = (uint32_t)(sin_table[v_idx % SPWM_TABLE_SIZE] * modulation_index); uint32_t cmp_w = (uint32_t)(sin_table[w_idx % SPWM_TABLE_SIZE] * modulation_index); TIM_SetCompare1(TIM1, cmp_u); TIM_SetCompare2(TIM1, cmp_v); TIM_SetCompare3(TIM1, cmp_w); phase_acc += PHASE_STEP;sin_table里的值在 0 到TIM_PERIOD-2之间,目的是给比较值留出 1~2 个计数余量,避免占空比极端情况。modulation_index是 0~1 的调制度,它最终决定输出线电压基波幅值。PHASE_STEP与基波频率的关系是:在中心对齐模式下,更新事件频率为2*fc,所以基波频率 = 2*fc * PHASE_STEP / SPWM_TABLE_SIZE。表运行时用phase_acc累加,配合查表可以平滑改变频率,不会出现相位跳变。代码用sinf生成表,实际工程可以提前用脚本生成 const 数组,避免运行时依赖浮点数学库。
3. IR2104 半桥驱动与死区时间控制落地
3.1 IR2104 自举电路的工作条件
IR2104 的高端驱动电压来自自举电容。PWM 低电平期间,VS 被拉到功率地,VCC 通过自举二极管给电容充电;高电平期间,电容为高端驱动电路供电。这就要求每个开关周期都有足够的低电平时间给电容充电,载波频率越高,充电时间越短。自举电容取值通常用Cbs = Qg / dVbs估算,Qg 是上管栅极电荷,dVbs 是允许的电压跌落。以 IRF540 为例,Qg 约 70nC,允许跌落 0.3V,计算得到约 233nF,取 470nF 比较稳妥。
电容太小会触发 IR2104 的欠压锁定,导致 HO 输出关断;电容太大则充电时间常数过大,启动时可能出现高端驱动不足。我一般用 470nF 陶瓷电容并联一个 0.1µF 高频电容,放在 IR2104 的 VB 与 VS 之间,离芯片尽量近。如果母线电压较高,自举二极管需要选快恢复管,不能用普通整流二极管。
3.2 死区时间由定时器生成,而不是靠软件延时
软件插入延时看起来可行,但中断响应、指令乱序和不同编译优化都会让死区时间抖动,严重时出现某些周期没有死区。正确做法是使用 TIM1 的 BDTR 寄存器,由硬件在互补输出之间强制插入死区。STM32F103 的死区步长tDTS来自定时器内部时钟,由TIM_ClockDivision控制,与计数器预分频无关。当 APB2 为 72MHz 且CKD=DIV1时,tDTS = 1/72MHz ≈ 13.89ns。
设置 1µs 死区时,DTG = 72,即二进制0b01001000,落在DTG[7:5]=0b010区间,死区时间等于(64 + DTG[4:0]) * tDTS,也就是(64+8)*13.89ns ≈ 1µs。所以TIM_DeadTime = 0x48。注意不要把计数器的 1MHz 周期 1µs 当成死区步长,那样会算偏一个数量级。
TIM_BDTRInitTypeDef sBD; sBD.TIM_OSSRState = TIM_OSSRState_Enable; sBD.TIM_OSSIState = TIM_OSSIState_Disable; sBD.TIM_LOCKLevel = TIM_LOCKLevel_1; sBD.TIM_DeadTime = 0x48; // (64+8) / 72MHz = 1us sBD.TIM_Break = TIM_Break_Enable; sBD.TIM_BreakPolarity = TIM_BreakPolarity_High; sBD.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable; TIM_BDTRConfig(TIM1, &sBD); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE);这里把刹车功能也打开了,过流保护时会用到。死区寄存器给出的是定时器输出引脚上的死区,经过 IR2104 输入到输出的传播延迟后,实际 MOSFET 栅极上的死区会变化。尤其是 HIN 到 HO 的延迟与 LIN 到 LO 的延迟有差异,所以最后必须用示波器实测。
3.3 三相桥式逆变电路接线与电源处理
三相桥由三个半桥组成,每个半桥用一个 IR2104。IR2104 的 HIN 接 TIM1_CH1,LIN 接 TIM1_CH1N,SD 引脚接高电平或接保护 GPIO。HO 驱动上管栅极,LO 驱动下管栅极,HS 输出点接上管源极和下管漏极,也就是半桥中点。栅极串联 10Ω 到 22Ω 电阻,抑制栅极驱动振荡。母线电容放在桥臂附近,用 220µF 电解电容并联 0.1µF 薄膜电容,吸收开关瞬态电流。
电源地的处理是很多 stm32f103 最小系统方案容易踩坑的地方。MCU 的逻辑地和功率地必须在母线电容负极单点连接,如果形成环路,IR2104 会因地弹产生闩锁,轻则重启,重则烧驱动芯片。工程包里看到的stm32f10x_tim.c、stm32f10x_rcc.c是标准外设库 V3.5.0 的源文件,建议编译选项里打开 MicroLIB,中断里的浮点运算会更省资源。
4. 反馈调节机制:从 ADC 采样到 PI 调压
4.1 输出电压采样与 ADC 触发时刻
闭环控制需要实时知道输出电压。逆变器输出是 PWM 方波经 LC 滤波后的正弦波,不能直接接入 STM32F103 的 ADC。常见做法是先用电阻分压把电压降到 ADC 范围,然后用运放差分电路去除共模,再叠加 1.65V 偏置,把双极性正弦波抬到 0~3.3V。如果要求电气隔离,需要线性光耦或隔离放大器,但带宽要跟上 10kHz 的 PWM 开关频率,普通光耦不够用。
采样时刻直接影响反馈精度。用 TIM1 的 TRGO 触发 ADC 注入组转换,可以在 PWM 中心附近采样,避开开关瞬态。核心配置如下:
ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_TRGO); ADC_ExternalTrigInjectedConvCmd(ADC1, ENABLE); ADC_ExternalTrigConvCmd(ADC1, DISABLE);配置后不再用软件启动 ADC,每次定时器更新事件自动发起注入组转换。注入组转换完成后读结果即可获得输出电压瞬时值。对 50Hz 基波,一个周期内有几百个采样点,相位延迟很小,适合做电压闭环。
4.2 增量式 PI 调节器实现
反馈调节的本质是把输出电压采样值与参考值比较,通过 PI 控制器调整调制度。增量式 PI 只需要保存上一次误差,计算量小,适合在定时器更新中断里执行。
typedef struct { float kp; float ki; float out_min; float out_max; float err_last; float out; } PI_Controller; float PI_Update(PI_Controller *pi, float ref, float fb) { float err = ref - fb; float delta = pi->kp * (err - pi->err_last) + pi->ki * err; pi->out += delta; if (pi->out > pi->out_max) pi->out = pi->out_max; if (pi->out < pi->out_min) pi->out = pi->out_min; pi->err_last = err; return pi->out; }kp决定对误差变化速度的响应,ki决定对累积误差的修正。整定时先把ki设为 0,逐步增加kp到输出电压开始振荡,再把kp乘 0.6,然后加入ki消除稳态误差。out_min和out_max限制在 0.1 到 0.9,避免过调制导致谐波激增。注意 PI 输出的是调制度,要作用到第 2 章的modulation_index上,而不是直接改定时器周期。
4.3 过流保护与故障封锁
快速保护不能等软件判断,要利用 TIM1 的刹车输入。当输出过流比较器或电流采样运放给出故障电平时,BKIN引脚立即关断互补 PWM,不需要 CPU 参与。配置中TIM_AutomaticOutput_Disable的作用是:刹车信号消失后 PWM 不会自动恢复,必须由软件清理标志并重新使能 MOE,防止故障未排除时反复重启。
| 参数 | 典型值 | 说明 |
|---|---|---|
| SPWM 载波 | 15kHz | 高于音频,低于自举驱动极限 |
| 死区时间 | 1µs | 根据 MOSFET 关断延迟调整 |
| 自举电容 | 470nF | 按上管 Qg 估算 |
| 电流保护阈值 | 1.2 倍额定电流 | 比较器输出接 TIM1_BKIN |
| 输出滤波电感 | 1mH ~ 3mH | 与负载功率和基波频率有关 |
刹车动作后要刹车标志清掉,再通过TIM_CtrlPWMOutputs(TIM1, ENABLE)重新打开输出。SD 引脚同样能关断 IR2104,但软件响应需要几百纳秒到几微秒,与硬件刹车不在一个量级,所以优先级最高的保护路径应该是 BKIN 引脚。
5. 用示波器验证 SPWM 与死区时间的一个实用技巧
SPWM 和死区配置完成后,光看程序里的寄存器值还不够,IR2104 的输入到输出有传播延迟,必须实测 MOSFET 栅极信号。把示波器 CH1 接 U 相上桥臂栅极,CH2 接下桥臂栅极,地线用接地弹簧而不是长地线夹,否则地线环路电感会造成测量波形振铃,甚至误判为桥臂直通。
触发方式设为上升沿触发在 CH1,时基先放到 10µs 看整个 PWM 周期,再放大到 1µs/div。用光标测量下桥臂关断沿到上桥臂导通关沿之间的低电平区间,这个区间就是实际死区时间。注意要分别测上桥臂先关断和下桥臂先关断两种情况,因为 IR2104 高低侧传输延迟不同,死区会不对称。
如果示波器有逻辑运算功能,把 CH1 和 CH2 做A&&B运算,可以直接看到两路同时为高的时间。正常情况下为零,出现毛刺说明死区配置不够,或者探头地线环路造成串扰。另一个实用做法是触发模式设为 Normal,触发电平放在母线电压一半,连续抓 100 个开关周期,记录最小死区值。STM32F103 的 DTG 寄存器只是定时器输出点的死区,经过 IR2104 后还要扣除高低侧延迟差,在低温或大电流下死区会进一步缩小,所以留 20% 裕量是合理的。记录不同母线电压和温度下的死区边缘抖动,把存储深度开到最大,统计 100 个周期的最小死区值,再决定是否在 PI 输出侧加入死区补偿。
本文还有配套的精品资源,点击获取