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