天天看點

QT使用libmodbus庫與施耐德PLC通信

**

QT使用libmodbus庫與施耐德PLC通信

**

1.環境配置

2.modbus簡介

3.libmodbus簡介

4.施耐德PLC位址映射

5.程式源碼

1.環境配置

我目前使用的環境為WIN10 + MSVC2013 X64 + QT 5.9.9 +QT Creator 4.11.0

2.modbus簡介

modbus作為一種通信協定,支援RS232,RS485以及以太網等裝置。請求建立連接配接的稱為主站master,

作出響應的稱為從站slave,主站可以單獨與一個從站進行通訊,也可以以廣播的方式進行通信。

modbus的資料傳輸支援兩種方式:一種是ASCLL方式,一種是RTU方式。

兩種方式的主要差別是;前者以ascll碼的方式進行傳輸,調試起來更加直覺,但是傳輸效率低,因為一個ascll碼的二進制是8位,相反,後者以16位資料傳輸,效率要高得多。以傳輸資料0xA8為例,ascll傳輸需要将‘A’和’8’分開傳輸,也即0x41和0x38,而rtu直接傳輸0xA8即可。

從站位址從0-247,0是廣播位址所有從機都可以識别,是以實際上從機的尋址範圍是1-247,主從通信時從站位址設為1即可

modbus的功能碼

QT使用libmodbus庫與施耐德PLC通信

modbus通信資料格式,隻介紹用到的幾個:

QT使用libmodbus庫與施耐德PLC通信
QT使用libmodbus庫與施耐德PLC通信

關于CRC(Modbus)校驗,這裡貼一個連結,做調試用

CRC線上校驗

3.libmodbus簡介

關于libmodbus的使用網上很多,我也看了許多,有兩個寫的還是很全的

libmodbus的編譯

libmodbus的使用

4.施耐德PLC位址映射

QT使用libmodbus庫與施耐德PLC通信

其他PLC的位址映射見這裡

5.程式源碼

在.pro檔案中連結WS2_32.LIB庫,以及頭檔案路徑

LIBS  += -L$$PWD/lib/ -lWS2_32
INCLUDEPATH += $$PWD/libmodbus
           

這是整個工程檔案

QT使用libmodbus庫與施耐德PLC通信

libmodbus檔案夾中有

QT使用libmodbus庫與施耐德PLC通信

在.h檔案中聲明modbus對象

.c檔案構造函數中添加

mb = modbus_new_rtu("COM5", 38400, 'N', 8, 1);
    modbus_set_slave(mb, 1); //設定modbus從機位址
    modbus_set_response_timeout(mb, 0, 1000000);/* 逾時時間1s */
           

析構函數中添加

MainWindow::~MainWindow()
{
    modbus_close(mb);
    modbus_free(mb);
    delete ui;
}

           

裝置連接配接

if(modbus_connect(mb) < 0)
    {
        ui->textEdit->append(QString::fromLocal8Bit("連接配接失敗"));
    }
    else {
        ui->textEdit->append(QString::fromLocal8Bit("連接配接成功"));
    }
           

0x01碼 (讀QX輸出線圈)

void MainWindow::on_pushButton_01_clicked()
{
    qDebug() << "0x01";
    uint8_t dest[8] = {0};
    if(modbus_read_bits(mb, 0, 8, dest) != 8 )/* 讀輸出端 */
    {
        qDebug() << "Read Coils Error";
    }else{
        qDebug() << "Read Coils Success";
    }
}
           

0x02碼 (讀取IX離散輸入)

void MainWindow::on_pushButton_02_clicked()   
{
    qDebug() << "0x02";
    uint8_t dest_2[8] = {0};
    modbus_read_input_bits(mb, 0, 8, dest_2); /* 讀輸入端 */

    qDebug() << dest_2[0] << ":" << dest_2[1] << ":" << dest_2[2] << ":" << dest_2[3];
    qDebug() << dest_2[4] << ":" << dest_2[5] << ":" << dest_2[6] << ":" << dest_2[7];
}
           

0x03碼(讀取MW寄存器)

void MainWindow::on_pushButton_03_clicked()
{
    qDebug() << "0x03";
    uint16_t reg[5];
    int rc = modbus_read_registers(mb, 0, 5, reg);

    for(int i = 0; i < rc; i++) {
        QString str = QString::fromLocal8Bit("寄存器reg[%1]的值為:%2").arg(i+300).arg(decTobin(QString::number(reg[i])));  /* 讀多個保持寄存器 */
        ui->textEdit->append(str);
    }

}
           

0x10碼 (寫MW寄存器)

void MainWindow::on_pushButton_10_clicked()
{
    /* MW0 40001 */
    /* MW359  40360*/
    qDebug() << "0x10";
    uint16_t tab_regs[5] = {206,0,1,0,0};
    if(modbus_write_registers(mb, 360, 5, tab_regs) == (-1)) 
    {
        qDebug() << "write error";
    }
}
           

以下是我修改後的使用0X01,0X02,0X03,0X10的例子供大家參考

void MainWindow::on_btnConfirm_clicked()
{
    byte keyValue = 1 << 5;
    qDebug() << keyValue;
    byte mode = ui->comboBoxMode->currentIndex(); /* 0 1 2 3 */
    qDebug() << mode;
    int plateNum = ui->lineEditNum->text().toUInt();
    qDebug() << plateNum;
    byte cmode = 0;
    switch (mode) {
    case 0: cmode = 1 << 0; break;
    case 1: cmode = 1 << 1; break;
    case 2: cmode = 1 << 2; break;
    case 3: cmode = 1 << 3; break;
    default:cmode = 0;      break;
    }
    qDebug() << cmode;
    uint16_t tab_reg[5] = {plateNum, 0, cmode, keyValue, 0};/* 數組實際為如 101,0,1,32,0 */
    byte rc = modbus_write_registers(mb, 360, 5, tab_reg); /* 判斷寫入的資料長度是不是5個,點對點通信時從站是有傳回值的 */
    if(rc == sizeof (tab_reg)/sizeof (tab_reg[0])){
        qDebug() << "write success";
    }
}
void MainWindow::on_btnViewPlate_clicked() /* 檢視擴充闆輸入輸出狀态 */
{
    uint16_t reg[2];
    int rc = modbus_read_registers(mb, 0, 2, reg);

    for(byte i = 0; i < rc; i++){
        ui->textEdit->append(QString::fromLocal8Bit("擴充闆0%1").arg(i+1));
        ui->textEdit->append("----------------------------");
        for(byte j = 0; j < 16; j++){
            QString str = QString("%1 : %2").arg(j+1).arg( (reg[rc] >> j) & 0x0001);/* 擷取位狀态 */
            ui->textEdit->append(str);
        }
    }


}

void MainWindow::on_btnViewPlc_clicked()  /* 檢視PLC輸入輸出點狀态 */
{
    uint8_t dest[8] = {0};
    byte rc = modbus_read_input_bits(mb, 0, 8, dest); /* 讀輸入端 */

    for(byte i = 0; i < rc; i++){

        QString in = QString("IX0.%1 = %2").arg(i).arg(dest[i]);
        ui->textEdit->append(in);
    }

    memset(dest, '\0', sizeof (dest));
    ui->textEdit->append("---------------------------");
    rc = modbus_read_bits(mb, 0, 8, dest);

    for(byte i = 0; i < rc; i++){

        QString in = QString("QX0.%1 = %2").arg(i).arg(dest[i]);
        ui->textEdit->append(in);
    }
    memset(dest, '\0', sizeof (dest));
}

           
QT使用libmodbus庫與施耐德PLC通信
QT使用libmodbus庫與施耐德PLC通信

輸入車位号206,點選确認,打開modscan32檢視一下PLC内部值是否改變

QT使用libmodbus庫與施耐德PLC通信

可以看到已經寫入成功

最後,說一下我調試的過程,首先先寫好一個測試用的DEMO,我把常用的幾個功能碼都使用了用按鈕觸發,就是上面貼的代碼,有幾個我沒貼,然後使用軟體VSPD虛拟兩個序列槽出來,再用軟體Modbus Slave來模拟從站,就可以對代碼進行調試了。如果有硬體,可以使用兩個USB轉485并聯與PLC相連,打開序列槽調試軟體比如我用的XCOM2.0,和你自己寫的軟體分别連接配接一個COM口,這樣你發送給PLC的資料就可以顯示在序列槽調試軟體上了,這裡要注意的是,點對點測試的時候,序列槽調試軟體會收到兩條資料,一條是你發送給PLC的資料,一條是PLC傳回的資料,一開始我以為是我發送的資料格式有誤。好了,今天就寫到這裡,關于工程檔案已經上傳CSDN了,積分窘迫的同學可以評論或者私信我,分享快樂。

繼續閱讀