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…...

低代码移动端开发:未来的趋势与挑战
什么是低代码移动端开发? 低代码移动端开发平台允许开发者通过可视化界面和少量编码来构建应用程序。相较于传统的代码开发,低代码平台大大降低了技术和学习门槛,使非专业开发人员也能参与到移动应用的开发过程中。 低代码移动端开发的优势 …...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...