加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長(zhǎng)期合作伙伴
立即加入
  • 正文
    • 6.4  嵌入式Linux串口應(yīng)用編程
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

文件I/O編程之: 嵌入式Linux串口應(yīng)用編程

2013/09/13
1
閱讀需 46 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

?

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)中給出。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

華清遠(yuǎn)見(www.farsight.com.cn)是國(guó)內(nèi)領(lǐng)先嵌入師培訓(xùn)機(jī)構(gòu),2004年注冊(cè)于中國(guó)北京海淀高科技園區(qū),除北京總部外,上海、深圳、成都、南京、武漢、西安、廣州均有直營(yíng)分公司。華清遠(yuǎn)見除提供嵌入式相關(guān)的長(zhǎng)期就業(yè)培訓(xùn)、短期高端培訓(xùn)、師資培訓(xùn)及企業(yè)員工內(nèi)訓(xùn)等業(yè)務(wù)外,其下屬研發(fā)中心還負(fù)責(zé)嵌入式、Android及物聯(lián)網(wǎng)方向的教學(xué)實(shí)驗(yàn)平臺(tái)的研發(fā)及培訓(xùn)教材的出版,截止目前為止已公開出版70余本嵌入式/移動(dòng)開發(fā)/物聯(lián)網(wǎng)相關(guān)圖書。企業(yè)理念:專業(yè)始于專注 卓識(shí)源于遠(yuǎn)見。企業(yè)價(jià)值觀:做良心教育、做專業(yè)教育,更要做受人尊敬的職業(yè)教育。