基础知识:
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_OBJECT
public:
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 serialport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = untitled5
TEMPLATE = 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.0
CONFIG += c++11
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: 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_OBJECT
public:
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 02
readData(0x01, 0x02, 0x0A);
// 0A 06 00 36 00 00
writeData(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个保存寄存器的值 寄存器起始地址为1
QModbusDataUnit 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个保存寄存器的值 寄存器起始地址为1
QModbusDataUnit 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;
}
//删除reply
reply->deleteLater();
}
QString Widget::fillNumber(quint16 number)
{
QString str = QString::number(number, 16);
while (str.size() < 4) {
str += "0";
}
return str;
}
Widget::~Widget()
{
delete ui;
}