串口数据包收发

数据包

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

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.mzph.cn/news/93374.shtml

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

相关文章

FFMPEG 视频类过滤器学习整理

addroi 作用 在视频帧上标记一块感兴趣的区域。 帧数据被原封不动地传递&#xff0c;但元数据被附加到帧&#xff0c;指示可能影响后续编码行为的感兴趣区域。可以通过多次应用过滤器来标记多个区域。 参数 qoffset: 应用在此区域的量化偏移。 参数范围&#xff1a;-1 ~ …

【JVM】第五篇 垃圾收集器G1和ZGC详解

导航 一. G1垃圾收集算法详解1. 大对象Humongous说明2. G1收集器执行一次GC运行的过程步骤3. G1垃圾收集分类4. G1垃圾收集器参数设置5. G1垃圾收集器的优化建议6. 适合使用G1垃圾收集器的场景?二. ZGC垃圾收集器详解1. NUMA与UMA2. 颜色指针3. ZGC的运作过程4. ZGC垃圾收集器…

开发中的前端和后端

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

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

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

排序算法之【希尔排序】

&#x1f4d9;作者简介&#xff1a; 清水加冰&#xff0c;目前大二在读&#xff0c;正在学习C/C、Python、操作系统、数据库等。 &#x1f4d8;相关专栏&#xff1a;C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。 欢迎点赞 &#x1f44d…

[管理与领导-112]:IT人看清职场中的隐性规则 - 9 - 付出与回报的关系:先付出,后回报,不行就止损,这才是职场价值交换的本质

目录 一、职场中付出与回报的先后关系 二、付出与回报四象限模型 三、职场专业性的本质 一、职场中付出与回报的先后关系 在职场中&#xff0c;个人的付出和回报之间存在着先后关系。以下是按照先后关系划分的四种类型&#xff1a; 先付出后回报型&#xff0c;不回报&#…

【数据结构和算法】--N叉树中,返回某些目标节点到根节点的所有路径

目录 一、前言二、具体实现及拓展2.1、递归-目标节点到根节点的路径数据2.2、list转换为tree结构2.3、tree转换为list结构 一、前言 这么多年工作经历中&#xff0c;“数据结构和算法”真的是超重要&#xff0c;工作中很多业务都能抽象成某种数据结构问题。下面是项目中遇到的…

华为云云耀云服务器L实例评测|云耀云服务器L实例部署Gogs服务器

华为云云耀云服务器L实例评测&#xff5c;云耀云服务器L实例部署Gogs服务器 一、云耀云服务器L实例介绍1.1 云耀云服务器L实例简介1.2 云耀云服务器L实例特点 二、Gogs介绍2.1 Gogs简介2.2 Gogs特点 三、本次实践介绍3.1 本次实践简介3.2 本次环境规划 四、远程登录华为云云耀云…

基于Java的无人仓库自动补货管理平台设计与实现(源码+lw+部署文档+讲解等)

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

【DRAM存储器九】SDRAM介绍-read、write、Precharge、DQM、Power down、Clock Suspend命令

&#x1f449;个人主页&#xff1a;highman110 &#x1f449;作者简介&#xff1a;一名硬件工程师&#xff0c;持续学习&#xff0c;不断记录&#xff0c;保持思考&#xff0c;输出干货内容 参考资料&#xff1a;《镁光SDRAM数据手册》、《PC SDRAM specification》 目录…

[Linux] 5.Linux虚拟机和Windows文件共享

一、拖拽 如果安装了VMware Tool可以从Windows直接拖进Linux中共享文件&#xff0c;通过拖拽的方式可以把文件从Linux 传输到Windows 二、 文件共享 需要安装VMware Tool点击添加&#xff0c;选择Windows文件的路径&#xff0c;名称作为Linux访问的路径 cd什么都不加&#xff…

<C++> STL_bitset使用和模拟实现

bitset的介绍 位图的引入 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中&#xff1f; 要判断一个数是否在某一堆数中&#xff0c;我们可能会想到如下方法&#xff1a; 将这一堆数进行排序&#xff0…

【python海洋专题九】Cartopy画地形等深线图

【python海洋专题九】Cartopy画地形等深线图 水深图基础差不多了&#xff0c;可以换成温度、盐度等 本期加上等深线 本期内容 1&#xff1a;地形等深线 cf ax.contour(lon, lat, ele[:, :], levelsnp.linspace(-9000,-100,10),colorsgray, linestyles-,linewidths0.25, t…

用python表格初级尝试

Excel&#xff0c;我的野心 当我输入3,2 就表示在第3行第2列。的单元格输入数据input输入表头 &#xff08;input内除了/&#xff0c;空格 回车 标点符号等 全部作为单元格分隔符&#xff09;由我设置input输入的是行or列 给选项 1. 行 2. 列默认回车或没输入值是列由我设置起…

数据结构 B树 B+树 B*树 特性与规则说明 图解

文章目录 前言B树基本规则B树的数据插入&#xff08;文字描述图解&#xff09;B树数据查找B树效率分析B树的作用B树基本规则B树 与 B树对比B*树基本规则B*树 与 B树对比拓展 前言 B树基本规则 每个节点最多有m个子节点&#xff0c;其中m是一个正整数。根节点除外&#xff0c;其…

聊聊并发编程——线程池

目录 Java线程池 处理流程 线程池主要参数 常见的拒绝策略 execute和submit区别 关闭线程池 常见的线程池 newSingleThreadExecutor newFixedThreadPool newCachedThreadPool newScheduledThreadPool 线程池的状态 Java线程池 运用场景最多的并发框架&#xff0c;…

阿里巴巴K8S集成seata

正文 在K8S集成seata&#xff0c;官方配置 代码 apiVersion: v1 kind: Service metadata:name: seata-servernamespace: wmz-devlabels:k8s-app: seata-server spec:type: NodePortports:- port: 8091nodePort: 30091protocol: TCPname: httpselector:k8s-app: seata-server-…

Java练习题-键盘录入字符串实现大小写转换

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;Java练习题 &#x1f4ac;个人格言&#xff1a;不断的翻越一座又…

idea清空缓存类

解决办法 网上有很多是让你去清空什么maven依赖&#xff0c;但假如这个项目是你不可以大刀阔斧的话 可以清空idea缓存 选择 Invalidate 开头的 然后全选 运行重启idea OK

Linux系统编程系列之线程

一、什么是线程 线程&#xff08;Thread&#xff09;是计算机中的基本执行单元&#xff0c;是操作系统调度的最小单位。线程是进程内的一个独立执行流程&#xff0c;一个进程可以包含多个线程&#xff0c;这些线程共享进程的资源&#xff0c;但每个线程都有自己的独立栈空间以及…