前言:之前一直觉得串口编程很简单,这两天仔细研究后发现串口里的各种参数还挺复杂,稍不注意就容易出错,这里总结一下网上的各种文章及自己的理解与实践。

open 函数

功能描述:用于打开或创建文件,成功则返回文件描述符,否则返回-1,open返回的文件描述符一定是最小的未被使用的描述符

#include<fcntl.h> int open(const char * pathname,int flags); int open(const char * pathname,int flags,mode_t mode);

嵌入式进阶教程分门别类整理好了,看的时候十分方便,由于内容较多,这里就截取一部分图吧。

linux 串口阻塞(Linux串口编程详解阻塞模式)(1)

需要的朋友私信【内核】即可领取。

内核学习地址:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂

参数flags:一些文件模式选择,有如下几个参数可以设置:

上面三个参数在设置的时候必须选择其中一个!下面的参数是可选的:

下面三个常量同样是选用的,他们用于同步输入输出:

对于串口的打开操作,必须使用O_NOCTTY参数,它表示打开的是一个终端设备,程序不会成为该端口的控制终端。如果不使用此标志,任务的一个输入(比如键盘终止信号等)都会影响进程。

open函数第三个参数mode为可选参数,表示打开文件权限

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);

fcntl()函数主要有5种功能:

F_SETFL :设置文件状态标志。其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY 和 O_TRUNC不受影响,

能更改的标志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。此函数功能强大,在此不做详细叙述!

串口参数初始化

串口参数由结构体termio设置:

struct termio { unsigned short c_iflag; /* 输入模式标志 */ unsigned short c_oflag; /* 输出模式标志 */ unsigned short c_cflag; /* 控制模式标志*/ unsigned short c_lflag; /* local mode flags */ unsigned char c_line; /* line discipline */ unsigned char c_cc[NCC]; /* control characters */ };

这里列出常见配置:

int set_port_attr(int fd,int baudrate, int databit, const char *stopbit, char parity, int vtime,int vmin ) { struct termios opt; tcgetattr(fd, &opt); //获取初始设置 set_baudrate(&opt, baudrate); set_data_bit(&opt, databit); set_parity(&opt, parity); set_stopbit(&opt, stopbit); opt.c_cflag &= ~CRTSCTS; // 不使用硬件流控制 opt.c_cflag |= CLOCAL | CREAD; //CLOCAL--忽略 modem 控制线,本地连线, 不具数据机控制功能, CREAD--使能接收标志 /* IXON--启用输出的 XON/XOFF 流控制 IXOFF--启用输入的 XON/XOFF 流控制 IXANY--允许任何字符来重新开始输出 IGNCR--忽略输入中的回车 */ opt.c_iflag &= ~(IXON | IXOFF | IXANY); opt.c_oflag &= ~OPOST; //启用输出处理 /* ICANON--启用标准模式 (canonical mode)。允许使用特殊字符 EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和 WERASE,以及按行的缓冲。 ECHO--回显输入字符 ECHOE--如果同时设置了 ICANON,字符 ERASE 擦除前一个输入字符,WERASE 擦除前一个词 ISIG--当接受到字符 INTR, QUIT, SUSP, 或 DSUSP 时,产生相应的信号 */ opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); opt.c_cc[VMIN] = vmin; //设置非规范模式下的超时时长和最小字符数:阻塞模式起作用 opt.c_cc[VTIME] = vtime; //VTIME与VMIN配合使用,是指限定的传输或等待的最长时间 单位:0.1S tcflush (fd, TCIFLUSH); /* TCIFLUSH-- update the options and do it NOW */ return (tcsetattr (fd, TCSANOW, &opt)); /* TCSANOW--改变立即发生 */ } static void set_baudrate(struct termios *opt, unsigned int baudrate) { cfsetispeed(opt, baudrate); cfsetospeed(opt, baudrate); } static void set_stopbit(struct termios *opt, const char *stopbit) { if (0 == strcmp (stopbit, "1")) { opt->c_cflag &= ~CSTOPB; /* 1位停止位t */ }else if (0 == strcmp (stopbit, "1.5")) { opt->c_cflag &= ~CSTOPB; /* 1.5位停止位 */ }else if (0 == strcmp (stopbit, "2")) { opt->c_cflag |= CSTOPB; /* 2 位停止位 */ }else { opt->c_cflag &= ~CSTOPB; /* 1 位停止位 */ } } static void set_data_bit(struct termios *opt, unsigned int databit) { opt->c_cflag &= ~CSIZE; switch (databit) { case 8: opt->c_cflag |= CS8; break; case 7: opt->c_cflag |= CS7; break; case 6: opt->c_cflag |= CS6; break; case 5: opt->c_cflag |= CS5; break; default: opt->c_cflag |= CS8; break; } } static void set_parity(struct termios *opt, char parity) { switch (parity) { case 'N': /* 无校验 */ case 'n': opt->c_cflag &= ~PARENB; break; case 'E': /* 偶校验 */ case 'e': opt->c_cflag |= PARENB; opt->c_cflag &= ~PARODD; break; case 'O': /* 奇校验 */ case 'o': opt->c_cflag |= PARENB; opt->c_cflag |= ~PARODD; break; case 'S': case 's': opt->c_cflag &= ~PARENB; /*清除校验位 disable pairty checking Space校验 */ opt->c_cflag &= ~CSTOPB; opt->c_iflag |= INPCK; break; default: /* 其它选择为无校验 */ opt->c_cflag &= ~PARENB; break; } }

阻塞式读写配置

打开时使用: fd = open(USAR1, O_RDWR | O_NOCTTY );//阻塞式读写 打开后使用fcntl函数修改: fcntl(fd, F_SETFL, 0); //设为阻塞

阻塞式读写可设置以下两参数:

opt.c_cc[VMIN] = vmin; //设置非规范模式下的超时时长和最小字符数:阻塞模式起作用opt.c_cc[VTIME] = vtime; //VTIME与VMIN配合使用,是指限定的传输或等待的最长时间

非阻塞式读写配置

打开时使用

打开后使用fcntl修改设置

fcntl(socket_descriptor, F_SETFL, flags | O_NONBLOCK); //设为非阻塞

O_NONBLOCK和O_NDELAY都是设置为非阻塞模式,但是它们有些差别,O_NDELAY会使I/O函式马上返回0,但是又衍生出一个问题,因为读取到档案结尾时所回传的也是0,这样无法得知是哪中情况;而O_NONBLOCK它在读取不到数据时会回传-1,并且设置errno为EAGAIN。

select函数读写

int select(int nfds, fd_set *rdfds, fd_set *wtfds, fd_set *exfds, struct timeval *timeout)

配置函数:

select函数非常强大,它能同时监测多个对象,只要在注册的对象集中有一个或多个对象被激活就会有反应,所以利用select函数能在一个线程中处理多个等待式操作,这里以多串口阻塞读取为例:

#include <string.h> #include <stdio.h> #include <assert.h> #include <sys/time.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <termios.h> #define USAR1 "/dev/ttyS1" #define USAR2 "/dev/ttyS2" #define USAR3 "/dev/ttyS3" #define USAR4 "/dev/ttyS4" char buf[255] = {0}; int set_port_attr (int fd,int baudrate, int databit, const char *stopbit, char parity, int vtime,int vmin ); static void set_baudrate (struct termios *opt, unsigned int baudrate); static void set_data_bit (struct termios *opt, unsigned int databit); static void set_stopbit (struct termios *opt, const char *stopbit); static void set_parity (struct termios *opt, char parity); void main() { int fd1,fd2,fd3,fd4; int ret; int rxlen = 0; fd_set rfds; struct timeval tv; int retval; fd1 = open(USAR1, O_RDWR | O_NOCTTY );//阻塞式读写 非阻塞| O_NDELAY | O_NONBLOCK if (fd1 < 0) { perror("open uart1 error\n"); } fd2 = open(USAR2, O_RDWR | O_NOCTTY );//阻塞式读写 非阻塞| O_NDELAY | O_NONBLOCK if (fd2 < 0) { perror("open uart2 error\n"); } fd3 = open(USAR3, O_RDWR | O_NOCTTY );//阻塞式读写 非阻塞| O_NDELAY | O_NONBLOCK if (fd3 < 0) { perror("open uart3 error\n"); } fd4 = open(USAR4, O_RDWR | O_NOCTTY );//阻塞式读写 非阻塞| O_NDELAY | O_NONBLOCK if (fd4 < 0) { perror("open uart4 error\n"); } ret = set_port_attr(fd1, B115200, 8, "1", 'N',1,255 ); /* 115200 8n1 */ if(ret < 0) { printf("set uart1 arrt faile \n"); exit(-1); } ret = set_port_attr(fd2, B115200, 8, "1", 'N',1,255 ); /* 115200 8n1 */ if(ret < 0) { printf("set uart2 arrt faile \n"); exit(-1); } ret = set_port_attr(fd3, B115200, 8, "1", 'N',1,255 ); /* 115200 8n1 */ if(ret < 0) { printf("set uart3 arrt faile \n"); exit(-1); } ret = set_port_attr(fd4, B115200, 8, "1", 'N',1,255 ); /* 115200 8n1 */ if(ret < 0) { printf("set uart4 arrt faile \n"); exit(-1); } while(1) { FD_ZERO(&rfds); FD_SET(fd1, &rfds); FD_SET(fd2, &rfds); FD_SET(fd3, &rfds); FD_SET(fd4, &rfds); // tv.tv_sec = 1; //in block mode is not used // tv.tv_usec = 0; ret = select(fd4 1, &rfds, NULL, NULL, NULL); //block mode if(ret > 0) { if(FD_ISSET(fd1,&rfds)) { rxlen = read(fd1, buf, 255); if(rxlen > 0) { printf("len = %d\n\r",rxlen); printf("rx:%s\n\r",buf); write(fd1, buf, rxlen); } } if(FD_ISSET(fd2,&rfds)) { rxlen = read(fd2, buf, 255); if(rxlen > 0) { printf("len = %d\n\r",rxlen); printf("rx:%s\n\r",buf); write(fd2, buf, rxlen); } } if(FD_ISSET(fd3,&rfds)) { rxlen = read(fd3, buf, 255); if(rxlen > 0) { printf("len = %d\n\r",rxlen); printf("rx:%s\n\r",buf); write(fd3, buf, rxlen); } } if(FD_ISSET(fd4,&rfds)) { rxlen = read(fd4, buf, 255); if(rxlen > 0) { printf("len = %d\n\r",rxlen); printf("rx:%s\n\r",buf); write(fd4, buf, rxlen); } } } } }

此段程序为同时监控4路串口接收状态,将接收的内容直接原路返回,串口采用的是阻塞读取模式,select函数也采用阻塞式读取模式。

原文地址:https://blog.csdn.net/m0_38096844/article/details/90716182?spm=1001.2014.3001.5502

,