Linux 串列埠程式設計

2020-08-13 10:24:04

 

序列介面

序列介面 (Serial Interface) 是指數據一位一位地順序傳送其特點是通訊線路簡單只要一對傳輸線就可以實現雙向通訊從而大大降低了成本特別適用於遠距離通訊但傳送速度較慢一條資訊的各位數據被逐位按順序傳送的通訊方式稱爲序列通訊

序列通訊的特點是數據位的傳送按位元順序進行最少只需一根傳輸線即可完成成本低但傳送速度慢序列通訊的距離可以從幾米到幾千米根據資訊的傳送方向序列通訊可以進一步分爲單工半雙工和全雙工三種。

同步序列介面SynchronousSerialInterfaceSSI是一種常用的工業用通訊介面。。

非同步序列是指UARTUniversal Asynchronous Receiver/Transmitter),通用非同步接收/發送UART是一個並行輸入成爲序列輸出的晶片通常整合在主機板上UART包含TTL電平的串列埠RS232電平的串列埠

串列埠除了RS232,還有RS422,RS485,今天主要說的是RS232,其實USB(Universal Serial Bus)也是串列埠的一種,它的協定更加複雜,還有採用D+和D-差分信號,抗幹擾能力更強,速率更快。

圖片都是之前從網上找到

接下來開始講程式碼了,主要分成兩個部分,一就是串列埠設定,二是串列埠讀寫

int open_uart(char* port)
{
	int fd = open(port,O_RDWR|O_NOCTTY);
	if (fd < 0){
		printf("Open serial %s fail\n",port);
		return -1;
	}
	else{
		//printf("Open serial %s successful\n",port);
	}
	//set bolck mode
	if (fcntl(fd,F_SETFL,0)<0){
		printf("set %s block failed\n",port);
	}
	if(0 == isatty(STDIN_FILENO)){
		printf("standard input is not a terminal device\n");
	}
	//else{
	//	printf("isatty success\n");
	//}
	return fd;
}

設定串列埠

int set_uart_config(int fd,uart_cfg_t* s_cfg)    
{    
  int   ret;
  unsigned int i;    
  int   speed_arr[] = { B115200, B9600};
  int   name_arr[] = {115200, 9600};
  struct termios options;    

  ret = tcgetattr( fd,&options);    
  if( ret !=  0){    
    printf("ERROR: Get Serial Failed\n");        
    return ret;     
  }    

  for ( i= 0;  i < sizeof(speed_arr) / sizeof(int);  i++){    
    if(s_cfg->speed == name_arr[i]){                 
      cfsetispeed(&options, speed_arr[i]);
      cfsetospeed(&options, speed_arr[i]);
    }    
  }         

  options.c_cflag |= CLOCAL;
  options.c_cflag |= CREAD;

  switch(s_cfg->flow_ctrl)
  {
    case 0 :    
      options.c_cflag &= ~CRTSCTS;    
      break;       
    case 1 :   
      options.c_cflag |= CRTSCTS;    
      break;    
    case 2 :   
      options.c_cflag |= IXON | IXOFF | IXANY;    
      break; 
    default:
      options.c_cflag |= IXON | IXOFF | IXANY;    
      break;  
  }    

  options.c_cflag &= ~CSIZE;    
  switch (s_cfg->data_bits){      
    case 5 :    
      options.c_cflag |= CS5;    
      break;    
    case 6 :    
      options.c_cflag |= CS6;    
      break;    
    case 7 :        
      options.c_cflag |= CS7;    
      break;    
    case 8:        
      options.c_cflag |= CS8;    
      break;      
    default:       
      options.c_cflag |= CS8;
      break;    
  }    

  switch (s_cfg->parity[0]){      
    case 'n':    
    case 'N': 
      options.c_cflag &= ~PARENB;     
      options.c_iflag &= ~INPCK;        
      break;     
    case 'o':      
    case 'O':        
      options.c_cflag |= (PARODD | PARENB);     
      options.c_iflag |= INPCK;                 
      break;     
    case 'e':     
    case 'E':      
      options.c_cflag |= PARENB;           
      options.c_cflag &= ~PARODD;           
      options.c_iflag |= INPCK;          
      break;    
    case 's':    
    case 'S':     
      options.c_cflag &= ~PARENB;    
      options.c_cflag &= ~CSTOPB;    
      break;     
    default:      
      options.c_cflag &= ~PARENB;    
      options.c_cflag &= ~CSTOPB;    
      break;     
  }     

	switch (s_cfg->stop_bits){
		case 1:
			options.c_cflag &= ~CSTOPB;
			break;
		case 2:
			options.c_cflag |= CSTOPB;
			break;
		default:
			options.c_cflag |= CSTOPB;
			break;
	}    

	options.c_oflag &= ~OPOST;    
	options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);    
	options.c_cc[VTIME] = 20;      
	options.c_cc[VMIN] = 255;     
	tcflush(fd,TCIFLUSH);    
	ret=tcsetattr(fd,TCSANOW,&options);    
	if (ret != 0){    
		printf("ERROR: com set error!\n");      
		return ret;     
	}    
	return ret;     
}

下面 下麪這些是我在寫的時候,從網上查詢並記錄下來的一些筆記:

tcflag_t 是 unsigned short型別,cc_t 是 unsigned char型別

CRTCTS
使用硬體流控制。在高速(19200bps或更高)傳輸時,使用軟體流控制會使效率降低,這個時候必須使用硬體流控制
~CRTCTS不控制
IXOFF
如果設定,爲避免tty裝置的輸入緩衝區溢位,tty裝置可以向終端發送停止符^S和開始符^Q,要求終端停止或重新開始
向計算機發送數據。通過停止符和開始符來控制數據流的方式叫軟體流控制,軟體流控制方式較少用,我們主要還是用
硬體流控制方式。硬體流控制在c_cflag標誌中設定。
IXON
如果設定,接收到^S後會停止向這個tty裝置輸出,接收到^Q後會恢復輸出。
IXANY
如果設定,則接到任何字元都會重新開始輸出,而不僅僅是^Q字元。

options.c_cflag |= CS8;         //使用8位元數據位
options.c_cflag |= CS7;         //使用7位數據位
options.c_cflag |= CS6;         //使用6位數據位
options.c_cflag |= CS5;         //使用5位數據位

CSIZE 先遮蔽其他標誌位

options.c_cflag |= CSTOPB;            //2位停止位
options.c_cflag &amp;= ~CSTOPB;       //1位停止位
PARODD               使用奇校驗而不是偶校驗
PARENB               校驗使能位
INPCK                 奇偶校驗使能
1. N(None [沒有])
2. O Odd 奇校驗

3. E Even 偶校驗

4. S Space 校驗位固定爲0

ISIG 當輸入INTR、QUIT、SUSP或DSUSP時,產生相應的信號
ICANON  使用標準輸入模式
XCASE 在ICANON和XCASE同時設定的情況下,終端只使用大寫。
ECHO  顯示輸入字元
ECHOE  如果ICANON同時設定,ERASE將刪除輸入的字元
ECHOK 如果ICANON同時設定,KILL將刪除當前行
OPOST 處理後輸出
open未設定O_NONBLOCK或O_NDELAY的情況下
VTIME定義要求等待的時間量(取值不能大於cc_t)。
VMIN定義了要求等待的最小位元組數。

options.c_cc[VTIME] = X;   //設定從獲取到1個位元組後開始計時的超時時間
options.c_cc[VMIN] = Y;   ? //設定要求等待的最小位元組數
1、X=0,Y!=0。函數read()只有在讀取了Y個位元組的數據或者收到一個信號的時候才返回;
2、X!=0,Y=0。即使沒有數據可以讀取,read()函數等待X時間量後返回;
3、X!=0,Y!=0。第一個位元組數據到時開始,最先滿足收到Y個位元組或達超時時間X任意一個條件,read()返回;
4、X=0,Y=0。即使讀取不到任何數據,函數read也會立即返回

常用函數介紹

1讀取當前參數函數
int tcgetattr(int fd,struct termios *termios_p)
    fd
open操作後返回的檔案控制代碼
    *termios_p爲前面介紹的結構體
    初始化開始前呼叫這個函數.

2
獲取當前波特率函數
int speed_t cfgetispeed(const struct termios *termios_p)
int speed_t cfgetospeed(const struct termios *termios_p)
    *termios_p
爲前面介紹的結構體
    成功返回0失敗返回-1

3
波特率設定函數
int cfsetispeed(struct termios *termios_p,speed_t speed)
int cfsetospeed(struct termios *termios_p,speed_t speed)
    *termios_p
爲前面介紹的結構體
    speed波特率常用B2400B4800B9600B115200B460800
   
成功返回0失敗返回-1

4
清空buffer數據函數
int tcflush(int fd,int queue_selector)
    queue_selector:
有三個常用宏定義
                    TCIFLUSH:清空正讀的數據且不會讀出
                    TCOFLUSH:清空正寫入的數據且不會發送到終端
                    TCIOFLUSH:清空所有正在發生的I/O數據.

    成功返回0失敗返回-1

5設定串列埠參數函數
int tcsetattr(int fd,int optional_actions,cons struct termios *termios_p)
    optional_actions:
有三個常用宏定義
                    TCSANOW:不等數據傳輸完畢立即改變屬性
                    TCSADRAIN:等所有數據傳輸完畢再改變屬性
                    TCSAFLUSH:清空輸入輸出緩衝區才改變屬性

    成功返回0失敗返回-1 

第二部分:串列埠讀寫,在這裏我使用的是經tx和rx連線起來,自環

int single_uart_test(uart_cfg_t *s_cfg)
{
	int len = 0,res = 0,len_tmp = 0,flag = 0;	
	char tmp[4096];
	bzero(tmp,sizeof(tmp));		
	int fd = open_uart(uart_map(s_cfg->dev_id));
	init_uart(fd,s_cfg);

	if(signal(SIGTSTP,func_quit) == SIG_ERR){
		printf("signal error exit now!\n");
		return -1;
	}	
	if(signal(SIGINT,func_quit) == SIG_ERR){
		printf("signal error exit now!\n");
		return -1;
	}	
	printf("Welcome to com%d test mode:\n",s_cfg->dev_id);
	
	while(true){
		if (!flag) printf("Com%d says:",s_cfg->dev_id);
		flag++;
		fgets(big_buf,BUFFER_SIZE,stdin);
		len = strlen(big_buf);
		//printf("len:%d\n",len);
		write_uart(fd,big_buf,len,s_cfg->dev_id);
		read_uart(fd,s_cfg->dev_id,len,read_buf);	
				
		if( (0 == strncmp(read_buf,"quit",4)) || (0 == strncmp(read_buf,"exit",4))){
			printf("quit console %d test mode \n",s_cfg->dev_id);
			exit(0);
			}
		if ( 0 != strncmp(read_buf,big_buf,sizeof(read_buf))){
			res++;
		}
		//string -'\0'
		if(len == (BUFFER_SIZE-1)){
			memcpy(tmp+len_tmp,read_buf,len);
			len_tmp += len;
			//printf("tmp:%s\n",tmp);
		}else{
			memcpy(tmp+len_tmp,read_buf,len);
			if(res == 0){
				printf("Com%d recv:%s\n",s_cfg->dev_id,tmp);
				printf("Com%d ------->pass \n",s_cfg->dev_id);
				len_tmp = 0;
				bzero(tmp,sizeof(tmp));
			}else{
				printf("Com%d ------->fail \n",s_cfg->dev_id);
				//flushes both data received but not read, and data written but not transmitted.
				tcflush(fd,TCIOFLUSH);
			}
			flag = 0;
		}
		bzero(read_buf,sizeof(read_buf));
		bzero(big_buf,sizeof(big_buf));			
	}
	close_uart(fd,s_cfg->dev_id);
	return err_no;
}

原始碼放在資源下載,原始碼是一個48ports串列埠伺服器的串列埠測試程式碼,有刪減。