system_config.c
源文件路径:
data/system_config/system_config.c所属模块:data/system_config(系统配置持久化:出厂值初始化、上电恢复、异步保存调度) 代码行数:3379
1. 文件职责
本文件是 EMS 全部配置数据的"落盘与还原中枢",负责三件事:
一是出厂值初始化。24 个 *_info_init() 函数把每一类配置(系统基础信息、逻辑配置及三个时间控制扩展、电表、电池及电池扩展、逆变器、CT 配置与 CT 电量、能量统计、干接点、网络、Modbus、EVC 一代/二代、自定义配置与其备份、系统备份)填成出厂默认值、算好 Modbus CRC16 后整块写进内部 Flash 的 EEPROM 模拟区(页 4071~4095,每页 4 KB)。
二是上电恢复。system_info_restore()(system_config.c:2371)在 system/board_init/board_init.c:250 被调用,逐页读取并校验 CRC,成功则灌进对应的 RAM 全局变量,失败则回退调用同页的 *_init() 出厂值;同时处理跨页的兼容修补(老版本字段迁移)和"Flash 备份页 4095 与 4071/4077/4075 互救"逻辑。
三是异步保存调度。它创建 uCOS-II 消息队列 SaveJob_Q 与内存分区 SaveJobMsgBuffer(save_job_init,:2602),对外提供 save_job_post(page) 投递"某页需要保存"的任务(带去重位图),由 data/data.c 的 task_system_data 任务调用 system_config_data_save()(:2894)从队列取任务并真正写 Flash,写前重算 CRC,写后清业务侧的"配置已改"标志并更新 EEPROM 读/写故障标志。
被谁驱动:初始化/恢复由 board_init 与 data.c 的复位流程驱动;保存由全工程 131 处 save_job_post 调用点(hmi/key.c、net/server.c、logic/logic.c、midware/* 等)驱动,最终由 task_system_data 每 500 ms 拉一次队列执行。
2. 关键数据结构与宏
| 名称 | 类型(结构体/枚举/宏/联合体) | 说明 |
|---|---|---|
SaveJob_Q | 全局变量 OS_EVENT * | 保存任务消息队列,容量 MESSAGES_SIZE(=25),save_job_init 中 OSQCreate,事件名 "SaveJob_Q"(:38、:2606) |
SaveJobMsgGrp | 全局数组 void *[MESSAGES_SIZE] | OSQCreate 的队列控制块存储(:39、:2606) |
SaveJobMsgBuffer | 全局变量 OS_MEM * | 保存任务报文的内存分区,块大小 sizeof(SAVE_JOB_T)(=11 字节),块数 25(:40、:2618) |
SaveJobMsgMem | 全局数组 SAVE_JOB_T[MESSAGES_SIZE] | 内存分区的实际存储(:41、:2618) |
g_t_system_base_info | 全局变量 SYSTEM_BASE_PARA_T | 4071 页 RAM 镜像:SN、语言、用户密码、OEM 标志、功能位 1/2、E-Link 密码、安装日期、上报间隔(:43) |
g_system_custom_info | 全局变量 STSTEM_CUSTOM_PARA_T | 4088 页 RAM 镜像:显示/登录/socket 服务器配置(:44) |
g_system_function_info | 全局变量 SYSTEM_FUNCTION_PARA_T | 运行期功能能力表,本文件只声明与比较,填充在 midware/auxiliary/system_aux.c(:45) |
g_system_msg_page_id | 全局变量 SYSTEM_MESSAGES_PAGE_ID_U | 保存任务去重位图,初值 {0}(:46) |
s_system_backup_info | static 变量 SYSTEM_BACKUP_TABLE_T | 4095 页的 RAM 镜像(SN/MAC/逆变器型号/服务器点/IP 配置)(:48) |
s_system_custom_backup_info | static 变量 STSTEM_CUSTOM_PARA_T | 4090 页的 RAM 镜像,g_system_custom_info 的备份(:49) |
SAVE_JOB_T | 结构体 | 队列报文:page_id(位号)、page(页号)、tim_s(投递时刻)、tim(耗时 tick),见 system_config.h:66-72 |
MESSAGES_SIZE | 宏 | 25(system_config.h:49),队列长度 / 内存块数 / save_job_post 位号上界 |
SYSTEM_STARTE_PAGE | 宏 | 4071(system_config.h:22),页号→位号换算基准 |
CONFIG_INFO_HEAD | 宏 | 0xAA55(system_config.h:20),配置块头标识 |
DEBUG_SYSTEM_CONFIG | 宏 | 0(system_config.h:18),打开后各函数打印 CRC 与 ShowData() 十六进制转储 |
3. 函数清单
| 函数名 | 作用域(static/全局) | 说明(一句话) |
|---|---|---|
system_sn_init | static | 生成默认 SN、用户密码、英文语言并写入 4071 页(:89) |
net_info_init | 全局(原型误写成 static,见风险点 1) | 网络信息出厂初始化,保留 MAC 与服务器点后写入 4077 页(:130) |
ct_config_info_init | static | CT 配置出厂初始化:A/B/C 电压 2 点、电网/光伏功率各 5 点校准系数置 10000,写 4080 页(:176) |
ui_info_init | 全局 | 把 LCD 里的密码与语言写回 4071 页(:256) |
logic_config_info_init | 全局 | 逻辑配置主表出厂值,写 4072 页(:291) |
logic_config_extend1_info_init | 全局 | 时间控制扩展 1(14 段充放电)出厂值,写 4091 页(:345) |
logic_config_extend2_info_init | 全局 | 时间控制扩展 2 出厂值,写 4092 页(:382) |
logic_config_extend3_info_init | 全局 | 时间控制扩展 3 出厂值,写 4093 页(:419) |
meter_config_info_init | 全局 | 电表配置出厂值(BB 优先级 1/2/3),写 4073 页(:451) |
bat_config_info_init | 全局 | 电池配置出厂值,写 4074 页(:494) |
bat_extend_config_info_init | 全局 | 电池扩展配置出厂值,写 4087 页(:526) |
inverter_config_info_init | 全局 | 逆变器配置出厂值,保留逆变器型号、写硬件版本、安全法规 AS4777_2,写 4075 页(:557) |
ct_energy_info_init | 全局 | CT 累计电量清零并写 4081 页(:595) |
energy_info_init | 全局 | 能量统计清零并写 4076 页(:631) |
dry_node_info_init | 全局 | 干接点配置出厂值,写 4078 页(:662) |
net_ip_method_init | 全局 | 网络地址方式复位为 DHCP 并写 4077 页(:698) |
modbus_config_info_init | 全局 | Modbus 配置出厂值(地址、9600、Sunspec 使能、版本 1),写 4079 页并调 Sunspec 数据初始化(:729) |
evc_config_info_init | 全局 | EVC 一代充电桩配置出厂值,写 4082 页(:772) |
evc_setting_info_init | 全局 | EVC 充电参数出厂值(优先级 1、三相、160 A),写 4083 页(:812) |
evc_ii_single_config_info1_init | 全局 | EVC 二代第 1 路出厂值,写 4084 页(:851) |
evc_ii_single_config_info2_init | 全局 | EVC 二代第 2 路出厂值,写 4085 页(:890) |
evc_ii_single_config_info3_init | 全局 | EVC 二代第 3 路出厂值,写 4086 页(:929) |
evc_ii_single_config_info4_init | 全局 | EVC 二代第 4 路出厂值,写 4089 页(:968) |
system_custom_info_init | 全局 | 自定义配置出厂值,写 4088 页(:1007) |
system_custom_backup_info_init | 全局 | 由 g_system_custom_info 生成备份并写 4090 页(:1040) |
system_backup_info_init | 全局 | 以 EEPROM 现状为准生成 4095 页备份数据(SN/MAC/型号/服务器点/IP)(:1073) |
system_sn_restore | static | 读取并校验 4071 页,安装商密码降级为用户密码,恢复 g_t_system_base_info(:1128) |
logic_config_info_restore | static | 读取并校验 4072 页,恢复逻辑配置并同步 g_t_time_control_run.weekday_charge_discahrge_en,做 3 项字段兜底(:1185) |
logic_config_extend1_info_restore | static | 读取并校验 4091 页,恢复并把 14 段时间控制拷进 g_t_time_control_run(:1247) |
logic_config_extend2_info_restore | static | 读取并校验 4092 页,同上(:1297) |
logic_config_extend3_info_restore | static | 读取并校验 4093 页,恢复逻辑扩展 3(:1346) |
meter_config_info_restore | static | 读取并校验 4073 页,恢复电表配置及四项分相累计电量(:1392) |
bat_config_info_restore | static | 读取并校验 4074 页,恢复电池配置并同步到 t_run_info(含老版本字段清理)(:1443) |
bat_extend_config_info_restore | static | 读取并校验 4087 页,恢复 12 节电池单体的软件版本/平台/SN(:1509) |
inverter_config_info_restore | static | 读取并校验 4075 页,恢复逆变器配置(:1562) |
ct_energy_info_restore | static | 读取并校验 4081 页,恢复 CT 累计电量(:1607) |
energy_info_restore | static | 读取并校验 4076 页,恢复能量统计(:1670) |
net_info_restore | static | 读取并校验 4077 页,恢复 IP_Address(:1727) |
dry_node_info_restore | static | 读取并校验 4078 页,恢复干接点配置(:1773) |
modbus_config_info_restore | static | 读取并校验 4079 页,恢复 Modbus 配置(Sunspec 版本为 0 时强制走重初始化)(:1820) |
ct_config_info_restore | static | 读取并校验 4080 页,恢复 CT 校准表(老版本缺 B/C 相校准时补齐)(:1873) |
evc_config_info_restore | static | 读取并校验 4082 页,恢复 EVC 一代配置(:1954) |
evc_setting_info_restore | static | 读取并校验 4083 页,恢复 EVC 充电参数(:2001) |
evc_ii_single_config_info1_restore | static | 读取并校验 4084 页,恢复 EVC 二代第 1 路(:2047) |
evc_ii_single_config_info2_restore | static | 读取并校验 4085 页,恢复 EVC 二代第 2 路(:2094) |
evc_ii_single_config_info3_restore | static | 读取并校验 4086 页,恢复 EVC 二代第 3 路(:2141) |
evc_ii_single_config_info4_restore | static | 读取并校验 4089 页,恢复 EVC 二代第 4 路(:2187) |
system_custom_info_restore | static | 读取并校验 4088 页,失败时直接重建 4088 页(:2234) |
system_custom_backup_info_restore | static | 读取并校验 4090 页,失败时重建备份页(:2280) |
system_backup_info_restore | static | 读取并校验 4095 页,失败时以当前 EEPROM 内容重建(:2326) |
system_info_restore | 全局 | 上电恢复总调度:逐页恢复/回退出厂值 + 跨页备份修复(:2371) |
save_job_init | 全局 | 创建保存队列与内存分区,并设置事件名(:2602) |
atomic_check_and_set_bit | static inline | 用 __LDREXW/__STREXW 原子实现"测试并置位",已置位返回 1(:2637) |
atomic_clear_bit | static inline | 用 __LDREXW/__STREXW 原子清位(:2666) |
save_job_post | 全局 | 去重、取内存块、投递队列;失败时回退位图(:2688) |
save_job_pend | static | 队列取报文、累加耗时、清位图、归还内存块(:2740) |
get_backup_data_refresh_flag | static | 比较 4095 页镜像与当前 SN/MAC/型号/服务器点/IP 是否一致,不一致返回 TRUE(:2780) |
save_energy_data | 全局 | 一次性投递能量、CT 电量、电表三页保存任务(:2880) |
system_config_data_save | 全局 | 保存主循环:定时能量保存、备份页刷新检测、按页号分派写 Flash 与善后(:2894) |
合计 59 个函数(其中 static inline 2 个,static 28 个,全局 29 个)。
4. 核心逻辑解析
4.1 保存任务机制(队列 + 内存分区 + 原子去重位图)
同步对象:save_job_init()(:2602)用 OSQCreate(SaveJobMsgGrp, MESSAGES_SIZE) 建队列、OSMemCreate(SaveJobMsgMem, MESSAGES_SIZE, sizeof(SAVE_JOB_T), &err) 建内存分区,两者都以失败仅打印不返回错误的宽松方式处理;随后 OSEventNameSet / OSMemNameSet 设置调试名。该函数由 data/data.c:257 在任务启动时调用一次。
投递路径 save_job_post(u16 msg)(:2688):
g_u8_msg_id = msg - SYSTEM_STARTE_PAGE,把页号换算成 0..24 的位号。- 越界检查 + 去重:
(g_u8_msg_id > MESSAGES_SIZE) || (atomic_check_and_set_bit(...) != 0)即返回 0。位已被置说明该页已有排队中的任务,直接丢弃这次请求——这是"同一页最多一个待保存任务"的合并策略。 OSMemGet取报文块,失败则atomic_clear_bit回退位图并返回 0。- 填
page_id/page/tim_s = OSTimeGet(),OSQPost入队。队列满(OS_ERR_Q_FULL)时回退位图;成功则返回 1。
重试次数限制:atomic_check_and_set_bit 内部 l_u16_retry = 1000,__STREXW 失败最多重试 1000 次,返回 2 表示"重试用尽"。三个返回码(0 成功、1 已置位、2 重试失败)在 save_job_post 里被统一当成非零即失败处理(:2695)。
取用路径 save_job_pend(void *msg)(:2740):OSQPend(SaveJob_Q, 1, &err) 超时仅 1 个 tick(OS_TICKS_PER_SEC=1000,即 1 ms),因此是"准非阻塞"轮询;成功时把 page 写到调用者的 u16 变量、算出 tim = OSTimeGet() - tim_s 作为耗时统计、原子清位图、OSMemPut 归还内存块。
主循环 system_config_data_save()(:2894):
- 定时动作:
if ((OSTimeGet() - s_u32_energy_tick) > 720 * 1000)即每 720 s(12 分钟)调用一次save_energy_data(),同时处理 tick 回绕(s_u32_energy_tick > OSTimeGet()时重置基准)。 - 备份刷新:
get_backup_data_refresh_flag()为真 → 投递SYSTEM_DATA_BACKUP_PAGE;g_system_custom_info与备份镜像memcmp不等 → 投递STSTEM_CUSTOM_CONFIG_BACKUP_PAGE。 while (save_job_pend(&page))循环按page分派到 21 个case,每个 case 的公共套路是:(1)把运行时统计值同步进待写结构体(如 METER/CT_ENERGY 页把分相电量抄进 config 结构、ENERGY_INFO 页先调enenrgy_count_handle()刷新统计);(2)重算u16_crc_code = GetCrcCode_Modbus(..., size - 2);(3)最多写 3 次feeprom_write(),成功即 break;(4)清业务侧"配置已改"状态位(Set_Setup_Config_Status(FALSE)/Set_Setup_Config_Extend1/2_Status(FALSE));(5)按结果更新 EEPROM 软件写故障标志set_eeprom_fault_flag(get_eeprom_software_write_fault_flag(), page, !l_u8_res)。SYSTEM_BASE_PARA_PAGE分支额外做两件事:OEM 不是OEM_BW时把s8_sn[15]清零(:2935);从 LCD 取回密码与语言覆盖(:2937-2938)。NET_PARA_PAGE分支把 IP 模式/地址/掩码/网关回写LCD_RTDB(:3078-3100)。STSTEM_CUSTOM_CONFIG_PAGE、STSTEM_CUSTOM_CONFIG_BACKUP_PAGE用feeprom_init_write()(先擦扇区再写,覆盖语义)而不是feeprom_write()(追加槽位语义),因为自定义配置页需要"整页唯一副本"。default分支把l_u8_save_flag = FALSE。- 掉电保护:每处理完一页,若
l_u8_save_flag == TRUE且gpio_input_bit_get(GPIOG, GPIO_PIN_10) == SET(掉电预警仍有效),立刻W5500_Power_OFF()+wifi_power_off(),把外部模块的电源让给 Flash 写操作(:3367-3371)。 - 单次调用处理上限:
if (l_u16_save_cnt++ >= 6) break;(:3373),即一次最多处理 7 页,避免长时间占用系统数据任务。
4.2 上电恢复状态机(system_info_restore)
system_info_restore()(:2371)是一段顺序执行的"逐页恢复 → 失败回退"链表,无显式状态变量,但用 4 个标志位记录"哪些页是从坏数据回退的",供后续备份修复使用:l_u8_sn_read_eeprom_flag、l_u8_net_read_eeprom_flag、l_u8_inv_read_eeprom_flag、l_u8_custom_read_eeprom_flag(初值均为 TRUE)。
迁移条件(按页号升序):
| 页 | 恢复函数 | 失败回退 |
|---|---|---|
| 4071 | system_sn_restore | system_sn_init,并置 l_u8_sn_read_eeprom_flag = FALSE |
| 4072 | logic_config_info_restore | logic_config_info_init |
| 4073 | meter_config_info_restore | meter_config_info_init |
| 4074 | bat_config_info_restore | bat_config_info_init |
| 4087 | bat_extend_config_info_restore | bat_extend_config_info_init |
| 4075 | inverter_config_info_restore | inverter_config_info_init,置 l_u8_inv_read_eeprom_flag = FALSE |
| 4081 | ct_energy_info_restore | ct_energy_info_init |
| 4076 | energy_info_restore | energy_info_init |
| 4077 | net_info_restore | net_info_init,置 l_u8_net_read_eeprom_flag = FALSE |
| 4078 | dry_node_info_restore | dry_node_info_init |
| 4079 | modbus_config_info_restore | modbus_config_info_init |
| 4080 | ct_config_info_restore | ct_config_info_init |
| 4082/4083 | evc_config_info_restore / evc_setting_info_restore | 对应 *_init |
| 4091/4092/4093 | logic_config_extend1/2/3_info_restore | 对应 *_init |
| 4084/4085/4086/4089 | evc_ii_single_config_info1..4_restore | 对应 *_init |
| 4088 / 4090 | system_custom_info_restore / system_custom_backup_info_restore | 见下 |
| 4095 | system_backup_info_restore | system_backup_info_init,并 ems_set_fault_bit(13, TRUE)(EMS_FAULT_FLASH_FAULT,:2530) |
三个跨页修复规则是本函数的"精华":
- 自定义配置互救(
:2509-2526):若 4088 页读失败(l_u8_custom_read_eeprom_flag == FALSE)但 4090 页备份读成功,就用备份内容重建g_system_custom_info并写回 4088 页。 - 系统备份反哺三页(
:2528-2591):只有 4095 页读成功时才生效——SN 读失败则用备份里的 SN 补进 4071 页并写回(用feeprom_write,因为 4071 页里还有当前 RAM 的其它字段,不能整页覆盖);逆变器型号丢失则set_inverter_type(备份型号)后重写 4075 页;网络页读失败则把 MAC/IP 方法/服务器点/IP/掩码/网关全量补进IP_Address并写回 4077 页。 - Flash 故障上报:4095 页也坏了就置 bit13,让上层出
EMS_FAULT_FLASH_FAULT告警。
单个 *_restore 的统一实现骨架(以 system_sn_restore(:1128)为例):
for (i = 0; i < 3; i++) {
l_u8_res = feeprom_read(&l_t_buf, PAGE, size);
if (l_u8_res == TRUE) {
l_u16_crc = GetCrcCode_Modbus(&l_t_buf, size - 2);
if (l_u16_crc == l_t_buf.u16_crc_code) {
memcpy(目标全局变量, &l_t_buf, size);
...(兼容修补)...
set_eeprom_fault_flag(读故障标志, PAGE, FALSE);
return TRUE;
}
}
}
set_eeprom_fault_flag(读故障标志, PAGE, TRUE);
return FALSE;
关键点:最多试 3 次(应对 feeprom_read 内部偶发读失败),每次都是独立读同一地址——因为 feeprom_read 只在读取失败时才可能触发一次"槽位回卷"的擦写,所以重试有实际意义。
4.3 恢复期的字段兼容修补(老版本数据迁移)
恢复函数不只是拷贝,还内嵌了大量"历史版本补丁":
| 位置 | 条件 | 动作 |
|---|---|---|
system_sn_restore:1160 | u16_user_password == INSTALLER_PASSWORD | 降级为 USER_PASSWORD(防止安装商密码被固化) |
logic_config_info_restore:1209 | GCRatePercent == 0 | 置 80(默认放电倍率百分比) |
logic_config_info_restore:1214 | storage_grid_breaker_specs == 0 | 置 32(电网侧空开规格) |
logic_config_info_restore:1219 | t_g100_info.u8_system_state > 4 或 < 1 | 钳到 1(英国 G100 系统状态合法域 1..4) |
bat_config_info_restore:1468 | u8_reserved_flag == FALSE(老版本无此标志) | 清空 u8_dod、u16_cell_rate_volt、u16_cell_rate_capacity、u8_series_para_state、s_type_splice、u8_reserved1,再置 TRUE |
ct_config_info_restore:1894 | u8_reserved_flag == FALSE | 把 B/C 相电压(2 点)与电网/光伏 B/C 相功率(各 5 点)的校准系数重置为 point=0、coef=10000、offset=0,再置 TRUE |
modbus_config_info_restore:1842 | u16_sunspec_addr == 0 | 置 ADDR_SUNSPEC_MODBUS |
modbus_config_info_restore:1846 | u16_sunspec_Ver == 0 | break 跳出循环 → 返回 FALSE → 上层走 modbus_config_info_init() 全量重初始化("版本 0 即不可用"的强判据) |
这些修补保证"老设备升级固件后配置不炸",但也意味着恢复函数的返回值语义被复用:modbus 的 break 是为了强制重初始化,而不是真的读失败。
4.4 出厂值初始化的写入策略与故障记账
所有 *_init() 的骨架一致:
memset(&目标结构体, 0, sizeof)(个别函数如system_custom_backup_info_init/system_backup_info_init走赋值而非清零)。- 填
u16_head = CONFIG_INFO_HEAD。 - 填默认业务值(例:
logic_config_info_init设u8_system_mode = DC_SYSTEM_MODE、u8_max_feedin_percent = 100、Charger_Cut_SOC = 100、UPS_Reserve_SOC = 10、GCRatePercent = 80、storage_grid_breaker_specs = 32、u8_ongrid_low_soc = 25、u8_offgrid_low_soc = 50、s32_grid_power_limit = 69000、馈网百分比 100/60/30/0,见:303-318;PV_Capacity_Storage取逆变器额定放电功率inverter_read_rated_dc_discharge_power)。 u16_crc_code = GetCrcCode_Modbus(..., size - 2)。- 最多 3 次
feeprom_init_write()(擦扇区 + 写,覆盖语义),成功即 break。 set_eeprom_fault_flag(get_eeprom_software_write_fault_flag(), PAGE, !l_u8_res)记账。
注意 net_ip_method_init(:698)的记账写法与其它函数不同:它写的是 if (l_u8_res == FALSE) set_eeprom_fault_flag(..., TRUE);,即失败才置位,成功时不清位——与其它页的 !l_u8_res 语义不对称(成功时清位)。
另外 *_init() 里对 l_u8_res 的判据有两套:大部分用 == TRUE,而 system_custom_info_init、system_custom_backup_info_init、system_backup_info_init 三处用 == FLASH_OK(:1028、:1061、:1116)。已核对定义:FLASH_ERR = 0, FLASH_OK = 1, FLASH_BSY = 2(hardware/flash/flash.h:105-110),TRUE = !FALSE = 1(system/sys/chip.h:64),两者数值相同,因此当前行为一致;但混用写法在枚举顺序被改动时会静默失效。
4.5 特殊初始化函数的额外副作用
logic_config_info_init:会根据编译宏给g_t_inv_info.t_config_data.u8_nn_short_detect_state赋NN_SET_FOLLOW(INVERTER_TYPE == ALPHAESS_TYPE)或NN_SET_CLOSE(:320-324)——初始化一个页的同时改了另一页的内存结构。inverter_config_info_init:先把当前型号存进l_u8_temp_model,清零整页后再set_inverter_type(l_u8_temp_model)恢复型号,随后inverter_write_safety(AS4777_2)写默认安全法规、u8_inv_igfollow_enable = TRUE、u16_sys_shutdown_days = 7(:567-575)。modbus_config_info_init:末尾调用sunspec_modbus_data_init()(:762)初始化 Sunspec 数据模型。bat_config_info_restore:恢复后把电池配置同步进运行信息(u16_soft_ver、u8_type、u8_num、u8_dod、u16_cell_rate_volt、u16_cell_rate_capacity、u8_series_para_state、充放电截止单体电压、memcpy(t_run_info.s_type_splice, ..., 31))(:1478-1487)。bat_extend_config_info_restore:循环 12 次把每节电池的单体软件版本/平台类型/SN 同步进t_run_info(:1534-1540)。logic_config_extend1/2_info_restore:恢复后把 14 段时间控制memcpy进g_t_time_control_run.logic_time_charge_dicharge_control1/2(:1271、:1320)。meter_config_info_restore与ct_energy_info_restore:恢复后把 4 项分相累计电量从 config 结构抄进运行结构(:1415-1418、:1633-1636)。
4.6 备份刷新检测(get_backup_data_refresh_flag)
get_backup_data_refresh_flag()(:2780)逐项比较 4095 页镜像与当前运行值,任何一项不同即返回 TRUE 并触发一次备份页写入:
s8_sn[16]与g_t_system_base_info.s8_sn;u8_mac[6]与IP_Address.u8_mac_addr;u8_inv_type与get_inverter_type();u8_server_point与IP_Address.u8_server_point;u8_ip_method与IP_Address.IPmethod;- 且仅当
IP_Address.IPmethod == IP_METHOD_MANUAL时才比较 IP/网关/掩码 12 个u16(DHCP 模式下这些字段无效,跳过比较以避免无意义的反复备份写)。
这一函数与 system_config_data_save() 中的备份写动作构成"变更即备份"闭环:任何 SN/MAC/IP/型号变化都会在 500 ms 内被投递保存一次。
4.7 与 uCOS-II 的配合汇总
| 同步对象 | 创建/使用位置 | 语义 |
|---|---|---|
SaveJob_Q(消息队列) | save_job_init:2606 创建;save_job_post:2712 投递;save_job_pend:2746 接收 | 生产者是任意任务/中断外的调用者,消费者是 task_system_data |
SaveJobMsgBuffer(内存分区) | save_job_init:2618 创建;OSMemGet:2698 / OSMemPut:2756 | 避免在队列里传大结构,只有 11 字节报文 |
g_system_msg_page_id(原子位图,非 OS 对象) | __LDREXW/__STREXW 独占访问 | 跨任务去重;不用关中断也能保证一致性 |
OSTimeGet() | save_job_post:2710、save_job_pend:2750、system_config_data_save:2905 | 记录投递时刻、计算单页保存耗时、720 s 定时 |
IWDG_Feed() | 本文件未调用 | 喂狗在调用方(data/data.c 的复位流程)中穿插 |
5. 对外接口
导出的函数(system_config.h:808-836 声明,除 net_info_init 原型写在 .c 内部):
| 名称 | 使用者 |
|---|---|
system_info_restore | system/board_init/board_init.c:250 |
save_job_init | data/data.c:257 |
save_job_post | 全工程 *.c 中 131 处匹配(其中 1 处是 system_config.c:2688 的定义,其余约 130 处为调用),分布在 18 个文件:data/data.c、data/resume_data/flash/flash_data_store.c、hmi/key.c、logic/logic.c、net/server.c、midware/auxiliary/system_aux.c、midware/bms/bms.c、midware/interface/interface.c、midware/inverter/*(alphaess/ginlong/goodwe/inovance/kelong/set)、midware/meter/meter.c、midware/meter/ct.c |
save_energy_data | data/data.c:269/302 |
system_config_data_save | data/data.c:277/306 |
ui_info_init、logic_config_info_init、logic_config_extend1/2/3_info_init、meter_config_info_init、bat_config_info_init、bat_extend_config_info_init、inverter_config_info_init、dry_node_info_init、net_ip_method_init、modbus_config_info_init、evc_config_info_init、evc_setting_info_init、evc_ii_single_config_info1..4_init、system_backup_info_init | data/data.c 的 factory_reset_handle(:94-144,实测调用行 :100-138) |
system_custom_info_init、system_custom_backup_info_init | data/data.c 的 custom_info_reset_handle(:55-65,实测 :60、:62) |
energy_info_init | data/data.c:80(energy_reset_handle) |
ct_energy_info_init | 全工程无外部调用者,仅在本文件 system_info_restore:2421 的 4081 页回退中使用(原型仍导出于 system_config.h:817) |
net_info_init | 仅本文件内部 system_info_restore:2433 使用(非 static 却无外部调用者) |
导出的全局变量:SaveJob_Q、SaveJobMsgGrp、SaveJobMsgBuffer、SaveJobMsgMem(未在头文件声明,仅本文件使用)、g_t_system_base_info、g_system_custom_info、g_system_function_info、g_system_msg_page_id。
本文件调用的外部函数/变量(按 include 分组,:13-34):
feeprom.h:feeprom_read、feeprom_write、feeprom_init_write。crc.h:GetCrcCode_Modbus。flash.h:FLASH_OK、扇区尺寸宏。data.h:ems_set_fault_bit、EMS_FAULT_FLASH_FAULT(以 bit13 字面量使用)。fault_warn_log.h:set_eeprom_fault_flag、get_eeprom_software_read_fault_flag、get_eeprom_software_write_fault_flag。ems.h:Set_Setup_Config_Status、Set_Setup_Config_Extend1_Status、Set_Setup_Config_Extend2_Status。ethernet.h:IP_Address、IP_METHOD_AUTO(:77)、IP_METHOD_MANUAL、AT_DEFAULT_PWD(:22)、服务器点枚举FORMAL_ENCRYPTION_SERVER/OEM_ZOE_FORMAL_SERVER/OEM_BYTEWATT_FORMAL_SERVER/OEM_BYTEWATT_TEST_SERVER(:147-162)、IP_ADDR_TAB_T。ems.h:WIRELESS(net/ems.h:82,取值 0x02,赋给IP_Address.u8_network_type)。lcd.h:LCD_RTDB(密码、语言、IP 显示、LCD_RTDB.u8_language)。logic.h:g_t_logic_info、g_t_time_control_run、DC_SYSTEM_MODE、ALL_DISABLE、RESERVE_SOC_DISABLE、LOGIC_CONFIG_TAB_T及三个扩展表类型。meter.h:g_t_meter、METER_CONFIG_TAB_T。bms.h:g_t_bat_info、BATTERY_CONFIG_TAB_T、BATTERY_EXTEND_CONFIG_TAB_T。inverter.h:g_t_inv_info、INVERTER_CONFIG_TAB_T、VERSION_INV_HARD、get_inverter_type、set_inverter_type、inverter_write_safety、AS4777_2、inverter_read_rated_dc_charge_power、inverter_read_rated_dc_discharge_power、INVERTER_TYPE/ALPHAESS_TYPE/INVERTER_MAX等编译宏。system_aux.h:g_t_energy_info、ENERGY_COUNT_INFO_T、enenrgy_count_handle。ct.h:g_t_ct、CT_CONFIG_TABLE_T、CT_ENERGY_SAVE_TABLE_T。modbus.h:g_t_modbus_config、MODBUS_CONFIG_INFO_T、ADDR_MODBUS(modbus.h:19,=0x55)、ADDR_SUNSPEC_MODBUS(modbus.h:20,=0x01)、BAUD_RATE_9600(modbus.h:1385,=0)。sunspec_business.h:sunspec_modbus_data_init。evc.h/evc_ii.h:g_t_evc_config_table、g_t_evc_setting_info、g_t_evc_ii_single_config[4]、各表类型、EVC_SLOW。dry_node.h:STDryNodeInfo、STDryNodeTab。gpio.h:gpio_input_bit_get(GPIOG, GPIO_PIN_10)、W5500_Power_OFF、wifi_power_off。debug.h:ShowData、STATIC_ASSERT。key.h:INSTALLER_PASSWORD、USER_PASSWORD(用户/安装商密码常量)、AT_DEFAULT_PWD。- uCOS-II:
OSQCreate、OSQPost、OSQPend、OSMemCreate、OSMemGet、OSMemPut、OSEventNameSet、OSMemNameSet、OSTimeGet、OS_ERR_*。 - Cortex-M 内核指令:
__LDREXW、__STREXW、__CLREX。
6. 依赖关系
- 依赖(include 列表,
system_config.c:13-34):system_config.h、dry_node.h、feeprom.h、crc.h、ems.h、data.h、ethernet.h、lcd.h、logic.h、meter.h、bms.h、inverter.h、system_aux.h、flash.h、ct.h、modbus.h、sunspec_business.h、evc.h、evc_ii.h、debug.h、key.h、fault_warn_log.h。可以说它直接耦合了工程中几乎所有业务模块的配置结构体。 - 被依赖:
system/board_init/board_init.c、data/data.c、data/resume_data/flash/flash_data_store.c、hmi/key.c、logic/logic.c、net/server.c、net/ems.c、midware/*(凡调用save_job_post或使用四个导出全局变量者)。
7. 备注与风险点
net_info_init的 static 声明与非 static 定义冲突::54声明static void net_info_init(void);,而:130定义void net_info_init(void),同时system_config.h:831又给出非 static 的原型。这是一个真实的语言层面矛盾(严格编译器会报错),说明该函数原本设计为私有、后来被提升为公开接口,但旧声明忘了删。修改时需同时动两处。save_job_post的越界判断是>而不是>=::2695写(g_u8_msg_id > MESSAGES_SIZE)。合法位号是 0..24,MESSAGES_SIZE=25,因此g_u8_msg_id == 25会通过检查并去置位位 25——虽然位 25 仍在 32 位联合体内(不会越界),但它是一个没有对应页面的保留位。若调用者传入SYSTEM_STARTE_PAGE + 25,就会产生永远无法被save_job_pend清掉的悬挂位,最终导致该位后续再也不会被设置。建议改为>=。l_u16_save_cnt未在每轮调用时复位:它在system_config_data_save()内定义为局部变量并初值 0(:2901),每次调用重新初始化,行为正确;但其累加判断>= 6位于循环末尾,实际最多处理 7 页,与注释"每页 10 ms"一起暗示了单次调用耗时上限约 70 ms,属于隐式时间预算。- 掉电时在页间关闭网络电源:
:3367依据 PG10 电平关闭 W5500/WiFi 电源,但system_config_data_save()也在正常运行态(task_system_data超时分支)被调用。若因某种原因 PG10 长期为高而系统并未真正掉电,W5500/WiFi 会被反复关断,只能靠data/data.c:281的W5500_Power_ON()/wifi_power_on()在同轮末恢复——依赖"每轮必然执行到恢复语句",若中途Sys_Soft_Reset()(ems_reset_handle)触发则不复电。 l_u8_res可能未初始化使用:SYSTEM_BASE_PARA_PAGE、LOGIC_CONFIG_PAGE等 case 中,l_u8_res每次循环都被feeprom_write赋值,无问题;但system_custom_info_init(:1025-1030)、system_custom_backup_info_init(:1058-1063)、system_backup_info_init(:1113-1118)三处循环结束后 未使用l_u8_res记账,即这三页的写失败不会被记录到 EEPROM 故障标志,与其它页不一致。TRUE与FLASH_OK混用(本次核对后确认数值等价,属风格问题):system_custom_info_init:1028、system_custom_backup_info_init:1061、system_backup_info_init:1116以及system_info_restore:2250/2296/2342用== FLASH_OK判写成功,其余全部用== TRUE。已核对定义:hardware/flash/flash.h:107-110的枚举是FLASH_ERR = 0, FLASH_OK, FLASH_BSY,即FLASH_OK == 1;system/sys/chip.h:64的TRUE = !FALSE = 1。因此当前行为一致,不构成缺陷,但仍建议统一写法,避免以后有人改枚举顺序时静默失效。net_ip_method_init的故障标志语义不对称::718-719只在失败时置位,成功时不清位;而其它页是set_eeprom_fault_flag(..., !l_u8_res)双向记账。结果是网络页写成功后,之前的软件写故障位不会被清除,EEPROM 故障告警可能"粘住"。clear_eeprom_fault_temp与set_eeprom_fault_flag的范围不一致:data/data.c:192-201循环i < 32而合法页范围只到 4095(位 24),后 7 次迭代被set_eeprom_fault_flag的addr > SYSTEM_DATA_BACKUP_PAGE保护静默丢弃(本文件未参与,但同属该机制)。atomic_check_and_set_bit的 return 2 未被区分::2657返回 2 表示 STREX 重试 1000 次仍失败(说明有极高并发的独占竞争),但save_job_post把 2 与 1 一样当作"已有任务"静默丢弃,可能造成该页长期不被保存而无人知晓(无日志、无故障标志)。system_info_restore依赖IP_Address已具备部分初值:net_info_init在清零前先备份IP_Address.u8_mac_addr与u8_server_point(:141-149),要求调用时该结构体里已经有 MAC(通常来自硬件/其它页)。若上电流程顺序变化导致IP_Address全 0,会写出一张全 0 MAC 的网卡配置,且因为 CRC 正确、head 正确,后续不会再被修复。STSTEM_CUSTOM_CONFIG_PAGE的case未做写失败记账::3254-3266只break,没有set_eeprom_fault_flag;同属default之外却缺少统一善后,属于不一致。sys_info_restore中l_u8_custom_read_eeprom_flag的双重语义:它先被赋成system_custom_info_restore()的返回值(TRUE表示读成功,:2509),而:2512又用== FALSE表示"读失败需要从备份恢复",与前三页的read_eeprom_flag(TRUE 表示读成功,FALSE 后用来触发修复)语义相反,容易误读。DEBUG_SYSTEM_CONFIG关闭时ShowData、printf全部被条件编译剔除,但system_sn_restore中l_u16_crc变量在关闭调试时只在 CRC 比较处使用(:1155),不会产生未使用告警;而dry_node_info_init:677的调试打印引用了g_t_evc_config_table.u16_crc_code(EVC 结构),属于复制粘贴留下的错误调试信息。evc_ii_single_config_info1_init的调试打印索引写错::869-870打印与ShowData用的是g_t_evc_ii_single_config[1],而该函数初始化的是[0](:861-866)。仅在DEBUG_SYSTEM_CONFIG == 1时可见,但会误导调试。- 魔法数字密集:
ems_set_fault_bit(13, TRUE)(:2530)、720 * 1000(:2905)、l_u16_save_cnt >= 6(:3373)、32/80/10000/31等默认值均无具名常量;memcpy(..., 31)(:1487)与s_type_splice数组长度的耦合也不明显。