简介:这份PDF文档面向光伏储能系统开发者、嵌入式工程师与逆变器运维人员,系统整理了Solax SK-SU系列混合太阳能逆变器的Modbus协议规范,解决设备远程监控与二次开发中寄存器定义不清、版本差异难追溯的问题。资源包共1个文件,为1.58MB的PDF,内容以协议说明与寄存器表为主,便于查阅与打印。文档从V1.0到V2.50逐版记录演进:EPS功能寄存器、Modbus TCP标准对接、远控模式寄存器、意大利自测与语言选择寄存器、uint16转int16格式调整、单写与保持寄存器、硬件开关标志读取、BMS警报、CAN通讯丢失、电池SOH与充电器输入能量等均有说明。协议部分明确Modbus TCP类型、502端口、帧格式与时间请求参数,并列出寄存器地址、读写属性、单位与数据格式,如序列号、启动电压阈值、安规类型等。已有87人学习,适合需要对接逆变器数据采集、远程控制或排错的技术人员参考。
1. Solax SK-SU 混合逆变器 Modbus 协议:从寄存器表到本地采集的完整路径
手里有一台 Solax SK-SU 系列混合储能逆变器,想把它接入自己的能源管理系统,第一道坎往往不是写代码,而是搞清楚它到底暴露了哪些寄存器、用什么功能码读、数据怎么换算。Solax SK-SU Hybrid Solar Inverter 的 Modbus Protocol 本质上是一份寄存器映射约定:逆变器作为从站,通过 RS485 或 Modbus TCP 把 PV 发电功率、电池 SOC、并网/离网状态、负载功率等运行数据放在固定地址上,上位机按功能码去轮询即可。这套东西解决的是「数据自主可控」的问题——不依赖厂商云平台,本地就能拿到秒级粒度的发电与储能数据,适合做家庭能源管理、电池充放电策略优化、发电量统计的从业者。下面按「先搞懂寄存器结构 → 再搭物理链路 → 再写采集代码 → 再避坑」的顺序讲透。
2. 读懂 SK-SU 的寄存器地图:功能码、地址段与数据换算
2.1 为什么先啃寄存器表而不是直接抄代码
很多人拿到逆变器第一反应是找现成的 Python 库,结果发现不同固件版本地址对不上,读出来的 SOC 是 6553 这种鬼数字。根因在于 Modbus 本身只定义了「怎么问」,没定义「问什么」——每个厂商自己排寄存器。Solax 的文档里通常把寄存器分成几段:输入寄存器(Input Register,功能码 04)放实时测量值,保持寄存器(Holding Register,功能码 03)放可读写的配置项。SK-SU 这类混合机型的实时数据基本都在输入寄存器区,地址从 0 开始编,但注意文档里写的地址可能是「协议地址」(从 0 起)也可能是「PLC 地址」(从 1 起),差一位就能让你读出一片零。
我一般会先做一件事:拿文档里的地址表,挑三个已知量——PV 电压、电池电压、电网电压——单独读一遍,用万用表或逆变器屏幕上的数值对照。对上了,说明地址偏移和字节序都对了,再往下铺开。这一步花十分钟,能省掉后面两小时的抓瞎。
2.2 关键寄存器段与换算系数
以 SK-SU 常见的映射为例(不同固件批次会有微调,务必以随机文档为准),核心数据分布大致如下:
| 数据项 | 功能码 | 协议地址(示例) | 数据类型 | 换算 |
|---|---|---|---|---|
| PV1 电压 | 04 | 0x0000 | U16 | ×0.1 V |
| PV1 电流 | 04 | 0x0001 | U16 | ×0.1 A |
| PV1 功率 | 04 | 0x0002 | U16 | ×1 W |
| 电池电压 | 04 | 0x0014 | U16 | ×0.1 V |
| 电池 SOC | 04 | 0x001C | U16 | ×1 % |
| 电网电压 | 04 | 0x0046 | U16 | ×0.1 V |
| 负载功率 | 04 | 0x004E | U16 | ×1 W |
| 并网状态 | 04 | 0x005A | U16 | 枚举 |
这里有两个高频翻车点。第一是数据类型:功率类数据经常是 32 位无符号整数(U32),占两个连续寄存器,高字在前还是低字在前取决于厂商,Solax 多数机型是高字在前(big-endian word order)。第二是换算系数,文档里写「×0.1」的意思是原始值除以 10,不是乘以 0.1 再取整,写代码时用浮点除,别用整数截断。
提示:如果读出来的值稳定地是真实值的 10 倍或 1/10,先查换算系数;如果是乱跳的大数,查字节序;如果全是 0,查地址偏移和从站号。
2.3 从站号、波特率与通信参数怎么定
SK-SU 出厂默认从站号通常是 1,波特率 9600,8 数据位,无校验,1 停止位(8N1)。但如果你并联了多台逆变器,或者接了 Meter 设备,从站号可能被改过。通信参数在逆变器菜单的「Advanced Settings → Communication」里能看到,改之前记下原值。RS485 接线用 A/B 两线,屏蔽层单端接地,别两端都接,否则地环流会让通信时断时续——这种玄学故障我见过太多次,最后都是接地问题。
3. 搭起物理链路:RS485 转以太网与 Modbus TCP 的取舍
3.1 两种接入方式的适用场景
SK-SU 本体一般带 RS485 口,部分型号带 Wi-Fi/LAN 模块走 Modbus TCP。直接读 RS485 的延迟最低,适合做闭环控制;走 Modbus TCP 省去布线,适合已经布好网线的家庭。我的建议是:如果要做电池充放电策略(比如根据电价时段控制充放),走 RS485 直连,别经过 Wi-Fi 模块,因为无线模块的转发延迟和丢包会让控制逻辑变得不可靠。
物理链路常见做法是:逆变器 RS485 → 串口服务器(如 USR-TCP232 系列)→ 局域网 → 上位机。串口服务器工作在 TCP Server 模式,上位机作为 TCP Client 连上去,发 Modbus TCP 帧。注意 Modbus TCP 和 Modbus RTU 的帧格式不同:TCP 帧多了 7 字节的 MBAP 头(事务 ID、协议 ID、长度、单元 ID),没有 CRC 校验;RTU 帧有 CRC16,没有 MBAP 头。用 pymodbus 时选对模式,选错了会一直超时。
3.2 串口服务器参数配置清单
配置串口服务器时,逐项核对下面这些参数,错一个都通不了:
- 工作模式:TCP Server
- 本地端口:502(Modbus 标准端口)或自定义高位端口
- 串口参数:9600 / 8 / None / 1,与逆变器一致
- 流控:None
- 心跳包:建议开启,30 秒一次,防止空闲断连
- 注册包:不需要
配完后用ping确认网络通,再用telnet <IP> <端口>确认 TCP 能连上。这两步过了,再上 Modbus 轮询工具。
3.3 用最小命令验证链路通不通
在写正式采集程序前,先用命令行工具发一帧读寄存器请求,确认能拿到数据。Linux 下可以用mbpoll,Windows 下用 Modbus Poll 试用版。以 mbpoll 为例:
# 读从站 1 的输入寄存器,起始地址 0,读 10 个,波特率 9600 # -m rtu 表示 Modbus RTU 模式,-a 1 是从站号,-r 0 是起始地址,-c 10 是数量 mbpoll -m rtu -a 1 -b 9600 -d 8 -s 1 -P none \ -t 4 -r 0 -c 10 /dev/ttyUSB0-t 4表示读输入寄存器(功能码 04),-t 3是保持寄存器。如果返回一串数值,说明链路和地址都对;如果返回No response,按「从站号 → 波特率 → A/B 是否接反 → 终端电阻」的顺序排查。A/B 接反是最常见的,症状是偶尔能通、大部分时候超时。
4. 写一个能长期跑的采集脚本:pymodbus 轮询与数据落库
4.1 轮询逻辑与超时重试设计
采集脚本的核心不是「读一次」,而是「稳定地读一万次」。我一般用 pymodbus 的同步客户端,配合显式超时和重试。下面是一个最小可用的采集骨架:
from pymodbus.client import ModbusSerialClient import time # 初始化 RTU 客户端,超时 1 秒,重试 3 次 client = ModbusSerialClient( port='/dev/ttyUSB0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, retries=3 ) def read_input(addr, count): """读输入寄存器,失败返回 None""" try: rr = client.read_input_registers(address=addr, count=count, slave=1) if rr.isError(): return None return rr.registers except Exception as e: print(f"read error at {addr}: {e}") return None def poll_once(): """一轮采集:PV 功率、电池 SOC、负载功率""" pv = read_input(0x0002, 1) # PV1 功率 soc = read_input(0x001C, 1) # 电池 SOC load = read_input(0x004E, 1) # 负载功率 if pv and soc and load: return { 'pv_power_w': pv[0], 'battery_soc': soc[0], 'load_power_w': load[0] } return None if __name__ == '__main__': client.connect() while True: data = poll_once() if data: print(data) time.sleep(5) # 5 秒一轮,别太快 client.close()逻辑说明:read_input把异常和 Modbus 错误都吞掉返回 None,避免单次失败打断整个循环;poll_once把三个关键量打包,任何一个读失败就整轮丢弃,防止写入半截数据。参数上,timeout=1对 9600 波特率读 1 个寄存器足够,retries=3能扛住偶发干扰。轮询间隔 5 秒是经验值——再快对逆变器 CPU 有压力,再慢做控制就不够用。
4.2 32 位数据的拼接与字节序处理
功率类数据经常是 U32,需要把两个寄存器拼起来。Solax 多数机型是高字在前:
def to_u32(regs): """高字在前拼接 32 位无符号整数""" return (regs[0] << 16) | regs[1] def to_u32_swapped(regs): """低字在前,部分固件用这种""" return (regs[1] << 16) | regs[0]怎么判断用哪个?读一个已知功率值,比如逆变器屏幕显示 3000W,如果to_u32算出 196608000 这种离谱数,换to_u32_swapped试试。别猜,用已知值验证。
4.3 数据落库与异常标记
采集到的数据建议落到时序库(InfluxDB、TDengine)或至少 SQLite。落库时给每条记录打两个标记:quality(0 正常,1 可疑)和raw(原始寄存器值)。quality用于标记那些换算后明显不合理的值(比如 SOC 超过 100、功率为负但逆变器没在放电),raw用于事后追溯字节序或系数问题。我吃过亏:早期没存原始值,后来发现某段时间 SOC 全是 0,根本没法判断是通信断了还是寄存器读错了。
5. 避坑与排查:SK-SU Modbus 采集的 5 个血泪教训
5.1 现象:读出来的值每隔几轮跳一次 0
原因:RS485 总线干扰或终端电阻缺失。SK-SU 的 RS485 口在长距离(超过 20 米)或与强电走同一线槽时,信号反射会导致偶发 CRC 错误,pymodbus 重试后可能返回上一次的缓存或 0。
解决:在总线两端各加 120Ω 终端电阻;RS485 线用双绞屏蔽线,屏蔽层单端接地;与 AC 动力线保持 20cm 以上距离。改完后如果还跳,把波特率降到 4800 试试。
5.2 现象:SOC 读出来是 6553 而不是 65
原因:换算系数用错。6553 是 65.53 的原始值,文档写「×0.01」你按「×0.1」算了,或者反过来。Solax 不同固件的 SOC 系数可能是 1、0.1 或 0.01。
解决:对照逆变器屏幕的 SOC 显示,反推系数。屏幕显示 65%,原始值 6553,系数就是 0.01。把这个验证步骤写进你的初始化脚本,每次换固件都跑一遍。
5.3 现象:Modbus TCP 连上了但读不到数据
原因:MBAP 头的单元 ID 填错,或者串口服务器工作在 RTU Over TCP 模式而客户端按标准 Modbus TCP 发帧。
解决:确认串口服务器模式。如果是「Modbus RTU Over TCP」,pymodbus 要用ModbusTcpClient但帧格式按 RTU 处理,或者干脆用ModbusSerialClient走socket。单元 ID 一般填从站号,但有些串口服务器要求填 0xFF 或 0。逐个试,别死磕一个。
5.4 现象:轮询频率一高,逆变器就断连
原因:SK-SU 的 Modbus 从站处理能力有限,连续高频请求会触发内部保护或缓冲区溢出。
解决:轮询间隔不低于 1 秒,单次请求的寄存器数量不超过 20 个。把需要的数据合并成连续地址段一次读完,减少请求次数。比如 PV 电压、电流、功率地址连续,一次读 3 个,别分三次读。
5.5 现象:脚本跑几天后内存涨到几个 G
原因:pymodbus 客户端在异常重连时没有释放旧连接,或者日志对象累积。
解决:在重试逻辑里显式client.close()再client.connect();日志用RotatingFileHandler限制大小;每 24 小时重启一次采集进程(用 systemd 的RuntimeMaxSec或 cron)。这是运维层面的后悔药,但真管用。
6. 进阶:用采集数据做电池充放电策略与验证
采集只是手段,用数据做决策才是目的。SK-SU 支持通过保持寄存器写入充放电命令,但写操作风险高,建议先在只读模式下跑一周,摸清电池的充放电曲线和 SOC 跳变规律,再考虑写入。
一个实用的进阶用法是「电价时段套利」:在低价时段(比如深夜)强制充电到 90%,高价时段放电到 30%。实现上,你需要读实时 SOC 和电网功率,判断当前是否处于目标时段,然后通过写保持寄存器设置Force Charge或Force Discharge模式。写之前务必确认寄存器地址和取值范围,写错可能触发逆变器保护停机。
验证策略是否生效,不能只看 SOC 变化,要交叉验证三个量:电池功率(寄存器读出)、电网功率(读出)、电表读数(如果有独立电表)。三者对不上,说明你的控制逻辑或采集有偏差。我一般会做一个简单的对账脚本,每天凌晨跑一次,把前一天的充放电量和电费节省算出来,连续三天对不上就回滚策略。
最后说个习惯:每次改寄存器地址或换算系数,先在注释里写清楚「依据哪份文档的哪一页、哪个固件版本」,再改代码。SK-SU 的固件更新后地址漂移是常事,没有注释,三个月后你自己都不记得为什么这么写。希望帮到你。
本文还有配套的精品资源,点击获取