Linux串口编程详解

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

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

#include<fcntl.h>
int open(const char * pathname,int flags);
int open(const char * pathname,int flags,mode_t mode);
参数flags:一些文件模式选择,有如下几个参数可以设置

O_RDONLY       只读模式
O_WRONLY      只写模式
O_RDWR          读写模式
上面三个参数在设置的时候必须选择其中一个!下面的参数是可选的

O_APPEND           每次写操作都写入文件的末尾
O_CREAT              如果指定文件不存在,则创建这个文件
O_EXCL                如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
O_TRUNC             如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
O_NOCTTY           如果路径名指向终端设备,不要把这个设备用作控制终端
O_NONBLOCK     如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式
O_NDELAY           表示用于设置非阻塞方式。同时通知Linux系统,这个程序不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。如果用户没有指定这个标志,则进程将会一直处在睡眠状态,直到DCD信号线被激活。
下面三个常量同样是选用的,他们用于同步输入输出

O_DSYNC       等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新
O_RSYNC       read等待所有写入同一区域的写操作完成后再进行
O_SYNC          等待物理 I/O 结束后再 write,包括更新文件属性的 I/O
对于串口的打开操作,必须使用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种功能:

复制一个现有的描述符(cmd=F_DUPFD/F_DUPFD_CLOEXEC)
获得/设置文件描述符标记(cmd=F_GETFD/F_SETFD)
获得/设置文件状态标记(cmd=F_GETFL/F_SETFL) #常用
获得/设置异步I/O所有权(cmd=F_GETOWN/F_SETOWN)
获得/设置记录锁(cmd=F_GETLK/F_SETLK/F_SETLKW) #常用
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 */
};
具体参见:http://kevinlad.me/2017/11/25/Termios%E7%BB%93%E6%9E%84%E4%BD%93/

这里列出常见配置:

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.1Stcflush (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配合使用,是指限定的传输或等待的最长时间
若 VMIN = 0 ,VTIME = 0  ,函数read未读到任何参数也立即返回,相当于非阻塞模式;

若 VMIN = 0,   VTIME > 0  ,函数read读取到数据立即返回,若无数据则等待VTIME时间返回;

若 VMIN > 0,   VTIME = 0  ,函数read()只有在读取到VMIN个字节的数据或者收到一个信号的时候才返回;

若 VMIN > 0,   VTIME > 0  ,从read读取第一个字节的数据时开始计时,并会在读取到VMIN个字节或者VTIME时间后返回。

非阻塞式读写配置
打开时使用
open(USAR1, O_RDWR | O_NOCTTY | O_NDELAY );//非阻塞式读写
open(USAR1, O_RDWR | O_NOCTTY | O_NONBLOCK);//非阻塞式读写
open(USAR1, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);//非阻塞式读写

打开后使用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)
ndfs:select监视的文件句柄,一般设为要监视的文件中的最大文件号加一;
rdfds:select监视的可读文件句柄集合,当rdfds映象的文件句柄状态变成可读时系统告诉select函数返回。这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值,可以传入NULL值,表示不关心任何文件的读变化;
wtfds: select监视的可写文件句柄集合,当wtfds映象的文件句柄状态变成可写时系统告诉select函数返回,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值,可以传入NULL值,表示不关心任何文件的写变化;
exfds:select监视的异常文件句柄集合,当exfds映象的文件句柄上有特殊情况发生时系统会告诉select函数返回; 
timeout:select的超时结束时间,设为0则为阻塞模式,设为大于0的值时若所监视的句柄无状态变化则等待timeout时间后返回0;
配置函数:
FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。
FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,>0表示可读写。

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 modeif(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函数也采用阻塞式读取模式。
————————————————
版权声明:本文为CSDN博主「发呆健将」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_38096844/article/details/90716182

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402034.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

远程WEB控制MP3播放器设计(基于mini2440)

网上有很多 基于mini2440的MP3播放器设计的资料&#xff0c;多是按键控制&#xff0c;这里博主做了些轻微改动&#xff0c;利用远程WEB来控制MP3播放&#xff0c;具体怎么实现&#xff0c;下面会给出&#xff0c;大家先看看效果&#xff1a; WEB界面&#xff1a; 后台运行&…

线程以及pthread库的使用

https://blog.csdn.net/weixin_38102771/article/details/91351126 https://blog.csdn.net/qq_29677867/article/details/108571388?utm_mediumdistribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm1001.2101.3001.4242 一.什么是线程 你可以想象你一边…

嵌入式数据库 SQLite 浅析

SQLite是一个非常轻量级自包含(lightweight and self-contained)的DBMS&#xff0c;它可移植性好&#xff0c;很容易使用&#xff0c;很小&#xff0c;高效而且可靠。SQLite嵌入到使用它的应用程序中&#xff0c;它们共用相同的进程空间&#xff0c;而不是单独的一个进程。从外…

程序会话后继续运行

https://blog.csdn.net/qq_44925149/article/details/89474134

socket 请求Web服务器过程

HTTP协议只是一个应用层协议&#xff0c;它底层是通过TCP进行传输数据的。因此&#xff0c;浏览器访问Web服务器的过程必须先有“连接建立”的发生。 而有人或许会问&#xff1a;众所周知&#xff0c;HTTP协议有两大特性&#xff0c;一个是“无连接”性&#xff0c;一个是“无状…

有些事情现在不做一辈子就都不会做了

这句话最近一直印在我的脑海里。这句话最早是在Casperkid的百度空间里面看见的&#xff0c;那时他生日。作为师傅的刺&#xff08;道哥&#xff09;送了他自己写的一本《白帽子讲WEB安全》给他&#xff0c;并在扉页上写着这句话。那时一看到这句话&#xff0c;仿佛有种触电的感…

让事件飞——Linux eventfd 原理

让事件飞——Linux eventfd 原理 让事件飞 ——Linux eventfd 原理与实践 原文作者&#xff1a;杨阳 eventfd/timerfd 简介 目前越来越多的应用程序采用事件驱动的方式实现功能&#xff0c;如何高效地利用系统资源实现通知的管理和送达就愈发变得重要起来。在Linux系统中&…

HTTP 数据包头解析

一、连接至Web服务器 一个客户端应用&#xff08;如Web浏览器&#xff09;打开到Web服务器的HTTP端口的一个套接字&#xff08;缺省为80&#xff09;。 例如&#xff1a;http://www.myweb.com:8080/index.html 在Java中&#xff0c;这将等同于代码&#xff1a; [java] view pla…

linux epoll 开发指南-【ffrpc源码解析】

linux epoll 开发指南-【ffrpc源码解析】 摘要 关于epoll的问题很早就像写文章讲讲自己的看法&#xff0c;但是由于ffrpc一直没有完工&#xff0c;所以也就拖下来了。Epoll主要在服务器编程中使用&#xff0c;本文主要探讨服务器程序中epoll的使用技巧。Epoll一般和异步io结合…

Shell 脚本中如何使用make命令

最近开发的项目中需要编写Shell脚本对整个工程进行自动化编译&#xff0c;即在Shell脚本中使用make命令来进行编译&#xff0c;下面回顾一下Shell脚本中如何使用make命令&#xff09; 在开发一个系统时&#xff0c;一般是将一个系统分成几个模块&#xff0c;这样做提高了系统的…

c语言linux TCP长连接 socket收发范例 断开自动重连

原文链接&#xff1a;https://blog.csdn.net/chenhao0568/article/details/103420615 c语言linux TCP长连接 socket收发范例 断开自动重连 改进1&#xff1a;加入接收超时。可以做别的事&#xff0c;等有接收才响应 #include <stdio.h> #include <stdlib.h> #inc…

Shell 脚本知识回顾 (六) —— Shell 函数

一、Shell函数&#xff1a;Shell函数返回值、删除函数、在终端调用函数 函数可以让我们将一个复杂功能划分成若干模块&#xff0c;让程序结构更加清晰&#xff0c;代码重复利用率更高。像其他编程语言一样&#xff0c;Shell 也支持函数。Shell 函数必须先定义后使用。 Shell 函…

ICE相关链接

ZeroC IceGrid介绍及demo构建&#xff08;使用IceBox&#xff09; ice grid 第一篇 ICE通信之IceGrid服务&#xff08;二&#xff09; IceGrid注册器复制 Ice服务详解及应用_IceGrid(01)IceGrid应用 配置手册 https://blog.csdn.net/abcdefg367/category_8275964.html ICE通信框…

C# 托管资源和非托管资源(Dispose、析构函数)

https://www.cnblogs.com/herenzhiming/articles/9691524.html 资源分类&#xff1a; 托管资源指的是.NET可以自动进行回收的资源&#xff0c;主要是指托管堆上分配的内存资源。托管资源的回收工作是不需要人工干预的&#xff0c;有.NET运行库在合适调用垃圾回收器进行回收。…

Shell 脚本知识回顾 (五) —— Shell 循环

一、Shell for循环 与其他编程语言类似&#xff0c;Shell支持for循环。 for循环一般格式为&#xff1a;for 变量 in 列表 docommand1command2...commandN done 列表是一组值&#xff08;数字、字符串等&#xff09;组成的序列&#xff0c;每个值通过空格分隔。每循环一次&…

android开发工具下载

android studio eclipse sdk adt

Shell 脚本知识回顾 (四) —— Shell 命令及Shell 相关语句

一、Shell echo命令 echo是Shell的一个内部指令&#xff0c;用于在屏幕上打印出指定的字符串。命令格式&#xff1a;echo arg您可以使用echo实现更复杂的输出格式控制。 显示转义字符 echo "\"It is a test\""结果将是&#xff1a;"It is a test"…

qt工程。。。。。。

分享Qt多工程多目录的编译案例&#xff0c;subdirs Qt编译debug和release版本–CONFIG(debug,debug|release) QT工程pro设置实践(with QtCreator)----非弄的像VS同样才顺手? Qt创建动态库并添加动态库版本号 qmake&#xff1a;变量手册 QtCreator按顺序编译多个子项目

Shell 脚本知识回顾 (三) —— 替换、运算符、字符串、数组

一、Shell替换&#xff1a;Shell变量替换&#xff0c;命令替换&#xff0c;转义字符 如果表达式中包含特殊字符&#xff0c;Shell 将会进行替换。例如&#xff0c;在双引号中使用变量就是一种替换&#xff0c;转义字符也是一种替换。 举个例子&#xff1a; [cpp] view plaincop…

最幸福的事就是吃饺子

中午了&#xff0c;不知道吃什么&#xff0c;就去煮了点饺子&#xff0c;人呼呼的&#xff0c;吃完了很暖和~~下午出去&#xff0c;晚上回来&#xff0c;一天就这样过了~~转载于:https://blog.51cto.com/tina1314luky/1343466