Modbus——基于libmodbus开发

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测试通过。

  1. 库介绍

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中,每个数据发出的时间是可以计算的,多主站存在的串口竞争问题,谁先发谁后发谁管理。实验中,有时候每个从站都正常,有时候就存在大量丢包,工程中,就是一个线路一个主站,还有一种就是双串口冗余,双线一主,保证系统的稳定性,这时候串口也是一个工作一个等待。

猜你喜欢

转载自blog.csdn.net/soinlove36/article/details/131152261
今日推荐