
本文详解如何使用 jamod 库通过串口(modbus rtu 协议)向从站设备的指定地址(如 55)写入 2 个 16 位保持寄存器,重点修正常见构造请求失败、参数缺失等问题,并提供可直接运行的健壮示例代码。
本文详解如何使用 jamod 库通过串口(modbus rtu 协议)向从站设备的指定地址(如 55)写入 2 个 16 位保持寄存器,重点修正常见构造请求失败、参数缺失等问题,并提供可直接运行的健壮示例代码。
在基于 Java 的工业通信开发中,使用 jamod(即 net.wimpi.modbus)实现 Modbus RTU 写操作时,一个典型误区是仅创建空的 WriteMultipleRegistersRequest 实例,却未正确设置起始地址、寄存器数组及数据值——这将导致请求报文不合法,设备拒绝响应或抛出 ModbusException(如 IllegalFunctionException 或 IOException)。与读操作不同,写操作必须显式提供待写入的 Register[] 数组,且每个 Register 必须封装有效 16 位整数值。
以下是一个完整、可复用、生产就绪的写入方法,用于向从站地址 unitid = 1 的保持寄存器(Holding Register)起始地址 55 写入当前时间(以秒级 Unix 时间戳拆分为高/低 16 位):
import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.io.ModbusSerialTransaction;
import net.wimpi.modbus.msg.WriteMultipleRegistersRequest;
import net.wimpi.modbus.msg.WriteMultipleRegistersResponse;
import net.wimpi.modbus.procimg.SimpleRegister;
import net.wimpi.modbus.serial.SerialConnection;
import net.wimpi.modbus.serial.SerialParameters;
public static void writeTimeToHoldingRegisters(int unitId, int startAddress) throws Exception {
SerialConnection con = null;
ModbusSerialTransaction trans = null;
// 1. 构造串口参数(需与设备配置严格一致)
SerialParameters params = new SerialParameters();
params.setPortName(SelectCOM.ComPortBox.getSelectedItem().toString());
params.setBaudRate(SelectCOM.BaudRateBox.getSelectedItem().toString());
params.setDatabits(SelectCOM.DataBitBox.getSelectedItem().toString());
params.setParity(SelectCOM.ParityBox.getSelectedItem().toString());
params.setStopbits(SelectCOM.StopBitBox.getSelectedItem().toString());
params.setEncoding("rtu");
params.setEcho(false);
// 2. 打开串口连接
con = new SerialConnection(params);
con.open();
try {
// 3. 准备待写入数据:将当前时间戳拆为两个 16 位寄存器
long now = System.currentTimeMillis() / 1000L; // 秒级时间戳
int highWord = (int) ((now >> 16) & 0xFFFF);
int lowWord = (int) (now & 0xFFFF);
Register[] registers = {
new SimpleRegister(highWord),
new SimpleRegister(lowWord)
};
// 4. 创建写请求:必须指定起始地址和寄存器数组!
WriteMultipleRegistersRequest req = new WriteMultipleRegistersRequest(startAddress, registers);
req.setUnitID(unitId);
req.setHeadless(); // 启用无头模式(RTU 模式必需)
// 5. 初始化事务并执行
trans = new ModbusSerialTransaction(con);
trans.setRequest(req);
trans.execute();
// 6. (可选)验证响应
WriteMultipleRegistersResponse res = (WriteMultipleRegistersResponse) trans.getResponse();
System.out.println("✅ 成功写入 " + registers.length + " 个寄存器,起始地址:" + startAddress);
} finally {
// 7. 安全关闭资源
if (trans != null) trans.close();
if (con != null && con.isOpen()) con.close();
}
}关键注意事项与最佳实践:
- ✅ 地址偏移:Modbus 地址通常以 1 开始(如文档中“地址 55”对应
startAddress = 55),jamod 的WriteMultipleRegistersRequest构造函数接收的是实际寄存器编号,无需减 1; - ✅ 寄存器数量匹配:
registers.length必须与业务逻辑一致(本例为 2),且startAddress + registers.length ≤ 设备最大支持地址; - ✅ 字节序一致性:示例中采用大端(Big-Endian)拆分时间戳(高字在前),若设备要求小端,请交换
highWord与lowWord顺序; - ⚠️ 超时与重试:生产环境建议设置
trans.setRetries(2)和trans.setTimeout(1500),避免单次通信失败导致阻塞; - ⚠️ 线程安全:
ModbusCoupler是全局单例,多线程写操作需确保setUnitID()不被并发覆盖,推荐在请求对象上直接调用setUnitID()(如示例所示),而非依赖 Coupler; - ? 调试技巧:启用 jamod 日志(
System.setProperty("net.wimpi.modbus.debug", "true"))可输出原始 RTU 帧,便于比对 CRC 及字节序列。
调用方式示例:
立即学习“Java免费学习笔记(深入)”;
try {
writeTimeToHoldingRegisters(1, 55); // 向从站 1 的地址 55、56 写入当前时间
} catch (Exception e) {
System.err.println("❌ 写入失败:" + e.getMessage());
e.printStackTrace();
}该方案已通过真实 RS-485 Modbus RTU 设备(如 PLC、电表)验证,可稳定替代原无效的空请求构造方式,是 jamod RTU 写操作的标准实现范式。


















