Modbus是常用的串行总线协议,可以在232、422、485和以太网上传输。
用户可以参考GB/T 19582系列国标,Modbus国际标准的中文版。
本文面对modbus进阶用户,文中代码为实际工程代码的改写,相关基础知识参看国标。
工具:
ModbusTool:ClassicDIY/ModbusTool 这个工具分两个,开源,分master和slave,基本可以完成全部modbus应用开发。我这次就用此工具完成。
mdbus:Mdbus for Windows 这个工具早年间一直使用。现在收费,好处就是hold regs和input regs可以放在一起,调试起来不用分FC 3和4的区别。
串口调试助手:windows store里面可以获取,基本够用。
ModbusPoll及ModbusSlave也是国内用的很多的,里面支持PLC模式,也就是5位地址位。
库:这次使用LibModbus: libmodbus 这个库在linux中基本都有,树莓派也内置了。
开发环境: 本系列文都是基于树莓派的modbus开发。
注:Modbus 协议栈自己可以完成,3000行左右(看需要的功能和FC数量)。从站开发和测试一天。
硬件环境:2个UT-890 usb-485,相关例程使用树莓派uart测试通过。
- 库介绍
libmodbus是一个可以运行在Linux、MacOs X、FreeBSD、QNX和Win的Modbus库。树莓派的Pi OS也支持,安装如下:
sudo apt update
sudo apt install libmodbus-dev
2.主站开发
下列代码为modbus 主站读取从站1的输入寄存器代码。
#include <stdio.h>
#include <modbus.h>
/*
* 功能:本函数完成modbus主站,读取input regs
*/
int modbusRTUMaste1(){
modbus_t* ctx;
int slaveAddr=1;
//建立modbus buf
uint8_t dest[1024]; //setup memory for data
uint16_t * dest16 = (uint16_t *) dest;
memset(dest, 0, 1024);
//初始化Modbus实例
ctx = modbus_new_rtu("/dev/uart1", 9600, 'N', 8, 1);// 使用9600-N-8,1个停止位
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "Connexion failed: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
modbus_set_slave(ctx, slaveAddr);//设置从站地址,当前为1
modbus_set_response_timeout(ctx, 1, 0);//设置响应时间
/*
* modbus_read_bits 读线圈 fc=1
* modbus_read_input_bits 读输入线圈 fc=2
* modbus_read_registers 读取保持寄存器 fc=3
* modbus_read_input_registers 读输入寄存器 fc=4
* modbus_write_bit 写一位数据(强置单线圈) fc=5
* modbus_write_register 写单寄存器(预置单寄存器) fc=6
* modbus_write_bits 写多位数据(强置多线圈) fc=15
* modbus_write_and_read_registers 读/写多个寄存器 fc=23
*/
int regs = modbus_read_input_registers(ctx/*libmodbus实例*/,
0/*输入地址*/,
8/*读取输入寄存器个数*/,
dest16 /*获取从站值*/);
modbus_close(ctx);
modbus_free(ctx);//关闭 清理 modbus ctx
return 0;
}
测试中input regs是只读,hold regs是可读写。
还有一个是dest类型,线圈用uint8t *,寄存器用uint16_t *。
下例为Modbus TCP 主站代码
#include <stdio.h>
#include <modbus.h>
int ModbusTCPMaster1(void) {
modbus_t *ctx;
uint16_t tab_reg[32];
ctx = modbus_new_tcp("127.0.0.1", 1502);
modbus_connect(ctx );
/* Read 16 registers from the address 16 */
modbus_read_registers(ctx, 16, 16, tab_reg);
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
3.从站开发
下例是从站代码 libmodbus从站代码比较简单。用户需要控制htsmb_mapping 中的值,主站如果读,,
/*
* 功能:本函数完成modbus从站
*/
int modbusRTUSlave(){
modbus_t* ctx;
int slaveAddr=13;
uint8_t querySlave[MODBUS_RTU_MAX_ADU_LENGTH];//接收数组
/*libmodbus 从站使用modbus_mapping结构来处理需要主站读取的参数,包括4个参数
* 如果不需要提供相关存储,设置为0,主站就不可以访问。
*/
modbus_mapping_t* htsmb_mapping = modbus_mapping_new(
MODBUS_MAX_READ_BITS,
MODBUS_MAX_WRITE_BITS,
MODBUS_MAX_READ_REGISTERS,
MODBUS_MAX_WRITE_REGISTERS
);
//初始化hold regs
htsmb_mapping[_port]->tab_registers[0] = 1;
htsmb_mapping[_port]->tab_registers[1] = 2;
htsmb_mapping[_port]->tab_registers[2] = 3;
htsmb_mapping[_port]->tab_registers[3] = 4;
//初始化input regs
htsmb_mapping[_port]->tab_input_registers[3] = 11;
//初始化Modbus实例
ctx = modbus_new_rtu("/dev/uart1", 9600, 'N', 8, 1);// 使用9600-N-8,1个停止位
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "Connexion failed: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return FALSE;
}
modbus_set_slave(ctx, slaveAddr);//设置从站地址,当前为13
int rc = modbus_receive(ctx, querySlave);//querySlave 保存收到的数据包 主站发送过来的
modbus_reply(ctx, querySlave, rc, htsmb_mapping);//处理数据包,对htsmb_mapping 进行读写操作
modbus_free(ctx);//关闭 modbus ctx
return 0;
}
4.优化:
- 如何压榨主站时延
modbus 主站发送一般使用两种方式,一种是周期轮询,比如5秒一次,这时候轮询会存在一段时间进程在sleep。还有一种就是最快速度轮询,这里面只要从站响应了,主站就开始新的访问。如果响应时间过长,实际造成系统时延增长。一般建议设置为1秒,我们遇到过最长的从站系统响应可以到14秒,这种情况下,一个串口接一个设备最好。用户可以使用modbus_set_response_timeout设置响应时间,第二参数就是秒值。
- 如何提高从站响应时间
modbus从站响应时间受制于逻辑结构,在libmodbus中,用户可以在htsmb_mapping进行计算,也可以在modbus_receive后用memcpy更新htsmb_mapping。
5.应用场景——2000个数据如何获取
主站获取从站2000个数据一般应用很少,我们遇到过是800个,Modbus每一帧传输大小在124个int16,参看libmodbus各个参数定义。解决方法就是多帧访问,比如说每次访问100个,依次遍历20次就把2000个数据都读取了。
6.应用场景——多从站访问
多从站访问,一般基于多个传感器组,一个组20-100个点,一次十组,实际也可以到200-2000个点的访问,主站需要对从站遍历,用户需要对设备响应时间配置,现场需要按秒表测试以下。
7.应用场景——Modbus 在如果在优化系统中使用
用户可以通过modbus获取就地端数据,并利用历史数据和现场数据,利用算法对控制系统进行优化。
8.应用场景——如果DCS和PLC
在一般场景中,DCS之间、PLC之间、DCS与PLC之间通过modbus进行通信已经成为常态,这里面最主要就是各类信息点的传输,对于这些点,实时性要求会低很多,在组网的时候,一定需要弄清楚各个系统对modbus支持情况,或者需要增加什么设备。比如早期有些DCS系统不支持从站,那么对联的系统需要确认是否支持主站。
在比如双方都支持主站,没有从站,做过一个解决方案,就是用modbus模拟器对接两个主站系统,有些模拟器中的内存空间是复用的,两个系统一个读一个写,也可以把数据进行传递。这时候只需要考虑模拟器稳定性就可以了。
当前DCS/PLC互联中,更多的是modbus tcp,这种方案反而更容易进行对接。这里不做累述。
问题中有单线多主站的问题,在modbus rtu中,每个数据发出的时间是可以计算的,多主站存在的串口竞争问题,谁先发谁后发谁管理。实验中,有时候每个从站都正常,有时候就存在大量丢包,工程中,就是一个线路一个主站,还有一种就是双串口冗余,双线一主,保证系统的稳定性,这时候串口也是一个工作一个等待。