基础知识:
1.功能码
modbus完整支持很多功能码,但是实际在应用的时候常用的也就那么几个。具体如下:
0x01: 读线圈寄存器
0x02: 读离散输入寄存器
0x03: 读保持寄存器
0x04: 读输入寄存器
0x05: 写单个线圈寄存器
0x06: 写单个保持寄存器
0x0f: 写多个线圈寄存器
0x10: 写多个保持寄存器
如上所示一共8种功能码。这其中有涉及到线圈、离散输入、保持、输入四种寄存器。这名字也不知道谁起的,让人看了一点不通俗易懂,搞得晕晕乎乎。实际上你要是看清他的本质就很简单了。下面分别解释一下:
线圈寄存器:实际上就可以类比为开关量,每个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01 0x05 0x0f
离散输入寄存器:如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02
保持寄存器:这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三个:0x03 0x06 0x10
输入寄存器:只剩下这最后一个了,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x04
对应的错误返回:
在对应功能码基础上加上0x80
————————————————
版权声明:本文为CSDN博主「JiaoCL」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/liboxiu/article/details/86473516
2.使用QT实现


#ifndef WIDGET_H#define WIDGET_H#include <QWidget>#include <QModbusRtuSerialMaster>#include <QModbusDataUnit>#include <QModbusReply>#include <QVariant>#include <QSerialPort>#include <QDebug>namespace Ui {class Widget;}class Widget : public QWidget{Q_OBJECTpublic:explicit Widget(QWidget *parent = nullptr);void connectSerial();void disconnectSerial();void readData(int startaddr, int num, int addr);bool writeData(int startaddr, int size, QVector<int> value, int addr);void onReadReady();QString fillNumber(quint16 number);~Widget();private:Ui::Widget *ui;QModbusClient *modbusDevice = nullptr;};#endif // WIDGET_H#-------------------------------------------------## Project created by QtCreator 2021-07-22T18:34:43##-------------------------------------------------QT += core gui serialbus serialportgreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsTARGET = untitled5TEMPLATE = app# The following define makes your compiler emit warnings if you use# any feature of Qt which has been marked as deprecated (the exact warnings# depend on your compiler). Please consult the documentation of the# deprecated API in order to know how to port your code away from it.DEFINES += QT_DEPRECATED_WARNINGS# You can also make your code fail to compile if you use deprecated APIs.# In order to do so, uncomment the following line.# You can also select to disable deprecated APIs only up to a certain version of Qt.#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0CONFIG += c++11SOURCES += \main.cpp \widget.cppHEADERS += \widget.hFORMS += \widget.ui# Default rules for deployment.qnx: target.path = /tmp/$${TARGET}/binelse: unix:!android: target.path = /opt/$${TARGET}/bin!isEmpty(target.path): INSTALLS += target
#ifndef WIDGET_H#define WIDGET_H#include <QWidget>#include <QModbusRtuSerialMaster>#include <QModbusDataUnit>#include <QModbusReply>#include <QVariant>#include <QSerialPort>#include <QDebug>namespace Ui {class Widget;}class Widget : public QWidget{Q_OBJECTpublic:explicit Widget(QWidget *parent = nullptr);void connectSerial();void disconnectSerial();void readData(int startaddr, int num, int addr);bool writeData(int startaddr, int size, QVector<int> value, int addr);void onReadReady();QString fillNumber(quint16 number);~Widget();private:Ui::Widget *ui;QModbusClient *modbusDevice = nullptr;};#endif // WIDGET_H
#include "widget.h"#include "ui_widget.h"#include <QPushButton>#include <QTimer>#include <QEventLoop>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget){ui->setupUi(this);connect(ui->btnConnect, &QPushButton::clicked, this, &Widget::connectSerial);connect(ui->btnQuit, &QPushButton::clicked, this, &Widget::disconnectSerial);QTimer *mytimer = new QTimer(this);connect(mytimer, &QTimer::timeout, this, [=](){// 0A 03 00 01 00 02readData(0x01, 0x02, 0x0A);// 0A 06 00 36 00 00writeData(0x36, 1, ,0, 0x0A);});connect(ui->pushButton, &QPushButton::clicked, [=](){mytimer->start(1000);});}void Widget::connectSerial(){//1. 创建QModbusDevice对象modbusDevice = new QModbusRtuSerialMaster(this);//2. 如果处于连接状态,则返回if (modbusDevice->state() == QModbusDevice::ConnectedState){return;}//3. 设置串口相关参数//设置串口信息modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, QVariant("ttyUSB0"));//设置校验 无校验modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);//设置波特率modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600);//设置停止位modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);//设置数据位modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);//其他设置//设置超时时间modbusDevice->setTimeout(300);//失败次数modbusDevice->setNumberOfRetries(3);bool ok = modbusDevice->connectDevice();if (!ok){qDebug() << "连接到串口失败: " << modbusDevice->errorString();ui->labelState->setText("串口设置有误,连接失败");}else{qDebug() << "连接到串口成功";ui->labelState->setText("串口连接成功");}}void Widget::disconnectSerial(){if(modbusDevice->state() != QModbusDevice::UnconnectedState){modbusDevice->disconnectDevice();ui->labelState->setText("关闭连接");}}void Widget::readData(int startaddr, int num, int addr){if(modbusDevice->state() == QModbusDevice::UnconnectedState) {return;}//读取2个保存寄存器的值 寄存器起始地址为1QModbusDataUnit data(QModbusDataUnit::HoldingRegisters, startaddr, num);data.setValue(0, 0);QModbusReply* reply = modbusDevice->sendWriteRequest(data, addr);if (nullptr == reply){qDebug() << "发送请求数据失败: " << modbusDevice->errorString();return;}else{if (!reply->isFinished()){QEventLoop loop;connect(reply, &QModbusReply::finished,&loop,&QEventLoop::quit);loop.exec();}}if (reply->error() == QModbusDevice::NoError){//读取响应数据const auto responseData = reply->rawResult();auto values = responseData.data().toHex();qDebug() << values;if(values.size() == 2) {bool ok;QString mystring = fillNumber(values.at(1)) + fillNumber(values.at(0));int getFLoat = mystring.toUInt(&ok, 16);//转换为浮点数float floatdata = *(float*)&getFLoat;ui->textEdit->append(QString("%1").arg(floatdata));}if(values.size() == 1) {ui->textEdit->append(QString("%1").arg(values.at(0)));}}else if (reply->error() == QModbusDevice::ProtocolError){qDebug() << "Read response Protocol error: " << reply->errorString();}else{qDebug() << "Read response Error: " << reply->errorString();}}bool Widget::writeData(int startaddr, int size, QVector<int> value, int addr){if(modbusDevice->state() == QModbusDevice::UnconnectedState) {return;}//读取2个保存寄存器的值 寄存器起始地址为1QModbusDataUnit data(QModbusDataUnit::HoldingRegisters, startaddr, size);for(int i = 0; i < size; i++) {data.setValue(i, value.at(i));}data.setValue(0, value);QModbusReply* reply = modbusDevice->sendWriteRequest(data, addr);if (nullptr == reply){qDebug() << "发送请求数据失败: " << modbusDevice->errorString();return;}else{if (!reply->isFinished()){QEventLoop loop;connect(reply, &QModbusReply::finished,&loop,&QEventLoop::quit);loop.exec();}}if (reply->error() == QModbusDevice::NoError){return true;}else{qDebug() << "Read response Error: " << reply->errorString();return false;}}void Widget::onReadReady(){auto reply = qobject_cast<QModbusReply*>(sender());if (nullptr == reply){return;}//删除replyreply->deleteLater();}QString Widget::fillNumber(quint16 number){QString str = QString::number(number, 16);while (str.size() < 4) {str += "0";}return str;}Widget::~Widget(){delete ui;}
