Flythings学习(四)串口通信
文章目录
- 1 串口编程基本步骤
- 1.1 打开串口
- 1.2 配置串口
- 1.3 读串口
- 1.4 发送串口
- 1.5 关闭串口
- 2 综合使用
- 3 如何在软件上保证串口稳定通信
- 4 flythings中的串口通讯
- 5 协议接收部分使用和修改方法
- 6 通讯协议数据怎么和UI控件对接
1 串口编程基本步骤
串口通信有5个步骤
1.打开串口
2.配置串口
3.读串口
4.写串口
5.关闭串口
1.1 打开串口
#include <fcntl.h>int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
open是linux系统函数,open成功,返回0,失败返回1。/dev/ttys0可以理解为串口号,类似于windows系统上的com1,
1.2 配置串口
int openUart() {int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);struct termios oldtio = { 0 };struct termios newtio = { 0 };tcgetattr(fd, &oldtio);//设置波特率为115200newtio.c_cflag = B115200 | CS8 | CLOCAL | CREAD;newtio.c_iflag = 0; // IGNPAR | ICRNLnewtio.c_oflag = 0;newtio.c_lflag = 0; // ICANONnewtio.c_cc[VTIME] = 0;newtio.c_cc[VMIN] = 1;tcflush(fd, TCIOFLUSH);tcsetattr(fd, TCSANOW, &newtio);//设置为非阻塞模式,这个在读串口的时候会用到fcntl(fd, F_SETFL, O_NONBLOCK);return fd;
}
termios函数族提供了终端接口,用于控制非同步通信端口
他的结构体
struct termios
{ unsigned short c_iflag; /* 输入模式标志*/ unsigned short c_oflag; /* 输出模式标志*/ unsigned short c_cflag; /* 控制模式标志*/ unsigned short c_lflag; /*区域模式标志或本地模式标志或局部模式*/ unsigned char c_line; /*行控制line discipline */ unsigned char c_cc[NCC]; /* 控制字符特性*/
};
每个所对应的关系在下面这个博客
termios 相关知识https://blog.csdn.net/lizuobin2/article/details/51775277
tcgetattr(fd, &oldtio); 这个函数的作用,是取得终端介质(fd)初始值,并把其值 赋给oldtio;函数可以从后台进程中调用;但是,终端属性可能被后来的前 台进程所改变。
tcflush是丢掉写入引用的对象,但是尚未传输的数据,或者收到但是尚未读取的数据
tcsetattr(fd, TCSANOW, &newtio);
设置与终端相关的参数 (除非需要底层支持却无法满足),使用termios_p 引用的 termios 结构。optional_actions (tcsetattr函数的第二个参数)指定了什么时候改变会起作用,这里是改变立即发生
1.3 读串口
#include <fcntl.h>
unsigned char buffer[1024] = {0};
int ret = read(fd, buffer, sizeof(buffer));
1.4 发送串口
#include <fcntl.h>
unsigned char buffer[4] = {0};
buffer[0] = 0x01;
buffer[1] = 0x02;
buffer[2] = 0x03;
buffer[3] = 0x04;
int ret = write(fd, buffer, sizeof(buffer));
write可以发送串口数据
1.5 关闭串口
#include <fcntl.h>
close(fd);
2 综合使用
/** test.cpp** Created on: 2024年10月14日* Author: AA*/#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>int main(int argc, char **argv){// 创建串口int fd = open("/dev/ttyS0",O_RDWR | O_NOCTTY);if(fd < 0){// 打开串口失败return -1;}// 配置串口struct tremios oldtio = {0};struct termios newtio = {0};tcgetattr(fd,oldtio);newtio.c_cflag = B115200 | CS8 | CLOCAL | CREAD;newtio.c_iflag = 0;newtio.c_oflag = 0;newtio.c_lflag = 0;newtio.c_cc[VTIME] = 0;newtio.c_cc[VMIN] = 1;tcflush(fd, TCIOFLUSH);tcsetattr(fd, TCSANOW, &newtio);// 设置为非阻塞模式fcntl(fd, F_SETFL, O_NONBLOCK);// 监听和发送while (true) {unsigned char buffer[1024] = {0};int ret = read(fd, buffer, sizeof(buffer));if (ret > 0) {//依次将读取到的数据输出到日志for (int i = 0; i < ret; ++i) {LOGD("收到%02x", buffer[i]);}//当收到数据时,再将收到的数据原样发送int n = write(fd, buffer, ret);if (n != ret) {LOGD("发送失败");}//当收到0xFF时,跳出循环if (buffer[0] == 0xFF) {break;}} else {//没收到数据时,休眠50ms,防止过度消耗cpuusleep(1000 * 50);}}close(fd);return 0;
}
3 如何在软件上保证串口稳定通信
串口通信会制定通信协议,一般包括帧头、帧尾、帧内容、校验等部分。并且由于read函数不能保证一次性将当时串口收到的所有数据都返回,所以需要多次调用read函数,然后拼接起来
//提高buffer数组的作用域,使得while循环中不会清空数据
unsigned char buffer[1024] = {0};
// 增加一个`legacy`变量,表示buffer中遗留的数据长度
int legacy = 0;
while (true) {//根据legacy的大小,调整缓冲区的起始指针及大小,防止数据覆盖int ret = read(fd, buffer + legacy, sizeof(buffer) - legacy);if (ret > 0) {if ((buffer[0] == 0xFF) && (buffer[1] == 0x55)) {if ((ret + legacy) == 10) {LOGD("正确读到一帧数据");//清空legacylegacy = 0;} else if (ret < 10) {legacy += ret;LOGD("协议头正确,但是帧长度不够,则暂存在buffer里");}}//当收到数据时,再将收到的数据原样发送int n = write(fd, buffer, ret);if (n != ret) {LOGD("发送失败");}//当收到0xFF时,跳出循环if (buffer[0] == 0xFF) {break;}} else {//没收到数据时,休眠50ms,防止过度消耗cpuusleep(1000 * 50);}
}
用于处理串口通信的while循环应该单独开一个线程
4 flythings中的串口通讯
flythings自带一套串口通讯的框架

uart协议解析和封装的串口HAL层:
uartContext:串口的实际控制层,提供串口的开关、收发接口
ProtocolData:定义通讯的数据结构体,用于保存通讯协议转化出来的实际变量
protocolSender:完成数据发送的封装
ProtocolParser:完成数据的协议解析部分,然后将解析号的数据放入ProtocolData中,同时管理了应用监听串口数据变化的回到接口
APP应用逻辑层:
通过ProtocolParser提供的接口注册串口数据接收监听获取串口更新出来的ProtocolData
通过ProtocolSender提供的接口往MCU发送指令信息
这个流程大概是这样

具体流程

最终都是通过UartContext与MCU进行串口通信的,
5 协议接收部分使用和修改方法

通讯协议格式修改
CommDef.h 文件中定义了同步帧头信息及最小数据包大小信息:
// 需要打印协议数据时,打开以下宏
//#define DEBUG_PRO_DATA// 支持checksum校验,打开以下宏
//#define PRO_SUPPORT_CHECK_SUM/* SynchFrame CmdID DataLen Data CheckSum (可选) */
/* 2Byte 2Byte 1Byte N Byte 1Byte */
// 有CheckSum情况下最小长度: 2 + 2 + 1 + 1 = 6
// 无CheckSum情况下最小长度: 2 + 2 + 1 = 5#ifdef PRO_SUPPORT_CHECK_SUM
#define DATA_PACKAGE_MIN_LEN 6
#else
#define DATA_PACKAGE_MIN_LEN 5
#endif// 同步帧头
#define CMD_HEAD1 0xFF
#define CMD_HEAD2 0x55
在ProtocolParser文件中配置文件命令格式
/*** 功能:解析协议* 参数:pData 协议数据,len 数据长度* pdata[0]、[1]、[2]、[3]都是协议头,[4]开始是数据* 返回值:实际解析协议的长度*/
int parseProtocol(const BYTE *pData, UINT len) {UINT remainLen = len; // 剩余数据长度UINT dataLen; // 数据包长度UINT frameLen; // 帧长度/*** 以下部分需要根据协议格式进行相应的修改,解析出每一帧的数据*/while (remainLen >= DATA_PACKAGE_MIN_LEN) {// 找到一帧数据的数据头// pdata[0]如果不是协议头,说明是数据,往后继续找,直到找到协议头while ((remainLen >= 2) && ((pData[0] != CMD_HEAD1) || (pData[1] != CMD_HEAD2))) {pData++;remainLen--;continue;}// 如果剩下的长度小于最小的包长,则说明已经传完了,退出循环if (remainLen < DATA_PACKAGE_MIN_LEN) {break;}dataLen = pData[4];frameLen = dataLen + DATA_PACKAGE_MIN_LEN;if (frameLen > remainLen) {// 数据内容不全break;}// 打印一帧数据,需要时在CommDef.h文件中打开DEBUG_PRO_DATA宏
#ifdef DEBUG_PRO_DATAfor (int i = 0; i < frameLen; ++i) {LOGD("%x ", pData[i]);}LOGD("\n");
#endif// 支持checksum校验,需要时在CommDef.h文件中打开PRO_SUPPORT_CHECK_SUM宏
#ifdef PRO_SUPPORT_CHECK_SUM// 检测校验码if (getCheckSum(pData, frameLen - 1) == pData[frameLen - 1]) {// 解析一帧数据procParse(pData, frameLen);} else {LOGE("CheckSum error!!!!!!\n");}
#else// 解析一帧数据procParse(pData, frameLen);
#endifpData += frameLen;remainLen -= frameLen;}return len - remainLen;
}

如果协议头需要更改
// 1.修改协议头部分的定义,如果协议头长度有变化,则要注意修改协议头判断部分语句。
#define CMD_HEAD1 0xFF
#define CMD_HEAD2 0x55// 2.协议头长度变化的时候需要修改这里。
while ((mDataBufLen >= 2) && ((pData[0] != CMD_HEAD1) || (pData[1] != CMD_HEAD2)))// 3.协议长度需要更改
// 这里的pData[4] 代表的是第5个数据是长度的字节,如果变化了在这里修改一下。
dataLen = pData[4];
// 帧长度一般是数据长度加上头尾长度。如果协议中传的长度计算方式发生变化修改这个部分。
frameLen = dataLen + DATA_PACKAGE_MIN_LEN;
6 通讯协议数据怎么和UI控件对接
使用procParse进行解析
/** 协议解析* 输入参数:* pData: 一帧数据的起始地址* len: 帧数据的长度*/
void procParse(const BYTE *pData, UINT len) {/** 解析Cmd值获取数据赋值到sProtocolData结构体中*/switch (MAKEWORD(pData[2], pData[3])) {case CMDID_POWER:sProtocolData.power = pData[5];LOGD("power status:%d",sProtocolData.power);break;}notifyProtocolDataUpdate(sProtocolData);
}
通过notifyProtocolDataUpdate 将封装好的数据,发送给UI界面
sprotocolData可以增加数据变量
在UI的activity中,默认已经创建好了一个串口数据回调接口
static void onProtocolDataUpdate(const SProtocolData &data) {// 串口数据回调接口if (mProtocolData.power != data.power) {mProtocolData.power = data.power;}if (mProtocolData.eRunMode != data.eRunMode) {mProtocolData.eRunMode = data.eRunMode;mbtn_autoPtr->setSelected(mProtocolData.eRunMode == E_RUN_MODE_MANUAL);if (mProtocolData.eRunMode != E_RUN_MODE_MANUAL) {mbtn_external_windPtr->setText(mProtocolData.externalWindSpeedLevel);mbtn_internal_windPtr->setText(mProtocolData.internalWindSpeedLevel);}}...
}
mProtocolData是UI界面默认的数据,在onUI_init的时候进行初始化,通过串口更新
APP层在ProtocolSender.cpp,当APP层需要发送数据到MCU的时候直接调用sendprotocol函数,并在sendProtocol函数中实现
/*** 需要根据协议格式进行拼接,以下只是个模板*/
bool sendProtocol(const UINT16 cmdID, const BYTE *pData, BYTE len) {BYTE dataBuf[256];dataBuf[0] = CMD_HEAD1;dataBuf[1] = CMD_HEAD2; // 同步帧头dataBuf[2] = HIBYTE(cmdID);dataBuf[3] = LOBYTE(cmdID); // 命令字节dataBuf[4] = len;UINT frameLen = 5;// 数据for (int i = 0; i < len; ++i) {dataBuf[frameLen] = pData[i];frameLen++;}#ifdef PRO_SUPPORT_CHECK_SUM// 校验码dataBuf[frameLen] = getCheckSum(dataBuf, frameLen);frameLen++;
#endifreturn UARTCONTEXT->send(dataBuf, frameLen);
}
按下按键发送时
BYTE mode[] = { 0x01, 0x02, 0x03, 0x04 };
sendProtocol(0x01, mode, 4);
相关文章:
Flythings学习(四)串口通信
文章目录 1 串口编程基本步骤1.1 打开串口1.2 配置串口 1.3 读串口1.4 发送串口1.5 关闭串口 2 综合使用3 如何在软件上保证串口稳定通信4 flythings中的串口通讯5 协议接收部分使用和修改方法6 通讯协议数据怎么和UI控件对接 1 串口编程基本步骤 串口通信有5个步骤 1.打开串口…...
[数据结构]带头双向循环链表的实现与应用
文章目录 一、引言二、链表的基本概念1、链表是什么2、链表与顺序表的区别3、带头双向循环链表 三、带头双向循环链表的实现1、结构体定义2、初始化3、销毁4、显示5、数据操作 四、分析带头双向循环链表1、存储方式2、优点3、缺点 五、总结1、练习题2、源代码 一、引言 链表作…...
商品详情数据API接口开发系列(属性规格详情图sku等)
商品详情数据API接口开发是一个复杂但至关重要的过程,它涉及多个方面,包括属性规格、详情图、SKU等关键信息的处理。以下是对该开发系列中这些关键要素的详细探讨: 一、商品详情数据API接口概述 商品详情数据API接口是指一种编程接口&#x…...
在 Ubuntu 上安装 clang-format-14
在 Ubuntu 上安装 clang-format-14 可以通过以下步骤完成: 1. 添加 LLVM 的官方 APT 仓库 首先,你需要添加 LLVM 的官方 APT 仓库,以便能够安装最新版本的 clang-format。 # 安装必要的依赖 sudo apt update sudo apt install -y wget gnu…...
【优选算法篇】双指针的华丽探戈:深入C++算法殿堂的优雅追寻
文章目录 C 双指针详解:进阶题解与思维分析前言第一章:有效三角形的个数1.1 有效三角形的个数示例 1:示例 2:解法一(暴力求解)解法二(排序 双指针)易错点提示代码解读 第二章&#…...
【springboot入门-mvc常用注解使用方式及原理】
常用注解 PathVariable:用于从URL路径中提取变量。RequestHeader:用于从HTTP请求头中获取数据。ModelAttribute:用于获取请求参数(包括URL参数和POST请求的表单数据),也可以用于将数据绑定到对象上。Reque…...
滚雪球学Redis[4.2讲]:Redis Sentinel 深度解析:工作原理、配置与高可用架构下的故障转移
全文目录: 🎉前言🚦4.2 Redis Sentinel🔄Sentinel的工作原理Sentinel的选举机制 ⚙️Sentinel的配置与使用示例:配置Redis SentinelSentinel自动故障转移过程示例 🧩高可用架构下的故障转移常见问题与优化实…...
Vue3 -- 设置分页,切换分页之后选项仍能保留 控制多个表格的选中不会互相影响
在 Vue 3 中实现分页功能,并确保在切换分页时选中的选项能够保留,同时控制多个表格之间的选中状态不互相影响,可以按照以下步骤进行: 1. 数据结构设计 为每个表格维护独立的选中项和分页状态。可以使用一个对象来存储每个表格的…...
如何在 JSON 中编写“anyOf”语句?
在 JSON 中,anyOf 语句通常用于 JSON Schema(JSON 模式)中,来定义多个可能的模式,表示数据可以匹配多个子模式中的任意一个。这种功能常用于验证 JSON 数据是否符合某一组可能的条件之一。 1、问题背景 问题ÿ…...
python开发环境配置
下载python安装包安装python配置环境变量调整类库下载位置 安装python 安装python是指安装python的基础编译环境及python运行所需的必须资源,类似于安装java的JDK python2与python3差异 进行python安装前,需要先了解python2和python3的差异࿰…...
QT开发--QT SQL模块
第十五章 QT SQL模块 15.1 QT SQL模块概览 Qt SQL模块是Qt框架中操作数据库的组件,提供易用API,支持SQLite、MySQL等多种数据库。它包含数据库驱动与连接功能。 15.1.1 QSqlDatabase 类 在Qt SQL模块中,数据库驱动基于QSqlDriver类…...
如何保证接口幂等性?
一、什么是接口幂等性? 幂等性是指:同一请求,执行很多次,最终结果都一样。 二、为什么会产生接口幂等性问题? 那么,什么情况下,会产生接口幂等性的问题呢? 网络波动, 可能会引起重…...
【9718】基于springboot+vue的生鲜交易系统
作者主页:Java码库 主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 项目描述 生鲜交易管理方面的任务繁琐,以至于交易市场每年都在生…...
Spring循环依赖解决方案
解决方案 使用提前暴露机制三级缓存进行解决 singletonObjects一级缓存,存放完整的 Bean。earlySingletonObjects二级缓存,存放提前暴露的Bean,Bean 是不完整的,未完成属性注入和执行 init 方法。singletonFactories三级缓存(用…...
解决 IntelliJ IDEA 运行时 “Command line is too long“ 问题
文章目录 文章标题:解决 IntelliJ IDEA 运行时 "Command line is too long" 问题简介问题描述解决方案代码示例代码示例1:使用JAR Manifest代码示例2:使用Classpath File代码示例3:优化项目依赖 结论进一步的资源 文章标…...
鸿蒙网络编程系列5-TCP连接超时分析
1. TCP连接超时简介 TCP是面向连接的协议,通过三次握手建立连接,但是,在建立连接的过程中对方有可能没有响应,这时候发起连接的一方会重试,如果重试多次仍然没有响应,就会触发超时,从而导致连接…...
金蝶云星空移动字段后关闭页面后重新打开无效
有同事反馈,单据的明细字段里面移动了字段,然后退出,其他字段都能按最后排版的位置显示,有个别字段始终无法按照排版的位置显示。 只需要打开BOS平台,找到对应字段,然后更改可见性。...
幂律分布笔记
一、幂律分布的数据拟合 数据分箱: 所谓分箱就是对原始数据进行分组,然后对每一组内的数据进行平滑处理。常见的分箱方式主要有等深分箱、等宽分箱、用户自定义等 对数分箱: 对原数据进行分箱,第i个箱的宽度为bi,b…...
一些NLP代表性模型
(一)BERT 由Bidirectional Encoder Representations from Transformers的首字母组成,是encoder-only结构类型的代表。 模型分预训练和微调两步,预训练任务有两类:masked language model(MLM)、next sentence predict…...
低代码移动端开发:未来的趋势与挑战
什么是低代码移动端开发? 低代码移动端开发平台允许开发者通过可视化界面和少量编码来构建应用程序。相较于传统的代码开发,低代码平台大大降低了技术和学习门槛,使非专业开发人员也能参与到移动应用的开发过程中。 低代码移动端开发的优势 …...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
