sunspec_modbus.c
RW,共 78 个)4.7 与 uCOS-II 任务的关系5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
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)+1 | 32/64 位量拆高低字;同一变量可被多行重复引用 |
addr | 40000~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_type | INIT 91 行 / RUNING 692 行 | INIT 集中在 Model 1 公共块与 WsetEna/WsetMod |
big_endian | BIG_ENDIAN 599 行 / LIT_ENDIAN 184 行 | LIT_ENDIAN 全是字符串字段(mn/md/opt/ver/id_str/电表型号/SN/软件版本) |
scale | SCALE_1 780 行 / SCALE_0_1 3 行 | 40115/40116/40117(A 相电流、R/S 相电压)用 SCALE_0_1 |
rw | RO 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 = 1 | 40003 = 66 | 40004~40069 | g_t_sunspec_base + g_t_system_base_info.s8_sn + g_t_modbus_config.u16_sunspec_addr |
| Model 701(DER AC 测量) | 40070 = 701 | 40071 = 154 | 40072~40225 | g_t_sunspec_run、g_t_sunspec_default、各模块读函数 |
| Model 702(DER 容量) | 40226 = 702 | 40227 = 50 | 40228~40277 | g_t_sunspec_inv_nameplate |
| Model 704(DER AC 控制) | 40278 = 704 | 40279 = 72 | 40280~40351 | g_t_sunspec_ctlAC |
| Model 713(储能容量) | 40352 = 713 | 40353 = 7 | 40354~40360 | g_t_sunspec_bat |
| Model 714(DER 直流测量) | 40361 = 714 | 40362 = 68 | 40363~40430 | g_t_sunspec_DC |
| Model 1(电网电表前通用块) | 40431 = 1 | 40432 = 66 | 40433~40498 | g_t_sunspec_base_GRID |
| Model 203(电网电表) | 40499 = 203 | 40500 = 105 | 40501~40605 | g_t_sunspec_AC_meter |
| Model 1(PV 电表前通用块) | 40606 = 1 | 40607 = 66 | 40608~40673 | g_t_sunspec_base_PV |
| Model 203(PV 电表) | 40674 = 203 | 40675 = 105 | 40676~40780 | g_t_sunspec_PV_meter |
| 结束标记 | 40781 = 0xFFFF | 40782 = 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 字):
- 40004~40019:
mn[32]厂商名,按 16 行 × 2 字节填入,首 5 个字默认0x414C 0x5048 0x412D 0x4553 0x5300= "AlphaESS";LIT_ENDIAN,所以主站按字节序读到的就是 ASCII 串。 - 40020~40035:
md[32]型号,默认全 0,运行时由sunspec_modbus_data_init()用sscanf(inverter_read_system_modle(), "%s", ...)填入。 - 40036~40043:
opt[16]可选描述,默认 0;GRID 块填 "Export+Import"、PV 块填 "Production"。 - 40044~40051:
ver[16]版本,由memcpy(..., EMS_SOFT_VERSION, ...)填入(本工程V0.11.676,10 字节)。 - 40052~40059:
g_t_system_base_info.s8_sn[16],即设备序列号。 - 40060~40067、40069:8 个字落到
g_t_sunspec_run.void_data(哑元)。 - 40068:
g_t_modbus_config.u16_sunspec_addr,唯一允许主站改写从站地址的寄存器(RW)。
电网电表块(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 字,是本表引用业务变量最密集的一段:
- 40072
inv_phase_num、40073operating_sta、40074inv_sta、40075grid_connect; - 40076/40077 是
inv_alarms(u32) 的高/低字,40078/40079 是DER_mode(u32),说明代码统一用((u16*)&u32字段)+1取高字(小端 ARM); - 40080 直接映射到
g_t_logic_info.t_logic_control_parameter.s32_pinvt(只读低 16 位); - 40085/40086/40116/40117/40139/40140/40162/40163 读
g_t_alphaess_comm_info.t_alphaess_run_info的u16_grid_volt_rs/r/rt/s/st/t,即线电压/相电压; - 40088 读
g_t_inv_info.t_run_info.u16_inv_freq; - 40091/40092、40095/40096 分别拆
u32_energy_inv_output、u32_energy_inv_input; - 40097~40100 / 40101~40104 是
g_t_sunspec_run.inv_reactive_energy_output/input(u64) 的 4 个字(高位在前,+3最先); - 40105~40110 是 6 路温度:
ambient_temp、cabinet_temp、BMS buck-boost、逆变模块、功率板、控制板; - 40111~40117 / 40134~40140 / 40157~40163:A/B/C 三相各自的
s32_inv_actpower_*、s32_inv_apparentpower_*、s32_inv_reactpower_*、s16_inv_cur_*、相电压,以及 4 处重复的g_t_sunspec_run.pf; - 40120~40125 / 40143~40148 / 40166~40171:A/B/C 三相电网侧的
u32_energy_output(馈出)与u32_energy_input(取入),各 2 字; - 40180~40192:
max_feedin_percent、throttle_source(u32)、以及 10 个*_scale比例因子; - 40193~40225:33 个字全部落到
g_t_sunspec_run.void_data。
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
- 40280~40329:PF 注入(
PFWinjEna、PFWinjnaRvrt、PFWinjRvrTms、PFWinjRvrtRem)、PF 吸收(PFWAbs*)、有功限值(WMaxLimPct*)、有功调度(WsetEna~WsetRvrtRem,40300~40312)、无功调度(VarSet*,40313~40326)与WRmp/WRmpRef/VarRmp;40330~40335 为 6 个*_SF,40336~40351 为w_fac_set[0]/[1]两条 PF 曲线共 16 字。 - 40354 的
energy_rating带一条注释/*g_t_bat_info.t_config_data.u16_cell_rate_capacity*/(sunspec_modbus.c:404),提示该值本应来自 BMS 配置容量,当前由sunspec_business.c运行时用bms_read_max_system_storage_capacity()写入。 - 40363~40405 为 Model 714 汇总 +
group[0](电池,PrtTyp=1),40406~40430 为group[1](PV,PrtTyp=0)。group[2]/group[3]未映射(DC_Ports_num最多报 2)。
4.6 可写寄存器完整清单(RW,共 78 个)
精确清单(按地址升序):
| 地址段 | 说明 |
|---|---|
| 40068 | g_t_modbus_config.u16_sunspec_addr |
| 40252~40270 | Model 702 的 *_set 段(含 40260/40261 = MAX_CHG_40260/MAX_DISCHG_40261),连续 19 个全可写 |
| 40280、40281 | PFWinjEna、PFWinjnaRvrt |
| 40282、40285 | PFWinjRvrTms 高字、PFWinjRvrtRem 低字(低字/高字为 RO,见风险点 3) |
| 40286、40287、40288、40291 | PFWAbsEna、PFWAbsEnaRvrt、PFWAbsRvrtTms 高字、PFWAbsRvrtRem 低字 |
| 40292~40296、40299 | 有功限值组(WMaxLimPctEna、WMaxLimPct、WMaxLimPctRvrt、WMaxLimPctEnaRvrt、WMaxLimPctRvrtTms 高字、WMaxLimPctRvrtRem 低字) |
| 40300~40310 | WSET_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~40329 | WRmp、WRmpRef、VarRmp |
| 40336~40351 | w_fac_set[0] / w_fac_set[1] 两条功率因数曲线共 16 个字 |
| 40497、40672 | GRID / PV 电表的 meter_addr |
明确只读的反直觉项:40283、40284、40289、40290、40297、40298、40311、40312、40324、40325(均为 32 位字段的另一半字)。其余 705 个寄存器全部只读。
4.7 与 uCOS-II 任务的关系
本文件是纯数据,不参与调度。但其 data_p 指向的变量分别被不同任务读写:
midware/interface/interface.c的task_modbus_rtu(Modbus-RTU 通信任务)在收到合法帧后调用sunspec_modbus_data_refresh(),把逆变器/电表/BMS 的数据写入这些实体;- 同一任务在
OSSemPend(g_t_uart3_rcv_sem, ...)成功后调用handle_modbus_ret_recv_data(),由midware/interface/modbus/modbus.c的handle_Modbus_response()/handle_modbus_write_single_response()/Handle_Modbus_Write_Response()反查本表应答; logic/ac_control.c、logic/logic.c通过访问器函数读取本表数据(见sunspec_business.c)。
写路径没有互斥保护:主站的 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 / _PV | sunspec_business.c:1049-1058 初始化;modbus.c 通过表读取 |
g_t_sunspec_default | sunspec_business.c:1260 写 operating_sta |
g_t_sunspec_run | sunspec_business.c:1060 memset、1308-1351 刷新 |
g_t_sunspec_inv_nameplate | sunspec_business.c:1257 由机型数组赋值,并被 ac_control.c 通过访问器读取 |
g_t_sunspec_ctlAC | sunspec_business.c 调度刷新;logic.c/ac_control.c 通过访问器读取 |
g_t_sunspec_bat | sunspec_business.c:1362-1365 刷新 |
g_t_sunspec_DC | sunspec_business.c:1397-1437 刷新 |
g_t_sunspec_AC_meter / _PV_meter | sunspec_business.c:1371-1381、1448-1449 刷新 |
本文件调用的外部内容
#include:sunspec_modbus.h、system_config.h、logic.h、inverter.h、meter.h、bms.h、modbus.h、alphaess_comm_usart.h、sdram.h(sunspec_modbus.c:13-21)。- 不调用任何函数;但在表内取地址引用了以下外部全局变量的成员,因此这些变量的生命周期/类型变更会直接破坏寄存器表:
g_t_system_base_info.s8_sn[16](system_config 模块)g_t_meter.t_grid_info.*/g_t_meter.t_pv_info.*(含t_data.s16_current/s16_factor/s32_actpower/s32_reactpower/u16_volt/u16_frequent/u32_energy_input/u32_energy_output/u32_total_phase_energy_*、u8_software_version[10]、u8_meter_sn[16]、u8_found)—— meter 模块g_t_inv_info.t_run_info.*(s32_inv_actpower_a/b/c、s32_inv_apparentpower_a/b/c、s32_inv_reactpower_a/b/c、s16_inv_cur_a/b/c、u16_inv_freq、u32_energy_inv_input/output)—— inverter 模块g_t_alphaess_comm_info.t_alphaess_run_info.*(u16_grid_volt_rs/r/rt/s/st/t、s16_bat_buckboost_temp、s16_inv_module_temp、s16_power_board_temp、s16_ctrl_board_temp)—— alphaess_comm_usart 模块g_t_logic_info.t_logic_control_parameter.*(s32_pinvt、s32_bat_charge_power_limit、s32_bat_dischg_power_limit)—— logic 模块g_t_bat_info.t_config_data.*(仅出现在 40354 的注释中)g_t_modbus_config.u16_sunspec_addr—— modbus 模块
6. 依赖关系
- 依赖:
sunspec_modbus.h及上节列出的 8 个头文件;运行期依赖system_config、logic、inverter、meter、bms、alphaess_comm_usart、modbus七个模块的全局变量。 - 被依赖:
midware/interface/modbus/sunspec_business.c—— 直接读写本文件所有表与实体;midware/interface/modbus/modbus.c—— 通过sunspec_modbus_data_get/_set间接使用(modbus.c:8222/8265/8320);- 间接(经
sunspec_business.h):logic/ac_control.c:1862-1912、logic/logic.c:3003-3004、logic/dispatch.c、midware/interface/interface.c:302-370、net/server.c:5169-5172、data/system_config/system_config.c:743-762。
- 编译单元关系:本文件与
sunspec_business.c必须同时编译,否则extern声明无法解析。
7. 备注与风险点
- 783 行表全部常驻普通 RAM:
sunspec_modbus.h:455中把数组放到 SDRAM 的__attribute__((at(SDRAM_SUNSPEC_DATA_ADDR)))被注释掉了。783 × 24 B ≈ 18.4 KB 的已初始化数据,既占 Flash(初值副本)也占内部 SRAM。 - 大小端标记与数值语义不自洽:字符串字段用
LIT_ENDIAN逐字交换字节后输出 ASCII,是 SunSpec 规范要求的"大端字符串 + 小端字内字节序"折中做法;但数值字段全用BIG_ENDIAN,而 32/64 位量在表里按+1(高字)在前、低字在后的顺序排列——即同一 u32 的高字寄存器地址更小,符合 SunSpec 规范,但与"从 40097 起的 4 个字依次+3/+2/+1/基址"的写法容易看错位。新增寄存器时极易插错高低字顺序。 RvrtTms/RvrtRem的高低字权限成对矛盾(除WsetRvrtTms外全部如此)。四组同构问题的实例:
| 组 | 高字(+1) | 低字(基址) | 结论 |
|---|---|---|---|
PFWinjRvrTms / PFWinjRvrtRem | 40282=RW、40284=RO | 40283=RO、40285=RW | 高字可写、低字只读 |
PFWAbsRvrtTms / PFWAbsRvrtRem | 40288=RW、40290=RO | 40289=RO、40291=RW | 同上 |
WMaxLimPctRvrtTms / WMaxLimPctRvrtRem | 40296=RW、40298=RO | 40297=RO、40299=RW | 同上 |
VarSetRvrtTms / VarSetRvrtRem | 40323=RW、40325=RO | 40324=RO、40326=RW | 同上 |
WsetRvrtTms / WsetRvrtRem | 40309=RW、40311=RO | 40310=RW、40312=RO | 一致(高字+低字均可写/均只读) |
由于 sunspec_modbus_data_set() 逐行判断权限位,主站写 40283(低字)会被静默忽略——即 32 位 PFWinjRvrtTms 实际只能改到高 16 位。任何小于 65536 秒(约 18.2 h)的回退时间都改不动,且写入不返回 Modbus 异常码,主站无从察觉。RvrtRem 反向(低字可写、高字只读)使得 Rem 的读取值永远保留复位初值 0 的高字,同样异常。四组共 10 个寄存器的权限位需要逐一复核。
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 个地址的读回值同时变化。- 无锁共享:主站写
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 字段)的可能。 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,属于该风险的暴露面。data_p类型统一为u16*,但有 s16/s32/u64 混用:读路径的函数返回u16,签名上的有符号信息丢失(如inv_apparent_power实际是 s32,主站需按 SunSpec 的 SF 规则自行解释符号)。这是 SunSpec 标准的做法,但表内没有校验s16/u16的一致性。g_t_sunspec_id[ID_1].model_len = 66等长度值是硬编码魔数:若上游结构体字段增减而忘记同步ID/LEN,主站扫描链会错位,且没有编译期断言保护(对比modbus.h:1479的MODBUS_CONFIG_INFO_T是有STATIC_ASSERT的)。40000/40001的 "SunS" 标识硬编码为两个字(0x5375→'S','u';0x6E53→'n','S'),若sunspec_star[5]的实际内容被运行时改写(sunspec_business.c:1052会memcpy"Suns"),会与这两个INIT行冲突——取决于调用顺序(初始化在启动期做,之后不再改写,故当前无碍)。- 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 帧刷新)。 - 文件头部注释仍写着
@file :sunspec_modbus.c但内容与sunspec_business.c的头部注释完全重复(作者 mike、日期缺失),说明两个文件由同一模板拷贝而来,无功能影响但降低可读性。 - 寄存器上限常量与实际表长不一致:
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,容易被主站误判为有效数据。