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

dispatch.c

源文件路径:logic/dispatch.c 所属模块:logic(能量调度核心 —— 云端 EverGen 系统调度 / FCAS-FFR 调频调度 / 老化测试) 代码行数:1615

1. 文件职责

本文件是 EMS 的「调度」执行中枢,把网络层下发的两类指令翻译成对 g_t_logic_info.t_logic_control_parameter 的功率限值写入:一类是云端 EverGen 系统调度(按 u8ControlMode 分派到 14 个 handle_* 执行体,覆盖光伏优先充电、负载跟随、最大输出、最大消纳、经济模式、电池禁充、西澳 AC/DC 调度、SunSpec 有功调度、老化测试等),另一类是本地调频调度(FCAS/FFR 共 14 种模式,实现阶跃与下垂两种频率响应算法并直接驱动充放电)。它同时负责两类指令的超时退出:超时后清空调度状态、恢复 MPPT 到 100%,让控制权回到 logic.c 的自发自用逻辑。驱动方是 logic 任务:logic.c:2928-2929 每周期调用 frequency_regulation_detection()(调频检测)与 dectect_api_dispatch_comm()(云端通信检测);logic.c:2314/2318 在 u8_sequencecontrol_mode 为 DISPATCH_FREQ_MODE/DISPATCH_MODE 时分别调用 excute_frequency_regulation_command() 与 excute_system_dispatch_command() 真正落功率。文件还维护老化测试的 6 态循环状态机与上报影子副本,供 net/ems.c 与 net/server.c 读写。

2. 关键数据结构与宏

本文件不定义新类型,全部来自 logic/dispatch.h 与 logic/logic.h。

名称类型说明
DISPATCH_TABLE_T / g_t_dispatch_t结构体 / 全局变量本文件 :30 定义实例,调度状态中枢,含系统调度表、调频表、老化表
STEverGenCmdTab结构体系统调度表,字段 u8Dispatch/u8ControlMode/u8AgingMode/u8ForceSOC/u8_pv_on_off/u16ActivePower/u16ReactivePower/u32CmdGetTick/u32_runtime
FREQ_DISPATCH_T结构体调频表,含死区/饱和带、满充放功率、动作标志 u8_freq_dispatch_flag、输出 s32_ac_dispatch_power
AGINGMODE_TABLE_T / s_t_agingmode_data_temp结构体 / static 变量老化上报表及其影子副本(:31 定义,= {0})
DISPATCH_CMD_T结构体云端指令载体:u8_enable + f_para[8] + u32_runtime
FM_BAND_E枚举调频阶跃锁存态:NORMAL_FREQ_E(0)/OVER_FREQ_E(1)/UNDER_FREQ_E(2)
AGING_MODE_E枚举老化子模式:AGING_END/PV_TO_GRID/PV_TO_BAT/BAT_TO_GRID/GRID_TO_BAT/BAT_TO_GRID_2/AGING_EXIT/AGING_START=0xff
AGING_STATE_E枚举老化结果:AGING_FAILURE/AGING_SUCCEED/AGING_ERROR/AGING_COMPLETE/AGING_ONGOING
ENModeControl枚举调度模式(FCAS_*/FFR_*/CHARGE_CONTROL_2/SUNSPECT_ACTIVE_MODE 等 30 个成员)
s_u8_pv_off_flagstatic u8(:29)本文件内部 MPPT 关断意图标志,TRUE 表示要求 u16_mppt_percentage = 0
POSITIVE_VALUE_OFFSET宏32000,功率零偏置。u16ActivePower/u16ReactivePower 与 32000 的差即实际功率,符号决定充放
NEGATIVE_VALUE_OFFSET宏-32000,本文件无引用点
FORCE_SOC_MULT宏4,u8ForceSOC 的放大倍数(SOC 判据 ×4 ± 4)
DISPATCH_POWER_MIN_SET / DISPATCH_POWER_MAX_SET宏0 / 64000,西澳 CHARGE_CONTROL_2 有效性开区间
EVERGEN_FUNC_START / EVERGEN_FUNC_STOP宏1 / 0,u8Dispatch 取值
FCAS_STEP_MODE/FAST_FCAS_STEP_MODE/VERY_FAST_FCAS_STEP_MODE 等枚举调频模式判别用;阶跃三兄弟走同一分支,FFR 八兄弟走同一分支
DEBUG_EVERGEN / DEBUG / DEBUG_TCP_CLIENT_STATE宏(编译开关)调试打印门控,来自 includes.h 链;大量 printf 被其包裹

3. 函数清单

本文件共 40 个函数定义,其中 static 1 个,其余 39 个为全局作用域。logic/dispatch.h 只登记了其中 24 个。

函数名作用域说明
set_freq_dispatch_parameterstatic(:44)判定是否为调频指令并按模式拆分 f_para[] 填入调频表;非调频模式返回 FALSE
set_dispatch_parameter全局(:98)指令总入口:先试调频解析,失败则按系统调度填充 t_stsystemcmdinfo
get_dispatch_running_mode全局(:128)返回当前调度模式(调频优先,否则 SFFR,否则 NO_DISPATCH_CONTROL)
dispatch_regulation_flag全局(:160)调频使能时返回调频动作标志,否则返回并网状态 u8_grid_status
dispatch_api_data_interval全局(:178)上报间隔 ms;调频固定 50,SFFR 为 1000,其余 50
dispatch_api_maxfeedin_priority全局(:210,未在头文件登记)按 get_maxfeedin_priority_flag() 决定是否恢复额定馈网功率,并可清电网侧参数
frequency_regulation_detection全局(:235)调频检测状态机:超时退出、优先级豁免、阶跃/下垂/FFR 算法、MPPT 与降载标志联动
excute_frequency_regulation_command全局(:529)把 s32_ac_dispatch_power 落成实际充放电限值并切换 MPPT
get_freq_dispatch_enable全局(:576)读调频使能
frequency_regulation_flag全局(:587)读调频动作标志
get_dispatch_running_freq全局(:598)读电网频率(0.01 Hz,环境 4500-5500)
get_fast_dispatch_running_freq全局(:620)读快速调频频率(0.001 Hz,环境 45000-55000)
get_fcas_running_flag全局(:642)是否 FCAS 阶跃/下垂模式
get_fast_fcas_running_flag全局(:667)是否 FAST_FCAS 模式
get_very_fast_fcas_running_flag全局(:692)是否 VERY_FAST_FCAS 模式
get_ffr_running_flag全局(:716)是否任一 FFR 模式
dectect_api_dispatch_comm全局(:748)云端调度超时退出、老化结果判定与翻转状态维护
handle_aging_mode全局(:877,未登记)老化测试 6 态循环状态机
handle_bat_charge_from_pv全局(:984,未登记)光伏优先充电模式执行体
handle_charge_control全局(:1011,未登记)按目标 SOC 分三段的强制充放电(实际只被 SFFR_MODE 调用)
handle_parallel_charge_control全局(:1072,未登记)并机充放电控制(实际被 CHARGE_CONTROL 调用)
handle_load_following全局(:1112,未登记)负载跟随模式执行体
handle_max_output全局(:1135,未登记)最大输出模式执行体
handle_normal_mode全局(:1151,未登记)普通模式执行体
handle_optimise_consume全局(:1174,未登记)优化消纳模式执行体
handle_max_consume全局(:1192,未登记)最大消纳模式执行体
handle_eco_mode全局(:1210,未登记)经济模式执行体
handle_bat_no_charge全局(:1228,未登记)电池禁充模式执行体
handle_charge_control_2_dc全局(:1249,未登记)西澳调度 DC 侧参数处理
handle_charge_control_2_ac全局(:1285,未登记)西澳调度 AC 侧参数处理(带最小 800 ms 计算间隔限制)
handle_charge_control_2全局(:1345,未登记)西澳调度入口:DC 有效优先,其次 AC,都无效则自发自用
dispatch_Sunspec_ative_power_mode全局(:1374,未登记)SunSpec 有功功率调度,仅 #ifdef INV_AC_CTRL 下有效
excute_system_dispatch_command全局(:1392)系统调度总分派 + MPPT 收尾
get_dispatch_enable全局(:1506)读 u8Dispatch
set_dispatch_enable全局(:1517)写 u8Dispatch
get_dispatch_mode全局(:1528)智能控制模式下返回 SMART_CONTROL,否则返回 u8ControlMode
get_dispatch_set_power全局(:1544)返回设定功率(已减零偏置);西澳模式只回有效值,都无效返回 65535
aging_mode_send_data_flag全局(:1582)memcmp 老化表与影子副本,判断是否有变化待上报
aging_mode_data_sync全局(:1593)把老化表拷进影子副本(上报成功回调)
dispatch_set_mode全局(:1605)仅当 mode < DISPATCH_MAX_MODE 时写 u8ControlMode,否则返回 -1

4. 核心逻辑解析

4.1 指令入口与「一个字段、两套语义」的解析

set_dispatch_parameter()(:98)是唯一写入口(由 net/server.c:835 调用):

if (set_freq_dispatch_parameter(dispatch_cmd) == FALSE) {   /* 不是调频指令 */
    u32CmdGetTick    = OSTimeGet();
    u8Dispatch       = u8_enable;
    u16ActivePower   = f_para[0];
    u16ReactivePower = f_para[1];
    u8ForceSOC       = f_para[2];
    u8ControlMode    = f_para[3];
    u8_pv_on_off     = f_para[7];
    u32_runtime      = u32_runtime;
}

set_freq_dispatch_parameter()(:44)先取 l_s32_mode = f_para[3] 做模式判别,命中 14 种 FCAS/FFR 模式之一才返回 TRUE。因此 f_para[3] 在调频指令里是"调频模式号",在系统调度指令里是 u8ControlMode——同一个槽位承担两种含义,靠"是否属于调频枚举集合"来区分。这是本文件最容易误改的地方。

调频参数的拆分规则(:66-79)分两类:

子类判定f_para[] 映射
阶跃类(FCAS_STEP_MODE/FAST_FCAS_STEP_MODE/VERY_FAST_FCAS_STEP_MODE)三个 STEP 模式u16_db = f_para[0]、u16_sb = f_para[1]
下垂类 + FFR 全部(FCAS_DROOP_MODE…FFR_Live_Test_MODE)其余 11 种u16_charge_power = f_para[0]、u16_ldb = f_para[1]、u16_hdb = f_para[2]、u16_lsb = f_para[4]、u16_hsb = f_para[5]、u16_discharge_power = f_para[6]

两个分支都会写 u8_dispatch_mode、u8_dispatch_enable、u32_dispatch_tick = OSTimeGet()、u32_dispatch_run_time = u32_runtime,返回 TRUE。未命中的模式返回 FALSE(:88),由调用方回落到系统调度分支。

4.2 调频检测状态机:frequency_regulation_detection()(:235)

函数内 5 个 static 状态量:s_u8_fcas_step_mode(阶跃锁存)、s_u32_freq_dispatch_tick(FFR 延时基准)、s_u8_inv_reduce_power_off(降载开关影子)、s_u16_charge_power/s_u16_discharge_power(上次满充放功率,用于检测变化并复位锁存)。执行顺序如下。

步骤 1 — 指令超时退出(:245-256)

if (mode != 0 || enable == TRUE) {
    if ((OSTimeGet() - u32_dispatch_tick) > (u32_dispatch_run_time * 1000))
        memset(&t_freq_dispatch_rundata, 0, sizeof(FREQ_DISPATCH_T));
    else if (u32_dispatch_tick > OSTimeGet())  u32_dispatch_tick = OSTimeGet();
}

u32_dispatch_run_time 单位为秒,OSTimeGet() 单位为毫秒(OS_TICKS_PER_SEC = 1000)。

步骤 2 — 锁存复位与优先级豁免(:258-276)

步骤 3 — 三种响应算法(:277-496)

(a) 阶跃模式(:281-372) 先把单死区/单饱和带展开成"四点"模型,并用逆变器额定输入/输出功率作为满充/满放功率:

u16_ldb = u16_hdb = u16_db;            // 死区
u16_lsb = u16_hsb = u16_sb;            // 饱和带
u16_charge_power    = s32_inv_rated_input_power;
u16_discharge_power = s32_inv_rated_output_power;

若充/放功率发生变化则复位锁存(:291-297)。频率异常保护:freq <= 4500 || freq >= 5500(45 Hz / 55 Hz)时输出 0 并复位锁存(:299-305)。正常区间的锁存迁移:

当前锁存条件输出 s32_ac_dispatch_power目标锁存
NORMAL_FREQ_Efreq <= 5000 - u16_lsb+u16_discharge_power(满放)UNDER_FREQ_E
NORMAL_FREQ_Efreq >= 5000 + u16_hsb-u16_charge_power(满充)OVER_FREQ_E
NORMAL_FREQ_E其他0不变
OVER_FREQ_Efreq <= 5000 - u16_lsb+u16_discharge_powerUNDER_FREQ_E
OVER_FREQ_Efreq <= 5000 + u16_hdb0NORMAL_FREQ_E
OVER_FREQ_E其他保持(不再赋值)不变
UNDER_FREQ_Efreq >= 5000 - u16_ldb0NORMAL_FREQ_E
UNDER_FREQ_Efreq >= 5000 + u16_hsb-u16_charge_powerOVER_FREQ_E
UNDER_FREQ_E其他保持不变

这是典型的带回差的施密特阶跃:进入动作按饱和带 sb,退出动作按死区 db,两者独立可调;由于阶跃模式 db≠sb 才产生回差。OVER_FREQ_E/UNDER_FREQ_E 的"其余分支只置 l_u8_freq_dispatch_flag_temp = TRUE 而不重写功率"体现了阶跃响应的锁存保持语义(l_u8_freq_dispatch_flag_temp 是本函数唯一输出,:504 再写回全局)。

(b) 下垂模式(:374-413) 线性插值公式,freq 与 5000(50.00 Hz)的偏差在 [db, sb] 区间内按比例出力:

频偏低于下限:
  freq <= 5000 - lsb          → +u16_discharge_power                       (满放)
  5000-lsb > freq > 5000-ldb  → +u16_discharge_power * ((5000-ldb) - freq) / (lsb - ldb)
频偏高于上限:
  freq >= 5000 + hsb          → -u16_charge_power                          (满充)
  5000+hsb > freq > 5000+hdb  → +u16_charge_power * ((5000+hdb) - freq) / (lsb - ldb)
死区内                        → 0(自发自用)

注意高频侧的斜率分母写的是 (u16_lsb - u16_ldb)——低侧的带宽,而不是 (u16_hsb - u16_hdb)。见备注 2。

(c) FFR 模式(:415-489) 这段代码用 C 的 case 穿透(fall-through) 给 l_u32_freq_dispatch_delay 赋不同延时:

case FFR_PRIMARY_MODE:     if (delay == 0) delay = 20*1000;                 /* 落到下一 case */
case FFR_SECONDARY_MODE:   if (delay == 0) delay = 30*60*1000;              /* 落到下一 case */
case FFR_HIGE_MODE:        if (delay == 0) { delay = 1000; s_u32_freq_dispatch_tick = OSTimeGet(); }
case FFR_STEP_TEST1_MODE:  ... /* 8 个测试/实机模式 */
case FFR_Live_Test_MODE:   if (delay == 0) { delay = 1000; s_u32_freq_dispatch_tick = OSTimeGet(); }

因为 delay 是每次调用都重新初始化为 0 的普通局部变量(:239),穿透链的净效果是:FFR_PRIMARY_MODE → 20 s;FFR_SECONDARY_MODE → 30 min;FFR_HIGE_MODE 与 8 个测试模式 → 1 s。代价是只有 FFR_HIGE_MODE 与测试模式那一支会给 s_u32_freq_dispatch_tick 打时间戳(见备注 3)。

动作逻辑与下垂模式相同(线性插值,同样用 (u16_lsb - u16_ldb) 作高频分母),区别在死区内不清零而是保持输出,靠 (OSTimeGet() - s_u32_freq_dispatch_tick) > l_u32_freq_dispatch_delay 延时归零(:476-488):

else {
    l_u8_freq_dispatch_flag_temp = TRUE;   // 进入 Waiting,电池不充不放,等待下次频率触发
    if ((OSTimeGet() - s_u32_freq_dispatch_tick) > l_u32_freq_dispatch_delay)
        s32_ac_dispatch_power = 0;
    else if (s_u32_freq_dispatch_tick > OSTimeGet())
        s_u32_freq_dispatch_tick = OSTimeGet();
}

(d) default(:491-494) 不支持的调频模式直接 memset 整张调频表,立即退出调度。

步骤 4 — 动作标志变化时恢复 MPPT(:504-512)

if (l_u8_freq_dispatch_flag_temp != u8_freq_dispatch_flag) {
    u8_freq_dispatch_flag = l_u8_freq_dispatch_flag_temp;
    if (u8_freq_dispatch_flag == FALSE) {
        s_u8_pv_off_flag = FALSE;              // 退出调频恢复 MPPT 追踪
        g_t_logic_info.u16_mppt_percentage = 10000;
    }
}

步骤 5 — 调频使能时关闭电压/频率降载(:514-520)

s_u8_inv_reduce_power_off 实际存的是 u8_dispatch_enable 的影子,变化时调用:

inverter_set_p_v_mode_down_load_flag(u8_dispatch_enable == FALSE);
inverter_set_p_f_mode_down_load_flag(u8_dispatch_enable == FALSE);

即调频使能期间关闭逆变器自身的 P-V / P-F 降载,避免与 EMS 调度叠加(:516 注释"开启频率调度关闭电压降载、频率降载")。

4.3 调频指令执行:excute_frequency_regulation_command()(:529)

由 logic.c:2314 在 DISPATCH_FREQ_MODE 下调用。以 s32_ac_dispatch_power 的符号二分:

条件动作
> 0(放电)s_u8_pv_off_flag = FALSE;s32_pgrid_metert = 0;s32_pinvt = 0;s32_max_feedin_power = s32_ac_dispatch_power;bat_force_discharge()
<= 0(充电)s_u8_pv_off_flag = TRUE;若 s32_bat_charge_power_limit > abs(s32_ac_dispatch_power) 则钳到该值;s32_pgrid_metert = 0;s32_pinvt = 0;s32_max_feedin_power = 0;bat_force_charge()

收尾统一按 s_u8_pv_off_flag 设置 g_t_logic_info.u16_mppt_percentage = 0 / 10000(:556-563)。注意充电分支只压不抬充电限值(若原限值更小则保持更小),放电分支则完全由 s32_max_feedin_power 承担限流。

4.4 频率读取与单位换算(:598、:620)

get_dispatch_running_freq():                                  // 单位 0.01 Hz
    debug_enable      → u16_debug_freq
    ct_read_frequent(GRID_CT) > 4400 → ct_read_frequent(GRID_CT)
    否则              → parallel_read_system_inv_grid_freq()
get_fast_dispatch_running_freq():                             // 单位 0.001 Hz
    debug_enable      → u16_debug_freq * 10
    ct_read_frequent(FAST_GRID_CT) > 44000 → ct_read_frequent(FAST_GRID_CT)
    否则              → parallel_read_system_inv_grid_freq() * 10

阈值 4400(44 Hz)与 44000(44 Hz)用来判定 CT 读数是否有效——低于 44 Hz 视为 CT 无输出,回落到并机模块汇总的逆变器电网频率。两个函数的调试注入路径都带 t_fm_debug.u8_debug_enable 使能。

4.5 云端调度超时与老化结果:dectect_api_dispatch_comm()(:748)

由 logic.c:2929 每周期调用。三段逻辑:

(a) 超时退出(:753-787) 仅对白名单内的 u8ControlMode(BAT_CHARGE_FROM_PV、CHARGE_CONTROL、LOAD_FOLLOWING、MAX_OUTPUT、NORMAL_MODE、OPTIMISE_CONSUME、MAX_CONSUME、ECO_MODE、BAT_NO_CHARGE、AGING_MODE、SFFR_MODE、CHARGE_CONTROL_2、SUNSPECT_ACTIVE_MODE)生效。条件 (OSTimeGet() - u32CmdGetTick) > (u32_runtime * 1000):

(b) u8Dispatch == EVERGEN_FUNC_STOP 的二次复位(:795-821) 再清一遍字段并把 u32_runtime 硬编码为 90 秒;若老化未完成则置 AGING_FAILURE/AGING_END;若调频未在动作则恢复 MPPT(s_u8_pv_off_flag = FALSE、u16_mppt_percentage = 10000)。

(c) 老化结果判定(:823-862)

if (u8ControlMode == AGING_MODE) {
    if (ems_read_fault_code() || bms_read_fault_code() || inverter_read_fault_code() ||
        inverter_read_fault_code_extend() || inverter_read_fault_code_extend2())
        u8_aging_result = AGING_ERROR;                        // 任一故障 → 错误
    else if (s_t_agingmode_data_temp.u8_aging_status != u8AgingMode &&   // 老化步进已推进
             u8AgingMode != AGING_END &&
             u8_aging_result != AGING_FAILURE && u8_aging_result != AGING_COMPLETE)
    {
        u8_aging_result = AGING_SUCCEED;                      // 当前步成功
        if (u8AgingMode != AGING_START) u8_aging_status = u8AgingMode;
    }
    else  u8_aging_result = AGING_ONGOING;
    if ((OSTimeGet() - s_u32_send_data_tick) >= 60 * 1000)     // 每 60 s 翻转一次
        u8_flip_status = (u8_flip_status == FALSE);
}
else  s_u32_send_data_tick = OSTimeGet();

u8_aging_status 相当于"已确认完成的步",与指令下发的 u8AgingMode 比较即得"步进已推进"。每 60 秒翻转 u8_flip_status,是给上位机做存活/心跳判据用的。

4.6 老化 6 态循环:handle_aging_mode()(:877)

SOC 门限来自功率字段(:879-891):l_u16_max_chargr_soc = u16ActivePower * 10、l_u16_max_dischargr_soc = u16ReactivePower * 10,为 0 时取默认 800 / 500(即 80% / 50%)。

u8AgingMode动作迁移条件目标
AGING_START(0xff)先赋值 u8AgingMode = PV_TO_BAT,然后穿透执行 PV_TO_GRID 的代码体穿透(无 break)PV_TO_GRID 行为
PV_TO_GRIDPV 开;pgrid_metert = 0、pinvt = 0、max_feedin = 额定输出功率、充电限值 0、放电限值 0;bat_force_discharge()无(由外部指令切走)—
PV_TO_BATPV 开;SOC >= 充电门限 → 转 BAT_TO_GRID;否则 max_feedin = 0、bat_force_charge()SOC 达充电门限BAT_TO_GRID
BAT_TO_GRIDPV 关;SOC <= 放电门限 → 转 GRID_TO_BAT;否则 max_feedin = 额定、充电限值 0、bat_force_discharge()SOC 跌破放电门限GRID_TO_BAT
GRID_TO_BATPV 关;SOC >= 充电门限 → 转 BAT_TO_GRID_2;否则 max_feedin = 0、bat_force_charge()SOC 达充电门限BAT_TO_GRID_2
BAT_TO_GRID_2PV 关;SOC <= 放电门限 → 转回 PV_TO_BAT(循环开始);否则 max_feedin = 额定、充电限值 0、bat_force_discharge()SOC 跌破放电门限PV_TO_BAT
AGING_END赋 u8AgingMode = AGING_END,穿透到 default穿透(无 break)AGING_START(被 default 覆盖)
defaultu8AgingMode = AGING_START—AGING_START

因此老化循环是 PV_TO_BAT → BAT_TO_GRID → GRID_TO_BAT → BAT_TO_GRID_2 → PV_TO_BAT 的 SOC 往复(PV 充电 / 上网放电 / 电网充电 / 上网放电),直到云端撤销指令。AGING_EXIT 只被 dectect_api_dispatch_comm() 写给 u8_aging_status,不参与这里的状态判断。

4.7 系统调度总分派:excute_system_dispatch_command()(:1392)

logic.c:2318 在 DISPATCH_MODE 下调用。switch (u8ControlMode) 的映射关系(:1410-1467):

u8ControlMode实际调用备注
BAT_CHARGE_FROM_PVhandle_bat_charge_from_pv()—
CHARGE_CONTROLhandle_parallel_charge_control()名字与用途错位
SFFR_MODEhandle_charge_control()名字与用途错位
LOAD_FOLLOWINGhandle_load_following()—
MAX_OUTPUThandle_max_output()—
NORMAL_MODEhandle_normal_mode()—
OPTIMISE_CONSUMEhandle_optimise_consume()—
MAX_CONSUMEhandle_max_consume()—
ECO_MODEhandle_eco_mode()—
BAT_NO_CHARGEhandle_bat_no_charge()—
AGING_MODEhandle_aging_mode()—
CHARGE_CONTROL_2handle_charge_control_2()西澳调度
SUNSPECT_ACTIVE_MODEdispatch_Sunspec_ative_power_mode()仅 #ifdef INV_AC_CTRL
defaults_u8_pv_off_flag = FALSE; self_use_funtion();未定义模式(含 21-25/31 等枚举空洞)回到自发自用

之后有两次收尾覆盖:

if (u8_pv_on_off == 1)      s_u8_pv_off_flag = FALSE;   // 云端强制开 PV
else if (u8_pv_on_off == 2) s_u8_pv_off_flag = TRUE;    // 云端强制关 PV

if (frequency_regulation_flag() == FALSE)               // 调频未动作时 MPPT 由本文件决定
    u16_mppt_percentage = s_u8_pv_off_flag ? 0 : 10000;

MPPT 的赋值刻意加了 frequency_regulation_flag() == FALSE 门控(:1478),避免覆盖调频分支的 MPPT 决策——这是两个子系统之间唯一的显式互斥点。

4.8 各模式执行体的功率限值算法

所有执行体只写三个字段,且限值只压不抬:

执行体关键动作
handle_bat_charge_from_pv(:984)s_u8_pv_off_flag = FALSE;dispatch_api_maxfeedin_priority(FALSE)(不管馈网优先级);充电限值钳到 abs(32000 - u16ActivePower);放电限值 = 0;self_use_funtion()
handle_charge_control(:1011)取 parallel_read_system_bat_average_soc() 与目标 u8ForceSOC * 4 比较,形成三段:<= 目标-4 强制充(并钳充电限值到 abs(32000 - u16ActivePower))、目标±4 内充放限值都置 0、>= 目标+4 强制放(钳放电限值)。每段都调 bat_force_charge()/bat_force_discharge()
handle_parallel_charge_control(:1072)按 32000 与 u16ActivePower 的大小关系二分:小于则钳充电限值并强制充、大于则钳放电限值并强制放、相等则两者归零
handle_load_following(:1112)充电限值与放电限值都钳到 abs(32000 - u16ActivePower),然后 self_use_funtion()
handle_max_output(:1135)dispatch_api_maxfeedin_priority(TRUE);直接 bat_force_discharge()(清充电限值那行被注释掉,:1141)
handle_normal_mode(:1151)同 handle_load_following
handle_optimise_consume(:1174)s32_pgrid_metert = -s32_pinvt,并逐相 s32_pgrid_meter{a,b,c} = -s32_pinv{a,b,c};s32_max_feedin_power = 0;bat_force_charge()
handle_max_consume(:1192)与 handle_optimise_consume 完全相同,唯一区别是 s_u8_pv_off_flag = TRUE(关 PV)
handle_eco_mode(:1210)只钳充电限值,然后 self_use_funtion()(不钳放电限值)
handle_bat_no_charge(:1228)充电限值 = 0;self_use_funtion();再把 s32_ac_total_set_inv_power 的下限钳到 0(:1237-1240,注释说明"电池禁充时 AC 控制值不能发负值,会导致 BUG 过压")
handle_charge_control_2_dc(:1249)32000 - dc_set_power < 0 → 钳放电限值 + bat_force_discharge();> 0 → 放电限值 0 + 钳充电限值 + bat_force_charge();== 0 → no_bat_logic_control()
handle_charge_control_2_ac(:1285)带节流:abs(OSTimeGet() - u32_calculation_total_inv_tick) < max(800, u32_calculation_interval) 时直接 return。放电时取 min(s32_max_feedin_power, abs(ac_set_power - 32000)) 作为电网侧偏置;充电时先 s32_pgrid_metert = s32_pgridt 再把 ac_set_power - 32000 叠加到 s32_pgrid_metert;最后 self_use_funtion()
handle_charge_control_2(:1345)参数有效性判定:u16ActivePower ∈ (0, 64000) → 走 DC;否则 u16ReactivePower ∈ (0, 64000) → 走 AC;都无效 → self_use_funtion()。注释说明云端用 -1 表示无效(u16 解析成 65535)

4.9 馈网优先级:dispatch_api_maxfeedin_priority()(:210)

s32_max_feedin_power = (get_maxfeedin_priority_flag() == TRUE) ? s32_max_feedin_power
                                                              : s32_inv_rated_output_power;
if (clear_grid_params && get_maxfeedin_priority_flag() != TRUE) {
    s32_pgrid_metert = s32_pgrid_metera = s32_pgrid_meterb = s32_pgrid_meterc = 0;
    s32_pinvt = 0;
}

即:若外部(CSIP/DNSP 等)声明了馈网优先级,则保留既有 s32_max_feedin_power;否则把它放宽到逆变器额定输出功率,并可顺带清空电网侧参数。

4.10 上报表与影子副本

aging_mode_send_data_flag()(:1582)用 memcmp 比较 g_t_dispatch_t.t_agingmode_info 与 s_t_agingmode_data_temp;aging_mode_data_sync()(:1593)在收到云端 CMD_SEND_BURNIN_DATA 确认(net/server.c:7021)后把前者拷入后者。这样 net/ems.c:5092 每 10 秒检查一次 aging_mode_send_data_flag() == TRUE 才发数据,避免重复上报。注意 memcmp 会包含 u8_flip_status 这个每 60 秒必然翻转的位,所以只要老化在跑,aging_mode_send_data_flag() 每 60 秒必然为 TRUE。

get_dispatch_set_power()(:1544)把零偏置去掉后返回;西澳模式(CHARGE_CONTROL_2)只回有效值,否则返回 65535 表示无效。

4.11 定时与 uCOS-II 配合

机制说明
调用节拍logic 任务以 20 tick(20 ms,logic.c:2912 的 OSFlagPend(..., 20, ...))循环,本文件所有检测函数都按此节拍轮询,不使用阻塞延时
立即唤醒net/server.c:838-839 在 set_dispatch_parameter() 后 OSFlagPost(g_t_fast_dispatch_flag, POST_PEND_FAST_ENABLE, ...) + OSTimeDlyResume(TASK_LOGIC_CONTROL_PRIO),使下行指令不必等满 20 ms
时间基准全部用 OSTimeGet(),单位 ms(OS_TICKS_PER_SEC = 1000)
时钟回绕本文件有 4 处 else if (tick > OSTimeGet()) tick = OSTimeGet(); 保护(:252、:783、:854、:484),但未保护"tick 早于当前时刻但差值已超 2^31"的情形
信号量 / 邮箱本文件不使用,全部通过共享全局变量与上面的单一事件标志组同步

4.12 g_t_dispatch_t 的读写全景

字段组写者读者
t_stsystemcmdinfo.*set_dispatch_parameter()(net 任务)、dectect_api_dispatch_comm()、handle_aging_mode()、get/set_dispatch_enable、dispatch_set_mode本文件全部 handle_*、net/ems.c:773/1490/1557、midware/interface/modbus、midware/inverter/parallel.c
t_freq_dispatch_rundata.*set_freq_dispatch_parameter()(net 任务)、frequency_regulation_detection()、excute_frequency_regulation_command()net/ems.c 上报、midware/interface/modbus/modbus.c:3604/3612、midware/inverter/parallel.c:788/959
t_agingmode_info.*dectect_api_dispatch_comm()、aging_mode_data_sync()net/ems.c:5092(经 aging_mode_send_data_flag)

t_fm_debug 只被测试代码写(外部写入 t_freq_dispatch_rundata.t_fm_debug),本文件只读。

4.13 通信协议帧格式

本文件不直接实现帧收发,但它是 net/server.c 下行 JSON 的消费者,隐含如下协议约定:

5. 对外接口

导出(供外部使用):

名称类型调用方
g_t_dispatch_t全局变量net/ems.c、net/server.c、midware/interface/modbus、midware/interface/dry_node、midware/inverter/parallel.c、data/resume_data/flash/flash_data_store.c
set_dispatch_parameter函数net/server.c:835
frequency_regulation_detection / dectect_api_dispatch_comm函数logic/logic.c:2928 / logic.c:2929
excute_frequency_regulation_command / excute_system_dispatch_command函数logic/logic.c:2314 / logic.c:2318
get_dispatch_running_mode / dispatch_api_data_interval / dispatch_regulation_flag函数net/ems.c:6492 / ems.c:6508 / ems.c:6537
frequency_regulation_flag函数logic/logic.c:2241/3501、midware/interface/modbus/modbus.c:3604/4513、net/ems.c:6355/6404/6453
get_freq_dispatch_enable函数midware/inverter/parallel.c:788/959
get_dispatch_running_freq / get_fast_dispatch_running_freq函数midware/interface/modbus/modbus.c:3612、net/ems.c:6349/6398/6447/6531/6589
get_fcas_running_flag / get_fast_fcas_running_flag / get_very_fast_fcas_running_flag / get_ffr_running_flag函数net/ems.c:6321/6370/6419/6573
get_dispatch_enable函数logic/logic.c:2245、midware/bms/bms.c:2225
get_dispatch_mode / get_dispatch_set_power函数net/ems.c:774/1491/1557 等上报点
aging_mode_send_data_flag / aging_mode_data_sync函数net/ems.c:5092 / net/server.c:7021
set_dispatch_enable / dispatch_set_mode函数midware/interface/modbus/sunspec_business.c:1113/1114
dispatch_api_maxfeedin_priority函数无外部调用方(只有本文件 handle_* 内部使用),且未在头文件登记

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

符号来源
OSTimeGet、OSFlagPost(间接)UCOSII/uCOS-II
bat_force_charge、bat_force_discharge、self_use_funtion、no_bat_logic_control、logic_read_sequencecontrol_modelogic/logic.h:653-660(实现在 logic/logic.c)
g_t_logic_info、u16_mppt_percentage、s32_ac_total_set_inv_power、s32_pgridt、u32_calculation_interval、u32_calculation_total_inv_tick、u8_dnsp_maxfeedin_priority_flaglogic/logic.h
parallel_read_system_bat_average_soc、parallel_read_system_inv_grid_freq、parallel_read_system_self_consumption(注释中)midware/inverter/parallel.h
ct_read_frequent、GRID_CT、FAST_GRID_CTmidware/meter/ct.h
meter_read_actpower、meter_read_reactpower、GRID_METER、PV_METER、PHASE_ALL、PHASE_Tmidware/meter/meter.h
inverter_read_inv_actpower、inverter_read_grid_actpower、inverter_read_backup_actpower、inverter_read_bat_power、inverter_read_pv_power、inverter_set_p_v_mode_down_load_flag、inverter_set_p_f_mode_down_load_flag、inverter_read_fault_code*、inverter_read_safety*、get_inverter_type(经 csip_aus)midware/inverter/inverter.h
bms_read_total_power、bms_read_fault_codemidware/bms/bms.h
ems_read_fault_codedata/data.h:127
ac_control_Sunspec_ative_power、ac_control_*logic/ac_control.h
get_maxfeedin_priority_flaglogic/logic.h:708
memset、memcmp、abs、printfC 运行库 / includes.h

6. 依赖关系

依赖(dispatch.c:13-23):dispatch.h、inverter.h、bms.h、ct.h、data.h、modbus.h、logic.h、server.h、ac_control.h、sunspec_business.h、meter.h。

被依赖:

7. 备注与风险点

  1. handle_charge_control 与 handle_parallel_charge_control 的映射与命名相反::1416-1422 里 case CHARGE_CONTROL: 调 handle_parallel_charge_control(),case SFFR_MODE: 调 handle_charge_control()。两个函数名都带 charge_control,但一个按并机偏置充放、一个按目标 SOC 三段充放,极易在维护时改错分支。建议改名或在 switch 处加注释。
  2. 下垂/FFR 高频侧的斜率分母用了低侧带宽::404-405 与 :472-473 的公式 u16_charge_power * ((5000+hdb) - freq) / (u16_lsb - u16_ldb) 分母是低侧的 (lsb - ldb),而低侧公式 :392-393、:458-459 用的也是 (lsb - ldb)。若高侧带宽 (hsb - hdb) 与低侧不等(CSIP/FCAS 规范允许不对称),高侧的出力斜率会算错。从代码结构看这更可能是复制粘贴漏改。注意:set_freq_dispatch_parameter() 里下垂/FFR 参数是按 ldb=f_para[1]、hdb=f_para[2]、lsb=f_para[4]、hsb=f_para[5] 拆的,低侧带宽 = lsb-ldb、高侧带宽 = hsb-hdb,两者可独立配置,因此这个分母确实是个 bug 而非巧合。
  3. FFR 的 fall-through 延时链导致 s_u32_freq_dispatch_tick 未打戳::415-440 依赖 case 穿透给 l_u32_freq_dispatch_delay 赋值。由于该变量每次调用都重置为 0,穿透链的 if (delay == 0) 只有第一次命中才执行,所以对 FFR_PRIMARY_MODE 与 FFR_SECONDARY_MODE,s_u32_freq_dispatch_tick 不会被打时间戳(只有 FFR_HIGE_MODE 与 8 个测试模式那一支会)。结果是这两个模式在死区内的"延时退出"分支(:480)用的是一个可能为 0 或过期很久的时间戳,(OSTimeGet() - tick) > delay 立刻成立,输出被立即归零——即这两模式的延时保持实际不生效。另外 l_u32_freq_dispatch_delay 不必要地是普通局部变量(每次调用重建),若改成 static 语义会变,改动时须留意。
  4. case AGING_END: 穿透到 default 使 AGING_END 无法保持::969-974
   case AGING_END:
       g_t_dispatch_t.t_stsystemcmdinfo.u8AgingMode = AGING_END;
   default:
       g_t_dispatch_t.t_stsystemcmdinfo.u8AgingMode = AGING_START;

第一句赋的 AGING_END 立即被 default 覆盖成 AGING_START。因此 AGING_END 是一个永远观察不到的状态值,dectect_api_dispatch_comm() 里 u8AgingMode != AGING_END 的判断(:834)也就永远为真。case AGING_START: 穿透到 PV_TO_GRID(:895-907)同理:第一句把模式置为 PV_TO_BAT,但接着执行的是 PV_TO_GRID 的动作体,所以老化开始的第一周期实际是"上网放电"而非"PV 充电"。

  1. 限制值"只压不抬"的隐式约定没有注释:全部 16 个 handle_* 都只在 limit > X 时把 limit 改小,从不放大。这意味着限值一旦被上一层(如 logic.c 的 SOC 限值、bms.c 的 get_dispatch_enable() 分支)压低,本文件的模式切换无法把它恢复。加上多个 handle_*(handle_max_output 的 :1141、handle_parallel_charge_control 的 :1094)把"清零某一侧限值"的语句注释掉了,行为差异很难从代码表面读出。
  2. handle_optimise_consume 与 handle_max_consume 仅差一个 MPPT 标志::1174-1202 两函数体除 s_u8_pv_off_flag 外完全相同(前者 FALSE、后者 TRUE)。这是刻意的(优化消纳 vs 最大消纳的差别就是是否允许 PV 发电),但两份 30 行代码重复维护,改动时容易只改一处。
  3. s_u8_pv_off_flag 是文件级 static,与调频子系统双向耦合:四个地方读写它(:509、:533、:543、:817、:899…),且 excute_system_dispatch_command() 的 MPPT 赋值被 frequency_regulation_flag() == FALSE 门控(:1478)。若调频标志为 TRUE 但同时收到了系统调度指令(两个指令源并存),系统调度的 PV 开关意图会被静默丢弃。
  4. g_t_dispatch_t 跨任务无锁:写者来自 net 任务(set_dispatch_parameter → net/server.c:835)与 logic 任务(所有 handle_*、dectect_api_dispatch_comm),读者还有 modbus、ems、parallel、bms 任务。同步手段只有 g_t_fast_dispatch_flag 事件标志(保证 logic 被唤醒),不对结构体本身互斥。set_dispatch_parameter() 分 8 条语句写 t_stsystemcmdinfo,期间被 logic 任务读走会得到"新模式 + 旧功率"的组合。风险在老化/西澳调度这类功率字段与模式字段强耦合的场景下最明显。
  5. 多处 u32/s32 时钟比较未防溢出::248、:767、:849、:1292 都用 (OSTimeGet() - tick) > X 的形式,tick 有的是 u32(u32CmdGetTick)有的是 s32。s32 承载 OSTimeGet() 返回值在 24.9 天后符号翻转,u32 的减法在回绕时虽能给出正确差值,但 else if (tick > OSTimeGet()) 这类保护只覆盖了"tick 在未来"的情况。
  6. u8ForceSOC * FORCE_SOC_MULT ± 4 的三处口径不一致:本文件 :1018/1035/1041 用 u8ForceSOC * 4 ± 4 作 SOC 判据,net/ems.c:774/1491 上报时写 u8ForceSOC * FORCE_SOC_MULT / 10,而 handle_aging_mode() 又把 u16ActivePower * 10 当 SOC 门限(:880)。同一批云端字段被三种单位解释,±4 与 /10 这两个缩放没有任何注释。
  7. 硬编码值散落::418 的 20*1000、:423 的 30*60*1000、:809 的 90、:849 的 60 * 1000、:1292 的 800、:299 的 4500/5500、:1420(SFFR_MODE)、:1565 的 65535。频率阈值 4500/5500 只有注释"频率异常"说明是 45/55 Hz 边界,没有宏;±4、800、90 完全靠读代码推断含义。
  8. 调试打印的门控组合可疑::789 与 :864 写的是 #if (DEBUG == 1) & (DEBUG_EVERGEN == 1)——用的是按位与 & 而不是逻辑与 &&。在 #if 预处理表达式里两者对整型常量结果等价,但写法本身是笔误;同样的模式在 :564、:1400 等 20 余处 #if (DEBUG_EVERGEN == 1) 里也存在。无功能影响,但会让人误以为条件更复杂。
  9. excute_system_dispatch_command() 里有引用未定义标识符的调试代码::1400-1403 的 printf 使用了 l_s32_inv_ac_rated_power、l_s32_allow_charge_power、l_s32_allow_dischg_power、l_u16_bat_total_soc、g_t_dispatch_t.t_stsystemcmdinfo.u8AgingModel——这些都不是本函数的局部变量,u8AgingModel 字段名也不存在(应为 u8AgingMode)。它被 #if (DEBUG_EVERGEN == 1) 包裹,只有开启该宏时才会编译失败。属版本遗留死代码,建议清理。
  10. get_dispatch_running_mode() 对调频模式永远返回 NO_DISPATCH_CONTROL::128-152 的调频分支只有 default: return NO_DISPATCH_CONTROL;,没有任何具体 case。因此只要调频使能,该函数就返回"无调度",net/ems.c:6492 的 if (get_dispatch_running_mode() != NO_DISPATCH_CONTROL) 也就不会为调频启动数据上报路径(调频上报走的是 net/ems.c:6321 的另一条路径)。看起来是有意为之,但这个"空 switch"很容易被误认为是漏写 case。
  11. dispatch_api_maxfeedin_priority 未在头文件声明:它被定义为全局而非 static,却只在本文件内部使用,且不出现在 dispatch.h。属于"既不是内部函数也没有对外契约"的中间态,建议加 static 或补声明。同类情况还有 15 个 handle_* 与 dispatch_Sunspec_ative_power_mode。
  12. NEGATIVE_VALUE_OFFSET 与本文件无引用:定义在 dispatch.h,本文件从未使用,属遗留宏。