Ymodel协议 STM32 HAL库 方便移植 附带移植函数接口


markdown文档生成目录的指令:[TOC]

注:我参考的教程:

Ymodem协议详解_lcmsir的博客-CSDN博客_ymodem协议

YModem协议_我的未来从这里开始-CSDN博客

一、Ymodel协议的传输数据过程:

数据发送端可以看成是电脑,接收端可以看成是单片机:

发送端 接收端

  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C
  • SOH 00 FF [55 53…6E 00]" "[32…30 00]’’ NUL[96] CRC CRC >>>
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C
  • STX 01 FE data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK
  • STX 02 FD data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK
  • STX 03 FC data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK
  • STX 04 FB data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK
  • SOH 05 FA data[100] 1A[28] CRC CRC>>>>>>>>>>>>>>>>
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK
  • EOT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< NAK
  • EOT>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< C
  • SOH 00 FF NUL[128] CRCCRC >>>>>>>>>>>>>>>>>>>>>>>
  • <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ACK

简单来说,就是通过串口通讯的方式,在数据的外围增加字符来包裹数据内容。注意Ymodel协议有每次发送1024个字节的数据和每次发送128字节的数据的区别。我使用的是每次接收128字节的方式接收数据的方式!!128字节!!

二、实现原理

1、单片机串口部分需要用到串口发送和串口接收两个接口。在串口中断接收内需要最多一次性接收大约200个字节以内的数据;

2、接收的数据没有做处理,在接收完成以后直接一次性打印到屏幕上,因此需要一个存放数据的大buffer;

3、单片机和数据发送端使用一个大的状态机switch来处理协议运行的过程。

三、代码

代码一共俩文件,Ymodel.h和Ymodel.c文件

1、Ymodel.h 文件内容

主要文件都在.c文件里面,头文件内容没什么可说的:

#ifndef ymodel_h
#define ymodel_h

#include "main.h"
#include "usart.h"
#include "w25q64.h"
#include "stdio.h"

#define	SOH	0x01
#define STX	0x02
#define ACK	0x06
#define NAK	0x15
#define EOT	0x04
#define C1	0x43

/*******************************************
* @function: 	执行Ymodel协议的运行主要流程 ,同时进行数据处理,边接收数据边处理数据
* @input:			无
* @return:		无
* @author:		田小花
* @time:			2021.4.30
*******************************************/
void Ymodel_process_fun(void);

#endif

Ymodel文件里面一共有三个函数,分别是:

2、Ymodel_usart_sand_instruct 函数

里面直接包含了串口发送函数,改变这个函数里面的串口发送函数的接口就可以移植。这里我使用的是stm32的HAL库的串口接口,可以在这个函数里面改成标准库的接口或者其他方式。但是注意这里的串口波特率要和数据发送端的波特率相同

/*******************************************
* @function: 	Ymodel协议的串口发送函数 ,方便修改串口发送函数内部流程,方便代码移植
* @input:		无
* @return:		无
* @author:		田小花
* @time:		2021.4.30
*******************************************/
void Ymodel_usart_sand_instruct(unsigned char Ymodel_instruct)//发送指令
{
	HAL_UART_Transmit_DMA(&huart1, &Ymodel_instruct, 1);  
}

3、Ymodel_usart_receive_instruct函数

此函数内对应了串口接收中断的数据内容。

在串口接收中断内,程序进入中断以后持续接收128多个的字节的数据内容,把数据存放在数组buffer内,同事在串口中断内需要放一个标志位变量。当这个变量变成代表了接收到数据的标志的以后,这个函数检查到了这个标志,就会把接收中断内数据拿出来,进行处理。

/*******************************************
* @function: 	Ymodel协议的串口接收函数,简单处理串口中断接收到的数据,通过改变串口接收函数内的代码方便移植
* @input:			无
* @return:		作为是否有接收到数据的判断标志
* @author:		田小花
* @time:			2021.4.30
*******************************************/
unsigned char Ymodel_usart_receive_instruct(unsigned char *buffer)//接收指令
{
	int i = 0;
	if(Rx1_Flag)    	// 通过串口中断函数检测到了串口有数据保存
	{  
		Rx1_Flag=0;	// 清除串口中断的标志
		
		for(i=0; i<Rx1_Len; i++)
		{
			buffer[i] = Rx1_Buf[i];
		}
		return 1; //有接收到了数据
	} 
	return 0;//没有接收到数据
}

4、Ymodel_process_fun Ymodel协议处理过程的主函数

/*******************************************
* @function: 	执行Ymodel协议的运行主要流程 ,同时进行数据处理,边接收数据边处理数据
* @input:			无
* @return:		无
* @author:		田小花
* @time:			2021.4.30
*******************************************/
unsigned char Ymodel_FLAG = 0; //作为运行流程的状态机标志位

unsigned char uasrt_buffer[200] = {0}; //转存串口中断函数的数据

unsigned char FILE_name[50] = {0}; //保存接收文件的文件名字符串
unsigned char FILE_length[50] = {0}; //保存接收文件的文件长度字符串


unsigned char buffer[4096] = {0}; //需要做处理的数据

long int buffer_sign = 0;

int buffer_address = 0x000000;

void Ymodel_process_fun(void)
{
	int j = 0, i = 0;
	
	while(1)
	{
		switch(Ymodel_FLAG)//运行流程依靠的状态机
		{
			//数据起始帧
			case 0:
			{
				
				if(Ymodel_usart_receive_instruct(uasrt_buffer) == 1)//判断串口接收中断是否接收到了数据
				{
					if(uasrt_buffer[0] == 0x01) //判断接收到的数据第一个数据是不是SOH SOH代表了128位数据
					{					
						//处理第一帧数据获取文件名
						for(j=3; j<Rx1_Len; j++) //第一帧的第三个数据开始时文件的名字
						{
							if(uasrt_buffer[j] == 0x00) break; //文件名字以0x00数据结束
							
							FILE_name[j-3] = uasrt_buffer[j]; //把文件名字保存到数组内
						}
						j=j+1;
						i=0;
						//处理第一帧数获取文件长度
						for(; j<Rx1_Len; j++) 
						{
							if(uasrt_buffer[j] == 0x20) break;
							
							FILE_length[i++] = uasrt_buffer[j];
						}
						Ymodel_FLAG = 1; //跳转到标志位1
						
						break; 
					}
				}

				HAL_Delay(200);
				
				Ymodel_usart_sand_instruct(C1);//发送应答
				
				break;
			}
				
			//发送ACK 和C
			case 1:
			{
				HAL_Delay(1);
				Ymodel_usart_sand_instruct(ACK);
				HAL_Delay(1);
				Ymodel_usart_sand_instruct(C1);
				HAL_Delay(10);
				
				Ymodel_FLAG = 2;
				
				break;
			}
			
			//接收数据帧
			case 2:
			{			
				if(Ymodel_usart_receive_instruct(uasrt_buffer) == 1)//判断串口接收中断是否接收到了数据
				{
					//处理接收到的128字节数据长度的数据
					if(uasrt_buffer[0] == 0x01)//判断数据帧的第一个标志位是不是SOH
					{
						Ymodel_FLAG = 3; //跳转到发送应答数据帧
						
						for(j=0; j < 128; j++) //第4字节到第131字节:数据段内容 数据长度128字节
						{	
							buffer[j+buffer_sign] = uasrt_buffer[j+3];//截取帧数据的数据内容
						}
						buffer_sign = buffer_sign+128;//每次接收128字节的数据
						
						//已经接收到了4K字节的数据,可以一次性将数据写入到W25Q64中去
						//不满4096个字节的数据是最后一帧数据,放在结束帧处理
						if(buffer_sign == 4096)
						{
							WriteData_4096(buffer_address,buffer);//将读取到的数据写入存储器
							
							buffer_address = buffer_address + 0x001000; //每次写入4096个字节,也就是0x1000个字节的数据
							
							buffer_sign = 0;
							
							for(j=0; j < 4096; j++) buffer[j] = 0xFF; //清理接收的数据内容
						}
						
						break;//case结束
					}
					
					//处理接收到的1024字节数据长度的数据
					if(uasrt_buffer[0] == 0x02)
					{
						Ymodel_FLAG = 0; //这里不做数据处理,不支持
					}
					
					//处理接收结束数据帧
					if(uasrt_buffer[0] == 0x04) //EOT 0x04结束数据帧
					{
						Ymodel_FLAG = 4; //跳转至发送应答结束数据帧的数据NAK
						
						//在传输最后一帧数据的时候一定不够4096个字节
						//所以将最后一帧不够4096字节的数据单独写入
						WriteData_4096(buffer_address,buffer);//将读取到的数据写入存储器
					}
					

					//检测到主机取消了发送
					if(uasrt_buffer[0] == 0x18) 
					{
						printf("Undo Send\r\n");
						Ymodel_FLAG = 0;
					}
				}
				HAL_Delay(1);
				break;
			}
			
			//发送ACK应答
			case 3:
			{
				HAL_Delay(1);
				Ymodel_usart_sand_instruct(ACK);
				HAL_Delay(1);
				Ymodel_FLAG = 2;
				break;				
			}
			
			//检测到了数据结束帧 EOT 需要使用NAK回应ETO请求结束传输
			case 4:
			{
				Ymodel_FLAG = 5; //跳转到下一个状态
				
				HAL_Delay(10);
				
				Ymodel_usart_sand_instruct(NAK);//发送NAK数据帧 表示结束数据传输
				
				HAL_Delay(10);

				break;	
			}
			
			//判断进入传输数据结束流程
			case 5:
			{
				if(Ymodel_usart_receive_instruct(uasrt_buffer) == 1)//判断串口接收中断是否接收到了数据
				{
					//检测到主机取消了发送
					if(Rx1_Buf[0] == 0x04) 
					{	
						Ymodel_FLAG = 6;		
					}
					break;
				}
				break;
			}
			
			
			//数据传输结束前需要再次发送应答数据帧和回应帧,通知发送端结束数据传输
			case 6:
			{
				Ymodel_FLAG = 7;
				
				HAL_Delay(2);
				Ymodel_usart_sand_instruct(ACK);
				HAL_Delay(1);
				Ymodel_usart_sand_instruct(C1);
				HAL_Delay(1);

				break;
			}
			
			//通过判断最后一个数据帧判断通讯已经完全结束
			case 7:
			{
				if(Ymodel_usart_receive_instruct(uasrt_buffer) == 1)//判断串口接收中断是否接收到了数据
				{
					//检测到主机取消了发送
					if(Rx1_Buf[0] == 0x01) 
					{	
						Ymodel_FLAG = 8;		
					}
				}				
				break;
			}
			
			//最后数据传输结束,输出打印信息
			case 8:
			{
				Ymodel_usart_sand_instruct(ACK);
				
				HAL_Delay(10);
				
				//显示已经传输完成
				printf("\r\nData transmission completed\r\n");
				
				//显示接收到的文件的名字
				printf("FILE_name :");
				
				for(j=0; j<sizeof(FILE_name)/sizeof(FILE_name[0]); j++)
				{
					printf("%c",FILE_name[j]);
				}
				printf("\r\n");
				
				//显示接收到的数据长度
				printf("FILE_length :");
				
				for(j=0; j<sizeof(FILE_length)/sizeof(FILE_length[0]); j++)
				{
					printf("%c",FILE_length[j]);
				}
				printf("Byts\r\n");
				
	//			//显示最后一帧接收到的数据内容
	//			printf("end data %d \r\n", sizeof(buffer)/sizeof(buffer[0]) );
	//			/* 输出数组内的信息 */
	//			for(j=0;j<4096;j++)
	//			{	
	//				printf("%c ",buffer[j]);
	//				
	//				if((j+1)%8 == 0) printf ("   ");
	//				
	//				if((j+1)%64 == 0) printf ("\r\n");
	//				
	//				if((j+1)%256 == 0) printf ("%d字节->\r\n\r\n",j+1);
	//			}
		
				printf ("\r\n\r\n显示内存数据\r\n\r\n");

				for(j=0; j<0x003000; j=j+0x001000)//循环显示12K字节数据内容
				{
					ReadData_4096(j);
					
					/* 显示存储器内的数据 */
					for(i=0;i<4096;i++)
					{	
						printf("%c ",rdata[i]);
						
						if((i+1)%8 == 0) printf ("   ");
						
						if((i+1)%64 == 0) printf ("\r\n");
						
						if((i+1)%256 == 0) printf ("%d字节->\r\n\r\n",i+1);
					}
					printf ("\r\n------------------------------------------------------------------------------\r\n\r\n");
				}

				
				Ymodel_FLAG = 0; //回到初始状态,接收数据
				
				buffer_sign = 0; //归位数组的位置
				
				for(j=0; j<50; j++) //清除文件名字和文件长度的内容
				{
					FILE_name[j] = 0;
					FILE_name[j] = 0;
				}
				break;
			}	
			
			case 10:
			{
				//处理第一帧数获取文件长度
				printf("\r\n");
				
				for(j=0; j<30; j++) 
				{
					printf("%x ",uasrt_buffer[j]);
				}
				
				printf("\r\n");
				
				for(j=0; j<30; j++) 
				{
					printf("%x ",FILE_length[j]);
				}
				
				while(1);
			}
		}
	}
}
STM32 HAL库是STMicroelectronics公司提供的一种软件抽象层,用于简化STM32系列微控制器的编程和开发。它提供了一系列的API,可以方便地访问STM32微控制器的外设,如GPIO、UART、SPI、I2C、定时器等。而标准库则是由C语言标准提供的一组库函数,用于对不同的硬件平台进行抽象和封装,提供一些常用的函数,如memcpy()、memset()、printf()等。 将STM32 HAL库移植到标准库,需要先了解两者的差异和共同点,然后进行逐个函数的替换和调试。 1. 差异: STM32 HAL库是针对STM32系列微控制器设计的,提供了非常丰富和方便的API,但是也存在以下一些问题: - 耗用的ROM和RAM资源较大,不适合一些资源受限的应用场景; - 由于API过于丰富和复杂,容易导致代码臃肿和难以维护; - 由于HAL库是由STMicroelectronics公司提供的,所以在一些商业项目中可能存在授权和版权问题。 而标准库则是基于C语言标准提供的,具有以下一些优点: - 软件资源占用较小,适合资源受限的应用场景; - 由于是标准库,所以不会涉及授权和版权问题; - 软件架构简单,易于维护和移植。 2. 共同点: STM32 HAL库和标准库都提供了一些常用的函数,如GPIO控制、定时器控制、串口通信等。这些函数在使用方法和参数上存在一些差异,但是功能是相同的。因此,移植的过程中,可以逐个函数进行替换和调试。 移植的具体步骤如下: 1. 了解STM32 HAL库和标准库的API,确定需要替换的函数。 2. 根据需要,修改工程的编译选项,将HAL库的头文件和库文件从工程中删除。 3. 修改代码中的函数调用,将HAL库函数替换为标准库的函数。例如,将HAL_GPIO_WritePin()替换为GPIO_WritePin()。 4. 修改函数调用的参数,确保函数参数的类型和数量与标准库的要求相符。例如,将HAL_GPIO_Init()的参数修改为GPIO_Init()的参数。 5. 编译、调试,直到程序能够正常运行。 需要注意的是,由于两种库的API存在一些差异,移植过程中需要对代码进行一定的修改和调试。如果不是很熟悉STM32 HAL库和标准库的使用方法,建议先学习官方文档和示例程序,再进行移植工作。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值