串口的設置主要是設置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應用程序開發》
轉自:http://www.farsight.com.cn/news/emb174.htm