简介:面向电源工程师与嵌入式开发者的三相四桥臂逆变电源设计资料,围绕国产GD32F330微控制器展开,重点解决离网三相四线供电场景下的数字控制实现问题,适合具备一定电力电子与MCU开发基础、希望参考完整软硬件方案的中高级读者。资源包内含1个PDF文档,约185KB,以图文形式呈现系统结构、PWM与AD引脚分配表、控制策略及保护逻辑,篇幅紧凑便于快速查阅。目前已有534人学习下载。内容涵盖推挽升压DC/DC单电压环控制、R/S/T三相电压外环加电流内环与N相开环的逆变控制、软件锁相电网检测,以及串口通讯、Flash模拟EEPROM、电池管理与过欠压过流短路过温保护、串口在线升级和逻辑时序控制等附加功能,可帮助读者理解引脚规划思路、借鉴控制框架并对照自身项目查漏补缺。
1. 一块不到十块钱的国产芯片,撑起了一台三相四桥臂逆变电源
去年帮一个做小型储能的朋友看板子,他指着主控说这颗芯片采购价压到个位数,我第一反应是“这能跑三相四桥臂?”——毕竟在多数人的印象里,三相逆变控制是 DSP 的活儿,浮点、高主频、专用 PWM 模块缺一不可。结果他把源码包甩过来,GD32F330RBT6,Cortex-M4 内核,跑 19.2kHz 的三相电压电流双闭环,外加 100kHz 的推挽 DC/DC、软件锁相、电池管理、串口在线升级,全塞在一颗芯片里。这套基于 GD32F330 的三相四桥臂逆变电源设计,适合正在做离网/备电逆变器、想从 DSP 方案切到国产芯片降本的开发者,也适合拿它当“单芯片搞定整套电源控制”的参考工程来读。下面我按系统结构、外设分配、控制算法、保护与升级、二次开发五个层面,把这份源码包拆开讲。
2. 系统结构与硬件分工:从三相市电到四桥臂输出的完整链路
2.1 整机拓扑:充电、推挽升压、逆变三段式
这套系统的能量路径很清晰:输入是三相四线市电,先经充电电路给电池充电;市电掉电后,电池电压经 DC/DC 推挽升压变换器抬到母线电压,再由三相四桥臂逆变器逆变成三相交流输出。控制板是整机的大脑,DC/DC 和逆变两级都走数字控制,没有独立的模拟 PWM 控制器。
推挽升压是低压大电流输入场景的经典选择:变压器原边中心抽头接电池,两个开关管交替导通,副边整流后得到高压母线。它的好处是原边只有两个开关管、驱动简单、变压器利用率高;代价是开关管承受两倍输入电压应力和变压器漏感引起的尖峰,所以源码里 100kHz 的开关频率配合死区与吸收电路是必须的。四桥臂逆变则是在传统三相三桥臂基础上多出一个 N 相桥臂,直接控制中性点电压,天然支持三相四线的不平衡负载——这是它相比三桥臂+电容中点方案最大的价值,也是这套设计选它的原因。
从工程角度看,这个拓扑组合覆盖了“市电在线充电 + 电池备电逆变”的典型 UPS/储能场景。读源码时建议先画一张能量流图:电池 → 推挽 → 母线电容 → 四桥臂 → LC 滤波 → 负载,每一级对应一组 PWM 和一组采样,后面章节的引脚分配就是围绕这张图展开的。
2.2 控制板资源盘点:一颗芯片扛下所有
GD32F330RBT6 在这套设计里承担的角色远超“主控”二字。按项目正文,它同时完成:10 路 PWM 输出、15 路 ADC 采样、软件锁相、双闭环控制、串口通讯、Flash 模拟 EEPROM、充电与电池管理、多重保护、串口在线升级、系统时序逻辑。这意味着芯片的定时器资源、ADC 通道、Flash 空间、RAM 都要精打细算。
常见的资源分配思路是:TIMER0 出三相互补 PWM(带死区),TIMER15 出 N 相,TIMER2 出 DC/DC 两路,ADC 用规则组扫描 + DMA 搬运,串口用 USART0 做通讯和 IAP,Flash 划出最后几页做参数存储。这套分配不是随便定的,而是被引脚复用表“逼”出来的——下一章会看到,PWM 和 ADC 的引脚几乎把可用外设占满,改任何一个都要连带改一片。
提示:拿到源码先别急着编译,先把原理图和引脚表对照一遍,确认板子上的 PWM 和采样通道与代码里的宏定义一致,否则调波形时会怀疑人生。
3. 外设分配拆解:10 路 PWM 与 15 路 ADC 怎么落到引脚上
3.1 PWM 引脚映射与定时器通道规划
项目给出的 PWM 分配表是这套设计的骨架,我把它整理成便于对照代码的形式:
| 信号 | 引脚 | 定时器通道 | 用途 |
|---|---|---|---|
| DCDCPWM1 | PC6 | TIMER2_CH0 | 推挽原边开关 1 |
| DCDCPWM1 | PC7 | TIMER2_CH1 | 推挽原边开关 2 |
| R_PWM_H | PA8 | TIMER0_CH0 | R 相上管 |
| R_PWM_L | PB13 | TIMER0_CH0_ON | R 相下管(互补) |
| S_PWM_H | PA9 | TIMER0_CH1 | S 相上管 |
| S_PWM_L | PB14 | TIMER0_CH1_ON | S 相下管(互补) |
| T_PWM_H | PA10 | TIMER0_CH2 | T 相上管 |
| T_PWM_L | PB15 | TIMER0_CH2_ON | T 相下管(互补) |
| N_PWM_H | PB8 | TIMER15_CH0 | N 相上管 |
| N_PWM_L | PN8 | TIMER15_CH0_ON | N 相下管(互补) |
这里有两个关键点值得展开。第一,R/S/T 三相用 TIMER0 的三个通道,每个通道的_ON引脚输出互补信号,死区由定时器的死区发生器硬件插入,不需要软件延时——这是保证上下管不直通的前提。第二,N 相单独用 TIMER15,因为 TIMER0 的三个通道已经占满,而 N 相是开环控制,对同步精度要求低于三相,独立定时器完全够用。
初始化代码的骨架大致如下(以 TIMER0 为例,GD32 标准外设库风格):
void inverter_pwm_init(void) { timer_parameter_struct tp; timer_oc_parameter_struct oc; rcu_periph_clock_enable(RCU_TIMER0); rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOB); /* PA8/PA9/PA10 复用为 TIMER0 通道输出 */ gpio_af_set(GPIOA, GPIO_AF_2, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10); gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10); /* 19.2kHz:120MHz / (1+1) / 3125 ≈ 19.2kHz,中心对齐模式 */ timer_deinit(TIMER0); tp.prescaler = 1 - 1; tp.alignedmode = TIMER_COUNTER_CENTER_ALIGNED; tp.period = 3125 - 1; tp.clockdivision = TIMER_CKDIV_DIV1; tp.repetitioncounter = 0; timer_init(TIMER0, &tp); /* 互补输出 + 死区,死区时间按驱动芯片要求设,常见 1us 量级 */ oc.outputstate = TIMER_CCX_ENABLE; oc.outputnstate = TIMER_CCXN_ENABLE; oc.ocpolarity = TIMER_OC_POLARITY_HIGH; oc.ocnpolarity = TIMER_OCN_POLARITY_HIGH; oc.ocidlestate = TIMER_OC_IDLE_STATE_LOW; oc.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; timer_channel_output_config(TIMER0, TIMER_CH_0, &oc); timer_channel_output_config(TIMER0, TIMER_CH_1, &oc); timer_channel_output_config(TIMER0, TIMER_CH_2, &oc); timer_channel_output_mode_config(TIMER0, TIMER_CH_0, TIMER_OC_MODE_PWM0); timer_channel_output_mode_config(TIMER0, TIMER_CH_1, TIMER_OC_MODE_PWM0); timer_channel_output_mode_config(TIMER0, TIMER_CH_2, TIMER_OC_MODE_PWM0); timer_channel_output_deadtime_config(TIMER0, 120); /* 120 个时钟 ≈ 1us */ timer_primary_output_config(TIMER0, ENABLE); timer_enable(TIMER0); }参数说明:prescaler设为 0 分频,计数时钟等于 120MHz;中心对齐模式下实际开关频率是120MHz / (2 × period),period 取 3125 得到约 19.2kHz,与项目正文一致。死区值 120 是按 120MHz 时钟估算的 1us,实际要按你的驱动芯片和功率管开关速度调整,设小了直通炸管,设大了波形畸变。ocidlestate和ocnidlestate设为低电平,是为了在刹车或复位时让所有管子可靠关断,这个细节很多新手会漏。
3.2 ADC 采样通道与 15 路数据的组织
15 路采样分三类:控制量(三相输出电压、三相电感电流、母线电压)、电网量(三相市电电压)、管理与保护量(电池电压、充电电流、散热器温度、参数 1/2)。引脚分布是 PA0~PA7、PB0、PB1、PC0~PC2、PC4、PC5。
采样组织的常见做法是:把控制相关的通道(输出电压、电感电流、母线电压)放在 ADC 规则组的前段,用定时器触发 + DMA 循环搬运,保证每个 PWM 周期采一次且相位固定;市电电压和慢变量(温度、电池电压)可以降采样率,隔几个周期采一次。这样做的原因是双闭环对采样时刻敏感,电流环尤其要求采样点落在开关周期的固定位置,否则纹波会污染反馈。
/* ADC 规则组通道顺序,与 DMA 缓冲区下标一一对应 */ const uint8_t adc_channel_seq[15] = { ADC_CHANNEL_0, /* PA0 R 相输出电压 */ ADC_CHANNEL_1, /* PA1 S 相输出电压 */ ADC_CHANNEL_2, /* PA2 T 相输出电压 */ ADC_CHANNEL_3, /* PA3 R 相电感电流 */ ADC_CHANNEL_4, /* PA4 S 相电感电流 */ ADC_CHANNEL_5, /* PA5 T 相电感电流 */ ADC_CHANNEL_6, /* PA6 R 相市电电压 */ ADC_CHANNEL_7, /* PA7 S 相市电电压 */ ADC_CHANNEL_8, /* PB0 T 相市电电压 */ ADC_CHANNEL_9, /* PB1 母线电压 */ ADC_CHANNEL_10, /* PC0 电池电压 */ ADC_CHANNEL_11, /* PC1 充电电流 */ ADC_CHANNEL_12, /* PC2 散热器温度 */ ADC_CHANNEL_14, /* PC4 参数 1 */ ADC_CHANNEL_15 /* PC5 参数 2 */ }; void adc_dma_init(void) { dma_parameter_struct dp; rcu_periph_clock_enable(RCU_DMA); dma_deinit(DMA_CH0); dp.direction = DMA_PERIPHERAL_TO_MEMORY; dp.memory_addr = (uint32_t)adc_raw; dp.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dp.periph_addr = (uint32_t)&ADC_RDATA(ADC0); dp.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dp.memory_width = DMA_MEMORY_WIDTH_16BIT; dp.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; dp.number = 15; dp.priority = DMA_PRIORITY_HIGH; dma_init(DMA_CH0, &dp); dma_circulation_enable(DMA_CH0); dma_channel_enable(DMA_CH0); }这段代码把 15 个通道的转换结果按顺序搬进adc_raw数组,下标与通道表严格对应。改采样通道时,adc_channel_seq和 DMA 的number必须同步改,否则会出现“电压读到电流值”的错位——这是我在类似项目里真实翻过车的地方。另外注意 PC3 没有出现在表里,说明该引脚被别的东西占了或者未用,移植时不要想当然按 PC0~PC5 连续排。
4. 控制算法落地:双闭环、三次谐波注入与软件锁相
4.1 DC/DC 单电压环:100kHz 推挽的稳压逻辑
推挽级的目标只有一个:把电池电压升到稳定的母线电压。项目采用单电压环,采样母线电压与给定值比较,误差经 PI 调节器输出占空比,直接写 TIMER2 的比较寄存器。100kHz 开关频率意味着控制周期只有 10us,PI 计算必须足够轻量,通常用定点或 Q 格式实现,避免在中断里跑浮点除法。
/* 100kHz 中断内执行,Q12 定点 PI */ int32_t dcdc_pi(int32_t v_ref, int32_t v_fb) { static int32_t integ = 0; int32_t err = v_ref - v_fb; int32_t p_term = (DCDC_KP * err) >> 12; integ += (DCDC_KI * err) >> 12; /* 抗积分饱和:输出限幅时停止积分累加 */ if (integ > DCDC_INTEG_MAX) integ = DCDC_INTEG_MAX; if (integ < 0) integ = 0; int32_t out = p_term + integ; if (out > DCDC_DUTY_MAX) out = DCDC_DUTY_MAX; if (out < DCDC_DUTY_MIN) out = DCDC_DUTY_MIN; return out; }参数说明:DCDC_KP、DCDC_KI是 Q12 定标后的增益,实际整定时先给 KP 让母线能起振,再慢慢加 KI 压稳态误差;DCDC_DUTY_MAX要留出变压器复位裕量,推挽占空比通常不超过 45% 每边。抗积分饱和这段是血泪经验——母线轻载时电压冲高,积分项如果不管,负载一加上来就会过冲甚至触发过压保护。
4.2 三相四桥臂:电压外环 + 电流内环与三次谐波注入
R/S/T 三相各有一套独立的电压外环和电流内环,N 相开环。控制频率与开关频率同为 19.2kHz,也就是每个 PWM 周期算一次。电压外环输出电流指令,电流内环输出占空比,这种双环结构对负载突变响应快,也比单电压环更容易限流。
三次谐波注入的作用是提高直流母线电压利用率:在正弦调制波里叠加三次谐波后,相电压峰值可以超过母线电压的一半而不进入过调制,等效多出约 15% 的电压输出能力。实现上就是在三相调制波里减去三者中的最大值与最小值之和的一半,代码很短但效果明显。
/* 三次谐波注入,输入输出均为标幺化调制波 */ void thipwm_inject(float *ma, float *mb, float *mc) { float vmax = *ma > *mb ? (*ma > *mc ? *ma : *mc) : (*mb > *mc ? *mb : *mc); float vmin = *ma < *mb ? (*ma < *mc ? *ma : *mc) : (*mb < *mc ? *mb : *mc); float offset = (vmax + vmin) * 0.5f; *ma -= offset; *mb -= offset; *mc -= offset; }参数说明:输入是电压外环/电流内环算出的三相调制波,范围大致在 ±1 之间;offset是共模分量,减去它不改变线电压,只改变各相对中性点的电压。N 相开环意味着中性点电流由负载自然决定,不平衡负载能力受限于 N 相桥臂的电流容量,选功率管时 N 相要和相桥臂同规格。
4.3 软件锁相:用 CMSIS 正余弦函数算相位
市电检测用软件锁相,核心是构造一个与电网同频同相的旋转参考。常见做法是采样三相市电电压,经 Clarke 变换得到 αβ 分量,再与内部生成的正余弦相乘做鉴相,误差送 PI 调节器修正内部相位角,最终让内部角度跟踪电网角度。项目正文特别提到用了 CMSIS 库的正余弦函数,说明角度更新在中断里完成,用查表或硬件三角函数加速。
/* 单相简化鉴相:内部角度与电网采样做叉积 */ float pll_step(float v_grid, float theta) { float s = arm_sin_f32(theta); float c = arm_cos_f32(theta); float err = v_grid * c; /* 与内部余弦相乘得到相位误差 */ pll_integ += PLL_KI * err; /* 积分修正频率 */ pll_freq = PLL_NOMINAL + pll_integ; theta += pll_freq * TS; /* TS 为控制周期 */ if (theta > 6.2831853f) theta -= 6.2831853f; return theta; }参数说明:PLL_NOMINAL是额定角频率(50Hz 对应 314.16 rad/s),PLL_KI决定锁相带宽,太大对电网谐波敏感,太小跟不上频率波动;TS是调用周期,必须与实际中断周期一致。三相版本会把三路误差相加或做 Park 变换后鉴相,思路相同。锁相输出给市电电压前馈和并网/切换逻辑用,相位跳变会导致切换瞬间冲击电流,调试时建议用信号发生器模拟电网频率偏移来验证跟踪能力。
5. 避坑与排查:调试这套板子最容易栽的五个地方
5.1 上电就炸管:死区与互补输出配置错位
现象:第一次上电,推挽或逆变桥上下管同时导通,保险丝断、管子击穿。原因多半是互补通道没使能、死区为 0,或者_ON引脚没有正确复用为定时器互补输出,导致上下管收到同相 PWM。解决:先用示波器双通道看上下管驱动,确认死区时间存在且大于驱动芯片传输延迟差;检查timer_channel_output_deadtime_config是否在timer_primary_output_config之前调用;把ocidlestate设为低,确保复位期间全关断。
5.2 采样值乱跳:ADC 通道顺序与 DMA 长度不一致
现象:母线电压显示正常,但电流环一投入就震荡,打印出来电流值像噪声。原因:DMA 搬运长度和规则组通道数不匹配,或者通道顺序与缓冲区下标错位,导致电流反馈里混进了电压通道的数据。解决:把adc_channel_seq、DMAnumber、缓冲区下标三者做成一张表统一维护;调试时逐个通道给已知电压,核对原始码值换算后的物理量。
5.3 锁相锁不上:CMSIS 函数调用开销拖垮中断
现象:市电频率稍偏,锁相角就来回摆,切换时冲击明显。原因:在 19.2kHz 中断里频繁调用浮点正余弦,加上其他运算,中断执行时间逼近周期,角度更新被推迟。解决:把锁相降频到 1~2kHz 单独跑,或者用查表 + 线性插值替代实时三角函数;确认中断优先级,锁相中断不要被串口等低速中断打断。
5.4 参数存不住:Flash 模拟 EEPROM 擦写时机不对
现象:调好的 PI 参数断电就丢,或者运行中偶发死机。原因:在中断里直接擦写 Flash,擦除期间取指停顿,看门狗复位;或者擦写页与程序区重叠。解决:参数写入放到主循环低优先级任务,擦写前关中断并喂狗,Flash 页地址用链接脚本预留,编译后核对 map 文件确认没有越界。
5.5 串口升级变砖:IAP 跳转前没关外设
现象:在线升级完成后程序不运行,必须重新上电甚至用仿真器救。原因:跳转到新程序前没关定时器、ADC、DMA 和全局中断,新程序初始化时外设状态残留。解决:IAP 跳转前统一执行__disable_irq()、关闭所有已使能外设时钟、把栈指针重设为新程序向量表首地址,跳转后再开中断;升级协议里加校验和回滚标志,校验失败留在 Bootloader。
6. 二次开发:从改一个参数到换一套功率等级的改造路径
拿到这份源码,最现实的改造入口是参数层。母线电压给定、PI 增益、保护阈值、电池容量这些量,通常集中在config.h或参数结构体里,改之前先确认它们和硬件采样网络的变比匹配——比如你把母线从 400V 改到 300V,分压电阻和 ADC 换算系数要一起改,否则保护点全错。我的习惯是先在纸面上把“物理量 → 采样电压 → ADC 码值 → 标幺值”这条链算一遍,再动代码。
第二个入口是功率等级。推挽级换更大的变压器和管子,开关频率可能要降,因为大磁芯的损耗和漏感尖峰在 100kHz 下不好压;逆变级换低 Rds(on) 的 MOS 或 IGBT,死区要按新器件的开关时间重算。改完不要直接带满载,先用电子负载从 10% 阶梯加到额定,每级记录母线跌落、相电压 THD 和管子温升,温升异常先查驱动电阻和死区,再查散热。
第三个入口是控制策略。N 相目前开环,如果你的负载不平衡度要求高,可以把 N 相也纳入电流环,用第四桥臂电流做反馈;三次谐波注入的系数可以做成可调,在母线电压偏低时加大注入深度换取输出能力。改算法时建议保留原参数作为对照,用同一组负载工况跑新旧两版,比较相电压波形和动态响应,别凭感觉说“好像更稳了”。
| 改造方向 | 主要改动点 | 验证方法 |
|---|---|---|
| 母线电压调整 | 分压网络、ADC 换算、保护阈值 | 阶梯加载看稳态误差与过压点 |
| 功率等级提升 | 变压器、功率管、死区、驱动电阻 | 满载温升与效率曲线 |
| N 相闭环 | 增加 N 相电流采样与调节器 | 不平衡负载下中性点偏移 |
| 锁相性能 | 降频、查表替代三角函数 | 频率阶跃与谐波电网下的相位误差 |
最后说个教训:这套设计里最容易被忽视的不是算法,而是时序——上电顺序、充电与逆变的切换、保护后的恢复逻辑,任何一处顺序错了,表现都是“偶发死机”这种玄学问题。从那以后我每次改完控制逻辑,都强制在切换点打 GPIO 翻转,用逻辑分析仪把整个时序抓一遍再上功率。希望帮到你。
本文还有配套的精品资源,点击获取