离网/并网切换时序与上位机联锁实战:厂家标称“切换时间 <10ms”,可每次停电路由器照样重启——我把那 118ms 一层层拆开,发现 90 毫秒根本不在开关上
先说结论,免得看到一半才发现不是你要的:
标称“切换时间 <10ms”这句话,缺一个起止点。 绝大多数厂家的 10ms 指的是静态开关(STS/晶闸管)从“判定要切”到“主回路换向”的动作时间;而从“电网电压真的跌下去”到“负载端电压真的恢复”,中间还隔着有效值检测窗、去抖确认、继电器释放、逆变建压四段。这四段加起来,在一台没有做相位连续的机器上,轻松就是 100ms 以上。
我手上这台 5kW 户用储能,实测 118ms。分解下来:检测 7.2ms、去抖 40ms、继电器 8ms、建压 50ms、软件调度残差 13ms。真正花在“开关”上的只有 8ms,占 6.8%。
而 118ms 这个数,足够让一台路由器重启、让一台工控机掉盘、让一台服务器报电源故障。这就是为什么“标称 10ms”和“体验上每次都断”可以同时为真——它们说的不是同一件事。
这篇要解决的是三件事:
- 把切换时间拆成可以一段段核对的账,而不是接受一个笼统的标称值;
- 把“无缝”的物理条件说清楚——它不是“快”,是“相位不断”;
- 把上位机(EMS/监控)在这件事里的位置摆正——它不该做切换决策,它该做的是知情、联锁、事后重对齐。
第三点是我见过的现场里错得最多的一条。太多系统的架构是“EMS 每秒读一次状态,读到电网没了就下发离网命令”。这条链路的总延迟是秒级的,而切换窗口是百毫秒级的。用秒级的环去控百毫秒级的事,注定控不住。
一、先把两个“切换”分开
中文里都叫“并离网切换”,但这是两个方向相反、难点完全不同的动作。混着讲,参数一定串。
| 方向 | 触发 | 主要矛盾 | 时间量级 | 失败表现 |
|---|---|---|---|---|
| 并网 → 离网 | 电网故障,被动 | 不能断太久,负载要活 | 10~200ms | 负载重启、掉盘、PLC 复位 |
| 离网 → 并网 | 电网恢复,主动 | 不能乱合,冲击要小 | 0.5~60s | 冲击电流、断路器跳、反复并脱 |
并转离的核心指标是 断电时长(transfer time)。
离转并的核心指标是 同期质量(Δθ、Δf、ΔU)+ 确认时长。
这两件事共用的只有一套东西:状态机、状态字、写权联锁。所以固件里必须是一台机器两个方向,但判据参数、去抖时长、允许写入的寄存器集合,全都是两套。把它们写成一套“切换参数”,是后面所有毛病的起点。
这篇以“并转离”为主线(因为它有明确的毫秒账可算),第八节单独讲“离转并”。
二、七层时间轴:T0 到 T6
我把整条链路切成七层。分层的好处是:现场出问题,先定位到层,再往下查参数,不用满世界翻。
| 层 | 名字 | 这一层决定什么 | 典型量级 |
|---|---|---|---|
| T0 | 事件源 | 谁最先知道电网没了 | 0 ~ 1000ms |
| T1 | 判据 | 什么条件下认定“真没了” | 7 ~ 60ms |
| T2 | 断开 | 主回路什么时候真的断开 | 2 ~ 150ms |
| T3 | 建立 | 离网电压什么时候建起来 | 20 ~ 200ms |
| T4 | 观测 | 上位机什么时候能看到 | 100ms ~ 数秒 |
| T5 | 联锁 | 这段窗口里谁不许写什么 | — |
| T6 | 回切 | 什么时候允许再并回去 | 0.5 ~ 60s |
T0 是最容易被忽略、但最致命的一层。 因为“谁先知道”直接决定了后面的账有没有意义。
- PCS 自己采样:最快,7~20ms 就能拿到结论。这是唯一正确的切换决策源。
- 外部电表 / STS 控制器:多一次通信,+10~100ms,且要看它是 Modbus 还是硬线。
- 上位机 / EMS:多一个轮询周期,+200ms ~ 数秒。
我见过一个项目,切换逻辑写在 EMS 里:EMS 每 500ms 读一次 PCS 的电网电压,低于阈值就下发“转离网”命令。听起来没问题,实际是:
电网跌落 ──→ 最多 500ms 后才被读到 ──→ 下发命令 20ms ──→ PCS 执行 118ms
总延迟最坏 638ms,最好 138ms
这个系统在“最好的情况”下都比一台纯本机逻辑的机器慢一个数量级。 而且它的延迟是抖的——取决于电网跌落发生在轮询周期的哪个位置。这种抖动在现场表现为“有时候断一下没事,有时候设备全重启”,然后所有人开始怀疑是“电网质量问题”。
所以 T0 的第一条铁律:
切换决策必须在保护任务里做,周期 ≤1ms。上位机只能知情,不能决策。
上位机可以做的是:读状态、记事件、切换后重新下发指令、报警。这些都不需要毫秒级响应。
三、T1 判据:窗口长度就是你的延迟下限
这一节是整篇里最“反直觉”的部分,也是我最想让人记住的一段。
3.1 有效值窗不是“延迟 W”,而是“延迟多少取决于跌多深”
绝大多数固件用滑动窗口算 RMS。窗口宽度 W 通常取一个工频周期 20ms(全波),或者半个周期 10ms(半波)。
问“检测要多久”,很多人的回答是“一个周期,20ms”。这个答案是错的,而且错得很有规律。
设电网电压从 U_old 跌到 U_new(用比例 k = U_new/U_old 表示),跌落发生在 t=0。窗口里在 t 时刻有 (t/W) 的比例是新值,剩下 (1-t/W) 是旧值。于是:
RMS²(t) = (1 - t/W) × U_old² + (t/W) × U_new²
要让它跌到门限 thr(相对 Un)以下,解出:
t/W = (1 - thr²) / (1 - k²)
代入 U_old = 230V、门限 0.80Un、全波窗 W = 20ms:
| 跌到 k | 残余电压 | t/W | 检测延迟 | 性质 |
|---|---|---|---|---|
| 0.00 | 0 V | 0.360 | 7.2 ms | 深跌,最快 |
| 0.10 | 23 V | 0.364 | 7.3 ms | 深跌 |
| 0.30 | 69 V | 0.396 | 7.9 ms | 深跌 |
| 0.50 | 115 V | 0.480 | 9.6 ms | 中跌 |
| 0.60 | 138 V | 0.562 | 11.3 ms | 中跌 |
| 0.70 | 161 V | 0.706 | 14.1 ms | 浅跌,明显变慢 |
| 0.75 | 172 V | 0.823 | 16.5 ms | 浅跌 |
| 0.79 | 182 V | 0.958 | 19.2 ms | 浅跌,接近满窗 |
| ≥0.80 | ≥184 V | — | 永不越限 | 残压高于门限 |
跌到 0,只要 7.2ms 就越限;跌到 0.79Un,要 19.2ms。 差了 2.7 倍。
这条规律的意义在于:“我的检测只要 5ms”这种说法,必须带上“跌到多少”才有意义。 厂商宣传的快速检测,测的是深度跌落(往往是直接断开,跌到 0)。而现场真正难处理的,是那种“跌到 0.7~0.8Un、持续几十毫秒”的浅跌——它慢、它不触发、它让设备在临界电压上跑。
顺带解释了一个常见现象:同一台机器,在电网强的现场切换很利索,在电网弱的现场切换很肉。 因为弱电网的跌落往往更浅、更“软”,正好落在上表最慢的那一段。
3.2 半波窗的诱惑与代价
半波窗(10ms)能把上表所有延迟减半。听起来很香。
代价是:半波 RMS 对直流分量和偶次谐波不敏感。当负载里有半波整流、或者变压器有直流偏磁时,波形正负半周不对称,半波 RMS 会长期偏移——轻则门限漂移(本来 0.8Un 的动作点变成 0.83Un),重则稳态误判(电压明明正常,读出来偏低)。
离网切换判据建议一律用全波窗。 想快,从别的地方省:去抖次数、建压斜坡,这两个是可以谈的;窗长是不能谈的,它是精度的物理下限。
3.3 去抖:这才是 40ms 的去处
检测只花 7.2ms,那 118ms 里的第二个大头在哪?在去抖。
“去抖”的意思是:检测到一次越限不算数,要连续 N 次/连续 N 个周期都越限才算数。原因是电网里真实存在重合闸(recloser)——配电网故障后,断路器会在 0.3~1s 内自动重合一次,电压会短暂恢复;还有大负载投切造成的瞬时凹陷。
如果不去抖,机器会在一次重合闸里切两次,负载被折腾两遍。
我这台机器的参数是:连续 3 次判定(每次间隔 5ms)+ 再等 2 个工频周期确认 = 40ms。这个数在行业里算比较保守的,激进一点的做到 20ms,代价是误切率上升。
关键点:去抖时长必须和“负载能扛多久”一起定。 如果负载只能扛 40ms(见第六节),那 40ms 的去抖就是自杀——等确认完,负载已经死了。
这就引出一个设计约束,我把它写成不等式:
T_detect + T_debounce < T_load_holdup
左边是“确认电网真没了”的最短时间,右边是“负载自己能顶多久”。这条不等式不成立的时候,你只有两条路:要么换无缝方案(相位连续,根本不断电),要么接受负载会重启。
四、T2 断开:器件时间表,以及“10ms”到底指什么
把开关器件的真实时间摆出来,就知道 10ms 是从哪来的了。1 个工频周期 = 20ms,半个周期 = 10ms。
| 器件 | 动作 | 释放 | 备注 |
|---|---|---|---|
| 电磁继电器(小型功率) | 5 ms | 8 ms | 无灭弧,直流侧要注意拉弧 |
| 接触器(AC-1) | 10 ms | 20 ms | 吸合 10~20ms,释放 5~15ms |
| ATS 机械转换开关 | 80 ms | 150 ms | 含机械联锁与灭弧,总时间 100~300ms |
| STS 晶闸管(半控) | 4 ms | 4 ms | ≤ 半个工频周期,靠自然过零关断 |
| STS 晶闸管(全控/混合) | 2 ms | 2 ms | 可强制换流,代价是损耗与散热 |
| 固态继电器 SSR(过零型) | 10 ms | 10 ms | 最多等半个周期才导通 |
“<10ms”这个数字,来源就是晶闸管能在半个工频周期内完成换向。 这是真的,但它只描述 T2 这一层。
于是问题就变成了:T2 = 4ms,其他五层加起来是多少?
| 层 | 我这台 | 能不能压 |
|---|---|---|
| T1 检测 | 7.2 ms | 不能(窗长决定) |
| T1 去抖 | 40 ms | 能,但误切率上升 |
| T2 释放 | 8 ms | 能(换 STS 到 2~4ms) |
| T3 建压 | 50 ms | 能,但伤负载 |
| T4 软件残差 | 13 ms | 能,收紧任务周期 |
| 合计 | 118.2 ms |
反解一下:假设器件已经是理想 STS(0ms),要把总时间做到 10ms,留给“检测 + 去抖 + 建压”的预算只有 10ms。而检测一项就要 7.2ms。
所以,凡是号称 10ms 又没做相位连续的机器,标称值一定不含检测和建压。 这不是骗人,是行业里没有统一口径。问的时候只需要问一句:
“你们这个切换时间,起点是电网电压跌到多少伏,终点是负载端电压恢复到多少伏?”
问出这句话,对方要么给你一份实测波形,要么开始含糊。含糊的就按 100ms 估。
五、T3 建立:无缝的本质是“相位不断”,不是“快”
这一段是很多人绕不过去的弯。
5.1 为什么大多数机器要“重新建压”
常规流程是这样:检测到电网没了 → 封锁 PWM → 断开主继电器 → 切换控制模式(电流源 → 电压源)→ 重新启动电压环 → 输出电压从 0 斜坡升到 230V。
那个“斜坡”就是 50ms 的来源。为什么要斜坡?因为突然给一个感性负载(变压器、电机)加满电压,会产生很大的励磁涌流;给容性负载加满电压,会产生充电尖峰。斜坡是把冲击摊平。
问题是:斜坡一开,负载端的电压就从 230V 掉到 0 再爬回 230V。这中间是真空的。
5.2 相位连续:把“断电”变成“换源”
并网运行时,逆变器的输出电流是锁相于电网的——它内部一定有一个正在跟踪的相位角 θ(t),从 0 到 360° 循环。
电网消失的那一刻,θ 恰好等于某个值 θ0。
如果此时不让相位归零,而是让内部振荡器从 θ0 继续积分,那么逆变器切到电压源模式的瞬间,输出电压的相位就是连续的——负载看到的是一个“相位没有跳变、幅值没有跌落”的电源。
并网: u_out = U_grid × sin(θ_grid) θ 跟踪电网
↓ 电网消失,θ = θ0
离网: u_out = 230 × sin(θ_free) θ_free 从 θ0 继续积分
这么做的话,T3 的“建压”时间就消失了——因为电压从来没掉下去过,只是参考源从 PLL 换成了本地振荡器。整条链路只剩检测 + 去抖 + 换向,可以做到 10~20ms 量级。
但这里有个前提:切换必须在电流过零点附近完成(或者至少要让电感电流有续流路径),否则储能元件里的能量没处去,会顶出一个电压尖峰。工程上的做法是:封锁 PWM 后等一个 PWM 周期让电流续流完毕,再切模式;这个等待通常 50~200μs,可以忽略。
5.3 相位不连续会怎样
如果相位归零重来(θ_free 从 0 开始),而断开瞬间 θ0 = 180°,那么负载端看到的是一个从负半周突然跳到正半周的电压。
对纯阻性负载,就是一次电压跳变,没事。
对变压器,是一次直流偏磁——铁芯饱和,励磁电流飙升到额定电流的 5~10 倍,持续几个周期。现场表现是:切换瞬间变压器“嗡”一声巨响,有时连带空开跳闸。
对电机,是一次转矩冲击,机械上听得见。
这就是为什么有些现场“切换成功、负载也没重启,但变压器开始发热”——相位跳变的后遗症,往往几天后才显现。
5.4 固件骨架
/* 相位连续切换的核心:只换参考源,不重置积分器 */
typedef struct {
float theta; /* 当前相位,弧度,0~2pi */
float omega; /* 角频率,2*pi*50 */
float u_amp; /* 幅值 */
uint8_t mode; /* 0=并网(电流源) 1=离网(电压源) */
} inv_ctrl_t;
static inv_ctrl_t g;
/* 并网:theta 由 PLL 拉动;离网:theta 自积分 */
void ctrl_tick_1ms(void)
{
if (g.mode == MODE_GRID) {
g.theta = pll_get_theta(); /* 跟着电网走 */
} else {
g.theta += g.omega * 0.001f; /* 自己走,不清零 */
if (g.theta >= 6.2831853f) g.theta -= 6.2831853f;
}
}
/* 切换入口:只改 mode,不动 theta */
void transfer_to_island(void)
{
pwm_disable(); /* 封锁,等续流 */
delay_us(150);
g.mode = MODE_ISLAND; /* theta 保持连续 */
vloop_reset_but_keep_theta(); /* 积分器复位,相位保留 */
pwm_enable();
}
注意 vloop_reset_but_keep_theta() 这个函数名——它就是这个设计的全部要点。电压环的积分器要清(否则带着并网时的稳态误差进场),相位不能清。
六、T4 负载侧:电容储能除以功率,这是负载的全部底牌
前面一直在算“电源能多快恢复”。但真正决定“会不会重启”的,是负载自己能顶多久。
开关电源的输入侧一定有一个整流后的高压电容。AC 掉电后,负载靠这个电容里的能量继续工作,直到母线电压跌到稳压器的下限。
E = 0.5 × C × (V1² - V2²)
t = E / P
V1 是正常时的母线电压(230Vrms 整流后 ≈ 325V),V2 是开关电源还能稳压的最低母线电压(通常 180~250V,取决于拓扑和输入范围)。
按 118ms 的断电缺口,算几个真实负载:
| 负载 | C | V1 | V2 | P | hold-up | 118ms 缺口 |
|---|---|---|---|---|---|---|
| 路由器 + 光猫 | 33 μF | 325 V | 180 V | 30 W | 40.3 ms | 重启 |
| 工控机(ATX) | 330 μF | 325 V | 250 V | 120 W | 59.3 ms | 重启 |
| 服务器(冗余电源) | 660 μF | 325 V | 250 V | 500 W | 28.5 ms | 重启 |
| PLC(24V 开关电源) | 47 μF | 325 V | 200 V | 20 W | 77.1 ms | 重启 |
| 变频器控制板(取自直流母线) | 100 μF | 530 V | 400 V | 15 W | 403 ms | 扛得住 |
这张表解释了两件事。
第一,为什么“10ms 的 UPS 级”是个真实存在的品类。 不是营销话术,是这批电容真的只能顶 30~80ms。10ms 对它们是“够用且便宜”的设计点。而 118ms 对它们全是死。
第二,为什么变频器/伺服这类设备反而不怕。 它们的控制板从直流母线取电,母线电容大、电压高,能量按 V² 走,顶几百毫秒很轻松。真正娇气的是那些用小反激、母线电容几十微法的通信设备和工控机。
所以选型的时候有个很实用的判据:
问负载厂家一句:“你的电源 hold-up 时间是多少毫秒?”
答不上来的,按 20ms 估——那是个只够扛半个工频周期的数。
算下来,要把 118ms 做到“所有负载都不重启”,唯一的路是让断电时间小于最娇气那个负载的 hold-up。按 20ms 算,就是 T1+T2+T3 ≤ 20ms。这又回到了相位连续——只有它能把 T3 抹掉。
七、T5 联锁:Modbus 侧到底要做几件事
现在换到上位机这一侧。前面全是功率电子,这一节是通信与协议。
7.1 状态字必须分层,不能只有一个“运行”
最常见的错误设计:状态字只有一个 bit,“0 = 停机,1 = 运行”。于是“并网运行”和“离网运行”在协议上无法区分,EMS 只能靠“电网电压是否为零”去猜,而这个判断本身就有 20ms 以上的延迟和一堆边界情况。
正确的做法是状态分层 + 切换态单列:
| 位 | 名称 | 含义 |
|---|---|---|
| bit0 | GRID_TIED | 并网运行(电流源,跟随电网) |
| bit1 | ISLAND | 离网运行(电压源,自建网) |
| bit2 | TRANSFERRING | 切换中(两个方向都置这一位) |
| bit3 | FAULT | 故障停机 |
| bit4 | ISLAND_LOCKOUT | 孤岛保护动作后锁定,需人工/延时复位 |
| bit5 | STANDBY | 待机(无电网、无负载、等指令) |
| bit6 | LOCAL | 本地模式(远程写被禁止) |
| bit7 | DERATING | 降额运行(温度/电池限流导致) |
规则三条:
- bit0 与 bit1 互斥,同一时刻最多一个为 1;
- bit2 为 1 时,bit0/bit1 必须都是 0——切换中就是“既不在并网也不在离网”;
- bit2 的持续时间 = 实际切换时间,这是自证标称值的最好证据。
我特别推荐第 3 条。固件里记一个 transfer_ms,每次切换结束时把实测值写进寄存器。这样标称 10ms 的机器,跑一个月,寄存器里的最大值会诚实地告诉你它是 10 还是 118。
7.2 切换窗口内,哪些寄存器必须禁写
这是联锁的核心。原则很简单:凡是“只在并网下有意义”的量,离网时必须拒绝;凡是“只在离网下有意义”的量,并网时必须拒绝。
| 寄存器类 | 并网 | 离网 | 切换中 | 拒绝时回什么码 |
|---|---|---|---|---|
| 并网有功指令 | 允许 | 拒绝 | 拒绝 | 0x06 |
| 功率因数 / 无功指令 | 允许 | 拒绝 | 拒绝 | 0x06 |
| 充放电限流(电池侧) | 允许 | 允许(但按离网限值) | 拒绝 | 0x06 |
| 离网输出电压设定 | 拒绝 | 允许 | 拒绝 | 0x06 |
| 离网频率设定 | 拒绝 | 允许(下垂曲线参数) | 拒绝 | 0x06 |
| 开/关机 | 允许 | 允许 | 拒绝 | 0x06 |
| 故障复位 | 允许 | 允许 | 拒绝 | 0x04 |
| 参数保存(落 NVM) | 允许 | 允许 | 拒绝 | 0x06 |
| 只读量(电压/电流/SOC) | 允许 | 允许 | 允许 | — |
注意“充放电限流”这一行的差别:并网时它是“我允许多大功率进出电网”,离网时它是“我允许电池放多大电流给负载”。同一个寄存器,两个模式下语义完全不同。 如果 EMS 不知道当前模式,这个值写下去就是错的。
7.3 回哪个异常码,是有讲究的
Modbus 的异常码不是装饰,它承载了“为什么不让你写”这个信息。可惜现场 90% 的 EMS 只看“成功/失败”。异常码的基础语义在同站那篇 Modbus 协议在工业物联网中的实现 里铺过,这里只讲“切换联锁”这个特定场景该选哪个。
| 码 | 名称 | 含义 | 上位机该怎么处置 |
|---|---|---|---|
| 0x01 | 非法功能 | 从站不支持该功能码 | 查点表功能码列 |
| 0x02 | 非法数据地址 | 寄存器不存在或越界 | 多半是 4xxxx 换 0 基算错 |
| 0x03 | 非法数据值 | 值超范围 / 数量超上限 | 范围拒绝不要 clamp,直接回 03 |
| 0x04 | 从站设备故障 | 设备处于不可执行态 | 最容易被误当“通信故障”,其实设备活着 |
| 0x05 | 确认(长任务) | 已受理,需轮询结果 | 切换、自检、写 NVM 建议用它 |
| 0x06 | 从站设备忙 | 正在执行,请稍后重试 | 切换窗口内最贴切的码 |
| 0x08 | 存储奇偶差错 | 写入校验失败 | 重试并计数,连续失败升级为 04 |
| 0x0A | 网关路径不可用 | 网关后目标不可达 | 串口服务器/网关场景 |
| 0x0B | 网关目标无响应 | 目标设备超时 | 与 0A 的区别:网关在、下游不在 |
三条实务建议:
第一,切换中回 0x06。 这不是“协议错误”,是“现在不行、等会再来”,语义最准。比回 0x04(设备故障)更轻,比回 0x03(值非法)更诚实——值没错。
第二,写权互锁导致的拒绝回 0x04。 地址对、值也对,只是设备当前状态不允许(本地模式、停机、故障锁定)。这时候回 0x02 会让人去查地址,白查半天。
第三,异常码必须原样进日志。 只记“写入失败”的 EMS,等于把 90% 的诊断信息扔了。我在现场排查“半夜充放电限流莫名其妙变成 30A”这个问题,最后就是靠 EMS 日志里一条 EXC=06 定位到“那次写入正好撞上切换窗口,被正确拒绝了,但 EMS 重试逻辑把它当成了瞬时错误,三秒后又写了一次,那次写进去了但值是上一次的缓存值”。
这条链路如果 EMS 不记异常码,永远查不出来。
7.4 事件计数器:为什么必须有
这是我最想强调的一条设计。
切换只持续 118ms,而 EMS 的轮询周期是 1s。 也就是说:
命中概率 = 118 / 1000 = 11.8%
漏掉概率 = 88.2%
88% 的切换,EMS 完全看不见。 它看到的只是“上一次是并网,这一次是离网”,中间发生了什么一概不知。于是日志里全是“并网 → 离网”,没有“切换中”,看起来像是瞬间完成的——这恰恰印证了那个错误的标称值。
解决办法不是加快轮询(1s 已经占了不少带宽,提到 100ms 只是把命中率提到 100%,但代价是总线负载和从站 CPU),而是加一个单调递增的事件计数器:
| 寄存器 | 名称 | 类型 | 说明 |
|---|---|---|---|
| 0x0100 | 状态字 | U16 | 见 7.1 |
| 0x0101 | 模式 | U16 | 0 停机 / 1 并网 / 2 离网 / 3 切换中 |
| 0x0102 | 切换事件计数 | U16 | 每次切换结束 +1,模 65536 回绕 |
| 0x0103 | 上次切换原因 | U16 | 1 欠压 / 2 过频 / 3 孤岛 / 4 手动 / 5 远程 |
| 0x0104 | 上次切换耗时 | U16 | ms |
| 0x0105 | 最大切换耗时 | U16 | ms,掉电保持 |
EMS 每次轮询比对 切换事件计数,变了就说明中间发生过切换(可能不止一次),然后去读原因和耗时。这样即使 100 次切换里有 88 次没被“看见过程”,也能被“看见结果”。
最大切换耗时 这个寄存器是给验收用的。装完之后跑一个月,读一次这个值,就知道厂家的标称是真是假。
7.5 撕裂:一次读多个寄存器时的假数据
EMS 一次读 20 个寄存器(0x03 功能码),这 20 个寄存器不是同一时刻的快照,而是一帧一帧搬过来的。如果状态正好在搬运过程中变化,就会读到“前半段是旧状态、后半段是新状态”的混合体。
撕裂概率 = 读帧占空比 = 帧时长 / 轮询周期:
| 波特率 | 一帧(20 寄存器)时长 | 撕裂概率 |
|---|---|---|
| 9600 | 55.6 ms | 5.56% |
| 19200 | 27.8 ms | 2.78% |
| 115200 | 4.6 ms | 0.46% |
注意这个概率和“切换持续多久”无关——只和“读帧在时间轴上占多大比例”有关。这是个很容易算错的点,我第一版把它和切换时长乘在一起了,算出来 0.66%,实际是 5.56%,差了 8 倍。
撕裂的后果很具体:状态字读到“并网”,功率读到“离网值”,两个数各自都对,组合起来是假的。 EMS 拿这个组合去做功率平衡计算,就会得出一个不存在的结论。
防御办法是一致性校验,不要试图用协议层解决。这一条和 逆变器 Modbus 点表与上位机对接实战 里讲的“32 位量撕裂”是同一个物理事实:多寄存器读不是快照。 只是那次撕裂发生在两个寄存器之间,这次发生在“状态字”和“功率值”之间。
读到的状态是 GRID_TIED 时:
并网有功 与 离网输出电压 不可能同时有效
若 离网输出电压 > 50V,则本次读数整帧作废,下一轮重读
读到的状态是 ISLAND 时:
并网功率因数 必须是无效值/哨兵值
最省事的做法:状态字和它管辖的量放在同一帧里读,并且在固件里保证“状态切换时,同一帧内的相关量要么全是旧值、要么全是新值”。这需要固件提供一个“快照锁”——中断里改状态时,同时把整组影子寄存器原子更新。
八、T6 回切:难点不在同期,在“确认电网真的回来了”
并网转离网是被动的,越快越好。离网转并网是主动的,越稳越好。
8.1 同期三条件
合闸瞬间,逆变器输出电压与电网电压之间如果有差异,就会产生环流。三个差异各自对应一种环流:
相角差 Δθ → 有功冲击。 两个同幅值、相差 Δθ 的正弦,其瞬时差仍是一个正弦,有效值为:
ΔU = 2 × U × sin(Δθ / 2)
代入 U = 230V、回路阻抗 Z = 0.4Ω(电网短路阻抗 + 电缆 + 变压器漏抗折算):
| Δθ | ΔU_rms | 冲击电流 | 判定(按 130A 额定) |
|---|---|---|---|
| 0.5° | 2.01 V | 5.0 A | 可接受 |
| 1.0° | 4.01 V | 10.0 A | 可接受 |
| 2.0° | 8.03 V | 20.1 A | 偏大 |
| 3.0° | 12.04 V | 30.1 A | 偏大 |
| 5.0° | 20.06 V | 50.2 A | 偏大(38.5% 额定) |
| 8.0° | 32.09 V | 80.2 A | 不可接受 |
| 10.0° | 40.09 V | 100.2 A | 不可接受 |
| 20.0° | 79.88 V | 199.7 A | 严重 |
5° 就是 50A。 这个数字解释了很多现场现象:同期整定写成“±10° 允许合闸”,合闸瞬间 100A 冲击,空开跳,然后系统以为是“并网失败”,重试,再跳。
压差 ΔU → 无功环流。 ΔU = 2%(4.6V)在 Z=0.4Ω 下就是 11.5A,全是无功。这个量级通常能接受,但如果变压器容量小、Z 大,压差的影响会被放大。
频差 Δf → 相角滑移。 这是最微妙的一条:
滑移率 = 360 × Δf (度/秒)
| Δf | 滑移率 | 跨 ±5° 用时 | 跨 ±10° 用时 |
|---|---|---|---|
| 0.005 Hz | 1.8 °/s | 5.56 s | 11.11 s |
| 0.010 Hz | 3.6 °/s | 2.78 s | 5.56 s |
| 0.020 Hz | 7.2 °/s | 1.39 s | 2.78 s |
| 0.050 Hz | 18.0 °/s | 0.56 s | 1.11 s |
| 0.100 Hz | 36.0 °/s | 0.28 s | 0.56 s |
| 0.200 Hz | 72.0 °/s | 0.14 s | 0.28 s |
| 0.500 Hz | 180.0 °/s | 0.06 s | 0.11 s |
这张表是“合闸窗口”的全部。 窗口宽度 T = 2θ_max / (360 × Δf)。
窗口内还得走完:相角测量(1 个工频周期 20ms)+ 出口继电器(8ms)+ 断路器合闸(40~80ms),合计约 88ms。
反解一下“要多小的 Δf 才够”:
| 允许窗口 | Δf 上限 |
|---|---|
| 100 ms | 0.278 Hz |
| 200 ms | 0.139 Hz |
| 500 ms | 0.056 Hz |
| 1000 ms | 0.028 Hz |
| 2000 ms | 0.014 Hz |
好消息是:这条通常不是瓶颈。 离网时逆变器频率由本机晶振决定(50.00Hz ±0.01Hz),电网也在 50.00Hz 附近,Δf 天然就在 0.01~0.05Hz 量级,窗口 0.5~2.8s,够用。
坏消息是:如果离网时带了 70% 负载、还开了 5% 的 P-f 下垂,频率是 48.1Hz。 Δf = 1.9Hz,滑移率 684°/s,窗口 15ms。这时候同期合闸物理上不可能成功——执行链要 88ms,窗口只有 15ms。
这个坑非常隐蔽,因为它在空载测试时完全正常。第九节专门讲下垂。
8.2 真正的难点:确认电网“真的”回来了
电网恢复的瞬间,电压会先出现一个不稳定的过渡过程——配电网上游的重合闸、其他并网机组的再同步、大负载的重新投入,都会让电压在几百毫秒内反复波动。
如果“看到电压在窗口内”就合闸,会发生什么:合上 → 电网又跌 → 触发孤岛保护 → 断开 → 电网又恢复 → 再合。这个循环在现场叫“打摆子”,能持续几分钟,把断路器和继电器折磨到寿命耗尽。它的结构与 储能 PCS 绝缘监测与漏电流检测实战 里那条“误报来自读数生成、漏报来自判据结构”完全同源:这里不是判据错了,是判据的“确认时间”被省掉了。
所以回切的判据必须是:
电压在合格窗口内,且频率在合格窗口内,连续保持 T_confirm(通常 0.5~60s,可设)
T_confirm 怎么定?经验值:
- 户用/小型(<10kW):0.5~5s
- 工商业(10~100kW):5~30s
- 电网弱、历史上打摆子过的现场:60s
- 有柴油机/其他电源并联的微网:按最慢的那台来
这个参数必须可配,而且要能写进 NVM 掉电保持。 出厂默认 5s 是个比较安全的起点。
8.3 回切状态机
ISLAND
├─ 检测电网电压/频率进入窗口
↓
GRID_RETURN_CONFIRM(持续计时 T_confirm,任何一次越限立刻归零重来)
├─ 计时未到 → 留在本状态
├─ 计时到 → 且 Δθ、Δf、ΔU 都在同期窗口内 → 允许合闸
↓
SYNCING(相位跟踪,把本地振荡器拉到电网上,PLL 缓慢收敛)
├─ 收敛完成(Δθ < 阈值)→ 合闸
├─ 超时(如 3s 未收敛)→ 回 ISLAND,记一次失败,延时后重试
↓
GRID_TIED
注意 SYNCING 这一步不能省。直接从 ISLAND 合闸是靠“碰窗口”,而 SYNCING 是先把本地相位拉到电网上(用 PLL 小带宽慢慢收敛,避免频率突变造成负载扰动),等 Δθ 稳定在阈值内再合。前者是打靶,后者是并轨。
九、下垂:为什么离网时的频率读数不是 50.00
离网运行时,逆变器是电压源,频率由它自己定。但为了多机并联时能自动分配负载,通常要加下垂控制(droop):
f = f0 × (1 - droop_p × P/P_rated)
U = U0 × (1 - droop_q × Q/Q_rated)
按 5% 的 P-f 下垂、5kW 机器:
| 负载率 | 有功 | 频率 | 电压 |
|---|---|---|---|
| 0% | 0 W | 50.00 Hz | 230.0 V |
| 25% | 1250 W | 49.38 Hz | 228.6 V |
| 50% | 2500 W | 48.75 Hz | 227.1 V |
| 75% | 3750 W | 48.12 Hz | 225.7 V |
| 100% | 5000 W | 47.50 Hz | 224.2 V |
“离网频率 50.00Hz”这个读数只在空载成立。 带 70% 负载读到 48.2Hz 是设计如此,不是故障。
这里有三个连锁问题:
第一,EMS 用并网口径的频率窗口去判离网机,必然误报。 并网侧常见的频率保护窗口是 49.5~50.5Hz(甚至更窄),拿它去判一台带载 70% 的离网机,直接报“频率异常”。我在三个现场见过这个误报,其中两个的操作员因此以为机器坏了,手动关机。
正确做法:状态字是 ISLAND 时,EMS 切换到离网口径(频率 47.5~50.5Hz,且要看负载率),并且这条规则要写进 EMS 的点表配置里,不能靠现场改代码。
第二,下垂会导致回切时同期失败(第 8.1 节算过:1.9Hz 的 Δf,窗口 15ms)。
解决办法是在回切前先卸下垂:进入 SYNCING 之前,把下垂系数临时置 0,让频率回到 50.00Hz,再拉 PLL。这个动作要快(几百毫秒),且期间负载会有轻微扰动,但比合闸失败好得多。
第三,多机并联时,下垂系数是负载分配的唯一依据。 两台机器下垂系数不一致,就会出现“一台满载、一台空转”。这个不是切换问题,但会在切换后暴露出来——因为切换前是并网,功率由电网决定;切换后才看下垂。
十、电池侧:切换瞬间那 102.8A
前面全是交流侧。但离网切换对电池是一次实打实的冲击。
并网时,负载由电网供电,电池可能在充电(电流为负)。切换瞬间,负载全部转由电池承担,电流从负数跳到正数,跳变量是两者的和。
算一下最基本的:
I_bat = P_load / (η × U_bat)
5kW 负载、51.2V 电池(16S LFP 标称)、95% 效率:
I = 5000 / (0.95 × 51.2) = 102.8 A
如果 BMS 的放电限流 DCL 是 100A——不够,差 2.8%。
| 负载 | 电池侧电流 | 对 100A DCL 的占比 |
|---|---|---|
| 2000 W | 41.1 A | 41.1% |
| 3000 W | 61.7 A | 61.7% |
| 5000 W | 102.8 A | 102.8% |
| 6000 W | 123.4 A | 123.4% |
| 8000 W | 164.5 A | 164.5% |
| 10000 W | 205.6 A | 205.6% |
5kW 机器配 100A DCL 的电池,余量只有 -2.8%。 这在户储里极其常见——电池按“5kW 持续”标称,但那个 5kW 是在 25℃、SOC 50%、短时口径下测的;到了冬天、SOC 低、又要连续放电,实际能力打折。
现场表现就是:并网好好的,一停电就跳。 因为并网时电池不出力,一切正常;离网时电池要出全部功率,BMS 判定过流,限流甚至关断,母线塌,负载二次掉电。
更麻烦的是 di/dt。假设切换前电池在充电 -30A,切换后要放电 +102.8A,变化量 132.8A。BMS 的过流判据通常是“1.2~1.5 倍 DCL 持续 100ms”,这个跳变会直接触发它。
所以切换逻辑里必须加一条预判:
/* 切换前先算这笔账,算不过就不许切 */
bool island_feasible(float p_load_est)
{
float i_need = p_load_est / (0.95f * bat_voltage());
float i_avail = bms_dcl_get(); /* 从 BMS 实时读,不是铭牌 */
float margin = i_avail - i_need;
if (margin < 0.10f * i_avail) { /* 留 10% 余量 */
return false; /* 不切,或先降载 */
}
return true;
}
算不过怎么办?三个选项,按优先级:
- 降载:切换前先把非关键负载切掉(如果有负载管理),或限制放电功率;
- 切但报警:接受短暂的限流,把“离网负载超出电池能力”作为事件上报;
- 不切:这在储能里几乎不可接受(离网的意义就是保供电),所以实际上只有前两条。
另外,bms_dcl_get() 必须读实时值而不是铭牌值。BMS 会根据温度、SOC、SOH 动态调整 DCL——冬天 SOC 20% 时的 DCL 可能只有铭牌的一半。这个值走 CAN 还是 Modbus 取决于 BMS,上一轮那篇 储能 BMS 与 PCS 的 CAN 对接实战 里讲的 0x351 报文,第三个字段(字节 4~5)就是放电电流限值 DCL,单位 0.1A(其中字节 2~3 是充电限流 CCL,别对错位置)。
十一、多机并联:选主黑洞
单机切换的账算清楚了,多机还有一层。
并联运行的离网系统需要一个“主机”来建立电压参考,其他机器跟随。并网运行时大家都不用建网(电网在),所以主机身份是“闲置”的;一旦转离网,必须有人立刻接管建网。
如果主机正好是那台检测到故障、正在重启的机器,其他机器就要等它——或者重新选主。
选主的过程通常是:检测到电压参考消失 → 等待 T_wait(100~500ms)→ 按优先级/地址竞选 → 胜出者建压 → 其他机器锁相。
这个 T_wait 就是第二段断电。 单机 118ms 的切换,在两机并联的系统里可能变成 118 + 300 = 418ms。
改善办法:
- 静态指定主机(优先级写死),不做动态竞选。简单可靠,代价是主机故障时整个系统瘫。
- 主机心跳 + 硬线:主机通过一根硬线(或 CAN 心跳)持续广播“我在”,从机检测到心跳丢失立刻接管,延迟可以做到 20ms 级。代价是多一根线。
- 预选主:并网时就让备用主机同步跟踪主机的相位,随时可接管。这是最好的方案,也是实现最复杂的。
现场调试时,如果发现“单机测试切换 100ms,两机并联变成 400ms”,八成就是选主在吃时间。
十二、固件侧:状态机与三个必须掉电保持的量
12.1 状态机
┌──────────┐
│ STANDBY │
└────┬─────┘
│ 上电自检通过 + 电网合格
↓
┌──────────┐ 电网越限 + 去抖通过
│ GRID_TIED├──────────────┐
└────┬─────┘ │
│ ↓
│ ┌───────────────┐
│ │ TRANSFERRING │
│ └───────┬───────┘
│ │ 断开完成 + 建压完成
│ ↓
│ ┌──────────┐
│ │ ISLAND │
│ └────┬─────┘
│ │ 电网恢复 + T_confirm
│ ↓
│ ┌──────────┐
│ │ SYNCING │
│ └────┬─────┘
│ │ Δθ 收敛
└─────────────────┘
注意 TRANSFERRING 是双向共用的(回切时也经过它),所以状态字 bit2 的含义是“正在切换”,不分方向,方向由 上次切换原因 和前后状态推断。
12.2 三个必须掉电保持的量
- 切换事件计数(U16)——EMS 靠它发现“发生过切换”。丢了会让 EMS 漏事件。
- 最大切换耗时(U16,ms)——验收证据。丢了等于放弃了自证能力。
- T_confirm 等可配参数——现场调过的值,掉电回默认会导致行为变化。
写入策略沿用上一轮那篇 逆变器 Modbus 写控制与参数掉电保持实战 的结论:A/B 双区 + seq + CRC 原子提交,且只在值真正变化时写。切换计数每次切换都变,一天切 5 次的话一年 1800 次,EEPROM 寿命完全够;但别在切换过程中写(那时候电源最不稳),要在切换结束后延时 5s 再写。
12.3 保护任务的时间预算
1ms 任务:采样、过流/过压硬件比较器轮询、PLL 更新
5ms 任务:有效值计算(滑窗)、状态机 tick
20ms 任务:Modbus 从站、LED、风扇
100ms 任务:通信心跳、温度
状态机必须在 5ms 任务里跑,不能在 20ms 任务里。 20ms 的周期意味着最坏情况多 20ms 的判定延迟——这在 118ms 的总账里占 17%,白送的。这条约束和 LIN 总线主从调度与报文超时实战 里那条“时隙必须大于帧最大时长 + 抖动”是同一条纪律:周期一旦小于你要处理的事件时长,事件就会被截断。
十三、上位机侧:轮询黑洞、撕裂、重对齐
13.1 三个不要
不要用上位机做切换决策。 前面算过:500ms 轮询 + 118ms 执行 = 最坏 638ms。用秒级的环控百毫秒级的事,控不住。
不要用“电网电压是否为 0”判断模式。 电压为 0 可能是采样故障、可能是通信中断(读到 0 而不是超时)、可能是真的停电。必须用状态字。
不要在切换后沿用切换前的指令。 这是最隐蔽的一条——切换前 EMS 下发的是“并网充电 40kW”,切换后 PCS 变成离网电压源,这个指令要么被拒绝(正确),要么被错误地映射成“放电 40kW”(灾难)。
13.2 切换后的“重对齐”流程
1. 轮询发现 切换事件计数 变化
2. 读 上次切换原因 / 上次切换耗时 / 当前模式
3. 若当前模式 = ISLAND:
a. 停止下发所有并网类指令(有功/无功/PF)
b. 若有离网类指令(输出电压/频率/下垂),重新下发当前期望值
c. 若配置了负载管理,按优先级切除非关键负载
d. 上报事件(原因、耗时、当前 SOC、当前负载率)
4. 若当前模式 = GRID_TIED(回切完成):
a. 并网类指令从 0 开始斜坡恢复,不要直接跳到切换前的值
b. 校验电池是否已从“放电”转回“充电/待机”
c. 清除离网期间的告警(但保留记录)
5. 记录本次事件的完整快照到日志
第 4a 条特别重要。回切后直接把功率指令恢复到切换前的值,会造成一次功率阶跃——电网侧看到的是几百毫秒内功率从 0 跳到 40kW,可能触发上级的功率变化率限制(很多园区的需量控制或电网的爬坡率要求)。
正确做法是斜坡:0 → 目标值,用时 5~30s,斜率可配。这也顺带解决了另一个问题:切换后电池 SOC 可能已经变化,原来的功率指令不再合适。
13.3 点表配置
{
"version": "2026-10-17",
"device": "PCS-5K",
"status": {
"reg": 256,
"bits": {
"grid_tied": 0,
"island": 1,
"transferring": 2,
"fault": 3,
"island_lockout": 4,
"standby": 5,
"local": 6,
"derating": 7
}
},
"event": {
"counter": 258,
"last_reason": 259,
"last_duration_ms": 260,
"max_duration_ms": 261
},
"write_guard": {
"grid_only": [512, 513, 514],
"island_only": [520, 521, 522],
"never_during_transfer": [512, 513, 514, 520, 521, 528, 529],
"reject_code_transfer": 6,
"reject_code_mode": 4
},
"ramp": {
"grid_power_recover_s": 10,
"grid_power_step_kw": 4
}
}
write_guard 这段是给 EMS 用的声明式联锁:不是靠代码里 if-else 硬编码,而是配置驱动。换一台 PCS 只改配置,不改代码。
十四、现场五招
第一招:先量“断电缺口”,别信标称值。
负载端接一个示波器(差分探头,或者一台带录波的万用表),触发条件设“电压跌到 200V 以下”,抓一次真实停电。量两个时间点:电压开始跌、电压恢复到 90%。这个差值就是你要的答案。没有示波器就用一个带事件记录的 USB 电压记录仪(几百块),采样率 1kHz 就够。
第二招:问清起止点,问不出就按 100ms 估。
“你们这个切换时间,起点是电网电压跌到多少伏,终点是负载端电压恢复到多少伏?”答不上来的,按 100ms 设计下游。
第三招:读“最大切换耗时”寄存器,跑一个月。
如果固件提供了这个寄存器(7.4 节),装完跑一个月读一次。这是唯一能长期自证的数据。没提供的话,建议加上——对厂家是自证,对用户是验收。
第四招:切换后立刻读“上次切换原因”。
原因码能立刻区分“电网真没了”和“我的判据太敏感”。前者要查电网,后者要调参数。没有原因码的机器,只能靠猜。
第五招:并联系统单独测“单机”和“双机”。
单机 118ms、双机 418ms,说明选主在吃时间。先解决选主,再谈切换指标。
十五、联锁自查清单
装机和联调时逐条过,一条不落。
时间账
- ☐ 量过真实断电缺口(示波器/记录仪),不是抄标称值
- ☐ 标称值的起止点已向厂家确认(起:电网跌到多少伏;终:负载恢复到多少伏)
- ☐ 检测窗长度、去抖次数、建压时长在固件里可查(不是黑盒)
- ☐ 满足不等式:T_detect + T_debounce < 最娇气负载的 hold-up
- ☐ 最娇气负载的 hold-up 已向负载厂家确认(问不到按 20ms 估)
状态与协议
- ☐ 状态字区分 GRID_TIED / ISLAND / TRANSFERRING,且三者互斥
- ☐ TRANSFERRING 期间 bit0、bit1 都为 0
- ☐ 有切换事件计数器(U16,模回绕)
- ☐ 有上次切换原因、上次切换耗时、最大切换耗时
- ☐ 最大切换耗时掉电保持
- ☐ 切换中拒绝写并网类寄存器,回 0x06
- ☐ 模式不允许时回 0x04,不是 0x02
- ☐ EMS 日志记录原始异常码,不只记“失败”
- ☐ 状态字与功率类量在同一帧读,且有跨状态一致性校验
回切
- ☐ 电网恢复确认时间 T_confirm 可配、掉电保持,默认 ≥5s
- ☐ T_confirm 期间任何一次越限都归零重来
- ☐ 同期三条件(Δθ / Δf / ΔU)都参与判断,不是只看相位
- ☐ 合闸窗口按 Δf 算过,执行链(测量+继电器+断路器)时长 < 窗口
- ☐ 回切前卸下垂,把频率拉回 50.00Hz 再进 SYNCING
- ☐ SYNCING 有超时退出(3s 未收敛回 ISLAND 并记录)
电池与并联
- ☐ 切换前按 P/(η×U) 算电池侧电流,与实时 DCL 比对
- ☐ DCL 从 BMS 实时读,不是铭牌值
- ☐ 留 10% 电流余量
- ☐ 并联系统测过单机/双机切换时间差,定位过选主延迟
小结
回到开头那个问题:为什么标称 10ms,负载还是每次都重启?
因为 10ms 是开关的动作时间,不是负载的断电时间。 后者是 118ms,中间隔着检测、去抖、建压,以及 13ms 我说不清但确实存在的软件残差。
三个可以直接拿走的结论:
一、切换时间的账必须自己算,且要算到每一层。
7.2 + 40 + 8 + 50 + 13 = 118.2ms,占大头的不是开关(8ms,6.8%),是去抖(40ms,33.8%)和建压(50ms,42.3%)。想优化,先别动开关,动这两项。
二、无缝的本质是相位连续,不是快。
记住 θ0、让振荡器从 θ0 继续积分、电压环复位但相位不复位——这三件事做到了,T3 就从 50ms 变成 0。做不到,换再快的开关也只能做到 60ms 级。
三、上位机的位置是“知情 + 联锁 + 重对齐”,不是“控制”。
88% 的切换它看不见(118ms vs 1s 轮询),5.56% 的读取会撕裂(9600 波特率、20 寄存器),回切后直接恢复旧指令会造成功率阶跃。这三件事都有解,但解法都不在“加快轮询”上——在事件计数器、一致性校验、斜坡恢复上。
最后一个我想留在这儿的观察:
这个行业里最贵的成本,不是器件选错了,是口径没说清。
“切换时间”没说起止点,“SOC”没说是哪个 SOC,“效率”没说是满载还是峰值,“寿命”没说是循环还是日历。
每一次口径模糊,都会在半年后变成一个现场故障。
下一篇写离网备电时长。一块 10kWh 的电池,铭牌写“备电 4 小时”,实际 1 小时 40 分就关机——中间少掉的那 2 小时 20 分,藏在放电深度、效率曲线、待机自耗、温度降额和“负载不是恒定功率”这五件事里。正好接在这篇后面:这篇讲“切换那 118ms”,那篇讲“切换之后能撑多久”。SOC 这一层的底子在 电池管理与 SOC 估算实战 里讲过,那篇只讲“读数准不准”,新篇讲“能放多少”。
本文的所有时间、电流、能量数字均由随文脚本 grid_transfer_calc.py 计算并可复现(--self-test 40 项全过)。其中标准档位类数值(并网电压异常响应区间)为工程常见口径,落地前请以项目适用的并网导则原文为准;器件动作时间为典型范围,具体以所选型号数据手册为准。