EMS 知识库
首页 / knowledge/代码解析/logic

self_test.c

源文件路径:logic/self_test.c 所属模块:logic(能量调度核心 —— 电网/PV 电表接线自检) 代码行数:369

1. 文件职责

本文件实现 EMS 的「安装接线自检」功能:由 logic 任务在 g_t_logic_info.u8_sequencecontrol_mode == SELF_TEST_CHARGE_MODE 时每个控制周期调用一次 system_self_test()(调用点 logic/logic.c:2306),驱动一个八态状态机(待机 → 充电 → 放电,每阶段各含"静置"与"采样"两个子态)。每个静置态持续 10 秒让功率稳定,每个采样态以 500 ms 间隔连续采集电网电表、PV 电表、逆变器三相功率各 5 次,用 EWMA(EWMA_ALPHA = 0.5)求出每相均值,存入 g_t_self_test。全部三阶段数据齐备后,system_self_test_result() 比较「充电/放电时电网电表功率是否按逆变器功率反向变化」来判定电网电表断线、接线错位,以及 PV 电表接错相,结果写入 g_t_logic_info.u_self_test_result 位域;失败时最多自动重测 2 次,随后清除自检使能并置位 SELF_TEST_RESULT 信息位通知网络层上报。它处于数据流的末端判定环节:上游是被强制设定的 g_t_logic_info.t_logic_control_parameter 充电/放电限值与 bat_force_charge()/bat_force_discharge(),下游是云端上报报文。

2. 关键数据结构与宏

本文件不定义任何新类型,全部复用 logic/self_test.h 与 logic/logic.h 的定义。

名称类型说明
SELF_RUN_TABLE_T / g_t_self_test结构体 / 全局变量自检运行表实例,定义于本文件 :25(= {0}),承载状态机状态与三阶段采样/均值缓存
SELF_STATE枚举状态机状态:SELF_STATE_START/STANDING/STANDING_RECORD/CHARGE/CHARGE_RECORD/DISCHARGE/DISCHARGE_RECORD/END
SAMPLE_COUNT宏5,每阶段采样次数,即 *_power_samples 第二维长度
EWMA_ALPHA宏0.5,EWMA 系数,作为 alpha 传入 calculate_dynamic_compensation()
SELF_TEST_RESULT_U / u_self_test_result联合体(位域)定义于 logic/logic.h:466-492,位于 g_t_logic_info 内。位定义:bit0-2 = u8_grid_meter_l1/l2/l3_loss(电表断线)、bit3-5 = u8_grid_meter_l1/l2/l3_location_err(电表接反)、bit6-8 = u8_pv_meter_l1/l2/l3_location_err(PV 表位置错)、bit9 = u8_other_reason、bit10-15 保留
SELF_TEST_RESULT宏(枚举成员)net/ems.h:265,值 0x10000,ENInfoType 的一个信息位
SAMPLE_COUNT = 5 对应数组 s32_*_samples[4][5] / [3][5]—电网侧 4 通道(A/B/C/总),PV 与逆变器侧 3 通道
PHASE_A / PHASE_B / PHASE_C / PHASE_T / PHASE_ALL宏电表/逆变器读取接口的相选择参数
GRID_METER / PV_METER宏电表实例选择参数
METER_SAMPLING枚举midware/meter/meter.h:184,meter_get_meter_or_ct() 的返回值之一,表示该路是 CT 采样而非真实电表
TRUE / FALSE宏布尔常量,来自 includes.h

3. 函数清单

函数名作用域说明
calculate_dynamic_compensation全局(无原型声明)对 count 个采样做系数 alpha 的 EWMA 迭代,返回均值(截断为 s32)
system_self_test_data全局(无原型声明)按 500 ms 间隔采样 5 次并回填三相均值到传入的 offset 数组;采满返回 TRUE
system_self_test_result全局(无原型声明)依据三阶段均值判定电表断线/接反/PV 位置错,写 u_self_test_result 并返回判定结论
system_self_test全局(原型在 logic/logic.h:699)自检状态机主入口,每控制周期调用一次,推进八态流转

共 4 个函数,全部为全局作用域;本文件没有任何 static 函数,也没有函数指针、回调或 static inline。

4. 核心逻辑解析

4.1 状态机与迁移条件

system_self_test()(:240)用 g_t_self_test.self_state_flag 做 switch(:247),两个 static 变量 s_u8_self_test_countr(:242)与 s_s32_self_test_tick(:243)保存重试次数与阶段计时基准。迁移图如下:

当前状态动作迁移条件目标状态
SELF_STATE_STARTmemset(&g_t_self_test,0,...) 整表清零;u_self_test_result.u16_all = 0x0000;记录 s_s32_self_test_tick无条件SELF_STATE_STANDING
SELF_STATE_STANDING清零充/放功率限值、清 u8_ac_total_set_bat_forced_charge_en;#ifdef INV_DC_CTRL 下再清 s32_pgrid_metert/s32_pinvt/s32_max_feedin_power 并 bat_force_charge();#ifdef INV_AC_CTRL 下清 s32_ac_total_set_inv_powerabs(OSTimeGet()-tick) > 10*1000(10 秒静置)SELF_STATE_STANDING_RECORD
SELF_STATE_STANDING_RECORD#ifdef INV_AC_CTRL 下再次清零限值与 AC 设定功率;调 system_self_test_data(offset1...)system_self_test_data() 返回 TRUE(采满 5 点)SELF_STATE_CHARGE
SELF_STATE_CHARGEs32_pgrid_metert = -s32_pinvt;s32_max_feedin_power = 0;bat_force_charge()静置 10 秒SELF_STATE_CHARGE_RECORD
SELF_STATE_CHARGE_RECORD调 system_self_test_data(offset2...)返回 TRUESELF_STATE_DISCHARGE
SELF_STATE_DISCHARGEs32_pgrid_metert = 0、s32_pinvt = 0、充电限值 = 0、s32_bat_dischg_power_limit = parallel_read_system_max_dischg_power_limit()、s32_max_feedin_power = s32_inv_rated_output_power;bat_force_discharge()静置 10 秒SELF_STATE_DISCHARGE_RECORD
SELF_STATE_DISCHARGE_RECORD调 system_self_test_data(offset3...)返回 TRUESELF_STATE_END
SELF_STATE_END若 system_self_test_result() != TRUE 且 s_u8_self_test_countr < 2 则重试计数 +1;否则清零计数、u8_self_test_enable = FALSE、set_info_status(SELF_TEST_RESULT)无条件(同一周期内)SELF_STATE_START
default空操作——

关键语义:SELF_STATE_END 会把状态直接写回 START,而 START 又会 memset 整表。因此只要 system_self_test_result() 返回 FALSE,就会立即从零重跑整套三阶段流程,最多重跑 2 次(共 3 轮)。TRUE 的含义是"本次判定结论明确、无需重试",而不是"接线正确"——u_other_reason 单独置位时也返回 TRUE(见 4.4)。

三个静置态(STANDING/CHARGE/DISCHARGE)都带有一处时钟回绕保护:else if(s_s32_self_test_tick > OSTimeGet()) 时把基准对齐到当前 tick(:277、:308、:337)。

4.2 采样算法:system_self_test_data()(:54)

static u8  s_u8_record_countr = 0;      // 阶段内已采点数
static s32 s_s32_self_test_tick = 0;    // 上次采点时刻
if (s_u8_record_countr < SAMPLE_COUNT) {
    if (abs(OSTimeGet() - s_s32_self_test_tick) > 500) {   // 500 ms 间隔
        grid_power_samples[0..3][countr] = meter_read_actpower(GRID_METER, PHASE_A/B/C/T);
        pv_power_samples[0..2][countr]   = meter_read_actpower(PV_METER,   PHASE_A/B/C);
        inv_power_samples[0..2][countr]  = g_t_logic_info.t_logic_control_parameter.s32_pinva/b/c;
        tick = OSTimeGet();  countr++;
    } else if (tick > OSTimeGet()) tick = OSTimeGet();   // 回绕保护
} else {
    ppv_meter_offset[0..2]  = calculate_dynamic_compensation(pv_power_samples[i],   SAMPLE_COUNT, EWMA_ALPHA);
    pgrid_meter_offset[0..3]= calculate_dynamic_compensation(grid_power_samples[i], SAMPLE_COUNT, EWMA_ALPHA);
    pinv_offset[0..2]       = calculate_dynamic_compensation(inv_power_samples[i],  SAMPLE_COUNT, EWMA_ALPHA);
    s_u8_record_countr = 0;
    s_s32_self_test_tick = OSTimeGet();
    return TRUE;
}
return FALSE;

要点:

EWMA 公式(calculate_dynamic_compensation,:37):

ewma = samples[0];
for (i = 1; i < count; i++)  ewma = alpha * samples[i] + (1 - alpha) * ewma;
return ewma;   // float 隐式截断为 s32

alpha = 0.5 时,5 个点的权重依次为 0.0625 / 0.0625 / 0.125 / 0.25 / 0.5,即越新的点权重越高(第 i 点与第 i-1 点权重相同,因为 samples[0] 与 samples[1] 都只被"半衰"一次)。返回时直接把 float 赋给 s32 返回值,未做四舍五入,负数向零截断。

4.3 判定算法:system_self_test_result()(:107)

先判系统相数(:109):inverter_get_phase_number() == 3 || meter_get_meter_or_ct(GRID_METER) != METER_SAMPLING 走三相分支,否则走单相分支。

三相分支,对 L1/L2/L3(i = 0/1/2)各自独立判定:

条件判定写入位
pinv_offset2[i] < -100(充电时逆变器功率为负)且 abs(pgrid_offset2[i] - pgrid_offset1[i]) < 100(电网表读数几乎不变)且 pinv_offset3[i] > 100(放电)且 abs(pgrid_offset3[i] - pgrid_offset1[i]) < 100充放电时电网表都不动 → 电表断线u8_grid_meter_l{1,2,3}_loss = 1
abs(pgrid_offset2[i] - (pgrid_offset1[i] - pinv_offset2[i])) > 300 或 abs(pgrid_offset3[i] - (pgrid_offset1[i] - pinv_offset3[i])) > 300电网表变化量与逆变器功率不符 → 电表接反/位置错u8_grid_meter_l{1,2,3}_location_err = 1
其余无故障三个位清 0(含 u8_other_reason = 0)

随后(:177)若两个充放阶段的三相逆变器功率都接近 0(abs(pinv_offset2[k]) < 100 三相全满足,或 pinv_offset3[k] 三相全满足),说明"三相均未充或均未放",直接 u16_all = 0x200(即 u8_other_reason = 1)。

单相分支(:184):

条件判定写入
abs(pinv_offset2[0]) < 100 或 abs(pinv_offset3[0]) < 100未充或未放u16_all = 0x200(other_reason)
pinv_offset2[0] < -100 且三路电网表读数与 offset1 差值都 < 100,且 pinv_offset3[0] > 100 且三路差值都 < 100充放电时电网表都不动u16_all = 0x007(L1/L2/L3 loss 全置位)→ 电表断线
abs(pgrid_offset2[3] - (pgrid_offset1[3] - pinv_offset2[0])) > 300 或 abs(pgrid_offset3[3] - (pgrid_offset1[3] - pinv_offset3[0])) > 300总功率变化量与逆变器功率不符u16_all = 0x038(L1/L2/L3 location_err 全置位)→ 电表接反
其余无故障u16_all = 0x000

单相分支用总功率通道 [3] 做主判据,而三相分支用各相通道 [i],这是两分支的核心差异。

最后 PV 位置检查(:211-222),与相数无关,任一路充电/放电阶段的 PV 均值绝对值 > 200 就置位:

返回值(:224):

if ((u16_all != FALSE) && (u8_other_reason != TRUE))  return FALSE;   // 有明确故障 → 重测
else                                                  return TRUE;    // 无故障,或仅 other_reason

4.4 与 uCOS-II 任务/事件标志的配合

本文件不创建任务、不挂起/恢复任务、不使用信号量或消息邮箱,全部计时靠 OSTimeGet()(轮询式)。ucOS-II 侧依赖只有两点:

  1. OS_TICKS_PER_SEC = 1000(UCOSII/uCOS-CONFIG/os_cfg.h:51),故所有 OSTimeGet() 差值的单位是毫秒,500 即 500 ms,10*1000 即 10 s。
  2. 调用者 logic 任务(logic/logic.c:2912 用 OSFlagPend(g_t_fast_dispatch_flag, POST_PEND_FAST_ENABLE, ..., 20, &err) 做 20 tick 周期等待)以约 20 ms 的节拍轮询 system_self_test()。因此一个静置态的 10 秒约等于 500 个控制周期,采样态的 5 个点需要 5 个周期命中 500 ms 门槛。

自检使能与结果的下行/上行通路(共 5 段,中间经过 u8_self_test_charge 转接):

  1. 云端下行:net/server.c:2132-2141 的 handle_server_sys_self_test_command() 从 Parameter1 解析出 0/1 写入 g_t_logic_info.u8_self_test_enable。
  2. 并机模块加工(含 150 秒看门狗):midware/inverter/parallel.c:1758-1788 的 parallel_read_system_self_test_flag()——
    • 离网时强制 u8_self_test_enable = FALSE(:1762-1765);
    • 该位变化时切换 inverter_set_p_v_mode_down_load_flag() / inverter_set_p_f_mode_down_load_flag()(取反)并把 u16_mppt_percentage 设为 enable ? 0 : 10000(:1767-1776);
    • abs(OSTimeGet() - s_s32_self_test_tick) >= 150*1000(150 秒)时无条件清 u8_self_test_enable(:1778-1780)——这是整个自检流程的硬性时间上限;
    • 返回该位。
  3. 转接与模式判定:logic/logic.c:1144 把返回值存进 u8_self_test_charge;logic/logic.c:2201-2204 判断 u8_self_test_charge != FALSE 时置 u8_sequencecontrol_mode = SELF_TEST_CHARGE_MODE。
  4. 调用本文件:logic/logic.c:2305-2306 的 case SELF_TEST_CHARGE_MODE: 调 system_self_test()。
  5. 结束上报:本文件 :359-360 清 u8_self_test_enable 并 set_info_status(SELF_TEST_RESULT)(net/ems.c:2722 定义),由 net/ems.c:2608、net/ems.c:4951 的 get_info_status() & SELF_TEST_RESULT 触发上报。

时间预算估算:单轮自检约需 10+2(待机静置+采样)+ 10+2(充电)+ 10+2(放电)≈ 36-40 秒;SELF_STATE_END 失败时最多重跑 2 次,三轮约 110 秒,仍落在 parallel.c 的 150 秒看门狗之内,但余量不大。

4.5 关键全局变量的读写

变量读写方向位置
g_t_self_test(本文件定义)全程读写,状态机与缓存:25 定义;:62-73、:82-91、:112-221、:247-362
g_t_logic_info.t_logic_control_parameter.s32_bat_charge_power_limit / s32_bat_dischg_power_limit写(自检期间强制覆盖):257-258、:285-286、:325-326
g_t_logic_info.t_logic_control_parameter.s32_pgrid_metert / s32_pinvt / s32_max_feedin_power写:261-263、:298-299、:323-324、:327
g_t_logic_info.t_logic_control_parameter.s32_inv_rated_output_power / s32_inv_rated_input_power写(每次调用都从并机模块刷新):244-245
g_t_logic_info.s32_ac_total_set_inv_power写(#ifdef INV_AC_CTRL):267、:288
g_t_logic_info.u8_ac_total_set_bat_forced_charge_en写 0:259、:287
g_t_logic_info.u_self_test_result写(位域):115-221
g_t_logic_info.u8_self_test_enable写 FALSE(结束自检):359
bat_force_charge() / bat_force_discharge()调用:264、:300、:328

4.6 编码与协议

本文件不实现任何通信协议,不涉及帧格式、校验或超时重传;唯一对外"协议"是 set_info_status(SELF_TEST_RESULT) 这一信息位(0x10000)与云端上报的布尔约定。

5. 对外接口

导出(供外部使用):

名称类型使用方
system_self_test全局函数logic/logic.c:2306(case SELF_TEST_CHARGE_MODE:),原型在 logic/logic.h:699
g_t_self_test全局变量仅本文件使用;extern 声明在 logic/self_test.h:50
calculate_dynamic_compensation全局函数仅本文件内 10 处调用;无原型声明
system_self_test_data全局函数仅本文件内 3 处调用;无原型声明
system_self_test_result全局函数仅本文件内 1 处调用;无原型声明

被调用(本文件引用的外部符号):

符号来源模块
OSTimeGetUCOSII/uCOS-II
meter_read_actpower、meter_get_meter_or_ct、GRID_METER、PV_METER、PHASE_A/B/C/T、METER_SAMPLINGmidware/meter/meter.h、midware/meter/ct.h
inverter_get_phase_numbermidware/inverter/inverter.h:1966
parallel_read_system_rated_output_power、parallel_read_system_rated_input_power、parallel_read_system_max_dischg_power_limitmidware/inverter/parallel.h:272/302 等
bat_force_charge、bat_force_dischargelogic/logic.h:653-654,实现在 logic/logic.c:1761/1782
set_info_status、SELF_TEST_RESULTnet/ems.h:513、net/ems.h:265
g_t_logic_infologic/logic.h:646
abs、memsetC 运行库 / includes.h
TRUE、FALSEincludes.h

6. 依赖关系

依赖(self_test.c:13-20):inverter.h、ct.h、logic.h、server.h、meter.h、bms.h、self_test.h、ems.h。

被依赖:

7. 备注与风险点

  1. u16_all = 0x200 会清掉同一轮里刚置位的故障位::177-181(三相)与 :185-188(单相)直接整体赋值 u16_all,这会覆盖 :112-175 刚写入的 u8_grid_meter_l*_loss / _location_err 位。即"电表断线且逆变器同时未充放"时,断线结论被 other_reason 吞掉。单相分支的 0x007 / 0x038 / 0x000 赋值同理,因为它们是 else if 链,不会与本轮前面置位冲突——只有三相分支的 :177 是无条件覆盖。
  2. PV 位置错误位只置位、从不清零::211-222 的三个 if 只在条件成立时写 1,没有 else 分支写 0。u16_all = 0x0000 只在 SELF_STATE_START(:252)执行一次,因此 PV 位一旦置位会一直保持到下一轮 SELF_STATE_START。若某次 system_self_test_result() 因 FALSE 触发重测,重测期间 PV 位仍为 1(START 会清),但在同一轮内多次调用 system_self_test_result()(例如 END 之后又回到 END)不会清零。
  3. system_self_test_result() 的返回值语义与函数名不符:注释写"检查电网电表接线",实际返回 TRUE 表示"结论明确"(含 other_reason 这种"根本没充放电、测不出来"的情况),返回 FALSE 才触发重测。按 END 分支 :352 的用法可以反推,但仅看函数名极易误读。建议改名或补注释。
  4. s_s32_self_test_tick 用 s32 承载 OSTimeGet() 的 u32::57、:243 两处(system_self_test_data 与本函数各一个)。abs(OSTimeGet() - (u32)s_s32_self_test_tick) 在 tick 超过 2^31 ms(约 24.9 天)后会符号翻转,此后 abs() 的比较行为不可预期。工程里带有 else if(tick > OSTimeGet()) 的回绕保护,但那只处理 tick 回绕,不处理这个有符号截断。
  5. system_self_test_data() 的 static 计数与状态机的 memset 不同步:SELF_STATE_START 的 memset(&g_t_self_test, 0, sizeof(g_t_self_test))(:250)不会清 s_u8_record_countr / s_s32_self_test_tick(它们是函数内 static)。若在采样态被外部打断后重新开始,采样计数会从上次的中间值继续,可能出现"少采几个点就回填均值"的情况。建议把计数纳入 SELF_RUN_TABLE_T。
  6. 大量魔法数字:500(采样间隔 ms)、10*1000(静置时长 ms)、100(充放电功率有效阈值 W)、300(功率变化一致性阈值 W)、200(PV 有值阈值 W)、0x007/0x038/0x200(u16_all 直写值)、2(重试上限)。这些阈值分散在代码里,没有宏定义或注释说明标定依据(尤其 100 与 300 两个不同量级的判据基于什么功率基准,代码里看不出来)。0x007/0x038/0x200 直接按位域布局硬编码,一旦 SELF_TEST_RESULT_U 的位序调整就会静默失配。
  7. #ifdef INV_DC_CTRL / #ifdef INV_AC_CTRL 在同一状态机里混合:SELF_STATE_STANDING(:256-268)与 SELF_STATE_STANDING_RECORD(:283-289)都按编译开关做不同清理,但 SELF_STATE_CHARGE/DISCHARGE 完全走 DC 侧字段(s32_pgrid_metert、s32_max_feedin_power)而没有 AC 分支。若目标机型只定义 INV_AC_CTRL,SELF_STATE_CHARGE 的 s32_pgrid_metert = -s32_pinvt 写的是未被使用的字段,自检可能无效。
  8. calculate_dynamic_compensation 用 float 参与中值计算::39-44 混合 float 与 s32,在 GD32F407(Cortex-M4F)上会引入软/硬浮点差异;返回值直接截断不四舍五入。同时 alpha 无范围校验,count 无上界校验(依赖调用方恒传 SAMPLE_COUNT)。
  9. DEBUG_HMI 未被使用:logic/self_test.h:15 定义的调试开关在本文件里没有任何 #if DEBUG_HMI,属版本遗留。
  10. 文件头与函数头注释复制错误::2 的 @file 与函数头注释把 system_self_test_data 的名字与"检查电网电表接线"的说明重复贴给了 system_self_test_result(:101-105 与 :48-52 内容相同),且 @Parameter 全为空。另外 logic/self_test.h 与本文件是 GBK→UTF-8 有损转换的产物,部分中文注释已成 U+FFFD 乱码(见 logic/self_test.h.md 备注 1),本文件 :16 附近的中文注释同样存在该问题。
  11. system_self_test_data 回填 offset 时不清零 samples::82-91 只写 offset,下一阶段由静置态的 memset 清 samples。这意味着三个 memset(:272-274、:303-305、:332-334)如果与采样态的执行顺序被打乱(例如 s_u8_record_countr 未归零,见风险 5),会用上一阶段的残留数据算均值。
  12. 自检有 150 秒硬性超时,且超时后不置任何结果位:midware/inverter/parallel.c:1778-1780 在 abs(OSTimeGet() - s_s32_self_test_tick) >= 150*1000 时无条件把 g_t_logic_info.u8_self_test_enable 清 0。这会让 logic.c:2201 的 u8_self_test_charge 变为 0,u8_sequencecontrol_mode 随即离开 SELF_TEST_CHARGE_MODE,system_self_test() 不再被调用——状态机被冻结在当时的 self_state_flag 上,既不会走 SELF_STATE_END,也不会 set_info_status(SELF_TEST_RESULT)。也就是说超时情况下云端永远收不到自检结论。三轮重测的理论耗时约 110 秒(见 4.4),与 150 秒上限的余量只有约 40 秒,任何一次采样被 20 ms 周期节拍拉长(例如某周期恰好错过 500 ms 门槛,需再等 20 ms)都会累积推迟。建议要么放宽上限、要么在超时路径上补一个失败结论。
  13. SELF_STATE_END 的重试会重跑整轮而不仅仅是失败阶段::362 在 END 里无条件把状态写回 SELF_STATE_START,而 START 会 memset 整表并进入 SELF_STATE_STANDING,于是 10 秒静置 + 充电 + 放电三阶段全部重来。若失败原因与"哪一阶段"无关(例如环境功率波动),重测的收益有限,却额外消耗 36 秒以上,与风险 12 的 150 秒上限形成相互挤压。
  14. 自检期间会覆盖逆变器自身的降载能力:parallel.c:1770-1773 在自检使能时调用 inverter_set_p_v_mode_down_load_flag(FALSE) / inverter_set_p_f_mode_down_load_flag(FALSE)(关闭逆变器 P-V / P-F 降载)并把 u16_mppt_percentage 设为 0。也就是说自检期间逆变器失去自身的电压/频率保护降载,且 PV 停止发电。本文件对此并不知情,但 dispatch.c:514-520 采用同样的做法(调频时关闭降载),两处都会关闭同一组保护,需注意叠加效应。