<Linux开发> linux应用开发-之-uart通信开发例程
一、简介
串口全称叫做串行接口,串行接口指的是数据一个一个的按顺序传输,通信线路简单。使用两条线即可.
实现双向通信,一条用于发送,一条用于接收。串口通信距离远,但是速度相对会低,串口是一种很常用的工业接口。
关于串口的基础知识以及通行原理、通行数据格式等之类的问题。串口(UART)在嵌入式 Linux 系统中常作为系统的标准输入、输出设备,系统运行过程产生的打印信息通过串口输出;同理,串口也作为系统的标准输入设备,用户通过串口与 Linux 系统进行交互。
更加详细介绍说明读者可自行查阅相关资料。
二、环境搭建
本次测试uart通信的应用例程是运行在ubuntu pc上的;当然也是可以运行在linux开发板 或相关linux设备上的。
如果在Linux开发板上运行,需要有交叉编译工具。
本次测试实现的是自发自收,在PC上插入一个串口模块(uart转CH340模块,其它模块也可),然后将RX 引脚 与TX引脚 通过杜邦线连接即可。
三、例程代码
本次代码会使用单独的一个c文件用来编写uart代码,用以接收数据并在终端打印;
代码如下:
/***************************************************************
Copyright © OneFu Co., Ltd. 1998-2022. All rights reserved.
文件名 : uart.c
作者 : waterfxw
版本 : V1.0
描述 : uart 示例代码
其他 : 主要是测试 使用cmake
日志 : 初版 V1.0 2023/03/20 waterfxw创建
***************************************************************/#define _GNU_SOURCE //在源文件开头定义_GNU_SOURCE 宏
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <termios.h>typedef struct uart_hardware_cfg {unsigned int baudrate; /* 波特率 */unsigned char dbit; /* 数据位 */char parity; /* 奇偶校验 */unsigned char sbit; /* 停止位 */
} uart_cfg_t;static struct termios old_cfg; //用于保存终端的配置参数
static int fd; //串口终端对应的文件描述符/**
** 串口初始化操作
** 参数 device 表示串口终端的设备节点
**/
static int uart_init(const char *device) {/* 打开串口终端 */fd = open(device, O_RDWR | O_NOCTTY);if (0 > fd) {fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));return -1;}/* 获取串口当前的配置参数 */if (0 > tcgetattr(fd, &old_cfg)) {fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));close(fd);return -1;}return 0;
}/**
** 串口配置
** 参数 cfg 指向一个 uart_cfg_t 结构体对象
**/
static int uart_cfg(const uart_cfg_t *cfg) {struct termios new_cfg = {0}; //将 new_cfg 对象清零speed_t speed;/* 设置为原始模式 */cfmakeraw(&new_cfg);/* 使能接收 */new_cfg.c_cflag |= CREAD;/* 设置波特率 */switch (cfg->baudrate) {case 1200: speed = B1200;break;case 1800: speed = B1800;break;case 2400: speed = B2400;break;case 4800: speed = B4800;break;case 9600: speed = B9600;break;case 19200: speed = B19200;break;case 38400: speed = B38400;break;case 57600: speed = B57600;break;case 115200: speed = B115200;break;case 230400: speed = B230400;break;case 460800: speed = B460800;break;case 500000: speed = B500000;break;default: //默认配置为 115200speed = B115200;printf("default baud rate: 115200\n");break;}if (0 > cfsetspeed(&new_cfg, speed)) {fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno));return -1;}/* 设置数据位大小 */new_cfg.c_cflag &= ~CSIZE; //将数据位相关的比特位清零switch (cfg->dbit) {case 5:new_cfg.c_cflag |= CS5;break;case 6:new_cfg.c_cflag |= CS6;break;case 7:new_cfg.c_cflag |= CS7;break;case 8:new_cfg.c_cflag |= CS8;break;default: //默认数据位大小为 8new_cfg.c_cflag |= CS8;printf("default data bit size: 8\n");break;}/* 设置奇偶校验 */switch (cfg->parity) {case 'N': //无校验new_cfg.c_cflag &= ~PARENB;new_cfg.c_iflag &= ~INPCK;break;case 'O': //奇校验new_cfg.c_cflag |= (PARODD | PARENB);new_cfg.c_iflag |= INPCK;break;case 'E': //偶校验new_cfg.c_cflag |= PARENB;new_cfg.c_cflag &= ~PARODD; /* 清除 PARODD 标志,配置为偶校验 */new_cfg.c_iflag |= INPCK;break;default: //默认配置为无校验new_cfg.c_cflag &= ~PARENB;new_cfg.c_iflag &= ~INPCK;printf("default parity: N\n");break;}/* 设置停止位 */switch (cfg->sbit) {case 1: //1 个停止位new_cfg.c_cflag &= ~CSTOPB;break;case 2: //2 个停止位new_cfg.c_cflag |= CSTOPB;break;default: //默认配置为 1 个停止位new_cfg.c_cflag &= ~CSTOPB;printf("default stop bit size: 1\n");break;}/* 将 MIN 和 TIME 设置为 0 */new_cfg.c_cc[VTIME] = 0;new_cfg.c_cc[VMIN] = 0;/* 清空缓冲区 */if (0 > tcflush(fd, TCIOFLUSH)) {fprintf(stderr, "tcflush error: %s\n", strerror(errno));return -1;}/* 写入配置、使配置生效 */if (0 > tcsetattr(fd, TCSANOW, &new_cfg)) {fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));return -1;}/* 配置 OK 退出 */return 0; }/***
--dev=/dev/ttyUSB0
--brate=115200
--dbit=8
--parity=N
--sbit=1
--type=read
***/
/**打印帮助信息**/
static void show_help(const char *app) {printf("Usage: %s [选项]\n""\n 必选选项:\n"" --dev=DEVICE 指定串口终端设备名称, 譬如--dev=/dev/ttyUSB0\n"" --type=TYPE 指定操作类型, 读串口还是写串口, 譬如--type=read(read 表示读、write 表示写、readwrite表示读写、其它值无效)\n""\n 可选选项:\n"" --brate=SPEED 指定串口波特率, 譬如--brate=115200\n"" --dbit=SIZE 指定串口数据位个数, 譬如--dbit=8(可取值为: 5/6/7/8)\n"" --parity=PARITY 指定串口奇偶校验方式, 譬如--parity=N(N 表示无校验、O 表示奇校验、E 表示偶校验)\n"" --sbit=SIZE 指定串口停止位个数, 譬如--sbit=1(可取值为: 1/2)\n"" --help 查看本程序使用帮助信息\n\n", app);
}/**
** 信号处理函数,当串口有数据可读时,会跳转到该函数执行
**/
static void io_handler(int sig, siginfo_t *info, void *context) {unsigned char buf[10] = {0};int ret;int n;if(SIGRTMIN != sig)return;/* 判断串口是否有数据可读 */if (POLL_IN == info->si_code) {ret = read(fd, buf, 8); //一次最多读 8 个字节数据printf("[ ");for (n = 0; n < ret; n++)printf("0x%hhx ", buf[n]);printf("]\n");}
}/**
** 异步 I/O 初始化函数
**/
static void async_io_init(void) {struct sigaction sigatn;int flag;/* 使能异步 I/O */flag = fcntl(fd, F_GETFL); //使能串口的异步 I/O 功能flag |= O_ASYNC;fcntl(fd, F_SETFL, flag);/* 设置异步 I/O 的所有者 */fcntl(fd, F_SETOWN, getpid());/* 指定实时信号 SIGRTMIN 作为异步 I/O 通知信号 */fcntl(fd, F_SETSIG, SIGRTMIN);/* 为实时信号 SIGRTMIN 注册信号处理函数 */sigatn.sa_sigaction = io_handler; //当串口有数据可读时,会跳转到 io_handler 函数sigatn.sa_flags = SA_SIGINFO;sigemptyset(&sigatn.sa_mask);sigaction(SIGRTMIN, &sigatn, NULL);
}int main(int argc, char *argv[])
{uart_cfg_t cfg = {0};char *device = NULL;int rw_flag = -1;unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; //通过串口发送出去的数据int n;printf("解析出参数 \n");/* 解析出参数 */for (n = 1; n < argc; n++) {if (!strncmp("--dev=", argv[n], 6))device = &argv[n][6];else if (!strncmp("--brate=", argv[n], 8))cfg.baudrate = atoi(&argv[n][8]);else if (!strncmp("--dbit=", argv[n], 7))cfg.dbit = atoi(&argv[n][7]);else if (!strncmp("--parity=", argv[n], 9))cfg.parity = argv[n][9];else if (!strncmp("--sbit=", argv[n], 7))cfg.sbit = atoi(&argv[n][7]);else if (!strncmp("--type=", argv[n], 7)) {if (!strcmp("read", &argv[n][7]))rw_flag = 0; //读if (!strcmp("write", &argv[n][7]))rw_flag = 1; //写if (!strcmp("readwrite", &argv[n][7]))rw_flag = 2; //读写}else if (!strcmp("--help", argv[n])) {show_help(argv[0]); //打印帮助信息exit(EXIT_SUCCESS);}}if (NULL == device || -1 == rw_flag) {fprintf(stderr, "Error: the device and read|write type must be set!\n");show_help(argv[0]);exit(EXIT_FAILURE);}printf("串口初始化 \n");/* 串口初始化 */if (uart_init(device))exit(EXIT_FAILURE);printf("串口配置 \n");/* 串口配置 */if (uart_cfg(&cfg)) {tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置close(fd);exit(EXIT_FAILURE);}printf("读|写串口 \n");/* 读|写串口 */switch (rw_flag) {case 0: //读串口数据async_io_init(); //我们使用异步 I/O 方式读取串口的数据,调用该函数去初始化串口的异步 I/Ofor ( ; ; )sleep(1); //进入休眠、等待有数据可读,有数据可读之后就会跳转到 io_handler()函数break;case 1: //向串口写入数据for ( ; ; ) { //循环向串口写入数据write(fd, w_buf, 8); //一次向串口写入 8 个字节sleep(1); //间隔 1 秒钟}break;case 2: //向串口写入数据async_io_init(); //我们使用异步 I/O 方式读取串口的数据,调用该函数去初始化串口的异步 I/Ofor ( ; ; ) { //循环向串口写入数据write(fd, w_buf, 8); //一次向串口写入 8 个字节sleep(2); //间隔 1 秒钟}break;}/* 退出 */tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置close(fd);exit(EXIT_SUCCESS);
}
上述是uart收发源码。
在这次例程中我们使用cmake来编译,如果有不熟悉cmake的可自行查阅相关资料,或作者的另一篇文章:linux开发工具-之-CMake简单例程[初见]
在uart.c文件的同级目录下创建文件“CMakeLists.txt”
编写cmake,在“CMakeLists.txt”中编写一下内容:
project(UART)
add_executable(uart ./uart.c)
四、编译
首先:cmake编译:
cmake ./
运行cmake后,在同级目录下会得到一个Makefile文件
其次再进行Makefile编译,Makefile编译直接运行make即可
make
即可得到执行文件uart
五、运行验证
查看文件类型
file uart
作者想串口模块插入ubuntu PC后得到的设备是 “/dev/ttyUSB0”
可通过“ls -a /dev/tty*” 命令查看,如下:
运行可执行程序uart;
查看uart的帮助信息:
./uart --help
查看帮助如下:
进行读写运行测试命令:
sudo ./uart --dev=/dev/ttyUSB0 --type=readwrite
效果如下图:
至此,uart的读写测试完成。
此例程只是简单实现读写,在实际运用中会比这复杂很多。
cmake相关文章,作者后续会持续更新,尽请关注。
相关文章:

<Linux开发> linux应用开发-之-uart通信开发例程
一、简介 串口全称叫做串行接口,串行接口指的是数据一个一个的按顺序传输,通信线路简单。使用两条线即可. 实现双向通信,一条用于发送,一条用于接收。串口通信距离远,但是速度相对会低,串口是一种很常用的工…...

基于深度学习的安全帽检测系统(YOLOv5清新界面版,Python代码)
摘要:安全帽检测系统用于自动化监测安全帽佩戴情况,在需要佩戴安全帽的场合自动安全提醒,实现图片、视频和摄像头等多种形式监测。在介绍算法原理的同时,给出Python的实现代码、训练数据集,以及PyQt的UI界面。安全帽检…...

Linux - 进程控制(进程替换)
0.引入创建子进程的目的是什么?就是为了让子进程帮我执行特定的任务让子进程执行父进程的一部分代码如果子进程想执行一个全新的程序代码呢? 那么就要使用进程的程序替换为什么要有程序替换?也就是说子进程想执行一个全新的程序代码ÿ…...
Java中 ==和equals的区别是什么?
作用: 基本类型,比较值是否相等引用类型,比较内存地址值是否相等不能比较没有父子关系的两个对象equals()方法的作用: JDK 中的类一般已经重写了 equals(),比较的是内容自定义类如果没有重写 equals(),将…...

Linux(网络基础---网络层)
文章目录0. 前言1. IP协议1-1 基本概念1-2 协议头格式2. 网段划分2-1 基本概念2.2 IP地址分五大类2-3 特殊的IP地址2-4 IP地址的数量限制2-5 私有IP地址和公网IP地址2-6 路由0. 前言 前面我们讲了,应用层、传输层;本章讲网络层。 应用层:我…...

空间信息智能应用团队研究成果介绍及人才引进
目录1、多平台移动测量技术1.1 车载移动测量系统1.2 机载移动测量系统2、数据处理与应用技术研究2.1 点云与影像融合2.2 点云配准与拼接2.3 点云滤波与分类2.4 道路矢量地图提取2.5 道路三维自动建模2.6 道路路面三维病害分析2.7 多期点云三维变形分析2.8 地表覆盖遥感监测分析…...

ChatGPT应用场景与工具推荐
目录 写在前面 一、关于ChatGPT 二、应用实例 1.写文章 2.入门新的知识 3.解决疑难问题 4.生成预演问题 5.文本改写 6.语言翻译 7.思维导图 8.PDF阅读理解 9.操作格式化的数据 10.模拟场景 11.写代码 三、现存局限 写在前面 本文会简单介绍ChatGPT的特点、局限以…...

图像分类卷积神经网络模型综述
图像分类卷积神经网络模型综述遇到问题 图像分类:核心任务是从给定的分类集合中给图像分配一个标签任务。 输入:图片 输出:类别。 数据集MNIST数据集 MNIST数据集是用来识别手写数字,由0~9共10类别组成。 从MNIST数据集的SD-1和…...

艹,终于在8226上把灯点亮了
接上次点文章ESP8266还可以这样玩这次,我终于学会了在ESP8266上面点亮LED灯了现在一个单片机的价格是几块,加上一个晶振,再来一个快递费,十几块钱还是需要的。所以能用这个ESP8266来当单片机玩,还是比较不错的可以在ub…...

脱不下孔乙己的长衫,现代的年轻人该怎么办?
“如果我没读过书,我还可以做别的工作,可我偏偏读过书” “学历本该是我的敲门砖,却成了我脱不下的长衫。” 最近,“脱下孔乙己的长衫”在网上火了。在鲁迅的原著小说中,孔乙己属于知识阶级(长衫客…...
Matlab实现遗传算法
遗传算法(Genetic Algorithm,GA)是一种基于生物进化理论的优化算法,通过模拟自然界中的遗传过程,来寻找最优解。 在遗传算法中,每个解被称为个体,每个个体由一组基因表示,每个基因是…...
评价公式-均方误差
均方误差的公式可以通过以下步骤推导得出: 假设有n个样本,真实值分别为y₁, y₂, ……, yₙ,预测值分别为ŷ₁, ŷ₂, ……, ŷₙ。 首先,我们可以定义误差(error)为预测值与真实值之间的差: …...

冲击蓝桥杯-时间问题(必考)
目录 前言: 一、时间问题 二、使用步骤 1、考察小时,分以及秒的使用、 2、判断日期是否合法 3、遍历日期 4、推算星期几 总结 前言: 时间问题可以说是蓝桥杯,最喜欢考的问题了,因为时间问题不涉及到算法和一些复杂的知识…...
10个杀手级应用的Python自动化脚本
10个杀手级应用的Python自动化脚本 重复的任务总是耗费时间和枯燥的。想象一下,逐一裁剪100张照片,或者做诸如Fetching APIs、纠正拼写和语法等任务,所有这些都需要大量的时间。为什么不把它们自动化呢?在今天的文章中,…...

2023史上最全软件测试工程师常见的面试题总结 备战金三银四
在这里我给大家推荐一套专门讲解软件测试简历,和面试题的视频,实测有效,建议大家可以看看! 春招必看已上岸,软件测试常问面试题【全网最详细,让你不再踩坑】_哔哩哔哩_bilibili春招必看已上岸,…...
2023年全国最新安全员精选真题及答案29
百分百题库提供安全员考试试题、建筑安全员考试预测题、建筑安全员ABC考试真题、安全员证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 81.(单选题)同一建筑施工企业在12个月内连续发生(&…...

关系数据库的7个基本特征
文章目录关系数据库中的二维表─般满足7个基本特征:①元组(行)个数是有限的——元组个数有限性。 ②元组(行)均不相同——元组的唯—性。 ③元组(行)的次序可以任意交换——元组的次序无关性。 ④元组(行)的分量是不可分割的基本特征——元组分量的原子性。 ⑤属性(列)名各不相…...
2023QT面试题总会
1、Qt信号槽机制的优势 (1)类型安全。需要关联的信号和槽的签名必须是等同的,即信号的参数类型和参数个数同接收该信号的槽的参数类型和参数个数相同。不过,一个槽的参数个数是可以少于信号的参数个数的,但缺少的参数…...

【微信小程序】-- npm包总结 --- 基础篇完结(四十七)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…...

Leetcode刷题之经典双指针问题
光是话不行,要紧的是做。 ——鲁迅 目录 一.什么是双指针问题? 二.最接近的三数之和 第一种暴力法: 第二种双指针: 三.移除元素 第一种暴力法: 第二种双指针: 四.盛最…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...

算术操作符与类型转换:从基础到精通
目录 前言:从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符:、-、*、/、% 赋值操作符:和复合赋值 单⽬操作符:、--、、- 前言:从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...
加密通信 + 行为分析:运营商行业安全防御体系重构
在数字经济蓬勃发展的时代,运营商作为信息通信网络的核心枢纽,承载着海量用户数据与关键业务传输,其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级,传统安全防护体系逐渐暴露出局限性&a…...

解析“道作为序位生成器”的核心原理
解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制,重点解析"道作为序位生成器"的核心原理与实现框架: 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...