嵌入式Linux——8 串口

目录

1.终端(tty)

/dev/tty*:物理/虚拟终端

/dev/pts/*:伪终端

/dev/tty:当前进程的控制终端

/dev/tty0:当前活动的虚拟控制台

2.行规程模式(line discipline)

比较行规程和原始模式:

1. 行规程模式

2.原始模式

 3.串口API

termios 结构体

基本的API

4.串口实验(看看代码怎么写)

4.1 串口回环实验(传出去马上传回来)

自定义 set_opt 设置串口参数函000数:

自定义 open_port 打开串口设备函数 

main函数示例

4.2 GPS 模块实验 (不看也行,差不多的,就是加了点应用性)


1.终端(tty)

/dev/tty*:物理/虚拟终端

/dev/ttyS*:物理串口(如 RS-232)

/dev/ttyUSB*:USB 转串口设备

/dev/tty1 ~ tty63:本地虚拟控制台(通过 Ctrl+Alt+F1~F12 切换)


/dev/pts/*:伪终端

特点
        动态创建:由终端模拟器或 SSH 会话按需生成,退出后自动消失。

        无硬件关联:完全由软件模拟,用于多用户会话管理。


典型用途

        SSH 远程连接

        图形界面中的终端模拟器(就是Ubuntu图形化界面的终端,虽然它是tty2,但它是运行在 X Server/Wayland 上的,而不是原生的文本控制台,是通过 伪终端(/dev/pts/*) 实现的,与 /dev/tty0 无直接关联)


/dev/tty:当前进程的控制终端

        指向当前会话实际使用的终端设备

切换到 tty3,执行su root之后,执行下面命令观察现象:

while [ 1 ]; do echo msg_from_tty3 > /dev/tty; sleep 3; done

现象:只有执行命令的那个终端会收到并打印信息 

/dev/tty0:当前活动的虚拟控制台

代表当前前台虚拟终端,不适用于伪终端(如 SSH 或图形终端

切换到 tty3,执行su root之后,执行下面命令观察现象:

while [ 1 ]; do echo msg_from_tty3 > /dev/tty0; sleep 3; done

现象:把哪个终端切换到前台,那个终端就会收到并打印信息

2.行规程模式(line discipline)

在终端和串口通信的过程中,设备和程序之间有一个行规程模式处理方式

韦的图,大致意思就是说 pc 在调试开发板的串口终端上输入一个字符 “a” ,通过串口传到开发板后,先不传入程序(app)中,而是保存在行规程中,然后行规程会把当前字符回传给 pc,所以串口终端界面上会出现我们输入的字符,这个过程叫做 “回显”,需要退格删掉一个字符也是把退格传到行规程,行规程删除字符后再把现存字符回显到 pc 上。直到行规程收到 “回车键” ,才会把保存的字符都发送给程序(app)处理

后面需要把设备的行规程模式设置为原始模式,是因为需要把信息的处理全权交给程序

比较行规程原始模式:

1. 行规程模式

特点:

        行缓冲:数据按行处理(遇到\n或EOF才提交给程序)

        字符回显:输入字符会显示在终端上

        特殊字符处理:支持Ctrl+C(中断)、Ctrl+Z(暂停)等控制功能


典型场景:

        用户交互式终端(如SSH会话、本地Shell)

        需要逐行输入的命令行工具

示例:在行规程模式下,输入 hello 后按回车,程序才会收到完整字符串

2.原始模式

特点:

        无缓冲:数据立即传递给程序,无需等待行结束符

        无回显:输入字符不自动显示

        禁用控制字符:Ctrl+C等被视为普通数据

        完全控制:可精确设置数据位、超时等参数

典型场景:

        串口通信(如与单片机、传感器通信)

        需要实时响应的应用(如游戏、网络协议栈)

        二进制数据传输

示例:在原始模式下,每次接收到1个字节就会立即触发读取操作。

可以看看下面原因:

 3.串口API

在 Linux 系统中,操作设备的统一接口就是:open/ioctl/read/write。

对于 UART,又在 ioctl 之上封装了很多函数,主要是用来设置行规程等参数

所以UART应用编程的套路就是:

  1. open;
  2. 设置行规程,比如波特率、数据位、停止位、检验位、RAW 模式(ioctl)
  3. read/write;

termios 结构体

struct termios 是 Linux 系统中用于终端 I/O 控制的关键数据结构,定义在 <termios.h> 头文件中。它包含了终端设备的全部控制参数,用于配置串口、控制台等设备的通信行为。

基本的API

tc:terminal contorl
cf:control flag

tcgetattr:获取终端的属性
tcsetattr:修改终端参数
tcflush:清空终端未完成的输入/输出请求及数据
cfsetispeed: 设置输入波特率
cfsetospeed: 设置输出波特率
cfsetspeed: 同时设置输入、输出波特率

这些API其实就是修改上面的 termios 结构体,这些函数更底层其实就是调用 ioctl 修改 termios 结构体

4.串口实验(看看代码怎么写)

4.1 串口回环实验(传出去马上传回来)

自定义 set_opt 设置串口参数函000数:

/*** 设置串口参数* @param fd      串口文件描述符* @param nSpeed  波特率(2400/4800/9600/115200)* @param nBits   数据位(7或8)* @param nEvent  校验方式(N:无校验,O:奇校验,E:偶校验)* @param nStop   停止位(1或2)* @return        成功返回0,失败返回-1*/
int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop) 
{struct termios newtio, oldtio;/* 1. 获取当前串口配置 */if (tcgetattr(fd, &oldtio) != 0) {perror("tcgetattr failed");return -1;}/* 2. 初始化新配置结构体 */bzero(&newtio, sizeof(newtio));/* 3. 设置控制模式标志 */newtio.c_cflag |= CLOCAL | CREAD;  // 保持本地连接和启用接收newtio.c_cflag &= ~CSIZE;          // 清除数据位掩码/* 4. 设置输入/输出模式 */newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);  // 原始输入模式(非规范模式)newtio.c_oflag &= ~OPOST;                           // 原始输出模式(无处理)/* 5. 设置数据位 */switch (nBits) {case 7:newtio.c_cflag |= CS7;break;case 8:newtio.c_cflag |= CS8;break;default:newtio.c_cflag |= CS8;  // 默认8位数据位break;}/* 6. 设置校验位 */switch (nEvent) {case 'O':  // 奇校验newtio.c_cflag |= PARENB | PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;case 'E':  // 偶校验newtio.c_cflag |= PARENB;newtio.c_cflag &= ~PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;case 'N':  // 无校验newtio.c_cflag &= ~PARENB;break;}/* 7. 设置波特率 */switch (nSpeed) {case 2400:cfsetispeed(&newtio, B2400);cfsetospeed(&newtio, B2400);break;case 4800:cfsetispeed(&newtio, B4800);cfsetospeed(&newtio, B4800);break;case 9600:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;case 115200:cfsetispeed(&newtio, B115200);cfsetospeed(&newtio, B115200);break;default:  // 默认9600cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;}/* 8. 设置停止位 */if (nStop == 1) {newtio.c_cflag &= ~CSTOPB;  // 1位停止位} else if (nStop == 2) {newtio.c_cflag |= CSTOPB;   // 2位停止位}/* 9. 设置非规范模式下的读取参数 */newtio.c_cc[VMIN]  = 1;   // 最小读取字节数:至少读取1字节才返回newtio.c_cc[VTIME] = 0;   // 超时时间(单位:0.1秒),0表示无限等待//等待第1个数据的时间//比如VMIN设为10表示至少读到10个数据才返回,但是没有数据总不能一直等吧? 可以设置VTIME,如果超时时间内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回/* 10. 清空输入缓冲区 */tcflush(fd, TCIFLUSH);/* 11. 应用新配置(立即生效) */if (tcsetattr(fd, TCSANOW, &newtio) != 0) {perror("tcsetattr failed");return -1;}return 0;
}

自定义 open_port 打开串口设备函数 

/*** 打开并初始化串口设备* @param com 串口设备路径(如 "/dev/ttyS0")* @return 成功返回文件描述符,失败返回-1*/
int open_port(char *com) 
{int fd;/* 以读写模式打开串口设备,并确保不被用作控制终端 */fd = open(com, O_RDWR | O_NOCTTY);if (fd == -1) {perror("open serial port failed");return -1;}/* 显式设置文件状态标志为阻塞模式,也就是当程序无法读/写数据,程序会休眠*/if (fcntl(fd, F_SETFL, 0) < 0) {perror("fcntl F_SETFL failed");close(fd);  // 失败时关闭文件描述符return -1;}return fd;  // 返回有效的文件描述符
}

main函数示例

/*** 串口通信测试程序* 功能:打开串口,配置参数(115200,8N1),实现简单的回显测试*/
int main(int argc, char **argv) 
{int fd;      // 串口文件描述符int iRet;    // 操作返回值char c;      // 读写数据的缓冲区/* 参数检查 */if (argc != 2) {printf("Usage: %s </dev/ttySAC1 or other>\n", argv[0]);return -1;}/* 1. 打开串口 */fd = open_port(argv[1]);if (fd < 0) {printf("open %s err!\n", argv[1]);return -1;}/* 2. 配置串口参数(115200波特率,8数据位,无校验,1停止位)*/iRet = set_opt(fd, 115200, 8, 'N', 1);if (iRet) {printf("set port err!\n");close(fd);  // 配置失败时关闭串口return -1;}/* 3. 串口读写测试 */printf("Enter a char: ");while (1) {/* 从标准输入获取字符 */scanf("%c", &c);/* 写入串口 */iRet = write(fd, &c, 1);if (iRet != 1) {printf("write failed\n");continue;}/* 从串口读取回显数据(非阻塞模式立即返回)*/iRet = read(fd, &c, 1);if (iRet == 1) {printf("get: %02x %c\n", c, c);  // 打印十六进制和ASCII格式} else {printf("can not get data\n");}}close(fd);  // 理论上不会执行到这里return 0;
}

因为有可能会出现读取串口数据时,由于串口回环设备传输太慢,导致发送后回环读取时会出现读取失败的情况,是因为数据还没传到。

set_opt 中 :

newtio.c_cc[VMIN]  = 1;   // 最小读取字节数:至少读取1字节才返回
newtio.c_cc[VTIME] = 0;   // 超时时间(单位:0.1秒),0表示无限等待//等待第1个数据的时间//比如VMIN设为10表示至少读到10个数据才返回,但是没有数据总不能一直等吧? 可以设置VTIME,如果超时时间内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回

这两段代码就设置了等待数据时间(这里设置为0,即无限等待)

这就解决了问题,把数据等到程序才继续运行,否则就一直阻塞

4.2 GPS 模块实验 (不看也行,差不多的,就是加了点应用性)

使用串口接收数据,收到的数据包含:$GPGGA(GPS 定位数据)、$GPGLL (地理定位信息)、$GPGSA(当前卫星信息)、$GPGSV(可见卫星状态信息)、 $GPRMC(推荐最小定位信息)、$GPVTG(地面速度信息)

只分析$GPGGA (Global Positioning System Fix Data)即可, 它包含了 GPS 定位经纬度、质量因子、HDOP、高程、参考站号等字段。

数据标准格式:
$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh

其他关于gps的介绍看手册吧,这里只放代码

沿用上面的 set_opt 和 open_port 函数

/*** GPS数据读取与解析程序* 功能:从串口读取GPS模块的NMEA数据并解析位置信息*//*** 从串口读取一行GPS原始数据* @param fd 串口文件描述符* @param buf 存储读取数据的缓冲区* @return 成功返回0,失败返回-1*/
int read_gps_raw_data(int fd, char *buf)
{int i = 0;int iRet;char c;int start = 0;  // 标记是否开始接收有效数据while (1) {iRet = read(fd, &c, 1);  // 每次读取1个字符if (iRet == 1) {if (c == '$') {  // NMEA语句起始符start = 1;i = 0;       // 重置缓冲区索引}if (start) {buf[i++] = c;  // 存储有效数据}// 遇到换行符表示一行数据结束if (c == '\n' || c == '\r') {buf[i] = '\0';  // 添加字符串结束符return 0;}} else {return -1;  // 读取失败}}
}/*** 解析GPS原始数据(GPGGA格式)* @param buf 原始数据缓冲区* @param time 存储时间信息* @param lat 存储纬度* @param ns 存储南北半球* @param lng 存储经度* @param ew 存储东西半球* @return 成功返回0,失败返回-1*/
int parse_gps_raw_data(char *buf, char *time, char *lat, char *ns, char *lng, char *ew)
{char tmp[10];// 检查数据有效性if (buf[0] != '$') {  // 必须以$开头return -1;} else if (strncmp(buf+3, "GGA", 3) != 0) {  // 必须是GPGGA语句return -1;} else if (strstr(buf, ",,,,,")) {  // 无效定位数据printf("Place the GPS to open area\n");return -1;} else {// 解析关键字段sscanf(buf, "%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]",tmp, time, lat, ns, lng, ew);return 0;}
}/** 主函数* 用法:./gps_reader </dev/ttySAC1 or other>*/
int main(int argc, char **argv)
{int fd;              // 串口文件描述符int iRet;            // 函数返回值char buf[1000];      // 原始数据缓冲区char time[100];      // 时间字段char Lat[100];       // 纬度字段char ns[100];        // 南北半球标识char Lng[100];       // 经度字段char ew[100];        // 东西半球标识float fLat, fLng;    // 转换后的经纬度/* 1. 参数检查 */if (argc != 2) {printf("Usage: %s </dev/ttySAC1 or other>\n", argv[0]);return -1;}/* 2. 打开串口 */fd = open_port(argv[1]);if (fd < 0) {printf("open %s err!\n", argv[1]);return -1;}/* 3. 配置串口(9600波特率,8N1)*/iRet = set_opt(fd, 9600, 8, 'N', 1);if (iRet) {printf("set port err!\n");close(fd);return -1;}/* 4. 主循环:读取并解析GPS数据 */while (1) {/* 读取一行NMEA数据 */iRet = read_gps_raw_data(fd, buf);/* 解析GPGGA数据 */if (iRet == 0) {iRet = parse_gps_raw_data(buf, time, Lat, ns, Lng, ew);}/* 打印解析结果 */if (iRet == 0) {printf("\n------ GPS Data ------\n");printf("Time : %s\n", time);printf("Lat  : %s %s\n", Lat, ns);printf("Lng  : %s %s\n", Lng, ew);/* 转换纬度格式:ddmm.mmmm → 十进制 */sscanf(Lat+2, "%f", &fLat);fLat = fLat / 60;fLat += (Lat[0] - '0')*10 + (Lat[1] - '0');/* 转换经度格式:dddmm.mmmm → 十进制 */sscanf(Lng+3, "%f", &fLng);fLng = fLng / 60;fLng += (Lng[0] - '0')*100 + (Lng[1] - '0')*10 + (Lng[2] - '0');printf("Decimal Coordinates:\n");printf("Lng,Lat: %.06f,%.06f\n", fLng, fLat);}}close(fd);  // 理论上不会执行到这里return 0;
}

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

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

相关文章

Docker日志查看与资源监控指令全解:从基础到高阶运维实践

Docker日志查看与资源监控指令全解&#xff1a;从基础到高阶运维实践 一、日志管理&#xff1a;穿透容器内部的眼睛1.1 基础日志操作核心命令&#xff1a;docker logs日志驱动配置 1.2 高级日志处理JSON日志解析多容器日志聚合 二、资源监控&#xff1a;掌握容器生命体征2.1 实…

初学STM32之编码器测速以及测频法的实现

资料来着江协科技 这篇是编码器测速&#xff0c;江科大的源码在测速的时候&#xff0c;定时器TIM2是一直在跑的&#xff0c;不受其它控的&#xff0c;它就一直隔1S读一次CNT的值。它也不管是否有输入信号。源码程序修改一下是可以实现对PWM信号以测频法的方式读取。 笔者稍微改…

oracle怎么查看是否走了索引

SELECT * FROM CRM_STATION_APPEAL_RESULT WHERE COMPLAINT_ID ce1a1d8f-e2a2-4126-8cb7-14384cb24468; 这是查询语句&#xff0c;怎么看这个查询是否走了索引呢 EXPLAIN PLAN FOR SELECT * FROM CRM_STATION_APPEAL_RESULT WHERE COMPLAINT_ID ce1a1d8f-e2a2-4126-8cb7-14…

C++进阶——C++11_{ }初始化_lambda_包装器

目录 1、{ }初始化 1.1 C98的{ } 1.2 C11的{ } 1.3 C11中的std::initializer_list 总结一下&#xff1a; 2、lambda 2.1 lambda的语法 2.2 捕捉列表 2.3 lambda的应用 2.4 lambda的原理 3、包装器 3.1 function 3.2 bind 1、{ }初始化 1.1 C98的{ } C98中一般数组…

【微知】Mellanox网卡网线插入后驱动的几个日志?(Cable plugged;IPv6 ... link becomes ready)

概要 本文是一个简单的信息记录。记录的是当服务器网卡的光模块插入后内核的日志打印。通过这种日志打印&#xff0c;可以在定位分析问题的时候&#xff0c;知道进行过一次模块插拔。 日志 截图版&#xff1a; 文字版&#xff1a; [32704.121294] mlx5_core 0000:01:00.0…

单片机Day05---静态数码管

目录 一、原理图&#xff1a;​编辑 二、思路梳理&#xff1a; 三&#xff1a;一些说明&#xff1a; 1.点亮方式&#xff1a; 2.数组&#xff1a; 3.数字与段码对应&#xff1a; 四&#xff1a;程序实现&#xff1a; 一、原理图&#xff1a; 二、思路梳理&#xff1a; …

Cesium.js(6):Cesium相机系统

Camera表示观察场景的视角。通过操作摄像机&#xff0c;可以控制视图的位置、方向和角度。 帮助文档&#xff1a;Camera - Cesium Documentation 1 setView setView 方法允许你指定相机的目标位置和姿态。你可以通过 Cartesian3 对象来指定目标位置&#xff0c;并通过 orien…

【Python技术生态全景:十大核心应用领域深度解析】

目录 前言&#xff1a;Python的统治力一、基础应用领域1. Web开发数据科学 二、前沿技术领域机器学习深度学习 三、行业解决方案量化金融生物信息 四、创新应用方向物联网开发区块链开发 五、效率工具生态自动化运维游戏开发 结语&#xff1a;Python的边界与突破技术局限未来演…

leetcode 2787. Ways to Express an Integer as Sum of Powers

题目描述 这道题是0-1背包问题。可以理解为&#xff0c;有一个最大容量是n的背包&#xff0c;有n个物品&#xff0c;第i个物品的重量是i^x&#xff0c;问装满背包有多少种装法。题目要求必须是互不相同的数的x次幂的和等于n&#xff0c;那就表示每个数只能用一次&#xff0c;也…

面试经验分享 | 成都渗透测试工程师二面面经分享

可以看看我的置顶文章和专栏找我哦 概况 面试过程 面试官的问题 问题1、你觉得当前OAuth2.0下的攻击手段有哪些&#xff1f;结合具体案例详细讲讲 问题2、php/java反序列化漏洞的原理?程序员/运维如何避免此类漏洞或如何防御? 问题3、如果一台服务器被入侵后,你会如何做应急…

模仿axios的封装效果来封装fetch,实现baseurl超时等

因为要在cocos游戏项目里面发送网络请求获取数据&#xff0c;并且还有可能用到websocket发送请求&#xff0c;所以这里封装一个fetch放便使用&#xff1a; // fetch封装// 基础配置 const BASE_URL 你的url const TIMEOUT 5000// 请求封装 const http async (url: string, …

小米运维面试题及参考答案(80道面试题)

请讲解一下 linux top 后进程的状态 在 Linux 系统中,使用top命令可以查看系统中正在运行的进程的相关信息,进程通常有以下几种状态: 运行(R):表示进程正在 CPU 上运行或者正在运行队列中等待运行。处于运行状态的进程正在积极地使用 CPU 资源来执行其任务。睡眠(S):进…

a sort.py demo

这份代码展示了如何使用 sort.py。注意&#xff0c;此处&#xff0c;我将文件名改为 my_sort.py。 你并不能直接 copy 使用&#xff0c;因为环境&#xff0c;包&#xff0c;还有模型。 此处使用 SSD-MobileNetv2 进行物体检测&#xff0c;将结果传入以 np 数组的形式传入sort…

使用Redis解决:集群的Session共享问题

使用Redis解决&#xff1a;集群的Session共享问题 session共享问题&#xff1a;多台Tomcat并不共享session存储空间&#xff0c;当请求切换到不同的tomcat服务时导致数据丢失的问题。 问题背景 ​无状态HTTP协议&#xff1a;HTTP协议本身是无状态的&#xff0c;服务器无法直接识…

Linux 内核知识体系[1]

1 Linux内核知识体系 2.Linux内核学习路线 2.1基础知识准备 操作系统基础&#xff1a;了解操作系统的概念和基本原理&#xff0c;包括进程管理、内存管理、文件系统、输入输出等。 书籍&#xff1a;《操作系统&#xff1a;设计与实现》&#xff08;Andrew S. Tanenbaum&…

KiActivateWaiterQueue函数和Queue->Header.WaitListHead队列等待列表的关系

第一部分&#xff1a; if (Thread->ApcState.KernelApcPending && (Thread->SpecialApcDisable 0) && (Thread->WaitIrql < APC_LEVEL)) { } else { // // Insert wait block in ob…

让DeepSeek API支持联网搜索

引子 DeepSeek官网注册的API token是不支持联网搜索的&#xff0c;这导致它无法辅助分析一些最新的情况或是帮忙查一下互联网上的资料。本文从实战角度提供一种稳定可靠的方法使得DeepSeek R1支持联网搜索分析。 正文 首先登录火山方舟控制台&#xff0c;https://www.volcen…

生物信息Rust-01

前言-为什么想学Rust&#xff1f; 一直想多学一门编译语言&#xff0c;主要有几个原因吧&#xff08;1. 看到一位老师实验室要求需要掌握一门编译语言&#xff1b;2. 自己享想试着开发一些实用的生信工具&#xff0c;感觉自己现在相比于数据分析&#xff0c;探索生物学层面的意…

字符串与相应函数(上)

字符串处理函数分类 求字符串长度&#xff1a;strlen长度不受限制的字符串函数&#xff1a;strcpy,strcat,strcmp长度受限制的字符串函数:strncpy,strncat,strncmp字符串查找&#xff1a;strstr,strtok错误信息报告&#xff1a;strerror字符操作&#xff0c;内存操作函数&…

asm汇编源代码之文件操作相关

提供7个子程序:   1. 关闭文件 FCLOSE   2. 打开文件 FOPEN   3. 文件大小 FSIZE   4. 读文件 FREAD   5. 写文件 FWRITE   6. 建立文件 FCREATE   7. 读取或设置文件指针 FPOS 具体功能及参数描述如下 ; ---------------------------- FCLOSE PROC  FAR ; IN…