QT串口通信

Qt串口通信專題教程

前言

去年我使用Qt編寫串口通信程序時,將自己的學習過程寫成了教程(Qt編寫串口通信程序全程圖文講解),但是由於時間等原因,我只實現了Windows下的串口通信,並沒有去做Linux下的。自從教程發佈到網上後,就不斷有人提出相關的問題,而其中問的最多的就是,怎樣在Linux下實現串口通信。因爲有計劃安排,而且沒有開發板,所以一直沒能去研究,也就沒能給出很好的解決辦法。前些天,網友hqwfreefly 用Qt寫了一個叫linucom的Linux下串口調試程序,實現了Linux的串口通信。而且,正好現在我有幾天假期,所以就和hqwfreefly合作,將linucom更新爲Lincom,並且推出了Windows下的Wincom,然後完成了這篇Qt編寫串口通信程序的專題教程,也算完成了我的一個心願。

查看以前的教程:Qt編寫串口通信程序全程圖文講解

查看Wincom和Lincom介紹:Qt跨平臺串口通信軟件Wincom與Lincom

下載軟件,文檔和源碼:資源下載

教程概述

       該教程分三部分講述,第一部分講解qextserialport類的一些東東;第二部分講解在Windows下使用qextserialport類實現串口通信的方法,這裏將講述兩種不同的方法;第三部分講解在Linux下利用qextserialport類實現串口通信的方法。

       在這個教程中我們更注重知識的講解,而不是界面的設計。關於界面和其他應用問題,你可以查看以前的串口通信教程或者查看一下Wincom軟件的源碼。

第一部分 Qextserialport類介紹

在Qt中並沒有特定的串口控制類,現在大部分人使用的是第三方寫的qextserialport類,我們這裏也使用了該類。

一、文件下載

文件下載地址:

http://sourceforge.net/projects/qextserialport/files/

也可以下載我上傳到網盤上的:

http://good.gd/494307.htm

二、文件內容介紹

1.下載到的文件爲qextserialport-1.2win-alpha ,解壓並打開後其內容如下。

(點擊圖片可以查看清晰大圖)

Hosted by ImageHost.org

下面分別介紹:

(1)doc文件夾中的文件內容是QextSerialPort類和QextBaseType的簡單的說明,我們可以使用記事本程序將它們打開。

(2)examples文件夾中是幾個例子程序,可以看一下它的源碼,不過想運行它們好像會出很多問題啊。

(3)html文件夾中是QextSerialPort類的使用文檔。

(4)然後就是剩下的幾個文件了。其中qextserialenumerator.cpp及qextserialenumerator.h文件中定義的QextSerialEnumerator類是用來獲取平臺上可用的串口信息的。不過,這個類好像並不怎麼好用,而且它不是我們關注的重點,所以下面就不再介紹它了。

(5)qextserialbase.cpp和qextserialbase.h文件定義了一個QextSerialBase類,win_qextserialport.cpp和win_qextserialport.h文件定義了一個Win_QextSerialPort類,posix_qextserialport.cpp和posix_qextserialport.h文件定義了一個Posix_QextSerialPort類,qextserialport.cpp和qextserialport.h文件定義了一個QextSerialPort類。這個QextSerialPort類就是我們上面所說的那個,它是所有這些類的子類,是最高的抽象,它屏蔽了平臺特徵,使得在任何平臺上都可以使用它。

2.幾個類的簡單介紹。

下面是這幾個類的關係圖。

Hosted by ImageHost.org

可以看到它們都繼承自QIODevice類,所以該類的一些函數我們也可以直接來使用。圖中還有一個QextBaseType類,其實它只是一個標識,沒有具體的內容,它用來表示Win_QextSerialPort或Posix_QextSerialPort 中的一個類,因爲在QextSerialPort類中使用了條件編譯,所以QextSerialPort類既可以繼承自Win_QextSerialPort類,也可以繼承自Posix_QextSerialPort類,所以使用了QextBaseType來表示。這一點我們可以在qextserialport.h文件中看到。再說QextSerialPort類,其實它只是爲了方便程序的跨平臺編譯,使用它可以在不同的平臺上,根據不同的條件編譯繼承不同的類。所以它只是一個抽象,提供了幾個構造函數而已,並沒有具體的內容。在qextserialport.h文件中的條件編譯內容如下:

/*POSIX CODE*/

#ifdef _TTY_POSIX_

#include “posix_qextserialport.h”

#define QextBaseType Posix_QextSerialPort

/*MS WINDOWS CODE*/

#else

#include “win_qextserialport.h”

#define QextBaseType Win_QextSerialPort

#endif

所以,其實我們沒有必要使用這個類,直接使用Win_QextSerialPort或Posix_QextSerialPort就可以了。當然如果你想使用這個類,實現同樣的源程序可以直接在Windows和Linux下編譯運行,那麼一定要注意在Linux下這裏需要添加 #define _TTY_POSIX_ 。而我們這裏爲了使得程序更明瞭,所以沒有使用該類,下面也就不再介紹它了。

       QextSerialBase類繼承自QIODevice類,它提供了操作串口所必需的一些變量和函數等,而Win_QextSerialPort和Posix_QextSerialPort均繼承自QextSerialBase類,Win_QextSerialPort類添加了Windows平臺下操作串口的一些功能,Posix_QextSerialPort類添加了Linux平臺下操作串口的一些功能。所以說,在Windows下我們使用Win_QextSerialPort類,在Linux下我們使用Posix_QextSerialPort類。

3.在QextSerialBase類中還涉及到了一個枚舉變量QueryMode。

它有兩個值Polling和EventDriven 。QueryMode指的是讀取串口的方式,下面我們稱爲查詢模式,我們將Polling稱爲查詢方式Polling,將EventDriven稱爲事件驅動方式。

       事件驅動方式EventDriven就是使用事件處理串口的讀取,一旦有數據到來,就會發出readyRead()信號,我們可以關聯該信號來讀取串口的數據。在事件驅動的方式下,串口的讀寫是異步的,調用讀寫函數會立即返回,它們不會凍結調用線程。

而查詢方式Polling則不同,讀寫函數是同步執行的,信號不能工作在這種模式下,而且有些功能也無法實現。但是這種模式下的開銷較小。我們需要自己建立定時器來讀取串口的數據。

在Windows下支持以上兩種模式,而在Linux下只支持Polling模式。

三、小結。

       這裏講了這麼多,最後要說的只是,我們在Qt中使用這個類編寫串口程序,根據平臺的不同只需要分別使用四個文件。

在Windows下是:

qextserialbase.cpp和qextserialbase.h 以及win_qextserialport.cpp和win_qextserialport.h

在Linux下是:

qextserialbase.cpp和qextserialbase.h 以及posix_qextserialport.cpp和posix_qextserialport.h

而在Windows下我們可以使用事件驅動EventDriven方式,也可以使用查詢Polling方式,但是在Linux下我們只能使用查詢Polling方式。

第二部分 在Windows下編寫串口通信程序

我們的環境是Windows xp,Qt4.6.3及Qt Creator2.0。

第一,下面我們首先使用事件驅動來實現串口通信。

1.新建工程。

我們在Qt Creator中新建Qt Gui工程,命名爲myCom,Base Class選擇QWidget。

2.添加文件。

我們將那四個文件添加到工程文件夾中。如下圖。

Hosted by ImageHost.org

然後我們將這四個文件添加到工程中,在Qt Creator的工程列表中的工程文件夾上點擊鼠標右鍵,在彈出的菜單中選擇“Add Existing Files”菜單。如下圖。

Hosted by ImageHost.org

我們在彈出的對話框中選中四個文件,按下“打開”按鈕即可,如下圖。

Hosted by ImageHost.org

最終工程文件列表如下圖。

Hosted by ImageHost.org

3.更改界面。

我們將界面設計如下。

Hosted by ImageHost.org

其中的Text Browser 部件用來顯示接收到的數據,Line Edit部件用來輸入要發送的數據,Push Button按鈕用來發送數據。我們保持各部件的屬性爲默認值即可。

4. 我們在widget.h文件中進行對象及函數聲明。

添加頭文件包含:#include “win_qextserialport.h”

然後在private中聲明對象:Win_QextSerialPort *myCom;

聲明私有槽函數:

private slots:

    void on_pushButton_clicked(); //”發送數據”按鈕槽函數

void readMyCom(); //讀取串口

5.在widget.cpp文件中進行更改。

在構造函數中添加代碼,完成後,構造函數內容如下:

Widget::Widget(QWidget *parent) :

    QWidget(parent),

    ui(new Ui::Widget)

{

    ui->setupUi(this);

    myCom = new Win_QextSerialPort(“COM1″,QextSerialBase::EventDriven);

    //定義串口對象,指定串口名和查詢模式,這裏使用事件驅動EventDriven

    myCom ->open(QIODevice::ReadWrite);

    //以讀寫方式打開串口

    myCom->setBaudRate(BAUD9600);

    //波特率設置,我們設置爲9600

    myCom->setDataBits(DATA_8);

   //數據位設置,我們設置爲8位數據位

    myCom->setParity(PAR_NONE);

    //奇偶校驗設置,我們設置爲無校驗

    myCom->setStopBits(STOP_1);

    //停止位設置,我們設置爲1位停止位

    myCom->setFlowControl(FLOW_OFF);

    //數據流控制設置,我們設置爲無數據流控制

    myCom->setTimeout(500);

    //延時設置,我們設置爲延時500ms,這個在Windows下好像不起作用

    connect(myCom,SIGNAL(readyRead()),this,SLOT(readMyCom()));

    //信號和槽函數關聯,當串口緩衝區有數據時,進行讀串口操作

}

實現槽函數:

void Widget::readMyCom() //讀取串口數據並顯示出來

{

    QByteArray temp = myCom->readAll();

    //讀取串口緩衝區的所有數據給臨時變量temp

    ui->textBrowser->insertPlainText(temp);

    //將串口的數據顯示在窗口的文本瀏覽器中

}

void Widget::on_pushButton_clicked() //發送數據

{

    myCom->write(ui->lineEdit->text().toAscii());

    //以ASCII碼形式將數據寫入串口

}

6.此時,我們運行程序,效果如下。

Hosted by ImageHost.org

可以看到,已經成功完成通信了。

(注:我們這裏下位機使用的是單片機,它使用串口與計算機的COM1相連。單片機上運行的程序的功能是,接收到一個字符便向上位機發送一個字符串然後發送接收到的字符。)

兩個重要問題的講解:

一、關於數據接收。

我們想在程序中對接收的數據進行控制,但是readyRead()信號是一旦有數據到來就發射的,不過我們可以使用bytesAvailable()函數來檢查已經獲得的字節數,從而對數據接收進行控制。

(1)我們在widget.cpp中添加頭文件包含:#include <QDebug>

       然後在讀串口函數中添加一行代碼,如下:

void Widget::readMyCom() //讀取串口數據並顯示出來

{

    qDebug() << “read: “<<myCom->bytesAvailable()<<”bytes”;

    //我們輸出每次獲得的字節數

    QByteArray temp = myCom->readAll();

    ui->textBrowser->insertPlainText(temp);

}

運行程序,效果如下:

Hosted by ImageHost.org

可以看到,我們獲取的數據並不是一次獲得的。

(2)利用上面的結論,我們可以讓串口緩衝區擁有了一定的數據後再讀取。

void Widget::readMyCom()

{

if(myCom->bytesAvailable() >=8 )

//如果可用數據大於或等於8字節再讀取

    {

        qDebug() << “read: “<<myCom->bytesAvailable()<<”bytes”;

        QByteArray temp = myCom->readAll();

        ui->textBrowser->insertPlainText(temp);

   }

}

運行程序,效果如下:

Hosted by ImageHost.org

我們發送了兩次數據,可以看到,這樣實現了每8個字節讀取一次,而最後剩餘的不夠8個字節的數據將會和後面的數據一起讀出。

然後我們將8改爲3,發送一次數據,效果如下:

Hosted by ImageHost.org

改爲7,發送兩次數據,效果如下:

Hosted by ImageHost.org

改爲11,發送兩次數據,效果如下:

Hosted by ImageHost.org

改爲17,發送三次數據,效果如下:

Hosted by ImageHost.org

重要結論:我們發送一次數據,應該獲得37字節的數據,然後我們對比上面的結果,發現了什麼?是的,其實串口每次讀取8字節的數據放到緩衝區,只有數據總數小於8字節時,纔會讀取小於8字節的數據。爲了再次驗證我們的結論,我們可以將上面程序中的“>=”改爲“==”,那麼只有8的倍數才能讀取數據(當然這裏37也可以),你可以測試一下。

       關於接收數據方面,可以根據你自己的需要再去進行研究和改進,這裏只是拋磚引玉。

二、關於發送數據。

我們也可以使用函數獲取要發送的數據的大小,這裏有個bytesToWrite()可以獲取要發送的字節數。例如將發送數據更改如下:

void Widget::on_pushButton_clicked() //發送數據

{

    myCom->write(ui->lineEdit->text().toAscii());

    qDebug() << “write: “<<myCom->bytesToWrite()<<”bytes”;

    //輸出要發送的字節數

}

運行後效果如下:

Hosted by ImageHost.org

當然,對於要發送的數據的大小我們不是很關心,而且它還有很多方法可以實現,這個還有個bytesWritten()信號函數來獲取已經發送的數據的大小,不過好像它不是很好用。這裏將它們提出來,只是供大家參考而已。

第二,使用查詢方式Polling來實現串口通信。

這裏再次說明,Polling方式是不能使用readyRead()信號的,所以我們需要自己設置定時器,來不斷地讀取緩衝區的數據。

1.我們在widget.h中聲明一個定時器對象。

添加頭文件包含:#include <QTimer>

添加private變量:QTimer *readTimer;

2.我們在widget.cpp文件中的構造函數中更改。

(1)將串口定義更改爲:

myCom = new Win_QextSerialPort(“COM1″,QextSerialBase::Polling);

//定義串口對象,指定串口名和查詢模式,這裏使用Polling

(2)定義定時器,並將以前的關聯更改爲定時器的關聯。

readTimer = new QTimer(this);

    readTimer->start(100);

    //設置延時爲100ms

    connect(readTimer,SIGNAL(timeout()),this,SLOT(readMyCom()));

//信號和槽函數關聯,延時一段時間,進行讀串口操作

3.此時運行程序,便可以正常收發數據了。

重點:關於延時問題。

上面的程序中可以進行數據的接收了,但是好像中間的延時有點長,要等一會兒才能收到數據,而且即便我們將定時器改爲10ms 也不行。問題在哪裏呢?其實真正控制串口讀寫時間的不是我們的定時器,而是延時timeout。我們在構造函數中設置了延時:

myCom->setTimeout(500);

//延時設置,我們設置爲延時500ms

我們前面說延時並不起作用,那是因爲是在事件驅動的情況下,一旦有數據到來就會觸發readyRead()信號,所以延時不起作用。但是現在,真正控制串口讀寫數據間隔的就是這個函數。這裏值得注意,我們現在所說的串口讀寫是指底層的串口讀寫,從上面的程序中我們也可以看到,我們每隔100ms去讀串口,確切地說,應該是去讀串口緩衝區。而timeout纔是正真的讀取串口數據,將讀到的數據放入串口緩衝區。所以如果timeout時間很長,即便我們的定時器時間再短,也是讀不到數據的。所以我們這裏需要將timeout設置爲較小的值,比如10。我們更改代碼:

myCom->setTimeout(10);

這樣再運行程序,我們就可以很快地獲得數據了。

關於數據接收:事件驅動那裏的結論依然有用,不過這裏更多的是靠讀取的時間間隔來控制。

關於發送數據:這時bytesToWrite()函數就不再那麼好用了。

第三部分 在Linux下編寫串口通信程序

我這裏的環境是Ubuntu 10.04,Qt 4.6.3和Qt Creator2.0 。上面已經提到,在Linux下只能使用Polling的方式讀取串口數據,所以我們將上面Windows下的應用Polling的程序在Linux下重新編譯。我們使用Qt Creator打開該工程,然後進行下面的操作。

1.文件替換。

將工程中的win_qextserialport.cpp和win_qextserialport.h文件替換成posix_qextserialport.cpp和posix_qextserialport.h文件。

(1)我們先刪除工程中的win_qextserialport.cpp和win_qextserialport.h文件。

在工程列表中用鼠標右擊win_qextserialport.h,然後選擇“Remove File”選項。如下圖。

Hosted by ImageHost.org

在彈出的對話框中我們選中“Delete file permanently”選項,確保刪除了工程文件夾中的文件。如下圖。

Hosted by ImageHost.org

然後我們使用同樣的方法刪除win_qextserialport.cpp文件。

(2)我們按照Windows下添加文件的方法,向工程中添加posix_qextserialport.cpp和posix_qextserialport.h文件。最終工程文件列表如下。

Hosted by ImageHost.org

2.設置編碼。

(這是因爲兩個系統使用的默認編碼不同造成的,如果你那裏沒有該問題,可以跳過這一步)

現在我們打開widget.cpp文件,發現中文出現亂碼,而且無法編輯。在編輯器最上面有一個黃色提示條和一個“Select Encoding”按鈕,我們點擊該按鈕。如下圖。

Hosted by ImageHost.org

在彈出的對話框中我們選擇“GB2312”。按下“Reload with Encoding”按鈕,中文就可以正常顯示了。

Hosted by ImageHost.org

3.更改程序。

在widget.h 文件中:

將以前的#include “win_qextserialport.h”更改爲#include “posix_qextserialport.h”

將以前的Win_QextSerialPort *myCom;更改爲Posix_QextSerialPort *myCom;

在widget.cpp文件中:

將以前的myCom = new Win_QextSerialPort(“COM1″,QextSerialBase::Polling);

更改爲:myCom = new Posix_QextSerialPort(“/dev/ttyS0″,QextSerialBase::Polling);

(這裏一定要注意串口名稱的寫法。)

4.下面我們運行程序。

這時可能會出現以下提示。

Hosted by ImageHost.org

錯誤是說一個函數的調用出現了問題。我們點擊該錯誤,定位到出錯的位置,然後將那個函數中的第一個參數刪除即可。如下圖。

Hosted by ImageHost.org

5.再次運行程序,這時已經可以正常運行了。

Hosted by ImageHost.org

6.小結

可以看到將Windows下的串口程序在Linux下重新編譯是很簡單的,我們只需要替換那兩個文件,然後更改一下頭文件包含,對象定義和串口名即可。

結尾

       本教程比較詳細的講述了使用Qt在Windows下和Linux下編寫串口通信程序的方法,但是對於串口通信的內容還有很多,我們現在還無法全部涵蓋。希望廣大網友可以提出寶貴建議,將Wincom軟件進行功能擴展,或者將本教程繼續更新下去。

       如果你喜歡本教程的寫作風格,而且您也是Qt愛好者,您可以訪問我們的網站,這裏有一系列教程和軟件供您參考學習,當然也希望您能爲我們的網站添磚加瓦,讓我們一起爲Qt 及Qt Creator的普及貢獻自己的力量。

關於我們

yafeilinux不是個人,而是一個團隊!

網站:www.yafeilinux.com   郵箱: QQ羣:158054692

合作者 hqwfreefly

郵箱:   個人主頁:http://hi.baidu.com/hqwfreefly


以上博客來自:http://hi.baidu.com/zhu8caizi/item/2a9a4da4a2de533a030a4de1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章