2021-01-06

博客园Logo
首页
新闻
博问
专区
闪存
班级

代码改变世界
搜索
注册
登录
返回主页
八卦程序
程序改变生活
博客园
首页
新随笔
联系
订阅
管理
使用Modbus4J进行RTU模式串口通信

Modus协议是由MODICON(现为施耐德电气公司的一个品牌)在1979年开发的,是全球第一个真正用于工业现场的总线协议,应用非常广泛,可谓大名鼎鼎。

理论性的东西就不多介绍了,推荐一本书《Modbus软件开发实战指南》,杨更更著,写得非常好,从理论到实战,手把手教你玩转Modbus,不过代码实战部分使用的是C#,笔者没练过这项武功,还是看一下Java中怎么应用吧,网上资料多用Modbus4J,就选它了。

Modbus4J源代码:https://github.com/infiniteautomation/modbus4j

Modbus4J没有提供底层串口驱动,因此需要先掌握一些Java串口编程的能力,快速入门可以参考笔者之前写的《Java串口编程例子》这篇文章,本文就是在此基础上进行的,也应用了其中的串口编程工具类代码。

创建项目
新建modbus4j项目,如下图所示:

源代码地址:https://github.com/wu-boy/modbus4j.git

测试步骤
实现底层串口驱动

Modbus4J没有提供底层串口驱动,因此使用串口工具类SerialPortUtils来打开和关闭串口,代码如下:

public class SerialPortUtils {

private static Logger log = LoggerFactory.getLogger(SerialPortUtils.class);

/**
 * 打卡串口
 * @param portName 串口名
 * @param baudRate 波特率
 * @param dataBits 数据位
 * @param stopBits 停止位
 * @param parity 校验位
 * @return 串口对象
 */
public static SerialPort open(String portName, Integer baudRate, Integer dataBits,
                              Integer stopBits, Integer parity) {
    SerialPort result = null;
    try {
        // 通过端口名识别端口
        CommPortIdentifier identifier = CommPortIdentifier.getPortIdentifier(portName);
        // 打开端口,并给端口名字和一个timeout(打开操作的超时时间)
        CommPort commPort = identifier.open(portName, 2000);
        // 判断是不是串口
        if (commPort instanceof SerialPort) {
            result = (SerialPort) commPort;
            // 设置一下串口的波特率等参数
            result.setSerialPortParams(baudRate, dataBits, stopBits, parity);
            log.info("打开串口{}成功", portName);
        }else{
            log.info("{}不是串口", portName);
        }
    } catch (Exception e) {
        log.error("打开串口{}错误", portName, e);
    }
    return result;
}

/**
 * 关闭串口
 * @param serialPort
 */
public static void close(SerialPort serialPort) {
    if (serialPort != null) {
        serialPort.close();
        log.warn("串口{}关闭", serialPort.getName());
    }
}

}
实现Modbus4J串口包装器接口

Modbus4J提供了串口包装器接口,但是没有提供实现,因此自己新建一个实现类SerialPortWrapperImpl,作用是为Modbus4J提供串口对象SerialPort和操作串口的方法,例如打开/关闭串口,获取串口输入/输出流等,核心代码如下:

模拟从站设备

RtuSlaveTest类模拟了一个地址为1的从站设备,使用串口“COM2“(请提前使用虚拟串口软件Virtual Serial Port Driver模拟出来COM1和COM2串口),通过ModbusFactory创建RtuSlave,然后模拟线圈状态、离散输入状态、保持寄存器和输入寄存器的数据,代码中有详细注释,代码如下:

public class RtuSlaveTest {

public static void main(String[] args) {
    createRtuSlave();
}

public static void createRtuSlave(){
    // 设置串口参数,串口是COM2,波特率是9600
    SerialPortWrapperImpl wrapper = new SerialPortWrapperImpl("COM2", 9600,
            SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, 0, 0);

    // Modbus工厂,可以创建RTU、TCP等不同类型的Master和Slave
    ModbusFactory modbusFactory = new ModbusFactory();

    final ModbusSlaveSet slave = modbusFactory.createRtuSlave(wrapper);

    // 这玩意网上有人叫做过程影像区,其实就是寄存器
    // 寄存器里可以设置线圈状态、离散输入状态、保持寄存器和输入寄存器
    // 这里设置了从站设备ID是1
    BasicProcessImage processImage = new BasicProcessImage(1);
    processImage.setInvalidAddressValue(Short.MIN_VALUE);
    slave.addProcessImage(processImage);

    // 添加监听器,监听slave线圈状态和保持寄存器的写入
    processImage.addListener(new MyProcessImageListener());

    setCoil(processImage);
    setInput(processImage);
    setHoldingRegister(processImage);
    setInputRegister(processImage);

    // 开启线程启动从站设备
    new Thread(() -> {
        try {
            slave.start();
        }
        catch (ModbusInitException e) {
            e.printStackTrace();
        }
    }).start();

    /*new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            // 间隔1秒修改从站设备1的保持寄存器数据
            updateHoldingRegister(slave.getProcessImage(1));
        }
    }, 1000, 1000);*/
}

private static void setCoil(ProcessImage processImage){
    // 模拟线圈状态
    processImage.setCoil(0, true);
    processImage.setCoil(1, false);
    processImage.setCoil(2, true);
}

private static void setInput(ProcessImage processImage){
    // 模拟离散输入状态
    processImage.setInput(0, false);
    processImage.setInput(1, true);
    processImage.setInput(2, false);
}

private static void setHoldingRegister(ProcessImage processImage){
    // 模拟保持寄存器的值
    processImage.setHoldingRegister(0,(short) 11);
    processImage.setHoldingRegister(1,(short) 22);
    processImage.setHoldingRegister(2,(short) 33);
}

private static void updateHoldingRegister(ProcessImage processImage){
    // 模拟修改保持寄存器的值
    processImage.setHoldingRegister(0, (short) RandomUtil.randomInt(0, 100));
    processImage.setHoldingRegister(1,(short) RandomUtil.randomInt(0, 100));
    processImage.setHoldingRegister(2,(short) RandomUtil.randomInt(0, 100));
}

private static void setInputRegister(ProcessImage processImage){
    // 模拟输入寄存器的值
    processImage.setInputRegister(0,(short) 44);
    processImage.setInputRegister(1,(short) 55);
    processImage.setInputRegister(2,(short) 66);
}

}
使用Modbus Poll测试模拟的从站设备

Modbus Poll和Modbus Slave分别是主站设备仿真工具和从站设备仿真工具,是Modbus开发最常用的两个测试软件,下载地址:https://www.modbustools.com/

网上最近出现了一个国产软件Mthings,能够同时支持模拟主从机功能,据说功能强大还有使用手册,免安装免费使用!笔者由于参考了《Modbus软件开发实战指南》这本书,就没使用Mthings,有兴趣的同学可以试用。

设置连接参数
下载安装后,打开连接参数进行设置,如下图所示:

RtuSlaveTest类使用了串口COM2来模拟从站设备,因此这里选择COM1,选择RTU模式,点击OK。

定义读写规则
选择菜单【Setup】->【Read/Write Definition…】,如下图所示:

设置从站设备地址为1,功能码03是读取保持寄存器数据,寄存器地址为0,数量为3,因为RtuSlaveTest程序中模拟了3个数据,点击OK,如下图所示:

可以看到读取到了RtuSlaveTest程序中模拟的3个寄存器的数据,注意别忘了先启动RtuSlaveTest程序!

选择不同的功能码就可以读取不同的数据,01读取线圈状态,02读取离散输入状态,03读取保持寄存器,04读取输入寄存器。

模拟主站设备

实际开发中可能更多的是开发主站设备程序,RtuMasterTest代码如下:

public class RtuMasterTest {

public static void main(String[] args) throws Exception{
    createRtuMaster();
}

private static void createRtuMaster() throws Exception{
    // 设置串口参数,串口是COM1,波特率是9600
    SerialPortWrapperImpl wrapper = new SerialPortWrapperImpl("COM1", 9600,
            SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE, 0, 0);
    ModbusFactory modbusFactory = new ModbusFactory();
    ModbusMaster master = modbusFactory.createRtuMaster(wrapper);
    master.init();

    // 从站设备ID是1
    int slaveId = 1;

    // 读取保持寄存器
    readHoldingRegisters(master, slaveId, 0, 3);
    // 将地址为0的保持寄存器数据修改为0
    writeRegister(master, slaveId, 0, 0);
    // 再读取保持寄存器
    readHoldingRegisters(master, slaveId, 0, 3);
}

private static void readHoldingRegisters(ModbusMaster master, int slaveId, int start, int len) throws Exception{
    ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len);
    ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);
    if (response.isException()){
        System.out.println("读取保持寄存器错误,错误信息是" + response.getExceptionMessage());
    }else {
        System.out.println("读取保持寄存器=" + Arrays.toString(response.getShortData()));
    }
}

private static void writeRegister(ModbusMaster master, int slaveId, int offset, int value) throws Exception{
    WriteRegisterRequest request = new WriteRegisterRequest(slaveId, offset, value);
    WriteRegisterResponse response = (WriteRegisterResponse) master.send(request);
    if (response.isException()){
        System.out.println("写保持寄存器错误,错误信息是" + response.getExceptionMessage());
    }else{
        System.out.println("写保持寄存器成功");
    }
}

}
先启动RtuSlaveTest从站设备模拟程序,再启动RtuMasterTest主站设备模拟程序,可以看到双方控制台均有预期输出,RtuMasterTest能够读写RtuSlaveTest中的数据。

参考资料
1、初探ModBus4j简单使用指南
2、使用java的modbus4j的Rtu方式获取监测数据
3、Modbus java slave app

分类: Java
标签: Modbus
好文要顶 关注我 收藏该文
八卦程序
关注 - 0
粉丝 - 0
+加关注
1 0
« 上一篇: Java串口编程例子
posted @ 2021-01-04 21:37 八卦程序 阅读(49) 评论(1) 编辑 收藏

评论列表
#1楼 2021-01-04 23:50 TYR
有使用netty+rxtx实现的经验吗?我目前用这种方式。
后期想用jssc替换掉,因为netty实现,代码提示过期。。。
支持(0) 反对(0)
刷新评论刷新页面返回顶部
登录后才能发表评论,立即 登录 或 注册, 访问 网站首页
【推荐】News: 大型组态、工控、仿真、CADGIS 50万行VC++源码免费下载
【推荐】有你助力,更好为你——博客园用户消费观调查,附带小惊喜!
【推荐】AWS携手博客园为开发者送福利,注册立享12个月免费套餐
【推荐】七牛云新老用户同享 1 分钱抢 CDN 1TB流量大礼包!
【推荐】了不起的开发者,挡不住的华为,园子里的品牌专区
【推荐】未知数的距离,毫秒间的传递,声网与你实时互动
【推荐】新一代 NoSQL 数据库,Aerospike专区新鲜入驻

相关博文:
· modbus4j中使用modbustcp/ip和modbusrtuovertcp/ip模式
· modbus-RTU-crc16——c语言
· 西门子1200与陀螺仪和称重传感器的RTU轮询通讯
· ArrayList实现原理(JDK1.8)
· 十分钟掌握Pandas(上)——来自官网API
» 更多推荐…

最新 IT 新闻:
· 蛋壳之后,房企们能否成为长租公寓的“解药”?
· 高通骁龙480发布:采用8nm工艺 处理器性能相比上一代翻倍
· 第九城市股价涨逾50%触发熔断,公告称将开展数字货币业务
· 不造整车造部件 华为驾车向何方?
· NASA回顾2020年在国际空间站上进行的关键实验
» 更多新闻…
公告
昵称: 八卦程序
园龄: 2年6个月
粉丝: 0
关注: 0
+加关注
< 2021年1月 >
日 一 二 三 四 五 六
27 28 29 30 31 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31 1 2 3 4 5 6
搜索

找找看

谷歌搜索
我的标签
Modbus(1)
串口编程(1)
随笔分类
Java(2)
随笔档案
2021年1月(2)
最新评论

  1. Re:使用Modbus4J进行RTU模式串口通信
    有使用netty+rxtx实现的经验吗?我目前用这种方式。
    后期想用jssc替换掉,因为netty实现,代码提示过期。。。
    –TYR
    阅读排行榜
  2. 使用Modbus4J进行RTU模式串口通信(49)
  3. Java串口编程例子(7)
    评论排行榜
  4. 使用Modbus4J进行RTU模式串口通信(1)
    推荐排行榜
  5. 使用Modbus4J进行RTU模式串口通信(1)
    Copyright © 2021 八卦程序
    Powered by .NET 5.0 on Kubernetes
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值