Linux 串列埠程式設計

詆調發表於2015-01-26

在linux中,所有的裝置檔案一般位於"/dev"目錄下,其中串列埠對應的名稱為"/dev/ttySx",(因驅動不同,該裝置名也會有所不同),可也檢視在"/dev"下的檔案以確認。在linux下對裝置的操作方法與對檔案操作方法一樣,因此,對串列埠的讀寫就可以使用簡單的read,write等函式來完成,所不同的只是需要對串列埠的其他引數另做配置。

       串列埠的設定主要是設定structtermios結構體中的成員,如下所示.

struct termios

{

  tcflag_t  c_iflag;       //輸入模式標誌

   tcflag_t  c_oflag;       //輸出模式標誌

  tcflag_t  c_cflag;       //控制模式標誌

  tcflag_t  c_lflag;       //本地模式標誌

   cc_t   c_line;           //線路規則

   cc_t   c_cc[NCC];        //控制特性

}

       C_iflag支援的常量名稱如下表所示,其用於控制埠接收端的字元輸入處理。

鍵    值

說    明

IGNBRK

忽略BREAK鍵輸入

BRKINT

如果設定了IGNBRK,BREAK鍵的輸入將被忽略,如果設定了BRKINT ,將產生SIGINT中斷

IGNPAR

忽略奇偶校驗錯誤

PARMRK

標識奇偶校驗錯誤

INPCK

允許輸入奇偶校驗

ISTRIP

去除字元的第8個位元

INLCR

將輸入的NL(換行)轉換成CR(回車)

IGNCR

忽略輸入的回車

ICRNL

將輸入的回車轉化成換行(如果IGNCR未設定的情況下)

IUCLC

將輸入的大寫字元轉換成小寫字元(非POSIX)

IXON

允許輸入時對XON/XOFF流進行控制

IXANY

輸入任何字元將重啟停止的輸出

IXOFF

允許輸入時對XON/XOFF流進行控制

IMAXBEL

當輸入佇列滿的時候開始響鈴,Linux在使用該引數而是認為該引數總是已經設定

 

C_oflag支援的常量名稱如下表所示,其用於控制終端埠傳送出去的字元處理。

鍵    值

說    明

OPOST

處理後輸出

OLCUC

將輸入的小寫字元轉換成大寫字元(非POSIX)

ONLCR

將輸入的NL(換行)轉換成CR(回車)及NL(換行)

OCRNL

將輸入的CR(回車)轉換成NL(換行)

ONOCR

第一行不輸出回車符

ONLRET

不輸出回車符

OFILL

傳送填充字元以延遲終端輸出

OFDEL

以ASCII碼的DEL作為填充字元,如果未設定該引數,填充字元將是NUL(‘\0’)(非POSIX)

NLDLY

換行輸出延時,可以取NL0(不延遲)或NL1(延遲0.1s)

CRDLY

回車延遲,取值範圍為:CR0、CR1、CR2和 CR3

TABDLY

水平製表符輸出延遲,取值範圍為:TAB0、TAB1、TAB2和TAB3

BSDLY

空格輸出延遲,可以取BS0或BS1

VTDLY

垂直製表符輸出延遲,可以取VT0或VT1

FFDLY

換頁延遲,可以取FF0或FF1

 

C_cflag支援的常量名稱如下表所示,其是結構中最重要的一個,通過對他的賦值,使用者可以設定波特率,字元大小,資料位,停止位,奇偶校驗位和硬體流控等。但是c_cflag中的成員不能直接對其初始化,需要將其通過“或”、“與”操作使用其中的某些選項。

鍵    值

說    明

CBAUD

波特率(4+1位)(非POSIX)

CBAUDEX

附加波特率(1位)(非POSIX)

CSIZE

字元長度,取值範圍為CS5、CS6、CS7或CS8

CSTOPB

設定兩個停止位

CREAD

使用接收器

PARENB

使用奇偶校驗

PARODD

對輸入使用奇偶校驗,對輸出使用偶校驗

HUPCL

關閉裝置時掛起

CLOCAL

忽略調變解調器線路狀態

CRTSCTS

使用RTS/CTS流控制

 

C_lflag支援的常量名稱如下表所示,其用於控制終端的本地資料處理和工作模式。

鍵    值

說    明

ISIG

當輸入INTR、QUIT、SUSP或DSUSP時,產生相應的訊號

ICANON

使用標準輸入模式

XCASE

在ICANON和XCASE同時設定的情況下,終端只使用大寫。如果只設定了XCASE,則輸入字元將被轉換為小寫字元,除非字元使用了轉義字元(非POSIX,且Linux不支援該引數)

ECHO

顯示輸入字元

ECHOE

如果ICANON同時設定,ERASE將刪除輸入的字元,WERASE將刪除輸入的單詞

ECHOK

如果ICANON同時設定,KILL將刪除當前行

ECHONL

如果ICANON同時設定,即使ECHO沒有設定依然顯示換行符

ECHOPRT

如果ECHO和ICANON同時設定,將刪除列印出的字元(非POSIX)

TOSTOP

向後臺輸出傳送SIGTTOU訊號

 

C_cc[nccs]支援的常量名稱如下表所示

巨集

說    明

巨集

說    明

VINTR

Interrupt字元 ,對應鍵 “ctrl+c”

VEOL

附加的End-of-file字元

VQUIT

Quit字元 ,”ctrl+z”

VTIME

非規範模式讀取時的超時時間

VERASE

Erase字元 , “backspace”

VSTOP

Stop字元

VKILL

Kill字元 ,”ctrl+u”

VSTART

Start字元

VEOF

End-of-file字元 ,”ctrl+d”

VSUSP

Suspend字元

VMIN

非規範模式讀取時的最小字元數

操作串列埠的控制函式

函式名

說明

Tcgetattr

獲取屬性(termios結構)

Tcsetattr

設定屬性(termios結構)

Tcsendbreak

傳送break字元

Tcdrain

等待所有輸出都被傳輸(使程式阻塞,直到輸出緩衝區中的資料全部傳送完畢)

tcflow

掛起傳輸或接收(用於暫停或重新開始輸出)

Tcflush

用於清空輸入/輸出緩衝區

cfmakeraw

設定串列埠模式為原始模式

Cfgetispeed

獲取輸入波特率

Cfgetospeed

獲取輸出波特率

Cfsetispeed

設定輸入波特率

Cfsetospeed

設定輸出波特率

cfsetspeed

設定波特率

Linux串列埠實現-uart.c

/************************************************************************************************************* 
 * 檔名: 	  		uart.c 
 * 功能:      		Linux串列埠操作函式
 * 作者:      		oxp_edward@163.com 
 * 建立時間:    	2014年12月31日 
 * 最後修改時間:	2014年12月31日  
 * 詳細:      		無
*************************************************************************************************************/  
#include <stdio.h>         
#include <stdlib.h>        
#include <unistd.h>       
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>        
#include <termios.h>     
#include <errno.h>        
#include <strings.h>

static int SerialPortFd = 0; //儲存串列埠的檔案描述符

/*************************************************************************************************************************
*函式        :	int SerialIsValid(void)
*功能        : 檢查串列埠檔案描述符的有效性
*引數        : 無
*返回        : 0:有效
			  -1:無效
*依賴        : 無
*作者        : edward
*時間        : 20141231
*最後修改時間: 20141231
*說明        : 無
*************************************************************************************************************************/
static int SerialIsValid(void)
{
	//SerialPortFd 安全檢查
	if(0 == SerialPortFd)
	{
		perror("SerialPortFd is invalid");
		return -1;
	}
	return 0;
}
/*************************************************************************************************************************
*函式        :	int SerialIsValid(void)
*功能        : 檢查串列埠檔案描述符的有效性
*引數        : 無
*返回        : 0:有效
			  -1:無效
*依賴        : 無
*作者        : edward
*時間        : 20141231
*最後修改時間: 20141231
*說明        : 無
*************************************************************************************************************************/

int SerialOpen(const char *devicePath)
{
	SerialPortFd = open(devicePath,O_RDWR | O_NOCTTY | O_NDELAY);
	if(SerialPortFd < 0)
	{
		perror(devicePath);
		printf("%s open failed\n",devicePath);
		return -1;
	}
	return 0;
}
/*************************************************************************************************************************
*函式        :	int SerialConfig(int nSpeed,int nBits,char nEvent,int nStop)
*功能        : 串列埠引數設定
*引數        : nSpeed:波特率
			   nBits :資料位
			   nEvent:奇偶校驗位
			   nStop :停止位
*返回        : 0:設定成功
			  -1:設定失敗
*依賴        : 無
*作者        : edward
*時間        : 20141231
*最後修改時間: 20141231
*說明        : 無
*************************************************************************************************************************/

int SerialConfig(int nSpeed,int nBits,char nEvent,int nStop)
{
	int rtn = 0;
	int speed = 0;
	struct termios NewConfig,OldConfig;
	
	//SerialPortFd 安全檢查
	if(SerialIsValid())
		return -1;
	
	
	//1、儲存原串列埠配置
	/*tcgetattr(serialPortFd,&options)得到與SerialFd指向物件的相關引數,
	並將它們儲存於oldtio,該函式,還可以測試配置是否正確,該串列埠是否
	可用等。若呼叫成功,函式返回值為0,若呼叫失敗,函式返回值為1.*/
	rtn = tcgetattr(SerialPortFd,&OldConfig);
	if(0 != rtn)
	{
		perror("Error on tcgetattr");
		return -1;
	}
	
	bzero(&NewConfig,sizeof(NewConfig));
	
	//2、啟用選項
	//CLOCAL:修改控制模式,保證程式不會佔用串列埠
	//CREAD:修改控制模式,接收使能,使得能夠從串列埠中讀取輸入資料
	NewConfig.c_cflag |= CLOCAL | CREAD;
	
	//3、設定資料位
	//在設定資料位時,需要先使用CSIZE位清空資料位設定,然後再設定相應的資料位
	NewConfig.c_cflag &= ~CSIZE;
	switch(nBits)
	{
		case 8: NewConfig.c_cflag |= CS8;break;
		case 7: NewConfig.c_cflag |= CS7;break;
		case 6: NewConfig.c_cflag |= CS6;break;
		case 5: NewConfig.c_cflag |= CS5;break;
		default:
			printf("Error on nBits\n");
			return -1;
	}
	
	//4、設定奇偶校驗位
	//設定奇偶校驗需要使用到termiso中的兩個成員:c_cflag和c_iflag,
	//首先啟用c_cflag中的校驗位使能標誌PARENB,這樣會對輸出資料產生校驗位,
	//而輸入資料進行校驗檢測。同時還要啟用c_iflag中的對於輸入資料的奇偶校驗使能INPCK
	//c_cflag中的PARODD為1時使用奇校驗,為0時使用偶校驗
	switch(nEvent)
	{
		case 'o':
		case 'O':  //設定為奇校驗
			NewConfig.c_cflag |=(PARODD|PARENB);
			NewConfig.c_iflag |= INPCK;
		break;
		case 'e':
		case 'E':  //設定為偶校驗
			NewConfig.c_cflag |=PARENB;
			NewConfig.c_cflag &= ~PARODD;
			NewConfig.c_iflag |= INPCK;
		break;
		case 's':
		case 'S':	/*as no parity*/
			NewConfig.c_cflag &=~PARENB;
			NewConfig.c_cflag &= ~CSTOPB;
		break;
		case 'n':
		case 'N': //設定無奇偶校驗位
			NewConfig.c_cflag &= ~PARENB;
			NewConfig.c_iflag &= ~INPCK;
		break;
		default:	
			printf("Error on nEvent\n");
			return -1;
	}
	
	//5、設定波特率
	switch(nSpeed)
	{
		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;
		case 115200: speed = B115200; break;
		default:
			printf("Error on nSpeed\n");
			return -1;
	}
	cfsetispeed(&NewConfig,speed);
	cfsetospeed(&NewConfig,speed);
	
	//6、設定停止位
	//設定停止位是通過啟用c_cflag中的CSTOPB而實現的。若停止位為1個,這清除CSTOPB,若停止位為2個,則啟用CSTOPB
	switch(nStop)
	{
		case 1: NewConfig.c_cflag &= ~CSTOPB;break;
		case 2: NewConfig.c_cflag |=  CSTOPB;break;
		default:
			printf("Error on nStop\n");
			return;
	}
	
	//7、設定等待時間和最小接收字元
	NewConfig.c_cc[VTIME] = 0;//讀取一個字元等待1*(1/10)s
	NewConfig.c_cc[VMIN] = 0;//讀取字元的最少個數

	
	//8、清除串列埠緩衝
	//在串列埠重新設定後,需要對當前的串列埠裝置進行適當的處理,
	//這時可以呼叫tcdrain,tcflow,tcflush等函式來處理目前串列埠緩衝區中的資料
	//tcdrain 函式使程式阻塞,直到輸出緩衝區中的資料全部傳送完畢
	//tcflow  函式用於暫停或重新開始輸出
	//tcflush 函式用於清空輸入/輸出緩衝區
	
	//如果發生資料溢位,接收資料,但是不再讀取
	tcflush(SerialPortFd,TCIFLUSH);
	
	//9、啟用配置
	//在串列埠配置完成後,需要使用tcsetattr函式啟用配置。
	rtn = tcsetattr(SerialPortFd,TCSANOW,&NewConfig);
	if(0 != rtn)
	{
		perror("Error on tcsetattr");
		return -1;
	}
	
	printf("Serial Set Done\n");
	return 0;
}
/*************************************************************************************************************************
*函式        :	int SerialReceive(unsigned char *buf,int size)
*功能        :  串列埠接收資料
*引數        : buf: 接收緩衝區
			   size:緩衝區大小
*返回        :錯誤-1,返回接收資料的大小
*依賴        : 無
*作者        : edward
*時間        : 20141231
*最後修改時間: 20141231
*說明        : 無
*************************************************************************************************************************/

int SerialReceive(unsigned char *buf,int size)
{
	int rtn ;
	//SerialPortFd 安全檢查
	if(SerialIsValid())
		return -1;
	rtn = read(SerialPortFd,buf,size);
//	printf("ReadLength = %d\n",rtn);
	return rtn;
}
/*************************************************************************************************************************
*函式        : int SerialSend(unsigned char *buf,int DataLength)
*功能        : 串列埠傳送資料
*引數        : buf:傳送緩衝區
			   DataLength:需要傳送的位元組數
*返回        : rtn:成功傳送的位元組數
			  -1:傳送失敗
*依賴        : 無
*作者        : edward
*時間        : 20141231
*最後修改時間: 20141231
*說明        : 無
*************************************************************************************************************************/

int SerialSend(unsigned char *buf,int DataLength)
{
	int rtn ;
	//SerialPortFd 安全檢查
	if(SerialIsValid())
		return -1;
#if 0
	int remain = DataLength;
	int offset = 0;
	int sub = 0;
	while(remain > 0 )
	{
		sub = (remain >= 8? 8:remain);
		write(SerialPortFd,buf+offset,sub);
		tcflush(SerialPortFd,TCOFLUSH);
		remain -= 8;
		offset += 8;
	}
#else
	rtn = write(SerialPortFd,buf,DataLength);
#endif
	return rtn;	
}
/*************************************************************************************************************************
*函式        : int SerialIsValid(void)
*功能        : 關閉串列埠
*引數        : 無
*返回        : 0:關閉成功
*依賴        : 無
*作者        : edward
*時間        : 20141231
*最後修改時間: 20141231
*說明        : 無
*************************************************************************************************************************/
int SerialClose(void)
{
	//SerialPortFd 安全檢查
	if(SerialIsValid())
			return -1;
	close(SerialPortFd);
	return 0;
}


uart.h標頭檔案

#ifndef __UART_H__
#define __UART_H__
int SerialOpen(const char *devicePath);
int SerialConfig(int nSpeed,int nBits,char nEvent,int nStop);
int SerialReceive(unsigned char *buf,int size);
int SerialSend(unsigned char *buf,int DataLength);
int SerialClose(void);
#endif

uart-test測試檔案,固定傳送字元,並接收,硬體上將串列埠的RX與TX相連線

#include <stdio.h>
#include <string.h>
#include "uart.h"
int main(int argc,char**argv)
{
	int rtn;
	int Count = 0;
	unsigned char *send = "Hello Uart";
	unsigned char buf[1024] ={0};
	if(2 > argc){
		printf("Input error\n");
		printf("Usage: %s <Device>\n",argv[0]);
		printf("Eample: %s /dev/ttyS0\n",argv[0]);
		return -1;
	}
	//開啟串列埠
	rtn = SerialOpen(argv[1]);
	if(0 > rtn)
		return -1;
	//配置串列埠
	rtn = SerialConfig(115200,8,'n',1);
	if(0 > rtn)
		return -1;
	//迴圈傳送與接收
	while(1)
	{	//傳送資料
		rtn = SerialSend(send,strlen(send));
		if(0 < rtn){
			printf("[Send]: %s\n",send);
		}
		sleep(1);
		//接收資料
		rtn = SerialReceive(buf,sizeof(buf));
		if(0 < rtn){
			printf("[Recv]: %s\n",buf);
		}
	}
}







相關文章