文章目录
- 一、Ymodel协议的传输数据过程:
- 二、实现原理
- 三、代码
- 1、Ymodel.h 文件内容
- 2、**Ymodel_usart_sand_instruct** 函数
- 3、**Ymodel_usart_receive_instruct**函数
- 4、Ymodel_process_fun Ymodel协议处理过程的主函数
markdown文档生成目录的指令:[TOC]
一、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);
}
}
}
}