光伏储能系统里,三端口DC-DC变换器是个绕不开的核心部件。它要同时对接光伏板、储能电池和负载母线三个端口,既要保证光伏最大功率输出,又要管理电池充放电,还得稳住母线电压。我接触这个方向有几年了,从最早用分立MOS搭同步Buck-Boost,到后来用STM32F103CBT6做主控跑MPPT和协同逻辑,踩过的坑不算少。这篇文章不打算写成教科书式的原理推导,而是把我实际做过的方案、选型理由、调试过程中遇到的问题和解决思路完整摊开来讲。如果你正在做光伏储能相关的电力电子项目,或者想搞清楚三端口变换器到底怎么协调三个端口之间的能量流动,这篇内容应该能帮你省下不少试错时间。
1. 三端口变换器的拓扑选择与能量流动逻辑
1.1 为什么是三端口而不是三个独立变换器
很多人第一反应是:光伏用一个Boost升压,电池用一个双向Buck-Boost,负载再用一个Buck,三个独立模块各管各的不就行了?理论上可以,但实际做下来问题很明显。三个独立变换器意味着三套电感、三套驱动、三套采样电路,体积和成本直接翻倍。更关键的是,三个模块之间需要额外的通信和协调逻辑,响应速度受限于通信延迟,母线电压在负载突变时容易出现较大的暂态波动。
三端口变换器的核心思路是让三个端口共享部分功率通道。以常见的三有源桥(TAB)或者Boost+双向Buck-Boost集成的拓扑为例,光伏端口和电池端口通过一个公共的直流母线耦合,能量可以在任意两个端口之间直接流动,不需要经过第三端口中转。这样做的好处是功率密度高、动态响应快,而且控制逻辑可以统一在一个MCU里完成。
我实际用的是Boost+双向Buck-Boost+母线电容的结构:光伏端经过Boost升压到母线,电池端通过双向Buck-Boost挂在母线上,负载直接取母线电压。这个拓扑的好处是每个端口的功率器件应力可控,而且光伏MPPT和电池充放电管理可以解耦设计。
1.2 能量流动的四种工作模式
三端口变换器在实际运行中会切换四种基本模式,理解这四种模式是设计协同控制策略的前提:
| 工作模式 | 光伏状态 | 电池状态 | 负载状态 | 能量流向 |
|---|---|---|---|---|
| 模式一 | 输出功率>负载需求 | 充电 | 正常工作 | 光伏→负载+电池 |
| 模式二 | 输出功率<负载需求 | 放电 | 正常工作 | 光伏+电池→负载 |
| 模式三 | 输出功率≈负载需求 | 待机 | 正常工作 | 光伏→负载 |
| 模式四 | 无输出(夜间/遮挡) | 放电 | 正常工作 | 电池→负载 |
模式切换的平滑性是个大问题。我最早做的版本在模式一和模式二之间切换时,母线电压会掉0.5V左右再恢复,持续时间大概2ms。对于12V母线来说这个波动还能接受,但如果母线是48V或者更高,同样的切换逻辑可能导致更大的跌落。后来我在切换逻辑里加了前馈补偿,把电池电流环的给定值在模式切换瞬间做一个斜坡过渡,波动压到了0.1V以内。
1.3 拓扑参数的计算与器件选型
以我最近做的一个48V母线、光伏输入18-36V、电池24V/10Ah、负载功率200W的系统为例,关键参数计算如下:
Boost电感计算:光伏端最大电流出现在最低输入电压时,假设光伏最大功率150W,最低输入18V,则输入电流约8.3A。取纹波系数0.3,开关频率100kHz,电感值L=(18×0.3)/(0.3×8.3×100k)≈21.7μH,实际取22μH。电感饱和电流要留足余量,我选的是33μH/15A的铁硅铝磁环电感,实测满载温升约35℃。
双向Buck-Boost电感计算:电池端电压24V,母线48V,最大充放电电流5A。取纹波系数0.4,开关频率100kHz,电感值L=(48×0.4)/(0.4×5×100k)≈96μH,实际取100μH。这个电感需要承受双向电流,选型时要注意磁芯的直流偏置特性。
MOS管选型:光伏Boost用IRF3205(55V/110A/Rds(on)=8mΩ),电池端用IRF4905(P沟道,55V/74A)做上管、IRF3205做下管。母线电容用两个470μF/63V的电解电容并联,再并一个10μF的陶瓷电容滤高频。
注意:电感选型时不要只看电感值,饱和电流和温升电流才是决定实际性能的关键。我早期用过一个标称22μH/10A的电感,实际在8A时就已经明显饱和,效率直接从94%掉到87%。
2. STM32F103CBT6主控的资源分配与外设配置
2.1 为什么选STM32F103CBT6而不是更高级的型号
STM32F103CBT6是Cortex-M3内核,72MHz主频,128KB Flash,20KB RAM,48引脚LQFP封装。对于三端口变换器来说,这个资源刚好够用而且有余量。我算过一笔账:MPPT算法(扰动观察法)大概占2KB Flash,协同控制状态机占3KB,PID调节器占1KB,保护逻辑占2KB,通信协议占3KB,加起来不到12KB。RAM方面,ADC采样缓冲区、PID中间变量、状态机变量加起来不到4KB。
选它而不是F4系列的原因很简单:F103的ADC和定时器资源对于100kHz开关频率的控制环路完全够用,而且价格便宜、供货稳定。F4的浮点单元在这里用不上,因为PID和MPPT用定点运算就足够了,浮点反而增加中断响应时间。
2.2 定时器与ADC的资源映射
三端口变换器需要三路PWM输出(光伏Boost、电池Buck上管、电池Buck下管),同时需要采样光伏电压/电流、电池电压/电流、母线电压、负载电流,共7路ADC通道。我的资源分配如下:
- TIM1:高级定时器,产生三路互补PWM,带死区控制。CH1驱动光伏Boost MOS,CH2/CH3驱动电池端上下管。死区时间设为500ns,这个值是根据IRF3205的开关时间和驱动电路延迟实测调出来的。
- TIM2:通用定时器,做系统时基,1ms中断一次,用于状态机调度和软启动计时。
- TIM3:触发ADC采样,设置在PWM中心对齐模式的计数峰值触发,这样可以采样到电感电流的平均值而不是开关纹波。
- ADC1:扫描模式,7个通道轮流转换,DMA搬运到缓冲区。采样时间设为55.5个ADC周期,总转换时间约7μs,远小于10μs的开关周期。
// ADC+DMA配置关键代码 void ADC_Config(void) { ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_Value; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 7; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_CC1; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 7; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); // ... 其余6个通道配置 ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); }2.3 中断优先级与实时性保障
三端口变换器的控制环路对实时性要求很高。我的中断优先级安排是:PWM周期中断(用于电流环计算)优先级最高,设为0;ADC转换完成中断优先级1;1ms时基中断优先级2;串口通信中断优先级3。
这里有个坑:STM32F103的NVIC优先级分组要设成NVIC_PriorityGroup_2,这样抢占优先级和响应优先级各占2位,才能保证PWM中断能打断其他所有中断。我最早用默认分组,结果串口中断偶尔会延迟PWM中断的响应,导致母线电压出现周期性抖动。
提示:PWM中断服务函数里只做最必要的计算(电流环PID和PWM占空比更新),其他逻辑全部放到1ms时基中断里。中断服务函数执行时间控制在5μs以内,否则会影响下一个周期的采样。
3. MPPT算法的定点化实现与实测对比
3.1 扰动观察法在STM32上的定点化改造
MPPT算法有很多种,电导增量法、扰动观察法、恒压法、模糊控制等等。我选的是扰动观察法(P&O),原因是逻辑简单、参数少、在STM32F103上跑定点运算效率很高。但标准P&O有个固有缺陷:稳态时会在最大功率点附近振荡,造成能量损失。
我的改进方案是变步长P&O:当功率变化率大时用大步长快速跟踪,接近最大功率点时自动减小步长。具体实现是把功率差值做归一化处理,步长与功率差值成正比,但设置上下限。
// 变步长扰动观察法定点实现 #define STEP_MAX 20 // 最大步长(PWM占空比单位) #define STEP_MIN 2 // 最小步长 #define P_THRESH 50 // 功率阈值 int16_t MPPT_Update(int16_t voltage, int16_t current) { static int16_t duty = 500; // 初始占空比50% static int16_t last_power = 0; static int16_t last_voltage = 0; static int8_t direction = 1; int16_t power = (voltage * current) >> 10; // 定点乘法,缩小1024倍 int16_t delta_p = power - last_power; int16_t delta_v = voltage - last_voltage; int16_t step; // 计算变步长 if (delta_p > P_THRESH || delta_p < -P_THRESH) step = STEP_MAX; else if (delta_p > 0) step = STEP_MAX - (STEP_MAX - STEP_MIN) * delta_p / P_THRESH; else step = STEP_MIN; // 判断扰动方向 if (delta_p > 0) { if (delta_v > 0) direction = 1; else direction = -1; } else { if (delta_v > 0) direction = -1; else direction = 1; } duty += direction * step; if (duty > 900) duty = 900; if (duty < 100) duty = 100; last_power = power; last_voltage = voltage; return duty; }3.2 采样时机对MPPT跟踪速度的影响
MPPT的跟踪速度很大程度上取决于采样时机。如果采样点落在电感电流的纹波峰值上,计算出的功率会偏高;落在谷值上则偏低。这两种情况都会导致MPPT误判扰动方向。
我的做法是用TIM3在PWM中心对齐模式的计数峰值触发ADC,这时候电感电流正好处于平均值附近。实测下来,同样的变步长P&O,采样时机正确时跟踪到最大功率点的时间约0.8s,采样时机不对时要1.5s以上,而且稳态振荡幅度大一倍。
另外,光伏电压和电流的采样要同步。我用的是双ADC规则同步模式,ADC1采电压、ADC2采电流,两个ADC同时触发转换,这样算出来的瞬时功率才是准确的。如果分时采样,在光照快速变化时功率计算会有明显误差。
3.3 局部阴影下的多峰问题与应对
光伏板在局部阴影下P-V曲线会出现多个峰值,标准P&O会陷入局部最优。我试过两种方案:一种是定期做全范围扫描,每30秒从0%到90%占空比扫一遍,找到全局最大功率点;另一种是用粒子群优化算法,但计算量太大,STM32F103跑起来吃力。
最终我采用的是折中方案:正常运行时用变步长P&O快速跟踪,每60秒做一次全范围扫描。扫描时占空比从10%到90%分16个点,每个点稳定200ms后记录功率,找到最大值后把P&O的初始工作点设到那里。这个方案在局部阴影下能找回全局最大功率点,代价是每60秒有约3秒的功率损失。
| 方案 | 跟踪速度 | 局部阴影表现 | CPU占用 | 实现复杂度 |
|---|---|---|---|---|
| 标准P&O | 快 | 易陷局部最优 | 低 | 低 |
| 变步长P&O | 较快 | 易陷局部最优 | 低 | 中 |
| 全范围扫描+P&O | 慢 | 能找到全局最优 | 中 | 中 |
| 粒子群优化 | 很慢 | 能找到全局最优 | 高 | 高 |
4. 协同控制策略的状态机设计与模式切换
4.1 状态机的整体架构
协同控制的核心是一个有限状态机,根据光伏功率、电池SOC、负载功率三个输入量决定当前工作模式。我的状态机有五个状态:初始化、光伏优先、电池补充、电池充电、故障保护。
状态转移条件如下:
- 初始化→光伏优先:母线电压建立到45V以上,且各端口采样值正常
- 光伏优先→电池充电:光伏功率>负载功率+50W,且电池SOC<90%
- 光伏优先→电池补充:光伏功率<负载功率-20W,且电池SOC>20%
- 电池充电→光伏优先:电池SOC>95%或光伏功率<负载功率
- 电池补充→光伏优先:光伏功率>负载功率+10W
- 任意状态→故障保护:母线过压/欠压、电池过流、光伏过压
状态机的执行周期是1ms,每次执行时先采样各端口状态,然后判断转移条件,最后执行当前状态的控制逻辑。
4.2 模式切换时的母线电压波动抑制
模式切换是协同控制里最棘手的部分。从光伏优先切到电池补充时,光伏Boost的占空比还在MPPT控制下,但电池端要突然从充电切换到放电,如果切换瞬间电池电流环的给定值跳变,母线电压会先被拉低再被拉高,形成振荡。
我的解决方案是三步走:第一步,在切换前先把光伏Boost的占空比锁定在当前值,暂停MPPT;第二步,电池电流环的给定值从当前值斜坡过渡到目标值,斜坡时间50ms;第三步,过渡完成后恢复MPPT,同时把电池电流环交给母线电压外环控制。
实测下来,这个方案把模式切换时的母线电压波动从±0.8V压到了±0.15V,恢复时间从5ms缩短到1.5ms。
4.3 电池充放电管理的三段式策略
电池管理我采用的是三段式充电:恒流、恒压、浮充。恒流阶段电流设为0.5C(5A),恒压阶段电压设为28.8V(24V电池的1.2倍),浮充阶段电压降到27.6V。
放电管理相对简单,主要是设置放电截止电压(21V)和最大放电电流(5A)。但有个细节要注意:电池内阻会随SOC降低而增大,在SOC低于30%时,同样的放电电流会导致端电压明显下降。如果放电截止电压设得太高,电池容量用不完;设得太低又伤电池。我的做法是在SOC低于30%时把最大放电电流降到3A,这样端电压下降不那么剧烈,电池能多放出约15%的容量。
注意:电池SOC估算不要只用开路电压法,在充放电过程中电压受内阻影响很大。我用的方法是安时积分+开路电压校正,静置30分钟后用开路电压修正SOC,运行中用安时积分跟踪。
5. 硬件调试中遇到的典型问题与排查过程
5.1 母线电压在轻载时周期性跳变
这个问题困扰了我将近一周。现象是负载功率低于20W时,母线电压每隔约200ms跳变一次,幅度约0.3V。用示波器抓PWM波形发现,光伏Boost的占空比在周期性变化,但MPPT的功率变化并不大。
排查过程:先怀疑是MPPT步长太大,把步长从20降到5,跳变频率降低了但没消失。然后怀疑是ADC采样噪声,在ADC输入端加了RC滤波(1kΩ+100nF),跳变幅度减小到0.1V但仍有。最后用示波器同时抓母线电压和电池端电流,发现电池端在轻载时进入了间歇工作模式——电池电流环的给定值很小,PWM占空比接近0,但电池电压略高于母线电压,导致电池通过体二极管向母线倒灌电流,触发过流保护后又关闭,形成周期性振荡。
解决方案:在轻载时把电池端切换到纯放电模式,禁止充电,同时把电池端PWM频率从100kHz降到50kHz,减少开关损耗。另外在电池端加了一个防倒灌的理想二极管电路(用MOS管+比较器实现),彻底解决了倒灌问题。
5.2 MPPT在光照突变时误判方向
光照从200W/m²突然跳到800W/m²时,MPPT会先往错误方向扰动一次,然后才纠正回来。原因是光照突变导致功率变化很大,P&O误以为是自己的扰动引起的。
解决方法是加一个光照变化率检测:如果连续两个采样周期的功率变化超过阈值(比如50W),就判定为光照突变,暂停MPPT一个周期,等功率稳定后再恢复。这个逻辑用了一个简单的计数器实现,代码量不到20行,但效果很明显,光照突变时的误判次数从每次必误降到基本不误。
5.3 电池端Buck-Boost的直通问题
双向Buck-Boost在切换充放电方向时,如果上下管的死区时间不够,会出现直通短路。我最早设的死区是200ns,在实验室测试没问题,但在高温环境下(环境温度45℃以上)偶尔会炸管。
后来用示波器抓上下管的驱动波形,发现高温下MOS管的关断延迟从80ns增加到了150ns,200ns死区不够了。把死区加到500ns后问题解决。但死区加大会降低效率,实测死区从200ns加到500ns,效率从95.2%降到94.1%。这个代价是值得的,毕竟可靠性优先。
提示:死区时间一定要在最高工作温度和最低工作温度下都实测验证,不能只看常温。我现在的做法是在高低温箱里各跑2小时,用示波器确认驱动波形没有交叠。
6. 系统实测数据与效率优化经验
6.1 满载效率与损耗分布
系统在48V母线、200W负载、光伏输入150W、电池补充50W的条件下,实测整体效率92.3%。各环节损耗分布如下:
| 损耗环节 | 损耗功率 | 占比 |
|---|---|---|
| 光伏Boost开关损耗 | 3.2W | 20.8% |
| 光伏Boost导通损耗 | 2.1W | 13.6% |
| 电池Buck-Boost开关损耗 | 2.8W | 18.2% |
| 电池Buck-Boost导通损耗 | 1.9W | 12.3% |
| 电感铜损 | 2.4W | 15.6% |
| 驱动与控制损耗 | 1.5W | 9.7% |
| 其他(采样、保护等) | 1.5W | 9.8% |
从损耗分布看,开关损耗是大头。我把光伏Boost的开关频率从100kHz降到70kHz,效率提升了1.2个百分点,但电感体积增大了约30%。最终折中在85kHz,效率93.1%,电感体积增加15%。
6.2 轻载效率的优化
轻载效率是光伏储能系统的痛点。负载低于30W时,系统效率从92%掉到85%以下。主要原因是开关损耗和驱动损耗在轻载时占比大幅上升。
我的优化措施有三个:一是轻载时自动降低开关频率到50kHz;二是关闭电池端的PWM,让电池通过理想二极管直接供电;三是把MCU从72MHz降到36MHz,降低自身功耗。这三项措施把轻载效率从84.5%提升到了89.2%。
6.3 长期运行的数据记录与分析
我在系统里加了一个基于串口的简易数据记录功能,每10秒通过串口发送一次运行数据(光伏电压/电流/功率、电池电压/电流/SOC、母线电压、负载功率、效率)。用电脑端的一个Python脚本接收并存入CSV文件。
连续运行72小时的数据显示:白天光伏优先模式占比约65%,电池充电模式占比约20%,电池补充模式占比约10%,夜间电池放电模式占比约5%。整体能量利用率(负载消耗能量/光伏输出能量+电池放电能量)约91.7%。电池SOC在30%-95%之间循环,没有出现过放或过充。
# 串口数据记录脚本关键部分 import serial import csv import time ser = serial.Serial('COM3', 115200, timeout=1) with open('system_log.csv', 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['timestamp','pv_v','pv_i','pv_p','bat_v','bat_i','soc','bus_v','load_p','eff']) while True: line = ser.readline().decode('ascii', errors='ignore').strip() if line.startswith('DATA'): values = line.split(',')[1:] writer.writerow([time.time()] + values) f.flush()这套系统从最初的面包板验证到最终的PCB版本,前后迭代了四版。最大的体会是:三端口变换器的难点不在单个端口的控制,而在端口之间的协调。MPPT算法再精确,如果模式切换时母线电压波动大,整体效率也上不去。协同控制的状态机设计要考虑到所有可能的工况组合,尤其是光照突变、负载突变、电池SOC边界这些场景。硬件上,死区时间、电感饱和、采样时机这三个点最容易出问题,建议在调试初期就用示波器逐一确认。