?
6.4??嵌入式Linux串口應(yīng)用編程
6.4.1??串口概述
常見的數(shù)據(jù)通信的基本方式可分為并行通信與串行通信兩種。
n 并行通信是指利用多條數(shù)據(jù)傳輸線將一個字數(shù)據(jù)的各比特位同時傳送。它的特點是傳輸速度快,適用于傳輸距離短且傳輸速度較高的通信。
n 串行通信是指利用一條傳輸線將數(shù)據(jù)以比特位為單位順序傳送。特點是通信線路簡單,利用簡單的線纜就可實現(xiàn)通信,降低成本,適用于傳輸距離長且傳輸速度較慢的通信。
串口是計算機一種常用的接口,常用的串口有RS-232-C接口。它是于1970年由美國電子工業(yè)協(xié)會(EIA)聯(lián)合貝爾系統(tǒng)、調(diào)制解調(diào)器廠家及計算機終端生產(chǎn)廠家共同制定的用于串行通信的標準,它的全稱是“數(shù)據(jù)終端設(shè)備(DTE)和數(shù)據(jù)通信設(shè)備(DCE)之間串行二進制數(shù)據(jù)交換接口技術(shù)標準”。該標準規(guī)定采用一個DB25芯引腳的連接器或9芯引腳的連接器,其中25芯引腳的連接器如圖6.3所示。
圖6.3??25引腳串行接口圖
S3C2410X內(nèi)部具有兩個獨立的UART控制器,每個控制器都可以工作在Interrupt(中斷)模式或者DMA(直接存儲訪問)模式。同時,每個UART均具有16字節(jié)的FIFO(先入先出寄存器),支持的最高波特率可達到230.4Kbps。UART的操作主要可分為以下幾個部分:數(shù)據(jù)發(fā)送、數(shù)據(jù)接收、產(chǎn)生中斷、設(shè)置波特率、Loopback模式、紅外模式以及硬軟流控模式。
串口參數(shù)的配置讀者在配置超級終端和minicom時也已經(jīng)接觸過,一般包括波特率、起始位比特數(shù)、數(shù)據(jù)位比特數(shù)、停止位比特數(shù)和流控模式。在此,可以將其配置為波特率115200、起始位1b、數(shù)據(jù)位8b、停止位1b和無流控模式。
在Linux中,所有的設(shè)備文件一般都位于“/dev”下,其中串口1和串口2對應(yīng)的設(shè)備名依次為“/dev/ttyS0”和“/dev/ttyS1”,而且USB轉(zhuǎn)串口的設(shè)備名通常為“/dev/ttyUSB0”和“/dev/ttyUSB1”(因版本不同該設(shè)備名會有所不同),可以查看在“/dev”下的文件以確認。在本章中已經(jīng)提到過,在Linux下對設(shè)備的操作方法與對文件的操作方法是一樣的,因此,對串口的讀寫就可以使用簡單的read()、write()函數(shù)來完成,所不同的只是需要對串口的其他參數(shù)另做配置,下面就來詳細講解串口應(yīng)用開發(fā)的步驟。
6.4.2??串口設(shè)置詳解
串口的設(shè)置主要是設(shè)置struct?termios結(jié)構(gòu)體的各成員值,如下所示:
#include<termios.h>
struct?termios
{??????
?????unsigned?short??c_iflag;?????????/*?輸入模式標志?*/
?????unsigned?short??c_oflag;?????????/*?輸出模式標志?*/
?????unsigned?short??c_cflag;?????????/*?控制模式標志*/
?????unsigned?short??c_lflag;?????????/*?本地模式標志?*/
?????unsigned?char??c_line;???????????/*?線路規(guī)程?*/
?????unsigned?char??c_cc[NCC];???????/*?控制特性?*/
?????speed_t????c_ispeed;????????????/*?輸入速度?*/
?????speed_t????c_ospeed;????????????/*?輸出速度?*/
};
termios是在POSIX規(guī)范中定義的標準接口,表示終端設(shè)備(包括虛擬終端、串口等)??谑且环N終端設(shè)備,一般通過終端編程接口對其進行配置和控制。在具體講解串口相關(guān)編程之前,先了解一下終端相關(guān)知識。
終端有3種工作模式,分別為規(guī)范模式(canonical?mode)、非規(guī)范模式(non-canonical?mode)和原始模式(raw?mode)。
通過在termios結(jié)構(gòu)的c_lflag中設(shè)置ICANNON標志來定義終端是以規(guī)范模式(設(shè)置ICANNON標志)還是以非規(guī)范模式(清除ICANNON標志)工作,默認情況為規(guī)范模式。
在規(guī)范模式下,所有的輸入是基于行進行處理。在用戶輸入一個行結(jié)束符(回車符、EOF等)之前,系統(tǒng)調(diào)用read()函數(shù)讀不到用戶輸入的任何字符。除了EOF之外的行結(jié)束符(回車符等)與普通字符一樣會被read()函數(shù)讀取到緩沖區(qū)之中。在規(guī)范模式中,行編輯是可行的,而且一次調(diào)用read()函數(shù)最多只能讀取一行數(shù)據(jù)。如果在read()函數(shù)中被請求讀取的數(shù)據(jù)字節(jié)數(shù)小于當(dāng)前行可讀取的字節(jié)數(shù),則read()函數(shù)只會讀取被請求的字節(jié)數(shù),剩下的字節(jié)下次再被讀取。
在非規(guī)范模式下,所有的輸入是即時有效的,不需要用戶另外輸入行結(jié)束符,而且不可進行行編輯。在非規(guī)范模式下,對參數(shù)MIN(c_cc[VMIN])和TIME(c_cc[VTIME])的設(shè)置決定read()函數(shù)的調(diào)用方式。設(shè)置可以有4種不同的情況。
n MIN?=?0和TIME?=?0:read()函數(shù)立即返回。若有可讀數(shù)據(jù),則讀取數(shù)據(jù)并返回被讀取的字節(jié)數(shù),否則讀取失敗并返回0。
n MIN?>?0和TIME?=?0:read()函數(shù)會被阻塞直到MIN個字節(jié)數(shù)據(jù)可被讀取。
n MIN?=?0和TIME?>?0:只要有數(shù)據(jù)可讀或者經(jīng)過TIME個十分之一秒的時間,read()函數(shù)則立即返回,返回值為被讀取的字節(jié)數(shù)。如果超時并且未讀到數(shù)據(jù),則read()函數(shù)返回0。
n MIN?>?0和TIME?>?0:當(dāng)有MIN個字節(jié)可讀或者兩個輸入字符之間的時間間隔超過TIME個十分之一秒時,read()函數(shù)才返回。因為在輸入第一個字符之后系統(tǒng)才會啟動定時器,所以在這種情況下,read()函數(shù)至少讀取一個字節(jié)之后才返回。
按照嚴格意義來講,原始模式是一種特殊的非規(guī)范模式。在原始模式下,所有的輸入數(shù)據(jù)以字節(jié)為單位被處理。在這個模式下,終端是不可回顯的,而且所有特定的終端輸入/輸出控制處理不可用。通過調(diào)用cfmakeraw()函數(shù)可以將終端設(shè)置為原始模式,而且該函數(shù)通過以下代碼可以得到實現(xiàn)。
termios_p->c_iflag?&=?~(IGNBRK?|?BRKINT?|?PARMRK?|?ISTRIP
???????????????????????????|?INLCR?|?IGNCR?|?ICRNL?|?IXON);
????termios_p->c_oflag?&=?~OPOST;
????termios_p->c_lflag?&=?~(ECHO?|?ECHONL?|?ICANON?|?ISIG?|?IEXTEN);
????termios_p->c_cflag?&=?~(CSIZE?|?PARENB);
????termios_p->c_cflag?|=?CS8;
下面講解設(shè)置串口的基本方法。設(shè)置串口中最基本的包括波特率設(shè)置,校驗位和停止位設(shè)置。在這個結(jié)構(gòu)中最為重要的是c_cflag,通過對它的賦值,用戶可以設(shè)置波特率、字符大小、數(shù)據(jù)位、停止位、奇偶校驗位和硬軟流控等。另外c_iflag和c_cc也是比較常用的標志。在此主要對這3個成員進行詳細說明。c_cflag支持的常量名稱如表6.11所示。其中設(shè)置波特率宏名為相應(yīng)的波特率數(shù)值前加上‘B’,由于數(shù)值較多,本表沒有全部列出。
?
表6.11 c_cflag支持的常量名稱
CBAUD |
波特率的位掩碼 |
B0 |
0波特率(放棄DTR) |
… |
… |
B1800 |
1800波特率 |
B2400 |
2400波特率 |
續(xù)表
B4800 |
4800波特率 |
B9600 |
9600波特率 |
B19200 |
19200波特率 |
B38400 |
38400波特率 |
B57600 |
57600波特率 |
B115200 |
115200波特率 |
EXTA |
外部時鐘率 |
EXTB |
外部時鐘率 |
CSIZE |
數(shù)據(jù)位的位掩碼 |
CS5 |
5個數(shù)據(jù)位 |
CS6 |
6個數(shù)據(jù)位 |
CS7 |
7個數(shù)據(jù)位 |
CS8 |
8個數(shù)據(jù)位 |
CSTOPB |
2個停止位(不設(shè)則是1個停止位) |
CREAD |
接收使能 |
PARENB PARODD |
校驗位使能 使用奇校驗而不使用偶校驗 |
HUPCL |
最后關(guān)閉時掛線(放棄DTR) |
CLOCAL |
本地連接(不改變端口所有者) |
CRTSCTS |
硬件流控 |
在這里,不能直接對c_cflag成員初始化,而要將其通過“與”、“或”操作使用其中的某些選項。輸入模式標志c_iflag用于控制端口接收端的字符輸入處理。c_iflag支持的常量名稱如表6.12所示。
表6.12 c_iflag支持的常量名稱
INPCK |
奇偶校驗使能 |
IGNPAR |
忽略奇偶校驗錯誤 |
PARMRK |
奇偶校驗錯誤掩碼 |
ISTRIP |
裁減掉第8位比特 |
IXON |
啟動輸出軟件流控 |
IXOFF |
啟動輸入軟件流控 |
IXANY |
輸入任意字符可以重新啟動輸出(默認為輸入起始字符才重啟輸出) |
IGNBRK |
忽略輸入終止條件 |
BRKINT |
當(dāng)檢測到輸入終止條件時發(fā)送SIGINT信號 |
INLCR |
將接收到的NL(換行符)轉(zhuǎn)換為CR(回車符) |
IGNCR |
忽略接收到的CR(回車符) |
ICRNL |
將接收到的CR(回車符)轉(zhuǎn)換為NL(換行符) |
IUCLC |
將接收到的大寫字符映射為小寫字符 |
IMAXBEL |
當(dāng)輸入隊列滿時響鈴 |
c_oflag用于控制終端端口發(fā)送出去的字符處理,c_oflag支持的常量名稱如表6.12所示。因為現(xiàn)在終端的速度比以前快得多,所以大部分延時掩碼幾乎沒什么用途。
表6.13 c_oflag支持的常量名稱
OPOST |
啟用輸出處理功能,如果不設(shè)置該標志,則其他標志都被忽略 |
OLCUC |
將輸出中的大寫字符轉(zhuǎn)換成小寫字符 |
ONLCR |
將輸出中的換行符(‘n’)轉(zhuǎn)換成回車符(‘r’) |
ONOCR |
如果當(dāng)前列號為0,則不輸出回車符 |
OCRNL |
將輸出中的回車符(‘r’)轉(zhuǎn)換成換行符(‘n’) |
ONLRET |
不輸出回車符 |
OFILL |
發(fā)送填充字符以提供延時 |
OFDEL |
如果設(shè)置該標志,則表示填充字符為DEL字符,否則為NUL字符 |
NLDLY |
換行延時掩碼 |
CRDLY |
回車延時掩碼 |
TABDLY |
制表符延時掩碼 |
BSDLY |
水平退格符延時掩碼 |
VTDLY |
垂直退格符延時掩碼 |
FFLDY |
換頁符延時掩碼 |
?
c_lflag用于控制控制終端的本地數(shù)據(jù)處理和工作模式,c_lflag所支持的常量名稱如表6.14所示。
表6.14 c_lflag支持的常量名稱
ISIG |
若收到信號字符(INTR、QUIT等),則會產(chǎn)生相應(yīng)的信號 |
ICANON |
啟用規(guī)范模式 |
ECHO |
啟用本地回顯功能 |
ECHOE |
若設(shè)置ICANON,則允許退格操作 |
ECHOK |
若設(shè)置ICANON,則KILL字符會刪除當(dāng)前行 |
ECHONL |
若設(shè)置ICANON,則允許回顯換行符 |
ECHOCTL |
若設(shè)置ECHO,則控制字符(制表符、換行符等)會顯示成“^X”,其中X的ASCII碼等于給相應(yīng)控制字符的ASCII碼加上0x40。例如:退格字符(0x08)會顯示為“^H”(’H’的ASCII碼為0x48) |
ECHOPRT |
若設(shè)置ICANON和IECHO,則刪除字符(退格符等)和被刪除的字符都會被顯示 |
ECHOKE |
若設(shè)置ICANON,則允許回顯在ECHOE和ECHOPRT中設(shè)定的KILL字符 |
NOFLSH |
在通常情況下,當(dāng)接收到INTR、QUIT和SUSP控制字符時,會清空輸入和輸出隊列。如果設(shè)置該標志,則所有的隊列不會被清空 |
TOSTOP |
若一個后臺進程試圖向它的控制終端進行寫操作,則系統(tǒng)向該后臺進程的進程組發(fā)送SIGTTOU信號。該信號通常終止進程的執(zhí)行 |
IEXTEN |
啟用輸入處理功能 |
c_cc定義特殊控制特性。c_cc所支持的常量名稱如表6.13所示。
表6.13 c_cc支持的常量名稱
VINTR |
中斷控制字符,對應(yīng)鍵為CTRL+C |
VQUIT |
退出操作符,對應(yīng)鍵為CRTL+Z |
VERASE |
刪除操作符,對應(yīng)鍵為Backspace(BS) |
VKILL |
刪除行符,對應(yīng)鍵為CTRL+U |
VEOF |
文件結(jié)尾符,對應(yīng)鍵為CTRL+D |
VEOL |
附加行結(jié)尾符,對應(yīng)鍵為Carriage?return(CR) |
VEOL2 |
第二行結(jié)尾符,對應(yīng)鍵為Line?feed(LF) |
VMIN |
指定最少讀取的字符數(shù) |
VTIME |
指定讀取的每個字符之間的超時時間 |
下面就詳細講解設(shè)置串口屬性的基本流程。
1.保存原先串口配置
首先,為了安全起見和以后調(diào)試程序方便,可以先保存原先串口的配置,在這里可以使用函數(shù)tcgetattr(fd,?&old_cfg)。該函數(shù)得到fd指向的終端的配置參數(shù),并將它們保存于termios結(jié)構(gòu)變量old_cfg中。該函數(shù)還可以測試配置是否正確、該串口是否可用等。若調(diào)用成功,函數(shù)返回值為0,若調(diào)用失敗,函數(shù)返回值為-1,其使用如下所示:
if??(tcgetattr(fd,?&old_cfg)??!=??0)?
{
?????perror("tcgetattr");
?????return?-1;
}
2.激活選項
CLOCAL和CREAD分別用于本地連接和接受使能,因此,首先要通過位掩碼的方式激活這兩個選項。
newtio.c_cflag??|=??CLOCAL?|?CREAD;
調(diào)用cfmakeraw()函數(shù)可以將終端設(shè)置為原始模式,在后面的實例中,采用原始模式進行串口數(shù)據(jù)通信。
cfmakeraw(&new_cfg);
3.設(shè)置波特率
設(shè)置波特率有專門的函數(shù),用戶不能直接通過位掩碼來操作。設(shè)置波特率的主要函數(shù)有:cfsetispeed()和cfsetospeed()。這兩個函數(shù)的使用很簡單,如下所示:
cfsetispeed(&new_cfg,?B115200);
cfsetospeed(&new_cfg,?B115200);
一般地,用戶需將終端的輸入和輸出波特率設(shè)置成一樣的。這幾個函數(shù)在成功時返回0,失敗時返回-1。
4.設(shè)置字符大小
與設(shè)置波特率不同,設(shè)置字符大小并沒有現(xiàn)成可用的函數(shù),需要用位掩碼。一般首先去除數(shù)據(jù)位中的位掩碼,再重新按要求設(shè)置。如下所示:
new_cfg.c_cflag?&=?~CSIZE;?/*?用數(shù)據(jù)位掩碼清空數(shù)據(jù)位設(shè)置?*/
new_cfg.c_cflag?|=?CS8;
5.設(shè)置奇偶校驗位
設(shè)置奇偶校驗位需要用到termios中的兩個成員:c_cflag和c_iflag。首先要激活c_cflag中的校驗位使能標志PARENB和是否要進行偶校驗,同時還要激活c_iflag中的對于輸入數(shù)據(jù)的奇偶校驗使能(INPCK)。如使能奇校驗時,代碼如下所示:?
new_cfg.c_cflag?|=?(PARODD?|?PARENB);?
new_cfg.c_iflag?|=?INPCK;
而使能偶校驗時,代碼如下所示:
new_cfg.c_cflag?|=?PARENB;
new_cfg.c_cflag?&=?~PARODD;???/*?清除偶校驗標志,則配置為奇校驗*/
new_cfg.c_iflag?|=?INPCK;
6.設(shè)置停止位
設(shè)置停止位是通過激活c_cflag中的CSTOPB而實現(xiàn)的。若停止位為一個,則清除CSTOPB,若停止位為兩個,則激活CSTOPB。以下分別是停止位為一個和兩個比特時的代碼:
new_cfg.c_cflag?&=??~CSTOPB;???/*?將停止位設(shè)置為一個比特?*/
new_cfg.c_cflag?|=??CSTOPB;????????/*?將停止位設(shè)置為兩個比特?*/
7.設(shè)置最少字符和等待時間
在對接收字符和等待時間沒有特別要求的情況下,可以將其設(shè)置為0,則在任何情況下read()函數(shù)立即返回,如下所示:
new_cfg.c_cc[VTIME]??=?0;
new_cfg.c_cc[VMIN]?=?0;
8.清除串口緩沖
由于串口在重新設(shè)置之后,需要對當(dāng)前的串口設(shè)備進行適當(dāng)?shù)奶幚?,這時就可調(diào)用在<termios.h>中聲明的tcdrain()、tcflow()、tcflush()等函數(shù)來處理目前串口緩沖中的數(shù)據(jù),它們的格式如下所示。
int?tcdrain(int?fd);?/*?使程序阻塞,直到輸出緩沖區(qū)的數(shù)據(jù)全部發(fā)送完畢*/
int?tcflow(int?fd,?int?action)?;?/*?用于暫?;蛑匦麻_始輸出?*/
int?tcflush(int?fd,?int?queue_selector);?/*?用于清空輸入/輸出緩沖區(qū)*/
?
在本實例中使用tcflush()函數(shù),對于在緩沖區(qū)中的尚未傳輸?shù)臄?shù)據(jù),或者收到的但是尚未讀取的數(shù)據(jù),其處理方法取決于queue_selector的值,它可能的取值有以下幾種。
n TCIFLUSH:對接收到而未被讀取的數(shù)據(jù)進行清空處理。
n TCOFLUSH:對尚未傳送成功的輸出數(shù)據(jù)進行清空處理。
n TCIOFLUSH:包括前兩種功能,即對尚未處理的輸入輸出數(shù)據(jù)進行清空處理。
如在本例中所采用的是第一種方法:
tcflush(fd,?TCIFLUSH);
9.激活配置
在完成全部串口配置之后,要激活剛才的配置并使配置生效。這里用到的函數(shù)是tcsetattr(),它的函數(shù)原型是:
tcsetattr(int?fd,?int?optional_actions,?const?struct?termios?*termios_p);
其中參數(shù)termios_p是termios類型的新配置變量。
參數(shù)optional_actions可能的取值有以下3種:
n TCSANOW:配置的修改立即生效。
n TCSADRAIN:配置的修改在所有寫入fd的輸出都傳輸完畢之后生效。
n TCSAFLUSH:所有已接受但未讀入的輸入都將在修改生效之前被丟棄。
該函數(shù)若調(diào)用成功則返回0,若失敗則返回-1,代碼如下所示:
if?((tcsetattr(fd,?TCSANOW,?&new_cfg))?!=?0)
{
?????perror("tcsetattr");
?????return?-1;
}
下面給出了串口配置的完整函數(shù)。通常,為了函數(shù)的通用性,通常將常用的選項都在函數(shù)中列出,這樣可以大大方便以后用戶的調(diào)試使用。該設(shè)置函數(shù)如下所示:
int?set_com_config(int?fd,int?baud_rate,?
???????????????????????int?data_bits,?char?parity,?int?stop_bits)
{
?????struct?termios?new_cfg,old_cfg;
?????int?speed;
?????/*保存并測試現(xiàn)有串口參數(shù)設(shè)置,在這里如果串口號等出錯,會有相關(guān)的出錯信息*/
?????if??(tcgetattr(fd,?&old_cfg)??!=??0)?
?????{
??????????perror("tcgetattr");
??????????return?-1;
?????}
?????/*?設(shè)置字符大小*/
?????new_cfg?=?old_cfg;
?????cfmakeraw(&new_cfg);?/*?配置為原始模式?*/
?????new_cfg.c_cflag?&=?~CSIZE;
?????/*設(shè)置波特率*/
?????switch?(baud_rate)
?????{
??????????case?2400:
??????????{
???????????????speed?=?B2400;
??????????}
??????????break;
??????????case?4800:
??????????{
???????????????speed?=?B4800;
??????????}
??????????break;
??????????case?9600:
?????{
???????????????speed?=?B9600;
??????????}
??????????break;
??????????case?19200:
??????????{
?????????????speed?=?B19200;
?????}
??????????break;
??????????case?38400:
??????????{
??????????????speed?=?B38400;
??????????}
??????????break;
??????????default:
??????????case?115200:
??????????{
??????????speed?=?B115200;
??????????}
??????????break;
?????}
?????cfsetispeed(&new_cfg,?speed);
?????cfsetospeed(&new_cfg,?speed);
?????/*設(shè)置停止位*/
?????switch?(data_bits)
?????{
??????????case?7:
??????????{
???????????????new_cfg.c_cflag?|=?CS7;
??????????}
??????????break;
??????????default:
??????????case?8:
??????????{
?????????????new_cfg.c_cflag?|=?CS8;
??????????}
??????????break;
?????}
?????
?????/*設(shè)置奇偶校驗位*/
?????switch?(parity)
?????{
??????????default:
??????????case?'n':
??????????case?'N':
??????????{
??????????????new_cfg.c_cflag?&=?~PARENB;???
??????????????new_cfg.c_iflag?&=?~INPCK;????
??????????}
??????????break;
??????????case?'o':
??????????case?'O':
??????????{
??????????????new_cfg.c_cflag?|=?(PARODD?|?PARENB);
??????????????new_cfg.c_iflag?|=?INPCK;????????????
??????????}
??????????break;
??????????case?'e':
??????????case?'E':
??????????{
??????????????new_cfg.c_cflag?|=?PARENB;??
??????????????new_cfg.c_cflag?&=?~PARODD;?
??????????????new_cfg.c_iflag?|=?INPCK;???
??????????}
??????????break;
??????????case?'s':??/*as?no?parity*/
??????????case?'S':
??????????{
??????????new_cfg.c_cflag?&=?~PARENB;
??????????new_cfg.c_cflag?&=?~CSTOPB;
??????????}
??????????break;
?????}
??????????
?????/*設(shè)置停止位*/
?????switch?(stop_bits)
?????{
??????????default:
??????????case?1:
??????????{
??????????????new_cfg.c_cflag?&=??~CSTOPB;
??????????}
??????????break;
??????????case?2:
??????????{
??????????????new_cfg.c_cflag?|=?CSTOPB;
??????????}
?????}
?????
?????/*設(shè)置等待時間和最小接收字符*/
?????new_cfg.c_cc[VTIME]??=?0;
?????new_cfg.c_cc[VMIN]?=?1;
?????
?????/*處理未接收字符*/
?????tcflush(fd,?TCIFLUSH);
?????/*激活新配置*/
?????if?((tcsetattr(fd,?TCSANOW,?&new_cfg))?!=?0)
?????{
??????????perror("tcsetattr");
??????????return?-1;
?????}?????
?????return?0;
}
?
6.4.3??串口使用詳解
在配置完串口的相關(guān)屬性后,就可以對串口進行打開和讀寫操作了。它所使用的函數(shù)和普通文件的讀寫函數(shù)一樣,都是open()、write()和read()。它們之間的區(qū)別的只是串口是一個終端設(shè)備,因此在選擇函數(shù)的具體參數(shù)時會有一些區(qū)別。另外,這里會用到一些附加的函數(shù),用于測試終端設(shè)備的連接情況等。下面將對其進行具體講解。
1.打開串口
打開串口和打開普通文件一樣,都是使用open()函數(shù),如下所示:
fd?=?open(?"/dev/ttyS0",?O_RDWR|O_NOCTTY|O_NDELAY);
可以看到,這里除了普通的讀寫參數(shù)外,還有兩個參數(shù)O_NOCTTY和O_NDELAY。
n O_NOCTTY標志用于通知Linux系統(tǒng),該參數(shù)不會使打開的文件成為這個進程的控制終端。如果沒有指定這個標志,那么任何一個輸入(諸如鍵盤中止信號等)都將會影響用戶的進程。
n O_NDELAY標志通知Linux系統(tǒng),這個程序不關(guān)心DCD信號線所處的狀態(tài)(端口的另一端是否激活或者停止)。如果用戶指定了這個標志,則進程將會一直處在睡眠狀態(tài),直到DCD信號線被激活。
接下來可恢復(fù)串口的狀態(tài)為阻塞狀態(tài),用于等待串口數(shù)據(jù)的讀入,可用fcntl()函數(shù)實現(xiàn),如下所示:
fcntl(fd,?F_SETFL,?0);
再接著可以測試打開文件描述符是否連接到一個終端設(shè)備,以進一步確認串口是否正確打開,如下所示:
isatty(STDIN_FILENO);
該函數(shù)調(diào)用成功則返回0,若失敗則返回-1。
這時,一個串口就已經(jīng)成功打開了。接下來就可以對這個串口進行讀和寫操作。下面給出了一個完整的打開串口的函數(shù),同樣考慮到了各種不同的情況。程序如下所示:
/*打開串口函數(shù)*/
int?open_port(int?com_port)
{
?????int?fd;
#if?(COM_TYPE?==?GNR_COM)??/*?使用普通串口?*/
?????char?*dev[]?=?{"/dev/ttyS0",?"/dev/ttyS1",?"/dev/ttyS2"};
#else?/*?使用USB轉(zhuǎn)串口?*/
?????char?*dev[]?=?{"/dev/ttyUSB0",?"/dev/ttyUSB1",?"/dev/ttyUSB2"};
#endif
?????if?((com_port?<?0)?||?(com_port?>?MAX_COM_NUM))
?????{
??????????return?-1;
?????}
?????/*?打開串口?*/
?????fd?=?open(dev[com_port?-?1],?O_RDWR|O_NOCTTY|O_NDELAY);
?????if?(fd?<?0)
?????{
??????????????perror("open?serial?port");
??????????????return(-1);
?????}
?????
?????/*恢復(fù)串口為阻塞狀態(tài)*/
?????if?(fcntl(fd,?F_SETFL,?0)?<?0)
?????{
??????????perror("fcntl?F_SETFLn");
?????}
?????
?????/*測試是否為終端設(shè)備*/
?????if?(isatty(STDIN_FILENO)?==?0)
?????{
??????????perror("standard?input?is?not?a?terminal?device");
?????}?????
?????return?fd;
}
2.讀寫串口
讀寫串口操作和讀寫普通文件一樣,使用read()和write()函數(shù)即可,如下所示:
write(fd,?buff,?strlen(buff));
read(fd,?buff,?BUFFER_SIZE);
下面兩個實例給出了串口讀和寫的兩個程序,其中用到前面所講述的open_port()和set_com_config?()函數(shù)。寫串口的程序?qū)⒃谒拗鳈C上運行,讀串口的程序?qū)⒃谀繕税迳线\行。
寫串口的程序如下所示。
/*?com_writer.c?*/
#include?<stdio.h>
#include?<stdlib.h>
#include?<string.h>
#include?<sys/types.h>
#include?<sys/stat.h>
#include?<errno.h>
#include?"uart_api.h"
int?main(void)?
{
?????int?fd;
?????char?buff[BUFFER_SIZE];
?????if((fd?=?open_port(HOST_COM_PORT))?<?0)?/*?打開串口?*/
?????{
??????????perror("open_port");
??????????return?1;
?????}
?????
?????if(set_com_config(fd,?115200,?8,?'N',?1)?<?0)?/*?配置串口?*/
?????{
??????????perror("set_com_config");
??????????return?1;
?????}
?????
?????do
?????{
??????????printf("Input?some?words(enter?'quit'?to?exit):");
??????????memset(buff,?0,?BUFFER_SIZE);
??????????if?(fgets(buff,?BUFFER_SIZE,?stdin)?==?NULL)
??????????{
??????????????perror("fgets");
??????????????break;
??????????}
??????????write(fd,?buff,?strlen(buff));
?????}?while(strncmp(buff,?"quit",?4));
?????close(fd);
?????return?0;
}
讀串口的程序如下所示:
/*?com_reader.c?*/
#include?<stdio.h>
#include?<stdlib.h>
#include?<string.h>
#include?<sys/types.h>
#include?<sys/stat.h>
#include?<errno.h>
#include?"uart_api.h"
int?main(void)?
{
?????int?fd;
?????char?buff[BUFFER_SIZE];
?????
?????if((fd?=?open_port(TARGET_COM_PORT))?<?0)?/*?打開串口?*/
?????{
??????????perror("open_port");
??????????return?1;
?????}
?????
?????if(set_com_config(fd,?115200,?8,?'N',?1)?<?0)?/*?配置串口?*/
?????{
??????????perror("set_com_config");
??????????return?1;
?????}
?????
?????do
?????{
??????????memset(buff,?0,?BUFFER_SIZE);
??????????if?(read(fd,?buff,?BUFFER_SIZE)?>?0)
??????????{
??????????????printf("The?received?words?are?:?%s",?buff);
??????????}
?????}?while(strncmp(buff,?"quit",?4));
?????close(fd);
?????return?0;
}
在宿主機上運行寫串口的程序,而在目標板上運行讀串口的程序,運行結(jié)果如下所示。
/*?宿主機?,寫串口*/
$?./com_writer?
Input?some?words(enter?'quit'?to?exit):hello,?Reader!
Input?some?words(enter?'quit'?to?exit):I'm?Writer!
Input?some?words(enter?'quit'?to?exit):This?is?a?serial?port?testing?program.
Input?some?words(enter?'quit'?to?exit):quit
/*?目標板?,讀串口*/
$?./com_reader?
The?received?words?are?:?hello,?Reader!
The?received?words?are?:?I'm?Writer!
The?received?words?are?:?This?is?a?serial?port?testing?program.
The?received?words?are?:?quit
另外,讀者還可以考慮一下如何使用select()函數(shù)實現(xiàn)串口的非阻塞讀寫,具體實例會在本章的后面的實驗中給出。