Linux下串列埠通訊詳解(上)開啟串列埠和串列埠初始化詳解
阿新 • • 發佈:2019-02-08
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_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:命令引數
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.串列埠的初始化
串列埠初始化工作需要做以下工作:- 設定波特率
- 設定資料流控制
- 設定幀的格式(即資料位個數,停止位,校驗位)
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]:特殊控制模式
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控制修改方式,共有三種修改方式,如下所示:
- TCSANOW:立刻對值進行修改
- TCSADRAIN:等當前的輸出完成後再對值進行修改
- TCSAFLUSH:等當前的輸出完成之後,再對值進行修改,但丟棄還未從read呼叫返回的當前的可用的任何輸入。
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:只使用奇校驗而不使用偶校驗。
- BRKINT:當在輸入行中檢測到一個終止狀態時,產生一箇中斷。
- TGNBRK:忽略輸入行中的終止狀態。
- TCRNL:將接受到的回車符轉換為新行符。
- TGNCR:忽略接受到的新行符。
- INLCR:將接受到的新行符轉換為回車符。
- IGNPAR:忽略奇偶校檢錯誤的字元。
- INPCK:對接收到的字元執行奇偶校檢。
- PARMRK:對奇偶校檢錯誤作出標記。
- ISTRIP:將所有接收的字元裁減為7位元。
- IXOFF:對輸入啟用軟體流控。
- IXON:對輸出啟用軟體流控。
標準模式和非標準模式下,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字元
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
這樣,所有的初始化操作我們就完成了。
下一篇文章我會記錄串列埠的讀寫及關閉操作的詳細步驟。並且會把原始碼連結給出供大家參考!