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

前言


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

本次博客开发板使用的是正点原子精英版,芯片是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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

yolov8 onnx推理

前言&#xff1a;yolov8的后处理在某些情况下会导致转模型失败&#xff0c;因此需要把后处理剥离出来。 代码需要做如下修改&#xff1a; 改完后&#xff0c;网络会有三个输出&#xff0c;如图&#xff1a; 最后&#xff0c;用python写网络的后处理&#xff1a; import onn…

用心研发好产品:健康品牌podeey是如何做到的?

在分析消费者健康需求的同时&#xff0c;美国podeey能量生命有限公司&#xff08;PODEEY Biotechnology LLC.&#xff09;不断提升自主研发实力&#xff0c;并且一直注重汇集全球前沿的研发力量&#xff0c;与贵州宏臻菌业达成战略合作&#xff0c;始终致力于以科学技术为核心&…

软件测试HR总结的软件测试常见面试题

一、测试流程是什么样的&#xff1f; 1.产品确定需求后&#xff0c;邀请项目经理&#xff0c;开发&#xff0c;测试等人员参加需求评审会&#xff1b; 2.评审结束后开发根据需求文档和接口文档开发&#xff0c;测试制定测试计划和编写手工测试用例&#xff0c;测试脑图&#xf…

【Redis】深入理解 Redis 常用数据类型源码及底层实现(1.结构与源码概述)

在文章【Redis】不卡壳的 Redis 学习之路&#xff1a;从十大数据类型开始入手中我们介绍了Redis常用的10大数据类型&#xff0c;这10大数据类型可并不是直接在底层通过代码实现的&#xff0c;而是通过不同的底层数据结构组合起来的&#xff0c;这篇我们介绍下Redis常用数据类型…

【LeetCode】每日一题 2023_12_12 下一个更大元素 IV(堆,优先级队列/单调栈)

文章目录 刷题前唠嗑题目&#xff1a;下一个更大元素 IV题目描述代码与解题思路 刷题前唠嗑 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 时隔两天&#xff0c;LeetCode 每日一题重新开张&#xff0c;流感已经不能阻挡我的脚步了&#xff01; 题目&#x…

乐小鱼大理之行

在一个晴朗的日子里&#xff0c;乐小鱼和她的家人一起踏上了一场梦幻般的大理之行。他们驱车穿越沧山&#xff0c;眼前豁然开朗&#xff0c;洱海在阳光下泛着碧绿的光芒。 乐小鱼好奇地探出头&#xff0c;看到了连绵的山脉和湛蓝的湖水。她兴奋地说&#xff1a;“哇&#xff0…

【C++】类与对象(下)

本文目录 1. 再谈构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit关键字 2. static成员2.1 概念2.2 特性 3. 友元3.1 友元函数3.2 友元类 4. 内部类5. 匿名对象6. 拷贝对象时的一些编译器优化7. 再次理解类和对象 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&am…

C++中的reverse函数

1.实现反转数组。 //头文件 #include <algorithm> //使用方法 reverse(a, an);//n为数组中的元素个数 #include<cstdio> #include<iostream> #include<algorithm> using namespace std; int main() {int a[100];int n,k;cin >> n >> k; …

QT 入门

目录 QT 概述 QT5安装 QT环境介绍 编写第一个QT的程序 QT项目文件介绍 QT 概述 QT简介 QT是一个跨平台的C图形用户界面应用程序框架。它为程序开发者提供图形界面所需的所有功能。它是完全面向对象的&#xff0c;很容易扩展&#xff0c;并且允许真正地组件编程。 QT的发…

欣赏动态之美,不如欣赏C语言实现动态内存管理之美 ! ! !

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 &#xff01;&#xff0…

【WinRAR】为什么右键没有压缩选项?

我们安装了WinRAR之后想要压缩文件&#xff0c;但是右键点击文件之后发现并没有WinRAR压缩选项&#xff0c;这应该如何设置才能出现右键带有压缩选项呢&#xff1f;方法如下&#xff1a; 首先打开WinRAR&#xff0c;在上面功能中点击选项 – 设置 然后我们在设置界面中切换到集…

数据结构:栈(Stack)的各种操作(入栈,出栈,判断栈非空,判断栈已满,附源码)

前言&#xff1a;在前面的文章中&#xff0c;我们讲解了顺序表&#xff0c;单链表&#xff0c;双向链表。而我们今天要分享的栈则是基于之前的数据结构上搭建的&#xff0c;但是相较于顺序表和链表来说&#xff0c;栈的实现就非常简单了。 目录 一.栈(Stack)的概念 二.栈的数…

html创建电子邮件链接

refer: 可以在a标签里使用&#xff1a; <a href"mailto:nameemail.com">Email</a>

大模型元年压轴盛会定档12月28日,第十届WAVE SUMMIT即将启航

回望2023年&#xff0c;大语言模型或许将是科技史上最浓墨重彩的一笔。从技术、产业到生态&#xff0c;大语言模型在突飞猛进中加速重构万物。随着理解、生成、逻辑、记忆四大能力显著提升&#xff0c;大语言模型为通用人工智能带来曙光。 AI开发者们正在用算法和代码书写一个…

ABB直流调速器维修DCS550 DCS400 DCS402.0200

德国ABB维修包括&#xff1a;直流调速器维修&#xff0c;伺服驱动器维修&#xff0c;变频器维修&#xff0c;伺服放大器维修&#xff0c;工控机维修&#xff0c;触摸屏维修 ABB直流调速器故障分析: 1、脱扣电流变压器过热引起的直流电机。 发现问题的根源在夏季常见或室内条…

聊天记录年度报告一览无余:轻松多格式导出永久保存,深度智能分析

聊天记录年度报告一览无余&#xff1a;轻松多格式导出永久保存&#xff0c;深度智能分析 1.功能简介效果展示 一个用于提取微信聊天记录的工具&#xff0c;支持将聊天记录导出成HTML、Word、CSV文档&#xff0c;以实现永久保存。此外&#xff0c;该工具还具有对聊天记录进行分…

Java 三元运算符

条件为真执行表达式1&#xff0c;条件为假执行表达式2&#xff0c;有点像if else语句&#xff0c;三目运算符的目的就是简化if else的编写形式。 <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>首页</title> <…

AG16K MCU ARM Cortex M3

AGM AG16K MCU 器件是 FPGAMCU 的 SoC 单芯片产品。 FPGA 单元具有 16K LEs 的逻辑资源&#xff0c;MCU 为硬核 ARM Cortex M3。 MCU 特性  内核 ARM32 位的 Cortex M3 CPU 最高 200 Mhz 工作频率单周期乘法和硬件除法集成的嵌套式的中断控制器&#xff08;NVIC&#xff09…

Jenkins 添加node节点

安装SSH插件 Jenkins- 插件管理- 可选插件- 搜索SSH Agent 配置启用SSH Server Jenkins- 系统管理 - 全局安全配置&#xff0c; 把 SSH Server 设置为启用(默认是禁用) 新增节点 第一种方式&#xff08;SSH密钥连接&#xff09;&#xff1a; 1.Jenkins主机生成SSH密钥 [rootk…

mysql——数据库基础

目录 一.什么是数据库 二.主流的数据库 三.服务器&#xff0c;数据库&#xff0c;表关系 四.数据逻辑存储 五.MySQL架构 六.SQL语句分类 七.存储引擎 一.什么是数据库 存储数据用文件就可以了&#xff0c;为什么还要弄个数据库? 文件保存数据有以下几个缺点&#xff1…