做并网逆变器的朋友大概都碰到过这样的场景:样机在实验室跑得好好的,一到并网前自检环节就偶发报“继电器故障”,复位一下又能并网;或者更吓人的是,停机后测量输出端,发现继电器明明给了断开信号,输出侧却依然有电压。前者多半是检测窗口和去抖参数没调好,后者则直接指向触点粘连。并网逆变器继电器自检逻辑与状态机实现,本质上就是要把“继电器到底断没断、闭没闭”这件事,用一套可验证、可复现、可追溯的软件流程管起来,而触点粘连检测和安全保护则是这套流程里最不能妥协的底线。
这套内容适合三类人看:一是刚接手并网逆变器控制软件的新人,需要把自检流程从“能跑”做到“敢交付”;二是负责硬件检测电路设计的工程师,想知道软件侧到底需要什么样的采样窗口和信号质量;三是做储能变流器、UPS、并离网切换装置的同行,继电器自检的底层逻辑是相通的。下面我按“失效模式—硬件拓扑—状态机—代码—参数—测试—保护”的顺序,把我在实际项目里踩过的坑和验证过的做法拆开讲。
1. 先把失效模式想清楚:继电器自检到底在防什么
1.1 并网继电器在系统里的位置与职责
并网逆变器的功率回路里,继电器通常放在逆变桥输出与电网接入点之间,常见配置是火线、零线各一只,或者火线侧两只串联做冗余。它的第一职责不是“控制通断”,而是“保证该断开的时候一定断开”。正常并网时继电器闭合,功率馈入电网;待机、故障、停机、孤岛保护动作时,继电器必须迅速断开,把逆变器与电网做物理隔离。这个隔离动作关系到检修人员安全和设备反送电风险,所以安规里对继电器的状态监测、粘连检测、断开确认都有明确要求,主流并网安规基本都要求并网前确认继电器处于可控的断开状态,不允许“闭着眼睛合闸”。
很多新人容易把继电器当成一个普通开关,写代码时给个GPIO电平就完事。实际在安规视角下,继电器是一个“可能失效的元件”:它可能该断不断,也可能该合不合,还可能触点抖动导致拉弧。软件自检要做的,是在每次并网前、并网后、断开后这几个关键节点,用可观测的电气量去反推触点状态,而不是相信驱动信号。驱动信号只能说明“我让它断了”,检测电路才能说明“它确实断了”。
1.2 触点粘连的三种典型成因
触点粘连不是单一原因造成的,我在项目里整理过三类高发场景。第一类是通断瞬间的浪涌电流。逆变器输出侧有滤波电容,继电器闭合瞬间相当于电容充电,触点之间会产生很大的浪涌电流,长期反复动作后触点表面容易熔焊。第二类是断开时的拉弧。如果继电器在较大电流下断开,触点间会拉弧,电弧能量集中在触点表面,冷却后就可能粘在一起。第三类是机械与材料老化。触点弹簧疲劳、表面氧化、异物进入,都会让触点该断开时无法分离。这三类成因对应的检测策略不完全一样:浪涌导致的粘连往往发生在闭合后不久,拉弧导致的粘连多发生在断开瞬间,老化导致的粘连则可能是渐进式的,需要运行中周期性监测。
所以自检逻辑不能只做“并网前测一次”,而要覆盖“闭合前确认断开、闭合后确认闭合、断开后确认断开”三个窗口。每个窗口的检测条件、阈值、判定时间都不同,这也是后面状态机要拆成多个状态的原因。把三个窗口混成一个“继电器检测”函数,是很多误报和漏报的根源。
1.3 自检要覆盖的三个时间窗口
第一个窗口是并网前。此时继电器应处于断开状态,电网侧有电压,逆变器侧应该没有电网电压。如果检测到逆变器侧存在与电网同频同相的电压,说明至少有一只继电器触点粘连,或者检测电路本身出了问题。这个窗口是最关键的,因为此时合闸会把粘连的继电器直接并入电网,失去隔离能力。
第二个窗口是闭合过程中。如果是双继电器串联结构,可以先闭合一只,通过中点采样判断另一只是否粘连;再闭合第二只。这个窗口能定位到具体是哪一只继电器出了问题,对维修和故障统计很有价值。
第三个窗口是断开后。逆变器停机或故障脱网后,继电器给出断开指令,等待动作时间后再检测逆变器侧电压。如果此时电网有电而逆变器侧仍有电压,说明触点没有断开。这个窗口的难点在于电网可能停电,停电时两侧都没有电压,检测就失去了参考。所以软件要明确:电网无电时不做粘连判定,但也不允许在电网恢复后未经检测就直接并网。这个逻辑后面会展开。
2. 硬件检测拓扑:怎么用低成本电路把触点状态“看”出来
2.1 分压采样式粘连检测电路的基本结构
最常见的做法是在继电器两侧加一组高阻值分压电阻,把电网侧电压引入逆变器侧的检测点,再送ADC采样。继电器断开时,如果触点正常,检测点应该接近零;如果触点粘连,电网电压会通过粘连触点传到检测点,ADC就能读到明显的工频信号。这个电路的优点是成本低、原理直观,缺点是检测精度受电阻误差、ADC基准漂移、共模干扰影响,而且检测电路本身失效时可能给出错误的安全信号。
我在实际设计里会在检测点对地加一个小容量滤波电容,配合软件做有效值计算或峰值判断。电阻取值要兼顾功耗和抗干扰:阻值太大,ADC输入阻抗和漏电流会影响精度;阻值太小,并网后检测电路会持续消耗功率,还可能影响安规耐压。一般按“电网峰值电压下分压到ADC量程的70%左右”来选,留出过压裕量。这个计算后面第5节会给具体过程。
2.2 双继电器中点检测的冗余思路
如果成本允许,我更推荐双继电器串联加中点采样的结构。火线侧两只继电器K1、K2串联,K1靠电网侧,K2靠逆变器侧,中点引出检测点。这样可以通过不同的开关组合分别判断两只继电器的状态:K1闭合、K2断开时,中点应该有电网电压;K1断开、K2闭合时,中点应该有逆变器侧电压。如果某一步的采样结果不符合预期,就能定位到具体是哪一只继电器粘连或开路。
这个结构的价值在于“单点检测无法区分是继电器粘连还是检测电路故障”。单继电器方案里,检测到异常电压时,你只能报“继电器或检测电路故障”,现场排查要靠万用表。双继电器中点检测能给出更细的故障码,比如“K1粘连”“K2未闭合”“中点采样异常”,维修效率完全不一样。当然,多一只继电器、多一路采样,成本和板面积都会增加,适合对安全等级要求高的机型。
2.3 驱动侧辅助判断:线圈电流与辅助触点
除了触点侧电压检测,驱动侧也能提供辅助信息。比如在继电器线圈回路里串一个小采样电阻,测线圈电流:如果驱动信号给了,电流却明显偏小,可能是线圈开路;电流偏大,可能是线圈短路或驱动管异常。这个信息不能替代触点检测,但能在故障时帮助区分“是继电器没动作”还是“触点粘住了”。
有些继电器带辅助触点,可以直接反馈机械状态。辅助触点的优点是直接反映衔铁位置,不受电网电压影响;缺点是多两根线、多一个接触点,辅助触点本身也会失效。我的做法是:以触点侧电压检测为主,线圈电流检测为辅,辅助触点作为可选项。软件上不要把所有信号做“与”逻辑,否则任何一个信号异常都会导致无法并网,误报率会很高;更合理的是分级判断,主检测信号异常直接锁故障,辅助信号异常只做告警和故障记录。
3. 状态机设计:把自检拆成可验证的小步骤
3.1 状态定义与迁移条件
继电器自检不适合写成一个线性函数,因为中间有等待、有超时、有故障分支。用状态机描述最清楚。我通常定义这几个状态:初始化、自检准备、断开确认、预充电、K1闭合确认、K2闭合确认、并网运行、断开执行、断开后确认、故障锁存。每个状态只做一件事,进入状态时启动超时计时,条件满足就迁移,条件不满足且超时就进故障锁存。
迁移条件要写得“可观测”,不能写“继电器已闭合”这种模糊描述,而要写“K1驱动输出高电平且中点电压有效值大于阈值且持续N个采样周期”。这样代码评审时能逐条对照,测试时也能构造用例。状态机的每个迁移最好记录一个故障码或事件码,方便后面追溯。比如从“断开确认”直接跳到“故障锁存”,事件码记录“并网前检测到逆变器侧电压异常”,现场读故障码就能知道是粘连还是采样问题。
3.2 关键时序参数怎么定
时序参数是状态机最容易拍脑袋的地方。继电器动作时间通常有几毫秒到十几毫秒的分散性,触点抖动可能持续几毫秒,检测电路滤波又需要时间。我的经验是:给出断开或闭合指令后,先等一个“动作等待时间”,再开始采样判定;判定窗口要覆盖至少两个工频周期,避免采到过零点附近导致误判;去抖计数按采样周期累加,连续多次超阈值才认定故障。
举个例子:继电器标称动作时间10ms,考虑低温、老化、驱动电压跌落,动作等待时间取50ms比较稳妥;判定窗口取40ms,覆盖两个20ms工频周期;ADC采样如果每1ms一次,40次采样里连续30次超过阈值才判定,这样既能滤掉窄脉冲干扰,又不会把真实的粘连漏掉。这些参数不是拍脑袋,后面第5节会结合具体电路给计算过程。
3.3 超时、去抖与故障锁存
超时机制是状态机的“保险丝”。每个状态都要有最大停留时间,比如“K1闭合确认”状态,如果等了500ms还没满足条件,不管是因为继电器没动作还是采样异常,都直接进故障锁存。没有超时,状态机可能卡在某个状态里,继电器保持一个危险的中继状态,这比直接报故障更可怕。
故障锁存要区分“可恢复”和“不可恢复”。触点粘连属于不可自动恢复故障,必须锁死,不允许软件自动重试合闸。我的做法是:粘连故障写入非易失存储,记录故障码和发生次数,只有收到维护复位指令或整机断电重启后才清除。而且重启后第一次并网前必须重新做断开确认,不能因为“上次复位了”就跳过自检。这个逻辑听起来简单,但实际项目里真有人为了“提升用户体验”加了自动重试,结果把粘连的继电器反复合闸,这是绝对不能接受的。
4. 代码实现:一个可落地的继电器自检状态机
4.1 数据结构与接口设计
代码层面,我倾向于把状态机的“数据”和“动作”分开。数据部分包括当前状态、状态计时、去抖计数、采样缓存、故障码;动作部分包括继电器驱动、ADC读取、有效值计算、故障上报。这样状态机主体只负责迁移判断,硬件相关操作通过接口函数调用,方便在台架上做故障注入。
下面是一个简化的结构定义,实际项目里会根据平台调整:
typedef enum { RLYS_INIT = 0, RLYS_SELF_TEST, RLYS_OPEN_CHECK, RLYS_PRECHARGE, RLYS_K1_CLOSE_CHECK, RLYS_K2_CLOSE_CHECK, RLYS_GRID_ON, RLYS_OPEN_EXEC, RLYS_POST_CHECK, RLYS_FAULT_LATCH } relay_state_t; typedef struct { relay_state_t state; uint32_t state_tick_ms; uint32_t state_timeout_ms; uint16_t debounce_cnt; uint16_t debounce_target; uint16_t fault_code; float grid_vrms; float inv_vrms; float mid_vrms; } relay_sm_t;接口函数至少要有:继电器驱动输出、电网侧电压有效值读取、逆变器侧电压有效值读取、中点电压有效值读取、故障锁存写入、故障上报。有效值计算建议放在单独的采样任务里,状态机只读结果,避免在状态机里做浮点运算导致周期抖动。
4.2 核心状态迁移代码
状态迁移用switch-case写最直观,每个case里先判断故障条件,再判断成功条件,最后判断超时。注意顺序:故障判断要放在成功判断之前,否则可能因为采样偶然满足成功条件而漏掉故障。下面给出“断开确认”和“K1闭合确认”两个关键状态的骨架:
static void relay_sm_run(relay_sm_t *sm, uint32_t elapsed_ms) { sm->state_tick_ms += elapsed_ms; switch (sm->state) { case RLYS_OPEN_CHECK: /* 并网前:继电器应断开,逆变器侧不应有电网电压 */ if (sm->inv_vrms > INV_VRMS_THRESHOLD) { sm->debounce_cnt++; if (sm->debounce_cnt >= sm->debounce_target) { relay_fault_latch(sm, FAULT_RELAY_STUCK_CLOSED); sm->state = RLYS_FAULT_LATCH; } } else { sm->debounce_cnt = 0; } if (sm->state_tick_ms >= RELAY_ACTION_WAIT_MS && sm->debounce_cnt == 0 && sm->grid_vrms > GRID_VRMS_MIN) { relay_drive_k1(1); relay_sm_enter(sm, RLYS_K1_CLOSE_CHECK, K1_CLOSE_TIMEOUT_MS); } if (sm->state_tick_ms > sm->state_timeout_ms) { relay_fault_latch(sm, FAULT_OPEN_CHECK_TIMEOUT); sm->state = RLYS_FAULT_LATCH; } break; case RLYS_K1_CLOSE_CHECK: /* K1闭合后,中点应检测到电网电压 */ if (sm->mid_vrms > MID_VRMS_THRESHOLD) { sm->debounce_cnt++; if (sm->debounce_cnt >= sm->debounce_target) { relay_drive_k2(1); relay_sm_enter(sm, RLYS_K2_CLOSE_CHECK, K2_CLOSE_TIMEOUT_MS); } } else { sm->debounce_cnt = 0; } if (sm->state_tick_ms > sm->state_timeout_ms) { relay_drive_k1(0); relay_fault_latch(sm, FAULT_K1_NOT_CLOSED); sm->state = RLYS_FAULT_LATCH; } break; } }这段代码里有两个细节值得注意。第一,进入“断开确认”后不是立刻判断,而是等RELAY_ACTION_WAIT_MS之后再结合去抖计数判断,避免把继电器动作过程中的暂态当成粘连。第二,超时分支里要先把继电器驱动回安全状态,再锁故障,顺序反了可能让继电器停在中间状态。
4.3 采样与阈值判断
采样侧我建议用“有效值+峰值”双重判断。有效值对工频信号稳定,但对窄脉冲不敏感;峰值能捕捉瞬间异常,但容易被干扰误触发。实际做法是:有效值超过阈值且峰值也超过对应阈值,才认为有电压存在。有效值计算用滑动窗口,窗口长度取两个工频周期,窗口内平方和累加,开方可以每秒算一次,不需要每个采样点都开方。
阈值不能写死一个绝对值,要跟电网电压额定值挂钩。比如额定220V系统,并网前逆变器侧检测到大于30V有效值就认为异常,这个30V要结合分压比和ADC量程换算成ADC码值。同时要设置一个“电网有电”的门槛,比如电网侧有效值大于80V才认为电网正常,低于这个值不做粘连判定。这样做的原因是:电网停电时,即使继电器粘连,逆变器侧也没有电压,检测没有意义;此时应该禁止并网,等电网恢复后重新做断开确认。
5. 参数计算与电路取值:别让检测窗口变成盲区
5.1 分压电阻与ADC量程的换算
假设电网额定220V有效值,峰值约311V;ADC参考电压3.3V,12位分辨率;检测电路从电网侧经两个电阻分压到ADC输入。为了让311V峰值对应ADC量程的70%左右,分压比约为311/2.31≈134.6。取上电阻1MΩ、下电阻7.5kΩ,分压比约134.3,311V峰值对应2.316V,ADC码值约2874,留有余量。这个取值下,检测电路在220V时的功耗约0.048W,可以接受。
阈值换算:如果判定“逆变器侧有电压”的阈值取30V有效值,对应峰值42.4V,分压后约0.316V,ADC码值约392。软件里直接比较码值即可,但要注意ADC基准的温漂和电阻精度。1%电阻带来的分压比误差约2%,对应阈值误差不到1V,影响不大;ADC基准如果用的是内部基准,温漂可能到几十ppm/℃,夏天高温时码值会偏移,所以阈值要留至少20%裕量。
5.2 RC滤波与采样窗口的配合
检测点对地加滤波电容,和分压电阻构成低通滤波。假设下电阻7.5kΩ,滤波电容100nF,时间常数约0.75ms,截止频率约212Hz,对50Hz信号衰减很小,对高频干扰有抑制作用。这个参数下,采样窗口取40ms(两个工频周期)足够让滤波后的信号稳定。如果电容取大了,比如1μF,时间常数7.5ms,截止频率21Hz,虽然抗干扰更好,但继电器动作后的暂态会更长,检测等待时间也要相应加长,否则容易误判。
我一般按“滤波时间常数不超过判定窗口的1/10”来选。判定窗口40ms,时间常数控制在4ms以内比较合适。这样既能让信号在窗口内稳定,又不会把真实的电压变化拖得太慢。继电器的机械抖动通常在几毫秒内,滤波电容还能起到一定的硬件去抖作用。
5.3 继电器动作时间与检测延时的匹配
继电器动作时间受线圈电压、环境温度、机械磨损影响,标称值只是典型值。我在低温箱里测过同一批继电器,-20℃时动作时间比常温慢了近一倍。所以RELAY_ACTION_WAIT_MS不能按标称值取,要按最坏情况再留裕量。比如标称10ms,低温实测18ms,等待时间取50ms就比较稳。检测延时也不是越长越好,太长会影响并网速度,用户会感觉逆变器启动慢;50ms等待加40ms判定,总共90ms,对用户体验影响很小。
还有一个容易被忽略的点:继电器断开时的释放时间通常比吸合时间短,但触点弹跳可能更明显。断开后检测的等待时间可以比闭合等待时间稍短,但判定窗口不能缩短,因为断开后如果粘连,电压是持续存在的,窗口短了反而容易漏判。我的做法是断开等待30ms、判定窗口40ms,闭合等待50ms、判定窗口40ms,参数分开配置,不共用一个宏。
6. 测试与排查:把误报和漏报压下去
6.1 台架测试怎么搭
继电器自检的测试不能只靠整机并网跑,要能单独构造故障。我通常搭一个简易台架:可调交流源模拟电网,继电器模块单独引出,逆变器侧用电阻负载或小功率电源模拟残余电压,中点检测点引到示波器和ADC板。测试用例至少覆盖:正常断开、正常闭合、K1粘连、K2粘连、双继电器粘连、检测电路开路、检测电路短路、电网停电、电网电压跌落、继电器动作缓慢。
粘连模拟可以用两种方法:一是机械方式,把继电器触点人为短接;二是电气方式,在继电器两端并联一个小电阻,模拟“没有完全断开”的漏电状态。后者更接近真实粘连前的渐进失效,能用来验证阈值和去抖参数是否合理。我建议两种都做,机械短接验证逻辑正确性,并联电阻验证检测灵敏度。
6.2 常见问题速查表
| 现象 | 可能原因 | 排查方向 | 处理建议 |
|---|---|---|---|
| 并网前偶发报粘连 | 判定窗口太短,采到过零点附近 | 看ADC原始波形和有效值窗口 | 判定窗口加到两个工频周期以上 |
| 继电器闭合后报未闭合 | 等待时间不够,继电器还没动作完 | 示波器抓驱动信号和中点电压 | 加长动作等待时间,低温复测 |
| 断开后检测不到粘连 | 电网停电或电网电压过低 | 查电网侧有效值 | 电网无电时不做判定,禁止并网 |
| 检测电路故障误报 | 分压电阻开路或ADC基准异常 | 测检测点静态电压 | 增加检测电路自检,读值超范围报故障 |
| 粘连故障复位后仍能并网 | 故障未写入非易失存储 | 查故障锁存逻辑 | 粘连故障必须持久化,维护复位才清除 |
| 双继电器机型定位不到具体故障 | 只做了单点采样 | 查中点采样电路 | 增加中点检测,分状态定位 |
这张表里的每一行我都至少遇到过一次。最隐蔽的是“检测电路故障误报”:分压电阻虚焊,ADC读值一直在阈值附近跳,软件偶尔判定粘连,偶尔又正常。后来加了一条“检测点静态电压必须在合理范围内,否则直接报检测电路故障”,才把这类问题压下去。
6.3 现场调试的几个坑
第一个坑是共模干扰。逆变器功率回路开关动作会在检测线上感应共模电压,导致ADC读值偏高。解决方法是检测线走差分、靠近ADC端加共模电感或小电容,软件上再用有效值窗口滤掉高频分量。第二个坑是继电器驱动电压跌落。辅助电源负载重时,继电器线圈电压可能从12V掉到9V,动作时间变长,原本50ms的等待时间不够了。调试时要测最恶劣负载下的线圈电压,按最低电压对应的动作时间留裕量。
第三个坑是并网后检测电路承受高压。有些设计在并网后没有断开检测回路,检测电阻长期承受电网电压,时间长了电阻老化、阻值漂移,阈值就偏了。我的做法是并网后用软件定期读一次检测点,跟历史值比较,偏差超过一定范围就报“检测电路漂移”告警,提醒维护。这个功能不复杂,但能提前发现渐进失效,比等到粘连误报再排查主动得多。
7. 安全保护与故障恢复策略
7.1 故障分级与响应动作
继电器相关故障不能一律“锁死等维护”,也不能一律“自动重试”,要分级。第一级是危险故障,包括并网前检测到粘连、断开后检测到粘连、双继电器同时异常。这类故障必须立即锁存,禁止自动重合闸,上报故障码,等待维护复位。第二级是可恢复故障,比如单次动作超时但复测正常、检测电路瞬时异常。这类可以允许有限次重试,比如连续三次自检失败才锁存,但重试之间要有足够的间隔,不能高频动作继电器。
第三级是告警,比如检测电路漂移、线圈电流偏大但触点状态正常。这类不影响并网,但要记录并上报,提醒现场检查。分级的意义在于:把“必须停机的”和“可以带病运行的”分开,既保证安全,又避免过度保护导致客户投诉。实际项目里,过度保护带来的售后成本有时比故障本身还高。
7.2 粘连故障的锁定与恢复
粘连故障的恢复策略要写进软件需求文档,不能由开发随手定。我的做法是:故障发生时立即断开所有继电器驱动,锁存故障码到非易失存储,点亮故障指示,停止并网流程。恢复条件有两个:一是收到维护复位指令,二是整机断电重启后完成一次完整的断开确认自检。两个条件都满足才允许重新并网。维护复位指令可以来自按键长按、上位机命令或调试接口,但不能是普通用户能轻易触发的,避免现场随意复位。
还有一个细节:故障锁存后,即使检测信号恢复正常,也不允许自动清除。因为粘连可能是间歇性的,信号暂时正常不代表触点可靠。软件上要把故障锁存和实时检测分开,锁存标志只能由复位流程清除,实时检测只负责置位。这个逻辑在代码评审时要重点检查,很多误自动恢复都是因为把“检测正常”直接当成了“故障清除”。
7.3 故障码设计与上位机上报
故障码不要只用一个“继电器故障”笼统上报,要能区分故障类型和发生阶段。我一般按“阶段+类型”编码:阶段包括并网前、闭合中、运行中、断开后;类型包括粘连、未闭合、超时、检测电路异常。这样现场读到一个码,就能大致判断是硬件问题还是软件参数问题。比如“并网前+粘连”大概率是触点真的粘了,“闭合中+超时”可能是驱动电路或继电器线圈问题,“运行中+检测电路异常”则要查采样通道。
上报周期也要注意。粘连故障是瞬态事件,如果只在故障发生时上报一次,上位机可能没收到。我的做法是故障锁存后周期上报,直到复位清除;同时记录故障发生的时间戳和当时的电压采样值,方便事后分析。这些数据不需要很高精度,但要有,否则现场只能靠猜。
最后分享一个我在实际项目里踩过的坑:早期版本为了赶进度,把继电器自检和并网流程写在一个大函数里,自检失败直接return,继电器驱动状态没有统一回收,结果出现“自检报错但继电器还吸着”的危险状态。后来改成状态机加统一的安全出口,任何故障分支都先走“断开所有继电器驱动”再锁故障,这类问题才彻底消失。继电器自检这件事,逻辑不难,难的是把所有异常分支都想到,并且让每个分支都落到一个安全状态上。