Qt編寫串口通信程序全程圖文解說


(說明:我們的編程環境是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

其內容例如以下圖:

1

我們在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,project名設置為mycom,其它使用默認選項。

(注意:建立的project路徑不能有中文。)

2.將上面所說的6個文件拷貝到project目錄下,例如以下圖。

2

3.在project中加入這6個文件。

在Qt Creator中左側的文件列表上,鼠標右擊project目錄,在彈出的菜單中選擇Add Existing Files,加入已存在的文件。例如以下圖:

3

選擇project目錄里的那6個文件,進行加入。例如以下圖。

4

加入好后文件列表例如以下圖所看到的:

5

4.點擊mainwindow.ui,在窗體上增加一個Text Browser,用來顯示信息。例如以下圖。

6

5.在mainwindow.h的對應位置加入頭文件#include "win_qextserialport.h",加入對象聲明Win_QextSerialPort *myCom;加入槽函數聲明 void readMyCom();加入完后,例如以下圖。

7

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);

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

}

加入完代碼后例如以下圖。

8

此時假設執行程序,已經能實現讀取串口數據的功能了。我們將單片機採集的溫度信息由串口傳給計算機,效果例如以下圖。

9

這樣最簡單的串口通信程序就完畢了。能夠看到它僅僅須要增加幾行代碼就可以,很easy。


第二部分:

上一部分中已經介紹了實現最簡單的串口接收程序的編寫,以下將對程序內容進行分析。

1.首先應說明操作串口的流程。

步驟一:設置串口參數,如:波特率,數據位,奇偶校驗,停止位,數據流控制等。

步驟二:選擇串口,如windows下的串口1為“com1”,Linux下為“ttyS0”等,並打開串口。

步驟三:讀或寫串口。

步驟四:關閉串口。

(我們上一個程序沒有寫串口和關閉串口的功能,打開串口也是在構造函數里完畢的,由於那僅僅是為了用最簡單的方法完畢串口程序的編寫。在后面我們將會對它進行改動和完好。)

2.以下我們將依照上面的操作串口的流程,解說第一個程序的編寫。

第一,我們在敲代碼之前,應該瀏覽一下那6個文件,大概看一下它們里面都是什么內容,各個文件各個類之間有什么聯系。在win_qextserialport.cpp文件里,我們看它的最后一個構造函數,會發現,串口能夠在這里進行初始化。

10

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,例如以下圖。

11

在彈出的對話框中輸入要查找的內容PortSettings,例如以下圖。

12

點擊Search后,便能在以下顯示出整個project中全部PortSettings的位置。例如以下圖。

13

我們點擊第一條,能夠看到在qextserialbase.h文件里有一個struct PortSettings,例如以下圖。

14

我們雙擊這一條,進入對應的文件。例如以下圖。

15

struct PortSettings

{

BaudRateType BaudRate;

DataBitsType DataBits;

ParityType Parity;

StopBitsType StopBits;

FlowType FlowControl;

long Timeout_Millisec;

};

能夠看到在這個結構體里定義了串口初始化的各個參數,而對於BaudRateType等類型的定義,我們在這個結構體的上面能夠看到,它們是多個枚舉變量。例如以下圖。
16
這時我們便應該明確了,這個結構體便是實現串口參數設置的。

第三,定義串口參數。

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中,例如以下圖。

17

能夠看到查詢模式也是枚舉變量,有兩個選項,我們選擇第二個EventDriven,事件驅動。

到這里,我們就能夠定義Win_QextSerialPort類的變量了,就是那句:

myCom = new Win_QextSerialPort("com1",myComSetting,QextSerialBase::EventDriven);

它完畢了串口的選擇和串口的初始化。

第五,寫打開串口函數和讀串口函數。

查看win_qextserialport.h文件,我們會發現Win_QextSerialPort類繼承自QextSerialBase類。

18

查看qextserialbase.h文件,我們會發現QextSerialBase類繼承自QIODevice 類。

19

我們在Qt的幫助中查看QIODevice 類,例如以下圖。

20

其部分內容例如以下圖。能夠看到當中有enum OpenModeFlag { NotOpen, ReadOnly, WriteOnly, ReadWrite, ..., Unbuffered },virtual bool open ( OpenMode mode ),QByteArray readAll ()等內容。

21

而以下的信號函數中有void readyRead ();它能夠查看串口是否有新的數據傳來。

22

所以,我們能夠用這個類里的這些函數操作串口。

如程序中的語句:

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.增加了“打開串口”,“關閉串口”“傳送數據”三個button,增加了一個行編輯框Line Edit。它們的命名例如以下:

“打開串口”button命名為:openMyComBtn

“關閉串口”button命名為:closeMyComBtn

“傳送數據”button命名為:sendMsgBtn

要傳送數據的行編輯框命名為:sendMsgLineEdit

界面例如以下圖。

23

2.在“打開串口”button上右擊,選擇Go to slot選項,然后選擇clicked()選項,進入它的單擊事件槽函數中,將上個程序中在構造函數里寫的語句所有剪切到這里。然后增加幾句button的狀態設置語句。例如以下:

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); //打開串口后“打開串口”button不可用

ui->closeMyComBtn->setEnabled(true); //打開串口后“關閉串口”button可用

ui->sendMsgBtn->setEnabled(true); //打開串口后“發送數據”button可用

}

在構造函數里也加入幾句button初始狀態設置語句,例如以下:

MainWindow::MainWindow(QWidget *parent)

: QMainWindow(parent), ui(new Ui::MainWindow)

{

ui->setupUi(this);

ui->closeMyComBtn->setEnabled(false); //開始“關閉串口”button不可用

ui->sendMsgBtn->setEnabled(false); //開始“發送數據”button不可用

}

更改后程序例如以下圖所看到的:
24

這時執行程序,效果例如以下:

25

3.按上面的方法進入“關閉串口”button和“發送數據”button的單擊事件的槽函數,更改例如以下。

void MainWindow::on_closeMyComBtn_clicked()      //關閉串口槽函數

{

myCom->close(); //關閉串口,該函數在win_qextserialport.cpp文件里定義

ui->openMyComBtn->setEnabled(true); //關閉串口后“打開串口”button可用

ui->closeMyComBtn->setEnabled(false); //關閉串口后“關閉串口”button不可用

ui->sendMsgBtn->setEnabled(false); //關閉串口后“發送數據”button不可用

}

/***********************************/

void MainWindow::on_sendMsgBtn_clicked()       //發送數據槽函數

{

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

//以ASCII碼形式將行編輯框中的數據寫入串口

}

程序例如以下圖:
26

終於效果例如以下:

(將數據x發送給單片機,單片機返回you send message is : x)
27

 

第四部分:

本文一開始先解說對程序的改進,在文章最后將要解說一些重要問題

1.在窗體中增加一些組合框Combo Box,它們的名稱及條目例如以下:

串口:portNameComboBox,條目為:COM1,COM2

波特率:baudRateComboBox,條目為:9600,115200

數據位:dataBitsComboBox,條目為:8,7

校驗位:parityComboBox,條目為:無,奇,偶

停止位:stopBitsComboBox,條目為:1,2

(注:在窗體上的Combo Box上雙擊,在彈出的對話框上按“+”號,可加入條目。我們僅僅是為了演示,所以僅僅加了這幾個條目,你能夠依據自己的須要加入。)

改好的窗體例如以下所看到的:

28

2.更改“打開串口”button的單擊事件槽函數。

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); //打開串口后“打開串口”button不可用

ui->closeMyComBtn->setEnabled(true); //打開串口后“關閉串口”button可用

ui->sendMsgBtn->setEnabled(true); //打開串口后“發送數據”button可用

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文件里定義,例如以下圖。
30

看完前面幾部分的內容,對於這幾個函數應該非常好理解,這里不再解釋。在最后我們對加入的那幾個組合框進行了不可用設置,使其在串口打開的情況下不能選擇。

程序例如以下:

29

3.更改“關閉串口”button單擊事件的槽函數。

void MainWindow::on_closeMyComBtn_clicked()

{

myCom->close();

ui->openMyComBtn->setEnabled(true); //關閉串口后“打開串口”button可用

ui->closeMyComBtn->setEnabled(false); //關閉串口后“關閉串口”button不可用

ui->sendMsgBtn->setEnabled(false); //關閉串口后“發送數據”button不可用

ui->baudRateComboBox->setEnabled(true); //設置各個組合框可用

ui->dataBitsComboBox->setEnabled(true);

ui->parityComboBox->setEnabled(true);

ui->stopBitsComboBox->setEnabled(true);

ui->portNameComboBox->setEnabled(true);

}

這里僅僅是增加了一些使組合框在“關閉串口”button按下后變為可用的語句。

程序例如以下:
31

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();

}

由於上面的程序中用到了中文,為了能使程序識別中文,我們須要在主函數中增加這些語句。

程序例如以下:
32

5.執行程序。

打開后程序界面例如以下。
33

正常發送1后效果例如以下。
34

設置為“奇校驗”,發送完1的效果例如以下圖。(接收到的是亂碼)
35

到這里,整個程序就寫完了。

重要問題說明:

(以下所說的第一個程序是指第一部分中寫的那個程序,第二個程序是指第三部分更改完后的程序,第三個程序是指第四部分更改完后的程序。)

問題一:更改第一個程序中的代碼。

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。能夠看到兩個程序上接受到的信息都正確。例如以下圖。

36

我們關閉第二個程序上的串口,再將第三個程序上設置為奇校驗,然后打開串口,發送數據1,能夠看到其收到的數據顯示亂碼。這時我們關閉第三個程序上的串口,打開第二個程序上的串口,發送數據1,你會驚奇地發現,它收到的信息也是亂碼。例如以下圖。

37

這究竟是怎么回事呢?我們也能夠去網上下載其它的串口助手進行實驗,也能夠改變波特率進行實驗。由全部的結果得出的結論僅僅能是:我們用那個結構體作為參數傳過去后,並沒有對串口進行設置,而程序執行使用的串口設置是系統曾經保留的設置。那么,為什么會這樣呢?我們看以下的一個問題。

問題三:更改第三個程序中的代碼。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM