在嵌入式Linux中,串口是一個字設備,訪問具體的串行端口的編程與讀/寫文件 的操作類似,只需打開相應的設備文件即可操作。串口編程特殊在於串 口通信時相關參數與屬性的設置,還有必須有串口驅動程序(提供設備節點),
在linux系統中一般都自帶有的串口驅動程序,我們只需要配置就可以使用串口的設置節點
配置串口驅動:
串口配置完成後,插上usb轉串口設備,使用ls -l /dev/tty* 查看設置節點, 出現一個ttyUBS0的設備節點
串口編程特殊在於串口通信時相關參數與屬性的設置
1 打開串口
打開串口設備文件的操作與普通文件的操作類似,都採用標準的I/O操作函數open()。
fd = open("/dev/ttyS0",O_RDWR|O_NDELAY|O_NOCTTY);
open()函數有兩個參數,第一個參數是要打開的文件名(此處爲串口設備文件/dev/ttyS0);第二個參數設置打開的方式,O_RDWR表示打開 的文件可讀/寫,O_NDELAY表示以非阻塞方式打開,O_NOCTTY表示若打開的文件爲終端設備,則不會將終端作爲進程控制終端。
2、設置串口屬性
串口通信時的屬性設置是串口編程的關鍵問題,許多串口通信時的錯誤都與串口的設置相關,所以編程時應特別注意這些設置,最常見的設置包括波特率、奇偶校驗和停止位以及流控制等。
在Linux中,串口被作爲終端I/O,它的參數設置需要使用struct termios結構體,這個結構體在termio.h文件中定義,且應在程序中包含這個頭文件。
typedef unsigned char cc_t ;
typedef unsigned int speed_t ;
typedef unsigned int tcflag_t ;
struct termios
{
tcflag_t c_iflag ; /*輸入模式標誌*/
tcflag_t c_oflag ; /*輸出模式標誌*/
tcflag_t c_cflag ; /*控制模式標誌*/
tcflag_t c_lflag ; /*本地模式標誌*/
tcflag_t c_line ; /*行規程類型,一般應用程序不使用*/
cc_t c_cc[NCC]; /*控制字符*/
speed_t c_ispeed ; /*輸入數據波特率*/
speed_t c_ospeed ; /*輸出數據波特率*/
};
串口的設置主要是設置這個結構體的各成員值,然後利用該結構體將參數傳給硬件驅動程序。在Linux中,串口以串行終端的方式進行處理,因而,可以使用tcgetattr()/tcsetattr()函數獲取/設置串口的參數。
int tcgetattr( int fd, struct termios *termios_p );
int tcsetattr( int fd, int optional_actions , struct termios *termios_p );
這兩個參數都有一個批向termios結構體的指針作爲參數,用於返回當前終端的屬性或設置該終端的屬性。參數fd就是用open()函數打開的終端文件 句柄,而串口就是用open()打開的串口設備文件句柄。tcsetattr()函數的optional_action參數用於指定新設定的參數起作用的 時間,其設定值可以爲:
TCSANOW 改變立即生效
TCSADRAIN 在所有的輸出都被傳輸後改變生效,適用於更改影響輸出參數的情況。
TCSAFLUSH 在所有輸出都被傳輸後改變生效,丟棄所有末讀入的輸入(清空輸入緩存)。
(1)設置波特率
使用cfsetospeed()/cfsetispeed()函數設置波特率,它們分別用於在termios結構體中設置輸出和輸入的波特率。設置波特率 可以使用波特率常數,其定義爲字母“B+速率”,如B19200就是波特率爲19200bps,B115200就是波特率爲115200bps。
int cfsetispeed( struct termios *termios_p, speed_t speed ); //speed爲波特率常數
int cfsetospeed( struct termios *termios_p, speed_t speed );
例 :
cfsetispeed( ttys0_opt, B115200 );
cfsetospeed( ttys0_opt, B115200 );
(2)設置控制模式標誌
控制模式標誌c_cflag主要用於設置串口對DCD信號狀態檢測、硬件流控制、字符位寬、停止位和奇偶校驗等,常用標誌位如下:
CLOCAL 忽略DCD信號,若不使用MODEM,或沒有串口沒有CD腳就設置此標誌
CREAD 啓用接收裝置,可以接收字符
CRTSCTS啓用硬件流控制,對於許多三線制的串不應使用,需設置~CRTCTS
CSIZE 字符位數掩碼,常用CS8
CSTOPB 使用兩個停止位,若用一位應設置~CSTOPB
PARENB 啓用奇偶校驗
例如,下面的代碼將串口設置爲忽略DCD信號,啓用接收裝置,關閉硬件流控制,傳輸數據時使用8位數據位和一位停止位(8N1),不使用奇偶校驗。
struct temios ttys0
ttyso_opt.c_cflag |= CLOCAL | CREAD ; //將CLOCAL與CREAD位設置爲1
ttys0_opt.c_cflag &= ~CRTSCTS ; //將硬件流控制位CRTSCTS清0,其他位不變
ttys0_opt.c_cflag &= ~CSIZE ; //清除數據位掩碼
ttys0_opt.c_cflag |= CS8 ; //設置8位數據位標誌CS8
ttys0_opt.c_cflag &= ~(PARENB|CSTOPB);//使用1位停止位,停用奇偶校驗
(3)設置本地模式標誌
本地模式標誌c_lflag主要用於設置終端與用戶的交互方式,常見的設置標誌位有ICAN-ON,ECHO和ECHOE等。其中,ICANON標誌位用 於實現規範輸入,即read()讀到行結束符後返回,常用於終端的處理;若串口用於發送/接收數據,則應清除此標誌,使用非規範模式(raw mode)。非規範模式中,輸入數據不組成行,不處規範模式中的特殊字符。在規範模式中,當設置ECHO標誌位時,用戶向終端輸入的字符將被回傳給用戶; 當設置ECHOE標誌位時,用戶輸入退格鍵時,則回傳“退格-空格-退格”序列給用戶,使得退格鍵覆蓋的字符從顯示中消失,這樣更符合用戶的習慣(若未設 置此標誌,輸入退格鍵時,則光標回退一個字符,但原有的字符未從顯示中消失)。
(4)設置輸入模式標誌
輸入模式標誌c_iflag主要用於控制串口的輸入特性,常用的設置有IXOFF和IXON,分別用於軟件流控制。其中,IXOFF用於防止輸入緩衝區溢 出;IXON則是在輸入數據中識別軟件流控制標誌。由於許多嵌入式系統無法使用硬件流控制,因此,只能使用軟件流控制數據傳輸的速度,但是,它可能降低串 口數據傳輸效率。啓用軟件流控制的代碼如下:
ttys0_opt.c_iflag |= IXOFF|IXON ;
(5)設置輸出模式標誌
輸出模式標誌c_oflag主要用於對串口在規範模式時輸出的特殊字符處理,而對非規範模式無效。
(6)設置控制字符
在非規範模式中,控制字符數組c_cc[]中的變量c_cc[VMIN]和c_cc[VTIME]用於設置read()返回前讀到的最少字節數和讀超時時間,其值分爲四種情況:
(a)c_cc[VMIN]>0,c_cc[VTIME]>0
讀到一個字節後,啓動定時器,其超時時間爲c_cc[VTIME],read()返回的條件爲至少讀到c_cc[VMIN]個字符或定時器超期。
(b)c_cc[VMIN]>0, c_cc[VTIME] ==0
只要讀到數據的字節數大於等於c_cc[VMIN],則read()返回;否則,將無限期阻塞等待。
(c)c_cc[VMIN] == 0, c_cc[VTIME]>0
只要讀到數據,則read()返回;若定時器超期(定時時間c_cc[VTIME])卻未讀到數據,則read()返回0;
(d)c_cc[VMIN] == 0, c_cc[VTIME] == 0
若有數據,則read()讀取指定數量的數據後返回;若沒有數據,則read()返回0;
在termios結構體中填寫完這些參數後,接下來就可以使用tcsetattr()函數設置串口的屬性。
tcsetattr( fd, &old_opt ); //將原有的設置保存到old_opt,以便程序結束後恢復
tcsetattr( fd, TCSANOW, &ttsy0_opt );
3、清空發送/接收緩衝區
爲保證讀/寫操作不被串口緩衝區中原有的數據幹攏,可以在讀/寫數據前用tcflush()函數清空串口發送/接收緩衝區。tcflush()函數的參數可爲:
TCIFLUSH 清空輸入隊列
TCOFLUSH 清空輸出隊列
TCIOFLUSH 同時清空輸入和輸出隊列
4、從串口讀寫數據
串口的數據讀/寫與普通文件的讀/寫一樣,都是使用read()/write()函數實現。
n = write( fd, buf, len ); //將buf中len個字節的數據從串口輸出,返回輸出的字節數
n = read( fd, buf, len ); //從串口讀入len個字節的數據並放入buf, 返回讀取的字節數
5、關閉串口
關閉串口的操作很簡單,將打開的串口設備文件句柄關閉即可。
close(fd);
打開文件代碼例子:
int open_port(char *com_port)
{
int fd;
/*打開串口*/
fd=open(com_port,O_RDWR|O_NOCTTY|O_NDELAY);
if(fd<0)
{
perror("open\n");
return -1;
}
/*恢復串口的阻塞狀態*/
if(fcntl(fd,F_SETFL,0)<0){
perror("fcntl\n");
}
/*判斷是否爲終端設備*/
if(isatty(fd)==0)
{
perror("this is not a terminal device\n");
}
return fd;
}
串口設置的方式的方式有2種
第一種方式:需要自己設置
//用於設置參數(可以理解爲的串口的初始化)
void set_com_config(int fd,int baud_rate,int date_bits,char parity,int stop_bits)
{
struct termios new_cfg,old_cfg;
int speed;
//linux還提供了tcgetattr函數和tcsetattr函數。tcgetattr用於獲取終端的相關參數,而tcsetattr函數用於設置終端參數。
/*保存原有的串口的數據*/
if(tcgetattr(fd,&old_cfg)!=0)
{
perror("tcgetattr");
return ;
}
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(date_bits)
{
case 7:{
new_cfg.c_cflag|=CS7;
break;
}
case 8:{
new_cfg.c_cflag|=CS8;
}
}
/*設置奇偶校驗位*/
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|=(PARENB|PARODD);//偶數校驗
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;
}
}
/*設置停止位*/
switch(stop_bits)
{
default:
case 1:{
new_cfg.c_cflag&=~CSTOPB;
break;
}
case 2:{
new_cfg.c_cflag|=CSTOPB;
break;
}
}
/*設置控制字符*/
//用於設置read()返回前讀到的最少字節數和讀超時時間,
/*設置等待時間和最小接收字符*/
new_cfg.c_cc[VTIME]=0;
new_cfg.c_cc[VMIN]=1;
// 只要讀到數據,則read()返回;若定時器超期(定時時間c_cc[VTIME])卻未讀到數據,則read()返回0;
//設置串口的屬性
tcflush(fd,TCIFLUSH);//TCIFLUSH 清空緩衝區
//用於設置串口的參數
if((tcsetattr(fd,TCSANOW,&new_cfg))!=0)
{
perror("tcsetattr");
return ;
}
}
第二種方式:串口的配置已經寫死
void serial_init(int fd)
{
struct termios options;
tcgetattr(fd, &options);
options.c_cflag |= ( CLOCAL | CREAD );
options.c_cflag &= ~CSIZE;
options.c_cflag &= ~CRTSCTS;
options.c_cflag |= CS8;
options.c_cflag &= ~CSTOPB;
options.c_iflag |= IGNPAR;
options.c_iflag &= ~(ICRNL | IXON);
options.c_oflag = 0;
options.c_lflag = 0;
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
tcsetattr(fd,TCSANOW,&options);
}