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);

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

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

相关文章

YOLOv11模型改进-注意力机制-引入自适应稀疏自注意力ASSA

随着目标检测领域的快速发展&#xff0c;YOLO系列模型凭借其端到端、高效的检测性能逐渐成为工业界和学术界的标杆。然而&#xff0c;如何进一步优化YOLOv11的特征提取能力&#xff0c;减少冗余信息并提升模型对复杂场景的适应性&#xff0c;仍是一个值得深入探讨的问题。为此&…

Android:记录一个打包发布版的release包以后闪退的问题

个人感觉其实release闪退的问题挺难排查的&#xff0c;因为release包运行起来as捕获不到相应的应用程序进程&#xff0c;从而不易查看到日志&#xff0c;也是我玩得不溜&#xff0c;大家有不同的方法可以评论区探讨&#xff0c;我也定期回复一些评论一起讨论。以下是我遇到的情…

【数据结构】宜宾大学-计院-实验三

线性表的应用——实现两多项式的相加 课前准备&#xff1a;实验学时&#xff1a;2实验目的&#xff1a;实验内容&#xff1a;实验结果&#xff1a;实验报告:&#xff08;及时撰写实验报告&#xff09;实验测试结果&#xff1a;代码实现&#xff1a;&#xff08;C/C&#xff09;…

安宝特方案 | AR技术在轨交行业的应用优势

随着轨道交通行业不断向智能化和数字化转型&#xff0c;传统巡检方式的局限性日益凸显。而安宝特AR眼镜以其独特的佩戴方式和轻便设计&#xff0c;为轨道交通巡检领域注入了创新活力&#xff0c;提供了全新的解决方案。 01 多样化佩戴方法&#xff0c;完美适应户外环境 安宝特…

访问控制列表(课内实验)

实验2&#xff1a;访问控制列表 实验目的及要求&#xff1a; 通过实验&#xff0c;进一步的理解标准ACL与扩展ACL的工作原理及执行过程。理解通配符的概念&#xff0c;熟练掌握标准ACL与扩展ACL的配置指令&#xff0c;掌握将访问控制列表应用VTY线路上&#xff0c;并且能够判断…

鸿蒙开发 四十五 鸿蒙状态管理(嵌套对象界面更新)

当运行时的状态变量变化&#xff0c;UI重新渲染&#xff0c;在ArkUI中称为状态管理机制&#xff0c;前提是变量必须被装饰器修饰。不是状态变量的所有更改都会引起刷新&#xff0c;只有可以被框架观测到的更改才会引起UI刷新。其中boolen、string、number类型&#xff0c;可观察…

Oracle漏洞修复 19.3 补丁包 升级为19.22

1.场景描述 上周末2024-10-12日,服务器扫出漏洞,希望及时修复。其中,oracle的漏洞清单如下,总结了下,基本都是 Oracle Database Server 的 19.3 版本到 19.20 版本和 21.3 版本到 21.11 版本存在安全漏洞,即版本问题。如: Oracle Database Server 安全漏洞(CVE-2023-22…

Spring AI Java程序员的AI之Spring AI(一)

SpringAI 基础使用 前言Spring AIChatClientImageClientOpenAiAudioTranscriptionClientEmbeddingClient 总结 前言 Spring AI&#xff0c;听着名字就感觉很好使用&#xff0c;快速上手&#xff0c;虽然功能没有太完善&#xff0c;但是社区活跃度很高&#xff0c;可以看看源码…

低代码框架参考

企业管理信息系统作为一类重要的应用软件系统&#xff0c;具有自己的特点&#xff0c;主要有两个方面&#xff1a; 1. 系统规模大&#xff0c;目前市场上常见的ERP系统一般都有几千个页面。 2. 页面逻辑相似性强。经过比较可以发现&#xff0c;大部分页面具有类似的功能&…

Docker新手必看:快速安装和配置BookStack在线文档系统

文章目录 前言1. 安装Docker2. Docker镜像源添加方法3. 创建并启动BookStack容器4. 登录与简单使用5. 公网远程访问本地BookStack5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 前言 本文主要介绍如何在Linux系统使用Docker本地部署在线文档管理…

【c++篇】:初识c++--编程新手的快速入门之道(二)

文章目录 前言一.引用1.引用的概念2.引用的特性3.引用的使用场景4.常引用5.引用和指针的区别 二.内联函数1.C语言的宏函数2.内联函数的概念3.内联函数的特性 三.auto关键字1.auto的定义2.auto的使用规则3.auto不能推导的场景 四.基于范围的for循环1.范围for的语法2.范围for的使…

时间序列预测(七)——梯度消失(Vanishing Gradient)与梯度爆炸(Exploding Gradient)

目录 一、定义 二、产生原因 三、解决方法&#xff1a; 梯度消失与梯度爆炸是深度学习中常见的训练问题&#xff0c;它们主要发生在神经网络的反向传播过程中&#xff0c;使得模型难以有效学习。 一、定义 1、梯度消失&#xff08;Vanishing Gradient&#xff09;&#xf…

PCL 点云配准-改进的RANSAC算法(粗配准)

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 计算FPFH特征 2.1.2 RANSAC配准 2.1.3 可视化点云 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff0…

FlinkCDC 实现 MySQL 数据变更实时同步

文章目录 1、基本介绍2、代码实战2.1、数据源准备2.2、代码实战2.3、数据格式 1、基本介绍 Flink CDC 是 Apache Flink 提供的一个功能强大的组件&#xff0c;用于实时捕获和处理数据库中的数据变更。可以实时地从各种数据库&#xff08;如MySQL、PostgreSQL、Oracle、MongoDB…

【图论】(一)图论理论基础与岛屿问题

图论理论基础与岛屿问题 图论理论基础深度搜索&#xff08;dfs&#xff09;广度搜索&#xff08;bfs&#xff09;岛屿问题概述 岛屿数量岛屿数量-深搜版岛屿数量-广搜版 岛屿的最大面积孤岛的总面积沉没孤岛建造最大人工岛水流问题岛屿的周长 图论理论基础 这里仅对图论相关核…

java学习--集合(大写五.5)

5.collection子接口&#xff1a;Set 5.1Set及其实现类特点 java.util.Collection:存储一个一个的数据 子接口&#xff1a;Set:存储无序的、不可重复的数据(高中学习的集合) 1&#xff09;HashsSet主要实现类 底层使用的是HashMap&#xff0c;即使用数组单向链表红黑树结构进行…

netron安装(windows linux)

目录 netron简介 不同操作系统的安装方式 linux windows mac系统 netron简介 netron可视化工具&#xff0c;是一个可以清晰的看到神经网络模型的每一层的输入输出&#xff0c;网络总体的架构&#xff0c;而且支持各种不同网络框架&#xff0c;简单好用。 效果如下所示 不…

【视频笔记】408新增知识点信号——里昂视频

文章目录 **2.信号****3.信号的实现**4.信号的处理**①忽略信号****②执行信号的默认操作****③捕获井处理信号**几个Linux支持的典型信号&#xff1a; **5.信号的产生**① **通过终端按键(键盘)产生信号**例如&#xff0c;CtrlC发送2号信号SIGINT、Ctrl\发送3号信号SIGQUIT② …

爬虫逆向-js进阶

1.作用域和闭包 //作用域 // var a 3 // // function test(a){ // var a 1; // console.log(函数内部,a) // } // test(2) // // console.log(a)//闭包 // function jiami(){ // function encrypt(){ // console.log(在这里进行加密了) // } // p…

五个必备的高清无水印视频素材库推荐

做抖音、短视频创作的朋友都知道&#xff0c;优质的素材往往决定了作品能否获得更多关注。如果你还不知道在哪里下载高清无水印的视频素材&#xff0c;不用担心&#xff01;今天为你推荐5个高品质的视频素材库&#xff0c;助你轻松创作出爆款视频。 蛙学网 是国内领先的视频素材…