串口数据包收发

news2025/1/10 23:41:21

数据包

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

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.coloradmin.cn/o/1057435.html

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

相关文章

计算机视觉——飞桨深度学习实战-图像分类算法原理与实战

基础理论&#xff1a; 图像分类是深度学习在视觉领域第一个取得突破性成果的任务。本章首先介绍了图像分类任务的发展历程与评价指标。然后分为三个角度分别介绍了在图像分类领域具有重要地位的三种模型。第一种是基于残差网络的模型&#xff0c;本章重点介绍了ResNet、DenseN…

PCB铺铜连接方式

在铺铜前先把栅格吸附关闭铺铜会流畅很多 在嘉立创专业版中&#xff0c;默认铺铜方式是这样 改变铺铜规则为直连 效果如下

基于SpringBoot的补习班线上报名系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

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

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

开发中的前端和后端

一、引言 前端和后端是Web开发中两个不同的领域。 前端开发主要负责实现用户界面的设计和功能&#xff0c;包括网页的布局、样式和交互效果。前端开发使用HTML、CSS和JavaScript等技术来构建用户在浏览器中直接与之交互的界面。前端开发人员需要关注网页的可视化效果和用户体验…

【密评】商用密码应用安全性评估从业人员考核题库(三)

商用密码应用安全性评估从业人员考核题库&#xff08;三&#xff09; 国密局给的参考题库5000道只是基础题&#xff0c;后续更新完5000还会继续更其他高质量题库&#xff0c;持续学习&#xff0c;共同进步。 501 多项选择题 《个人信息保护法》要求个人信息处理者应当采取哪些…

[C语言经典100例题-67】(指针解决)输入数组,最大的与第一个元素交换,最小的与最后一个元素交换,输出数组

代码 下面是使用指针解决的代码示例&#xff1a; #include <stdio.h>void swap(int *a, int *b) {int temp *a;*a *b;*b temp; }int main() {int arr[100], n, max_index 0, min_index 0;printf("Enter the size of the array: ");scanf("%d"…

堆优化迪氏最短单源路径原理及C++实现

时间复杂度 O(ElogE)&#xff0c;E是边数。适用与稀疏图。 使用前提 边的权为正。可以非连通&#xff0c;非连通的距离为-1。 原理 优选队列&#xff08;小根堆&#xff09;记录两个数据&#xff1a;当前点到源点距离&#xff0c;当前点。先处理距离小的点&#xff1b;如果…

数据在内存中的存储(一个新手的理解)

1.整数在内存中的存储 正整数的原&#xff0c;补&#xff0c;反码都相同。 负整数的三种表示方法各不相同。 提示&#xff1a;负数的反码等于原码符号位不变&#xff0c;其他位置的二进制位取反。 负数的补码等于反码1. 对于整型来说&#xff1a;数据存放在内存中其实存放的是…

【软件测试】自动化测试selenium(一)

文章目录 一. 什么是自动化测试二. Selenium的介绍1. Selenium是什么2. Selenium的特点3. Selenium的工作原理4. SeleniumJava的环境搭建 一. 什么是自动化测试 自动化测试是指使用软件工具或脚本来执行测试任务的过程&#xff0c;以替代人工进行重复性、繁琐或耗时的测试活动…

C++(List)

本节目标&#xff1a; 1.list介绍及使用 2.list深度剖析及模拟实现 3.list和vector对比 1.list介绍及使用 1.1list介绍 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向链表结构&#xff0c;…

[React] react-redux基本使用

文章目录 1.redux2.安装redux3.操作redux3.1 创建最为核心的store3.2 创建为store工作的reducer3.3 redux的响应式处理 4.完整版redux4.1 完善actionCreators4.2 thunk中间件 5.react-redux5.1 Count容器组件5.2 connect函数5.3 Provider 1.redux redux原理图 actionCreators:…

司空见惯 - 奈尔宝的NTTP

联合国对21世纪人才定义的标准&#xff0c;包括六种核心技能&#xff0c;即批判性思维&#xff08;critical thinking)、人际交往&#xff08;communication)、与人合作&#xff08;collaboration)、创造性&#xff08;creativity)、信息素养&#xff08;information literacy)…

【中国知名企业高管团队】系列25:360

今天华研荟的“走进中国知名企业高管团队系列”带大家走进360——这是少数以数字作为产品名称的公司&#xff0c;如果您在网上看到有人说“数字公司”&#xff0c;那么大概率指的就是360公司。 360公司正式的名称是三六零安全科技股份有限公司&#xff0c;可以说是中国覆盖面最…

数据结构: 数组与链表

目录 1 数组 1.1 数组常用操作 1. 初始化数组 2. 访问元素 3. 插入元素 4. 删除元素 5. 遍历数组 6. 查找元素 7. 扩容数组 1.2 数组优点与局限性 1.3 数组典型应用 2 链表 2.1 链表常用操作 1. 初始化链表 2. 插入节点 3. 删除…

正确完成实时 AI

发表于 构建真实世界的实时 AI 一、说明 我们知道&#xff0c;当前的AI进展是扎根于历史数据&#xff0c;这就造成一个事实&#xff0c;模型总是赶不上实时进展&#xff0c;模型的洞察力不够尖锐&#xff0c;或者&#xff0c;时间损失等&#xff0c;本篇对这一系列AI的短板展开…

【初识Linux】:常见指令(1)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux的基础知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…

WebSocket基础——WebSocket的基本概念 VS Http SpringBoot整合WebSocket vue前端代码和效果展示

前言 WebSocket是一种在Web浏览器和服务器之间进行全双工通信的协议。它允许在单个TCP连接上进行双向通信&#xff0c;而不需要通过多个HTTP请求-响应循环来实现。相比传统的HTTP请求&#xff0c;WebSocket提供了更低的延迟和更高的实时性。 本篇博客介绍WebSocket的基本概念…

计算机网络(四):网络层

参考引用 计算机网络微课堂-湖科大教书匠计算机网络&#xff08;第7版&#xff09;-谢希仁 1. 网络层概述 网络层的主要任务是实现网络互连&#xff0c;进而实现数据包在各网络之间的传输 要实现网络层任务&#xff0c;需要解决以下主要问题 网络层向运输层提供怎样的服务 (“…

基于SpringBoot的养老监护管理平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…