串行接口簡稱串口,也稱串行通信接口或串行通訊接口(通常指COM接口),是采用串行通信方式的擴展接口。串行接口(Serial Interface) 是指數據一位一位地順序傳送,其特點是通信線路簡單,只要一對傳輸線就可以實現雙向通信,大大降低了成本,特別適用於遠距離通信,但傳送速度較慢。根據信息的傳送方向,串行通訊可以進一步分為單工、半雙工和全雙工三種。串行接口按電氣標准及協議來分包括RS-232-C、RS-422、RS485等。
異步串行是指UART(Universal Asynchronous Receiver/Transmitter),通用異步接收/發送。UART包含TTL電平的串口和RS232電平的串口。 TTL電平是3.3V的,而RS232是負邏輯電平,它定義+5~+12V為低電平,而-12~-5V為高電平。UART作為異步串口通信協議的一種,工作原理是將傳輸數據的每個字符一位接一位地傳輸,其中各位的意義如下:
起始位:先發出一個邏輯”0”的信號,表示傳輸字符的開始。
數據位:緊接在起始位之后,數據位的個數可以是4、5、6、7、8等,構成一個字符,從最低位開始傳送,靠時鍾定位。
奇偶校驗位:數據位加上這一位后,使得“1”的位數應為偶數(偶校驗)或奇數(奇校驗),以此來校驗數據傳送的正確性。
停止位:它是一個字符數據的結束標志,可以是1位、1.5位、2位的高電平。 由於數據是在傳輸線上定時的,並且每一個設備有其自己的時鍾,很可能在通信中兩台設備間出現了小小的不同步。因此停止位不僅僅是表示傳輸的結束,並且提供計算機校正時鍾同步的機會。適用於停止位的位數越多,不同時鍾同步的容忍程度越大,但是數據傳輸率同時也越慢。
空閑位:處於邏輯“1”狀態,表示當前線路上沒有數據傳送。
此外,在異步通信中還有一個重要的參數,即波特率,它是衡量數據傳送速率的指標,表示每秒鍾傳送的符號數(symbol)。收發雙方的波特率必須保持一致,才能保證數據的正常通信。
在通信程序中,一般來說如何獲取收到的信息是一個問題。因為接收是被動的,即不知道對方什么時候會發送信息來,所以需要一直對接收端進行偵聽,看是否有信息到來,這樣就需要CPU反復地進行查詢,會消耗過多的資源。所以,在硬件層面一般會使用中斷方式來進行接收偵聽,而不會采用查詢方式。同樣,在基於操作系統的編程中,對於接收也有查詢和事件兩種方式。查詢方式同樣需要反復地對接收進行讀取,看是否收到了信息,同樣會消耗過多的系統資源,一般會把它放在一個線程中來進行,以保證系統資源不會被過度地消耗掉。另一種是事件方式,即當接收端收到信息時,會采用事件的方式來通知主程序,以對收到的信息進行相應地處理。事件方式有點類似硬件上的中斷方式,效率較高。
在基於Qt的串口編程中,有多種實現的方式。比如可以使用Qt自己帶的串口模塊(如QSerialPort),也可以使用其他第三方的控件(如qextserialport等),還可以使用操作系統中直接編寫的串口程序。各種方式都有自己的特點,但對於Qt自帶的QSerialPort模塊,雖然兼容性較好,但目前只有Qt5.0以上的版本才支持該模塊。對於第三方控件qextserialport,雖然在Qt4.0的版本就可以使用,但目前對於Linux系統而言,僅支持查詢方式,而不支持事件方式,所以在Linux中一般只能把它放到一個新建的線程中去運行。對於在操作系統中直接編程處理串口通信的方式,需要一定的程序開發能力,但適用性更廣泛一些,執行的效率也要高一些。
在Qt4.0及以上的版本中,新增加了一個名為QSocketNotifier的模塊,它用來監聽系統文件的操作,把操作轉換為Qt事件進入系統的消息循環隊列,並調用預先設置的事件接受函數來處理事件。QSocketNotifier一共設置了三類事件:read、write、exception,具體如下表所示。
在使用QSocketNotifier來處理串口時,只需要設置為Read屬性即可。每個QSocketNotifie對象監聽一個事件,在使用open方法打開串口並設置好屬性后,就可以使用Qt的類 QSocketNotifier來監聽串口是否有數據可讀,它是事件驅動的,配合Qt的信號/槽機制,當有數據可讀時,QSocketNotifier就會發射ativated信號,只需要創建一個槽連接到該信號即可,然后在槽函數中處理串口讀到的數據。這樣一來,讀取串口的任務就不用新開辟線程來處理了,這也是Qt官方給出的建議。
在Linux系統中,一切設備都可以按文件來處理,這一機制也正好符合QSocketNotifie監聽系統文件變化的機制。因此,在Linux系統中使用QSocketNotifie來處理串口接收數據,可謂相得益彰。因此,下面討論的就是在Linux系統中,基於Qt4.8.7的版本,結合Linux的串口模塊編程,然后在Qt中使用QSocketNotifie來實現串口信息的讀取。下面給出具體步驟。
首先,在Linux系統中,所有的設備文件都位於/dev目錄下,以tty為前綴的文件,是終端設備。以tty加大寫S開頭的即為串口設備文件,如ttyS0即為串口0(即COM0)。但在嵌入式Linux中,串口是以ttySAC為前綴的,如ttySAC0即代表串口0。
其次,在Linux系統中,要對文件進行相應地操作,必須先打開它,對串口設備文件也不例外,所以需要調用open函數來打開對應的串口端口。另外在打開的同時,還需要設置打開文件的合適屬性(如可讀可寫、不將設備分配為控制終端、無延時模式等)。 比如,可執行語句“open("/dev/ttySAC0", O_RDWR | O_NOCTTY | O_NDELAY);”。
接下來,需要對打開的串口進行配置,其中的重要參數有:波特率、數據位數、停止位位數、奇偶校驗、硬件流量控制等。在Linux中,這些配置項目由一個名為termios 結構體實現,結合前面的打開串口,可以一起寫在一個串口初始化函數中,如下 。
int MainWindow::UART_Init(void)
{
struct termios opt;
fdUart = open("/dev/ttySAC1", O_RDWR | O_NOCTTY | O_NDELAY);
tcgetattr(fdUart, &opt); //獲取當前打開的串口參數到termios型結構體mycom中
opt.c_cflag &= ~(PARENB | CSTOPB | CSIZE | OPOST);//無奇偶校驗、1位停止位、原始模式
opt.c_cflag |= (CS8 | CLOCAL | CREAD); //8位數據位
cfsetispeed(&opt, B9600);
cfsetospeed(&opt, B9600); //波特率為9600
opt.c_cc[VMIN] = 0;
opt.c_cc[VTIME] = 0;
tcflush(fdUart,TCIOFLUSH); //清除輸出輸入緩沖
if (tcsetattr(fdUart, TCSANOW, &opt) < 0) //使用mycom中的參數設置當前已打開的串口,不等數據傳輸完畢就立即改變
{
return -1;
}
return fdUart;
}
接下來需要在Qt的主程序中,加入串口初始化,創建一個QSocketNotifier類的對象並將其實例化,建立信號與槽的連接函數,如下。
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
UART_Init();
QSocketNotifier *com0_notifier;
com0_notifier = new QSocketNotifier(fdUart, QSocketNotifier::Read, this);
connect(com0_notifier, SIGNAL(activated(int)), this, SLOT(SerialIncoming(void)));
}
最后,還要寫一個槽函數來讀取串口數據,如下。
void MainWindow::SerialIncoming(void)
{
char buff[8];
read(fdUart,buff,8);
}
在上述程序中,文件描述符fdUart應定義為全局變量。程序運行后,只要串口接收到數據,就會觸發SerialIncoming函數,然后把收到的數據存入buff數組中,以供后續處理之用。當然,若要向串口發送數據,直接寫fdUart即可,比如執行“write(fdUart,buff,8);”即可把buff中的8個字節數據及時發送出去。
為了讓上述程序能夠正常運行,還需要包含下面的三個頭文件。
#include <QSocketNotifier>
#include <fcntl.h>
#include <termios.h>
本例給出的只是一個最小框架,在具體應用時,還要根據實際的項目進行適當的修改。