Qt modbus使用详解

不讲理论,只讲应用。看完这篇就能用起来,爽不爽!!!

具体内容目录如下,如需请订阅专栏后观看。

目录

一、Modbus协议通信过程

1.1 主机对从机写数据操作(0x06)

1.2 主机对从机读数据操作(0x03)

1.3 Modbus的CRC校验

二、在Qt中使用QModbus读写数据

2.1 部署准备——头文件中(.h)

2.2 部署准备——cpp文件中

2.3 modbus的使用——写函数

2.4 modbus的使用——读数据

2.5 额外——涉及的数据类型说明


一、Modbus协议通信过程

Modbus能实现的功能较多,理解了如下2个操作,基本算掌握了Modbus 80%的使用了。在此基础上,能很快扩展到其他功能使用上。

1.1 主机对从机写数据操作(0x06)

这个功能的目的是:实现主机对从机 寄存器 的写入操作。

注意:是  主机   对    从机

说明:假如从机地址是  1,那么单片机接收到这串数据,根据CRC16进行校验判断数据是否正确,如果数据无误,就改变自己寄存器的值。

然后从机原封不动返回这句话,告诉主机通讯成功!!!

*****下面用 串口调试助手XCOM V2.0作为主机,Modbus Slave软件作为从机 ,通过串口数据监视软件AccessPort 捕获通讯中的数据,过程如下:

如下图所示,发送数据01 06 00 04 00 07 89 C9,接收到从机返回数据01 06 00 04 00 07 89 C9。说明通讯成功。

从串口数据监视软件AccessPort里也可以看出,如下图中红色框中所示,

对主机而言,发送01 06 00 04 00 07 89 C9后,接收了01 06 00 04 00 07 89 C9;

对从机而言,接收01 06 00 04 00 07 89 C9后,发送了01 06 00 04 00 07 89 C9。

1.2 主机对从机读数据操作(0x03)

这个功能的目的是:实现主机对从机 寄存器 的值的读操作。

注意:是  主机   对    从机

说明:假如从机地址是  1,那么单片机接收到这串数据,根据CRC16进行校验判断数据是否正确,如果数据无误,就发送相应的寄存器的值给主机!!!

注意:这里的数据字节个数,在主机下发数据那里可以变更,单位是不同的——对主机端,一个数据个数对应从机端两个字节的数据。

*****下面用 串口调试助手XCOM V2.0作为主机,Modbus Slave软件作为从机 ,通过串口数据监视软件AccessPort 捕获通讯中的数据,过程如下:

如下图所示,串口调试助手作为主机发送01 03 00 00 00 0A C5 CD,Modbus Slave作为从机,接收到主机的读信息后,发送01 03 14 78 67 00 00 00 05 00 00 00 00 00 00 00 06 00 00 00 00 00 08 80 AC作为回应。

主机端要求从从机0x01的寄存器0x0000开始读取10个数据;从机端接收到命令后,从寄存器0x0000,读取10个数据共20个字节,发送给主机。

从串口数据监视软件AccessPort里也可以看出,如下图中红色框中所示,

对主机而言,发送01 03 00 00 00 0A C5 CD后,接收了01 03 14 78 67 00 00 00 05 00 00 00 00 00 00 00 06 00 00 00 00 00 08 80 AC;

对从机而言,接收01 03 00 00 00 0A C5 CD后,发送了01 03 14 78 67 00 00 00 05 00 00 00 00 00 00 00 06 00 00 00 00 00 08 80 AC。

1.3 Modbus的CRC校验

Modbus的CRC校验使用的是CRC16,输出两个字节校验结果。这个过程了解即可,Qt自带算CRC的算法。

     

CRC16校验的查表法  C++算法如下:

//CRC16校验算法 系数表
uchar auchCRCHi[]=
{
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
    0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40
};

uchar auchCRCLo[] =
{
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
    0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
    0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
    0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
    0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
    0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
    0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
    0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
    0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
    0x40
};

//查表法:Modbus的16位CRC校验
uint ModbusTool::N_CRC16(uchar *updata,uint len)
{
    uchar uchCRCHi=0xff;
    uchar uchCRCLo=0xff;
    uint  uindex;
    while(len--)
    {
        uindex=uchCRCHi^*updata++;
        uchCRCHi=uchCRCLo^auchCRCHi[uindex];
        uchCRCLo=auchCRCLo[uindex];
    }
    return (uchCRCHi<<8|uchCRCLo);
}

二、在Qt中使用QModbus读写数据

我的软件环境作为参考,win7,win10下都可以。

2.1 部署准备——头文件中(.h)

需要包含如下头文件和QModbus类,至于定义Modbus对象,可以在头文件中,也可以在cpp中。

#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QModbusDataUnit>

class QModbusClient;
class QModbusReply;


//类中定义,我的类名叫ModbusTool
private:
    QModbusClient *modbustoolDevice;//Modbus对象

private slots:
    void readReady();//读下位机寄存器数据回调函数

public:
    void write2client(unsigned int regStartAddr,unsigned int number);//上位机向下位机的某个寄存器写入数据
    void readfromclient(unsigned int startAddress,int number);//从指定下位机寄存器处读取数据

2.2 部署准备——cpp文件中

定义modbus串口的打开和关闭函数

//当然,相应的.cpp和.h文件需要对应起来哈,别傻傻的不同的.h和.cpp中写入代码了

//再包含一个头文件
#include <QModbusRtuSerialMaster>

//实例化Modbus对象
modbustoolDevice = new QModbusRtuSerialMaster(this);


//连接串口,这里是一个按钮
void ModbusTool::on_modbus_connect_clicked()
{
    //判断modbus是否已经定义,没有则直接返回
    if (!modbustoolDevice)
        return;

    //判断modbus是否已经连接
    if (modbustoolDevice->state() != QModbusDevice::ConnectedState) {
        //如果没有连接,则先设置串口的名称,串口奇偶校验位,串口波特率,串口数据位,串口停止位
        modbustoolDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,
                                                 ui->modbus_searialport_name->currentText());
        modbustoolDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,
                                                 QSerialPort::EvenParity);
        modbustoolDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
                                                 ui->modbus_searialport_btl->currentText().toInt());
        modbustoolDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,
                                                 QSerialPort::Data8);
        modbustoolDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,
                                                 QSerialPort::OneStop);

        //再设置从机无响应时的动作
        modbustoolDevice->setTimeout(1000);//从设备回复信息的超时时间
        modbustoolDevice->setNumberOfRetries(2);//重复发送次数

        //开始连接串口,并判断连接状态
        if (!modbustoolDevice->connectDevice()) {
            //如果连接失败,提示
            statusBar()->showMessage(tr("连接失败: ") + modbustoolDevice->errorString(), 5000);
            //FlagGetStatus=0;//连接失败,停止定时刷新
            //ui->Status_SerialConnect->setStyleSheet("font-size:100px;border-radius:30px;\nbackground-color: red");
        }else{
            //如果连接成功,设置下面按钮的状态,同时给出提示
            ui->modbus_connect->setEnabled(false);
            ui->modbus_disconnect->setEnabled(true);
            ui->send_commander->setEnabled(true);

            statusBar()->showMessage(tr("连接成功!"), 5000);
            //FlagGetStatus=1;//连接成功,定时刷新
            //ui->Status_SerialConnect->setStyleSheet("font-size:100px;border-radius:30px;\nbackground-color: green");
        }
    } else {
        //如果已经连接,则关闭串口
        modbustoolDevice->disconnectDevice();
        //ui->Status_SerialConnect->setStyleSheet("font-size:100px;border-radius:30px;\nbackground-color: rgb(205, 205, 205)");
    }
}

//断开串口连接
void ModbusTool::on_modbus_disconnect_clicked()
{
    //关闭串口
    modbustoolDevice->disconnectDevice();
    //设置下面按钮的状态,同时给出提示
    ui->modbus_connect->setEnabled(true);
    ui->modbus_disconnect->setEnabled(false);
    ui->send_commander->setEnabled(false);
    statusBar()->showMessage(tr("断开连接成功!"), 5000);
}

2.3 modbus的使用——写函数

只需要这一个函数就能完成写操作,明确写入寄存器的起始地址,以及写入的数据,调用就能完成写操作。

/*
 * @FuncName   上位机向下位机的某个寄存器写入数据
 *
 * @Function   向指定下位机寄存器写入数据
 *
 * @param 参数a 下位机寄存器起始地址
 * @param 参数b 写入值
 *
 * @return 返回值无
 */
void ModbusTool::write2client(unsigned int regStartAddr,unsigned int number)
{
    //如果没有定义modbus串口,直接返回
    if (!modbustoolDevice)
    {
        statusBar()->showMessage(tr("Modbus通讯失败,请检查串口连接!"),5000);
        return;
    }

    //清除状态栏显示
    statusBar()->clearMessage();

    //从页面的输入框获取设备地址
    unsigned int serverAddress=ui->modbus_addr->text().toInt();
    if(serverAddress==0)
    {
        statusBar()->showMessage(tr("设备地址为空,写入失败!"),5000);
        return;
    }

    //定义Modbus的基本数据单元,这里设置了modbus为0x06写模式,寄存器地址,以及传输数据的大小
    //QModbusDataUnit::QModbusDataUnit(RegisterType type, int address, quint16 size)
    QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters,regStartAddr,1);//06
    //QModbusDataUnit writeUnit(QModbusDataUnit::Coils,regStartAddr,1);//05

    //传输的数据为0,写入数据单元,这里还在做数据准备
    //number = 0x08;
    //void QModbusDataUnit::setValue(int index, quint16 value)
    writeUnit.setValue(0,number);

    //发送Modbus格式的数据到从机地址为serverAddress的从机
    //sendWriteRequest(const QModbusDataUnit &write, int serverAddress)
    if (auto *reply = modbustoolDevice->sendWriteRequest(writeUnit, serverAddress)) {
        if (!reply->isFinished()) {
            //如果接收到响应信息
            connect(reply, &QModbusReply::finished, this, [this, reply]() {
                if (reply->error() == QModbusDevice::ProtocolError) {
                    //接收到的响应信息有协议错误,则更新状态栏,错误码
                    statusBar()->showMessage(tr("写入数据错误: %1 (Mobus exception: 0x%2)")
                                             .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16),
                                             5000);
                } else if (reply->error() != QModbusDevice::NoError) {
                    //接收到的响应信息有其他错误,则更新状态栏,错误码
                    statusBar()->showMessage(tr("写入数据错误: %1 (code: 0x%2)").
                                             arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
                }else{
                    //接收到的响应信息没有任何问题,更新状态栏
                    //注意,注意,注意,注意,注意,注意:这里解析了回应的信息,具体方法如下。一般没必要
                    //获取响应信息的数据到modbus基本数据QModbusDataUnit
                    const QModbusDataUnit unit = reply->result();

                    //统计响应信息中数据的个数
                    QString showdata=tr("总数:%1 值为:").arg(unit.valueCount());

                    //依次展示响应信息中的数据
                    for (uint i = 0; i < unit.valueCount(); i++) {
                        const QString entry=tr("%1 ").arg(unit.value(i));
                        showdata=showdata+entry;
                    }
                    //显示在界面中
                    ui->recv_area->setText(showdata);
                }
                reply->deleteLater();
            });
        } else {
            //发送多次后,没有响应数据,停止发送,更新状态栏信息
            // broadcast replies return immediately
            reply->deleteLater();
        }
    } else {
        //发送没有成功,更新状态栏信息
        statusBar()->showMessage(tr("写入数据错误: ") + modbustoolDevice->errorString(), 5000);
    }
}

2.4 modbus的使用——读数据

读操作包含两个函数。一个是发送读数据命令的函数,一个是处理读取的数据的函数。

/*
 * @FuncName   函数名:从指定下位机寄存器处读取数据
 *
 * @Function   功能:读取下位机数据
 *
 * @param 参数a 下位机寄存器起始地址
 *
 * @return 返回值无
 */
void ModbusTool::readfromclient(unsigned int startAddress,int number)
{
    //如果没有定义modbus串口,直接返回
    if (!modbustoolDevice)
        return;
    //    ui->readValue->clear();
    //清空状态栏的显示
    statusBar()->clearMessage();

    //定义Modbus的基本数据单元,这里设置了modbus为0x03读模式,寄存器地址,以及传输数据的大小
    //QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters,startAddress,num);
    QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters,startAddress,number);

    //发送Modbus格式的数据到从机地址为serverAddress的从机,读取相应的数据
    //if(auto* reply = Master->sendReadRequest(readUnit,ServerAddress))
    //获取设备地址
    unsigned int serverAddress=ui->modbus_addr->text().toInt();
    if (auto *reply = modbustoolDevice->sendReadRequest(readUnit, serverAddress)) {
        if (!reply->isFinished())
            //如果有响应信息了,进入readReady函数处理接收到的数据
            connect(reply, &QModbusReply::finished, this, &ModbusTool::readReady);
        else
            //如果多次发送后没有响应,结束
            delete reply; // broadcast replies return immediately
    } else {
        //如果发送失败,更新状态栏信息
        statusBar()->showMessage(tr("读取失败: ") + modbustoolDevice->errorString(), 5000);
    }
}

/*
 * @FuncName   函数名:读下位机寄存器数据回调函数
 *
 * @Function   功能:解析下位机上传的数据
 *
 * @param 参数无
 *
 * @return 返回值无
 */
void ModbusTool::readReady()
{
    auto reply = qobject_cast<QModbusReply *>(sender());
    //如果没有响应数据,直接返回
    if (!reply)
        return;

    if (reply->error() == QModbusDevice::NoError) {
        //如果响应数据校验后,没有错误。解析数据
        const QModbusDataUnit unit = reply->result();

        QString showdata=tr("总数:%1 值为:").arg(unit.valueCount());

        for (uint i = 0; i < unit.valueCount(); i++) {
            const QString entry=tr("%1 ").arg(unit.value(i));
            showdata=showdata+entry;
        }

        ui->recv_area->setText(showdata);

    } else if (reply->error() == QModbusDevice::ProtocolError) {
        //如果响应数据校验后,有协议错误,更新状态栏显示错误码
        statusBar()->showMessage(tr("读取回应错误: %1 (Mobus exception: 0x%2)").
                                 arg(reply->errorString()).
                                 arg(reply->rawResult().exceptionCode(), -1, 16), 5000);
    } else {
        //如果响应数据校验后,有错误,更新状态栏显示错误码
        statusBar()->showMessage(tr("读取回应错误: %1 (code: 0x%2)").
                                 arg(reply->errorString()).
                                 arg(reply->error(), -1, 16), 5000);
    }

    //结束响应过程
    reply->deleteLater();
}

2.5 额外——涉及的数据类型说明

const QModbusDataUnit unit = reply->result();

这个代表接收到的响应信息的数据部分。

使用unit.value(i)依次读取就能得到各个数据。

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kissgoodbye2012

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值