Linux串口编程详解(阻塞模式、非阻塞模式、select函数)
sinye56 2024-12-27 16:35 9 浏览 0 评论
前言:之前一直觉得串口编程很简单,这两天仔细研究后发现串口里的各种参数还挺复杂,稍不注意就容易出错,这里总结一下网上的各种文章及自己的理解与实践。
open 函数
功能描述:用于打开或创建文件,成功则返回文件描述符,否则返回-1,open返回的文件描述符一定是最小的未被使用的描述符
#include<fcntl.h>
int open(const char * pathname,int flags);
int open(const char * pathname,int flags,mode_t mode);
嵌入式进阶教程分门别类整理好了,看的时候十分方便,由于内容较多,这里就截取一部分图吧。
需要的朋友私信【内核】即可领取。
内核学习地址:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂
参数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 */
};
这里列出常见配置:
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配合使用,是指限定的传输或等待的最长时间
- 若 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 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
相关推荐
- CTO偷偷传我的系统性能优化十大绝招(万字干货)
-
上篇引言:取与舍软件设计开发某种意义上是“取”与“舍”的艺术。关于性能方面,就像建筑设计成抗震9度需要额外的成本一样,高性能软件系统也意味着更高的实现成本,有时候与其他质量属性甚至会冲突,比如安全性、...
- 提升效率!VMware虚拟机性能优化十大实用技巧
-
我40岁,干跨境婚恋中介的。为服务各国用户,常得弄英语、日语、俄语系统环境,VMware虚拟机帮了不少忙。用久了发现优化下性能,效率能更高。今儿就来聊聊优化技巧和同类软件。一、VMware虚拟...
- 低延迟场景下的性能优化实践
-
本文摘录自「全球C++及系统软件技术大会」ScottMeyers曾说到过,如果你不在乎性能,为什么要在C++这里,而不去隔壁的Pythonroom呢?今天我们就从“低延迟的概述”、“低延迟系...
- Linux性能调优之内存负载调优的一些笔记
-
写在前面整理一些Linux内存调优的笔记,分享给小伙伴博文没有涉及的Demo,理论方法偏多,可以用作内存调优入门博文内容涉及:Linux内存管理的基本理论寻找内存泄露的进程内存交换空间调优不同方式的...
- 优化性能套路:带你战胜这只后段程序员的拦路虎
-
来源|极客时间《卖桃者说》作者|池建强编辑|成敏你好,这里是卖桃者说。今天给大家推荐一篇文章,来自倪朋飞老师的专栏《Linux性能优化实战》,文章主要讲的是优化性能的套路,这几乎是每个后端程序员...
- SK海力士CXL优化解决方案已成功搭载于Linux:带宽提升30%,性能提升12%以上
-
SK海力士宣布,已将用于优化CXL(ComputeExpressLink)存储器运行的自研软件异构存储器软件开发套件(HMSDK)中主要功能成功搭载于全球最大的开源操作系统Linux上,不但提升了...
- Linux内核优化:提升系统性能的秘诀
-
Linux内核优化:提升系统性能的艺术在深入Linux内核优化的世界之前,让我们先来理解一下内核优化的重要性。Linux内核是操作系统的核心,负责管理系统资源和控制硬件。一个经过精心优化的内核可以显著...
- Linux系统性能优化:七个实战经验
-
Linux系统的性能是指操作系统完成任务的有效性、稳定性和响应速度。Linux系统管理员可能经常会遇到系统不稳定、响应速度慢等问题,例如在Linux上搭建了一个web服务,经常出现网页无法打开、打开速...
- 腾讯面试:linux内存性能优化总结
-
【1】内存映射Linux内核给每个进程都提供了一个独立且连续的虚拟地址空间,以便进程可以方便地访问虚拟内存;虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同字长的处理器,地址空间的范围也不同...
- Linux文件系统性能调优《参数优化详解》
-
由于各种的I/O负载情形各异,Linux系统中文件系统的缺省配置一般来说都比较中庸,强调普遍适用性。然而在特定应用下,这种配置往往在I/O性能方面不能达到最优。因此,如果应用对I/O性能要求较高,除...
- Nginx 性能优化(吐血总结)
-
一、性能优化考虑点当我需要进行性能优化时,说明我们服务器无法满足日益增长的业务。性能优化是一个比较大的课题,需要从以下几个方面进行探讨当前系统结构瓶颈了解业务模式性能与安全1、当前系统结构瓶颈首先需要...
- Linux问题分析与性能优化
-
排查顺序整体情况:top/htop/atop命令查看进程/线程、CPU、内存使用情况,CPU使用情况;dstat2查看CPU、磁盘IO、网络IO、换页、中断、切换,系统I/O状态;vmstat2查...
- 大神级产品:手机装 Linux 运行 Docker 如此简单
-
本内容来源于@什么值得买APP,观点仅代表作者本人|作者:灵昱Termux作为一个强大的Android终端模拟器,能够运行多种Linux环境。然而,直接在Termux上运行Docker并不可行,需要...
- 新手必须掌握的Linux命令
-
Shell就是终端程序的统称,它充当了人与内核(硬件)之间的翻译官,用户把一些命令“告诉”终端程序,它就会调用相应的程序服务去完成某些工作。现在包括红帽系统在内的许多主流Linux系统默认使用的终端是...
- Linux 系统常用的 30 个系统环境变量全解析
-
在Linux系统中,环境变量起着至关重要的作用,它们犹如隐藏在系统背后的“魔法指令”,掌控着诸多程序的运行路径、配置信息等关键要素。尤其在shell脚本编写时,巧妙运用环境变量,能让脚本如虎...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle忘记用户名密码 (59)
- oracle11gr2安装教程 (55)
- mybatis调用oracle存储过程 (67)
- oracle spool的用法 (57)
- oracle asm 磁盘管理 (67)
- 前端 设计模式 (64)
- 前端面试vue (56)
- linux格式化 (55)
- linux图形界面 (62)
- linux文件压缩 (75)
- Linux设置权限 (53)
- linux服务器配置 (62)
- mysql安装linux (71)
- linux启动命令 (59)
- 查看linux磁盘 (72)
- linux用户组 (74)
- linux多线程 (70)
- linux设备驱动 (53)
- linux自启动 (59)
- linux网络命令 (55)
- linux传文件 (60)
- linux打包文件 (58)
- linux查看数据库 (61)
- linux获取ip (64)
- linux进程通信 (63)