在 Linux 下串口文件是位于 /dev 下的
打开串口是通过使用标准的文件打开函数open操作:
#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> static int fd; int uart_open(int fd,const char *pathname) { assert(pathname); fd = open(pathname,O_RDWR|O_NOCTTY| O_NONBLOCK); if(fd == -1) { perror("Open UART failed!"); return -1; } return fd; }
其中:
O_NOCTTY | 如果路径名指向终端设备,不要把这个设备用作控制终端 |
O_NONBLOCK | 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode) |
对于串口的打开操作,必须使用O_NOCTTY参数,它表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务的一个输入都会影响进程。如键盘上过来的Ctrl+C中止信号等都将影响进程。
设置串口
串口初始化需要设置串口波特率,数据流控制,帧的格式(即数据位个数,停止位,校验位,数据流控制)。
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; }
读写串口
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; }
关闭串口
关闭串口就是关闭文件。
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> #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_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; } int uart_close(int fd) { assert(fd); close(fd); /*可以在这里做些清理工作*/ return 0; } int main(void) { const char *w_buf = "something to write"; size_t w_len = sizeof(w_buf); char r_buf[1024]; bzero(r_buf,1024); fd = uart_open(fd,"/dev/ttyS0");/*串口号/dev/ttySn,USB口号/dev/ttyUSBn*/ if(fd == -1) { fprintf(stderr,"uart_open error\n"); exit(EXIT_FAILURE); } if(uart_set(fd,9600,0,8,'N',1) == -1) { fprintf(stderr,"uart set failed!\n"); exit(EXIT_FAILURE); } ret = uart_write(fd,w_buf,w_len); if(ret == -1) { fprintf(stderr,"uart write failed!\n"); exit(EXIT_FAILURE); } while(1) { ret = uart_read(fd,r_buf,1024); if(ret == -1) { fprintf(stderr,"uart read failed!\n"); exit(EXIT_FAILURE); } } ret = uart_close(fd); if(ret == -1) { fprintf(stderr,"uart_close error\n"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }