STM32 IAP固件升级(四)

章节说明

STM32 IAP固件升级实验分为以下的章节(加粗的字体是本章节的内容):

一、Flash和RAM的区域划分、工程建立、程序分散加载、程序烧写
二、Stm32 bootloader、application、firmware 程序的分析和编写
三、使用DMA收发串口的不定长数据
四、通信协议的设计
五、STM32 IAP程序的设计
六、上位机的程序的编写

一、前言

为了能使上位机和下位机能进行可靠的通信,所以需要设计一个相对可靠的协议。当然设计的协议也不会太复杂,但是该有的功能还是得有。数据头部,控制指令,数据长度,数据,校验码。这种类型通讯协议用于串口相对还是比较简单便捷的,当然也可以设计的很复杂例如:加入版本控制,物理设备号等等。在本文中就不涉及太复杂的东西了,就设计一个相对完整又相对简单的协议。话不多说,接下来进入正题吧。

二、通讯格式

设计的通讯格式为问答式(即一问一答的方式),分为控制指令,和应答指令。

1、控制指令格式

  1. 首部 : 所有的数据包都要加包头:0xA5A5
  2. 父指令: 根据需求来设计
  3. 子指令: 根据需求来设计
  4. 长度 : 数据长度字节,用来指定该帧中携带数据的长度(单位是长度)
  5. 数据 : 该帧中写带的数据内容
  6. 校验码: 校验码使用校验和的方式;校验和是子指令到校验和之间的所有字节之和,超出 2 字节的进位忽略

2、应答指令格式

  1. 首部 : 所有的数据包都要加包头:0xA5A5
  2. 父指令: 根据需求来设计
  3. 子指令: 根据需求来设计
  4. 长度 : 数据长度字节,用来指定该帧中携带数据的长度(单位是长度)
  5. 数据 : 该帧中写带的数据内容
  6. 校验码: 校验码使用校验和的方式;校验和是子指令到校验和之间的所有字节之和,超出 2 字节的进位忽略

三、控制指令设计

1、查询指令/复位指令




2、程序更新控制指令




3、数据传送指令

四、应答指令设计

1、查询应答指令



2、状态指令



五、帧管理程序设计

1、帧管理数据结构

/* 回调函数的类型定义如下 */
/* typedef uint16_t (*FRAME_VALIDATE)(u8 *buf, u16 len); */
typedef struct
{
	u8 *buffer;//指向一块buff,用于存储一帧的数据
	u16 head;  //数据存储在头部的位置
	u16 count; //接收到的数据
	u16 BUFFERLENGTH; //buffer的最大长度
	u16 MIN_FRAME_LEN; //最小帧
	FRAME_VALIDATE ValidateFrame; //这个是一个回调函数的指针,主要是处理帧的回掉函数
} FrameBufferStr;

2、结构体初始化函数

注意修改堆(heap)的大小

void Frame_Buffer_Init(FrameBufferStr *frame, u16 len, FRAME_VALIDATE validate_cb)
{
	/* 但是要值得注意启动文件的堆内存的大小(默认是 512Byte)。*/
	/* 如果使用大于等于512Byte的大小,则需要修改堆内存大小,否则会报 Fault 异常 */
	/* 由于本实验需要的缓存需要3Kb,所以已经在启动文件里面修改为4KB */
	frame->buffer = (u8*)malloc(len);  
	frame->BUFFERLENGTH = len;
	frame->ValidateFrame = validate_cb;
	Frame_Buffer_Clean(frame);
}

3、清buff函数

void Frame_Buffer_Clean(FrameBufferStr *frame)
{
	memset(frame->buffer, 0, frame->BUFFERLENGTH);
	frame->head = 0;
	frame->count = 0;

}

4、帧管理程序

void Append_Frame_Buffer(FrameBufferStr *frame, u8 *input, u16 length)
{ 
	u16 i = 0;
	//s16 head=0;
	u16 mlen=0;
	
	// 如果上传数据长度比缓存总长度还要长
	if (length > frame->BUFFERLENGTH)
	{
		// 此处可添加错误代码
		return;
	}
	/* 需要将后面的数据copy到前面 */
	if((u16)frame->count + length > frame->BUFFERLENGTH)
	{
		/* frame->head 记录frame->buffer的头部,frame->count记录接收到数据的尾部*/
		memmove(frame->buffer, frame->buffer+frame->head, frame->count - frame->head);
		/* 重新计算接收到数据的尾部 */
		frame->count = frame->count - frame->head;
		/* 头部指向为0 */
		frame->head = 0;	
		/* 将后面的数据清零 */
		memset(frame->buffer + frame->count, 0, frame->BUFFERLENGTH - frame->count);
		/* 后面需要添加的数据超出了fream的范围 */
		if((frame->count + length) > frame->BUFFERLENGTH)
		{
			// index out of buffer range
			/* 将数据清零 */
			frame->count=0;
			frame->head=0;
			return;
		}
	}

	if(length != 0)
	{
		/* 复制后面添加进来的数据 */
		memcpy(frame->buffer+frame->count, input, length);
		frame->count+=length;
	}

	i = 0;
	
	while(frame->head + i < frame->count)
	{
		/* 调用回调函数,解析接收数据,一个一个往下迭代,直到找到需要校验的起始头部 */
		mlen = frame->ValidateFrame(frame->buffer + frame->head + i, frame->count - frame->head - i);
		
		if(mlen > 0)
		 {
			 /* 返回的长度比buffer的总长度长,也将frame初始化 */
			if(mlen > frame->BUFFERLENGTH)
			{
				frame->head = 0;
				frame->count = 0;	
				memset(frame->buffer, 0, frame->BUFFERLENGTH);
				break;
			}
			/* 处理完这帧数据了,将数据清零 */
			frame->head = frame->head + i + mlen;		
			/* 如果计算完的后的frame->head后比 frame->count大,说明接收的数据有偏差*/
			if(frame->head > frame->count)
			{
				frame->count = frame->head;
			}
			i = 0;
			
			if(frame->head == frame->count)
			{
				/* 有偏差则需要重新将frame->buffer初始化 */
				frame->head = 0;
				frame->count = 0;	
				memset(frame->buffer, 0, frame->BUFFERLENGTH);
				break;
			}
		}
		else
		{
			i++;	
		}		
	}

}

六、在串口使用

1、DMA的回调函数

IAP实验三里面有讲到,使用DMA接收串口数据时,首先需要给USART2_Service函数指针赋值让DMA接收函数回调。下面的USART2_DMA_Callback就是赋值给USART2_Service的函数。而这个函数也比较简单,就是将数据存放到帧管理程序中。

void USART2_DMA_Callback(u8 *buff, u16 len)
{
	/* 里面调用帧管理程序,用于接收一帧数据 */
	Append_Frame_Buffer(&USART2_Frame,buff,len);
}

2、USART2 的 Frame 管理程序初始化

/* 定义一个 FrameBufferStr 变量*/
FrameBufferStr USART2_Frame;

/* frame_cb 是帧管理程序的回调函数,由用户定义,在IAP实验中,主要用于处理协议的 */
void USART2_Frame_Init(FRAME_VALIDATE frame_cb)
{
	/* 初始化USART2_Frame */
	Frame_Buffer_Init(&USART2_Frame,USART2_FRAME_BUFFER_LEN,frame_cb);
	/* 给 USART2_Service 赋值*/
	USART2_Service = USART2_DMA_Callback;
}

3、帧管理程序的回调函数

/* 这个函数就是处理协议的回调函数了,具体怎么处理就留给用户来设计了 */
/* 在本实验中,这里只是做一个接收复位指令,然后执行软件复位 */
uint16_t Protocol_Handle(u8 *buf, u16 len)
{
	/* 判断头是否正确 */
	if(buf[0] == 0xa5 && buf[1] == 0xa5){
		/* 等待接收够一帧数据 */
		/* 这个 len 是包括头部的,不是协议上的len*/
		if(len >= 8){
			
			/* 判断校验位 */
			uint16_t check = (uint16_t)(buf[len-2]<<8 | buf[len-1]);
			if(check == 1){
				/* 判断是否是软复位指令 */
				if(buf[2]  == 0 && buf[3] == 1){
					/* 执行软复位 */
					SoftReset();
				}
			}
			return len;
		}
	}
	return 0;
}

七、总结&实验现象

1、使用方法

通过上面步骤的层层封装,只要使用下面的步骤就可以正常使用:

  1. 定义一个帧管理的回调函数
  2. 调用 USART2_Frame_Init 初始化
  3. 使能串口
  4. 在回调函数中处理协议

2、本实验的现象

当通过 USART2 发送复位指令之后(下图中左边的调试助手)就可以看 STM32 复位,并且打印复位信息(右边的调试住手)。
这个实验是在 application 环境下执行的。

3、project

最后将快要完成IAP实验的工程送给大家--->点我project!!!

说明:由于上面的通讯协议还没实验过,有可能还会更改,但是更改的一般不会很多。

posted @ 2020-03-08 01:12  古澜  阅读(2051)  评论(6编辑  收藏  举报