linux 下串口程序編寫

轉自https://www.ibm.com/developerworks/cn/linux/l-serials/


串口簡介

串行口是計算機一種常用的接口,具有連接線少,通訊簡單,得到廣泛的使用。常用的串口是 RS-232-C 接口(又稱 EIA RS-232-C)它是在 1970 年由美國電子工業協會(EIA)聯合貝爾系統、 調制解調器廠家及計算機終端生產廠家共同制定的用於串行通訊的標準。它的全名是"數據終端設備(DTE)和數據通訊設備(DCE)之間串行二進制數據交換接口技術標準"該標準規定採用一個 25 個腳的 DB25 連接器,對連接器的每個引腳的信號內容加以規定,還對各種信號的電平加以規定。傳輸距離在碼元畸變小於 4% 的情況下,傳輸電纜長度應爲 50 英尺。

Linux 操作系統從一開始就對串行口提供了很好的支持,本文就 Linux 下的串行口通訊編程進行簡單的介紹,如果要非常深入瞭解,建議看看本文所參考的 《Serial Programming Guide for POSIX Operating Systems》

計算機串口的引腳說明

序號 信號名稱 符號 流向 功能
2 發送數據 TXD DTE→DCE DTE發送串行數據
3 接收數據 RXD DTE←DCE DTE 接收串行數據
4 請求發送 RTS DTE→DCE DTE 請求 DCE 將線路切換到發送方式
5 允許發送 CTS DTE←DCE DCE 告訴 DTE 線路已接通可以發送數據
6 數據設備準備好 DSR DTE←DCE DCE 準備好
7 信號地     信號公共地
8 載波檢測 DCD DTE←DCE 表示 DCE 接收到遠程載波
20 數據終端準備好 DTR DTE→DCE DTE 準備好
22 振鈴指示 RI DTE←DCE 表示 DCE 與線路接通,出現振鈴

串口操作

串口操作需要的頭文件

#include     <stdio.h>      /*標準輸入輸出定義*/
#include     <stdlib.h>     /*標準函數庫定義*/
#include     <unistd.h>     /*Unix 標準函數定義*/
#include     <sys/types.h>  
#include     <sys/stat.h>   
#include     <fcntl.h>      /*文件控制定義*/
#include     <termios.h>    /*PPSIX 終端控制定義*/
#include     <errno.h>      /*錯誤號定義*/


打開串口

在 Linux 下串口文件是位於 /dev 下的

串口一 爲 /dev/ttyS0

串口二 爲 /dev/ttyS1

打開串口是通過使用標準的文件打開函數操作:

int fd;
/*以讀寫方式打開串口*/
fd = open( "/dev/ttyS0", O_RDWR);
if (-1 == fd){ 
/* 不能打開串口一*/ 
perror(" 提示錯誤!");
}


設置串口

最基本的設置串口包括波特率設置,效驗位和停止位設置。

串口的設置主要是設置 struct termios 結構體的各成員值。

struct termio
{	unsigned short  c_iflag;	/* 輸入模式標誌 */	
	unsigned short  c_oflag;		/* 輸出模式標誌 */	
	unsigned short  c_cflag;		/* 控制模式標誌*/	
	unsigned short  c_lflag;		/* local mode flags */	
	unsigned char  c_line;		    /* line discipline */	
	unsigned char  c_cc[NCC];    /* control characters */
};

設置這個結構體很複雜,我這裏就只說說常見的一些設置:

波特率設置

下面是修改波特率的代碼:

struct  termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200);     /*設置爲19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);

設置波特率的例子函數:

/**
*@brief  設置串口通信速率
*@param  fd     類型 int  打開串口的文件句柄
*@param  speed  類型 int  串口速度
*@return  void
*/
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,
          B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400,  19200,  9600,  4800,  2400,  1200,  300, 38400,  
          19200,  9600, 4800, 2400, 1200,  300, };
void set_speed(int fd, int speed){
  int   i; 
  int   status; 
  struct termios   Opt;
  tcgetattr(fd, &Opt); 
  for ( i= 0;  i < sizeof(speed_arr) / sizeof(int);  i++) { 
    if  (speed == name_arr[i]) {     
      tcflush(fd, TCIOFLUSH);     
      cfsetispeed(&Opt, speed_arr[i]);  
      cfsetospeed(&Opt, speed_arr[i]);   
      status = tcsetattr(fd1, TCSANOW, &Opt);  
      if  (status != 0) {        
        perror("tcsetattr fd1");  
        return;     
      }    
      tcflush(fd,TCIOFLUSH);   
    }  
  }
}

效驗位和停止位的設置:

無效驗 8位 Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS8;
奇效驗(Odd) 7位 Option.c_cflag |= ~PARENB;
Option.c_cflag &= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
偶效驗(Even) 7位 Option.c_cflag &= ~PARENB;
Option.c_cflag |= ~PARODD;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= ~CSIZE;
Option.c_cflag |= ~CS7;
Space效驗 7位 Option.c_cflag &= ~PARENB;
Option.c_cflag &= ~CSTOPB;
Option.c_cflag &= &~CSIZE;
Option.c_cflag |= CS8;

設置效驗的函數:

/**
*@brief   設置串口數據位,停止位和效驗位
*@param  fd     類型  int  打開的串口文件句柄
*@param  databits 類型  int 數據位   取值 爲 7 或者8
*@param  stopbits 類型  int 停止位   取值爲 1 或者2
*@param  parity  類型  int  效驗類型 取值爲N,E,O,,S
*/
int set_Parity(int fd,int databits,int stopbits,int parity)
{ 
	struct termios options; 
	if  ( tcgetattr( fd,&options)  !=  0) { 
		perror("SetupSerial 1");     
		return(FALSE);  
	}
	options.c_cflag &= ~CSIZE; 
	switch (databits) /*設置數據位數*/
	{   
	case 7:		
		options.c_cflag |= CS7; 
		break;
	case 8:     
		options.c_cflag |= CS8;
		break;   
	default:    
		fprintf(stderr,"Unsupported data size\n"); return (FALSE);  
	}
switch (parity) 
{   
	case 'n':
	case 'N':    
		options.c_cflag &= ~PARENB;   /* Clear parity enable */
		options.c_iflag &= ~INPCK;     /* Enable parity checking */ 
		break;  
	case 'o':   
	case 'O':     
		options.c_cflag |= (PARODD | PARENB); /* 設置爲奇效驗*/  
		options.c_iflag |= INPCK;             /* Disnable parity checking */ 
		break;  
	case 'e':  
	case 'E':   
		options.c_cflag |= PARENB;     /* Enable parity */    
		options.c_cflag &= ~PARODD;   /* 轉換爲偶效驗*/     
		options.c_iflag |= INPCK;       /* Disnable parity checking */
		break;
	case 'S': 
	case 's':  /*as no parity*/   
	    options.c_cflag &= ~PARENB;
		options.c_cflag &= ~CSTOPB;break;  
	default:   
		fprintf(stderr,"Unsupported parity\n");    
		return (FALSE);  
	}  
/* 設置停止位*/  
switch (stopbits)
{   
	case 1:    
		options.c_cflag &= ~CSTOPB;  
		break;  
	case 2:    
		options.c_cflag |= CSTOPB;  
	   break;
	default:    
		 fprintf(stderr,"Unsupported stop bits\n");  
		 return (FALSE); 
} 
/* Set input parity option */ 
if (parity != 'n')   
	options.c_iflag |= INPCK; 
tcflush(fd,TCIFLUSH);
options.c_cc[VTIME] = 150; /* 設置超時15 seconds*/   
options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)   
{ 
	perror("SetupSerial 3");   
	return (FALSE);  
} 
return (TRUE);  
}

需要注意的是:

如果不是開發終端之類的,只是串口傳輸數據,而不需要串口來處理,那麼使用原始模式(Raw Mode)方式來通訊,設置方式如下:

options.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
options.c_oflag  &= ~OPOST;   /*Output*/


讀寫串口

設置好串口之後,讀寫串口就很容易了,把串口當作文件讀寫就是。

  • 發送數據
    char  buffer[1024];int    Length;int    nByte;nByte = write(fd, buffer ,Length)
    

  • 讀取串口數據

    使用文件操作read函數讀取,如果設置爲原始模式(Raw Mode)傳輸數據,那麼read函數返回的字符數是實際串口收到的字符數。

    可以使用操作文件的函數來實現異步讀取,如fcntl,或者select等來操作。

    char  buff[1024];int    Len;int  readByte = read(fd,buff,Len);
    


關閉串口

關閉串口就是關閉文件。

close(fd);


例子

下面是一個簡單的讀取串口數據的例子,使用了上面定義的一些函數和頭文件

/**********************************************************************
代碼說明:使用串口二測試的,發送的數據是字符,
但是沒有發送字符串結束符號,所以接收到後,後面加上了結束符號。
我測試使用的是單片機發送數據到第二個串口,測試通過。
**********************************************************************/
#define FALSE  -1
#define TRUE   0
/*********************************************************************/
int OpenDev(char *Dev)
{
	int	fd = open( Dev, O_RDWR );         //| O_NOCTTY | O_NDELAY	
	if (-1 == fd)	
	{ 			
		perror("Can't Open Serial Port");
		return -1;		
	}	
	else	
		return fd;
}
int main(int argc, char **argv){
	int fd;
	int nread;
	char buff[512];
	char *dev  = "/dev/ttyS1"; //串口二
	fd = OpenDev(dev);
	set_speed(fd,19200);
	if (set_Parity(fd,8,1,'N') == FALSE)  {
		printf("Set Parity Error\n");
		exit (0);
	}
while (1) //循環讀取數據
{   
	while((nread = read(fd, buff, 512))>0)
	{ 
		printf("\nLen %d\n",nread); 
		buff[nread+1] = '\0';   
		printf( "\n%s", buff);   
	}
}
	//close(fd);  
	// exit (0);
}

轉自http://www.linuxidc.com/Linux/2011-02/32253.htm


用戶常見的數據通信的基本方式可分爲並行通信和串行通信。

並行通信是指利用多條數據傳輸線將一個資料的各位同時傳送。特點是傳輸速度快,適用於短距離通信,但要求傳輸速度較高的應用場合。

串行通信是指利用一條傳輸線將資料一位位的順序傳送。特點是通信線路簡單,利用簡單的線纜就可以實現通信,減低成本,適用於遠距離通信,但傳輸速度慢的應用場合。常用的串口有RS-232-C接口(全稱是“數據終端設備(DTE)和數據通訊設備(DCE)之間串行二進制數據交換接口技術標準”)。

UART控制器:可以工作在Interrupt(中斷)模式或者DMA(直接內存訪問)模式。據有16字節的FIFO(先入先出寄存器),支持最高波特率可達到230.4Kbps。

UART操作:資料發送、資料接收、產生中斷、產生波特率、Loopback模式、紅外模式及自動流控制模式。

串口設置包括:波特率、起始位數量、數據位數量、停止位數量和流控協議。在此可以配置波特率爲115200、起始位爲1b、數據位8b、停止位1b和無流控制協議。

串口一、串口二對應設備名依次是“/dev/ttyS0”、“/dev/ttyS1”。

在Linux下對串口的讀寫可以使用簡單的“read”、“write”函數完成,不同的是需要對串口的其它參數另作設置。

6.4.2 串口設置詳情

串口設置主要是設置struct termios結構體成員值:

#include<termios.h>

Struct termio

{

       unsigned short c_iflag;          /*輸入模式標誌*/

       unsigned short c_oflag;         /*輸出模式標誌*/

       unsigned short c_cflag;         /*控制模式標誌*/

       unsigned short c_lfag;           /*本地模式標誌*/

unsigned short c_line;           /*line discipline*/

unsigned short c_cc[NCC];   /*control characters*/

};

通過對c_cflag的賦值,可以設置波特率、字符大小、數據位、停止位、奇偶校驗位和硬件流控等。


設置串口屬性基本流程:

1.       保存原先串口配置

爲了安全起見和以後調試程序方便,可先保存原先串口的配置,使用函數tcgetattr(fd,&oldtio)。該函數得到與fd指向對象的相關參數,並將它們保存於lodtio引用的termios結構中。該函數可以測試配置是否正確、該串口是否可用等。調試成功,函數返回0,失敗,函數返回-1.

if(tcgetattr(fd,&oldtio)!=0)

{

        perror(“SetupSerial 1”);

        return -1;

}

2.       激活選項有CLOCAL和CREAD

CLOCAL和CREAD分別用於本地連接和接受使能,通過位掩碼的方式激活這兩個選項。

Newtio.c_cflag |= CLOCAL | CREAD;

3.       設置波特率

設置波特率的函數主要有cfsetispeed和cfsetospeed。

cfsetispeed(&newtio,B115200);

cfsetospeed(&newtio,B115200);

一般地用戶需要將輸入輸出函數的波特率設置成一樣的。這幾個函數在成功時返回0,失敗-1。

4.       設置字符大小

沒有現成可用函數,需要位掩碼。一般先去除數據位中的位掩碼,再重新按要求設置。

options.c_cflag &= ~CSIZE; /*mask the character size bits*/

options.c_cflag |= CS8;

5.       設置奇偶校驗位

先激活c_cflag中的校驗位使能標誌PARENB和是否要進行偶校驗,同時還要激活c_iflag中的奇偶校驗使能。如使能奇校驗時,代碼如下:

newtio.c_cflag |= PARENB;

newtio.c_cflag |=PARODD;

newtio.c_iflag |= (INPCK | ISTRIP);

而使能偶校驗代碼爲:

newtio.c_iflag |= (INPCK | ISTRIP);

newtio.c_cflag |= PARENB;

newtio.c_cflag &= ~PAROOD;

6.       設置停止位

通過激活c_cflag中的CSTOPB而實現的。若停止位爲1,則清除CSTOPB,若停止位爲0,則激活CSTOPB。下面是停止位爲1時的代碼:

newtio.c_cflag &= ~CSTOPB;

7.       設置最少字符和等待時間

在對接收字符和等待時間沒有特別要求的情況下,可以將其設置爲0:

newtio.c_cc[VTIME] =0;

newtio.c_cc[VMIN]=0;

8.       處理要寫入的引用對象

在串口重新設置之後,在之前要寫入的引用對象要重新處理,可調用函數tcflush(fd,queue_selector)來處理要寫入引用的對象。對於爲傳輸的數據,或收到但未讀取的數據,其處理方法取決於queue_selector的值。

Queue_selector可能取值:

TCIFLUSH:刷新收到的數據但不讀

TCOFLUSH:刷新寫入的數據但不傳送

TCIOLFLUSH:同時刷新收到的數據但不讀,並且刷新寫入的數據但不傳送

本例採用一:

tcflush(fd, TCIFLUSH)

9.       激活配置

用到函數tcsetattr:

函數原型:tcsetattr(fd,OPTION,&newtio);

這裏的newtio就是termios類型的變量,OPTION可能的取值如下:

TCSANOW:改變的配置立即生效

TCSADRAIN:改變的配置在所有寫入fd的輸出都結束後生效

TCSAFLUSH:改變的配置自愛所有寫入fd引用對象的輸出都被結束後生效,所有已接受但爲讀入的輸入都在改變發生前丟棄。

該函數調用成功返回0,失敗-1.

if((tcsetattr(fd,TCSANOW,&newtio))!=0)

{

       perror(“com set error”);

       return -1;

}

/*串口配置的完整函數,爲了函數的通用性,通常將常用的選項都在函數中列出,可大大方便以後用戶的調試使用*/
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”);
        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'://奇數
   newtio.c_cflag |= PARENB;
   newtio.c_cflag |=PARODD;
   newtio.c_iflag |= (INPCK | ISTRIP);
   break;
case 'E'://偶數
   newtio.c_iflag |= (INPCK | ISTRIP);
   newtio.c_cflag |= PARENB;
   newtio.c_cflag &= ~PARODD;
case 'N'://無奇偶校驗位
   newtio.c_cflag &= ~PARENB;
   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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章