串口数据包收发

数据包

把属于同一批的数据进行打包和分割,方便接收方进行识别

HEX数据包

思路:一个数据规定四个字节,以0xFF为包头,0xFE为包尾,当检测到0xFF时,接下来四个数据就是数据,接收到0xFE时,置一个接收完毕标志位。

 这样存在几个问题需要解决:

问题1:包头包尾和数据载荷重复的问题

解决方法:

一、限制载荷数据的范围,不超过包头包尾

二、严格限制数据包的长度

三、增加包头包尾的数量,且组合方式为载荷数据不会出现的情况

问题2:包头包尾并不是全都需要的,可以只要包头不要包尾(只能用于固定包长的情况)

问题3:各种数据转换为字节流的问题,这里的数据包都是一个个字节组成的,如果想发送16位的整形数据,32位的整形数据,float,double,甚至是结构体,其实都没问题,因为他们内部也都是由一个个字节组成的,只需要用uint8_t的指针指向它,把它们当做一个字节数组发送就行了

文本数据包

两者优缺点 

HEX数据包:传输最直接,解析数据非常简单,比较适合一些模块发送原始的数据,比如一些使用串口通信的陀螺仪、温湿度传感器,缺点是灵活性不足、载荷容易和包头包尾重复;

文本数据包:数据直观,易理解,非常灵活,比较适合一些输入指令进行人机交互的场合,比如蓝牙模块常用的AT指令,CNC和3D打印机常用的G代码,缺点是解析效率低

数据包的发送

HEX数据包的发送:定义一个数组,填充数据,然后用Send函数一发即可

文本数据包的发送:定义一个字符串.......

数据包的接收

如何接收固定包长的HEX数据包

 在之前的代码中,串口每接收到一个数据,程序都会进入一个中断,在中断中获取到这一个字节,在这之后会退出中断,所以每拿到一个数据,都是一个独立的过程,对于数据包来说,很明显它具有前后关联性——包头之后是数据,数据之后是包尾,对应包头,数据和包尾这三种状态,我们都需要有不同的处理逻辑,在程序中我们需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移,这种程序思维被称为“状态机”。

如图是状态转移图,我们设定三种状态——1、等待包头,2、接收数据,3、等待包尾

等待包头状态下,S=0,直到接收到0xFF时,把S置1,然后进入接收数据状态,再然后直到收集满4个数据,并把数据存储到数组中后,把S置2,然后进入等待包尾状态,直到接收到包尾0xFE后,把S置0,进入等待包头状态。

不固定包长的文本数据包接收

代码实操 

串口收发HEX数据包

发:

//发送数据包
void Serial_SendPacket(void)
{Serial_SendByte(0xFF);Serial_SendArray(Serial_TXPacket, 4);Serial_SendByte(0xFE);
}
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"uint16_t Data;int main(void)
{OLED_Init();Serial_Init();Serial_TXPacket[0]=0x01;Serial_TXPacket[1]=0x02;Serial_TXPacket[2]=0x03;Serial_TXPacket[3]=0x04;Serial_SendPacket();while(1){}
}

收:

uint8_t Serial_RXFlag;
uint16_t Serial_TXPacket[4];
uint16_t Serial_RXPacket[4];//用于获取自建的标志位
uint8_t Serial_GetRXFlag(void)
{if (Serial_RXFlag == 1){//检测标志位位1后立马清零//以便下次获取串口接收值Serial_RXFlag = 0;return 1;}return 0;
}//中断函数
void USART1_IRQHandler(void)
{static uint8_t RXState = 0;static uint8_t pRXPacket = 0;if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET){uint8_t RXData = USART_ReceiveData(USART1);if (RXState == 0){if (RXData == 0xFF){RXState = 1;//给接收数据的数组下标清零//在这清零能保证每次接收数据时下标正确pRXPacket = 0;}}else if (RXState == 1){//把接收到的数据存入数组中Serial_RXPacket[pRXPacket] = RXData;pRXPacket ++;if (pRXPacket >= 4){RXState = 2;}}else if (RXState == 2){if (RXData == 0xFE){RXState = 0;Serial_RXFlag = 1;}}USART_ClearITPendingBit(USART1, USART_IT_RXNE);}
}

两个数组还要在.h文件中加上extern,以便在主函数中直接修改或者调用其值

 

即使载荷数据和包头包尾重复都没有影响

隐藏的问题

RXPacket是一个同时被写入又同时被读出的数组,在中断函数中,我们会依次写入它,在主函数中,我们又依次读出它,这会造成数据包之间的数据混在一起,比如读出的过程太慢了,可能会造成前面两个数据是新的,后面两个数据是之前的数据,即我们读出的数据可能一部分属于上一个数据包,解决办法:在接收部分加入判断,在每个数据包读取处理完毕后,再接收下一个数据包(线程安全),但其实这个问题也是相对实际情况而言的,可以不处理,也可能必须要处理。

再添加一个功能——按下按键TXPacket中的数据都+1,用于检测发出数据包程序是否正确运行

在主函数中修改即可,顺便优化代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"uint16_t Data;int main(void)
{OLED_Init();Key_Init();Serial_Init();OLED_ShowString(1, 1, "TXPacket");OLED_ShowString(3, 1, "RXPacket");	Serial_TXPacket[0]=0x01;Serial_TXPacket[1]=0x02;Serial_TXPacket[2]=0x03;Serial_TXPacket[3]=0x04;Serial_SendPacket();while(1){if (Key_GetNum()==1){Serial_TXPacket[0]++;Serial_TXPacket[1]++;Serial_TXPacket[2]++;Serial_TXPacket[3]++;Serial_SendPacket();OLED_ShowHexNum(2, 1, Serial_TXPacket[0], 2);OLED_ShowHexNum(2, 4, Serial_TXPacket[1], 2);OLED_ShowHexNum(2, 7, Serial_TXPacket[2], 2);OLED_ShowHexNum(2, 10, Serial_TXPacket[3], 2);}if (Serial_GetRXFlag() == 1){OLED_ShowHexNum(4, 1, Serial_RXPacket[0], 2);OLED_ShowHexNum(4, 4, Serial_RXPacket[1], 2);OLED_ShowHexNum(4, 7, Serial_RXPacket[2], 2);OLED_ShowHexNum(4, 10, Serial_RXPacket[3], 2);}}
}

就可以实现按一下按钮,串口就输出一组数据包,且这组数据包每个数据逐渐递增

然后在发送区发送的数据在OLED中显示

串口收发文本数据包

接收数据包直接使用SendString即可,所以可以把TXPacket相关函数删去

然后修改一下中断函数的判断条件

 

//以下皆用于接收数据包
uint8_t Serial_RXFlag;
char Serial_RXPacket[100];
//用于获取自建的标志位
uint8_t Serial_GetRXFlag(void)
{if (Serial_RXFlag == 1){//检测标志位位1后立马清零//以便下次获取串口接收值Serial_RXFlag = 0;return 1;}return 0;
}//中断函数
void USART1_IRQHandler(void)
{static uint8_t RXState = 0;static uint8_t pRXPacket = 0;if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET){char RXData = USART_ReceiveData(USART1);if (RXState == 0){if (RXData == '@'){RXState = 1;//给接收数据的数组下标清零//在这清零能保证每次接收数据时下标正确pRXPacket = 0;}}else if (RXState == 1){if (RXData == '\r'){RXState = 2;}else{//把接收到的数据存入数组中Serial_RXPacket[pRXPacket] = RXData;pRXPacket ++;}}else if (RXState == 2){if (RXData == '\n'){RXState = 0;//标志位'\0'标志着字符串的结束Serial_RXPacket[pRXPacket] = '\0';Serial_RXFlag = 1;}}USART_ClearITPendingBit(USART1, USART_IT_RXNE);}
}

主函数中实验一下

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include <string.h>int main(void)
{OLED_Init();LED_Init();Serial_Init();OLED_ShowString(1, 1, "TXPacket");OLED_ShowString(3, 1, "RXPacket");	while(1){if (Serial_GetRXFlag() == 1){OLED_ShowString(4, 1, "                ");OLED_ShowString(4, 1, Serial_RXPacket);}}
}

接下来就应该实现通过串口输入文本来控制LED的亮灭,并通过OLED上有所反映

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include <string.h>int main(void)
{OLED_Init();LED_Init();Serial_Init();OLED_ShowString(1, 1, "TXPacket");OLED_ShowString(3, 1, "RXPacket");	while(1){if (Serial_GetRXFlag() == 1){OLED_ShowString(4, 1, "                ");OLED_ShowString(4, 1, Serial_RXPacket);if(strcmp(Serial_RXPacket, "LED_ON") == 0){LED_On();OLED_ShowString(2, 1, "                ");OLED_ShowString(2, 1, "LED_ON_OK");Serial_SendString("LED_ON_OK\r\n");}else if (strcmp(Serial_RXPacket, "LED_OFF") == 0){LED_Off();OLED_ShowString(2, 1, "                ");OLED_ShowString(2, 1, "LED_OFF_OK");Serial_SendString("LED_OFF_OK\r\n");}else{OLED_ShowString(2, 1, "                ");OLED_ShowString(2, 1, "Error");Serial_SendString("Error\r\n");}}}
}

这样就可以实现目标了

但是还会有个问题,如果连续发送数据包,程序处理不及时,可能会导致数据包错位,这时候我们就需要添加一个程序使其在上一个数据包未处理完成,就不接受新的数据包的功能。

只需把Serial_GetRXFlag(void)函数删去,在中断函数的第一个判断语句中修改为

    if (RXData == '@' && Serial_RXFlag == 0){RXState = 1;//给接收数据的数组下标清零//在这清零能保证每次接收数据时下标正确pRXPacket = 0;}

然后再在主函数if条件修改为

if (Serial_RXFlag == 1)

然后再在其最后添加

Serial_RXFlag = 0;

即可

或者可以再定义一个指令缓存区,把接收好的字符串放在这个指令缓存区里排队,这样处理起来更有条理

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Win10系统中GPU深度学习环境配置记录

运行环境 系统&#xff1a;Win10 处理器 Intel(R) Core(TM) i7-9700K CPU 3.60GHz 3.60 GHz 机带 RAM 16.0 GB 设备 ID A18D4ED3-8CA1-4DC6-A6EF-04A33043A5EF 产品 ID 00342-35285-64508-AAOEM 系统类型 64 位操作系统, 基于 x64 的处理器 显卡&#xff1a;NVIDIA GeF…

pycharm一直没显示运行步骤,只是出现waiting for process detach

pycharm一直没显示运行步骤&#xff0c;只是出现waiting for process detach&#xff1b;各类音乐免费软件&#xff1b;最棒的下载torch-geometric-CSDN博客&#xff08;不太推荐&#xff09;我强烈推荐这个&#xff1a;_waiting for process detachhttps://blog.csdn.net/weix…

2023年汉字小达人市级比赛在线模拟题来了,四种练习助力好成绩

2023年第十届汉字小达人比赛区级自由报名活动已于9月30日结束&#xff0c;尽管最终晋级市级比赛的名单还需要在11月初发布&#xff08;有一些学校的校级选拔还没结束&#xff09;&#xff0c;但是许多小朋友已经开始准备市级比赛了。 根据往年的经验&#xff0c;实际比赛也是在…

Android 活动Activity

目录 一、启停活动页面1.1 Activity的启动和结束1.2 Activity的生命周期1.3 Activity的启动模式 二、在活动之间传递消息2.1 显式Intent和隐式Intent2.2 向下一个Activity发送数据2.3 向上一个Activity返回数据 三、补充附加信息3.1 利用资源文件配置字符串3.2 利用元数据传递配…

国庆加速度!新增功能点锁定功能,敏捷开发新增估算功能,助力项目快速突破!

大家好&#xff0c;CoCode开发云旗下Co-Project V3.6智能项目管理平台正式发布&#xff0c;平台新增功能点锁定功能、敏捷开发模式新增估算板块和两种估算方式。 功能点锁定功能进一步提高了项目估算的灵活性和准确性&#xff0c;有利于提高项目估算效率&#xff1b;而敏捷开发…

数据分析:人工智能篇

文章目录 第三章 数据可视化库matplotlib3.1 matplotlib基本绘图操作3.2 plot的线条和颜色3.3 条形图分析3.4 箱型图分析3.5 直方图分析3.6 散点图分析3.7 图表的美化 第四章 数据预测库Sklearn4.1 sklearn预测未来4.2 回归数据的预测4.2.1 回归数据的切分4.2.2 线性回归数据模…

【小尘送书-第六期】《巧用ChatGPT轻松玩转新媒体运营》AI赋能运营全流程,帮你弯道超车、轻松攀登运营之巅

大家好&#xff0c;我是小尘&#xff0c;欢迎你的关注&#xff01;大家可以一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; &#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1…

云原生数据库TDSQL-C

数据库系统核心模块 云原生数据库要解决什么问题&#xff1f; HTAP 云数据库VS云原生数据库

《C++ primer plus》精炼(OOP部分)——对象和类(8)

学习是一项持续的投资&#xff0c;永远不会白费——本杰明富兰克林 文章目录 第13章&#xff1a;类继承一个基类和派生类公有继承的逻辑关系&#xff1a;is-a多态公有继承 第13章&#xff1a;类继承 一个基类和派生类 从一个类派生出另一个类时&#xff0c;原始类称为基类&am…

怎么通过portainer部署一个vue项目

这篇文章分享一下今天通过docker打包vue项目&#xff0c;并使用打包的镜像在portainer上部署运行&#xff0c;参考了vue-cli和docker的官方文档。 首先&#xff0c;阅读vue-cli关于docker部署的说明 vue-cli关于docker部署的说明https://cli.vuejs.org/guide/deployment.html#…

信创办公–基于WPS的EXCEL最佳实践系列 (数据整理复制粘贴)

信创办公–基于WPS的EXCEL最佳实践系列 &#xff08;数据整理复制粘贴&#xff09; 目录 应用背景操作步骤1、数据查找与替换2、复制或粘贴数据3、使用自动填充工具4、将数据拆分到多列5、应用数字格式 应用背景 数据的整理复制粘贴等在日常的工作中经常使用。本章内容主要学习…

Codeforces Round 665 (Div. 2) (A-F)

A.Problem - A - Codeforces &#xff08;1&#xff09;题意 给你个X轴&#xff0c;初始A点在n这个位置&#xff0c;O在源点0&#xff0c;问你要把B放在哪才能让|AB-BO| k&#xff0c;最小化A需要移动多少次。 &#xff08;2&#xff09;思路 直接分情况套路即可。 &#xff0…

MySQL约束

文章目录 简单介绍主键约束添加单列主键多列主键删除主键 自增长约束(auto_increment)语法&#xff1a;指定自增字段初始值 非空约束唯一约束(unique)默认约束(default)零填充约束(zerofill) 简单介绍 概念&#xff1a;表中数据的约束条件 作用&#xff1a;表在设计的时候加入…

用AI原生向量数据库Milvus Cloud 搭建一个 AI 聊天机器人

搭建聊天机器人 一切准备就绪后,就可以搭建聊天机器人了。 文档存储 机器人需要存储文档块以及使用 Towhee 提取出的文档块向量。在这个步骤中,我们需要用到 Milvus。 安装轻量版 Milvus Lite,使用以下命令运行 Milvus 服务器: (chatbot_venv) [egoebelbecker@ares milvus_…

Python与Scrapy:构建强大的网络爬虫

网络爬虫是一种用于自动化获取互联网信息的工具&#xff0c;在数据采集和处理方面具有重要的作用。Python语言和Scrapy框架是构建强大网络爬虫的理想选择。本文将分享使用Python和Scrapy构建强大的网络爬虫的方法和技巧&#xff0c;帮助您快速入门并实现实际操作价值。 一、Pyt…

QSS之QComboBox

QComboBox在Qt开发过程中经常使用&#xff0c;默认的下载列表风格达不到设计师的要求&#xff0c;本篇介绍基本的QComboBox的qss设置。 属性意思QComboBoxQComboBox基本样式QComboBox:editable右边可选择按钮QComboBox:!editable, QComboBox::drop-down:editable不可编辑或下拉…

从“概念”到“应用”,字节跳动基于 DataLeap 的 DataOps 实践

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 近日&#xff0c;火山引擎数智平台 VeDI Meetup「超话数据」在深圳举办&#xff0c;来自火山引擎的产品专家分享了字节跳动基于 DataLeap 的 DataOps 实践&#xff…

Audacity 使用教程:轻松录制、编辑音频

Audacity 使用教程&#xff1a;轻松录制、编辑音频 1. 简介 Audacity 是一款免费、开源且功能强大的音频录制和编辑软件。它适用于 Windows、Mac 和 Linux 等多种操作系统&#xff0c;适合音乐制作、广播后期制作以及普通用户进行音频处理。本教程将带领大家熟悉 Audacity 的…

管道-匿名管道

一、管道介绍 管道&#xff08;Pipe&#xff09;是一种在UNIX和类UNIX系统中用于进程间通信的机制。它允许一个进程的输出直接成为另一个进程的输入&#xff0c;从而实现数据的流动。管道是一种轻量级的通信方式&#xff0c;用于协调不同进程的工作。 1. 创建和使用管道&#…

机器人中的数值优化|【七】线性搜索牛顿共轭梯度法、可信域牛顿共轭梯度法

机器人中的数值优化|【七】线性搜索牛顿共轭梯度法、可信域牛顿共轭梯度法 Line Search Newton-CG, Trust Region Newton-CG 往期回顾 机器人中的数值优化|【一】数值优化基础 机器人中的数值优化|【二】最速下降法&#xff0c;可行牛顿法的python实现&#xff0c;以Rosenbro…