当前位置:   article > 正文

Modbus——基于libmodbus开发_modbuslib库

modbuslib库

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也支持,安装如下:

  1. sudo apt update
  2. sudo apt install libmodbus-dev

2.主站开发

下列代码为modbus 主站读取从站1的输入寄存器代码。

  1. #include <stdio.h>
  2. #include <modbus.h>
  3. /*
  4. * 功能:本函数完成modbus主站,读取input regs
  5. */
  6. int modbusRTUMaste1(){
  7. modbus_t* ctx;
  8. int slaveAddr=1;
  9. //建立modbus buf
  10. uint8_t dest[1024]; //setup memory for data
  11. uint16_t * dest16 = (uint16_t *) dest;
  12. memset(dest, 0, 1024);
  13. //初始化Modbus实例
  14. ctx = modbus_new_rtu("/dev/uart1", 9600, 'N', 8, 1);// 使用9600-N-8,1个停止位
  15. if (modbus_connect(ctx) == -1) {
  16. fprintf(stderr, "Connexion failed: %s\n", modbus_strerror(errno));
  17. modbus_free(ctx);
  18. return -1;
  19. }
  20. modbus_set_slave(ctx, slaveAddr);//设置从站地址,当前为1
  21. modbus_set_response_timeout(ctx, 1, 0);//设置响应时间
  22. /*
  23. * modbus_read_bits 读线圈 fc=1
  24. * modbus_read_input_bits 读输入线圈 fc=2
  25. * modbus_read_registers 读取保持寄存器 fc=3
  26. * modbus_read_input_registers 读输入寄存器 fc=4
  27. * modbus_write_bit 写一位数据(强置单线圈) fc=5
  28. * modbus_write_register 写单寄存器(预置单寄存器) fc=6
  29. * modbus_write_bits 写多位数据(强置多线圈) fc=15
  30. * modbus_write_and_read_registers 读/写多个寄存器 fc=23
  31. */
  32. int regs = modbus_read_input_registers(ctx/*libmodbus实例*/,
  33. 0/*输入地址*/,
  34. 8/*读取输入寄存器个数*/,
  35. dest16 /*获取从站值*/);
  36. modbus_close(ctx);
  37. modbus_free(ctx);//关闭 清理 modbus ctx
  38. return 0;
  39. }

测试中input regs是只读,hold regs是可读写。

还有一个是dest类型,线圈用uint8t *,寄存器用uint16_t *。

下例为Modbus TCP 主站代码

  1. #include <stdio.h>
  2. #include <modbus.h>
  3. int ModbusTCPMaster1(void) {
  4. modbus_t *ctx;
  5. uint16_t tab_reg[32];
  6. ctx = modbus_new_tcp("127.0.0.1", 1502);
  7. modbus_connect(ctx );
  8. /* Read 16 registers from the address 16 */
  9. modbus_read_registers(ctx, 16, 16, tab_reg);
  10. modbus_close(ctx);
  11. modbus_free(ctx);
  12. return 0;
  13. }

3.从站开发

下例是从站代码 libmodbus从站代码比较简单。用户需要控制htsmb_mapping 中的值,主站如果读,,

  1. /*
  2. * 功能:本函数完成modbus从站
  3. */
  4. int modbusRTUSlave(){
  5. modbus_t* ctx;
  6. int slaveAddr=13;
  7. uint8_t querySlave[MODBUS_RTU_MAX_ADU_LENGTH];//接收数组
  8. /*libmodbus 从站使用modbus_mapping结构来处理需要主站读取的参数,包括4个参数
  9. * 如果不需要提供相关存储,设置为0,主站就不可以访问。
  10. */
  11. modbus_mapping_t* htsmb_mapping = modbus_mapping_new(
  12. MODBUS_MAX_READ_BITS,
  13. MODBUS_MAX_WRITE_BITS,
  14. MODBUS_MAX_READ_REGISTERS,
  15. MODBUS_MAX_WRITE_REGISTERS
  16. );
  17. //初始化hold regs
  18. htsmb_mapping[_port]->tab_registers[0] = 1;
  19. htsmb_mapping[_port]->tab_registers[1] = 2;
  20. htsmb_mapping[_port]->tab_registers[2] = 3;
  21. htsmb_mapping[_port]->tab_registers[3] = 4;
  22. //初始化input regs
  23. htsmb_mapping[_port]->tab_input_registers[3] = 11;
  24. //初始化Modbus实例
  25. ctx = modbus_new_rtu("/dev/uart1", 9600, 'N', 8, 1);// 使用9600-N-8,1个停止位
  26. if (modbus_connect(ctx) == -1) {
  27. fprintf(stderr, "Connexion failed: %s\n", modbus_strerror(errno));
  28. modbus_free(ctx);
  29. return FALSE;
  30. }
  31. modbus_set_slave(ctx, slaveAddr);//设置从站地址,当前为13
  32. int rc = modbus_receive(ctx, querySlave);//querySlave 保存收到的数据包 主站发送过来的
  33. modbus_reply(ctx, querySlave, rc, htsmb_mapping);//处理数据包,对htsmb_mapping 进行读写操作
  34. modbus_free(ctx);//关闭 modbus ctx
  35. return 0;
  36. }

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

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号