串口的設置主要是設置struct termios結構體的各成員值,如下所示:
#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; /* 線路規程 */
unsigned char c_cc[NCC]; /* 控制特性 */
speed_t c_ispeed; /* 輸入速度 */
speed_t c_ospeed; /* 輸出速度 */
};
termios是在Posix規范中定義的標準接口,表示終端設備(包括虛擬終端、串口等)。因為串口是一種終端設備,所以通過終端編程接口對其進行配置和控制。因此在具體討論串口相關編程之前,需要先了解一下終端的相關知識。
終端是指用戶與計算機進行對話的接口,如鍵盤、顯示器和串口設備等物理設備,X Window上的虛擬終端。類UNIX操作系統都有文本式虛擬終端,使用【Ctrl+Alt】+F1~F6鍵可以進入文本式虛擬終端,在X Window上可以打開幾十個以上的圖形式虛擬終端。類UNIX操作系統的虛擬終端有xterm、rxvt、zterm、eterm等,而Windows上有crt、putty等虛擬終端。
終端有三種工作模式,分別為規范模式(canonical mode)、非規范模式(non-canonical mode)和原始模式(raw mode)。
通過在termios結構的c_lflag中設置ICANNON標志來定義終端是以規范模式(設置ICANNON標志)還是以非規范模式(清除ICANNON標志)工作,默認情況為規范模式。
在規范模式下,所有的輸入是基于行進行處理的。在用戶輸入一個行結束符(回車符、EOF等)之前,系統調用read()函數是讀不到用戶輸入的任何字符的。除了EOF之外的行結束符(回車符等)與普通字符一樣會被read()函數讀取到緩沖區中。在規范模式中,行編輯是可行的,而且一次調用read()函數多只能讀取一行數據。如果在read()函數中被請求讀取的數據字節數小于當前行可讀取的字節數,則read()函數只會讀取被請求的字節數,剩下的字節下次再被讀取。
在非規范模式下,所有的輸入是即時有效的,不需要用戶另外輸入行結束符,而且不可進行行編輯。在非規范模式下,對參數MIN(c_cc[VMIN])和TIME(c_cc[VTIME])的設置決定read()函數的調用方式。設置可以有4種不同的情況。
● MIN = 0和TIME = 0:read()函數立即返回。若有可讀數據,則讀取數據并返回被讀取的字節數,否則讀取失敗并返回0。
● MIN > 0和TIME = 0:read()函數會被阻塞,直到MIN個字節數據可被讀取。
● MIN = 0和TIME > 0:只要有數據可讀或者經過TIME個十分之一秒的時間,read()函數則立即返回,返回值為被讀取的字節數。如果超時并且未讀到數據,則read()函數返回0。
● MIN > 0和TIME > 0:當有MIN個字節可讀或者兩個輸入字符之間的時間間隔超過TIME個十分之一秒時,read()函數才返回。因為在輸入第一個字符后系統才會啟動定時器,所以,在這種情況下,read()函數至少讀取一個字節后才返回。
按照嚴格意義來講,原始模式是一種特殊的非規范模式。在原始模式下,所有的輸入數據以字節為單位被處理。在這個模式下,終端是不可回顯的,而且所有特定的終端輸入/輸出控制處理不可用。通過調用cfmakeraw()函數可以將終端設置為原始模式,而且該函數通過以下代碼可以得到實現:
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;
現在講解設置串口的基本方法。如上所述,串口設置基本的操作包括波特率設置,校驗位和停止位設置。在這個結構中為重要的是c_cflag,通過對它的賦值,用戶可以設置波特率、字符大小、數據位、停止位、奇偶校驗位和硬軟流控等。另外,c_iflag和c_cc也是比較常用的標志。在此主要對這3個成員進行詳細說明。c_cflag支持的常量名稱如表2.11所示。其中設置波特率宏名為相應的波特率數值前加上B,由于數值較多,本表沒有全部列出。
表2.11 c_cflag支持的常量名稱
CBAUD |
波特率的位掩碼 |
B0 |
0波特率(放棄DTR) |
… |
… |
續表
CBAUD |
波特率的位掩碼 |
B1800 |
1800波特率 |
B2400 |
2400波特率 |
B4800 |
4800波特率 |
B9600 |
9600波特率 |
B19200 |
19200波特率 |
B38400 |
38400波特率 |
B57600 |
57600波特率 |
B115200 |
115200波特率 |
EXTA |
外部時鐘率 |
EXTB |
外部時鐘率 |
CSIZE |
數據位的位掩碼 |
CS5 |
5個數據位 |
CS6 |
6個數據位 |
CS7 |
7個數據位 |
CS8 |
8個數據位 |
CSTOPB |
2個停止位(不設則是1個停止位) |
CREAD |
接收使能 |
PARENB
PARODD
|
校驗位使能
使用奇校驗而不使用偶校驗
|
HUPCL |
后關閉時掛線(放棄DTR) |
CLOCAL |
本地連接(不改變端口所有者) |
CRTSCTS |
硬件流控 |
在這里,對于c_cflag成員不能直接對其初始化,而要將其通過“與”、“或”操作使用其中的某些選項。
輸入模式標志c_iflag用于控制端口接收端的字符輸入處理。c_iflag支持的常量名稱,如表2.12所示。
表2.12 c_iflag支持的常量名稱
INPCK |
奇偶校驗使能 |
IGNPAR |
忽略奇偶校驗錯誤 |
PARMRK |
奇偶校驗錯誤掩碼 |
ISTRIP |
裁減掉第8位比特 |
IXON |
啟動輸出軟件流控 |
IXOFF |
啟動輸入軟件流控 |
續表
INPCK |
奇偶校驗使能 |
IXANY |
允許輸入任意字符可以重新啟動輸出(默認為輸入起始字符才重啟輸出) |
IGNBRK |
忽略輸入終止條件 |
BRKINT |
當檢測到輸入終止條件時發送SIGINT信號 |
INLCR |
將接收到的NL(換行符)轉換為CR(回車符) |
IGNCR |
忽略接收到的CR(回車符) |
ICRNL |
將接收到的CR(回車符)轉換為NL(換行符) |
IUCLC |
將接收到的大寫字符映射為小寫字符 |
IMAXBEL |
當輸入隊列滿時響鈴 |
c_oflag用于控制終端端口發送出去的字符處理,c_oflag支持的常量名稱如表2.13所示。因為現在終端的速度比以前快得多,所以大部分延時掩碼幾乎沒什么用途。
表2.13 c_oflag支持的常量名稱
OPOST |
啟用輸出處理功能,如果不設置該標志則其他標志都被忽略 |
OLCUC |
將輸出中的大寫字符轉換成小寫字符 |
ONLCR |
將輸出中的換行符('\n')轉換成回車符('\r') |
ONOCR |
如果當前列號為0,則不輸出回車符 |
OCRNL |
將輸出中的回車符('\r')轉換成換行符('\n') |
ONLRET |
不輸出回車符 |
OFILL |
發送填充字符以提供延時 |
OFDEL |
如果設置該標志,則表示填充字符為DEL字符,否則為NUL字符 |
NLDLY |
換行符延時掩碼 |
CRDLY |
回車符延時掩碼 |
TABDLY |
制表符延時掩碼 |
BSDLY |
水平退格符延時掩碼 |
VTDLY |
垂直退格符延時掩碼 |
FFLDY |
換頁符延時掩碼 |
c_lflag用于控制終端的本地數據處理和工作模式,c_lflag所支持的常量名稱如表2.14所示。
表2.14 c_lflag支持的常量名稱
ISIG |
若收到信號字符(INTR、QUIT等),則會產生相應的信號 |
ICANON |
啟用規范模式 |
ECHO |
啟用本地回顯功能 |
ECHOE |
若設置ICANON,則允許退格操作 |
續表
ECHOK |
若設置ICANON,則KILL字符會刪除當前行 |
ECHONL |
若設置ICANON,則允許回顯換行符 |
ECHOCTL |
若設置ECHO,則控制字符(制表符、換行符等)會顯示成“^X”,其中X的ASCII碼等于給相應控制字符的ASCII碼加上0x40。例如,退格字符(0x08)會顯示為“^H”('H'的ASCII碼為0x48) |
ECHOPRT |
若設置ICANON和IECHO,則刪除字符(退格符等)和被刪除的字符都會被顯示 |
ECHOKE |
若設置ICANON,則允許回顯在ECHOE和ECHOPRT中設定的KILL字符 |
NOFLSH |
在通常情況下,當接收到INTR、QUIT和SUSP控制字符時,會清空輸入和輸出隊列。如果設置該標志,則所有的隊列不會被清空 |
TOSTOP |
若一個后臺進程試圖向它的控制終端進行寫操作,則系統向該后臺進程的進程組發送SIGTTOU信號。該信號通常終止進程的執行 |
IEXTEN |
啟用輸入處理功能 |
c_cc定義特殊控制特性,c_cc所支持的常量名稱如表2.15所示。
表2.15 c_cc支持的常量名稱
VINTR |
中斷控制字符,對應鍵為Ctrl+C |
VQUIT |
退出操作符,對應鍵為Ctrl+Z |
VERASE |
刪除操作符,對應鍵為Backspace(BS) |
VKILL |
刪除行符,對應鍵為Ctrl+U |
VEOF |
文件結尾符,對應鍵為Ctrl+D |
VEOL |
附加行結尾符,對應鍵為Carriage return(CR) |
VEOL2 |
第二行結尾符,對應鍵為Line feed(LF) |
VMIN |
指定少讀取的字符數 |
VTIME |
指定讀取的每個字符之間的超時時間 |
下面就詳細講解設置串口屬性的基本流程。
1.保存原先串口配置
首先,為了安全起見和以后調試程序方便,可以先保存原先串口的配置,在這里可以使用函數tcgetattr(fd, &old_cfg)。該函數得到由fd指向的終端的配置參數,并將它們保存于termios結構變量old_cfg中。該函數還可以測試配置是否正確、該串口是否可用等。若調用成功,函數返回值為0,若調用失敗,函數返回值為-1,其使用如下所示:
if (tcgetattr(fd, &old_cfg) != 0)
{
perror("tcgetattr");
return -1;
}
2.激活選項
CLOCAL和CREAD分別用于本地連接和接收使能,因此,首先要通過位掩碼的方式激活這兩個選項。
newtio.c_cflag |= CLOCAL | CREAD;
調用cfmakeraw()函數可以將終端設置為原始模式,在后面的實例中,采用原始模式進行串口數據通信。
cfmakeraw(&new_cfg);
3.設置波特率
設置波特率有專門的函數,用戶不能直接通過位掩碼來操作。設置波特率的主要函數有cfsetispeed()和cfsetospeed()。這兩個函數的使用很簡單,如下所示:
cfsetispeed(&\&new_cfg, B115200);
cfsetospeed(&new_cfg, B115200);
cfsetispeed()函數在termios結構中設置數據輸入波特率,而cfsetospeed()函數在termios結構中設置數據輸入波特率。一般來說,用戶需將終端的輸入和輸出波特率設置成一樣的。這幾個函數在成功時返回0,失敗時返回-1。
4.設置字符大小
與設置波特率不同,設置字符大小并沒有現成可用的函數,需要用位掩碼。一般首先去除數據位中的位掩碼,再重新按要求設置,如下所示:
new_cfg.c_cflag &= ~CSIZE; /* 用數據位掩碼清空數據位設置 */
new_cfg.c_cflag |= CS8;
5.設置奇偶校驗位
設置奇偶校驗位需要用到termios中的兩個成員:c_cflag和c_iflag。首先要激活c_cflag中的校驗位使能標志PARENB和確認是否要進行校驗,這樣會對輸出數據產生校驗位,而對輸入數據進行校驗檢查。同時還要激活c_iflag中的對于輸入數據的奇偶校驗使能(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.設置停止位
設置停止位是通過激活c_cflag中的CSTOPB而實現的。若停止位為一個比特,則清除CSTOPB;若停止位為兩個,則激活CSTOPB。以下分別是停止位為一個和兩個比特時的代碼:
new_cfg.c_cflag &= ~CSTOPB; /* 將停止位設置為一個比特 */
new_cfg.c_cflag |= CSTOPB; /* 將停止位設置為兩個比特 */
7.設置少字符和等待時間
在對接收字符和等待時間沒有特別要求的情況下,可以將其設置為0,則在任何情況下read()函數立即返回,此時串口操作會設置為非阻塞方式,如下所示:
new_cfg.c_cc[VTIME] = 0;
new_cfg.c_cc[VMIN] = 0;
8.清除串口緩沖
由于串口在重新設置后,需要對當前的串口設備進行適當的處理,這時就可調用在<termios.h>中聲明的tcdrain()、tcflow()、tcflush()等函數來處理目前串口緩沖中的數據,它們的格式如下所示:
int tcdrain(int fd); /* 使程序阻塞,直到輸出緩沖區的數據全部發送完畢 */
int tcflow(int fd, int action); /* 用于暫停或重新開始輸出 */
int tcflush(int fd, int queue_selector); /* 用于清空輸入/輸出緩沖區 */
在本實例中使用tcflush()函數,對于在緩沖區中尚未傳輸的數據,或者收到的但是尚未讀取的數據,其處理方法取決于queue_selector的值,它可能的取值有以下幾種。
● TCIFLUSH:對接收到而未被讀取的數據進行清空處理。
● TCOFLUSH:對尚未傳送成功的輸出數據進行清空處理。
● TCIOFLUSH:包括前兩種功能,即對尚未處理的輸入/輸出數據進行清空處理。
如在本例中所采用的是第一種方法,當然可以使用TCIOFLUSH參數:
tcflush(fd, TCIFLUSH);
9.激活配置
在完成全部串口配置后,要激活剛才的配置并使配置生效。這里用到的函數是tcsetattr(),它的函數原型是:
tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
其中,參數termios_p是termios類型的新配置變量。
參數optional_actions可能的取值有以下3種。
● TCSANOW:配置的修改立即生效。
● TCSADRAIN:配置的修改在所有寫入fd的輸出都傳輸完畢之后生效。
● TCSAFLUSH:所有已接收但未讀入的輸入都將在修改生效之前被丟棄。
該函數若調用成功則返回0,若失敗則返回-1,代碼如下所示:
if ((tcsetattr(fd, TCSANOW, &new_cfg)) != 0)
{
perror("tcsetattr");
return -1;
}
下面給出了串口配置的完整函數。為了函數的通用性,通常將常用的選項都在函數中列出,這樣可以大大方便以后用戶的調試使用。該設置函數如下所示:
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;
/* 保存并測試現有串口參數設置,在這里如果串口號等出錯,會有相關的出錯信息 */
if (tcgetattr(fd, &old_cfg) != 0)
{
perror("tcgetattr");
return -1;
}
new_cfg = old_cfg;
cfmakeraw(&new_cfg); /* 配置為原始模式 */
new_cfg.c_cflag &= ~CSIZE;
/* 設置波特率 */
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);
switch (data_bits) /* 設置數據位 */
{
case 7:
{
new_cfg.c_cflag |= CS7;
}
break;
default:
case 8:
{
new_cfg.c_cflag |= CS8 ;
}
break;
}
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;
}
switch (stop_bits) /* 設置停止位 */
{
default:
case 1:
{
new_cfg.c_cflag &= ~CSTOPB;
}
break;
case 2:
{
new_cfg.c_cflag |= CSTOPB;
}
}
/* 設置等待時間和小接收字符 */
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;
}
本文選自華清遠見嵌入式培訓教材《從實踐中學嵌入式Linux應用程序開發》
熱點鏈接:
1、嵌入式Linux串口應用編程基礎知識
2、Linux下多路復用I/O接口
3、linux 文件鎖的實現及其應用
4、底層文件I/O操作的系統調用
5、Linux中的文件及文件描述符
更多新聞>> |