1. 储能系统状态机设计背景
搞过新能源系统的兄弟都清楚,5kW级别的储能逆变器最难啃的骨头就是并网/离网无缝切换。这玩意儿涉及到电网同步、孤岛检测、模式切换三大死亡关卡,一个没处理好轻则设备保护停机,重则直接炸管放烟花。
去年我们团队接手某海外离网项目时,实测市电切换过程中电压波动超过±15%就会触发保护。后来用STM32F103搞定了毫秒级切换的状态机,今天就把核心代码和设计思路扒开来聊聊。
2. 系统架构与硬件选型
2.1 主控芯片的取舍
为什么选STM32F103C8T6这颗老将?三个硬核理由:
- 72MHz主频配合硬件FPU,能扛住5kW系统的PWM实时计算
- 内置12位ADC采样电网电压相位误差<1°
- 5美元以内的价格让方案有竞争力
实测在切换过程中:
- ADC采样周期控制在50μs
- 状态判断逻辑执行时间<100μs
- 整个切换过程控制在3ms内
2.2 关键外围电路设计
电网检测部分用了这种骚操作:
c复制// 电网电压检测电路
#define GRID_VOLTAGE_THRESHOLD 180 // 电压有效值阈值
if(Get_ADCValue(ADC_Channel_1) < GRID_VOLTAGE_THRESHOLD){
grid_status = OFF_GRID;
}
电池管理用了TI的BQ76940,通过I2C和STM32通讯。这里有个坑:电池组均衡时会产生干扰,建议在状态切换前暂停均衡。
3. 状态机核心逻辑实现
3.1 七种工作状态定义
c复制typedef enum {
INIT_MODE, // 初始化
GRID_TIE_MODE, // 并网发电
OFF_GRID_MODE, // 离网运行
TRANS_TO_GRID, // 向并网切换
TRANS_TO_OFFGRID, // 向离网切换
FAULT_MODE, // 故障状态
STANDBY_MODE // 待机
} SystemState_t;
3.2 状态转移触发条件
用二维数组定义转移矩阵,比if-else更清晰:
c复制const StateTransition_t transitionTable[NUM_STATES][NUM_EVENTS] = {
// 事件:电网恢复 电网异常 负载突变 故障触发
[INIT_MODE] = {GRID_TIE_MODE, OFF_GRID_MODE, INIT_MODE, FAULT_MODE},
[GRID_TIE_MODE] = {GRID_TIE_MODE, TRANS_TO_OFFGRID, GRID_TIE_MODE, FAULT_MODE},
// 其他状态转移规则...
};
3.3 切换过程的关键代码
并网同步这段最要命,我们用了软件锁相环(SPLL):
c复制void GridSynchronization(void){
// 1. 采样电网电压
gridVoltage = ADC_GetVoltage(GRID_PHASE_A);
// 2. 计算相位差
phaseError = CalculatePLLError(gridVoltage);
// 3. 调整逆变器输出
AdjustPWMPhase(phaseError);
// 4. 检查同步条件
if(fabs(phaseError)<5.0 && fabs(voltageDiff)<10.0){
SetSyncFlag(TRUE);
}
}
4. 实测避坑指南
4.1 硬件层面的坑
- 电压采样必须用真有效值芯片(我们用的AD8436),普通ADC测畸变波形会翻车
- 继电器要用宏发的HF32F-G,普通继电器切换时触点弹跳会导致波形畸变
- 在DC-DC环节预充10%负载,避免切换时空载震荡
4.2 软件层面的坑
c复制// 错误示例:直接切换PWM模式
void Wrong_SwitchMode(void){
PWM_Stop();
Change_Params();
PWM_Start(); // 这里会产生电压尖峰
}
// 正确做法:渐变过渡
void Correct_SwitchMode(void){
for(int i=0; i<100; i++){
PWM_Duty += step;
Delay_us(10);
}
}
5. 性能优化技巧
5.1 状态机执行效率
- 把高频检测事件(如过流)放在定时中断里
- 低频状态(如电池SOC)用状态机主循环处理
- 关键代码用汇编优化:
assembly复制; 快速判断电网状态
GridCheck:
LDR R0, =ADC_BASE
LDR R1, [R0, #ADC_DR]
CMP R1, #GRID_THRESHOLD
BGT Grid_OK
5.2 抗干扰设计
- 所有状态变量加CRC校验
- 重要标志位用三取二表决
- 状态恢复函数要能回溯历史状态
6. 现场问题排查实录
去年在海南某项目出现的灵异现象:系统在雷雨天气会误切离网。最后发现是这么解决的:
- 现象:电网电压采样出现毛刺
- 排查:用示波器抓取ADC输入波形
- 发现:TVS管响应速度不够
- 解决:换成SMBJ系列TVS管并增加RC滤波
c复制// 修改后的抗干扰算法
#define SAMPLE_NUM 5
uint16_t GetStableVoltage(void){
uint16_t buf[SAMPLE_NUM];
for(int i=0; i<SAMPLE_NUM; i++){
buf[i] = ADC_Read();
Delay_us(20);
}
return MedianFilter(buf); // 中值滤波
}
7. 系统测试数据
在5kW样机上实测数据:
| 测试项目 | 国标要求 | 本方案实测 |
|---|---|---|
| 切换时间 | <2s | 12ms |
| 输出电压畸变率 | <5% | 2.3% |
| 孤岛检测时间 | <2s | 0.8s |
关键波形截图:
- 并网切换时的电压重合度(相位差<3°)
- 离网切换时的负载电压波动(<±5%)
8. 代码工程结构建议
我们的项目这样组织:
code复制/Drivers
/BSP
grid_detect.c # 电网检测
relay_ctrl.c # 继电器驱动
/LIB
pll_algorithm.c # 锁相环库
/Application
state_machine.c # 状态机主逻辑
fault_handler.c # 故障处理
特别提醒:状态机变量必须放在保留内存段,防止被意外修改:
c复制__attribute__((section(".noinit"))) SystemState_t currentState;
这套方案经过两年现场验证,最久的单机已无故障运行16000+小时。最后分享一个骚操作:在STM32的Flash最后页存状态日志,出问题时直接读Flash分析,比串口日志靠谱得多。