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_handle | static | EPO 紧急停机三态机:检测 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 路优先级分支 的循环:
| 优先级 | 条件 | 行为 | 延时 |
|---|---|---|---|
| 1 | u8_parallel_en == TRUE 且 并机方案为 USART3 或 NONE 且 u8_precept_change_recv_tick_flag == FALSE | parallel_precept_change() | 100 ms |
| 2 | pvcharger_read_machine_mode() == PVCHARGR_HOST_E | 强制 9600 波特率 + pvcharger_send_host_machine_info() | 100 ms |
| 3 | get_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 ─────────────────────────────┘
- EPO_IDLE 分支(
midware/interface/interface.c:157-179):先判epo_read_config_enable();配置使能后再读epo_read_signal_status()。若信号有效(紧急停机输入触发)→ems_set_fault_bit(24, TRUE)置 24 号故障位、epo_set_protect_enable(TRUE)置保护使能、迁移到EPO_SEND_SHUTDOWN。若信号无效但epo_read_protect_enable()为真(说明此前进过停机保护),且此时逆变器运行模式g_t_inv_info.t_run_info.u8_alphaess_run_mode == 1且inverter_read_comm_flag()通讯正常 → 迁移到EPO_SEND_STARTUP自动恢复。 - EPO_SEND_SHUTDOWN 分支(
midware/interface/interface.c:180-190):没有出口(不改变状态),因此每 500 ms 会重复执行:inverter_shutdown()发逆变器关机命令 →OSTimeDlyHMSM(0,0,0,500)阻塞 500 ms →bms_set_shutdown(TRUE)→ 若柴油机使能则强制g_t_logic_info.u8_generator_on = FALSE。即只要 EPO 信号还在,就一直周期性重复下关机指令,属于"持续压制"而非"单次触发"设计。注意该状态下的额外 500 ms 延时会让task_interface_parse的实际周期变为 1000 ms。 - EPO_SEND_STARTUP 分支(
midware/interface/interface.c:191-197):inverter_startup()→ 清 24 号故障位 →epo_set_protect_enable(FALSE)→ 回EPO_IDLE。注释"电池需手动开启"说明 BMS 侧不自动解除关机(bms_set_shutdown(TRUE)没有对应的FALSE调用)。 - default 分支:状态越界时兜底回
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_flag | SYSTEM_BASE_PARA_PAGE |
u8_set_ui_config_flag | SYSTEM_BASE_PARA_PAGE(与上一项同页) |
u8_set_logic_config_flag | LOGIC_CONFIG_PAGE |
u8_set_logic_config_flag2 | LOGIC_CONFIG_PAGE2 |
u8_set_logic_config_flag3 | LOGIC_CONFIG_PAGE3 |
u8_set_meter_config_flag | METER_CONFIG_PAGE |
u8_set_battery_config_flag | BATTERY_CONFIG_PAGE |
u8_set_inverter_config_flag | INVERTER_CONFIG_PAGE |
u8_set_net_para_flag | NET_PARA_PAGE |
u8_set_modbus_config_flag | MODBUS_CONFIG_PAGE |
u8_set_ct_config_flag | CT_CONFIG_PAGE |
u8_set_system_custom_info_flag | STSTEM_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 调用,逻辑分四种情况:
- 已经是 USART3 方案 → 直接返回,不做任何切换(把 USART3 当作并机的最终优选方案)。
- 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"的兜底。 - 光伏充电桩非单机模式(
pvcharger_read_machine_mode() != PVCHARGR_SINGLE_E)→ 直接锁 CAN。 - 未配置方案 + 不依赖通讯卡:若
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。 - 未配置方案 + 依赖通讯卡 → 直接锁 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 | 信号量 Pend | interface.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_judge | midware/interface/drm/drm.c(经 drm.h) |
Handle_Dry_Node_Logic | midware/interface/dry_node/dry_node.c(经 dry_node.h) |
handle_modbus_ret_recv_data / g_t_modbus_config / g_t_config_save_flag | midware/interface/modbus/modbus.c |
sunspec_modbus_data_init / sunspec_modbus_data_refresh / sunspec_refresh_flag_get | midware/interface/modbus/sunspec_business.c |
save_job_post | data/system_config/system_config.c:2688 |
ems_set_fault_bit | data/data.c:338 |
epo_read_config_enable / epo_read_signal_status / epo_set_protect_enable / epo_read_protect_enable / epo_set_signal_status | logic/logic.c:3379 / 3435 / 3391 / 3411 / 3423 |
inverter_shutdown / inverter_startup / inverter_read_comm_flag | midware/inverter/inverter.c:4137 / 4125 / 4022 |
bms_set_shutdown | midware/bms/bms.c:2983 |
parallel_read_config_machine_precept / parallel_set_config_machine_precept | midware/inverter/parallel.c:2232 / 2217 |
pvcharger_read_machine_mode / pvcharger_send_host_machine_info | midware/inverter/pvcharger.c |
get_evc_enable_flag / evc_control | midware/evc/evc.c:1043 / 958 |
get_evc_ii_enable_flag / evc_ii_control | midware/evc/evc_ii.c:1969 / 1716 |
is_upgrade_evc / evc_upgrad_handle | midware/evc/evc_upgrade.c:755 / 433 |
Set_Status_Is_Updated_From_Server | net/upd_download.c:359 |
uart3_init | hardware/usart3/usart3.c:35 |
g_t_uart3_rcv_sem / OSTimeGet / OSTimeDlyHMSM / OSSemPend / abs | uCOS-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、数据层。
被依赖(哪些文件会用到它)
user/main.c:24includeinterface.h并创建两个任务 —— 这是唯一的外部使用者。- 无其它
.c文件引用本文件的函数。
7. 备注与风险点
- 文件头注释错误:
midware/interface/interface.c:2写的是@file :bms.c,是从 BMS 文件复制后未改,检索时会造成困扰。 query_parameter_save_tags/parallel_precept_change缺static也无声明:属于隐式全局符号,链接期无原型校验,若将来有重名函数不会被立即发现。建议加static(当前无跨文件引用)或补声明。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。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 节流)与干接点响应,属于耦合副作用。bms_set_shutdown(TRUE)没有配对恢复:EPO_SEND_STARTUP只调inverter_startup()并清故障位,BMS 关机状态保持,代码注释也承认"电池需手动开启",属于有意设计但易被误读为 bug。- 分支 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),但本文件层无法保证。 - 波特率被强制改写:分支 2/3 一旦进入就把
u8_modbus_baud_rate改成 9600 并触发落盘(interface.c:320-326、333-339),意味着用户通过 HMI/云端设置的其它波特率在接入充电桩时会被静默覆盖并持久化,退出充电桩模式后不会自动还原。 #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 机型"来理解这些分支。- 并机 1 h 兜底阈值是魔法数字:
3600000直接写在interface.c:244,未定义具名宏;PARALLEL_MSTER_CHECK_TIME/PARALLEL_SLAVE_CHECK_TIME有宏但单位靠注释说明。 - 任务内共享变量无保护:
g_t_parallel_total_machine_network同时被本文件的parallel_precept_change()与midware/inverter/parallel.c、parallel_comm/*读写,且跨任务/中断(CAN/SCI 接收),本文件未使用任何临界区或互斥量,存在读改写竞态风险。