dry_node.h
源文件路径:
midware/interface/dry_node/dry_node.h所属模块:midware/interface/dry_node(干接点接口) 代码行数:211
1. 文件职责
本文件是干接点(Dry Contact / 继电器接口)模块的类型与接口定义中心,为 EMS 定义两路干接点的全部配置结构、枚举与 IO 访问宏。它向外导出两块数据:一是持久化配置表 STDryNodeTab STDryNodeInfo(带 CRC 与 #pragma pack(1),直接对应 Flash 一个扇区的一份镜像,由 net/ems.c、net/server.c、modbus.c 读写),二是入口函数 dry_node_io_init() 与 Handle_Dry_Node_Logic()。模块的语义是"把储能的 SOC / 馈网功率 / 时间段 / UPS 状态翻译成两路继电器输出",用于负载投切、SG-Ready 热泵联动、柴油机启停、EPO 复归等外部联动。头文件本身不含逻辑,只有类型、宏和断言。
2. 关键数据结构与宏
| 名称 | 类型(结构体/枚举/宏/联合体) | 说明 |
|---|---|---|
DEBUG_DRY_NODE | 宏 | 调试开关,定义为 (0)(dry_node.h:18);置 1 时 dry_node.c 会输出大量 printf 分支编号 |
DRY_NODE1_ON / DRY_NODE1_OFF | 宏 | 干接点 1 闭合/断开,展开为 do0_output_on() / do0_output_off()(dry_node.h:20-21) |
DRY_NODE2_ON / DRY_NODE2_OFF | 宏 | 干接点 2 闭合/断开,展开为 do1_output_on() / do1_output_off()(dry_node.h:26-27) |
DI_1_DRY_CONTACT | 宏 | 干接点 1 当前继电器状态回读,展开为 do0_output_bit_get()(dry_node.h:23) |
DI_1_ACCESS_POINT | 宏 | DI0 输入点电平,展开为 di0_input_bit_get()(dry_node.h:24);用于 EPO 外部信号采集 |
DI_2_DRY_CONTACT / DI_2_ACCESS_POINT | 宏 | 干接点 2 的继电器回读 / DI1 输入点电平(dry_node.h:29-30) |
ENDryNodeDo | 枚举 | 干接点动作标记:NODE_DONE_OPEN = 0x01、NODE_DONE_CLOSE = 0x02、NODE_DONE_NONE = 0x03(dry_node.h:32-37) |
ENDryNodeStatus | 枚举 | 继电器实际状态:NODE_OPEN = 0x00、NODE_CLOSE = 0x01(dry_node.h:39-43) |
ENDryNodeMode | 枚举 | 控制模式:MODE_ON = 0x01、MODE_OFF = 0x02、MODE_AUTO = 0x03(dry_node.h:45-50) |
ENDryNodeCharge | 枚举 | SOC / 馈网比较模式:AT_LEAST = 0x01、AT_MOST = 0x02、AT_DISABLE = 0x03(dry_node.h:52-57) |
ENDryNodeNum | 枚举 | 通道编号:DRY_NODE_ONE = 0x01、DRY_NODE_TWO = 0x02(dry_node.h:59-63) |
DRY_NODE_II_MODE_E | 枚举 | 新版(II 版)逻辑的 13 个运行模式:DRY_NODE_NONE_MODE、DRY_NODE_UPS_MODE、DRY_NODE_SGREADY_MODE、TIME_MODE_SWITCH_ON、TIME_MODE_SWITCH_OFF、AUTO_MODE_SOC_ON_UPS_ON_AT_LEAST … AUTO_MODE_SOC_OFF_FEED_ON_AT_MOST(dry_node.h:65-80)。注意前三个枚举值在 dry_node.c 中从未被赋值使用 |
DRY_NODE_MODE_STATUS_E | 枚举 | 新版逻辑的延时状态机:DRY_NODE_STATUS_NONE、DRY_NODE_DELAY_STATUS、DRY_NODE_DURATION_STATUS、DRY_NODE_PAUSE_STATUS、DRY_NODE_STATUS_MAX(dry_node.h:82-89) |
DRY_NODE_II_TAB_T | 结构体 | 新版逻辑的运行时工作表(dry_node.h:93-131,#pragma pack(1)):把 STDryNodeTab 的原始配置与本周期采集的 u16_sys_workmode、u8_bat_soc、s32_meter_actpower_t 以及延时/持续/暂停 tick 与状态打包,供 dry_node.c 的 II 版流水线使用 |
STDryNodeTab | 结构体 | 干接点持久化配置表(dry_node.h:133-199,#pragma pack(1),带 u16_head 头与 u16_crc_code 尾),长度必须整除 Flash 扇区 |
STATIC_ASSERT(ASSERT_FLASH_SECTOR_SIZE % sizeof(STDryNodeTab) == 0) | 宏 | 编译期断言,保证配置表能整份塞进 Flash 扇区(dry_node.h:201) |
STDryNodeInfo | 全局变量(extern) | 干接点配置表的唯一实例,dry_node.h:205 声明,dry_node.c:29 定义 |
ASSERT_FLASH_SECTOR_SIZE | 宏 | Flash 扇区大小,定义于 midware/debug/debug.h:19(#define ASSERT_FLASH_SECTOR_SIZE FLASH_SECTOR_SIZE) |
STDryNodeTab 字段分组(dry_node.h:135-198):
| 组 | 字段 | 含义 |
|---|---|---|
| 头 | u16_head | 数据头,用于校验 Flash 内容有效性 |
| 通道 1 配置 | u8Channel1、u8ControlMode1、u8StartTime1A/1B、u8EndTime1A/1B、u8Date1、u8ChargeSOC1、u8ChargeMode1、u8UPS1、u32SwitchOn1、u32SwitchOff1、u8Delay1、u8Duration1、u8Pause1 | 使能、模式、两段时间、周掩码、SOC/馈网阈值与延时参数 |
| 通道 1 运行态 | u8Status1、u32DelayTick1、u32DurationTick1、u32PauseTick1、u8RelayDone1 | 会在掉电前被保存,属于"半运行态"字段 |
| Modbus 映射 | u8_modbus_dry1、u8_modbus_dry2 | Modbus 侧直接控制干接点用 |
| 通道 2 | 与通道 1 同构的一整组 ...2 字段 | 第二路干接点 |
| SG-Ready 扩展 | u8FeedMode1、u16FeedValue1、u8SGEnable1、u16StartTimeM1A/1B、u16EndTimeM1A/1B(及 ...2) | 新版分钟级时间段 + 馈网功率阈值 + SG-Ready 使能 |
| 尾部 | u8_reserve[28]、u16_crc_code | 预留区与 CRC |
3. 函数清单
本文件只含函数声明,无函数定义。
| 函数名 | 作用域(static/全局) | 说明(一句话) |
|---|---|---|
dry_node_io_init | 全局(声明) | 干接点 IO 初始化,声明于 dry_node.h:208,在 dry_node.c 中被 include 但未见实现 |
Handle_Dry_Node_Logic | 全局(声明) | 干接点总入口,声明于 dry_node.h:209,由 midware/interface/interface.c:221 每 500 ms 调用 |
4. 核心逻辑解析
4.1 两代配置表的共存
本头文件同时保留了两套"同一件事"的表示:
- 老版字段(
u8StartTime1A/u8EndTime1A,u8小时粒度,u8ChargeSOC1百分比,u32SwitchOn1/u32SwitchOff1瓦特)——供Dry_Node1_Logic()/Dry_Node2_Logic()使用。 - 新版字段(
u16StartTimeM1A/u16EndTimeM1A,u16分钟粒度:"M" 指 Minute;u8FeedMode1/u16FeedValue1馈网功率阈值;u8SGEnable1SG-Ready 使能)——供Dry_Node_Logic_ii()流水线使用。
两套字段在同一个 STDryNodeTab 里并排存放,运行时由 dry_node.c:1899 的判据决定走哪一套:
if (STDryNodeInfo.u8FeedMode1 != 0 || STDryNodeInfo.u8FeedMode2 != 0)
Dry_Node_Logic_ii(); // 新版:馈网/SG-Ready 模式
else {
Dry_Node1_Logic(); // 老版:时间 / SOC / UPS 模式
Dry_Node2_Logic();
}
即 u8FeedMode1/2 充当"新老版本仲裁开关":一旦云端或 Modbus 下发过馈网模式(非 0),就整机切到新版逻辑,老版两个函数不再执行。这是一个典型的"版本遗留"式双实现共存。
4.2 结构体对齐与 Flash 存储约束
STDryNodeTab 与 DRY_NODE_II_TAB_T 都用了 #pragma pack(1)(dry_node.h:91 开启,dry_node.h:202 关闭),保证结构在 RAM 中与 Flash/通讯线上的字节布局一致,可直接 memcpy 到 Flash 或直接按字节发给云端。代价是:
STDryNodeTab中u32SwitchOn1(4 字节)等字段在 pack(1) 下不再自然对齐,GD32F407(Cortex-M4)允许非对齐访问,但没有性能优势;STATIC_ASSERT(ASSERT_FLASH_SECTOR_SIZE % sizeof(STDryNodeTab) == 0)是防止结构体尺寸变化导致 Flash 页布局错乱的关键保护——任何新增字段都可能让这个断言编译失败,这是有意设计。
DRY_NODE_II_TAB_T 末尾的 u16_sys_workmode、u8_bat_soc、s32_meter_actpower_t 是每周期刷新的采集量却被放在 pack(1) 的"表"结构里,命名上带 _tab 后缀但实为运行态工作区,容易被误当成持久化配置。
4.3 宏与硬件的绑定关系
dry_node.h:20-30 的 8 个宏把逻辑层与 hardware/gpio/gpio.h 绑定:
| 宏 | 展开 | 底层实现 |
|---|---|---|
DRY_NODE1_ON / DRY_NODE1_OFF | do0_output_on() / do0_output_off() | hardware/gpio/gpio.h:168-169 |
DI_1_DRY_CONTACT | do0_output_bit_get() | hardware/gpio/gpio.h:170(读 DO0 的输出寄存器回读) |
DI_1_ACCESS_POINT | di0_input_bit_get() | hardware/gpio/gpio.h:171 |
DRY_NODE2_ON / DRY_NODE2_OFF | do1_output_on() / do1_output_off() | hardware/gpio/gpio.h:173-174 |
DI_2_DRY_CONTACT | do1_output_bit_get() | hardware/gpio/gpio.h:175 |
DI_2_ACCESS_POINT | di1_input_bit_get() | hardware/gpio/gpio.h:176 |
命名上 DI_1_DRY_CONTACT 实际读的是 DO 的输出回读位(而非输入引脚),DI_1_ACCESS_POINT 才是真正的 DI 输入——名字里的 DI/DO 容易混淆。
4.4 声明与实现的不一致
dry_node_io_init()只在本头文件声明(dry_node.h:208),全工程没有任何.c文件实现它,也没有任何地方调用它(grep 全工程仅命中该声明)。属于死声明。- 头文件声明了
Handle_Dry_Node_Logic,也声明了extern STDryNodeTab STDryNodeInfo,但没有导出dry_node.c中实际存在的其它函数:Get_Dry_Node_Status、Get_Week_By_Date、Judge_By_Time、Dry_Node1_Logic、Dry_Node2_Logic、update_dry_node_data、check_dry_node_mode、judge_by_week_time、deal_dry_node_mode、deal_dry_node_tick、driver_dry_node、handle_outside_npe_function、Dry_Node_Logic_ii,以及两个全局变量g_t_dry_node_ii_info1/g_t_dry_node_ii_info2。这些符号全部是隐式全局(无static、无原型),无跨文件引用。
5. 对外接口
本文件导出的函数与全局变量:
| 名称 | 类型 | 供谁调用 |
|---|---|---|
Handle_Dry_Node_Logic | 函数 | midware/interface/interface.c:221(仅此一处调用) |
dry_node_io_init | 函数声明 | 无调用者、无实现(死声明) |
STDryNodeInfo | 全局变量 | data/system_config/system_config.c、midware/interface/dry_node/dry_node.c、midware/interface/modbus/modbus.c、net/ems.c、net/server.c(经 dry_node.h 引用的所有 10 个 include 者) |
| 13 个枚举 / 4 个结构体 / 8 个 IO 宏 | 类型与宏 | 经 dry_node.h 引用的所有文件 |
本文件调用的外部函数/变量:
| 名称 | 来源模块 |
|---|---|
do0_output_on/off、do0_output_bit_get、di0_input_bit_get、do1_output_on/off、do1_output_bit_get、di1_input_bit_get | hardware/gpio/gpio.h(本头文件 dry_node.h:14 include) |
ASSERT_FLASH_SECTOR_SIZE | midware/debug/debug.h:19(同文件还有 FLASH_SECTOR_SIZE) |
STATIC_ASSERT | 工程通用断言宏,midware/debug/debug.h:16(dry_node.h:15 include) |
6. 依赖关系
依赖(本文件 include)
dry_node.h:13-15:chip.h、gpio.h、debug.h。ASSERT_FLASH_SECTOR_SIZE 与 STATIC_ASSERT 都由 debug.h 提供,不需要 system_config.h。
被依赖(哪些文件会用到它)
全工程共 10 个文件 include dry_node.h:
| 文件 | 用途 |
|---|---|
midware/interface/dry_node/dry_node.c:15 | 本模块实现 |
midware/interface/interface.c:16 | 调用 Handle_Dry_Node_Logic() |
data/system_config/system_config.c:14 | STDryNodeInfo 的 Flash 读写与 CRC 校验 |
midware/interface/modbus/modbus.c:38 | Modbus 寄存器映射读写 |
net/ems.c:26 | 云端上报/下发 |
net/server.c:27 | 本地 Web 服务器按字段名读写(net/server.c:3969-3997 有完整字段表) |
logic/generator.c:16 | 柴油机联动 |
midware/bms/bms.c:26 | BMS 侧联动 |
midware/debug/board_test.c:30 | 产测板级调试 |
system/board_init/board_init.c:42 | 上电初始化 |
7. 备注与风险点
dry_node_io_init()是死声明:只声明、无实现、无调用(dry_node.h:208),应删除或补实现,否则调用者会拿到链接错误。DRY_NODE_II_MODE_E有 3 个枚举值形同虚设(dry_node.h:67-69):DRY_NODE_UPS_MODE与DRY_NODE_SGREADY_MODE在dry_node.c中从未被赋给u8_cur_dry_node_mode,是纯设计残留;DRY_NODE_NONE_MODE仅在dry_node.c:1278(干接点未使能时)被赋值,但deal_dry_node_mode()(dry_node.c:1406-1526)与deal_dry_node_tick()(dry_node.c:1548-1654)都没有为它写 case,一律落到default分支强制NODE_DONE_OPEN,因此它的行为与"未知模式"完全相同,None语义没有独立实现。- 命名不一致:
STDryNodeTab(老式匈牙利/裸命名)与DRY_NODE_II_TAB_T(下划线全大写)两种风格并存;DRY_NODE_STATUS_MAX(无_E后缀)与其它..._E后缀枚举混杂;u8_modbus_dry1/2是全表唯一使用小写下划线风格的字段。 - pack(1) 下的非对齐多字节字段:
STDryNodeTab在 pack(1) 下u32SwitchOn1、u16FeedValue1等字段的偏移完全由前序u8数量决定,任何字段增删都会移动后续所有字段,与 Flash 中已存数据、net/server.c的字段表、Modbus 寄存器映射三处产生联动破坏。STATIC_ASSERT只保护了"总尺寸整除扇区",不保护字段偏移,缺少版本号/字段偏移固化机制。 u8Status1/2、u32DelayTick1/2、u32DurationTick1/2、u32PauseTick1/2、u8RelayDone1/2是运行态却存在持久化表里:这些字段会被写入 Flash 并在下电恢复时读回,而 tick 值是OSTimeGet()的绝对 tick,重启后 tick 归零,恢复出来的u32DelayTick1会变成"未来时间",导致首次判断时OSTimeGet() - u32DelayTick1出现巨大的无符号差值回绕——dry_node.c:1573/1603/1632有专门回绕分支处理,但依旧依赖正确性,属于高风险设计。u8_reserve[28]手算预留:预留区长度 28 是为凑 Flash 扇区整除而人工计算的结果(dry_node.h:197),一旦增删字段必须重新手算,建议改为编译期按剩余空间推导。DEBUG_DRY_NODE是"分支编号打印"式调试:dry_node.c全文件共 52 × 2 处printf("Dry_Node1_Logic...N"),一旦打开会让 500 ms 周期的任务严重超时,不宜在生产构建中启用。STDryNodeTab的u16_head/u16_crc_code无版本/长度字段:Flash 格式升级时无法识别旧版布局,只能靠 CRC 失败后整表复位。