当前位置:   article > 正文

ModBus协议原理、Modbus Slave以及基于C++和Qt的代码实现_qt modbus c++代码

qt modbus c++代码

ModBus 协议目的:

  • 规定与PLC交互的指令,其数据帧包括两部分:报文头(MBAP)帧结构(PDU)

报文头(MBAP)(分为6个部分):

  • 1. 事务处理标识:即报文序列号,一般每次通信之后就要加1以区别不同的通信数据报文长度2字节
  • 2. 协议标识符:有串口的RTU协议和TCP协议,如0000表示ModbusTCP协议长度2字节
  • 3. 长度:单元标识符字节长度、功能码字节长度、数据域字节长度三者的总和长度2字节
  • 4. 设备地址:长度1字节
  • 5. 功能码:包含有:0x01 读继电器/线圈、0x05 写继电器/线圈、0x0F 写多个继电器/线圈、0x02 读离散量输入、0x04 读输入寄存器、0x03 读保持寄存器数据、0x06 写单个保持寄存器、0x10 写多个保持寄存器长度1字节
  • 6. 具体参数:长度不定

帧结构(PDU)(主要是将上面的报文头进行举例)

  • 示例1:读取继电器/线圈的帧结构,由事务处理标识+协议标识符+长度+设备地址+功能码+两个字节的起始地址+两个字节的读取个数组成,假设现在要读取从0开始的10个继电器/线圈的状态,则指令为:0000 0000 0006 01 01 0000 000A
  • 示例2:写单个继电器/线圈的帧结构,事务处理标识+协议标识符+长度+设备地址+功能码+两个字节的地址+两个字节的目标状态组成,假设现在要打开地址0线圈,则指令为:0000 0000 0006 01 05 0000 FF00
  • 示例3:读取离散量输入的帧结构,事务处理标识+协议标识符+长度+设备地址+功能码+两个字节的地址+两个字节的读取个数组成,假设现在要读取从0开始的10个离散输入的状态,则指令为:0000 0000 0006 01 02 0000 000A
  • 示例4:读取保持寄存器的帧结构,事务处理标识+协议标识符+长度+设备地址+功能码+两个字节的地址+两个字节的读取个数组成,假设现在要读取从0开始的10个保持寄存器的状态,则指令为:0000 0000 0006 01 03 0000 000A
  • 需要注意的是:十六进制的两位比如FF(255)就是一个字节,而四位比如FFFF(65535)则占两个字节。

软件仿真

  • 软件介绍:Modbus Slave是模拟Modbus协议从机的上位机软件。该软件内部封装标准Modbus协议栈,通过图形化界面使得操作更为简便。与之成套存在的另一个软件--Modbus Poll则是一个模拟Modbus协议主机的上位机软件,主要用于模拟测试跟其他从机设备通信的过程。目前软件支持01、02、03、04、05、06、15、16功能码,异常报文检测,原始报文查看,数据记录等功能,是调试Modbus协议栈的好帮手。
  • 下载链接:链接:https://pan.baidu.com/s/1KjFEPxGgvKyT5APBGlLq2g 提取码:txrz 

使用:

  • 1. 首先点击Connection连接PLC,这里我们选择TCP/IP协议(注:选择"Serial Port",表示当前是用串口通信,如果使用的是Modbus/TCP,则选择“TCP/IP)。

  • 2. 点击Setup的Slave Definition,可以设置如下参数:Slave ID:可以配置从机地址。Function:可以配置功能码。Address:可以配置读/写的寄存器/线圈起始地址。Quantity:可以配置读/写的寄存器/线圈个数。Scan Rate:可以配置帧的扫描周期。Rows:可以选择该窗口一列可以显示多少行,数字是对应的行数,最后一个选项"Fit to Quantity"是可以根据前面设置的"Quantity"数量自动匹配行数。Hide Alias Columns:可以选择是否隐藏"Alias"列。PLC Addresses(Base 1):可以选择通信的首地址是从0开始还是从nX开始

  • 3. 双击数据的位置,可快速调出编辑写指令的窗口,输入需要修改的数值,点击OK,即可完成一次写入。此外,右键可以修改显示的数据类型。

  • 4. 最后,关于界面的状态显示:ID:表示当前窗口通信的从机地址(Slave ID);F:表示当前窗口的功能码(Function);No Connection等红字:表示当前窗口的异常通信状态。

Qt 5.13.2的实现,以TCP为例:

  • 1. 在pro文件中添加:QT += serialbus 
  • 2. 引入头文件:#include <QModbusTcpClient>   注意:软件是客户端,PLC是服务端,这个得搞清楚
  • 3. .h文件代码:
  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3. #include <QMainWindow>
  4. #include <QModbusTcpClient> // 软件是客户端,PLC是服务端,这个得搞清楚
  5. namespace Ui {
  6. class MainWindow;
  7. }
  8. class MainWindow : public QMainWindow
  9. {
  10. Q_OBJECT
  11. public:
  12. explicit MainWindow(QWidget *parent = nullptr);
  13. ~MainWindow();
  14. private slots:
  15. void on_pushButton_clicked();
  16. void on_pushButton_2_clicked();
  17. void replyData(); //读取异步槽函数
  18. private:
  19. Ui::MainWindow *ui;
  20. QModbusTcpClient *client; // 声明一个客户端实例作为子类成员
  21. };
  22. #endif // MAINWINDOW_H
  • 4. .cpp文件代码(有注释):
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include <QDebug>
  4. MainWindow::MainWindow(QWidget *parent) :
  5. QMainWindow(parent),
  6. ui(new Ui::MainWindow)
  7. {
  8. ui->setupUi(this);
  9. client = new QModbusTcpClient(this);//实例化对象
  10. client->setConnectionParameter(QModbusDevice::NetworkAddressParameter,"127.0.0.1");// 设置连接信息:服务端IP
  11. client->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);// 设置连接信息:服务端端口
  12. client->connectDevice();
  13. }
  14. MainWindow::~MainWindow()
  15. {
  16. if(client->state()==QModbusTcpClient::ConnectedState) //如果设备连接,则要断开
  17. {
  18. client->disconnectDevice();
  19. }
  20. delete ui;
  21. }
  22. void MainWindow::on_pushButton_clicked() // 读
  23. {
  24. QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters,500,2);// 定义数据单元:保持寄存器类型、PLC的开始地址和地址读取数
  25. QModbusReply* reply = client->sendReadRequest(unit,1); // 参数二为设备号
  26. if(reply)
  27. {
  28. if(!reply->isFinished())
  29. {
  30. connect(reply,&QModbusReply::finished,this,&MainWindow::replyData); //异步处理槽函数
  31. return;
  32. }
  33. reply->deleteLater(); // 针对广播消息
  34. }
  35. }
  36. void MainWindow::on_pushButton_2_clicked() // 写
  37. {
  38. QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters,500,1);// 定义数据单元:保持寄存器类型、PLC的开始地址和地址读取数
  39. unit.setValue(0,ui->lineEdit->text().toUInt()); // 向一个地址写一个值
  40. // QVector<quint16> data;
  41. // data<<100<<124;
  42. // QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters,500,data);
  43. // unit.setValues(data);//向连续地址写多个值 集合的元素个数需要和unit中的地址读取数保持一致
  44. QModbusReply* reply = client->sendWriteRequest(unit,1); // 参数二为设备号
  45. if(reply)
  46. {
  47. reply->deleteLater(); // 针对广播消息
  48. }
  49. }
  50. void MainWindow::replyData() // 处理读的数据
  51. {
  52. QModbusReply* reply = (QModbusReply*)(sender());
  53. if(reply)
  54. {
  55. QModbusDataUnit unit = reply->result(); // 提取reply数据
  56. reply->deleteLater(); // 释放内存
  57. if(unit.valueCount()>0)
  58. {
  59. QVector<quint16> data = unit.values();
  60. QString s;
  61. Q_FOREACH(quint16 i,data)
  62. {
  63. s.append(QString::number(i)).append(" "); // 把所有值放到一个字符串中
  64. }
  65. ui->lineEdit->setText(s);
  66. //ui->lineEdit->setText(QString::number(unit.value(0)));
  67. }
  68. }
  69. }
  • 5.  运行效果,可以看到在Modbus Slave的帮助下,能够正确读写寄存器。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/559240
推荐阅读
相关标签
  

闽ICP备14008679号