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

interface.c

源文件路径:midware/interface/interface.c 所属模块:midware/interface(外部接口层) 代码行数:380

1. 文件职责

本文件是 EMS 主控固件"外部接口层"的任务实现体,承载两个 uCOS-II 任务:task_interface_parse(500 ms 周期)与 task_modbus_rtu(信号量/100 ms 周期)。task_interface_parse 把 DRM 判定(drm_mode_judge())、干接点逻辑(Handle_Dry_Node_Logic())、配置参数落盘轮询(query_parameter_save_tags())和 EPO(紧急停机)状态机(epo_control_handle())串成一个固定顺序的轮询链,是这几个子模块唯一的驱动者。task_modbus_rtu 则根据运行态把 485 口(USART3)在三种用途之间仲裁——并机方案自适应探测、光伏充电桩/交流充电桩控制、Modbus-RTU 主站收帧,是 485 总线的"总线仲裁者"。它既是数据入口(Modbus 帧 → handle_modbus_ret_recv_data()),也是控制出口(逆变器开关机、充电桩启停),被 user/main.c 创建、被硬件中断(USART3 RECV)通过信号量唤醒。

2. 关键数据结构与宏

本文件不定义新结构体或枚举,只使用外部定义的类型与宏。

名称类型(结构体/枚举/宏/联合体)说明
EPO_STATUS_E枚举EPO 状态机的三个状态,定义于 logic/logic.h:107-112:EPO_IDLE = 0、EPO_SEND_SHUTDOWN、EPO_SEND_STARTUP;本文件 s_u8_epo_status 即该类型
g_t_config_save_flag / CONFIG_SAVE_FLAG_T结构体定义于 midware/interface/modbus/modbus.h:1416-1431,共 13 个 u8 标志位,是本文件参数保存轮询的唯一输入源
SYSTEM_BASE_PARA_PAGE 等宏Flash 保存页编号,来自 data/system_config/system_config.h,作为 save_job_post() 的实参
PARA_COM_TYPE_*枚举并机通讯方案类型,定义于 midware/inverter/parallel.h:34-37:PARA_COM_TYPE_NONE = 0、PARA_COM_TYPE_UART4、PARA_COM_TYPE_CAN、PARA_COM_TYPE_USART3
PARA_COM_RELY_ON_CARD宏并机通讯"依赖通讯卡",定义于 midware/inverter/parallel.h:43,用于分支判断
PARALLEL_MSTER_CHECK_TIME / PARALLEL_SLAVE_CHECK_TIME宏并机方案自适应主机/从机切换间隔,分别为 15000 / 30000(ms),定义于 midware/inverter/parallel.h:16-17
PVCHARGR_SINGLE_E / PVCHARGR_HOST_E枚举光伏充电桩工作模式,定义于 midware/inverter/pvcharger.h:19、21
BAUD_RATE_9600宏Modbus 波特率常量,来自 midware/interface/modbus/modbus.h
ALPHAESS_TYPE / INVERTER_TYPE宏逆变器厂商类型,midware/inverter/inverter.h:167、172;用于 EPO 上电 30 s 宽限期开关
UPDT_EVC枚举值升级对象类型,来自 net/upd_download.h,用于 Set_Status_Is_Updated_From_Server()
AGING_MODE枚举值老化模式,定义于 logic/dispatch.h:46(本文件不直接用,但经干接点/调度联动)
DEBUG_INTERFACE宏定义为 0(midware/interface/interface.h:13),控制任务计数调试代码

3. 函数清单

函数名作用域(static/全局)说明(一句话)
query_parameter_save_tags全局(但无头文件声明,实际仅本文件使用)每 5 s 轮询 g_t_config_save_flag,把置位的配置变更标志转成 save_job_post() 落盘任务,并清标志
epo_control_handlestaticEPO 紧急停机三态机:检测 DI 信号 → 发逆变器关机+BMS 关机 → 信号恢复后发开机并清故障位
task_interface_parse全局接口解析任务主体:drm_init() 后进入 500 ms 循环,依次跑 DRM 判定、干接点逻辑、参数保存轮询、EPO 控制
parallel_precept_change全局(但无头文件声明,实际仅本文件使用)并机通讯方案(UART4 / USART3 / CAN)自适应探测与切换,带 1 h 强制切换到 CAN 的超时逻辑
task_modbus_rtu全局Modbus-RTU 任务主体:可选 Sunspec 初始化,然后按运行态在并机探测 / 光伏充电桩 / 交流充电桩 / Modbus 收帧四条分支间切换

本文件共 5 个函数(1 个 static,4 个全局),无遗漏。

4. 核心逻辑解析

4.1 任务总览与调度时序

task_interface_parse(midware/interface/interface.c:211-232):

drm_init();                          // 一次性:DRM 模式复位为 16(无效)
for(;;) {
    drm_mode_judge();                // DRM 电平采集 + 模式判定(内部自带 1 s 节流)
    Handle_Dry_Node_Logic();         // 干接点(EPO 输入 / 柴油机 / 老化 / 馈网控制)
    query_parameter_save_tags();     // 参数保存标志轮询(内部 5 s 节流)
    epo_control_handle();            // EPO 状态机
    OSTimeDlyHMSM(0,0,0,500);        // 固定 500 ms 周期
}

四个子逻辑的执行顺序是固定的且不可重入——因为它们共享 g_t_logic_info、STDryNodeInfo、DRY_NODE0/1 的 DO 输出。注意干接点逻辑 Handle_Dry_Node_Logic() 内部会直接操作 DRY_NODE1 宏(do0_output_on/off),而 DRM 判定会写 g_t_logic_info.u8_drm_mode,二者无锁保护,但都在同一个任务上下文里串行执行,因此不会自我竞争;风险在于其它任务(HMI/Net)对同一批全局量的读改写。

task_modbus_rtu(midware/interface/interface.c:298-379)是一个 4 路优先级分支 的循环:

优先级条件行为延时
1u8_parallel_en == TRUE 且 并机方案为 USART3 或 NONE 且 u8_precept_change_recv_tick_flag == FALSEparallel_precept_change()100 ms
2pvcharger_read_machine_mode() == PVCHARGR_HOST_E强制 9600 波特率 + pvcharger_send_host_machine_info()100 ms
3get_evc_enable_flag() == TRUE 或 get_evc_ii_enable_flag() == TRUE强制 9600 波特率;若 is_upgrade_evc() 则 evc_upgrad_handle()(50 ms),否则 evc_control() / evc_ii_control()50 / 100 ms
4以上都不满足(默认)OSSemPend(g_t_uart3_rcv_sem, 100, &err) → handle_modbus_ret_recv_data() → 条件性 sunspec_modbus_data_refresh()由信号量决定,最长阻塞 100 ms

只有在分支 4(纯 Modbus 从站模式)下才真正走信号量驱动:USART3 接收中断在 hardware/usart3/usart3.c:123 调 OSSemPost(g_t_uart3_rcv_sem),本任务 OSSemPend 超时 100 ms(midware/interface/interface.c:364),超时后不再强行解析——即"有帧才处理,无帧就空转"。注释明确写着"调整阻塞时间,防止切换到充电桩时任务无法继续执行",说明 100 ms 超时是为了让任务能感知运行态变化、及时跳出分支 4 去跑充电桩逻辑。

分支 2 / 3 会主动改写波特率并置保存标志(midware/interface/interface.c:320-326、333-339),把 g_t_modbus_config.u8_modbus_baud_rate 强制为 BAUD_RATE_9600,然后 uart3_init(9600) 重配外部 485;同时置 g_t_config_save_flag.u8_set_modbus_config_flag = TRUE,让 query_parameter_save_tags() 在另一个任务里落盘。这是一个跨任务的"写→标记→异步保存"模式。

4.2 EPO(紧急停机)状态机

状态机定义在 epo_control_handle()(midware/interface/interface.c:139-202):

EPO_IDLE ──(config_enable && signal_status==TRUE)──> EPO_SEND_SHUTDOWN
   │                                                        │
   └──(protect_enable && !signal_status && run_mode==1 && comm_ok)──> EPO_SEND_STARTUP
                                                                    │
EPO_SEND_STARTUP ──(无条件)──> EPO_IDLE ─────────────────────────────┘

上电宽限期(midware/interface/interface.c:142-153):在 (INVERTER_TYPE == ALPHAESS_TYPE) 编译条件下,用 static bool s_b_first 记录首帧 tick,前 30 s(30 * 1000)直接 return,让系统状态(尤其 inverter_read_comm_flag())稳定后再启用 EPO 判定。在本快照工程中 INVERTER_TYPE 被定义为 INVERTER_NULL(midware/inverter/inverter.h:172),因此该宽限期代码不参与编译,s_u32_tick_cond_start 也会成为未使用变量。

4.3 参数保存标志轮询

query_parameter_save_tags()(midware/interface/interface.c:46-130)是一个"标志→Flash 页"的转换表,节流逻辑先用 abs(s_u32_query_tick - OSTimeGet()) < 5000 判断,即至少间隔 5 s 才扫描一次。扫描表:

标志位目标 Flash 页
u8_set_sys_base_para_flagSYSTEM_BASE_PARA_PAGE
u8_set_ui_config_flagSYSTEM_BASE_PARA_PAGE(与上一项同页)
u8_set_logic_config_flagLOGIC_CONFIG_PAGE
u8_set_logic_config_flag2LOGIC_CONFIG_PAGE2
u8_set_logic_config_flag3LOGIC_CONFIG_PAGE3
u8_set_meter_config_flagMETER_CONFIG_PAGE
u8_set_battery_config_flagBATTERY_CONFIG_PAGE
u8_set_inverter_config_flagINVERTER_CONFIG_PAGE
u8_set_net_para_flagNET_PARA_PAGE
u8_set_modbus_config_flagMODBUS_CONFIG_PAGE
u8_set_ct_config_flagCT_CONFIG_PAGE
u8_set_system_custom_info_flagSTSTEM_CUSTOM_CONFIG_PAGE

每个分支都是"先清标志、再投递任务"的顺序(如 midware/interface/interface.c:57-61),这样即使 save_job_post() 的队列满(返回失败),标志也不会被重复消费导致无限重试——但代价是这次配置变更会永久丢失。g_t_config_save_flag 的实际写者散布在各处:midware/interface/modbus/modbus.c(Modbus 写寄存器时大量置位,例如 modbus.c:5358 置 u8_set_ct_config_flag)、midware/ethernt/ethernet.c:1177、midware/ethernt/wireless/wifi/e_linker_wifi.c:341、以及本文件自身(interface.c:325、interface.c:338)等。

注意 u8_set_time_flag 是结构体中唯一没有被本函数处理的标志位。

4.4 并机通讯方案自适应

parallel_precept_change()(midware/interface/interface.c:239-291)由 task_modbus_rtu 在分支 1 调用,逻辑分四种情况:

  1. 已经是 USART3 方案 → 直接返回,不做任何切换(把 USART3 当作并机的最终优选方案)。
  2. 1 小时强制超时:abs(OSTimeGet() - u32_precept_change_recv_tick) >= 3600000(1 h)时,无条件置 u8_run_machine_precept = PARA_COM_TYPE_CAN 并置 u8_precept_change_recv_tick_flag = TRUE。这是"探测 1 h 仍未确定就用 CAN"的兜底。
  3. 光伏充电桩非单机模式(pvcharger_read_machine_mode() != PVCHARGR_SINGLE_E)→ 直接锁 CAN。
  4. 未配置方案 + 不依赖通讯卡:若 u8_comm_success_flag == TRUE(已通)→ 把运行时方案固化进配置;否则按 u8_parallel_mode(主机/从机)选择 PARALLEL_MSTER_CHECK_TIME(15 s)或 PARALLEL_SLAVE_CHECK_TIME(30 s)作为切换节拍,在 PARA_COM_TYPE_UART4 与 PARA_COM_TYPE_USART3 之间乒乓切换,并每轮把 u8_comm_success_flag = FALSE。
  5. 未配置方案 + 依赖通讯卡 → 直接锁 CAN,等 u8_comm_success_flag 置位后固化。

这段代码本质是"开机把并机口当两个串口轮流试,谁通了就用谁",而 CAN 是所有探测失败的兜底。它只改 g_t_parallel_total_machine_network.u8_run_machine_precept(运行时方案),通过 parallel_set_config_machine_precept() 落到配置。

4.5 Sunspec 分支

task_modbus_rtu 启动时若 g_t_modbus_config.u8_sunspec_en == TRUE,先 sunspec_modbus_data_init()(midware/interface/interface.c:302-305)。运行中在分支 4 收到 Modbus 帧后,若 Sunspec 使能且 sunspec_refresh_flag_get() != 0,刷新 Sunspec 数据模型(interface.c:369-371)。Sunspec 的具体实现位于 midware/interface/modbus/sunspec_business.c(sunspec_modbus_data_init 在 sunspec_business.c:1043,sunspec_modbus_data_refresh 在 sunspec_business.c:1173)。

4.6 全局变量读写一览

变量读/写位置
g_t_config_save_flag.*读+清interface.c:57-128;另外在 interface.c:325、338 写 u8_set_modbus_config_flag
g_t_inv_info.t_run_info.u8_alphaess_run_mode读interface.c:172
g_t_inv_info.t_config_data.u8_parallel_en读interface.c:309
g_t_inv_info.t_config_data.u8_parallel_com_type_can读interface.c:255、282
g_t_inv_info.t_config_data.u8_parallel_mode读interface.c:262
g_t_logic_info.t_config_data.Generator读+写interface.c:186 读;interface.c:188 写 g_t_logic_info.u8_generator_on
g_t_parallel_total_machine_network.*读+写interface.c:244-289(4 个字段:u32_precept_change_recv_tick、u8_precept_change_recv_tick_flag、u8_run_machine_precept、u8_comm_success_flag、u32_precept_adaptive_tick)
g_t_modbus_config.u8_sunspec_en / u8_modbus_baud_rate读+写interface.c:302、320-322、333-335、369
g_t_uart3_rcv_sem信号量 Pendinterface.c:364
g_t_task_run_status写(仅 DEBUG)interface.c:228、376

5. 对外接口

本文件导出的函数与全局变量:

名称类型供谁调用
task_interface_parse函数(全局)user/main.c:222 创建任务;声明在 midware/interface/interface.h:19
task_modbus_rtu函数(全局)user/main.c:288 创建任务;声明在 midware/interface/interface.h:21
query_parameter_save_tags函数(全局,无声明)仅 interface.c:223 自用,无跨文件引用(grep 全工程无其它引用)
parallel_precept_change函数(全局,无声明)仅 interface.c:313 自用,无跨文件引用

本文件未导出任何全局变量(/**** Globales ****/ 段为空)。

本文件调用的外部函数/变量:

名称来源模块
drm_init / drm_mode_judgemidware/interface/drm/drm.c(经 drm.h)
Handle_Dry_Node_Logicmidware/interface/dry_node/dry_node.c(经 dry_node.h)
handle_modbus_ret_recv_data / g_t_modbus_config / g_t_config_save_flagmidware/interface/modbus/modbus.c
sunspec_modbus_data_init / sunspec_modbus_data_refresh / sunspec_refresh_flag_getmidware/interface/modbus/sunspec_business.c
save_job_postdata/system_config/system_config.c:2688
ems_set_fault_bitdata/data.c:338
epo_read_config_enable / epo_read_signal_status / epo_set_protect_enable / epo_read_protect_enable / epo_set_signal_statuslogic/logic.c:3379 / 3435 / 3391 / 3411 / 3423
inverter_shutdown / inverter_startup / inverter_read_comm_flagmidware/inverter/inverter.c:4137 / 4125 / 4022
bms_set_shutdownmidware/bms/bms.c:2983
parallel_read_config_machine_precept / parallel_set_config_machine_preceptmidware/inverter/parallel.c:2232 / 2217
pvcharger_read_machine_mode / pvcharger_send_host_machine_infomidware/inverter/pvcharger.c
get_evc_enable_flag / evc_controlmidware/evc/evc.c:1043 / 958
get_evc_ii_enable_flag / evc_ii_controlmidware/evc/evc_ii.c:1969 / 1716
is_upgrade_evc / evc_upgrad_handlemidware/evc/evc_upgrade.c:755 / 433
Set_Status_Is_Updated_From_Servernet/upd_download.c:359
uart3_inithardware/usart3/usart3.c:35
g_t_uart3_rcv_sem / OSTimeGet / OSTimeDlyHMSM / OSSemPend / absuCOS-II 与 user/main.c:56

6. 依赖关系

依赖(本文件 include 或使用的外部模块)

interface.c:13-29 依次 include:interface.h、includes.h、drm.h、dry_node.h、modbus.h、main.h、system_config.h、inverter.h、evc.h、evc_ii.h、evc_download.h、evc_upgrade.h、usart3.h、logic.h、bms.h、data.h、sunspec_business.h。

即依赖 DRM、干接点、Modbus/Sunspec、系统配置存储、逆变器抽象层、两代充电桩驱动与升级、USART3 驱动、逻辑层、BMS、数据层。

被依赖(哪些文件会用到它)

7. 备注与风险点

  1. 文件头注释错误:midware/interface/interface.c:2 写的是 @file :bms.c,是从 BMS 文件复制后未改,检索时会造成困扰。
  2. query_parameter_save_tags / parallel_precept_change 缺 static 也无声明:属于隐式全局符号,链接期无原型校验,若将来有重名函数不会被立即发现。建议加 static(当前无跨文件引用)或补声明。
  3. abs(s_u32_query_tick - OSTimeGet()) 使用有符号差值判断 5000 tick(midware/interface/interface.c:50):s_u32_query_tick 未初始化时为 0,首次上电若 OSTimeGet() 大于 5000 会立即执行一次(可接受)。但用 abs() 处理 u32 差值在 tick 回卷边界(约 49.7 天)附近行为依赖实现,属于潜在边界问题。同样的写法出现在 interface.c:244、262(这里差值最大 1 h,无实际风险)和 drm.c:77。
  4. EPO_SEND_SHUTDOWN 无出口状态:只要 EPO 信号保持,每轮都会 inverter_shutdown() + bms_set_shutdown(TRUE),并以 OSTimeDlyHMSM(0,0,0,500) 在任务内额外阻塞 500 ms,把 task_interface_parse 的实际周期从 500 ms 拉到约 1000 ms。这会连带减慢 DRM 判定(内部 1 s 节流)与干接点响应,属于耦合副作用。
  5. bms_set_shutdown(TRUE) 没有配对恢复:EPO_SEND_STARTUP 只调 inverter_startup() 并清故障位,BMS 关机状态保持,代码注释也承认"电池需手动开启",属于有意设计但易被误读为 bug。
  6. 分支 4 依赖 100 ms 信号量超时:OSSemPend(..., 100, &err) 调用后未清除信号量计数残留,若一帧数据触发多次 OSSemPost(hardware/usart3/usart3.c:123 在中断里 Post),可能出现"多 Post 单 Pend"导致下一轮 Pend 立即返回而缓冲区里没有新帧。handle_modbus_ret_recv_data() 内部应有长度/CRC 校验兜底(在 modbus.c:8389),但本文件层无法保证。
  7. 波特率被强制改写:分支 2/3 一旦进入就把 u8_modbus_baud_rate 改成 9600 并触发落盘(interface.c:320-326、333-339),意味着用户通过 HMI/云端设置的其它波特率在接入充电桩时会被静默覆盖并持久化,退出充电桩模式后不会自动还原。
  8. #if (INVERTER_TYPE == ALPHAESS_TYPE) 在本快照中不成立:midware/inverter/inverter.h:172 将 INVERTER_TYPE 定义为 INVERTER_NULL(0),故 interface.c:142-153 的 30 s 上电宽限期与 s_u32_tick_cond_start 变量均被条件编译剔除;同样 dry_node.c 中多处 INVERTER_TYPE == ALPHAESS_TYPE 的运行时比较恒为假(见 dry_node.c 文档)。分析/调试时不能按"已启用 ALPHAESS 机型"来理解这些分支。
  9. 并机 1 h 兜底阈值是魔法数字:3600000 直接写在 interface.c:244,未定义具名宏;PARALLEL_MSTER_CHECK_TIME / PARALLEL_SLAVE_CHECK_TIME 有宏但单位靠注释说明。
  10. 任务内共享变量无保护:g_t_parallel_total_machine_network 同时被本文件的 parallel_precept_change() 与 midware/inverter/parallel.c、parallel_comm/* 读写,且跨任务/中断(CAN/SCI 接收),本文件未使用任何临界区或互斥量,存在读改写竞态风险。