【轉】Qt編寫串口通訊程序全程圖文講解

本文章原創於www.yafeilinux.com 轉載請註明出處。linux

(說明:咱們的編程環境是windows xp下,在Qt Creator中進行,若是在Linux下或直接用源碼編寫,程序稍有不一樣,請本身改動。)編程

在Qt中並無特定的串口控制類,如今大部分人使用的是第三方寫的qextserialport類,咱們這裏也是使用的該類。windows

源碼和範例均可以到咱們網站的資源下載頁面進行下載。瀏覽器

下載到的文件爲:qextserialport-1.2win-alpha.zip函數

其內容以下圖:網站

 1_42.jpg (48 KB)

 

咱們在windows下只須要使用其中的6個文件:ui

qextserialbase.cpp和qextserialbase.h,qextserialport.cpp和qextserialport.h,win_qextserialport.cpp和win_qextserialport.hthis

若是在Linux下只需將win_qextserialport.cpp和win_qextserialport.h 換爲 posix_qextserialport.cpp和posix_qextserialport.h便可。spa

第一部分:線程

下面咱們將講述編程的詳細過程,這裏咱們先給出完整的程序,而後到第二部分再進行逐句分析。

1.打開Qt Creator,新建Qt4 Gui Application,工程名設置爲mycom,其餘使用默認選項。

(注意:創建的工程路徑不能有中文。)

2.將上面所說的6個文件複製到工程文件夾下,以下圖。

 2_23.jpg (39 KB)

3.在工程中添加這6個文件。

在Qt Creator中左側的文件列表上,鼠標右擊工程文件夾,在彈出的菜單中選擇Add Existing Files,添加已存在的文件。以下圖:

 3_1.jpg (30 KB)

選擇工程文件夾裏的那6個文件,進行添加。以下圖。

 4_19.jpg (47 KB)

添加好後文件列表以下圖所示:

 5_7.jpg (18 KB)

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

 6_3.jpg (33 KB)

5.在mainwindow.h的相應位置添加頭文件#include 「win_qextserialport.h」,添加對象聲明Win_QextSerialPort *myCom;添加槽函數聲明 void readMyCom();添加完後,以下圖。

 7_3.jpg (40 KB)

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_4.jpg (78 KB)

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

 9_5.jpg (43 KB)

這樣最簡單的串口通訊程序就完成了。能夠看到它只須要加入幾行代碼便可,很是簡單。
第二部分:

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

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

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

步驟二:選擇串口,如windows下的串口1爲「com1」,Linux下爲「ttyS0」等,並打開串口。

步驟三:讀或寫串口。

步驟四:關閉串口。

(咱們上一個程序沒有寫串口和關閉串口的功能,打開串口也是在構造函數裏完成的,由於那只是爲了用最簡單的方法完成串口程序的編寫。在後面咱們將會對它進行修改和完善。)

2.下面咱們將按照上面的操做串口的流程,講解第一個程序的編寫。

第一,咱們在寫程序以前,應該瀏覽一下那6個文件,大概看一下它們裏面都是什麼內容,各個文件各個類之間有什麼聯繫。在win_qextserialport.cpp文件中,咱們看它的最後一個構造函數,會發現,串口能夠在這裏進行初始化。

 10_10.jpg (57 KB)
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_6.jpg (70 KB)

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

 12_2.jpg (43 KB)

點擊Search後,便能在下面顯示出整個工程中全部PortSettings的位置。以下圖。

 13_8.jpg (31 KB)

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

 14_9.jpg (36 KB)

咱們雙擊這一條,進入相應的文件。以下圖。

 15.jpg (14 KB)

struct PortSettings

{

BaudRateType BaudRate;

DataBitsType DataBits;

ParityType Parity;

StopBitsType StopBits;

FlowType FlowControl;

long Timeout_Millisec;

};

能夠看到在這個結構體裏定義了串口初始化的各個參數,而對於BaudRateType等類型的定義,咱們在這個結構體的上面能夠看到,它們是多個枚舉變量。以下圖。 
 16_10.jpg (46 KB)
這時咱們便應該明白了,這個結構體即是實現串口參數設置的。

第三,定義串口參數。

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_6.jpg (27 KB)

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

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

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

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

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

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

 18_7.jpg (7 KB)

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

 19_2.jpg (6 KB)

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

 20.jpg (29 KB)

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

 21.jpg (69 KB)

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

 22_9.jpg (53 KB)

因此,咱們能夠用這個類裏的這些函數操做串口。

如程序中的語句:

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

界面以下圖。

 23.jpg (37 KB)

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); //開始「發送數據」按鈕不可用

}

更改後程序以下圖所示: 
 24_6.jpg (95 KB)

這時運行程序,效果以下:

 25_7.jpg (20 KB)

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碼形式將行編輯框中的數據寫入串口

}

程序以下圖: 
 26.jpg (34 KB)

最終效果以下:

(將數據x發送給單片機,單片機返回you send message is : x) 
 27_8.jpg (25 KB)
第四部分:

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

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

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

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

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

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

中止位:stopBitsComboBox,條目爲:1,2

(注:在窗口上的Combo Box上雙擊,在彈出的對話框上按「+」號,可添加條目。咱們只是爲了演示,因此只加了這幾個條目,你能夠根據本身的須要添加。)

改好的窗口以下所示:

 28_4.jpg (44 KB)

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文件中定義,以下圖。 
 30.jpg (107 KB)

看完前面幾部分的內容,對於這幾個函數應該很好理解,這裏再也不解釋。在最後咱們對添加的那幾個組合框進行了不可用設置,使其在串口打開的狀況下不能選擇。

程序以下:

 29.jpg (133 KB)

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

}

這裏只是加入了一些使組合框在「關閉串口」按鈕按下後變爲可用的語句。

程序以下: 
 31_6.jpg (39 KB)

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.jpg (27 KB)

5.運行程序。

打開後程序界面以下。 
 33.jpg (30 KB)

正常發送1後效果以下。 
 34_7.jpg (30 KB)

設置爲「奇校驗」,發送完1的效果以下圖。(接收到的是亂碼) 
 35_2.jpg (34 KB)

到這裏,整個程序就寫完了。
重要問題說明:

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

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

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系列教程,但願能對你的入門有所幫助。

附錄:

1.在Linux下寫串口通訊程序。

首先portName應該改成/dev/ttyS0, 而後QextSerialBase::EventDriven須要改成QextSerialBase::Polling,
也就是說,Linux下不支持事件驅動,這就是麻煩所在,這樣下面就不能再用

connect(myCom,SIGNAL(readyRead()),this,SLOT(readMyCom()));這條語句了。

要想讀取串口內容,就要寫一個while(1){}一直讀取串口內容,這樣就能顯示出串口的內容了。
固然能夠把它放到一個線程中去。不過就算這樣,也不能像windows下那樣很好的讀取串口的內容。還有阻塞等問題。

若是誰很好的解決問題了,很高興能分享出來。

2.網友寫的文章:讓串口能夠自動分辨一幀數據

相關文章
相關標籤/搜索