1. 程式人生 > 實用技巧 >Linux-QT串列埠通訊

Linux-QT串列埠通訊

  Linux-QT串列埠通訊

  環境:Ubuntu18.04 QT4.8.6

  1.QT新建Qt Console Application

#include <QCoreApplication>

#include "ThreadTest.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    ThreadTest * thTest = new ThreadTest();
    thTest->start();

    return a.exec();
}
View Code

  2.新建一個串列埠類SerialPort

  .h檔案

#ifndef SERIALPORT_H
#define SERIALPORT_H

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <QString>
#include <QDebug>

class SerialPort
{
public: // 構造 SerialPort(QString devName); // 析構 ~SerialPort(); public: // 設定波特率等 int set_port_attr (int baudrate, int databit, const char *stopbit, char parity, int vtime,int vmin ); // 資料位 void set_data_bit (struct termios *opt, unsigned int databit);
// 校驗位 void set_parity (struct termios *opt, char parity); // 停止位 void set_stopbit (struct termios *opt, const char *stopbit); // 寫資料 int Write(const uchar *data,const int len); // 讀資料 int Read(void *data,const int len, const int waitms); // 設定波特率 void set_baudrate (struct termios *opt, uint baudrate); private: int serialPortFd; }; #endif // SERIALPORT_H
View Code

  .CPP檔案

#include "SerialPort.h"

// 構造
SerialPort::SerialPort(QString devName)
{
    serialPortFd = open(devName.toAscii().data(), O_RDWR | O_NOCTTY);
    qDebug() << "serialPortFd = " << serialPortFd;

}

// 設定波特率等
int SerialPort::set_port_attr (int  baudrate, int  databit,
                               const char *stopbit, char parity, int vtime,int vmin ){
    struct termios opt;
    tcgetattr(serialPortFd, &opt);
    set_baudrate(&opt, baudrate);
    opt.c_cflag          |= CLOCAL | CREAD;      /* | CRTSCTS */
    set_data_bit(&opt, databit);
    set_parity(&opt, parity);
    set_stopbit(&opt, stopbit);
    opt.c_oflag          = 0;
    //opt.c_lflag           |= 0;
    opt.c_lflag &= ~(ICANON | ECHO | ECHOE);
    opt.c_oflag          &= ~OPOST;
    opt.c_cc[VTIME]     = vtime;
    opt.c_cc[VMIN]      = vmin;
    tcflush (serialPortFd, TCIFLUSH);
    return (tcsetattr (serialPortFd, TCSANOW, &opt));
}

// 設定波特率
void SerialPort::set_baudrate (struct termios *opt, uint baudrate)
{
    cfsetispeed(opt, baudrate);
    cfsetospeed(opt, baudrate);
}

// 資料位
void SerialPort::set_data_bit (struct termios *opt, unsigned int databit)
{
    opt->c_cflag &= ~CSIZE;
    switch (databit) {
    case 8:
        opt->c_cflag |= CS8;
        break;
    case 7:
        opt->c_cflag |= CS7;
        break;
    case 6:
        opt->c_cflag |= CS6;
        break;
    case 5:
        opt->c_cflag |= CS5;
        break;
    default:
        opt->c_cflag |= CS8;
        break;
    }
}

// 校驗位
void SerialPort::set_parity (struct termios *opt, char parity)
{
    switch (parity) {
    case 'N':                                              /* 無校驗        */
    case 'n':
        opt->c_cflag &= ~PARENB;
        break;
    case 'E':                                              /* 偶校驗        */
    case 'e':
        opt->c_cflag |= PARENB;
        opt->c_cflag &= ~PARODD;
        break;
    case 'O':                                              /* 奇校驗            */
    case 'o':
        opt->c_cflag |= PARENB;
        opt->c_cflag |= ~PARODD;
        break;
    default:                                                 /* 其它選擇為無校驗 */
        opt->c_cflag &= ~PARENB;
        break;
    }
}

// 停止位
void SerialPort::set_stopbit (struct termios *opt, const char *stopbit)
{
    if (0 == strcmp (stopbit, "1")) {
        opt->c_cflag &= ~CSTOPB;                            /* 1位停止位t         */
    }  else if (0 == strcmp (stopbit, "1.5")) {
        opt->c_cflag &= ~CSTOPB;                            /* 1.5位停止位    */
    }  else if (0 == strcmp (stopbit, "2")) {
        opt->c_cflag |= CSTOPB;
    }  else {
        opt->c_cflag &= ~CSTOPB;                             /* 1 位停止位        */
    }
}

// 寫資料
int SerialPort::Write(const uchar *data,const int len){
    int returnLength = write(serialPortFd, data, len);                        /* 向串列埠傳送字串            */
    return returnLength;
}
// 讀資料  waitms超時時間
int SerialPort::Read(void *data,const int len, const int waitms){
    fd_set inputs;
    struct timeval timeout;
    FD_ZERO(&inputs);
    FD_SET(serialPortFd,&inputs);
    timeout.tv_sec = waitms/1000;
    timeout.tv_usec = (waitms%1000)*1000;
    int returnLength = select(FD_SETSIZE,&inputs,(fd_set *)NULL,(fd_set *)NULL,&timeout);
    if (returnLength == 0){
        //qDebug() << "read timeout!\n";
        return -1;
    }
    //qDebug() << "ComDevice::Read  5";
    if (returnLength == -1){
        qDebug() << "select read device error!\n";
        return -2;
    }

    returnLength = read(serialPortFd, data, len);                        /* 在串列埠讀取字串            */
    return returnLength;
}

// 析構
SerialPort::~SerialPort(){

}
View Code

  3.請求串列埠資料的執行緒類ThreadTest

  .h檔案

#ifndef THREADTEST_H
#define THREADTEST_H

#include <QThread>
#include <QDateTime>

#include "SerialPort.h"

class ThreadTest : public QThread
{
public:
    ThreadTest();
    ~ThreadTest();

private:
    // 執行緒
    virtual void run();
    // 請求實時資料
    void RequestRealData();
    // 列印資料
    void PrintData(QString str, uchar *data, int length);

private:
    // 校驗和,左閉右開
    ushort GetCheckSum(uchar * data, int startIndex, int endIndex);
    // 十六進位制轉ASCII
    uchar CharToAscii(uchar bHex);
    // ASCII轉十六進位制
    uchar AsciiToChar(uchar bChar);

private:
    // 串列埠
    SerialPort * serialPort;
};

#endif // THREADTEST_H
View Code

  .CPP檔案

#include "ThreadTest.h"

ThreadTest::ThreadTest()
{
    serialPort = new SerialPort("/dev/ttymxc5");
    serialPort->set_port_attr(9600, 8, "1", 'N',20,255);
}

// 執行緒
void ThreadTest::run(){
    while (TRUE) {
        RequestRealData();
        sleep(2);
    }
}

// 請求實時資料
void ThreadTest::RequestRealData(){
    // 清除串列埠記憶體中資料
//    uchar clearData[148];
//    int clearLen = serialPort->Read(clearData, 148, 1000);
//    qDebug() << "clearLen = " << clearLen;

    uchar dataReq[18] = {0x7E,0x31,0x31,0x30,0x31,0x32,0x41,0x34,0x34,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00,0x0D };
    ushort checkSum = GetCheckSum(dataReq, 1, 13);
    dataReq[13] = CharToAscii((checkSum >> 12)&0x0F);
    dataReq[14] = CharToAscii((checkSum >> 8)&0x0F);
    dataReq[15] = CharToAscii((checkSum >> 4)&0x0F);
    dataReq[16] = CharToAscii((checkSum >> 0)&0x0F);

    PrintData(QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz") + " Send: ",
              dataReq, 18);
    // 2020.12.04 02:58:35

    serialPort->Write(dataReq, 18);

    usleep(20 * 1000);
    uchar realDataAck[148];
    int ret1 = serialPort->Read(realDataAck, 148, 1000);
    int ret2 = 0;
//    if(ret1 < 148){
//        ret1 = ret1 < 0 ? 0 : ret1;
//        usleep(1000 * 100);
//        ret2 = serialPort->Read(realDataAck+ret1, 148-ret1, 1000);
//    }

    if(ret1+ret2 <= 0){
        return;
    }

    PrintData(QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz") + " Revice: ",
              realDataAck, ret1+ret2);

    // ParseAlarmDataAck(realDataAck, ret1);
}

// 校驗和,左閉右開
ushort ThreadTest::GetCheckSum(uchar * data, int startIndex, int endIndex){
    ushort sum = 0;
    for(int i = startIndex; i < endIndex; i++){
        sum += data[i];
    }

    sum = (~sum) + 1;
    return sum;
}

// 十六進位制轉ASCII
uchar ThreadTest::CharToAscii(uchar bHex){
    if((bHex>=0)&&(bHex<=9)){
        return bHex + 0x30;
    }

    return bHex += 0x37;
}

// ASCII轉十六進位制
uchar ThreadTest::AsciiToChar(uchar bChar){
    if((bChar>=0x30)&&(bChar<=0x39)){
        return bChar - 0x30;
    }else if((bChar>=0x41)&&(bChar<=0x46)){
        // Capital
        return bChar - 0x37;
    }

    // littlecase
    return bChar - 0x57;
}

// 列印資料
void ThreadTest::PrintData(QString str, uchar *data, int length){
    // return;

    for(int i = 0; i < length; i++){
        str += QString("%1 ").arg(data[i], 2, 16, QLatin1Char('0'));
    }

    qDebug() << "R:\t" << str;
}

ThreadTest::~ThreadTest()
{
    if(serialPort != NULL){
        delete serialPort;
        serialPort = NULL;
    }
}
View Code

  相關注釋程式碼中都有了,相對比較簡單,主要是讀寫操作,借用了一個例項,有些BCD碼的轉換和校驗位的處理,實際操作中可以替換掉處理。

  注意事項:

    串列埠名字肯定是不一樣的,圖紙上都會有;

    檢驗位、資料位、奇偶校驗這些設定;

    超時時間;

    請求資料的長度,接收資料的長度,這些不如在Windows上好用,都是需要自己注意處理的,有些程式碼是遮蔽的,比如是請求兩次達到你想要的長度,這些可以防止一次請求不完;

    讀請求應答ParseAlarmDataAck這個遮蔽的方法可以用來處理資料,這兒只是列印顯示了下;

  下面是測試用例,

  執行環境:ARM V7開發板 232串列埠

  1. 開發板接出線轉USB接入到電腦上,開啟電腦除錯軟體,如下圖所示,波特率、校驗位設定好,開啟串列埠就可以讀取到資料了;  

  

  2.設定串列埠除錯軟體傳送資料,點選收到回答後發下一幀,即在接收到串列埠的資料就發一幀應答資料,傳送區2,點選自動發,16進位制傳送;傳送區1 2 3都是獨立的,都可以傳送,咱們用一個即可;

  

  3.檢視開發板上列印的日誌,即接收到的資料,列印的Send是傳送的資料,Revice就是接收到的資料,接收到資料之後,自己寫一個ParseAlarmDataAck方法就可以處理資料啦,根據具體協議來操作;

  

  相對比較簡單,QT5是有了自帶的串列埠操作類,但貌似用QT4.8.6的更多呀...