ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

储能 EMS 的功率策略:从一块电表到一次逆流的 200 毫秒

储能 EMS 的功率策略:从一块电表到一次逆流的 200 毫秒 写在前面我是薛定谔的悦储能领域的工程师。最近系统里最让我有表达欲的不是那些花哨的协议栈而是一套看起来特别土的东西——几块电表、一个信号量、两个预保护值拼出来的防逆流和防过载。这套逻辑代码不多核心就分布在ccu_sampler和kwhmeter_sampler两个模块里加起来可能不到两千行。但它是整个储能系统并网安全的底线充电的时候不能把变压器顶爆放电的时候不能把电倒灌回电网。这篇文章我把这套逻辑从头理一遍。大纲为什么要盯着一块电表两个模块之间的门铃System V 信号量快速通道从轮询到事件驱动防逆流别把电倒回电网防过载别把变压器顶爆两个公式一个思想工程里真正让人头疼的坑写在最后1. 为什么要盯着一块电表一个工商业储能的典型拓扑是这样的变压器出来接一路电网侧关口表Grid Meter然后往下分两条支路一条接普通负载一条接储能机柜。储能机柜自己还挂着一块柜内表Cab Meter。为什么要装两块表因为 EMS 需要实时知道负载到底吃了多少电而这个值没法直接测只能靠两块表做减法负载功率 网侧表功率 - 柜内表功率网侧表测的是变压器进线的总功率柜内表测的是储能这条支路的功率。两个一减剩下的就是纯负载消耗。这个减法公式是整个功率控制的地基。充电和放电的跟随负载全都是在这行减法之上算出来的。我第一次读这块代码的时候其实没觉得有什么。不就是两块表嘛。直到后来在一次现场联调里客户现场只装了柜内表、没装网侧表结果防逆流直接不干活了。我才意识到这套逻辑对硬件是有前提的。这一点放到第七节细说。2. 两个模块之间的门铃System V 信号量电表的数据是谁采的kwhmeter_sampler一个跑在 RS485 上的采样器。防逆流、防过载的策略是谁算的ccu_sampler另一个采样器。问题来了这两个是独立的模块各自一个线程在跑。电表采样器发现逆流了怎么第一时间告诉 CCU 采样器答案是一个 System V 信号量。代码里就一行pCCU-nEssSemCCUWith485MeterCreat_SemID(SEM_CCU_485_PATH,SEM_CCU_485_PROJ_ID,1,IPC_CREAT|0666);展开来Creat_SemID就干两件事intCreat_SemID(constchar*pathname,intproj_id,intnsems,intsemflg){key_tkeyftok(pathname,proj_id);// 用路径字符生成一个 IPC keyintsemidsemget(key,nsems,semflg);// 用 key 拿到信号量returnsemid;}其中SEM_CCU_485_PATH是/varSEM_CCU_485_PROJ_ID是字符S。关键在于ftok只要两个进程用同一个路径加同一个字符算出来的 key 就一定相同于是它们拿到的是内核里同一个信号量。我第一次接触这个脑子里冒出来的问题是这跟普通的锁有啥区别区别在于作用域。pthread 的互斥锁只能在一个进程内用而 System V 信号量是内核对象跨进程。CCU 采样器和电表采样器是两个.so各自独立普通锁管不到彼此只有这种内核级的东西能当桥梁。然后就是两个操作一个等、一个发voidsem_wait_op(intsemid)// P 操作减一减到 0 以下就阻塞{structsembufsop{0,-1,0};semop(semid,sop,1);}voidsem_post_op(intsemid)// V 操作加一唤醒等待者{structsembufsop{0,1,0};semop(semid,sop,1);}我把它理解成一个门铃。电表采样器那边按一下sem_post_opCCU 采样器这边一直睡在sem_wait_op上门铃一响就被内核叫醒。这里有个容易误解的点信号量不传数据。它只传有事情发生了这一个信号。真正的功率值、电压电流是通过 DPR 数据模型共享的。信号量只是负责敲门。ftok这个函数也值得说两句。它是拿一个文件路径加一个字符算出一个整数 key。这里有个隐蔽的坑路径必须是真实存在的文件不存在就会返回 -1然后semget拿到的是个非法值信号量静默失效。项目里用的是/var一个稳定的系统目录而不是某个可能被清理的临时文件。另外路径加字符这个组合只要两个模块各写错一个字符就会各拿到各的信号量谁也等不到谁——这种 bug 不报错就是永远不触发比崩溃还难查。还有一点我一开始没想明白为什么不用 POSIX 命名信号量非要用 System V 这套ftok semget semop的老古董。后来看代码发现这个项目里 System V 信号量已经在 syshand 守护进程和主进程之间用着了是一套既有的 IPC 约定。新模块沿用同一套比再引入一种信号量风格要省心。工程里很多为什么这么写的答案其实是因为这里已经这么写了。3. 快速通道从轮询到事件驱动为什么要费劲搞一个信号量直接让 CCU 采样器定时去读电表数据不行吗行但是慢而且浪费 CPU。定时轮询的问题在于响应延迟取决于轮询周期。周期设短了CPU 忙等周期设长了逆流都发生几百毫秒了 CCU 还没反应过来。而逆流这件事等不起。电网那边对逆流的容忍度是按一次调频级别来要求的端到端要压到 200 毫秒以内。信号量是阻塞的。CCU 采样器的线程平时就睡在sem_wait_op上不占 CPU。电表那边一sem_post_op内核立刻把它唤醒微秒级的延迟。这就是快速通道的由来代码里对应一个bQuickChannle标志。整条端到端的时序我拆开来算过。电表采样器那边RS485 采集一轮网侧表功率大概几十毫秒发现两个功率都为负sem_post一下微秒级CCU 采样器被唤醒跑一遍能量管理策略算出发电功率上限再通过 CAN 或 Modbus 把指令下给 PCS又是几十毫秒。全部加起来从逆流发生到PCS 开始降功率控制在 200 毫秒以内。这 200 毫秒就是电网给储能定的硬指标也是快速响应这个说法的来源。这里面的关键是信号量那一段几乎不耗时。如果换成轮询哪怕周期压到 100 毫秒光是等下一轮就要吃掉一半的预算。事件驱动和轮询的差距在这种硬实时场景里会被放大得特别明显。触发条件在kwhmeter_sampler的Judge_ActvPower_Reflux里if((fCabPwr_Sum0)(fGridPwr_Sum0)){sem_post_op(pWhim_Rough-nSemCCUWith485Meter);// 发信号唤醒 CCU}柜内功率和网侧功率同时为负说明功率在往电网倒灌这时候按下门铃。CCU 采样器被唤醒后先检查一个前置条件if(pCCU-emPMCtrlMode!AUTO_CTRL_PM_MODE)// 非自动模式直接跳过continue;必须是自动功率管理模式才会响应。手动模式下即使逆流了系统也不该自作主张去调 PCS。这是安全上的考虑——操作员在手动调试的时候最怕设备自己乱动。4. 防逆流别把电倒回电网放电的时候储能给负载供电。理想情况是负载吃多少储能放多少。但负载是实时波动的万一负载突然掉下去储能还在按原来的功率放多出来的电就会倒灌回电网。这就是逆流。判断逻辑在JudgeIfEnterAnti_RefluxOrOverload里BOOL bAntiReflux(fAnti_RefluxMeter-pCCU-blobBackendCfg.fPreProt4RefluxAndOverLoad(pEMS-fAllowedReverseValue*-1.0f));拆开看三个量fAnti_RefluxMeter防逆流表的实时有功功率就是网侧表功率fPreProt4RefluxAndOverLoad放电预保护值一个提前量fAllowedReverseValue逆流允许值允许倒灌多少翻译成人话当网侧功率负到「低于逆流允许值 预保护值」的时候判定进入防逆流。为什么要有预保护值注释里写得很直白Pre-Sett Value, Due To EMS Adjust Power is Too Slow——EMS 调节功率太慢了所以要提前动手。等真正越界了再去调传输和计算延迟早就让逆流多倒灌了几十毫秒。而fAllowedReverseValue是给奥地利认证这种场景留的。有些电网允许一点点逆流这个值可以配置成 3KW、5KW 之类。真正算放电功率上限的地方在Calc_ESS_DischgLmtPowerpEMS-fTotal_PessDischgLmt(pEMS-fPacload4Dischg-fProt)*pCCU-blobBackendCfg.fOutput2AC_PwrRate;pEMS-fTotal_PessDischgLmtpEMS-fAllowedReverseValue;这里fPacload4Dischg就是前面说的负载功率网侧表减柜内表fProt是放电预保护值fOutput2AC_PwrRate是放电跟随百分比。最后再加回逆流允许值。举个例子。负载 60KW放电预保护值 5KW跟随百分比 0.9逆流允许值 3KW放电上限 (60 - 5) × 0.9 3 52.5 KW储能最多放 52.5KW负载吃 60KW中间还差 7.5KW 的余量不会倒灌。上面说的是三相总量的防逆流。还有一个单相的场景是奥地利认证项目带出来的。欧标对单相逆流管得比国内严光看三相总量不够得看每一相。代码里fAtRflxMtr_kWPhMin就是三相功率里的最小值单相防逆流的触发条件是if(pEMS-fAtRflxMtr_kWPhMinpCCU-blobBackendCfg.fPreProt4Reflux_Phase)pEMS-bEnterAnti_Reflux_PhaseTRUE;三相里只要有一相的功率负到超过单相预保护值就触发。这个场景的坑在于三相负载往往是不平衡的某一相可能已经逆流了三相总量还是正的。如果只做总量防逆流单相逆流根本抓不住。奥地利认证要求三相总量 单相最小值双通道同时检测缺一不可。5. 防过载别把变压器顶爆充电的时候问题反过来了。储能充电相当于一个大负载充电功率加上原本的负载功率不能超过变压器的容量。超了变压器过载跳闸整条线路都断。判断逻辑同样在那个函数里BOOL bAntiOverLoad((pCCU-blobBackendCfg.fSysACInput_PwrLmt-fAnti_OverLoadMeter)pCCU-blobBackendCfg.fPreProt_Chrg);fSysACInput_PwrLmt就是变压器可用功率注释直接写着变压器可用功率(防过载)。fAnti_OverLoadMeter是网侧表功率fPreProt_Chrg是充电预保护值。翻译变压器功率减去网侧表功率剩下的可用功率如果已经小于充电预保护值了就判定进入防过载。充电功率上限的完整计算在Calc_ESS_ChgLmtPower// 剩余充电功率 变压器功率 - 负载功率pEMS-fRemainPwr4ChrgpEMS-fGridACInputPwr_Lmt-pEMS-fPacload4Chg;// 再减预保护值乘充电跟随百分比pEMS-fGridRemainACPwr2ChrgMAX(0.0f,(pEMS-fGridRemainACPwr2Chrg-fProt)*pCCU-blobBackendCfg.PowerRate_Chrg);举个例子变压器 100KW负载 40KW充电预保护值 5KW跟随百分比 0.9剩余 100 - 40 60 充电上限 MAX(0, (60 - 5) × 0.9) 49.5 KW 充电 49.5 负载 40 89.5离 100 还有 10.5 的余量这套逻辑就是跟随负载的核心。充电功率不是写死的而是每一轮都根据当前的负载重新算负载大就少充点负载小就多充点始终贴着变压器容量走又留着一层安全垫。6. 两个公式一个思想把充电和放电摆在一起看会发现它们是一对镜像充电防过载充电功率 (变压器功率 - 负载功率 - 充电预保护值) × 充电跟随百分比放电防逆流放电功率 (负载功率 - 放电预保护值) × 放电跟随百分比 逆流允许值一个减的是变压器剩余容量一个减的是负载需求。方向相反思想一样——都是在理论上能用的功率里先扣掉一个预保护值当缓冲再乘一个跟随百分比让实际功率始终比理论极限小那么一截。预保护值这个东西我理解成给系统留的反应时间。EMS 从采样到计算到下发 PCS 指令是有一个延迟的。如果贴着极限跑这个延迟窗口里负载稍微动一下就越界了。预保护值就是把这个延迟窗口换算成功率提前预留出来。两个预保护值当初是共用的一个参数后来拆开了代码里能看到LJH250703的改动注释因为充电和放电需要留的余量不一样混用一个值两边都别扭。7. 工程里真正让人头疼的坑代码逻辑看懂了是一回事现场跑起来又是另一回事。第一个坑电表没装齐。前面说了负载功率 网侧表 - 柜内表。这是硬前提。代码里Judge_ActvPower_Reflux开头就判断if((meterData[CAB_FIRST].emMeterTypeMETER_TYPE_NotInstalled)||(meterData[GRID_FIRST].emMeterTypeMETER_TYPE_NotInstalled))return0;// 直接不处理注释写得很清楚站级 EMS 快速防逆流必须接柜内表和网侧表。但现场总有各种情况——有的站从设备只装了柜内表没装网侧表有的干脆把表接错了位置。这种时候防逆流是静默失效的不报错只是永远不触发。最怕的就是这种安静的错误。第二个坑电表安装位置决定负载怎么算。负载功率不是固定用网侧表减柜内表这一种算法。代码里Calc_ACLoadPwrByDiffMeterPosition按电表位置分了五六种场景工业用电、民用电、多负载支路每种减法的表不一样。配置里emAuxMeterPos参数 1,7设错了负载算出来就是错的后面全错。第三个坑迟滞防振荡。逆流和过载的进入、退出都不是瞬时的中间隔了一个MAX_RETDIFF_PERIOD时间窗。为什么因为功率在阈值附近抖动的时候如果进出没有迟滞PCS 会疯狂地一会儿开一会儿关功率振荡。这段代码的注释我记得特别清楚防逆流退出要超过时间窗就是怕抖。第四个坑预保护值怎么调。预保护值设大了系统太保守功率上不去白白浪费容量设小了余量不够负载一抖就越界。这个值没有银弹只能现场一点一点试。我自己的经验是先设成变压器容量的 5% 左右然后看功率曲线有振荡就加大有余量就减小。第五个坑手动模式和自动模式的边界。被信号量唤醒后第一步就是检查emPMCtrlMode是不是自动模式。这个检查不能省。现场调试的时候操作员会切到手动模式去单独控制某一台 PCS如果这时候自动策略还在跑两边指令冲突后果比逆流严重得多。除了这五个坑我还想记一个真实的联调事故。有一次客户现场的防逆流怎么都触发不了日志里明明能看到网侧表功率已经负了但 PCS 就是不动。排查了一整天最后发现是电表安装位置那个参数配错了。客户现场是多负载支路的接法负载功率应该用网侧表 - 柜内表 辅助表来算但配置里还停在默认的无辅助表算出来的负载比真实值小了一截导致逆流判断的阈值整个偏移了。表接对了位置参数配错了等于白接。那次之后我养成一个习惯现场上电之前先把这几块表谁是谁在配置里对一遍拿一个已知负载去验证减法公式对不对。表读数的正负号、安装位置、参数 ID任何一个对不上后面所有策略都是空中楼阁。功率控制这种东西地基错了楼越高塌得越狠。关于这几个参数到底藏在哪也顺带说清楚。变压器可用功率fSysACInput_PwrLmt、充电预保护值fPreProt_Chrg、放电预保护值fPreProt4RefluxAndOverLoad、充电跟随百分比PowerRate_Chrg、放电跟随百分比fOutput2AC_PwrRate这些全在同一个后端配置 blobA_BLOB_ES_BACKEND_CFG里参数 ID 是nDevId1, nParamId26。也就是说你在 Web 配置里改一个后端配置的 blob这几个字段一起生效。变压器功率这个值代码里还留了三种来源可以直接用手填的fSysACInput_PwrLmt也可以设成固定值模式读fFixed_GridInput_Limit或者动态模式读管理计划里每个时段的nGridInputPwrLmt。如果现场是分时电价、不同时段变压器额度不同就用动态模式如果变压器容量恒定直接禁用动态限制、手填一个值最省事。我自己的习惯是变压器功率填实际容量的值预保护值先设成容量的 5%跟随百分比设 0.9然后上电看功率曲线再微调。这三个值没有一个正确答案只有适合这个现场的答案。8. 写在最后这套防逆流、防过载的逻辑代码层面没有太多花哨的东西。两块表做减法算负载一个信号量当门铃两个预保护值留余量再加一个迟滞窗防振荡。全部加起来就是储能系统并网安全的底线。但它让我重新理解了什么叫实时。实时不是 CPU 跑得快而是事件一发生系统能在规定时间内响应。信号量那套阻塞等待 内核唤醒就是为此设计的——平时零开销地睡着关键时刻微秒级地醒来。也让我理解了跟随的含义。跟随负载不是把公式写对就完了而是要接受负载永远在变这个事实然后用预保护值去对冲不确定性用迟滞窗去对抗抖动。工程和理论的区别大概就在这些留一手的细节里。最后说一句有点感慨的话。储能这个行业安全从来不是一句口号。防逆流和防过载背后是电网对每一个并网点的硬约束。我们写的每一行减法、每一个信号量、每一个预保护值最终都是为了让系统在无人值守的凌晨三点负载突变的瞬间还能稳稳地待在自己该待的位置上。
返回列表