1. 程式人生 > >Linux下串列埠通訊詳解(上)開啟串列埠和串列埠初始化詳解

Linux下串列埠通訊詳解(上)開啟串列埠和串列埠初始化詳解

linux下串列埠通訊主要有下面幾個步驟

Linux串列埠通訊流程

串列埠通訊流程圖

下面我會一一介紹這幾個步驟。

1.開啟串列埠

程式碼(串列埠為ttyUSB0)

//開啟串列埠
int open_port(void)
{
	int fd;
		
	fd=open("/dev/ttyUSB0",O_RDWR | O_NOCTTY | O_NONBLOCK);//O_NONBLOCK設定為非阻塞模式,在read時不會阻塞住,在讀的時候將read放在while迴圈中,下一節篇文件將詳細講解阻塞和非阻塞
//	printf("fd=%d\n",fd);
	
	if(fd==-1)
	{
		perror("Can't Open SerialPort");
	}
	
	return fd;
}
開啟串列埠時也可以多加一些內容,比如判斷串列埠為阻塞狀態、測試是否為終端裝置等,這些是必要的,所以較上面的基本的開啟串列埠的程式碼,更加完整健壯一些的程式碼流程如下所示:


開啟串列埠較完整流程圖

程式碼:

/**
 * open port
 * @param  fd
 * @param  comport 想要開啟的串列埠號
 * @return  返回-1為開啟失敗
 */
int open_port(int fd,int comport) 
{ 
	char *dev[]={"/dev/ttyUSB0","/dev/ttyS1","/dev/ttyS2"};

	if (comport==1)//串列埠1 
	{
		fd = open( "/dev/ttyUSB0", O_RDWR|O_NOCTTY|O_NDELAY); 
		if (-1 == fd)
		{ 
			perror("Can't Open Serial Port"); 
			return(-1); 
		} 
     } 
     else if(comport==2)//串列埠2 
     {     
		fd = open( "/dev/ttyS1", O_RDWR|O_NOCTTY|O_NDELAY); //沒有設定<span style="font-family: Arial, Helvetica, sans-serif;">O_NONBLOCK非阻塞模式,也可以設定為非阻塞模式,兩個模式在下一篇部落格中具體說明</span>

		if (-1 == fd)
		{ 
			perror("Can't Open Serial Port"); 
			return(-1); 
		} 
     } 
     else if (comport==3)//串列埠3 
     { 
		fd = open( "/dev/ttyS2", O_RDWR|O_NOCTTY|O_NDELAY); 
		if (-1 == fd)
		{ 
			perror("Can't Open Serial Port"); 
			return(-1); 
		} 
     } 
     /*恢復串列埠為阻塞狀態*/ 
     if(fcntl(fd, F_SETFL, 0)<0) 
     		printf("fcntl failed!\n"); 
     else 
		printf("fcntl=%d\n",fcntl(fd, F_SETFL,0)); 
     /*測試是否為終端裝置*/ 
     if(isatty(STDIN_FILENO)==0) 
		printf("standard input is not a terminal device\n"); 
     else 
		printf("isatty success!\n"); 
     printf("fd-open=%d\n",fd); 
     return fd; 
}

關鍵函式解釋:

open

功能描述:用於開啟或建立檔案,成功則返回檔案描述符,否則返回-1,open返回的檔案描述符一定是最小的未被使用的描述符

#include<fcntl.h>
int open(const char *pathname, int oflag, ... );
引數解釋:

pathname:檔案路徑名,串列埠在Linux中被看做是一個檔案

oflag:一些檔案模式選擇,有如下幾個引數可以設定

  • O_RDONLY只讀模式
  • O_WRONLY只寫模式
  • O_RDWR讀寫模式
上面三個引數在設定的時候必須選擇其中一個!!!下面的是可選的
  • O_APPEND每次寫操作都寫入檔案的末尾
  • O_CREAT如果指定檔案不存在,則建立這個檔案
  • O_EXCL如果要建立的檔案已存在,則返回 -1,並且修改 errno 的值
  • O_TRUNC如果檔案存在,並且以只寫/讀寫方式開啟,則清空檔案全部內容
  • O_NOCTTY如果路徑名指向終端裝置,不要把這個裝置用作控制終端。
  • O_NONBLOCK如果路徑名指向 FIFO/塊檔案/字元檔案,則把檔案的開啟和後繼 I/O設定為非阻塞模式(nonblocking mode)

下面三個常量同樣是選用的,他們用於同步輸入輸出

  • O_DSYNC等待物理 I/O 結束後再 write。在不影響讀取新寫入的資料的前提下,不等待檔案屬性更新。
  • O_RSYNC讀(read)等待所有寫入同一區域的寫操作完成後再進行
  • O_SYNC等待物理 I/O 結束後再 write,包括更新檔案屬性的 I/O
對於串列埠的開啟操作,必須使用O_NOCTTY引數,它表示開啟的是一個終端裝置,程式不會成為該埠的控制終端。如果不使用此標誌,任務的一個輸入(比如鍵盤終止訊號等)都會影響程序。

O_NDELAY表示不關心DCD訊號所處的狀態(埠的另一端是否啟用或者停止)。

fcntl

功能描述:根據檔案描述詞來操作檔案的特性,返回-1代表出錯

#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd,int cmd);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd,int cmd,struct flock *lock);
引數說明:
  • fd:檔案描述符
  • cmd:命令引數
fcntl函式有5種功能:
1. 複製一個現有的描述符(cmd=F_DUPFD). 
2. 獲得/設定檔案描述符標記(cmd=F_GETFD或F_SETFD). 
3. 獲得/設定檔案狀態標記(cmd=F_GETFL或F_SETFL). 
4. 獲得/設定非同步I/O所有權(cmd=F_GETOWN或F_SETOWN). 
5. 獲得/設定記錄鎖(cmd=F_GETLK , F_SETLK或F_SETLKW).

isatty

2.串列埠的初始化

串列埠初始化工作需要做以下工作:
  1. 設定波特率
  2. 設定資料流控制
  3. 設定幀的格式(即資料位個數,停止位,校驗位)
串列埠初始化 串列埠初始化 程式碼:
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop) 
{ 
     struct termios newtio,oldtio; 
/*儲存測試現有串列埠引數設定,在這裡如果串列埠號等出錯,會有相關的出錯資訊*/ 
     if  ( tcgetattr( fd,&oldtio)  !=  0) {  
      perror("SetupSerial 1");
	printf("tcgetattr( fd,&oldtio) -> %d\n",tcgetattr( fd,&oldtio)); 
      return -1; 
     } 
     bzero( &newtio, sizeof( newtio ) ); 
/*步驟一,設定字元大小*/ 
     newtio.c_cflag  |=  CLOCAL | CREAD;  
     newtio.c_cflag &= ~CSIZE;  
/*設定停止位*/ 
     switch( nBits ) 
     { 
     case 7: 
      newtio.c_cflag |= CS7; 
      break; 
     case 8: 
      newtio.c_cflag |= CS8; 
      break; 
     } 
/*設定奇偶校驗位*/ 
     switch( nEvent ) 
     { 
     case 'o':
     case 'O': //奇數 
      newtio.c_cflag |= PARENB; 
      newtio.c_cflag |= PARODD; 
      newtio.c_iflag |= (INPCK | ISTRIP); 
      break; 
     case 'e':
     case 'E': //偶數 
      newtio.c_iflag |= (INPCK | ISTRIP); 
      newtio.c_cflag |= PARENB; 
      newtio.c_cflag &= ~PARODD; 
      break;
     case 'n':
     case 'N':  //無奇偶校驗位 
      newtio.c_cflag &= ~PARENB; 
      break;
     default:
      break;
     } 
     /*設定波特率*/ 
switch( nSpeed ) 
     { 
     case 2400: 
      cfsetispeed(&newtio, B2400); 
      cfsetospeed(&newtio, B2400); 
      break; 
     case 4800: 
      cfsetispeed(&newtio, B4800); 
      cfsetospeed(&newtio, B4800); 
      break; 
     case 9600: 
      cfsetispeed(&newtio, B9600); 
      cfsetospeed(&newtio, B9600); 
      break; 
     case 115200: 
      cfsetispeed(&newtio, B115200); 
      cfsetospeed(&newtio, B115200); 
      break; 
     case 460800: 
      cfsetispeed(&newtio, B460800); 
      cfsetospeed(&newtio, B460800); 
      break; 
     default: 
      cfsetispeed(&newtio, B9600); 
      cfsetospeed(&newtio, B9600); 
     break; 
     } 
/*設定停止位*/ 
     if( nStop == 1 ) 
      newtio.c_cflag &=  ~CSTOPB; 
     else if ( nStop == 2 ) 
      newtio.c_cflag |=  CSTOPB; 
/*設定等待時間和最小接收字元*/ 
     newtio.c_cc[VTIME]  = 0; 
     newtio.c_cc[VMIN] = 0; 
/*處理未接收字元*/ 
     tcflush(fd,TCIFLUSH); 
/*啟用新配置*/ 
if((tcsetattr(fd,TCSANOW,&newtio))!=0) 
     { 
      perror("com set error"); 
      return -1; 
     } 
     printf("set done!\n"); 
     return 0; 
} 
講解這片程式碼之前,我們要先研究一下termios的資料結構。最小的termios結構的典型定義如下:
struct termios
{
           tcflag_t c_iflag;
           tcflag_t c_oflag;
           tcflag_t c_cflag;
           tcflag_t c_lflag;
           cc_t           c_cc[NCCS];
};
上面五個結構成員名稱分別代表:
  • c_iflag:輸入模式
  • c_oflag:輸出模式
  • c_cflag:控制模式
  • c_lflag:本地模式
  • c_cc[NCCS]:特殊控制模式
五種模式的引數說明見部落格http://blog.csdn.net/querdaizhi/article/details/7436722
tcgetattr可以初始化一個終端對應的termios結構,tcgetattr函式原型如下:
#include<termios.h>  
int tcgetattr(int fd, struct termios *termios_p); 
這個函式呼叫把低昂前終端介面變數的值寫入termios_p引數指向的結構。如果這些值其後被修改了,可以通過呼叫函式tcsetattr來重新配置。 tcsetattr函式原型如下:
#include<termios.h>  
int tcsetattr(int fd , int actions , const struct termios *termios_h);  
引數actions控制修改方式,共有三種修改方式,如下所示:
  1. TCSANOW:立刻對值進行修改
  2. TCSADRAIN:等當前的輸出完成後再對值進行修改
  3. TCSAFLUSH:等當前的輸出完成之後,再對值進行修改,但丟棄還未從read呼叫返回的當前的可用的任何輸入。
在我們的程式碼中,我們設定為NOW立即對值進行修改。 tcflush用於清空中端為完成的輸入/輸出請求及資料,它的函式原型如下:
int tcflush(int fd, int queue_selector);
其中queue_selector時控制tcflush的操作,取值可以為如下引數中的一個:TCIFLUSH清楚正收到的資料,且不會讀出來;TCOFLUSH清楚正寫入的資料,且不會發送至終端;TCIOFLUSH清除所有正在傳送的I/O資料。 再看我們的程式碼,我們修改字元大小的程式碼為
newtio.c_cflag  |=  CLOCAL | CREAD;  
newtio.c_cflag &= ~CSIZE;  
c_cflag代表控制模式
  • CLOCAL含義為忽略所有調變解調器的狀態行,這個目的是為了保證程式不會佔用串列埠。
  • CREAD代表啟用字元接收器,目的是是的能夠從串列埠中讀取輸入的資料。
  • CS5/6/7/8表示傳送或接收字元時使用5/6/7/8位元。
  • CSTOPB表示每個字元使用兩位停止位。
  • HUPCL表示關閉時結束通話調變解調器。
  • PARENB:啟用奇偶校驗碼的生成和檢測功能。
  • PARODD:只使用奇校驗而不使用偶校驗。
c_iflag代表輸入模式
  • BRKINT:當在輸入行中檢測到一個終止狀態時,產生一箇中斷。
  • TGNBRK:忽略輸入行中的終止狀態。
  • TCRNL:將接受到的回車符轉換為新行符。
  • TGNCR:忽略接受到的新行符。
  • INLCR:將接受到的新行符轉換為回車符。
  • IGNPAR:忽略奇偶校檢錯誤的字元。
  • INPCK:對接收到的字元執行奇偶校檢。
  • PARMRK:對奇偶校檢錯誤作出標記。
  • ISTRIP:將所有接收的字元裁減為7位元。
  • IXOFF:對輸入啟用軟體流控。
  • IXON:對輸出啟用軟體流控。
c_cc特殊的控制字元

標準模式和非標準模式下,c_cc陣列的下標有不同的值:

標準模式:

  • VEOF:EOF字元
  • VEOL:EOF字元
  • VERASE:ERASE字元
  • VINTR:INTR字元
  • VKILL:KILL字元
  • VQUIT:QUIT字元
  • VSTART:START字元 
  • VSTOP:STOP字元

非標準模式:

  • VINTR:INTR字元
  • VMIN:MIN值
  • VQUIT:QUIT字元
  • VSUSP:SUSP字元
  • VTIME:TIME值
  • VSTART:START字元 
  • VSTOP:STOP字元
cfsetispeed和cfsetospeed用來設定輸入輸出的波特率,函式模型如下:
int cfsetispeed(struct termios *termptr, speed_t speed);
int cfsetospeed(struct termios *termptr, speed_t speed);
引數說明:
  • struct termios *termptr:指向termios結構的指標
  • speed_t speed:需要設定的波特率
  • 返回值:成功返回0,否則返回-1

這樣,所有的初始化操作我們就完成了。

下一篇文章我會記錄串列埠的讀寫及關閉操作的詳細步驟。並且會把原始碼連結給出供大家參考!