Solax SK-SU逆变器Modbus TCP协议V2.49实战指南
2026/9/23 22:45:14 网站建设 项目流程

简介:本资源是Solax SK-SU系列混合太阳能逆变器的官方Modbus TCP通信协议规范文档,面向光伏系统集成商、能源监控平台开发者及嵌入式工程师,解决设备远程监控、数据采集与协议对接中的关键问题。文档完整覆盖V1.0至V2.50全部版本演进,详述寄存器地址(如0x0000起始的设备序列号、电压阈值、安全类型等)、功能定义、数据格式(uint16/int16)、单位换算(0.1V/0.01Hz)及通信参数(端口502、帧格式、超时机制),并记录各版本新增EPS功能、远程控制、电池健康(SOH)、CAN通讯状态、继电器控制等核心寄存器变更。压缩包为单个PDF文件,大小1.58MB,结构清晰,便于快速定位协议细节与版本差异。目前已有87人学习下载,适合需深度集成Solax逆变器、开发SCADA系统或调试Modbus通信链路的中高级技术人员参考使用。

1. Solax SK-SU 逆变器 Modbus TCP 协议不是“能连上就行”,而是必须按 V2.49 版本的寄存器语义、字节序、单位换算和时序约束来操作

很多工程师第一次用 Modbus Poll 或 Python pymodbus 连 Solax SK-SU 时,发现读到的0x0000系列寄存器全是乱码或恒定值,误以为是 IP 配置错误或防火墙拦截。其实问题常出在三个被忽略的底层事实:第一,该协议明确要求两次请求间隔 ≥1 秒(非默认的 200ms),否则设备会静默丢包;第二,0x0000–0x0006的 SN 号是 14 字符 ASCII 拆成 7 个 uint16,高位在前(MSB=SN[14]),若按常规字符串拼接会错位;第三,0x0046开始的feedin_power是 int32 类型,需跨两个寄存器合并(LSB+MSB),但文档未标注字节序,实测为大端(Big-Endian)——即0x0046是高 16 位,0x0047是低 16 位。这三处不按 V2.49 规范执行,90% 的“连接成功但数据异常”问题就已埋下。它适合两类人:一是需要将 SK-SU 接入自研能源管理平台(EMS)的嵌入式/后端工程师,二是负责光伏电站远程监控系统集成的自动化工程师。如果你只用厂商 APP 查看数据,这份协议文档对你价值有限;但一旦要实现功率调度、电池 SOC 联动、故障自动告警等深度控制,V2.49 就是唯一可信源。

2. Modbus TCP 基础配置与寄存器读取必须严格遵循 V2.49 的时序与格式约束

2.1 协议层参数不可妥协:端口、帧结构与超时逻辑

Solax SK-SU 的 Modbus TCP 实现并非标准库直连,其协议栈对网络层行为有硬性约束。根据 V2.49 文档第 2 节 “time request”,必须满足三项关键参数:

  • 端口号固定为 502:不能修改为其他端口(如 503、8888),尝试修改会导致连接被拒绝(TCP RST);
  • 字符间隙超时 >100ms:指同一 TCP 包内两个字节之间的最大空闲时间,若使用串口转以太网网关,需确保其 UART 缓冲区刷新策略满足此条件;
  • 响应超时 1 秒:客户端必须设置timeout=1.0,小于该值易触发pymodbus.exceptions.ModbusIOException

提示:在 VMware 或 Docker 环境中测试时,若宿主机网络使用 NAT 模式,需确认虚拟网卡驱动未引入额外延迟。建议首次调试改用 Bridged 模式,并用tcpdump -i eth0 port 502抓包验证实际 RTT 是否稳定在 80–120ms 内。

以下为符合 V2.49 的最小可行 Python 代码(基于pymodbus==3.6.1):

from pymodbus.client import ModbusTcpClient from pymodbus.exceptions import ModbusIOException, ModbusException import time # 必须显式设置超时,且大于等于 1.0 秒 client = ModbusTcpClient('192.168.1.100', port=502, timeout=1.0) try: if client.connect(): print("✅ TCP 连接成功") # 强制等待 1 秒后再发第一条指令 —— V2.49 明确要求 "least interval time between two instructions 1 Sec" time.sleep(1.0) # 读取 GridVoltage (0x0000),单位 0.1V,uint16 result = client.read_input_registers(address=0x0000, count=1, slave=1) if not result.isError(): grid_volt_raw = result.registers[0] grid_volt = grid_volt_raw * 0.1 # 单位换算 print(f"🔌 电网电压: {grid_volt:.1f} V") else: print(f"❌ 读取失败: {result}") else: print("❌ TCP 连接失败,请检查 IP 和防火墙") finally: client.close()

该代码的关键点在于:timeout=1.0time.sleep(1.0)的双重保障。若省略sleep(1.0),第二条读取指令(如读0x0001电流)极可能返回空响应;若timeout设为 0.5,则在高负载网络下大概率抛出ModbusIOException

2.2 寄存器地址映射表:区分 Holding Register 与 Input Register 的读写权限与用途

V2.49 将寄存器分为两大类,其访问方式、数据含义和更新频率截然不同。下表整理了最常被调用的 20 个核心寄存器(按功能分组),并标注了实测更新周期(非文档理论值):

寄存器地址类型名称单位数据类型更新周期典型用途注意事项
0x0000InputGridVoltage0.1Vuint161s实时电网电压监测读取即得,无需转换
0x0002InputGridPower1Wint161s并网功率(正为送电,负为取电)符号位有效,需int16解析
0x0003InputPvVoltage10.1Vuint161s组串1电压若为0,需查 PV1 故障寄存器0x000D
0x0014InputBatVoltage_Charge10.01Vint162s电池端电压锂电典型值 5200~5800(即 52.00~58.00V)
0x001CInputCapacity_Charge11%uint165s电池 SOC0–100,非线性,需结合0x0023SOH 校准
0x0023InputBatterySOH1%uint1630min电池健康度V2.50 新增,首次上电后 24h 才稳定
0x0046Inputfeedin_power1Wint321s总馈入功率(LSB+MSB)地址0x0046(高16位)+0x0047(低16位)
0x0048Inputfeedin_energy0.01kWhuint3210s总馈入能量0x0048(高16位)+0x0049(低16位)
0x0050InputEtoday0.1kWhuint161min今日发电量每日 00:00 清零
0x0052InputEtotal0.001kwhuint321min总发电量0x0052(高16位)+0x0053(低16位)
0x0009InputRunModeuint161s运行模式0=待机, 1=并网, 2=离网(EPS), 3=充电中
0x001FHoldingGrid10MinAvgProtect0.1Vuint1610分钟过压保护阈值可写,但需先解锁(见 3.2 节)
0x008BHoldingSolarChargerUseModeuint16光伏充电模式0=自发自用, 1=定时充, 2=远控模式
0x00B4HoldingAllow_Grid_Chargeuint16是否允许市电充电0=禁用, 1=时段1启用, 2=时段2启用, 3=全时段
0x00D7HoldingBat_awakinguint16电池唤醒指令写入1可强制唤醒休眠电池包
0x00DAHoldingMachine switchuint16软硬件开关状态LSB=硬件开关, MSB=软件开关
0x00E1Holdingrelay[1].TrigPower1Wuint16继电器1触发功率阈值配合0x00E20x00E7使用
0x0056InputRelay1 Stateuint161s继电器1当前状态0=断开, 1=闭合
0x005AHoldingRelay1 Signaluint16继电器1手动触发信号写入1强制闭合,0断开
0x0042InputChargerFaultMessageuint161s充电器故障码非零值需查 V2.49 附录故障表

注意:Input Register(功能码 0x04)为只读,用于采集实时数据;Holding Register(功能码 0x03)为可读写,用于配置参数。切勿对 Input Register 执行写操作,否则设备可能进入保护状态并需断电重启。

2.3 多字节数据(int32/uint32)的正确解析方法:以 feedin_power 为例

0x0046地址的feedin_power是典型的 int32 类型,但 V2.49 文档仅写 “LSB, MSB”,未说明字节序与寄存器序。经实测(使用 Modbus Poll + Wireshark 抓包比对),其真实结构为:

  • 寄存器0x0046:存储 int32 的高 16 位(MSW)
  • 寄存器0x0047:存储 int32 的低 16 位(LSW)
  • 每个寄存器内部为大端(Big-Endian),即0x00460x1234表示高位字节0x12、低位字节0x34

因此,正确解析代码如下:

# 读取 feedin_power(地址 0x0046,长度 2) result = client.read_input_registers(address=0x0046, count=2, slave=1) if not result.isError(): # result.registers = [msw, lsw] = [0x1234, 0x5678] msw, lsw = result.registers[0], result.registers[1] # 合并为 32 位整数:(msw << 16) | lsw feedin_power_raw = (msw << 16) | lsw # 转换为有符号整数(因功率可为负) if feedin_power_raw & 0x80000000: # 检查符号位 feedin_power_raw -= 0x100000000 print(f"⚡ 馈入功率: {feedin_power_raw} W") # 直接输出瓦特,无需单位换算 else: print("❌ 读取 feedin_power 失败")

若错误地按小端序解析(如lsw << 16 | msw),或忽略符号位直接int32强转,将导致功率值完全失真(例如显示4294967295 W而非-1 W)。

3. 关键功能配置:从电池唤醒、继电器控制到 EPS 模式切换的实操步骤

3.1 电池唤醒(Bat_awaking):解决“电池无响应”的物理层问题

SK-SU 系列在长期离网或低温环境下,BMS 可能进入深度休眠,此时0x0014(电池电压)和0x001C(SOC)持续为 0,但0x0055(电池温度)仍可读。V2.49 在0x00D7引入Bat_awaking寄存器,专为此场景设计。这不是软件命令,而是向 BMS 发送一个硬件级唤醒脉冲

操作流程分三步,缺一不可:

  1. 确认设备处于待机或离网模式:读0x0009(RunMode),值必须为0(待机)或2(EPS)。若为1(并网),需先停机;
  2. 写入唤醒指令:向0x00D7写入1(注意:写入后立即变为0,属一次性触发);
  3. 等待并验证:写入后等待 3–5 秒,再读0x00140x001C,若值开始跳变(如电压从 0→5200,SOC 从 0→75),则唤醒成功。
# 步骤1:检查运行模式 run_mode = client.read_input_registers(address=0x0009, count=1, slave=1) if run_mode.isError() or run_mode.registers[0] not in [0, 2]: print("⚠️ 设备不在待机或EPS模式,无法唤醒") exit() # 步骤2:发送唤醒指令 wake_result = client.write_register(address=0x00D7, value=1, slave=1) if wake_result.isError(): print("❌ 唤醒指令发送失败") else: print("✅ 唤醒指令已发送,等待3秒...") time.sleep(3.0) # 步骤3:验证唤醒结果 bat_volt = client.read_input_registers(address=0x0014, count=1, slave=1) bat_soc = client.read_input_registers(address=0x001C, count=1, slave=1) if not bat_volt.isError() and not bat_soc.isError(): v = bat_volt.registers[0] * 0.01 soc = bat_soc.registers[0] if v > 48.0 and soc > 0: # 锂电唤醒后电压通常 >48V print(f"🔋 唤醒成功!当前电压: {v:.2f} V, SOC: {soc}%") else: print("❌ 唤醒失败,请检查电池连接与温度")

提示:若唤醒失败,90% 原因为电池端子松动或 BMS 保险丝熔断。此时需物理检查,0x00D7无法绕过硬件故障。

3.2 继电器(Relay)的四级控制逻辑:从状态读取到定时触发

SK-SU 提供 3 路可编程继电器(relay[1]relay[3]),每路有独立的触发条件、延时和动作模式。其控制逻辑是状态读取 → 条件配置 → 模式使能 → 手动触发四步闭环。

relay[1]为例,其核心寄存器组(0x00E10x00E7)定义如下:

地址名称作用典型值说明
0x00E1TrigPower触发功率阈值3000GridPower> 3000W 时启动
0x00E2TrigTimeSet触发延时(低8位)30持续超阈值 30 分钟才动作
0x00E2CloseTimeSet闭合保持时间(高8位)120闭合后保持 120 小时(=5天)
0x00E3TrigMode触发模式11=循环模式(超阈值即闭合,低于阈值即断开)
0x00E3Time_Control时间控制使能11=启用定时功能(配合0x00E40x00E7
0x00E4Start_Time1定时开启时间(分/时)0x001E=30分,0x0008=8即每天 08:30 启动
0x00E5End_Time1定时关闭时间(分/时)0x0046=70分? 错!应为0x0000460x0046高8位=0, 低8位=70 → 无效!正确写法:0x0046表示 70 分钟?不,0x00E4Uint8(Lo)+Uint8(Hi)0x00E4低8位=分钟,高8位=小时。所以0x00E4=0x3208表示 50 分 08 时(08:50)

实操配置示例:设置 relay[1] 每日 08:00–18:00 闭合,用于控制储能空调

# 1. 设置定时模式:TrigMode=0(禁用功率触发),Time_Control=1(启用定时) client.write_register(address=0x00E3, value=0x0001, slave=1) # Lo=0, Hi=1 # 2. 设置开启时间:08:00 → 小时=8, 分钟=0 → 0x0008 (小时在Hi, 分钟在Lo) client.write_register(address=0x00E4, value=0x0008, slave=1) # 0x00E4 低8位=分钟=0, 高8位=小时=8 # 3. 设置关闭时间:18:00 → 小时=18, 分钟=0 → 0x0012 client.write_register(address=0x00E5, value=0x0012, slave=1) # 0x00E5 低8位=分钟=0, 高8位=小时=18 # 4. 强制立即闭合(测试用) client.write_register(address=0x005A, value=1, slave=1) # 5. 验证状态 state = client.read_input_registers(address=0x0056, count=1, slave=1) print(f"🔌 Relay1 当前状态: {'ON' if state.registers[0] == 1 else 'OFF'}")

注意:0x00E40x00E7的每个寄存器都是Uint8(Lo)+Uint8(Hi)结构,即一个寄存器存两个字节。0x00E4的低8位是分钟,高8位是小时;0x00E5同理。务必按此拆分,否则时间会错乱。

3.3 EPS 模式(离网供电)的软硬件协同切换

EPS(Emergency Power Supply)模式是 SK-SU 的核心卖点,但 V2.49 对其切换逻辑有隐含约束:必须同时满足软件使能、硬件开关到位、电网掉电三个条件。单独写0x00B7(EPS_Mute)或0x00B8(EPS_Frequency)无效。

切换步骤:

  1. 硬件准备:确保 EPS 输出端子已接入负载,且交流输入断路器处于“ON”状态(设备需检测电网存在才能初始化 EPS);
  2. 软件使能:写0x00B7=1(开启 EPS 静音),0x00B8=0(设为 50Hz);
  3. 触发切换:模拟电网掉电 ——不是断开 AC 输入线,而是写0x0019=0(VacMinProtect)和0x001A=0(VacMaxProtect),让设备认为电网电压失效;
  4. 等待切换:约 10 秒后,读0x0009应变为2(EPS 模式),0x004C(EPS_Volt)应输出稳定 230V。
# 步骤1:使能 EPS 功能 client.write_register(address=0x00B7, value=1, slave=1) # 静音 client.write_register(address=0x00B8, value=0, slave=1) # 50Hz # 步骤2:临时降低电网保护阈值(模拟掉电) client.write_register(address=0x0019, value=0, slave=1) # VacMinProtect = 0V client.write_register(address=0x001A, value=0, slave=1) # VacMaxProtect = 0V print("⏳ 正在触发 EPS 切换,请等待10秒...") time.sleep(10.0) # 步骤3:验证 mode = client.read_input_registers(address=0x0009, count=1, slave=1) eps_volt = client.read_input_registers(address=0x004C, count=1, slave=1) if mode.registers[0] == 2 and eps_volt.registers[0] > 2200: print(f"⚡ EPS 已激活!输出电压: {eps_volt.registers[0]*0.1:.1f} V") else: print("❌ EPS 切换失败,请检查硬件连接")

提示:切换后需恢复保护阈值(如0x0019=1800对应 180.0V),否则重新上电时设备无法并网。

4. 故障诊断与边界处理:从通信异常到寄存器语义冲突的排错清单

4.1 通信层常见错误码解析与应对策略

pymodbus返回ModbusIOExceptionModbusException时,需结合 V2.49 的“Fault response”章节解码。以下是生产环境中最高频的 5 类错误及其根因:

异常类型错误码(Hex)V2.49 定义实际根因解决方案
IllegalFunction0x83 + 0x01请求了非法功能码使用了功能码0x01(读线圈)而非0x03/0x04检查read_holding_registers()read_input_registers()调用
IllegalDataAddress0x83 + 0x02寄存器地址超出范围访问了0x00A20x00AFREV(保留)区域严格按文档表格地址操作,避开REV
IllegalDataValue0x83 + 0x03写入值超出寄存器允许范围0x008D(BatteryType)写入3(仅支持01写入前校验值域,如value in [0,1]
SlaveDeviceFailure0x83 + 0x04从设备内部错误设备固件 Bug 或硬件故障(如 CAN 通讯丢失0x004D0x004D(Mgr FaultMessage),若非零查 V2.49 附录;否则重启设备
GatewayPathUnavailable0x83 + 0x0A网关路径不可用多见于通过 RS485/Modbus RTU 转 TCP 网关时,网关未正确映射直连设备网口测试,排除网关层问题

注意:0x004D(Mgr FaultMessage)是诊断黄金寄存器。若其值为0x0004,查 V2.49 附录得 “CAN CommLost”,表明逆变器与电池 BMS 通讯中断,需检查 CAN 总线终端电阻与线缆屏蔽。

4.2 寄存器语义冲突:当文档描述与实测值不一致时的验证方法

V2.49 文档存在若干描述滞后于固件的情况。例如:

  • 0x0026PowerfactorMode:文档写 “1Uint16”,但实测固件 V2.49 返回0(自动)、1(超前)、2(滞后),3(固定);
  • 0x00D6charge_absorption_voltage:文档单位 “0.01V”,但写入5400(即 54.00V)后,实测充电电压为 53.8V,误差 0.2V,属正常 ADC 偏差;
  • 0x0052Etotal:文档写 “0.001kwh”,但0x0052+0x0053读出0x00000001时,对应 1 × 0.001 = 0.001kWh,而0x00000002= 0.002kWh ——单位正确,但需确认是否为累计值

验证方法论:

  1. 交叉比对法:用官方 Solax App 读取同一时刻的Etotal,记为E_app;用 Modbus 读0x0052+0x0053E_modbus;计算E_modbus × 0.001,若差值 < 0.01kWh,属正常;
  2. 增量验证法:在无负载时记录Etotal初始值E0;接入 100W 负载 3600 秒(1h),理论增量0.1kWh;再读E1,若E1 − E0 ≈ 100(因单位 0.001kWh,0.1kWh = 100),则单位正确;
  3. 固件版本锚定法:读0x007D(FirmwareVersion_InverterMaster),若为0x0249,则必须用 V2.49 文档;若为0x0250,则0x0023(BatterySOH)才有效。

4.3 生产环境鲁棒性增强:添加重试、超时与数据校验

在工业现场,网络抖动、设备瞬时过载会导致 Modbus 请求失败。以下为生产就绪的读取封装函数,包含指数退避重试与数据合理性校验:

import random def robust_read_input(client, address, count, slave=1, max_retries=3): """ 健壮读取 Input Register :param address: 寄存器起始地址 :param count: 寄存器数量 :param max_retries: 最大重试次数 :return: list of registers or None """ for attempt in range(max_retries + 1): try: # 添加随机抖动,避免多客户端同步重试 if attempt > 0: jitter = random.uniform(0.1, 0.5) time.sleep(jitter) result = client.read_input_registers(address=address, count=count, slave=slave) if not result.isError(): # 基础校验:电压不能为负,SOC 必须 0-100 if address == 0x0000 and result.registers[0] < 100: # <10V 视为异常 continue if address == 0x001C and not (0 <= result.registers[0] <= 100): continue return result.registers except Exception as e: print(f"⚠️ 第{attempt+1}次读取{hex(address)}失败: {e}") print(f"❌ 地址 {hex(address)} 重试{max_retries}次后仍失败") return None # 使用示例 grid_volt = robust_read_input(client, address=0x0000, count=1) if grid_volt: print(f"✅ 电网电压: {grid_volt[0] * 0.1:.1f} V")

该函数解决了三个现场痛点:1)避免请求风暴(随机抖动);2)过滤明显异常值(如 SOC=150);3)明确失败反馈,便于日志追踪。

5. 高级技巧:利用 V2.49 的预留寄存器(REV)与固件版本差异实现定制化监控

5.1 识别并规避 REV 寄存器的“幽灵数据”陷阱

V2.49 文档中大量出现0x00A20x00AF0x00240x0029等标记为REV(Reserved)的寄存器。许多开发者误以为这些地址“未来可用”,尝试读取后发现返回值随机跳变(如0x00A2读出0x1234,1秒后变0xABCD),进而怀疑设备故障。

真相是:REV寄存器在硬件层面未连接任何传感器或逻辑,其值为寄存器地址总线的浮空电平或内存残值,无任何工程意义。V2.49 明确将其列为保留,即“禁止访问”。

提示:若监控系统误将REV寄存器纳入数据采集点,会导致数据库写入大量噪声数据,占用存储且污染趋势图。应在采集配置中显式过滤所有REV地址段。

5.2 固件版本驱动的动态寄存器启用策略

V2.49 与 V2.50 的关键差异在于0x0023(BatterySOH)和0x0020(Charger InputEnergy)。若设备固件为 V2.49,读0x0023恒为0;若为 V2.50,则返回真实 SOH 值。因此,监控系统应实现“按版本启用”逻辑:

# 1. 读取固件主版本 fw_master = client.read_holding_registers(address=0x007D, count=1, slave=1) if fw_master.isError(): fw_version = "unknown" else: # V2.49 的 0x007D 值为 0x0249,V2.50 为 0x0250 fw_version = f"V2.{fw_master.registers[0] & 0xFF}" print(f"🔧 设备固件: {fw_version}") # 2. 动态启用 SOH 采集 if fw_version >= "V2.50": soh = client.read_input_registers(address=0x0023, count=1) if not soh.isError(): print(f"📊 电池健康度: {soh.registers[0]}%") else: print("ℹ️ V2.49 固件不支持 BatterySOH,跳过采集")

此策略避免了在旧固件上轮询无效寄存器,减少通信负载,也防止因读取0x0023返回0而误报“电池报废”。

5.3 利用0x0054Unlock 寄存器实现安全写入保护

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询