Qt Serial Port【概述】
Qt Serial Port提供了基本的功能,包括配置,I/O操作,擷取和設定RS-232引腳的訊號。
本模組暫不支援如下特性:
*終端的特性,例如回顯,控制CR/LF等等
*文字模式
*配置讀操作的逾時和延時
*當RS-232引腳訊號改變的時候跟蹤和通知
要在自己的應用程式中使用這些類,那麼就必須包括如下的聲明:
#include <QtSerialPort/QtSerialPort>
要連結本模組,那麼需要在.pro檔案中添加如下內容:
QT += serialport
【類】
本模組包括兩個類:
QSerialPort:提供訪問串口的功能
QSerialPortInfo:提供系統中存在的串口的資訊
【QSerialPort】
該類提供訪問串口的功能。你可以使用QSerialPortInfo協助類擷取系統上可用的串口的資訊,可以枚舉系統上存在的所有串口。通過該類你可以擷取串口的正確名稱。你可以傳遞一個該類的對象作為setPort()或者setPortName()方法的參數指定想要訪問的串口裝置。
在設定完了串口,你就可以以唯讀或者唯寫或者讀寫入模式調用open()方法開啟串口。
注意:串口都是以互斥的方式訪問,這也就是說我們不能開啟一個已經開啟的串口。
成功開啟之後,QSerialPort嘗試著擷取串口當前的配置並初始化它。你也可以使用setBaudRate(),setDataBits(),setParity(),setStopBits()
和setFlowControl()方法重新設定它。控制管腳的狀態是根據isDataTerminalReady(),isRequestToSend()和pinoutSignals()決定的。要改變控制資訊,可以使用如下方法:setDataTerminalReady()和setRequestToSend()。
一旦你知道了串口可用於讀或者寫,你就可以調用read()或者write()方法。可選的還有readline()和readAll()方法。如果資料不能在一次讀完,那麼剩下的資料接下來就會存在QSerialPort的內部緩衝區中。你可以使用setReadBufferSize()方法設定緩衝區的大小。可以使用close()方法來關閉串口和取消I/O操作。
看看下面的樣本:
int numRead = 0, numReadTotal = 0;char buffer[50];forever { numRead = serial.read(buffer, 50); // Do whatever with the array numReadTotal += numRead; if (numRead == 0 && !serial.waitForReadyRead()) break;}
在串口串連被關閉或者出現錯誤的時候,waitForReadyRead()就會返回false。
阻塞的串口編程與非阻塞的串口編程是不一樣的。阻塞的串口不許要事件迴圈,需要的代碼很少。然而,在一個GUI應用程式中,阻塞的串口操作應該僅僅在非GUI線程中使用,避免使用者介面凍結。對於這一點,詳細資料可以查看樣本程式。
QSerialPort類可以跟QTextStream和QDataStream的流操作一起使用(operator<<()和operator>>())。這裡需要注意一件事:使用操作符operator>>()的重載函數讀取之前請確保有足夠的資料可用。
【QSerialPortInfo】
提供系統中已存在的串口資訊。
使用該類的靜態函數擷取一系列的QSerialPortInfo對象。列表中的每一個QSerialPort對象都代表了一個單獨的串口,可以用於擷取串口名、
系統路徑、描述以及製造商等資訊。QSerialPortInfo類也可以作為QSerialPort類的setPort()方法的一個輸入參數。
【樣本】【樣本一】顯示系統上可用的串口資訊
樣本一展示了如何使用QSerialInfo類擷取系統上可用的串口,顯示所有串口的資訊:
【原始碼】
#include <QTextStream>#include <QCoreApplication>#include <QtSerialPort/QSerialPortInfo>QT_USE_NAMESPACEint main(int argc, char *argv[]){ QCoreApplication a(argc, argv); QTextStream out(stdout); QList<QSerialPortInfo> serialPortInfoList = QSerialPortInfo::availablePorts(); out << QObject::tr("Total number of ports available: ") << serialPortInfoList.count() << endl; foreach (const QSerialPortInfo &serialPortInfo, serialPortInfoList) { out << endl << QObject::tr("Port: ") << serialPortInfo.portName() << endl << QObject::tr("Location: ") << serialPortInfo.systemLocation() << endl << QObject::tr("Description: ") << serialPortInfo.description() << endl << QObject::tr("Manufacturer: ") << serialPortInfo.manufacturer() << endl << QObject::tr("Vendor Identifier: ") << (serialPortInfo.hasVendorIdentifier() ? QByteArray::number(serialPortInfo.vendorIdentifier(), 16) : QByteArray()) << endl << QObject::tr("Product Identifier: ") << (serialPortInfo.hasProductIdentifier() ? QByteArray::number(serialPortInfo.productIdentifier(), 16) : QByteArray()) << endl << QObject::tr("Busy: ") << (serialPortInfo.isBusy() ? QObject::tr("Yes") : QObject::tr("No")) << endl; } return 0;}【運行效果如下】
【樣本二】:顯示系統可用串口資訊---圖形介面版本【原始碼】:
#include <QApplication>#include <QWidget>#include <QVBoxLayout>#include <QLabel>#include <QtSerialPort/QSerialPortInfo>QT_USE_NAMESPACEint main(int argc, char *argv[]){ QApplication a(argc, argv); QWidget w; w.setWindowTitle(QObject::tr("Info about all available serial ports.")); QVBoxLayout *layout = new QVBoxLayout; foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) { QString s = QObject::tr("Port: ") + info.portName() + "\n" + QObject::tr("Location: ") + info.systemLocation() + "\n" + QObject::tr("Description: ") + info.description() + "\n" + QObject::tr("Manufacturer: ") + info.manufacturer() + "\n" + QObject::tr("Vendor Identifier: ") + (info.hasVendorIdentifier() ? QString::number(info.vendorIdentifier(), 16) : QString()) + "\n" + QObject::tr("Product Identifier: ") + (info.hasProductIdentifier() ?QString::number(info.productIdentifier(), 16) : QString()) + "\n" + QObject::tr("Busy: ") + (info.isBusy() ? QObject::tr("Yes") : QObject::tr("No")) + "\n"; QLabel *label = new QLabel(s); layout->addWidget(label); } w.setLayout(layout); w.show(); return a.exec();}【啟動並執行效果如下】:
【樣本三】:阻塞式編程之主裝置
本樣本展示了如何在非GUI線程中使用QSerialPort的同步API。
QSerialPort支援兩種編程方法:
*非同步(非阻塞)方式。當控制權返回到Qt的事件迴圈中,相應的操作就會被調度和執行。當操作執行完了QSerial就會觸發一個訊號。例如:
QSerialPort::write()方法返回。當資料發送到串口,QSerialPort就會觸發bytesWirtten()。
*同步(阻塞)方式。在非GUI和多線程應用程式中,我們可以調用waitFor...()函數(例如QSerialPort::waitForReadyRead())使得調用線程在完成之前都是阻塞的。
在本樣本中,我們展示如何使用同步的方式,在Terminal樣本中展示如何使用非同步方式。
本樣本的目的是向你展示一種模式:簡化代碼而不至於使得UI失去響應。Qt中阻塞的串口編程API可以簡化代碼,但是由於它的阻塞特性,所以我們必須只在非GUI線程中使用它。但是跟大多數人相反,使用QThread並不會使得你的應用程式更複雜。
本樣本是主程式,還有一個與之對應的從程式樣本。主程式通過串口將請求發送給從程式並等待響應。
下面是我們定義的主線程類,它整合自QThread:
【原始碼】
class MasterThread : public QThread{ Q_OBJECTpublic: MasterThread(QObject *parent = 0); ~MasterThread(); void transaction(const QString &portName, int waitTimeout, const QString &request); void run();signals: void response(const QString &s); void error(const QString &s); void timeout(const QString &s);private: QString portName; QString request; int waitTimeout; QMutex mutex; QWaitCondition cond; bool quit;};
線上程的run函數中主要就是向串口中寫入資料,代碼如下:
// write request QByteArray requestData = currentRequest.toLocal8Bit(); serial.write(requestData); if (serial.waitForBytesWritten(waitTimeout)) { // read response if (serial.waitForReadyRead(currentWaitTimeout)) { QByteArray responseData = serial.readAll(); while (serial.waitForReadyRead(10)) responseData += serial.readAll(); QString response(responseData); emit this->response(response); } else { emit timeout(tr("Wait read response timeout %1") .arg(QTime::currentTime().toString())); } } else { emit timeout(tr("Wait write request timeout %1") .arg(QTime::currentTime().toString())); }
【樣本四】:阻塞式編程之從裝置
從程式類似上面的,就是起線程,線程從串口中讀取資料:
【原始碼】
if (serial.waitForReadyRead(currentWaitTimeout)) {//! [7] //! [8] // read request QByteArray requestData = serial.readAll(); while (serial.waitForReadyRead(10)) requestData += serial.readAll();//! [8] //! [10] // write response QByteArray responseData = currentRespone.toLocal8Bit(); serial.write(responseData); if (serial.waitForBytesWritten(waitTimeout)) { QString request(requestData);//! [12] emit this->request(request);//! [10] //! [11] //! [12] } else { emit timeout(tr("Wait write response timeout %1") .arg(QTime::currentTime().toString())); }//! [9] //! [11] } else { emit timeout(tr("Wait read request timeout %1") .arg(QTime::currentTime().toString())); }【運行結果如下】:
【樣本五】:非阻塞編程
Terminal樣本展示了如何使用Qt Serial Port建立簡單的終端。
本樣本展示了QSerialPort類的主要特性,例如:配置,I/O實現等等。本樣本也同樣調用了QSerialPortInfo類擷取系統中可用的串口資訊。
QSerialPort支援兩種基本的編程方法:
*非同步(非阻塞)方式。當控制權返回到Qt的事件迴圈中時,操作就得以調度和執行。當操作執行完成了就會QSerialPort就會觸發一個訊號。例如:QSerialPort::write()函數會立即返回,而不會阻塞在寫操作上。當有資料發送到串口,QSerialPort就會觸發bytesWritten()訊號。
*同步(阻塞)方式。在非GUI和多線程應用程式中,可以調用waitFor...()函數(例如:QSerialPort::waitForReadyRead())掛起調用線程,直到操作完成。
在本樣本中,展示非同步編程方式。
樣本中包含了一些GUI視窗組件:
主視窗:應用程式的主視窗,包括了串口編程的所有邏輯工作,包括配置,I/O處理等等,繼承自QMainWindow。
Console:主視窗的中心組件,顯示發送和接受的資料。該類繼承自QPlainTextEdit類。
設定對話窗:用於配置串口的對話方塊,也顯示了系統上可用的串口的資訊。
【原始碼】
本程式主要展示非同步作業方式,也就是調用write和read函數,並且設定好相應的槽函數。
點擊了connect菜單,就會觸發如下訊號-槽串連
connect(serial, SIGNAL(readyRead()), this, SLOT(readData()));
在終端上輸入字元就會觸發如下訊號-槽串連
connect(console, SIGNAL(getData(QByteArray)), this, SLOT(writeData(QByteArray)));
//! [6]void MainWindow::writeData(const QByteArray &data){ serial->write(data);}//! [6]//! [7]void MainWindow::readData(){ QByteArray data = serial->readAll(); console->putData(data);}//! [7]【運行效果如下】