Linux串口編程教程(三)——串口編程詳解
前言:本章將正式講解串口編程技術,利用一個串口收發資料的程式,來分步講解。
注意:您可以下載我的原始碼進行參考。 開啟串口
大家都知道,在Linux系統中裝置被以作檔案形式存在,所以我們以開啟檔案的方式訪問裝置。這裡要注意的是普通使用者一般不能直接存取裝置,需要root許可權。
有3個方法可以解決這個問題: 以root超級使用者的身份運行。(常用) 改變裝置檔案的存取權限。 在程式中使用setuid,以串口裝置所有者的身份運行程式。
代碼:
#include<stdio.h>#include<fcntl.h>#include<assert.h>static int fd;int uart_open(int fd,const char *pathname){ assert(pathname); /*開啟串口*/ fd = open(pathname,O_RDWR|O_NOCTTY|O_NDELAY); if(fd == -1) { perror("Open UART failed!"); return -1; } /*清除串口非阻塞標誌*/ if(fcntl(fd,F_SETFL,0) < 0) { fprintf(stderr,"fcntl failed!\n"); return -1; } return fd;}
說明: O_NOCTTY:表示開啟的是一個終端裝置,程式不會成為該連接埠的控制終端。如果不使用此標誌,鍵盤上過來的Ctrl+C中止訊號等都將影響進程。 O_NDELAY:表示不關心DCD訊號線所處的狀態(連接埠的另一端是否啟用或者停止)。 關閉串口
關閉串口操作很簡單,不過需要注意在關閉後是不是需要做一些清理操作。
代碼:
#include<unistd.h>#include<assert.h>static int fd;int uart_close(int fd){ assert(fd); close(fd); /*可以在這裡做些清理工作*/ return 0;}
配置串口
串口初始化需要設定串口傳輸速率,資料流控制,幀的格式(即資料位元個數,停止位,校正位,資料流控制)。
代碼:
#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include<assert.h>#include<termios.h>#include<string.h>static int ret;static int fd;int uart_set(int fd,int baude,int c_flow,int bits,char parity,int stop){ struct termios options; /*擷取終端屬性*/ if(tcgetattr(fd,&options) < 0) { perror("tcgetattr error"); return -1; } /*設定輸入輸出傳輸速率,兩者保持一致*/ switch(baude) { case 4800: cfsetispeed(&options,B4800); cfsetospeed(&options,B4800); break; case 9600: cfsetispeed(&options,B9600); cfsetospeed(&options,B9600); break; case 19200: cfsetispeed(&options,B19200); cfsetospeed(&options,B19200); break; case 38400: cfsetispeed(&options,B38400); cfsetospeed(&options,B38400); break; default: fprintf(stderr,"Unkown baude!\n"); return -1; } /*設定控制模式*/ options.c_cflag |= CLOCAL;//保證程式不佔用串口 options.c_cflag |= CREAD;//保證程式可以從串口中讀取資料 /*設定資料流控制*/ switch(c_flow) { case 0://不進行流量控制 options.c_cflag &= ~CRTSCTS; break; case 1://進行硬體流量控制 options.c_cflag |= CRTSCTS; break; case 2://進行軟體流量控制 options.c_cflag |= IXON|IXOFF|IXANY; break; default: fprintf(stderr,"Unkown c_flow!\n"); return -1; } /*設定資料位元*/ switch(bits) { case 5: options.c_cflag &= ~CSIZE;//屏蔽其它標誌位 options.c_cflag |= CS5; break; case 6: options.c_cflag &= ~CSIZE;//屏蔽其它標誌位 options.c_cflag |= CS6; break; case 7: options.c_cflag &= ~CSIZE;//屏蔽其它標誌位 options.c_cflag |= CS7; break; case 8: options.c_cflag &= ~CSIZE;//屏蔽其它標誌位 options.c_cflag |= CS8; break; default: fprintf(stderr,"Unkown bits!\n"); return -1; } /*設定校正位*/ switch(parity) { /*無同位位元*/ case 'n': case 'N': options.c_cflag &= ~PARENB;//PARENB:產生奇偶位,執行同位 options.c_cflag &= ~INPCK;//INPCK:使同位起作用 break; /*設為空白格,即停止位為2位*/ case 's': case 'S': options.c_cflag &= ~PARENB;//PARENB:產生奇偶位,執行同位 options.c_cflag &= ~CSTOPB;//CSTOPB:使用兩位停止位 break; /*設定奇數同位*/ case 'o': case 'O': options.c_cflag |= PARENB;//PARENB:產生奇偶位,執行同位 options.c_cflag |= PARODD;//PARODD:若設定則為奇數同位,否則為偶校正 options.c_cflag |= INPCK;//INPCK:使同位起作用 options.c_cflag |= ISTRIP;//ISTRIP:若設定則有效輸入數字被剝離7個位元組,否則保留全部8位 break; /*設定偶校正*/ case 'e': case 'E': options.c_cflag |= PARENB;//PARENB:產生奇偶位,執行同位 options.c_cflag &= ~PARODD;//PARODD:若設定則為奇數同位,否則為偶校正 options.c_cflag |= INPCK;//INPCK:使同位起作用 options.c_cflag |= ISTRIP;//ISTRIP:若設定則有效輸入數字被剝離7個位元組,否則保留全部8位 break; default: fprintf(stderr,"Unkown parity!\n"); return -1; } /*設定停止位*/ switch(stop) { case 1: options.c_cflag &= ~CSTOPB;//CSTOPB:使用兩位停止位 break; case 2: options.c_cflag |= CSTOPB;//CSTOPB:使用兩位停止位 break; default: fprintf(stderr,"Unkown stop!\n"); return -1; } /*設定輸出模式為原始輸出*/ options.c_oflag &= ~OPOST;//OPOST:若設定則按定義的輸出處理,否則所有c_oflag失效 /*設定本地模式為原始模式*/ options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /* *ICANON:允許規範模式進行輸入處理 *ECHO:允許輸入字元的本機回應 *ECHOE:在接收EPASE時執行Backspace,Space,Backspace組合 *ISIG:允許訊號 */ /*設定等待時間和最小接受字元*/ options.c_cc[VTIME] = 0;//可以在select中設定 options.c_cc[VMIN] = 1;//最少讀取一個字元 /*如果發生資料溢出,只接受資料,但是不進行讀操作*/ tcflush(fd,TCIFLUSH); /*啟用配置*/ if(tcsetattr(fd,TCSANOW,&options) < 0) { perror("tcsetattr failed"); return -1; } return 0;}
讀寫串口
代碼:
#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include<assert.h>#include<termios.h>#include<string.h>#include<sys/time.h>#include<sys/types.h>#include<errno.h>static int ret;static int fd;/* * 安全讀寫函數 */ssize_t safe_write(int fd, const void *vptr, size_t n){ size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while(nleft > 0) { if((nwritten = write(fd, ptr, nleft)) <= 0) { if(nwritten < 0&&errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return(n);}ssize_t safe_read(int fd,void *vptr,size_t n){ size_t nleft; ssize_t nread; char *ptr; ptr=vptr; nleft=n; while(nleft > 0) { if((nread = read(fd,ptr,nleft)) < 0) { if(errno == EINTR)//被訊號中斷 nread = 0; else return -1; } else if(nread == 0) break; nleft -= nread; ptr += nread; } return (n-nleft);}int uart_read(int fd,char *r_buf,size_t len){ ssize_t cnt = 0; fd_set rfds; struct timeval time; /*將檔案描述符加入讀描述符集合*/ FD_ZERO(&rfds); FD_SET(fd,&rfds); /*設定逾時為15s*/ time.tv_sec = 15; time.tv_usec = 0; /*實現串口的多路I/O*/ ret = select(fd+1,&rfds,NULL,NULL,&time); switch(ret) { case -1: fprintf(stderr,"select error!\n"); return -1; case 0: fprintf(stderr,"time over!\n"); return -1; default: cnt = safe_read(fd,r_buf,len); if(cnt == -1) { fprintf(stderr,"read error!\n"); return -1; } return cnt; }}int uart_write(int fd,const char *w_buf,size_t len){ ssize_t cnt = 0; cnt = safe_write(fd,w_buf,len); if(cnt == -1) { fprintf(stderr,"write error!\n"); return -1; } return cnt;}
完整程式
具體讀寫命令請讀者自行編寫。
/************************************************************************* > File Name: uart_operation.c > Author: AnSwEr > Mail: 1045837697@qq.com > Created Time: 2015年09月02日 星期三 12時45分48秒 ************************************************************************/#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include<assert.h>#include<termios.h>#include<string.h>#include<sys/time.h>#include<sys/types.h>#include<errno.h>static int ret;static int fd;/* * 安全讀寫函數 */ssize_t safe_write(int fd, const void *vptr, size_t n){ size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while(nleft > 0) { if((nwritten = write(fd, ptr, nleft)) <= 0) { if(nwritten < 0&&errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return(n);}ssize_t safe_read(int fd,void *vptr,size_t n){ size_t nleft; ssize_t nread; char *ptr; ptr=vptr; nleft=n; while(nleft > 0) { if((nread = read(fd,ptr,nleft)) < 0) { if(errno == EINTR)//被訊號中斷 nread = 0; else return -1; } else if(nread == 0) break; nleft -= nread; ptr += nread; } return (n-nleft);}int uart_open(int fd,const char *pathname){ assert(pathname); /*開啟串口*/ fd = open(pathname,O_RDWR|O_NOCTTY|O_NDELAY); if(fd == -1) { perror("Open UART failed!"); return -1; } /*清除串口非阻塞標誌*/ if(fcntl(fd,F_SETFL,0) < 0) { fprintf(stderr,"fcntl failed!\n"); return -1; } return fd;}int uart_set(int fd,int baude,int c_flow,int bits,char parity,int stop){ struct termios options; /*擷取終端屬性*/ if(tcgetattr(fd,&options) < 0) { perror("tcgetattr error"); return -1; } /*設定輸入輸出傳輸速率,兩者保持一致*/ switch(baude) { case 4800: cfsetispeed(&options,B4800); cfsetospeed(&options,B4800); break; case 9600: cfsetispeed(&options,B9600); cfsetospeed(&options,B9600); break; case 19200: cfsetispeed(&options,B19200); cfsetospeed(&options,B19200); break; case 38400: cfsetispeed(&options,B38400); cfsetospeed(&options,B38400); break; default: fprintf(stderr,"Unkown baude!\n"); return -1; } /*設定控制模式*/ options.c_cflag |= CLOCAL;//保證程式不佔用串口 options.c_cflag |= CREAD;//保證程式可以從串口中讀取資料 /*設定資料流控制*/ switch(c_flow) { case 0://不進行流量控制 options.c_cflag &= ~CRTSCTS; break; case 1://進行硬體流量控制 options.c_cflag |= CRTSCTS; break; case 2://進行軟體流量控制 options.c_cflag |= IXON|IXOFF|IXANY; break; default: fprintf(stderr,"Unkown c_flow!\n"); return -1; } /*設定資料位元*/ switch(bits) { case 5: options.c_cflag &= ~CSIZE;//屏蔽其它標誌位 options.c_cflag |= CS5; break; case 6: options.c_cflag &= ~CSIZE;//屏蔽其它標誌位 options.c_cflag |= CS6; break; case 7: options.c_cflag &= ~CSIZE;//屏蔽其它標誌位 options.c_cflag |= CS7; break; case 8: options.c_cflag &= ~CSIZE;//屏蔽其它標誌位 options.c_cflag |= CS8; break; default: fprintf(stderr,"Unkown bits!\n"); return -1; } /*設定校正位*/ switch(parity) { /*無同位位元*/ case 'n': case 'N': options.c_cflag &= ~PARENB;//PARENB:產生奇偶位,執行同位 options.c_cflag &= ~INPCK;//INPCK:使同位起作用 break; /*設為空白格,即停止位為2位*/ case 's': case 'S': options.c_cflag &= ~PARENB;//PARENB:產生奇偶位,執行同位 options.c_cflag &= ~CSTOPB;//CSTOPB:使用兩位停止位 break; /*設定奇數同位*/ case 'o': case 'O': options.c_cflag |= PARENB;//PARENB:產生奇偶位,執行同位 options.c_cflag |= PARODD;//PARODD:若設定則為奇數同位,否則為偶校正 options.c_cflag |= INPCK;//INPCK:使同位起作用 options.c_cflag |= ISTRIP;//ISTRIP:若設定則有效輸入數字被剝離7個位元組,否則保留全部8位 break; /*設定偶校正*/ case 'e': case 'E': options.c_cflag |= PARENB;//PARENB:產生奇偶位,執行同位 options.c_cflag &= ~PARODD;//PARODD:若設定則為奇數同位,否則為偶校正 options.c_cflag |= INPCK;//INPCK:使同位起作用 options.c_cflag |= ISTRIP;//ISTRIP:若設定則有效輸入數字被剝離7個位元組,否則保留全部8位 break; default: fprintf(stderr,"Unkown parity!\n"); return -1; } /*設定停止位*/ switch(stop) { case 1: options.c_cflag &= ~CSTOPB;//CSTOPB:使用兩位停止位 break; case 2: options.c_cflag |= CSTOPB;//CSTOPB:使用兩位停止位 break; default: fprintf(stderr,"Unkown stop!\n"); return -1; } /*設定輸出模式為原始輸出*/ options.c_oflag &= ~OPOST;//OPOST:若設定則按定義的輸出處理,否則所有c_oflag失效 /*設定本地模式為原始模式*/ options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /* *ICANON:允許規範模式進行輸入處理 *ECHO:允許輸入字元的本機回應 *ECHOE:在接收EPASE時執行Backspace,Space,Backspace組合 *ISIG:允許訊號 */ /*設定等待時間和最小接受字元*/ options.c_cc[VTIME] = 0;//可以在select中設定 options.c_cc[VMIN] = 1;//最少讀取一個字元 /*如果發生資料溢出,只接受資料,但是不進行讀操作*/ tcflush(fd,TCIFLUSH); /*啟用配置*/ if(tcsetattr(fd,TCSANOW,&options) < 0) { perror("tcsetattr failed"); return -1; } return 0;}int uart_read(int fd,char *r_buf,size_t len){ ssize_t cnt = 0; fd_set rfds; struct timeval time; /*將檔案描述符加入讀描述符集合*/ FD_