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

sunspec_modbus.c

源文件路径:midware/interface/modbus/sunspec_modbus.c 所属模块:midware/interface/modbus(SunSpec 协议设备侧从站数据模型) 代码行数:837

1. 文件职责

本文件是整个 SunSpec Modbus 从站功能的数据实体与寄存器映像表。它定义了 sunspec_modbus.h 中 extern 的全部 11 个 SunSpec 数据实体(Model 1 公共块 ×3、Model 701/702/704/713/714、Model 203 电表 ×2)与实例编号数组 g_t_sunspec_id[],并用 783 行初始化式构造了覆盖 40000~40782 全部保持寄存器的映射表 g_t_sunspec_mudbus[]。表内每一行把"一个 SunSpec 寄存器地址"绑定到"某个业务模块全局变量的 16 位片段",并附带默认值、初始化策略、大小端与读写权限。它处在数据流的回答端:外部 SunSpec 主站(Modbus RTU/地址由 g_t_modbus_config.u16_sunspec_addr 指定)来读,sunspec_business.c 的二分查找直接取这里的 data_p;主站来写,也通过本表的权限位落回业务变量。本文件自身不含任何函数,不被任何逻辑主动驱动,纯数据。

2. 关键数据结构与宏

本文件不新定义类型,全部使用 sunspec_modbus.h 中的声明。下表列出本文件定义(分配存储)的全局对象及表中所引用的关键要素。

名称类型说明
g_t_sunspec_id[ID_MAX]数组(SUNSPEC_ID_T)各信息模型实例的 (model_id, model_len) 对,映射到 40002/40003、40070/40071、40226/40227、40278/40279、40352/40353、40361/40362、40431/40432、40499/40500、40606/40607、40674/40675,以及结束标记 ID_END = 0xFFFF/0(sunspec_modbus.c:24)
g_t_sunspec_base / g_t_sunspec_base_GRID / g_t_sunspec_base_PV结构体(SUNSPEC_DATA_BASE_T)3 份 Model 1:主设备公共块、电网电表前通用块、PV 电表前通用块(sunspec_modbus.c:26-28)
g_t_sunspec_default结构体(SUNSPEC_DATA_DEFAULT_T)仅 operating_sta,映射 40073(sunspec_modbus.c:31)
g_t_sunspec_run结构体(SUNSPEC_RUN_DATA_T)Model 701 运行数据实体(sunspec_modbus.c:33)
g_t_sunspec_ctlAC结构体(SUNSPEC_CONTROL_T)Model 704 控制实体(sunspec_modbus.c:35)
g_t_sunspec_bat结构体(SUNSPEC_BAT_T)Model 713 储能容量实体(sunspec_modbus.c:37)
g_t_sunspec_DC结构体(SUNSPEC_DC_T)Model 714 直流测量实体(sunspec_modbus.c:39)
g_t_sunspec_AC_meter / g_t_sunspec_PV_meter结构体(SUNSPEC_AC_METER_T)电网侧 / PV 侧 Model 203 电表实体(sunspec_modbus.c:41-42)
g_t_sunspec_inv_nameplate结构体(SUNSPEC_NAMEPLATE_T)Model 702 容量铭牌实体(sunspec_modbus.c:44)
g_t_sunspec_mudbus[783]数组(SUNSPEC_MODBUS_T)核心寄存器映像表,783 行(sunspec_modbus.c:49-834),与 40000~40782 严格 1:1

表内各"列"(初始化式字段)的含义与统计:

列取值分布说明
data_p业务变量地址或 ((u16*)&var)+132/64 位量拆高低字;同一变量可被多行重复引用
addr40000~40782 连续无空洞二分查找前提是严格升序
default_value字符串魔数(0x5375='Su'、0x6E53='nS'、0x414C='AL'、0x5048='PH'、0x412D='A-'、0x4553='ES'、0x5300='S\0')、模型 ID/长度、少量 0/1仅 INIT 行在 sunspec_modbus_data_init() 时生效
init_typeINIT 91 行 / RUNING 692 行INIT 集中在 Model 1 公共块与 WsetEna/WsetMod
big_endianBIG_ENDIAN 599 行 / LIT_ENDIAN 184 行LIT_ENDIAN 全是字符串字段(mn/md/opt/ver/id_str/电表型号/SN/软件版本)
scaleSCALE_1 780 行 / SCALE_0_1 3 行40115/40116/40117(A 相电流、R/S 相电压)用 SCALE_0_1
rwRO 705 行 / RW 78 行 / WO 0 行可写地址清单见 4.6 节

3. 函数清单

本文件不包含任何函数定义(已用 grep -n "^[a-zA-Z_].*(" sunspec_modbus.c 与逐行扫描确认:除第 1-47 行的文件头注释、#include、第 24-44 行的全局变量定义外,第 49 行至第 834 行全部是 g_t_sunspec_mudbus[] 的初始化式和收尾的 };)。所有与该表配套的函数(二分查找、初始化、刷新、访问器)都实现在 midware/interface/modbus/sunspec_business.c。

4. 核心逻辑解析

4.1 信息模型实例布局(SunSpec 扫描链)

主站按 SunSpec 规范从 40000 开始顺序扫描:读到 40000 的 "SunS" 标识(默认值 0x5375/0x6E53 = "Su"/"nS")后,在 40002 读 Model 1 的 ID 与 LEN,跳过 LEN 个字继续找下一个 ID/LEN,遇到 ID = 0xFFFF 结束。本文件的映射严格遵循这一链:

实例model_id 地址model_len数据区对应实体
Model 1(主设备)40002 = 140003 = 6640004~40069g_t_sunspec_base + g_t_system_base_info.s8_sn + g_t_modbus_config.u16_sunspec_addr
Model 701(DER AC 测量)40070 = 70140071 = 15440072~40225g_t_sunspec_run、g_t_sunspec_default、各模块读函数
Model 702(DER 容量)40226 = 70240227 = 5040228~40277g_t_sunspec_inv_nameplate
Model 704(DER AC 控制)40278 = 70440279 = 7240280~40351g_t_sunspec_ctlAC
Model 713(储能容量)40352 = 71340353 = 740354~40360g_t_sunspec_bat
Model 714(DER 直流测量)40361 = 71440362 = 6840363~40430g_t_sunspec_DC
Model 1(电网电表前通用块)40431 = 140432 = 6640433~40498g_t_sunspec_base_GRID
Model 203(电网电表)40499 = 20340500 = 10540501~40605g_t_sunspec_AC_meter
Model 1(PV 电表前通用块)40606 = 140607 = 6640608~40673g_t_sunspec_base_PV
Model 203(PV 电表)40674 = 20340675 = 10540676~40780g_t_sunspec_PV_meter
结束标记40781 = 0xFFFF40782 = 0—g_t_sunspec_id[ID_END]

之所以出现多个 Model 1 / Model 203,见 sunspec_modbus.h:53-57 的注释:"客户要求每个电表前都增加一个通用模型 1 且 Grid 电表和 PV 电表可以都用 203 模型"。

4.2 Model 1 公共块的字段布局

以主设备块为例(40004~40069,66 字):

电网电表块(40433~40498)与 PV 电表块(40608~40673)结构相同,但把 ver 位置换成了电表软件版本(g_t_meter.t_grid_info.u8_software_version[10] / t_pv_info.u8_software_version[10]),SN 位置换成电表 SN(各 16 字节),并额外提供 meter_addr(40497 / 40672,RW)。

4.3 Model 701 运行数据的映射要点

40072~40225 共 154 字,是本表引用业务变量最密集的一段:

4.4 Model 702 容量铭牌

40228~40277 逐字段对应 SUNSPEC_NAMEPLATE_T:40228~40235 额定值、40236~40239 复用 g_t_logic_info.t_logic_control_parameter.s32_bat_charge_power_limit / s32_bat_dischg_power_limit 的高低字、40240~40251 电网参数与工况类别、40252~40277 *_set 段与 7 个 *_scale。其中 40260/40261 用枚举常量 MAX_CHG_40260 / MAX_DISCHG_40261 表示,与 sunspec_modbus.h 中的 E_SUNSPEC_WSET_ENA 对齐。

4.5 Model 704 AC 控制与 Model 713/714

4.6 可写寄存器完整清单(RW,共 78 个)

精确清单(按地址升序):

地址段说明
40068g_t_modbus_config.u16_sunspec_addr
40252~40270Model 702 的 *_set 段(含 40260/40261 = MAX_CHG_40260/MAX_DISCHG_40261),连续 19 个全可写
40280、40281PFWinjEna、PFWinjnaRvrt
40282、40285PFWinjRvrTms 高字、PFWinjRvrtRem 低字(低字/高字为 RO,见风险点 3)
40286、40287、40288、40291PFWAbsEna、PFWAbsEnaRvrt、PFWAbsRvrtTms 高字、PFWAbsRvrtRem 低字
40292~40296、40299有功限值组(WMaxLimPctEna、WMaxLimPct、WMaxLimPctRvrt、WMaxLimPctEnaRvrt、WMaxLimPctRvrtTms 高字、WMaxLimPctRvrtRem 低字)
40300~40310WSET_ENA_40300~WSET_ENA_40310:WsetEna、WsetMod、Wset、WsetRvrt、WsetPct、WsetPctRvrt、WsetEnaRvrt、WsetRvrtTms(高低字均可写,与风险点 3 的其它组不同)
40313~40323、40326无功调度组(VarSetEna、VarSetMod、VarSetPri、VarSet、VarSetRvrt、VarSetPct、VarSetPctRvrt、VarSetEnaRvrt、VarSetRvrtTms 高字、VarSetRvrtRem 低字)
40327~40329WRmp、WRmpRef、VarRmp
40336~40351w_fac_set[0] / w_fac_set[1] 两条功率因数曲线共 16 个字
40497、40672GRID / PV 电表的 meter_addr

明确只读的反直觉项:40283、40284、40289、40290、40297、40298、40311、40312、40324、40325(均为 32 位字段的另一半字)。其余 705 个寄存器全部只读。

4.7 与 uCOS-II 任务的关系

本文件是纯数据,不参与调度。但其 data_p 指向的变量分别被不同任务读写:

写路径没有互斥保护:主站的 Modbus 写直接落在 g_t_sunspec_ctlAC.* 上,而刷新函数在另一时刻读取它,属于典型的无锁共享(见风险点 5)。

5. 对外接口

本文件导出的全局变量(均通过 sunspec_modbus.h 的 extern 暴露)

变量主要使用方
g_t_sunspec_mudbus[]sunspec_business.c(sunspec_modbus_data_get/_set/_init 遍历)
g_t_sunspec_id[]仅本文件建模用;sunspec_business.c 不直接读
g_t_sunspec_base / _GRID / _PVsunspec_business.c:1049-1058 初始化;modbus.c 通过表读取
g_t_sunspec_defaultsunspec_business.c:1260 写 operating_sta
g_t_sunspec_runsunspec_business.c:1060 memset、1308-1351 刷新
g_t_sunspec_inv_nameplatesunspec_business.c:1257 由机型数组赋值,并被 ac_control.c 通过访问器读取
g_t_sunspec_ctlACsunspec_business.c 调度刷新;logic.c/ac_control.c 通过访问器读取
g_t_sunspec_batsunspec_business.c:1362-1365 刷新
g_t_sunspec_DCsunspec_business.c:1397-1437 刷新
g_t_sunspec_AC_meter / _PV_metersunspec_business.c:1371-1381、1448-1449 刷新

本文件调用的外部内容

6. 依赖关系

7. 备注与风险点

  1. 783 行表全部常驻普通 RAM:sunspec_modbus.h:455 中把数组放到 SDRAM 的 __attribute__((at(SDRAM_SUNSPEC_DATA_ADDR))) 被注释掉了。783 × 24 B ≈ 18.4 KB 的已初始化数据,既占 Flash(初值副本)也占内部 SRAM。
  2. 大小端标记与数值语义不自洽:字符串字段用 LIT_ENDIAN 逐字交换字节后输出 ASCII,是 SunSpec 规范要求的"大端字符串 + 小端字内字节序"折中做法;但数值字段全用 BIG_ENDIAN,而 32/64 位量在表里按 +1(高字)在前、低字在后的顺序排列——即同一 u32 的高字寄存器地址更小,符合 SunSpec 规范,但与"从 40097 起的 4 个字依次 +3/+2/+1/基址"的写法容易看错位。新增寄存器时极易插错高低字顺序。
  3. RvrtTms/RvrtRem 的高低字权限成对矛盾(除 WsetRvrtTms 外全部如此)。四组同构问题的实例:
组高字(+1)低字(基址)结论
PFWinjRvrTms / PFWinjRvrtRem40282=RW、40284=RO40283=RO、40285=RW高字可写、低字只读
PFWAbsRvrtTms / PFWAbsRvrtRem40288=RW、40290=RO40289=RO、40291=RW同上
WMaxLimPctRvrtTms / WMaxLimPctRvrtRem40296=RW、40298=RO40297=RO、40299=RW同上
VarSetRvrtTms / VarSetRvrtRem40323=RW、40325=RO40324=RO、40326=RW同上
WsetRvrtTms / WsetRvrtRem40309=RW、40311=RO40310=RW、40312=RO一致(高字+低字均可写/均只读)

由于 sunspec_modbus_data_set() 逐行判断权限位,主站写 40283(低字)会被静默忽略——即 32 位 PFWinjRvrtTms 实际只能改到高 16 位。任何小于 65536 秒(约 18.2 h)的回退时间都改不动,且写入不返回 Modbus 异常码,主站无从察觉。RvrtRem 反向(低字可写、高字只读)使得 Rem 的读取值永远保留复位初值 0 的高字,同样异常。四组共 10 个寄存器的权限位需要逐一复核。

  1. void_data 哑元被 110 个寄存器共用,精确地址为:40060~40067、40069、40087、40089、40090、40093、40094、40118、40119、40122、40123、40126~40133、40141、40142、40145、40146、40149~40156、40164、40165、40168、40169、40172~40179、40193~40225(连续 33 个)、40478~40480、40489~40496、40498、40653~40655、40664~40671、40673、40686~40688。全部指向同一个 g_t_sunspec_run.void_data,主站写任一地址都会改写该 u16,导致其余 109 个地址的读回值同时变化。
  2. 无锁共享:主站写 g_t_sunspec_ctlAC.Wset*(在 task_modbus_rtu 上下文)与 sunspec_modbus_data_refresh() 读同一结构体,两者在同一任务内串行(都挂在 g_t_uart3_rcv_sem 之后)因而当前实际安全;但 logic/ac_control.c / logic/logic.c 在其他任务里通过访问器读 g_t_sunspec_ctlAC(如 mppt_percentage),与写路径无互斥,存在撕裂读(尤其是 u32 字段)的可能。
  3. sscanf 无宽度限制的隐患在配套代码中:40020~40035 的 md[32] 由 sunspec_modbus_data_init() 用 sscanf(inverter_read_system_modle(), "%s", g_t_sunspec_base.md) 填充(sunspec_business.c:1055)。型号字符串若超过 31 字节即缓冲区溢出。本文件把缓冲区大小固定为 32,属于该风险的暴露面。
  4. data_p 类型统一为 u16*,但有 s16/s32/u64 混用:读路径的函数返回 u16,签名上的有符号信息丢失(如 inv_apparent_power 实际是 s32,主站需按 SunSpec 的 SF 规则自行解释符号)。这是 SunSpec 标准的做法,但表内没有校验 s16/u16 的一致性。
  5. g_t_sunspec_id[ID_1].model_len = 66 等长度值是硬编码魔数:若上游结构体字段增减而忘记同步 ID/LEN,主站扫描链会错位,且没有编译期断言保护(对比 modbus.h:1479 的 MODBUS_CONFIG_INFO_T 是有 STATIC_ASSERT 的)。
  6. 40000/40001 的 "SunS" 标识硬编码为两个字(0x5375→'S','u';0x6E53→'n','S'),若 sunspec_star[5] 的实际内容被运行时改写(sunspec_business.c:1052 会 memcpy "Suns"),会与这两个 INIT 行冲突——取决于调用顺序(初始化在启动期做,之后不再改写,故当前无碍)。
  7. Modbus TCP 从站端口不提供 SunSpec 数据:modbus.c 的 is_start_addr_and_num_err()(modbus.c:261-275)与 handle_modbus_tcp_recv_data()(modbus.c:9562)都放行 SunSpec 从站地址与 40000~41042 的寄存器范围,但 response_modbus_tcp_cmd()(modbus.c:9060)走的是 get_para_from_reg_addr() / Set_Para_to_Reg_Addr(),而这两个函数(modbus.c:290、5047)都没有 SunSpec 分支,最终落入空的 else { } 返回 0(modbus.c:5330-5334)。结果是:通过 TCP 访问 SunSpec 寄存器会读到全 0、写入被静默丢弃,主站收不到任何异常码。对比 RTU 路径确实调用了 sunspec_modbus_data_get/_set(modbus.c:8222/8265/8320)。若产品宣称支持 SunSpec over Modbus TCP,这是功能缺口(注意:TCP 路径同样不会调用 sunspec_refresh_flag_set(),因此 SunSpec 通信标志只能由 RTU 帧刷新)。
  8. 文件头部注释仍写着 @file :sunspec_modbus.c 但内容与 sunspec_business.c 的头部注释完全重复(作者 mike、日期缺失),说明两个文件由同一模板拷贝而来,无功能影响但降低可读性。
  9. 寄存器上限常量与实际表长不一致:modbus.h:87-89 定义 REG_SUNSPEC_START = 0x9C40(40000)、REG_SUNSPEC_END = 0xA052(41042)、REG_MAX_SUNSPEC_MODBUS(41043),而实际表只到 40782。主站请求 40783~41042 会通过地址校验,由 sunspec_modbus_data_get() 二分查找失败后返回 0(不报异常)。这 259 个"合法但无数据"的地址会长期返回 0,容易被主站误判为有效数据。