(說明:我們的編程環境是windows xp下,在Qt Creator中進行,如果在Linux下或直接用源碼編寫,程式稍有不同,請自己改動。)
在Qt中並沒有特定的串口控制類,現在大部分人使用的是第三方寫的qextserialport類,我們這裡也是使用的該類。我們可以去
http://sourceforge.net/projects/qextserialport/files/
進行下載,也可以去下載我上傳到網上的:
http://download.csdn.net/source/1762781 或 http://www.qtcn.org/bbs/read.php?tid=22847
下載到的檔案為:qextserialport-1.2win-alpha.zip
其內容如:
我們在windows下只需要使用其中的6個檔案:
qextserialbase.cpp和qextserialbase.h,qextserialport.cpp和qextserialport.h,win_qextserialport.cpp和win_qextserialport.h
如果在Linux下只需將win_qextserialport.cpp和win_qextserialport.h 換為 posix_qextserialport.cpp和posix_qextserialport.h即可。
第一部分:
下面我們將講述編程的詳細過程,這裡我們先給出完整的程式,然後到第二部分再進行逐句分析。
1.開啟Qt Creator,建立Qt4 Gui Application,工程名設定為mycom,其他使用預設選項。
(注意:建立的工程路徑不能有中文。)
2.將上面所說的6個檔案複製到工程檔案夾下,如。
3.在工程中添加這6個檔案。
在Qt Creator中左側的檔案清單上,滑鼠右擊工程檔案夾,在彈出的菜單中選擇Add Existing Files,添加已存在的檔案。如:
選擇工程檔案夾裡的那6個檔案,進行添加。如。
添加好後檔案清單如所示:
4.點擊mainwindow.ui,在視窗上加入一個Text Browser,用來顯示資訊。如。
5.在mainwindow.h的相應位置添加標頭檔#include "win_qextserialport.h",添加對象聲明Win_QextSerialPort *myCom;添加槽函式宣告 void readMyCom();添加完後,如。
6.在mainwindow.cpp的類的建構函式中添加如下語句。
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
struct PortSettings myComSetting = {BAUD9600,DATA_8,PAR_NONE,STOP_1,FLOW_OFF,500};
//定義一個結構體,用來存放串口各個參數
myCom = new Win_QextSerialPort("com1",myComSetting,QextSerialBase::EventDriven);
//定義串口對象,並傳遞參數,在建構函式裡對其進行初始化
myCom ->open(QIODevice::ReadWrite);
//以可讀寫方式開啟串口
connect(myCom,SIGNAL(readyRead()),this,SLOT(readMyCom()));
//訊號和槽函數關聯,當串口緩衝區有資料時,進行讀串口操作
}
在下面添加readMyCom()函數的定義,添加如下代碼。
void MainWindow::readMyCom() //讀串口函數
{
QByteArray temp = myCom->readAll();
//讀取串口緩衝區的所有資料給臨時變數temp
ui->textBrowser->insertPlainText(temp);
//將串口的資料顯示在視窗的文本瀏覽器中
}
添加完代碼後如。
此時如果運行程式,已經能實現讀取串口資料的功能了。我們將單片機採集的溫度資訊由串口傳給電腦,效果如。
這樣最簡單的串口通訊程式就完成了。可以看到它只需要加入幾行代碼即可,非常簡單。
第二部分:
上一部分中已經介紹了實現最簡單的串口接收程式的編寫,下面將對程式內容進行分析。
1.首先應說明操作串口的流程。
步驟一:設定串口參數,如:傳輸速率,資料位元,同位,停止位,資料流控制等。
步驟二:選擇串口,如windows下的串口1為“com1”,Linux下為“ttyS0”等,並開啟串口。
步驟三:讀或寫串口。
步驟四:關閉串口。
(我們上一個程式沒有寫串口和關閉串口的功能,開啟串口也是在建構函式裡完成的,因為那隻是為了用最簡單的方法完成串口程式的編寫。在後面我們將會對它進行修改和完善。)
2.下面我們將按照上面的操作串口的流程,講解第一個程式的編寫。
第一,我們在寫程式之前,應該瀏覽一下那6個檔案,大概看一下它們裡面都是什麼內容,各個檔案各個類之間有什麼聯絡。在win_qextserialport.cpp檔案中,我們看它的最後一個建構函式,會發現,串口可以在這裡進行初始化。
Win_QextSerialPort::Win_QextSerialPort(const QString & name, const PortSettings& settings, QextSerialBase::QueryMode mode) {
Win_Handle=INVALID_HANDLE_VALUE;
setPortName(name);
setBaudRate(settings.BaudRate);
setDataBits(settings.DataBits);
setStopBits(settings.StopBits);
setParity(settings.Parity);
setFlowControl(settings.FlowControl);
setTimeout(settings.Timeout_Millisec);
setQueryMode(mode);
init();
}
它共有三個參數,其中第一個參數const QString & name,應該是串口的名字,是QString類型,我們可以用串口1即“com1”,不用過多說明。下面我們主要研究第二個和第三個參數。
第二,我們查看第二個參數的位置。
在Qt Creator的菜單中選擇Edit->Find/Replace->All projects,如。
在彈出的對話方塊中輸入要尋找的內容PortSettings,如。
點擊Search後,便能在下面顯示出整個工程中所有PortSettings的位置。如。
我們點擊第一條,可以看到在qextserialbase.h檔案中有一個struct PortSettings,如。
我們雙擊這一條,進入相應的檔案。如。
struct PortSettings
{
BaudRateType BaudRate;
DataBitsType DataBits;
ParityType Parity;
StopBitsType StopBits;
FlowType FlowControl;
long Timeout_Millisec;
};
可以看到在這個結構體裡定義了串口初始化的各個參數,而對於BaudRateType等類型的定義,我們在這個結構體的上面可以看到,它們是多個枚舉變數。如。
這時我們便應該明白了,這個結構體便是實現串口參數設定的。
第三,定義串口參數。
BaudRateType BaudRate;
傳輸速率設定,我們設定為9600,即程式中用BAUD9600;
DataBitsType DataBits;
資料位元設定,我們設定為8位元據位,即DATA_8;
ParityType Parity;
同位設定,我們設定為無校正,即PAR_NONE;
StopBitsType StopBits;
停止位設定,我們設定為1位停止位,即STOP_1;
FlowType FlowControl;
資料流控制設定,我們設定為無資料流控制,即FLOW_OFF;
long Timeout_Millisec;
延時設定,我們設定為延時500ms,即500;
這樣便寫出了程式中的那句:
struct PortSettings myComSetting = {BAUD9600,DATA_8,PAR_NONE,STOP_1,FLOW_OFF,500};
我們定義了一個結構體變數myComSetting,並對其進行了初始化。
第四,設定第三個參數。
我們先按上面的方法找到它的定義位置,在qextserialbase.h中,如。
可以看到查詢模式也是枚舉變數,有兩個選項,我們選擇第二個EventDriven,事件驅動。
到這裡,我們就可以定義Win_QextSerialPort類的變數了,就是那句:
myCom = new Win_QextSerialPort("com1",myComSetting,QextSerialBase::EventDriven);
它完成了串口的選擇和串口的初始化。
第五,寫開啟串口函數和讀串口函數。
查看win_qextserialport.h檔案,我們會發現Win_QextSerialPort類繼承自QextSerialBase類。
查看qextserialbase.h檔案,我們會發現QextSerialBase類繼承自QIODevice 類。
我們在Qt的協助中查看QIODevice 類,如。
其部分內容如。可以看到其中有enum OpenModeFlag { NotOpen, ReadOnly, WriteOnly, ReadWrite, ..., Unbuffered },virtual bool open ( OpenMode mode ),QByteArray readAll ()等內容。
而下面的訊號函數中有void readyRead ();它可以查看串口是否有新的資料傳來。
所以,我們可以用這個類裡的這些函數操作串口。
如程式中的語句:
myCom ->open(QIODevice::ReadWrite);
//我們調用了其中的open函數,用ReadWrite可讀寫的方式進行開啟串口,這個open函數在win_qextserialport.cpp中被重定義了
connect(myCom,SIGNAL(readyRead()),this,SLOT(readMyCom()));
//我們關聯訊號readyRead(),和自己寫的槽函數readMyCom(),當串口有資料傳來時進行讀串口操作
void MainWindow::readMyCom() //自己寫的讀串口函數
{
QByteArray temp = myCom->readAll();
//我們調用readAll()函數,讀取串口中所有資料,在上面可以看到其傳回值是QByteArray類型
ui->textBrowser->insertPlainText(temp);
//調用insertPlainText()函數,是視窗上的文本瀏覽器中連續輸出資料,而不是每次寫資料前都清除以前的
//資料,可以在Qt的協助裡查看這個函數的說明
}
這樣我們便寫完了所有的語句,最後只需要在mainwindow.h檔案中加入相應的標頭檔,對象聲明,函式宣告即可。
這裡需要說明的是我們一定要學會查看檔案和使用協助文檔,將我們不懂得知識一點一點搞明白。
第三部分:
下面的程式在第一部分中所寫的程式上進行了一些改進。加入開啟和關閉串口,發送資料等功能。
1.加入了“開啟串口”,“關閉串口”“傳送資料”三個按鈕,加入了一個行編輯框Line Edit。它們的命名如下:
“開啟串口”按鈕命名為:openMyComBtn
“關閉串口”按鈕命名為:closeMyComBtn
“傳送資料”按鈕命名為:sendMsgBtn
要傳送資料的行編輯框命名為:sendMsgLineEdit
介面如。
2.在“開啟串口”按鈕上右擊,選擇Go to slot選項,然後選擇clicked()選項,進入它的單擊事件槽函數中,將上個程式中在建構函式裡寫的語句全部剪下到這裡。然後加入幾句按鈕的狀態設定語句。如下:
void MainWindow::on_openMyComBtn_clicked()
{
struct PortSettings myComSetting = {BAUD9600,DATA_8,PAR_NONE,STOP_1,FLOW_OFF,500};
//定義一個結構體,用來存放串口各個參數
myCom = new Win_QextSerialPort("com1",myComSetting,QextSerialBase::EventDriven);
//定義串口對象,並傳遞參數,在建構函式裡對其進行初始化
myCom ->open(QIODevice::ReadWrite);
//以可讀寫方式開啟串口
connect(myCom,SIGNAL(readyRead()),this,SLOT(readMyCom()));
//訊號和槽函數關聯,當串口緩衝區有資料時,進行讀串口操作
ui->openMyComBtn->setEnabled(false); //開啟串口後“開啟串口”按鈕不可用
ui->closeMyComBtn->setEnabled(true); //開啟串口後“關閉串口”按鈕可用
ui->sendMsgBtn->setEnabled(true); //開啟串口後“發送資料”按鈕可用
}
在建構函式裡也添加幾句按鈕初始狀態設定語句,如下:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->closeMyComBtn->setEnabled(false); //開始“關閉串口”按鈕不可用
ui->sendMsgBtn->setEnabled(false); //開始“發送資料”按鈕不可用
}
更改後程式如所示:
這時運行程式,效果如下:
3.按上面的方法進入“關閉串口”按鈕和“發送資料”按鈕的單擊事件的槽函數,更改如下。
void MainWindow::on_closeMyComBtn_clicked() //關閉串口槽函數
{
myCom->close(); //關閉串口,該函數在win_qextserialport.cpp檔案中定義
ui->openMyComBtn->setEnabled(true); //關閉串口後“開啟串口”按鈕可用
ui->closeMyComBtn->setEnabled(false); //關閉串口後“關閉串口”按鈕不可用
ui->sendMsgBtn->setEnabled(false); //關閉串口後“發送資料”按鈕不可用
}
/***********************************/
void MainWindow::on_sendMsgBtn_clicked() //發送資料槽函數
{
myCom->write(ui->sendMsgLineEdit->text().toAscii());
//以ASCII碼形式將行編輯框中的資料寫入串口
}
程式如:
最終效果如下:
(將資料x發送給單片機,單片機返回you send message is : x)
第四部分:
本文一開始先講解對程式的改進,在文章最後將要講解一些重要問題。
1.在視窗中加入一些組合框Combo Box,它們的名稱及條目如下:
串口:portNameComboBox,條目為:COM1,COM2
傳輸速率:baudRateComboBox,條目為:9600,115200
資料位元:dataBitsComboBox,條目為:8,7
校正位:parityComboBox,條目為:無,奇,偶
停止位:stopBitsComboBox,條目為:1,2
(註:在視窗上的Combo Box上雙擊,在彈出的對話方塊上按“+”號,可添加條目。我們只是為了示範,所以只加了這幾個條目,你可以根據自己的需要添加。)
改好的視窗如下所示:
2.更改“開啟串口”按鈕的單擊事件槽函數。
void MainWindow::on_openMyComBtn_clicked()
{
QString portName = ui->portNameComboBox->currentText(); //擷取串口名
myCom = new Win_QextSerialPort(portName,QextSerialBase::EventDriven);
//定義串口對象,並傳遞參數,在建構函式裡對其進行初始化
myCom ->open(QIODevice::ReadWrite); //開啟串口
if(ui->baudRateComboBox->currentText()==tr("9600")) //根據組合框內容對串口進行設定
myCom->setBaudRate(BAUD9600);
else if(ui->baudRateComboBox->currentText()==tr("115200"))
myCom->setBaudRate(BAUD115200);
//設定傳輸速率
if(ui->dataBitsComboBox->currentText()==tr("8"))
myCom->setDataBits(DATA_8);
else if(ui->dataBitsComboBox->currentText()==tr("7"))
myCom->setDataBits(DATA_7);
//設定資料位元
if(ui->parityComboBox->currentText()==tr("無"))
myCom->setParity(PAR_NONE);
else if(ui->parityComboBox->currentText()==tr("奇"))
myCom->setParity(PAR_ODD);
else if(ui->parityComboBox->currentText()==tr("偶"))
myCom->setParity(PAR_EVEN);
//設定同位
if(ui->stopBitsComboBox->currentText()==tr("1"))
myCom->setStopBits(STOP_1);
else if(ui->stopBitsComboBox->currentText()==tr("2"))
myCom->setStopBits(STOP_2);
//設定停止位
myCom->setFlowControl(FLOW_OFF); //設定資料流控制,我們使用無資料流控制的預設設定
myCom->setTimeout(500); //設定延時
connect(myCom,SIGNAL(readyRead()),this,SLOT(readMyCom()));
//訊號和槽函數關聯,當串口緩衝區有資料時,進行讀串口操作
ui->openMyComBtn->setEnabled(false); //開啟串口後“開啟串口”按鈕不可用
ui->closeMyComBtn->setEnabled(true); //開啟串口後“關閉串口”按鈕可用
ui->sendMsgBtn->setEnabled(true); //開啟串口後“發送資料”按鈕可用
ui->baudRateComboBox->setEnabled(false); //設定各個組合框不可用
ui->dataBitsComboBox->setEnabled(false);
ui->parityComboBox->setEnabled(false);
ui->stopBitsComboBox->setEnabled(false);
ui->portNameComboBox->setEnabled(false);
}
這裡我們先擷取串口的名稱,然後調用另一個建構函式對myCom進行定義,這個建構函式裡沒有串口的設定參數。然後開啟串口。然後擷取串口的設定資料,用setBaudRate();等一系列函數進行串口的設定,這些函數都在win_qextserialport.cpp檔案中定義,如。
看完前面幾部分的內容,對於這幾個函數應該很好理解,這裡不再解釋。在最後我們對添加的那幾個組合框進行了不可用設定,使其在串口開啟的情況下不能選擇。
程式如下:
3.更改“關閉串口”按鈕單擊事件的槽函數。
void MainWindow::on_closeMyComBtn_clicked()
{
myCom->close();
ui->openMyComBtn->setEnabled(true); //關閉串口後“開啟串口”按鈕可用
ui->closeMyComBtn->setEnabled(false); //關閉串口後“關閉串口”按鈕不可用
ui->sendMsgBtn->setEnabled(false); //關閉串口後“發送資料”按鈕不可用
ui->baudRateComboBox->setEnabled(true); //設定各個組合框可用
ui->dataBitsComboBox->setEnabled(true);
ui->parityComboBox->setEnabled(true);
ui->stopBitsComboBox->setEnabled(true);
ui->portNameComboBox->setEnabled(true);
}
這裡只是加入了一些使組合框在“關閉串口”按鈕按下後變為可用的語句。
程式如下:
4.更改main.cpp檔案。
#include
#include //加入標頭檔
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTextCodec::setCodecForTr(QTextCodec::codecForLocale());
//使程式可處理中文
MainWindow w;
w.show();
return a.exec();
}
因為上面的程式中用到了中文,為了能使程式識別中文,我們需要在主函數中加入這些語句。
程式如下:
5.運行程式。
開啟後程式介面如下。
正常發送1後效果如下。
設定為“奇數同位”,發送完1的效果如。(接收到的是亂碼)
到這裡,整個程式就寫完了。
重要問題說明:
(下面所說的第一個程式是指第一部分中寫的那個程式,第二個程式是指第三部分更改完後的程式,第三個程式是指第四部分更改完後的程式。)
問題一:更改第一個程式中的代碼。
struct PortSettings myComSetting = {BAUD9600,DATA_8,PAR_NONE,STOP_1,FLOW_OFF,500};
myCom = new Win_QextSerialPort("com1",myComSetting,QextSerialBase::EventDriven);
這兩行代碼如果換為下面一行:
myCom = new Win_QextSerialPort("com1",QextSerialBase::EventDriven);
你再運行一下程式,是不是還能用?那是說明我們的串口設定的結構體myComSetting沒有用嗎?你可以把上面的結構體裡的傳輸速率由9600改為115200,如果這個結構體有用,那麼程式不可能再接收到資料,不過,你再運行一下程式,是這樣嗎?
如此看來,我們對串口進行的設定果真沒用,那預設的串口設定是什麼呢?我們先看下一個問題。
問題二:同時開啟第三個程式和第二個程式。
(注意:兩個程式的串口不能同時開啟,所以開啟一個程式的串口時要將另一個程式的串口關閉。)
我們先在第三個程式上按預設設定開啟串口,發送資料1。然後關閉串口,在第二個程式上開啟串口,發送資料1。可以看到兩個程式上接受到的資訊都正確。如。
我們關閉第二個程式上的串口,再將第三個程式上設定為奇數同位,然後開啟串口,發送資料1,可以看到其收到的資料顯示亂碼。這時我們關閉第三個程式上的串口,開啟第二個程式上的串口,發送資料1,你會驚奇地發現,它收到的資訊也是亂碼。如。
這到底是怎麼回事呢?我們也可以去網上下載其他的串口助手進行實驗,也可以改變傳輸速率進行實驗。由所有的結果得出的結論只能是:我們用那個結構體作為參數傳過去後,並沒有對串口進行設定,而程式運行使用的串口設定是系統以前保留的設定。那麼,為什麼會這樣呢?我們看下面的一個問題。
問題三:更改第三個程式中的代碼。
myCom ->open(QIODevice::ReadWrite);
放到設定串口的語句之後,
connect(myCom,SIGNAL(readyRead()),this,SLOT(readMyCom()));
這句之前,然後再運行程式。你會發現程式的串口設定功能已經不起作用了。現在知道原因了吧?!
其實,上面的三個問題是一個問題,它的結論是,寫串口程式時,要先開啟串口再對它進行設定,不然設定就不會起作用。所以,這裡應該說明,第一個和第二個程式都是不太正確的,正確的方法應該是像第三個程式一樣,先定義Win_QextSerialPort類對象,然後開啟串口,再用那幾個設定函數對串口進行設定。
到這裡,整篇文章就結束了。對於其中的一些問題也只是我個人的觀點,由於水平有限,所以理解上可能會有偏差,或者錯誤,還請廣大網友批評指正。我寫這篇文章的目的只是想讓Qt初學者能更輕鬆的用Qt寫出串口通訊程式,及掌握Qt寫程式時的一些技巧。如果你從我的文章中學到了一個知識點,那麼我的這篇文章就有它的意義了。
最後,如果你喜歡我的寫作風格,並且初學Qt,可以在我的空間查看Qt Creator系列教程,希望能對你的入門有所協助。
到這裡可以下載本文的PDF文檔:http://download.csdn.net/source/1763251