最近项目上涉及到一些硬件设备,这些硬件设备是通过PLC来控制完成自动化的,而我们的项目是Java语言开发的,这就涉及到Java和PLC的通讯问题,之前没有接触过PLC,完全就不知道怎么去和PLC通讯,询问设备的厂家,厂家也只知道PLC的自动化编程,不知道怎么对外通讯,也咨询了一些搞PLC编程的人,他们也都是PLC之间的通讯,没做过和外部系统的通讯,没办法只能自己去查资料了解。
通过了解之后,发现PLC可以走开放式以太网的 Modbus Tcp 通讯,配置一个modbus tcp server服务,外部系统可以通过modbus tcp去和PLC进行通讯,取值或写值都可以,从而达到控制PLC的目的,下面说一下具体的配置。
PLC端的配置:
1、在PLC程序块中增加一个 modbus tcp server 服务
2、点击程序块,右键选择“库存储器”,建议从1000开始,防止地址冲突
3、设置你要控制的点,比喻说我要控制第1台电机和第3台电机启动,那我就在第1台电机设置一个 V101.0 的保持寄存器,在第3台电机设置一个 V105.0 的保持寄存器,并且都设置为常开状态
4、到此为止,PLC端配置完毕
Java端配置:
Java端的modbus-tcp调用,我们引用 jlibmodbus-1.2.9.7.jar 这个架包,通过这个架包去读取或设置PLC保持寄存器的值,从而达到控制PLC的目的
这里先对 V101.0 这个寄存器解释一下,V101.0 就是属于功能码03的V区寄存器,它是从下标0开始计算的,V0代表V区的第0个寄存器,V1代表V区的第1个寄存器,以此类推。而功能码03代表的V区是按照字来存放的,一个字有2个字节,一个字节有8位,一个寄存器有8位,所以一个字可以存放2个寄存器,所以V0和V1都是存放在V区第1个字里的,V0存放在高8位上,V1存放在低8位上。
寄存器都是按照二进制的8位来存放的,0代表开,1代表关。V101.0代表在V区第50个字里的第2个字节上存放的寄存器的第1位,0代表寄存器的第1位,7代表寄存器的第8位,寄存器的介绍到此为止,网上关于PLC的寄存器介绍都比较模糊,所以在此详细介绍一下。
jlibmodbus 功能码对应的方法:
下面上代码:
public class ModbusTcpUtil {
// 定义主机
private static ModbusMaster master = null;
// PLC地址
private static final String ip = "192.168.0.2";
// 默认端口,这里设置是默认端口502
private static final int port = Modbus.TCP_PORT;
// 从机地址
private static final int slaveId = 1;
static {
try {
// 设置主机TCP参数
TcpParameters tcpParameters = new TcpParameters();
// 设置TCP的ip地址
InetAddress adress = InetAddress.getByName(ip);
// TCP参数设置ip地址
tcpParameters.setHost(adress);
// TCP设置长连接
tcpParameters.setKeepAlive(true);
// TCP设置端口
tcpParameters.setPort(port);
// 创建一个主机
master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
// 设置自增的id
Modbus.setAutoIncrementTransactionId(true);
// 设置读取超时时间
master.setResponseTimeout(3000);
// 开启连接
master.connect();
} catch (Exception e) {
e.printStackTrace();
}
}
public static ModbusMaster getMaster() {
try {
// 主机是否在线
if (!master.isConnected()) {
// 开启连接
master.connect();
}
} catch (ModbusIOException e) {
e.printStackTrace();
}
return master;
}
/**
* @title: 获取保持寄存器的值
* @parem: offset V区字的地址
* @parem: quantity 字的数量
**/
public static void getSlaveV(int offset, int quantity) {
// 初始化
master = getMaster();
int[] registerValues = null;
try {
// 读取对应从机保持寄存器的数据
registerValues = master.readHoldingRegisters(slaveId, offset, quantity);
// 控制台输出
for (int value : registerValues) {
DecimalFormat decimalFormat = new DecimalFormat("0000,0000,0000,0000");
String value2 = decimalFormat.format(Long.valueOf(MutateUtil.toBin(value)));
System.out.println("===readHoldingRegisters===Address: " + offset++ + ", value: " + value + ", Value2: " + value2);
}
} catch (ModbusProtocolException e) {
e.printStackTrace();
} catch (ModbusNumberException e) {
e.printStackTrace();
} catch (ModbusIOException e) {
e.printStackTrace();
}
}
/**
* @title: 写入单个保持寄存器的值
* @parem: address V区字的地址
* @parem: value 字的值,值的内容是16位二进制转换的十进制
**/
public static void setSlaveV(int address, int value) {
// 初始化
master = getMaster();
try {
// 写入对应从机保持寄存器的数据
master.writeSingleRegister(slaveId, address, value);
} catch (ModbusProtocolException e) {
e.printStackTrace();
} catch (ModbusNumberException e) {
e.printStackTrace();
} catch (ModbusIOException e) {
e.printStackTrace();
}
}
// 二进制转换为十进制
public static int dectobin(String str) {
str = str.trim().replace(" ","");
int cnt = 0;
int sum = 0;
// 反转字符串
str = new StringBuilder(str).reverse().toString();
for (int i = 0; i < str.length(); i++) {
cnt++;
if (str.charAt(i) == '1') {
int mul = 1;
for (int j = 1; j < cnt; j++) {
mul *= 2;
}
sum += mul;
} else continue;
}
return sum;
}
// 十进制转换为二进制
public static String toBin(int num){
return conversion(num,1,1);
}
private static String conversion(int num, int diwei, int yiwei) {
// 如果num等于0,结果输出为0
if (num == 0){
return "0";
}
// 定义一个包含二进制、八进制、十六进制的表
char[] chs = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',};
// 定义一个临时容器
char[] arr = new char[32];
// 定义一个操作数组的指针
int pos = arr.length;
// 利用与低位最大值的方式取出低位,存到临时数组中
while(num != 0){
// --pos倒著往临时容器里存
arr[--pos] = chs[num & diwei];
// 无条件右移相应位数
num >>= yiwei;
}
StringBuffer stringBuffer = new StringBuffer();
// 转换后的结果
for( int x = pos; x < arr.length; x++) {
stringBuffer.append(arr[x]);
}
// 返回转换后的结果
return stringBuffer.toString();
}
public static void main(String[] args) {
// 设置 V101.0 的值,二进制0000 0000 0000 0001转换成十进制1,写入的值必须是十进制
setSlaveV(50, 1) ;
// 查询V区第50个字的值
getSlaveV(50, 1);
}
}
测试的结果:
到此为止,已经完成了Java和PLC之间的通讯,也完成了通过外部系统控制PLC的需求,特此记录一下,防止自己以后忘记了,便于追溯,也提供给有相同需求的程序员一个Demo,便于借鉴一二。