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/
也可以下載我上傳到網盤上的:
二、文件內容介紹
1.下載到的文件爲qextserialport-1.2win-alpha ,解壓並打開後其內容如下。
(點擊圖片可以查看清晰大圖)
下面分別介紹:
(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.幾個類的簡單介紹。
下面是這幾個類的關係圖。
可以看到它們都繼承自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.添加文件。
我們將那四個文件添加到工程文件夾中。如下圖。
然後我們將這四個文件添加到工程中,在Qt Creator的工程列表中的工程文件夾上點擊鼠標右鍵,在彈出的菜單中選擇“Add Existing Files”菜單。如下圖。
我們在彈出的對話框中選中四個文件,按下“打開”按鈕即可,如下圖。
最終工程文件列表如下圖。
3.更改界面。
我們將界面設計如下。
其中的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.此時,我們運行程序,效果如下。
可以看到,已經成功完成通信了。
(注:我們這裏下位機使用的是單片機,它使用串口與計算機的COM1相連。單片機上運行的程序的功能是,接收到一個字符便向上位機發送一個字符串然後發送接收到的字符。)
兩個重要問題的講解:
一、關於數據接收。
我們想在程序中對接收的數據進行控制,但是readyRead()信號是一旦有數據到來就發射的,不過我們可以使用bytesAvailable()函數來檢查已經獲得的字節數,從而對數據接收進行控制。
(1)我們在widget.cpp中添加頭文件包含:#include <QDebug>
然後在讀串口函數中添加一行代碼,如下:
void Widget::readMyCom() //讀取串口數據並顯示出來{
qDebug() << “read: “<<myCom->bytesAvailable()<<”bytes”;
//我們輸出每次獲得的字節數
QByteArray temp = myCom->readAll();
ui->textBrowser->insertPlainText(temp);
}
運行程序,效果如下:
可以看到,我們獲取的數據並不是一次獲得的。
(2)利用上面的結論,我們可以讓串口緩衝區擁有了一定的數據後再讀取。
void Widget::readMyCom()
{
if(myCom->bytesAvailable() >=8 )
//如果可用數據大於或等於8字節再讀取
{
qDebug() << “read: “<<myCom->bytesAvailable()<<”bytes”;
QByteArray temp = myCom->readAll();
ui->textBrowser->insertPlainText(temp);
}
}
運行程序,效果如下:
我們發送了兩次數據,可以看到,這樣實現了每8個字節讀取一次,而最後剩餘的不夠8個字節的數據將會和後面的數據一起讀出。
然後我們將8改爲3,發送一次數據,效果如下:
改爲7,發送兩次數據,效果如下:
改爲11,發送兩次數據,效果如下:
改爲17,發送三次數據,效果如下:
重要結論:我們發送一次數據,應該獲得37字節的數據,然後我們對比上面的結果,發現了什麼?是的,其實串口每次讀取8字節的數據放到緩衝區,只有數據總數小於8字節時,纔會讀取小於8字節的數據。爲了再次驗證我們的結論,我們可以將上面程序中的“>=”改爲“==”,那麼只有8的倍數才能讀取數據(當然這裏37也可以),你可以測試一下。
關於接收數據方面,可以根據你自己的需要再去進行研究和改進,這裏只是拋磚引玉。
二、關於發送數據。
我們也可以使用函數獲取要發送的數據的大小,這裏有個bytesToWrite()可以獲取要發送的字節數。例如將發送數據更改如下:
void Widget::on_pushButton_clicked() //發送數據
{
myCom->write(ui->lineEdit->text().toAscii());
qDebug() << “write: “<<myCom->bytesToWrite()<<”bytes”;
//輸出要發送的字節數
}
運行後效果如下:
當然,對於要發送的數據的大小我們不是很關心,而且它還有很多方法可以實現,這個還有個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”選項。如下圖。
在彈出的對話框中我們選中“Delete file permanently”選項,確保刪除了工程文件夾中的文件。如下圖。
然後我們使用同樣的方法刪除win_qextserialport.cpp文件。
(2)我們按照Windows下添加文件的方法,向工程中添加posix_qextserialport.cpp和posix_qextserialport.h文件。最終工程文件列表如下。
2.設置編碼。
(這是因爲兩個系統使用的默認編碼不同造成的,如果你那裏沒有該問題,可以跳過這一步)
現在我們打開widget.cpp文件,發現中文出現亂碼,而且無法編輯。在編輯器最上面有一個黃色提示條和一個“Select Encoding”按鈕,我們點擊該按鈕。如下圖。
在彈出的對話框中我們選擇“GB2312”。按下“Reload with Encoding”按鈕,中文就可以正常顯示了。
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.下面我們運行程序。
這時可能會出現以下提示。
錯誤是說一個函數的調用出現了問題。我們點擊該錯誤,定位到出錯的位置,然後將那個函數中的第一個參數刪除即可。如下圖。
5.再次運行程序,這時已經可以正常運行了。
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