(新手必看)自定义数据传输通信协议+STM32代码详解

news2024/9/20 20:35:44

前言


       本篇博客主要学习和了解一些单片机协议的格式,在对传输大数据或者要求准确性的时候,都需要通过协议来发送接收,下面通过了解协议的基本构成和代码来分析和实现协议的发送和接收。本篇博客大部分是自己收集和整理,如有侵权请联系我删除。

本次博客开发板使用的是正点原子精英版,芯片是STM32F103ZET6,需要资料可以@我拿取。

交流群:717237739

如果觉得有用点赞关注收藏三连,多谢支持

本博客内容原创,创作不易,转载请注明
————————————————

一 .什么是协议?

协议,是网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定

如怎么建立连接,怎么样互相识别等,只有遵守这个约定,计算机之间才能相互通信交流。它的三要素是:语法,语义,时序。为了使数据在网络上从源到达目的,网络通信的参与方必须遵循相同的规则,这套规则为协议,最终体现为在网络上传输的数据包的格式。 

例如,串口的波特率,也是协议的一种表现格式。

参考知乎的资料了解七层协议:OSI 七层模型和TCP/IP模型及对应协议(详解)

OSI 七层模型和TCP/IP模型及对应协议(详解)

以上图片是为了让我们对协议有个充足的概念,具体的自己可以先行了解协议的含义和作用。

二 .协议的组成 

1.概念

这里说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所常用的RS232,RS485,红外,光纤,无线等通信方式

2.组成部分

比较可靠的通信协议包括:帧头,地址信息,数据类型,数据长度,数据块,校验位,帧尾

在日常自己定义的格式里,我们一般也是要遵循这些格式来定义一个数据包的发送和接收,以保证数据的完整性和准确性。

  • 帧头:一帧数据的开始,可以使用多个字节,具体内容自己定义。例如:0X55
  • 地址信息:主要用于多机通信中,通过地址信息的不同来识别不同的通信终端,确定和哪个设备进行通信(类似于IIC的芯片地址ID)。例如:0X01
  • 数据类型:可以标识后面紧接的是命令还是数据,例如0X01标识二进制,0x02标识十六进制等。
  • 数据长度:标识后面的要发送/接收的数据长度的个数。例如:0X02,表示后面跟两个数据。
  • 数据块:真正发送的数据内容,发送数据内容的长度和上面的数据长度对应。
  • 校验码:用来检验数据的完整性和准确性,一般常见的校验方式有(MODBUS_CRC16/CRC32,ADD8/16求和等等)
  • 帧尾:判断数据包的结束,可以为一个数据或者多个数据,自己定义,例如0XAA

根据以上的协议格式介绍,一般正常的数据包为:

0X55        0X01        0X01        0X02        0X13        0X88        CRC16H        CRC16L        0XAA

(帧头        地址        数据类型     长度        数据        数据        CRC检验H      CRC校验L      帧尾)

 

三. 类似的芯片手册协议格式讲解

这里参考一个芯片手册:SYN6658中文语音合成芯片 用户手册

 从这里分析可以得到:

帧头为:        0XFD        数据长度:2字节 0xXX 0xXX       命令字:参考手册一字节

命令参数:一字节     数据文本:最大4K字节

下面是手册的一些命令字和命令参数的参考:

完整的数据包格式:

 注意:在一些使用其他芯片的场景下,我们一般都需要遵守各个芯片的协议格式,然后根据手册和要求发送对应的数据包,这样协议对应才能驱动芯片。

四 . STM32代码实现自定义格式协议

我们在网上找到有对应的嵌入式协议测试题目,我们根据这个题目写出对应的协议发送和接收代码,代码内部做了注释,整体就不再讲解了,不懂的只能去补补C语言了,代码仅做参考。

测试题:

题目分析:

  • 1.数据包格式,建议直接做成结构体的模式,然后传入地址,一个个调用就很方便了
  • 2.已知的信息,直接宏定义调用,例如帧头帧尾
  • 3.提前写好串口数据包的发送和接收函数,类似数组遍历那种,然后了解清楚占用的字节大小
  • 4.题目要求最大65536,但是我们在STM32F103上实现,就最大256算了,不然超范围了
  • 串口发送和接收函数讲解和应用:基于STM32 + UART串口通信新手详解

 代码.C.H部分和main部分

#include "rs485.h"
#include <stdio.h>
#include <string.h>
u8 sendbuf[Send_Buf_Size];  //发送数据缓冲区
u8 recbuf[Rec_Buf_Size];		//接收数据缓冲区
RS485 rs485def={sendbuf,recbuf,0,0}; //rs485相关信息结构体

//中断服务函数接收数据
void USART3_IRQHandler(void)
{
	u8 data;
	u32 head,tail;
	u8 len;
	if(USART3->SR & 1<<5)
	{
		data= USART3->DR;
//		printf("%x\t",data);
		rs485def.recbuf[rs485def.reclen]=data ;//存放收到的数据
		rs485def.reclen++;
		if(rs485def.reclen >=8) //收到了包头和数据域长度
		{
			head = *(u32*)rs485def.recbuf;//获取包头
//			printf("head:0x%x\r\n",head);
			if(head == PACK_START)//收到包头
			{
				len = *(u32*)&rs485def.recbuf[4];//获取数据域长度
//				printf("reclen:%d,len:%d\r\n",rs485def.reclen,len);
				if(rs485def.reclen >= len+12) //一帧数据接收完成
				{
					tail = *(u32*)&rs485def.recbuf[len+4+4];//获取包尾 4包头所占4字节,4数据域长度所占4个字节
//					printf("tail:0x%x\r\n",tail);
					if(tail == PACK_TAIL )//包尾正确
					{
						rs485def.recflag = 1; //接收完成标志
					}
					else
					{
						rs485def.reclen = 0 ;
					}
				}
			}
			else
			{
				rs485def.reclen = 0 ;
			}
		}
	}
}

//数据包接收函数
//函数功能:得到数据包的相关内容
//出口参数 :
//返回值 : 0 接收到数据 1 ,没收到数据
u8 RecPacket(Packet* pdata)
{
	u8* ptemp = rs485def.recbuf;
	u8 len;
	u8 buf[Rec_Buf_Size-20];
	pdata->pInform = (char *)buf;
	if(rs485def.recflag == 1)
	{
		rs485def.recflag = 0 ;		
		//跳过包头
		ptemp += 4;
		//获取数据域长度
		len = *(u32*)ptemp;
	  //获取验证码
		ptemp += 4;
		pdata->identify = *(u16*)ptemp;
		//获取源地址
		ptemp += 2;
		pdata->SrcAddr = *(u16*)ptemp;
		//获取目的地址
		ptemp += 2;
		pdata->DesAddr = *(u16*)ptemp;
		//获取命令码
		ptemp += 2;
		pdata->CmdNum = *(u16*)ptemp;
		ptemp += 2;  //指向信息内容
		//获取信息长度
		pdata->InformLlen = len - 8;	
		//获取信息内容
		memcpy(pdata->pInform,ptemp,pdata->InformLlen);
		//清除
//		memset(&rs485def,0,sizeof(RS485));
		rs485def.reclen = 0 ;
		return 0 ;
	}
	return 1;
}

//数据包发送函数
//入口参数:发送的数据包
void SendPacket(Packet* pdata)
{
	u8* ptemp = rs485def.sendbuf;
	u8 sendlen;
//	u8 i;
	//把需要发送的数据赋给sendbuf
	//包头
	*(u32*)ptemp=PACK_START;
//	printf("0x%x\r\n",*ptemp);
	//数据域长度
	ptemp += 4;
	*(u32*)ptemp = pdata->InformLlen+8;
	//验证码
	ptemp += 4;
	*(u16*)ptemp = pdata->identify;
	//源地址
	ptemp += 2;
	*(u16*)ptemp = pdata->SrcAddr;
	//目的地址
	ptemp += 2;
	*(u16*)ptemp = pdata->DesAddr;
	//命令码
	ptemp += 2;
	*(u16*)ptemp = pdata->CmdNum;
	//信息内容
	ptemp += 2;
	//strcpy strncpy memcpy memset-->这几个函数的区别
	memcpy(ptemp,pdata->pInform,pdata->InformLlen);
//RS485_Send(ptemp,pdata->InformLlen);
	
	//包尾
	ptemp+=pdata->InformLlen;
	*(u32*)ptemp=0x88CC55AA;
		
	//发送数据
	ptemp += 4;
	sendlen = ptemp - rs485def.sendbuf;//发送数据长度	
//	printf("send:");
//	for(i=0;i<sendlen;i++)
//		printf("%x\t",rs485def.sendbuf[i]);
	RS485_Send(rs485def.sendbuf,sendlen); //发送数据
}
#ifndef _RS485_H_
#define _RS485_H_
#include "stm32f10x.h"
#include "io_bit.h"

#define Send_Buf_Size 256
#define Rec_Buf_Size 	Send_Buf_Size

typedef struct
{
	u8 *sendbuf;		 //发送数据缓冲区
	u8 *recbuf;			//接收数据缓冲区
	u8 reclen;			//接收数据总长度
	u8 recflag;			//接收完成事件标志位
}RS485;
extern RS485 rs485def;

//数据包
#define PACK_START 0xAA55CC88	//帧头
#define PACK_TAIL  0x88CC55AA //帧尾
typedef struct
{
	u16 identify;  //验证码 --字节
	u16 SrcAddr;   //源地址
	u16 DesAddr;   //目的地址
	u16 CmdNum;    //命令码
	char* pInform; //信息内容
	u8 InformLlen; //信息长度
}Packet;

u8 RecPacket(Packet* pdata);
void SendPacket(Packet* pdata);


#endif

 main


int main()
{	
	Packet SendPack;
	Packet RecPack;
	char sendbuf[6]= "12345";
	LED_Init();
	KEY_Init();
	SCB->AIRCR= 0X05FA0500 ; //设置为分组2
	USART1_Init(115200);
	delay_init(72);
	OLED_Init();
	W25QXX_Init();
	DHT11_Init();
	RS485_Init();
	while(1)
	{
		if(Key_Scanf(0))  //发送
		{	 
//			printf("key\r\n");
			//填充数据包   ---  学习 433模块 CC1101
			SendPack.identify = 0x1234;		//验证码
			SendPack.SrcAddr  = 0x5678;		//源地址
			SendPack.DesAddr  = 0x90ab;		//目的地址
			SendPack.CmdNum   = 0xcdef;	  //命令码
			SendPack.pInform  = sendbuf;	//信息内容
			SendPack.InformLlen=strlen(sendbuf);//信息长度
			SendPacket(&SendPack);
		}
		if(!RecPacket(&RecPack))//收到数据
		{
			//打印收到的数据
			printf("identify:0x%x\r\n",RecPack.identify);  //验证码
			printf("SrcAddr:0x%x\r\n",RecPack.SrcAddr);		 //源地址
			printf("DesAddr:0x%x\r\n",RecPack.DesAddr);		 //目的地址
			printf("CmdNum:0x%x\r\n",RecPack.CmdNum);			 //命令码
			printf("pInform:%s\r\n",RecPack.pInform);			 //信息内容
			printf("InformLlen:%d\r\n",RecPack.InformLlen);//信息长度
		}
	}	
}

总结:


      协议在一些公司的项目一般都会用到,最常见的就是485-modbus,不过基本的格式都差不多,这部分内容在项目了算是比较重要的,IAP升级的常用YModem协议也是异曲同工,类似的芯片DHT11和模块也会有协议格式要求。大家如果对我的博客有疑问或者错误,可以@我修改,大家相互交流。

交流群:717237739

如果觉得有用点赞关注收藏三连,多谢支持

本博客内容原创,创作不易,转载请注明

  点赞收藏关注博主,不定期分享单片机知识,互相学习交流。
————————————————
 

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

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

相关文章

图的搜索(二):贝尔曼-福特算法、狄克斯特拉算法和A*算法

图的搜索&#xff08;二&#xff09;&#xff1a;贝尔曼-福特算法、狄克斯特拉算法和A*算法 贝尔曼-福特算法 贝尔曼-福特&#xff08;Bellman-Ford&#xff09;算法是一种在图中求解最短路径问题的算法。最短路径问题就是在加权图指定了起点和终点的前提下&#xff0c;寻找从…

【教3妹学编程-算法题】下一个更大元素 IV

3妹&#xff1a;“太阳当空照&#xff0c;花儿对我笑&#xff0c;小鸟说早早早&#xff0c;你为什么背上炸药包” 2哥 :3妹&#xff0c;什么事呀这么开发。 3妹&#xff1a;2哥你看今天的天气多好啊&#xff0c;阳光明媚、万里无云、秋高气爽&#xff0c;适合秋游。 2哥&#x…

计算机网络:应用层(一)

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

STM32单片机项目实例:基于TouchGFX的智能手表设计(4)LPBAM的应用

STM32单片机项目实例&#xff1a;基于TouchGFX的智能手表设计&#xff08;4&#xff09;LPBAM的应用 目录 一、概述 二、LPBAM简介 2.1 支持 LPBAM 的外设 三、LPBAM硬件机制 3.1 电源和时钟架构 3.2 速度限制 3.3 LPGPIO_IOToggle实验 一、概述 STM32U5 系列微控制器基…

星际飞船大战

欢迎来到程序小院 星际飞船大战 玩法&#xff1a;滑动鼠标控制方向&#xff0c;点击鼠标左键射击&#xff0c;生命值100分&#xff0c;被敌船击中减去20&#xff0c; 5次生命复活机会&#xff0c;统计分数&#xff0c;快去星际飞船大战吧^^。开始游戏https://www.ormcc.com/pl…

深度解读 Cascades 查询优化器

数据库中查询优化器是数据库的核心组件&#xff0c;其决定着 SQL 查询的性能。Cascades 优化器是 Goetz 在 volcano optimizer generator 的基础上优化之后诞生的一个搜索框架。 本期技术贴将带大家了解 Cascades 查询优化器。首先介绍 SQL 查询优化器&#xff0c;接着分析查询…

css 表示具有特定类或者其他属性的某种标签类型的元素

需求 通过 css 选择器获取某种标签&#xff08;如&#xff1a;div、input 等&#xff09;具有某个属性&#xff08;如&#xff1a;class、id 等&#xff09;的元素&#xff0c;从而修改其样式。 代码 通过 [标签].[属性] 的方式来获取 <div class"test">&l…

HyperGCN笔记

1 Title HyperGCN: A New Method of Training Graph Convolutional Networks on Hypergraphs&#xff08;Naganand Yadati、Prateek Yadav、Anand Louis、Madhav Nimishakavi、Vikram Nitin、Partha Talukdar&#xff09;【NeurIPS 2019】 2 Conclision This paper proposes H…

gprMax安装步骤

本来是想直接在base环境下直接弄的&#xff0c;但是报错了&#xff0c;因为base环境里的conda版本不匹配&#xff0c;于是重新建立虚拟环境gprMax&#xff0c;如下所示。 然后激活建立的gprMax环境&#xff0c;在gprMax环境中安装git 参考文章&#xff1a; https://zhuanlan.…

mysqldump --set-gtid-purged参数详解

在开启了GTID模式的数据库&#xff0c;使用mysqldump进行部分数据备份的时候&#xff0c;经常会遇到如下警告 Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of t…

『 Linux 』进程地址空间概念

文章目录 &#x1fad9; 前言&#x1fad9; 进程地址空间是什么&#x1fad9; 写时拷贝&#x1fad9; 可执行程序中的虚拟地址&#x1fad9; 物理地址分布方式 &#x1fad9; 前言 在c/C中存在一种内存的概念; 一般来说一个内存的空间分布包括栈区,堆区,代码段等等; 且内存是…

智慧机房与3D机房动环监控系统的应用

智慧机房是什么&#xff1f; 智慧机房是集采集信息、实时监控、数据分析、统一管理、故障告警等功能于一体的全方位、立体化的智能环境监控系统&#xff0c;构建物联网、大数据和云计算背景下现代企业的“数据心脏”。它能为机房管理者呈现细致入微的关键性数据&#xff0c;优…

Error: Failed to resolve vue/compiler-sfc——vite项目启动报错——npm run serve

运行项目时&#xff0c;报错如下&#xff1a; Error: Failed to resolve vue/compiler-sfc 根据报错信息的提示&#xff1a;vue的版本必须大于3.2.25&#xff0c;经过查看package.json文件&#xff0c;可以看到vue的版本为3.2.36&#xff0c;是满足条件的。 因此考虑缓存问题&…

Git 硬重置之后恢复历史提交版本

****硬重置之前一定要备份分支呀&#xff0c;谨慎使用硬重置&#xff0c;特别是很多人一起使用的分支**** 如果你在reset的时候选择了Hard选项&#xff0c;也就是硬重置 重置完且push过&#xff0c;那么被你本地和远端后面的提交记录肯定就会被抹去。 解决办法&#xff1a; …

BearPi Std 板从入门到放弃 - 先天神魂篇(1)(RT-Thread 指令点亮LED)

简介 使用 BearPi IOT Std板&#xff0c; 开发板简单信息 主芯片: STM32L431RCT6 串口: Usart1 USER LED : PC13 E53_SC1 扩展板与主板连接: I2C : I2C1 (光照强度传感器&#xff1a;BH1750) LED: PB9RT-Thread 创建线程 线程的管理方式 添加用户代码 main.c #include <…

从零构建属于自己的GPT系列6:模型本地化部署2(文本生成函数解读、模型本地化部署、文本生成文本网页展示、代码逐行解读)

&#x1f6a9;&#x1f6a9;&#x1f6a9;Hugging Face 实战系列 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在PyCharm中进行 本篇文章配套的代码资源已经上传 从零构建属于自己的GPT系列1&#xff1a;数据预处理 从零构建属于自己的GPT系列2&#xff1a;模型训…

adb命令学习记录

1、 adb ( android debug bridge)安卓调试桥&#xff0c;用于完成电脑和手机之间的通信控制。 xcode来完成对于ios设备的操控&#xff0c;前提是有个mac电脑。 安卓系统是基于linux内核来进行开发的。 2、adb的安装: 本身 adb是 android SDK 其中自带的工具&#xff0c;用于完…

山西电力市场日前价格预测【2023-12-09】

1.日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-12-09&#xff09;山西电力市场全天平均日前电价为366.40元/MWh。其中&#xff0c;最高日前电价为629.26元/MWh&#xff0c;预计出现在08:00。最低日前电价为216.58元/MWh&#xff0c;预…

PySpark大数据处理详细教程

欢迎各位数据爱好者&#xff01;今天&#xff0c;我很高兴与您分享我的最新博客&#xff0c;专注于探索 PySpark DataFrame 的强大功能。无论您是刚入门的数据分析师&#xff0c;还是寻求深入了解大数据技术的专业人士&#xff0c;这里都有丰富的知识和实用的技巧等着您。让我们…

使用工业级以太网交换机,需要注意哪些问题?

企业常用工业级以太网交换机进行网络组网&#xff0c;主要有两种情况。第一种是通过协议转换器将专线转换为以太网交换机&#xff0c;第二种是直接租用裸光纤。具体而言&#xff0c;在三、四层网络选择的三层交换机通常只能配置简单的动态路由协议、简单的策略路由和简单的访问…