参考教程:https://www.bilibili.com/video/BV1SatHeBEVG/?spm_id_from=333.1387.favlist.content.click

一、分区跳转功能实现

1、规划AB分区参数

(1)STM32F103C8T6的Flash容量是64KB,一个扇区的大小为1KB,则扇区的编号为0~63。

(2)首先对Flash的空间做一个初步的规划,B区占用20KB(0~19号扇区),A区占用44KB(20~63号扇区)。

(3)在User组中添加main.h文件,用于存放BootLoader相关及其它的比较重要的宏定义等内容。

#ifndef __MAIN_H
#define __MAIN_H

#define STM32_FLASH_SADDR 0x08000000           //FLASH程序区起始地址
#define STM32_PAGE_SIZE    1024                 //FLASH一个扇区的字节数
#define STM32_PAGE_NUM   64                   //FLASH的扇区数(页数)
#define STM32_B_PAGE_NUM 20                   //B区所占页数
#define STM32_A_PAGE_NUM STM32_PAGE_NUM-STM32_B_PAGE_NUM  //A区所占页数
#define STM32_A_STAET_PAGE STM32_B_PAGE_NUM  //A区第一个扇区的编号
#define STM32_A_SADDR     STM32_FLASH_SADDR + STM32_A_STAET_PAGE * STM32_PAGE_SIZE                                      //A区起始地址

#endif

2、OTA Flag的定义、读取及判定

(1)OTA Flag的定义:

①在main.h文件中定义OTA相关信息结构体,其中包含成员OTA Flag。

#define OTA_SET_FLAG        0xAABB1122        //FLAG为该值时代表OTA Flag置位
typedef struct{
	uint32_t OTA_Flag;
}OTA_InfoCB;

extern OTA_InfoCB OTA_Info;

②在main.c文件中定义OTA信息相关结构体变量。

OTA_InfoCB OTA_Info;

(2)在AT24C02.c文件中增加读取OTA相关信息结构体的函数,并在AT24C02.h文件中声明。OTA相关信息结构体存储在AT24C02地址为0的地方。

①AT24C02.c文件增加内容:(使用memset函数需包含string.h文件)

void AT24C02_ReadOTAInfo(void)
{
	memset(&OTA_Info, 0, sizeof(OTA_InfoCB));   //将OTA_Info整块内存空间清零
	AT24C02_ReadData(0, (uint8_t *)&OTA_Info, sizeof(OTA_InfoCB));  //读出OTA_Info内容
}

②AT24C02.h文件增加内容:

void AT24C02_ReadOTAInfo(void);

(3)在Hardware组中添加Boot.c文件和Boot.h文件,并编写判定OTA Flag的逻辑。

①Boot.c文件:

#include "stm32f10x.h"                  // Device header
#include "main.h"
#include "Serial.h"
#include "Boot.h"

void BootLoader_Branch(void){
	if(OTA_Info.OTA_Flag == OTA_SET_FLAG){
		Serial_Printf("OTA更新\r\n");
	}
	else{
		Serial_Printf("跳转A分区\r\n");
	}
}

②Boot.h文件:

#ifndef __BOOT_H
#define __BOOT_H

void BootLoader_Branch(void);

#endif

3、跳转A分区前的工作

(1)Corte-M3处理器拥有R0‐R15的寄存器组,其中R13作为堆栈指针SP。SP有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。

①R0~R12都是32位通用寄存器,用于数据操作。

②Cortex-M3拥有两个堆栈指针,然而它们是banked,因此任一时刻只能使用其中的一个。

        主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程)

        进程堆栈指针(PSP):由用户的应用程序代码使用

③当呼叫一个子程序时,由R14存储返回地址。

④R15指向当前的程序地址,如果修改它的值,就能改变程序的执行流。

(2)运行B区的程序时,通用寄存器中会保存一些数据用于BootLoader程序,如果要从B区跳转到A区,那么本次单片机上电循环内,不会再返回运行BootLoader程序,也就是说,从B区跳转到A区时,可以直接舍弃B区在通用寄存器中保存的数据、SP指针、PC指针以及R14中保存的返回地址,不需要“保存现场”。

(3)当指令指针跳转到A区时,对于A区的程序而言相当于按下了复位按键,在进入A区程序的main函数以前,会先运行__main函数对通用寄存器进行初始化,所以不需要再去手动初始化通用寄存器。

(4)实际上,前面说“单片机上电/复位后总是从Flash的起始位置开始运行第一条指令”,这个说法并不准确,上电/复位后,CPU首先从内存的固定位置(即向量表的起始地址)获取两样东西,先设置好主堆栈指针(MSP),再拿到复位中断服务函数的入口地址(Reset_Handler),然后跳转过去执行,所以,当指令指针跳转到A区时,实际上并非真正意义上的复位,应仿照按下复位按键的逻辑,手动设置SP指针和PC指针的初始值。

①如果不人为干预,程序的向量表将存放在Flash的起始位置,这是默认没有BootLoader程序的情况,如果有BootLoader程序,那么A区程序的向量表应该存放在A区的起始位置(0x08005000)。

②向量表的第一个成员__initial_sp就是SP指针的初始值,初始值为多少取决于编译结果,而编译结果则取决于程序中设置了多少全局变量等。无论A区程序SP指针的初始值为多少,它都是可以通过一段固定代码访问到的(也就是存放的位置固定,为0x08005000),那么从B区跳转到A区时,可以读出此值,并赋给SP指针,以完成初始值的设置。

③向量表的第二个成员Reset_Handler就是PC指针的初始值,同样的,无论A区程序PC指针的初始值为多少,它都是可以通过一段固定代码访问到的(也就是存放的位置固定,为0x08005004),那么从B区跳转到A区时,可以读出此值,并赋给PC指针,以完成初始值的设置。

(5)在BootLoader中初始化了一些外设,在将“运行权”交给A区应用功能程序前,BootLoader应当将使用过的外设恢复为默认状态,若A区应用功能程序需要使用这些外设,则由A区应用功能程序重新初始化。

4、无OTA事件时的分区跳转实现

(1)在STM32中,若需要直接访问寄存器,须通过汇编指令。在Boot.c文件中封装函数,负责初始化跳转A区后的SP指针。

__asm void MSR_SP(uint32_t addr)
{
	MSR MSP, r0
	BX r14             //主调函数返回,相当于return语句
}
//进入该函数后,函数的第一个形参addr会存在通用寄存器R0中,汇编指令仅支持访问寄存器,不支持访问变量

(2)编写将使用过的外设恢复为默认状态的函数。

①在Boot.c文件中定义外设恢复默认状态函数。

void BootLoader_Clear(void)
{
	USART_DeInit(USART1);
	GPIO_DeInit(GPIOA);
	GPIO_DeInit(GPIOB);
}

②在Boot.h文件中声明刚刚定义的函数。

void BootLoader_Clear(void);

(3)由于无法直接修改PC寄存器的值,需要定义一个指向函数的指针,指针指向A区程序的复位向量,调用该函数指针,就相当于调用A区的复位中断服务函数,从而间接修改PC寄存器的值为A区复位中断服务函数的入口地址。

①在Boot.c文件中定义函数指针并封装函数,负责初始化跳转A区后的SP指针和PC指针。

load_a load_A;     //函数指针

void LOAD_A(uint32_t addr)
{
	/* 先判断addr索引得到的__initial_sp是否在RAM的地址范围中,是则对SP指针和PC指针赋初始值 */
	if((*(uint32_t *)addr >= 0x20000000)&&((*(uint32_t *)addr <= 0x20004FFF)))
	{
		MSR_SP(*(uint32_t *)addr);      //对SP指针赋初始值
		load_A = (load_a)(*(uint32_t *)(addr + 4));   //取出复位中断服务函数的地址
		BootLoader_Clear();             //恢复外设默认状态
		load_A();    //直接访问复位中断服务函数,修改PC指针
	}
}

②在Boot.h文件中重命名void类型的函数指针,并声明刚刚定义的函数。

typedef void (*load_a)(void);

__asm void MSR_SP(uint32_t addr);
void LOAD_A(uint32_t addr);

③更新BootLoader_Branch函数,判断无OTA事件时调用跳转A分区的子函数。

void BootLoader_Branch(void)
{
	if(OTA_Info.OTA_Flag == OTA_SET_FLAG)
	{
		Serial_Printf("OTA更新\r\n");
	}
	else
	{
		Serial_Printf("跳转A分区\r\n");
		LOAD_A(STM32_A_SADDR);
	}
}

5、功能开发阶段性调试

(1)在main.c文件中添加如下调试代码。

#include "stm32f10x.h"                  // Device header
#include "Serial.h"
#include "MyDMA.h"
#include "Delay.h"
#include "MyI2C.h"
#include "W25Q64.h"
#include "AT24C02.h"
#include "MyFLASH.h"
#include "main.h"
#include "Boot.h"

OTA_InfoCB OTA_Info;

int main(void)
{
	/*串口模块初始化*/
	Serial_Init();
	U0Rx_PtrInit();
	MyDMA_Init();
	/*AT24C02模块初始化*/
	MyI2C_Init();
	/*W25Q64模块初始化*/
	W25Q64_Init();
	
	
	AT24C02_ReadOTAInfo();    //读取OTA Flag
	BootLoader_Branch();
	
	while (1)
	{
				
	}
}

(2)本章构建的工程是B分区的程序,A分区的程序需要另外准备。

①选择一个古早开发的成熟工程,按照下图所示将起始地址更改为0x08005000。

②将向量表偏移字段由0x0更改为0x5000(注意是偏移量,应为相对于Flash起始地址的差值)。

(3)依次将A分区程序和B分区程序下载到单片机中,理想情况下,串口助手输出“跳转A分区”字样,A分区程序随后开始运行。

二、程序更新功能实现

1、OTA更新相关变量的定义和保存

(1)修改main.h文件中OTA相关信息结构体的定义,增加一个数组成员,用于记录本次OTA程序及W25Q64中其它程序的大小(单位为字节),其中数组的0号成员为本次OTA程序大小。

typedef struct{
	uint32_t OTA_Flag;
	uint32_t APPlen[11];
}OTA_InfoCB;

(2)更新程序按照每次1KB的大小进行“分片式”更新(不管是OTA更新还是从W25Q64中读取程序并切换)。

①需在main.h文件中增加更新程序片段结构体的定义,用于缓存从W25Q64中读出的程序片内容以及本次更新程序在W25Q64中存储的块的编号。

typedef struct{
	uint8_t  Updatabuff[STM32_PAGE_SIZE];
	uint32_t W25Q64_BlockNumber;   //本次更新程序在W25Q64中存储的块的编号
}UpDataA_CB;

extern UpDataA_CB UpDataA;

②在main.c文件中定义更新程序片段结构体变量。

UpDataA_CB UpDataA;

(3)在AT24C02.c文件中增加写入OTA相关信息结构体的函数,并在AT24C02.h文件中声明。OTA信息相关结构体存储在AT24C02地址为0的地方。

①AT24C02.c文件增加内容:

void AT24C02_WriteOTAInfo(void)
{
	uint8_t *wptr;                //指向需写入的字节数据
	wptr = (uint8_t *)&OTA_Info;    //指向OTA_Info结构体中的首字节数据
	for(uint8_t i = 0;i < sizeof(OTA_InfoCB)/8;i++)  //按页写入W25Q64
	{
		AT24C02_WritePage(i * 8, wptr + i * 8);
		Delay_ms(5);
	}
}

②AT24C02.h文件增加内容:

void AT24C02_WriteOTAInfo(void);

2、从W25Q64更新A区程序

(1)在main.c文件中定义Boot状态变量BootStatus,并在main.h文件中进行外部声明,同时修改Boot.c文件中的BootLoader_Branch函数,当发生OTA事件时,BootLoader_Branch函数将状态变量BootStatus置为“A区程序更新”状态,代表需要更新A区程序,同时将更新程序片段结构体中的块号更改为0,即更新的程序从W25Q64的第0块取出。

①main.c文件增加内容:

uint32_t BootStatus;

②main.h文件增加内容:

#define UPDATA_A_FLAG       0x00000001    //A区程序更新状态

extern uint32_t BootStatus;

③Boot.c文件中的BootLoader_Branch函数修改为:

void BootLoader_Branch(void)
{
	if(OTA_Info.OTA_Flag == OTA_SET_FLAG)
	{
		Serial_Printf("OTA更新\r\n");
		
		BootStatus |= UPDATA_A_FLAG;
		UpDataA.W25Q64_BlockNumber = 0;
	}
	else
	{
		Serial_Printf("跳转A分区\r\n");
		LOAD_A(STM32_A_SADDR);
	}
}

(2)从W25Q64更新A区程序需分为几步,首先判断是否需要更新A区程序,然后获取需要更新的程序存放在W25Q64中的哪一块,并判断合法性,确认合法后再将其中的程序按片的形式更新到A区的Flash空间中,更新完成后,如果此次更新是由OTA引起,则需要清除OTA Flag。

①根据Boot状态变量BootStatus判断是否需要更新A区程序。

②需要更新的程序存放在W25Q64中的哪一块,可通过访问更新程序片段结构体的W25Q64_BlockNumber成员获取;本次更新程序的大小,可根据程序在W25Q64中存储的块的编号,访问更新程序片段结构体的APPlen[UpDataA.W25Q64_BlockNumber]成员获取。

③程序大小不为4的倍数(以字节为单位)都是不合法的,不能将其更新到A区中,同时应该清除状态变量BootStatus的“A区程序更新”状态,执行相应的后处理方案(比如直接跳转至A区运行老版本应用功能程序,具体方案需根据项目情况确定,以下代码暂无后处理方案设计),在得到合法的程序前,都不能将状态变量BootStatus恢复为“A区程序更新”状态。

④为了加快程序更新效率,首先按照1KB一片的大小进行“分片式”更新,最后更新不足1KB大小的一片(如有),另外在更新过程中,每从W25Q64中读取一片,应该将其暂存在更新程序片段结构体中的Updatabuff成员数组里,然后再写入A区的Flash空间中,写入前需擦除Flash中A区的所有内容。

⑤接着根据更新程序在W25Q64中的块号判断是否属于OTA更新,如果是,则清除OTA Flag,最后再重启单片机,完成更新。

(3)在main.c文件的主函数中增加A区程序更新相关代码。

uint8_t i = 0;

int main(void)
{
	/*串口模块初始化*/
	Serial_Init();
	U0Rx_PtrInit();
	MyDMA_Init();
	/*AT24C02模块初始化*/
	MyI2C_Init();
	/*W25Q64模块初始化*/
	W25Q64_Init();
	
	
	AT24C02_ReadOTAInfo();    //读取OTA信息相关结构体
	BootLoader_Branch();      //判断是否进行OTA更新
	
	while (1)
	{
		if(BootStatus & UPDATA_A_FLAG)  //判断是否为A区程序更新状态
		{
			Serial_Printf("程序长度%d字节\r\n",OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber]);
			if(OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 4 == 0)  //判断程序长度是否合法
			{
				MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
				for(i = 0;i < OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber]/STM32_PAGE_SIZE;i++)
				{
					W25Q64_ReadData(i * 1024 + UpDataA.W25Q64_BlockNumber * 64 * 1024, 
							UpDataA.Updatabuff, STM32_PAGE_SIZE);  //读取程序片段,暂存更新程序片段结构体中
					MyFLASH_WriteFlash(STM32_A_SADDR + i * STM32_PAGE_SIZE,
					(uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);  //按页写入Flash的A区空间
				}
				if(OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 1024 != 0)  //判断1024整除程序大小后是否有余数
				{
					W25Q64_ReadData(i * 1024 + UpDataA.W25Q64_BlockNumber * 64 * 1024, 
							UpDataA.Updatabuff, OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 1024);  //读取最后一段,暂存更新程序片段结构体中
					MyFLASH_WriteFlash(STM32_A_SADDR + i * STM32_PAGE_SIZE,
							(uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);  //将最后一段写入Flash的A区空间
				}
				if(UpDataA.W25Q64_BlockNumber == 0)  //判断是否属于OTA更新
				{
					OTA_Info.OTA_Flag = 0;    //清除OTA Flag
					AT24C02_WriteOTAInfo();   //将OTA Flag保存在24C02中
				}
				NVIC_SystemReset();    //重启单片机
			}
			else
			{
				Serial_Printf("程序长度错误\r\n");
				BootStatus &= ~UPDATA_A_FLAG;      //清除A区程序更新状态
			}
		}
	}
}

三、串口交互式命令行功能实现

1、串口命令行进入方式及提示菜单

(1)在单片机上电后,约定一个时间段,在时间段内,如果上位机通过串口发出特定的命令,则进入命令行,并打印命令菜单提示,否则直接判断是否有OTA更新事件。

(2)在Boot.c文件中修改BootLoader_Branch函数,增加判断是否不需要进入命令行的逻辑。

#include "Delay.h"

void BootLoader_Branch(void){
	if(BootLoader_Enter(20)== 0)   //判断是否不需要进入命令行
	{
		if(OTA_Info.OTA_Flag == OTA_SET_FLAG){
			Serial_Printf("OTA更新\r\n");
			
			BootStatus |= UPDATA_A_FLAG;
			UpDataA.W25Q64_BlockNumber = 0;
		}
		else{
			Serial_Printf("跳转A分区\r\n");
			LOAD_A(STM32_A_SADDR);
		}
	}
	else
	{
		Serial_Printf("进入BootLoader命令行\r\n");
		BootLoader_Info();    //打印命令菜单
	}
}

uint8_t BootLoader_Enter(uint8_t timeout){
	Serial_Printf("%dms内输入小写字母“w”,则进入BootLoader命令行\r\n",timeout*100);
	while(timeout--)    //如果形参timeout初始值为20,则2000ms内等待上位机指令
	{
		Delay_ms(100);
		if(U0_RxBuff[0] == 'w')  return 1;    //返回1,表示进入命令行
	}
	return 0;    //返回0,表示不进入命令行
}

void BootLoader_Info(void){
	Serial_Printf("\r\n");
	Serial_Printf("[1]擦除A区程序\r\n");
	Serial_Printf("[2]串口IAP下载A区程序\r\n");
	Serial_Printf("[3]设置OTA版本号\r\n");
	Serial_Printf("[4]查询OTA版本号\r\n");
	Serial_Printf("[5]向外部Flash下载程序\r\n");
	Serial_Printf("[6]使用外部Flash内的程序\r\n");
	Serial_Printf("[7]重启\r\n");
}

(3)在Boot.h文件中声明新增的子函数。

uint8_t BootLoader_Enter(uint8_t timeout);
void BootLoader_Info(void);

2、擦除A区程序功能

(1)在Boot.c文件中新增BootLoader_Event函数,函数可根据上位机输入的命令执行相应的处理逻辑。首先实现擦除A区程序的功能。

#include "MyFLASH.h"
void BootLoader_Event(uint8_t *data, uint16_t datalen){
	if((datalen == 1)&&(data[0] == '1'))
	{
		Serial_Printf("擦除A区程序\r\n");
		MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
	}
}

(2)在Boot.h文件中声明BootLoader_Event函数,以便主函数调用。

void BootLoader_Event(uint8_t *data, uint16_t datalen);

(3)由于计划实现通过命令行擦除A区程序的功能,那么就会存在擦除A区程序后,A区没有程序的情况,因此需要修改Boot.c文件中的LOAD_A函数,如果判断A区向量表中的SP初始值不合法,就发出信息提示——“跳转A分区失败”,并且不能跳转至A分区。

void LOAD_A(uint32_t addr)
{
	/* 先判断addr索引得到的__initial_sp是否在RAM的地址范围中,是则对SP指针和PC指针赋初始值 */
	if((*(uint32_t *)addr >= 0x20000000)&&((*(uint32_t *)addr <= 0x20004FFF)))
	{
		MSR_SP(*(uint32_t *)addr);       //对SP指针赋初始值
		load_A = (load_a)(*(uint32_t *)(addr + 4));   //取出复位中断服务函数的地址
		BootLoader_Clear();             //恢复外设默认状态
		load_A();    //直接访问复位中断服务函数,修改PC指针
	}
	else
	{
		Serial_Printf("跳转A分区失败\r\n");
	}
}

(4)更新主函数,在判断是否需要更新A区程序之前,先判断上位机是否有发出命令或其它数据,如有,则调用BootLoader_Event函数,对收到的命令或数据进行处理。

int main(void){
	Serial_Init(); U0Rx_PtrInit(); MyDMA_Init(); MyI2C_Init(); W25Q64_Init();
	AT24C02_ReadOTAInfo();    //读取OTA信息相关结构体
	BootLoader_Branch();      //判断是否进行OTA更新
	while (1){
		if(U0CB.URxDataOUT != U0CB.URxDataIN){  //判断是否有接收数据未处理
			BootLoader_Event(U0CB.URxDataOUT->start, U0CB.URxDataOUT->end - U0CB.URxDataOUT->start + 1);
			U0CB.URxDataOUT++;    //读出一组数据,OUT指针右移
			if(U0CB.URxDataOUT == U0CB.URxDataEND)
				U0CB.URxDataOUT = &U0CB.URxDataPtr[0];   //OUT指针越界回滚
		}
		if(BootStatus & UPDATA_A_FLAG){ //判断是否为A区程序更新状态
			Serial_Printf("程序长度%d字节\r\n",OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber]);
			if(OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 4 == 0){
				MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);
				for(i = 0;i < OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber]/STM32_PAGE_SIZE;i++){
					W25Q64_ReadData(i * 1024 + UpDataA.W25Q64_BlockNumber * 64 * 1024, UpDataA.Updatabuff, STM32_PAGE_SIZE); 
					MyFLASH_WriteFlash(STM32_A_SADDR + i * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);
				}
				if(OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 1024 != 0){
					W25Q64_ReadData(i * 1024 + UpDataA.W25Q64_BlockNumber * 64 * 1024, UpDataA.Updatabuff, OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 1024);
					MyFLASH_WriteFlash(STM32_A_SADDR + i * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);
				}
				if(UpDataA.W25Q64_BlockNumber == 0){
					OTA_Info.OTA_Flag = 0;  AT24C02_WriteOTAInfo();
				}
				NVIC_SystemReset();
			}
			else{
				Serial_Printf("程序长度错误\r\n");  BootStatus &= ~UPDATA_A_FLAG; 
			}
		}
	}
}

3、重启单片机功能

        更新Boot.c文件中的BootLoader_Event函数,增加重启单片机功能

void BootLoader_Event(uint8_t *data, uint16_t datalen)
{
	if((datalen == 1)&&(data[0] == '1'))
	{
		Serial_Printf("擦除A区程序\r\n");
		MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
	}
	else if((datalen == 1)&&(data[0] == '7'))
	{
		Serial_Printf("重启\r\n");
		Delay_ms(100);
		NVIC_SystemReset();    //重启单片机
	}
}

4、串口IAP下载A区程序功能

(1)因为接下来会使用Xmdoem协议,所以后续的实验需要借助比较高级的“串口助手”SecureCRT,打开SecureCRT后,配置如下图所示的快速连接(端口需要选择与STM32连接的端口),然后点击“连接”即可。

(2)Xmdoem协议单次数据传输流程如下图所示,它用于大容量数据分片传输,将每一片数据都分别通过一个数据包传输。

①首先接收方发送字符C,然后间隔一段时间(不一定是3s,需要接收方和发送方约定),接收方再发送一次字符C,发送方能成功收到两个字符C后,开始传输数据包。(这一步相当于“握手”)

②数据包的格式如下,从发送顺序看,依次为:

[1]SOH = 0x01,标识数据包的起始标志,占1个字节。

[2]本轮传输中数据包的编号,标识这是第几片数据,占1个字节。

[3]本轮传输中数据包的编号的反码,占1个字节。

[4]数据包的主体数据,也就是被“分割成一片”的数据,占128个字节。

[5]16位CRC校验码,占2个字节。

③整包数据的大小为133字节,接收方收到数据包后,如果通过CRC校验,则发送肯定应答标志ACK = 0x06,告知发送方数据传输无误,继续下一组传输,如果未通过CRC校验,则发送否定应答标志NAK = 0x15,告知发送方数据传输有误,发送方须将本组数据重传一遍。

④当发送方完成数据传输后,发送结束标志EOT = 0x04,接收方收到结束标志后发送肯定应答标志ACK = 0x06,发送方收到ACK后再发送一次结束标志EOT = 0x04,接收方收到该标志后,结束本轮数据传输。

(3)Xmdoem协议中CRC数据校验的实现:

①由于STM32F103C8T6的硬件只支持32位CRC校验,因此16位CRC校验只能通过软件实现。

②循环冗余校验(CRC,Cyclic Redundancy Check)算法介绍:

[1]收发双方约定好一个生成多项式G(x),发送方基于待发送的数据和生成多项式计算出差错检测码(冗余码),将其添加到待传输数据后面一起传输,接收方通过生成多项式计算收到的数据是否产生了误码。

[2]发送方的处理:

        待发送的数据作为被除数的一部分,后面添加生成多项式最高次个0(下面的生成多项式举例中生成多项式最高次个0为4个0)以构成被除数,生成多项式各项系数构成的比特串作为除数,进行除法运算,得到商和余数

        余数就是所计算出的冗余码,将其添加到待发送数据的后面一起发送

        冗余码的长度与生成多项式最高次数相同,商仅作为标记防止计算过程中对错位

[3]接收方的处理:

        将接收到的数据作为被除数,除数仍是生成多项式各项系数构成的比特串,进行除法运算,得到商和余数

        如果余数为0,可判定传输过程中没有产生误码;如果余数不为0,可判定传输过程中产生了误码

[4]生成多项式的各项系数只能是0或1;CRC算法要求生成多项式必须包含最低次项。

[5]循环冗余校验CRC举例:

③在Boot.c文件中添加CRC校验函数实现。

uint16_t Xmodem_CRC16(uint8_t *data, uint16_t datalen){
	uint8_t i;
	uint16_t Crcinit = 0x0000;    //初始值
	uint16_t Crcipoly = 0x1021;   //生成多项式
	
	while(datalen--){
		Crcinit = (*data << 8) ^ Crcinit;
		for(i = 0;i < 8;i++){
			if(Crcinit & 0x8000)
				Crcinit = (Crcinit << 1) ^ Crcipoly;
			else
				Crcinit = (Crcinit << 1);
		}
		data++;
	}
	return Crcinit;
}

④在Boot.h文件中添加CRC校验函数声明。

uint16_t Xmodem_CRC16(uint8_t *data, uint16_t datalen);

(4)Xmdoem协议中发送字符C的实现:

①发送字符C分为事件触发和事件处理两个步骤,如下所示。

②更新main.h文件中UpDataA_CB结构体的定义,增加成员Xmdoem计时器,用于管理接收数据前两个字符C的发送间隔。

typedef struct{
	uint8_t  Updatabuff[STM32_PAGE_SIZE];
	uint32_t W25Q64_BlockNumber;   //本次更新程序在W25Q64中存储的块的编号
	uint32_t XmodemTimer;
}UpDataA_CB;

③在main.h文件中添加宏定义,指示Xmodem协议中发送字符C事件。

#define IAP_XMODEMC_FLAG    0x00000002    //Xmodem协议中发送字符C事件标志

④更新Boot.c文件中的BootLoader_Event函数,增加串口IAP下载A区程序功能分支,进入此分支后,先擦除A区程序,然后置位BootStatus的IAP_XMODEMC_FLAG标志,表示有发送字符C事件等待处理,同时重置Xmdoem计时器。

void BootLoader_Event(uint8_t *data, uint16_t datalen){
	if(BootStatus == 0){
		if((datalen == 1)&&(data[0] == '1')){
			Serial_Printf("擦除A区程序\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);
		}
		else if((datalen == 1)&&(data[0] == '2')){
			Serial_Printf("使用Xmodem协议,串口IAP下载A区程序,请添加bin格式文件\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
			BootStatus |= IAP_XMODEMC_FLAG;  //置位BootStatus的IAP_XMODEMC_FLAG标志,表示有发送字符C事件等待处理
			UpDataA.XmodemTimer = 0;
		}
		else if((datalen == 1)&&(data[0] == '7')){
			Serial_Printf("重启\r\n"); Delay_ms(100); NVIC_SystemReset();
		}
	}
}

⑤更新主函数,在判断是否需要更新A区程序前,增加字符C事件的处理逻辑。

int main(void)
{
	/*串口模块初始化*/
	Serial_Init();
	U0Rx_PtrInit();
	MyDMA_Init();
	/*AT24C02模块初始化*/
	MyI2C_Init();
	/*W25Q64模块初始化*/
	W25Q64_Init();
	
	AT24C02_ReadOTAInfo();    //读取OTA信息相关结构体
	BootLoader_Branch();      //判断是否进行OTA更新
	
	while (1)
	{
		Delay_ms(10);
		if(U0CB.URxDataOUT != U0CB.URxDataIN)   //判断是否有接收数据未处理
		{
			BootLoader_Event(U0CB.URxDataOUT->start, U0CB.URxDataOUT->end - U0CB.URxDataOUT->start + 1);
			
			U0CB.URxDataOUT++;    //读出一组数据,OUT指针右移
			if(U0CB.URxDataOUT == U0CB.URxDataEND)
			{
				U0CB.URxDataOUT = &U0CB.URxDataPtr[0];   //OUT指针越界回滚
			}
		}

		if(BootStatus & IAP_XMODEMC_FLAG)   //判断是否有发送字符C事件未处理
		{
			if(UpDataA.XmodemTimer >= 100)  //判断计时时间是否达到1s
			{
				Serial_Printf("C");
				UpDataA.XmodemTimer = 0;   //重置XmodemTimer
			}
			UpDataA.XmodemTimer++;         //XmodemTimer递增计时
		}
		
		if(BootStatus & UPDATA_A_FLAG)  //判断是否为A区程序更新状态
		{
			Serial_Printf("程序长度%d字节\r\n",OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber]);
			if(OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 4 == 0)  //判断程序长度是否合法
			{
				MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
				for(i = 0;i < OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber]/STM32_PAGE_SIZE;i++)
				{
					W25Q64_ReadData(i * 1024 + UpDataA.W25Q64_BlockNumber * 64 * 1024, 
							UpDataA.Updatabuff, STM32_PAGE_SIZE);  //读取程序片段,暂存更新程序片段结构体中
					MyFLASH_WriteFlash(STM32_A_SADDR + i * STM32_PAGE_SIZE,
					(uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);  //按页写入Flash的A区空间
				}
				if(OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 1024 != 0)  //判断1024整除程序大小后是否有余数
				{
					W25Q64_ReadData(i * 1024 + UpDataA.W25Q64_BlockNumber * 64 * 1024, 
							UpDataA.Updatabuff, OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 1024);  //读取最后一段,暂存更新程序片段结构体中
					MyFLASH_WriteFlash(STM32_A_SADDR + i * STM32_PAGE_SIZE,
							(uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);  //将最后一段写入Flash的A区空间
				}
				if(UpDataA.W25Q64_BlockNumber == 0)  //判断是否属于OTA更新
				{
					OTA_Info.OTA_Flag = 0;    //清除OTA Flag
					AT24C02_WriteOTAInfo();   //将OTA Flag保存在24C02中
				}
				NVIC_SystemReset();    //重启单片机
			}
			else
			{
				Serial_Printf("程序长度错误\r\n");
				BootStatus &= ~UPDATA_A_FLAG;      //清除A区程序更新状态
			}
		}
	}
}

(5)接收方Xmdoem协议实现:

①更新main.h文件中UpDataA_CB结构体的定义,增加成员XmdoemNB记录当前接收的数据包个数,增加成员XmdoemCRC记录接收数据包的CRC校验码。

typedef struct{
	uint8_t  Updatabuff[STM32_PAGE_SIZE];
	uint32_t W25Q64_BlockNumber;   //本次更新程序在W25Q64中存储的块的编号
	uint32_t XmodemTimer;          //字符C发送间隔
	uint32_t XmodemNB;             //当前接收的数据包个数
	uint32_t XmodemCRC;            //接收数据包的CRC校验码
}UpDataA_CB;

②在main.h文件中添加宏定义,指示Xmodem协议中接收数据事件。

#define IAP_XMODEMD_FLAG    0x00000004    //Xmodem协议中接收数据事件标志

③更新Boot.c文件中的BootLoader_Event函数,增加接收数据事件的处理逻辑,在实现Xmodem协议接收数据的同时,将程序“分片式”地写入A区(在实际开发中需对接收的数据做充分校验,比如检查数据包编号等,此处仅做CRC校验)。接收数据事件的处理逻辑如下图所示。

void BootLoader_Event(uint8_t *data, uint16_t datalen)
{
	if(BootStatus == 0)
	{
		if((datalen == 1)&&(data[0] == '1'))
		{
			Serial_Printf("擦除A区程序\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
		}
		else if((datalen == 1)&&(data[0] == '2'))
		{
			Serial_Printf("使用Xmodem协议,串口IAP下载A区程序,请添加bin格式文件\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
			BootStatus |= IAP_XMODEMC_FLAG;  //置位BootStatus的IAP_XMODEMC_FLAG标志,表示有发送字符C事件等待处理			
			BootStatus |= IAP_XMODEMD_FLAG;  //置位BootStatus的IAP_XMODEMD_FLAG标志,表示有接收数据事件等待处理
			UpDataA.XmodemNB = 0;      //接收数据包个数重置为0
			UpDataA.XmodemTimer = 0;
		}
		else if((datalen == 1)&&(data[0] == '7'))
		{
			Serial_Printf("重启\r\n");
			Delay_ms(100);
			NVIC_SystemReset();    //重启单片机
		}
	}
	
	if(BootStatus & IAP_XMODEMD_FLAG)   //判断是否有接收数据事件未处理
	{
		if((datalen== 133) && (data[0] == 0x01))  //判断数据包是否合法
		{
			BootStatus &= ~IAP_XMODEMC_FLAG;   //清除发送字符C事件标志
			UpDataA.XmodemCRC = Xmodem_CRC16(&data[3], 128);  //根据接收数据计算CRC校验码
			if(UpDataA.XmodemCRC == data[131]* 256 + data[132])
			{
				Serial_Printf("\x06");        //通过CRC校验,发送ACK
				UpDataA.XmodemNB++;           //接收数据包数加1
				
				memcpy(&UpDataA.Updatabuff[(UpDataA.XmodemNB - 1)%(STM32_PAGE_SIZE/128)* 128],&data[3], 128);   //接收的有效数据拷贝至缓冲区
				if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))== 0)   //判断接收的数据是否足够1KB
				{
					MyFLASH_WriteFlash(STM32_A_SADDR + (UpDataA.XmodemNB/(STM32_PAGE_SIZE/128) - 1) * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);   //写入A区
				}
			}
			else
			{
				Serial_Printf("\x15");        //未通过CRC校验,发送NAK
			}
		}
		if((datalen== 1) && (data[0] == 0x04))  //判断接收的内容是否为EOT
		{
			Serial_Printf("\x06");        //发送ACK
			if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))!= 0)   //判断是否有剩余不足够1KB的数据未写入A区
			{
				MyFLASH_WriteFlash(STM32_A_SADDR + (UpDataA.XmodemNB/(STM32_PAGE_SIZE/128)) * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,(UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))*128);
			}
			BootStatus &= ~IAP_XMODEMD_FLAG;   //清除接收数据事件标志
			Delay_ms(100);
			NVIC_SystemReset();    //重启单片机
		}
	}
}

(6)功能开发阶段性测试:

①打开另一个成熟的古早工程(中断向量表的位置需要配置为0x08005000),按照下图所示配置,其中指示处输入的命令如下。

D:\Keil(MDK)\ARM\ARMCC\bin\fromelf.exe --bin -o.\Objects\Project.bin .\Objects\Project.axf

        需要注意的是,fromelf.exe文件是在Keil的安装目录下的,也就是说,需要参考Keil的安装路径,在该安装路径下找到fromelf.exe文件,再将“fromelf.exe”及其之前的路径替换为电脑中存放fromelf.exe文件的路径,另外文件夹Objects需要在项目文件夹中

        还需要注意的是,Objects文件夹需要在工程文件夹中存在,Project.bin和Project.axf需与Keil工程同名

②打开“Create HEX File”选项,然后编译生成应用功能程序,在对应目录下找到bin文件。

③重启单片机,先将BootLoader程序下载到单片机中,然后按照命令提示进入串口IAP下载A区程序功能,等待单片机开始发送字符C,然后操作上位机,使用Xmodem协议传输应用功能程序bin文件。

5、设置及查询OTA版本号功能

(1)更新main.h文件中OTA_InfoCB结构体的定义,增加成员用于记录OTA版本号(版本号格式定义为“VER-1.0.0-2026/03/01-11:45”),并添加宏定义,指示OTA版本号设置事件。

#define SET_VERSION_FLAG    0x00000008    //OTA版本号设置事件标志

typedef struct{
	uint32_t OTA_Flag;
	uint32_t APPlen[11];
	uint8_t OTA_ver[32];
}OTA_InfoCB;

(2)更新Boot.c文件中的BootLoader_Event函数,增加设置及查询OTA版本号的处理逻辑。

#include "AT24C02.h"

void BootLoader_Event(uint8_t *data, uint16_t datalen){
	int temp;
	
	if(BootStatus == 0)
	{
		if((datalen == 1)&&(data[0] == '1'))
		{
			Serial_Printf("擦除A区程序\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
		}
		else if((datalen == 1)&&(data[0] == '2'))
		{
			Serial_Printf("使用Xmodem协议,串口IAP下载A区程序,请添加bin格式文件\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);
			BootStatus |= IAP_XMODEMC_FLAG;
			BootStatus |= IAP_XMODEMD_FLAG;
			UpDataA.XmodemNB = 0;
			UpDataA.XmodemTimer = 0;
		}
		else if((datalen == 1)&&(data[0] == '3'))
		{
			Serial_Printf("设置版本号\r\n");
			BootStatus |= SET_VERSION_FLAG;  //置位BootStatus的SET_VERSION_FLAG标志,表示有设置OTA版本号事件等待处理
		}
		else if((datalen == 1)&&(data[0] == '4'))
		{
			Serial_Printf("查询版本号\r\n");
			AT24C02_ReadOTAInfo();           //读出EEPROM中的版本号
			Serial_Printf("版本号:%s\r\n",OTA_Info.OTA_ver);
			BootLoader_Info();                //重新打印命令菜单
		}
		else if((datalen == 1)&&(data[0] == '7'))
		{
			Serial_Printf("重启\r\n");
			Delay_ms(100);
			NVIC_SystemReset();
		}
	}
	else if(BootStatus & IAP_XMODEMD_FLAG) 
	{
		if((datalen== 133) && (data[0] == 0x01)) 
		{
			BootStatus &= ~IAP_XMODEMC_FLAG;
			UpDataA.XmodemCRC = Xmodem_CRC16(&data[3], 128);
			if(UpDataA.XmodemCRC == data[131]* 256 + data[132])
			{
				Serial_Printf("\x06");       
				UpDataA.XmodemNB++; 
				memcpy(&UpDataA.Updatabuff[(UpDataA.XmodemNB - 1)%(STM32_PAGE_SIZE/128)* 128],&data[3], 128); 
				if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))== 0)
				{
					MyFLASH_WriteFlash(STM32_A_SADDR + (UpDataA.XmodemNB/(STM32_PAGE_SIZE/128) - 1) * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE); 
				}
			}
			else
			{
				Serial_Printf("\x15");
			}
		}
		if((datalen == 1) && (data[0] == 0x04))
		{
			Serial_Printf("\x06");
			if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))!= 0)
			{
				MyFLASH_WriteFlash(STM32_A_SADDR + (UpDataA.XmodemNB/(STM32_PAGE_SIZE/128)) * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,(UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))*128);
			}
			BootStatus &= ~IAP_XMODEMD_FLAG;
			Delay_ms(100);
			NVIC_SystemReset();
		}
	}
	else if(BootStatus & SET_VERSION_FLAG)   //判断是否有设置OTA版本号事件未处理
	{
		if(datalen == 26)  //判断版本号长度是否合法
		{
			if(sscanf((char*)data,"VER-%d.%d.%d-%d/%d/%d-%d:%d",&temp,&temp,&temp,&temp,&temp,&temp,&temp,&temp)== 8)    //判断版本号格式是否合法
			{
				memset(OTA_Info.OTA_ver, 0, 32);
				memcpy(OTA_Info.OTA_ver, data, 26);  //将请求设置的版本号赋给OTA_ver
				AT24C02_WriteOTAInfo();             //版本号存入EEPROM
				Serial_Printf("设置版本号成功\r\n");
				BootStatus &= ~SET_VERSION_FLAG;   //清除设置OTA版本号事件标志位
				BootLoader_Info();    //重新打印命令菜单
			}
			else
			{
				Serial_Printf("设置版本号格式错误\r\n");
			}
		}
		else
		{
			Serial_Printf("设置版本号长度错误\r\n");
		}
	}
}

6、使用外部Flash内的程序功能

(1)在main.h文件中添加宏定义,分别指示向外部Flash下载程序事件和使用外部Flash内的程序事件。

#define DOWNLOAD_TOFALSH    0x00000010    //向外部Flash下载程序事件标志
#define UPLOAD_FROMFLASH    0x00000020    //使用外部Flash内的程序事件标志

(2)向外部Flash下载程序的命令执行流程:

(3)将外部Flash内的程序写入STM32Flash的命令执行流程:

(4)更新Boot.c文件中的BootLoader_Event函数,增加向外部Flash下载程序以及将外部Flash内的程序写入STM32Flash的处理逻辑。

void BootLoader_Event(uint8_t *data, uint16_t datalen)
{
	int temp;
	
	if(BootStatus == 0)
	{
		if((datalen == 1)&&(data[0] == '1'))
		{
			Serial_Printf("擦除A区程序\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
		}
		else if((datalen == 1)&&(data[0] == '2'))
		{
			Serial_Printf("使用Xmodem协议,串口IAP下载A区程序,请添加bin格式文件\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
			BootStatus |= IAP_XMODEMC_FLAG;  //置位BootStatus的IAP_XMODEMC_FLAG标志,表示有发送字符C事件等待处理			
			BootStatus |= IAP_XMODEMD_FLAG;  //置位BootStatus的IAP_XMODEMD_FLAG标志,表示有接收数据事件等待处理
			UpDataA.XmodemNB = 0;      //接收数据包个数重置为0
			UpDataA.XmodemTimer = 0;
		}
		else if((datalen == 1)&&(data[0] == '3'))
		{
			Serial_Printf("设置版本号\r\n");
			BootStatus |= SET_VERSION_FLAG;  //置位BootStatus的SET_VERSION_FLAG标志,表示有设置OTA版本号事件等待处理
		}
		else if((datalen == 1)&&(data[0] == '4'))
		{
			Serial_Printf("查询版本号\r\n");
			AT24C02_ReadOTAInfo();           //读出EEPROM中的版本号
			Serial_Printf("版本号:%s\r\n",OTA_Info.OTA_ver);
			BootLoader_Info();    //重新打印命令菜单
		}
		else if((datalen == 1)&&(data[0] == '5'))
		{
			Serial_Printf("向外部FLASH下载程序,请输入需要使用的块编号,范围1~9\r\n");
			BootStatus |= DOWNLOAD_TOFALSH;  //置位BootStatus的DOWNLOAD_TOFALSH标志,表示有程序包要写入外部Flash的事件等待处理
		}
		else if((datalen == 1)&&(data[0] == '6'))
		{
			Serial_Printf("使用外部Flash内的程序,请输入需要使用的块编号,范围1~9\r\n");
			BootStatus |= UPLOAD_FROMFLASH;  //置位BootStatus的UPLOAD_FROMFLASH标志,表示有从外部Flash写程序入内部FLASH的事件等待处理
		}
		else if((datalen == 1)&&(data[0] == '7'))
		{
			Serial_Printf("重启\r\n");
			Delay_ms(100);
			NVIC_SystemReset();    //重启单片机
		}
	}
	else if(BootStatus & IAP_XMODEMD_FLAG)   //判断是否有接收数据事件未处理
	{
		if((datalen== 133) && (data[0] == 0x01))  //判断数据包是否合法
		{
			BootStatus &= ~IAP_XMODEMC_FLAG;   //清除发送字符C事件标志
			UpDataA.XmodemCRC = Xmodem_CRC16(&data[3], 128);  //根据接收数据计算CRC校验码
			if(UpDataA.XmodemCRC == data[131]* 256 + data[132])
			{
				Serial_Printf("\x06");        //通过CRC校验,发送ACK
				UpDataA.XmodemNB++;           //接收数据包数加1
				
				memcpy(&UpDataA.Updatabuff[(UpDataA.XmodemNB - 1)%(STM32_PAGE_SIZE/128)* 128],&data[3], 128);   //接收的有效数据拷贝至缓冲区
				if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))== 0)   //判断接收的数据是否足够1KB
				{
					MyFLASH_WriteFlash(STM32_A_SADDR + (UpDataA.XmodemNB/(STM32_PAGE_SIZE/128) - 1) * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);   //写入A区
				}
			}
			else
			{
				Serial_Printf("\x15");        //未通过CRC校验,发送NAK
			}
		}
		if((datalen == 1) && (data[0] == 0x04))  //判断接收的内容是否为EOT
		{
			Serial_Printf("\x06");        //发送ACK
			if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))!= 0)   //判断是否有剩余不足够1KB的数据未写入A区
			{
				MyFLASH_WriteFlash(STM32_A_SADDR + (UpDataA.XmodemNB/(STM32_PAGE_SIZE/128)) * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,(UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))*128);
			}
			BootStatus &= ~IAP_XMODEMD_FLAG;   //清除接收数据事件标志
			Delay_ms(100);
			NVIC_SystemReset();    //重启单片机
		}
	}
	else if(BootStatus & SET_VERSION_FLAG)   //判断是否有设置OTA版本号事件未处理
	{
		if(datalen == 26)  //判断版本号长度是否合法
		{
			if(sscanf((char*)data,"VER-%d.%d.%d-%d/%d/%d-%d:%d",&temp,&temp,&temp,&temp,&temp,&temp,&temp,&temp)== 8)    //判断版本号格式是否合法
			{
				memset(OTA_Info.OTA_ver, 0, 32);
				memcpy(OTA_Info.OTA_ver, data, 26);    //将请求设置的版本号赋给OTA_ver
				AT24C02_WriteOTAInfo();                //版本号存入EEPROM
				Serial_Printf("设置版本号成功\r\n");
				BootStatus &= ~SET_VERSION_FLAG;       //清除设置OTA版本号事件标志位
				BootLoader_Info();    //重新打印命令菜单
			}
			else
			{
				Serial_Printf("设置版本号格式错误\r\n");
			}
		}
		else
		{
			Serial_Printf("设置版本号长度错误\r\n");
		}
	}
	else if(BootStatus & DOWNLOAD_TOFALSH)   //判断是否有向外部Flash下载程序事件未处理
	{
		if((datalen== 133) && (data[0] == 0x01))  //判断数据包是否合法
		{
			BootStatus &= ~IAP_XMODEMC_FLAG;   //清除发送字符C事件标志
			UpDataA.XmodemCRC = Xmodem_CRC16(&data[3], 128);  //根据接收数据计算CRC校验码
			if(UpDataA.XmodemCRC == data[131]* 256 + data[132])
			{
				Serial_Printf("\x06");        //通过CRC校验,发送ACK
				UpDataA.XmodemNB++;           //接收数据包数加1
				
				memcpy(&UpDataA.Updatabuff[(UpDataA.XmodemNB - 1)%(STM32_PAGE_SIZE/128)* 128],&data[3], 128);   //接收的有效数据拷贝至缓冲区
				if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))== 0)   //判断接收的数据是否足够1KB
				{
					for(uint8_t i = 0;i < 4;i++)
					{
						W25Q64_PageProgram((UpDataA.XmodemNB / 8 - 1)* 1024 + i * 256 + UpDataA.W25Q64_BlockNumber * 64 * 1024,&UpDataA.Updatabuff[i * 256], 256);  //写入外部FLASH
					}
				}
			}
			else
			{
				Serial_Printf("\x15");        //未通过CRC校验,发送NAK
			}
		}
		else if((datalen == 1) && (data[0] == 0x04))  //判断接收的内容是否为EOT
		{
			Serial_Printf("\x06");        //发送ACK
			if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))!= 0)   //判断是否有剩余不足够1KB的数据未写入A区
			{
				for(uint8_t i = 0;i < 4;i++)
				{
					W25Q64_PageProgram((UpDataA.XmodemNB / 8)* 1024 + i * 256 + UpDataA.W25Q64_BlockNumber * 64 * 1024,&UpDataA.Updatabuff[i * 256], 256);  //写入外部FLASH
				}
			}
			BootStatus &= ~DOWNLOAD_TOFALSH;   //清除向外部Flash下载程序事件标志
			OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] = UpDataA.XmodemNB * 128;  //记录程序字节数
			AT24C02_WriteOTAInfo();            //将程序字节数保存在EEPROM中
			Delay_ms(100);
			BootLoader_Info();    //重新打印命令菜单
		}
		else    //上位机发送的不是数据包,则应该是块编号
		{
			if((datalen == 1)&&(data[0]>= 0x31)&&(data[0]<= 0x39))  //判断块编号是否合法(由于取值1~9,则可根据长度判断)
			{
				UpDataA.W25Q64_BlockNumber = data[0] - 0x30;    //记录块编号
				UpDataA.XmodemTimer = 0;
				UpDataA.XmodemNB = 0;
				OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] = 0;
				W25Q64_Erase64K(UpDataA.W25Q64_BlockNumber);    //擦除对应块的原始数据
				
				Serial_Printf("使用Xmodem协议,向外部FLASH第%d块下载程序,请添加bin格式文件\r\n",UpDataA.W25Q64_BlockNumber);
				BootStatus |= IAP_XMODEMC_FLAG;  //触发发送字符C事件,与上位机完成握手
			}
			else
			{
				Serial_Printf("块编号不合法\r\n");
			}
		}
	}
	else if(BootStatus & UPLOAD_FROMFLASH)   //判断是否有使用外部Flash内的程序事件未处理
	{
		if((datalen == 1)&&(data[0]>= 0x31)&&(data[0]<= 0x39))  //判断块编号是否合法(由于取值1~9,则可根据长度判断)
		{
			
			UpDataA.W25Q64_BlockNumber = data[0] - 0x30;    //更新操作的块编号
			BootStatus |= UPDATA_A_FLAG;      //触发更新A区程序事件
			BootStatus &= ~UPLOAD_FROMFLASH;  //清除使用外部Flash内的程序事件标志
		}
	}
}

四、关键代码概览

1、Boot.c文件

#include "stm32f10x.h"                  // Device header
#include "main.h"
#include "Boot.h"
#include "Serial.h"
#include "Delay.h"
#include "MyDMA.h"
#include "MyI2C.h"
#include "W25Q64.h"
#include "MyFLASH.h"
#include "AT24C02.h"
#include <string.h>

load_a load_A;

void BootLoader_Branch(void)
{
	if(BootLoader_Enter(20)== 0)   //判断是否不需要进入命令行
	{
		if(OTA_Info.OTA_Flag == OTA_SET_FLAG)
		{
			Serial_Printf("OTA更新\r\n");
			BootStatus |= UPDATA_A_FLAG;
			UpDataA.W25Q64_BlockNumber = 0;
		}
		else
		{
			Serial_Printf("跳转A分区\r\n");
			LOAD_A(STM32_A_SADDR);
		}
	}
	else
	{
		Serial_Printf("进入BootLoader命令行\r\n");
		BootLoader_Info();    //打印命令菜单
	}
}

uint8_t BootLoader_Enter(uint8_t timeout)
{
	Serial_Printf("%dms内输入小写字母“w”,则进入BootLoader命令行\r\n",timeout*100);
	while(timeout--)    //如果形参timeout初始值为20,则2000ms内等待上位机指令
	{
		Delay_ms(100);
		if(U0_RxBuff[0] == 'w')
		{
			return 1;    //返回1,表示进入命令行
		}
	}
	return 0;    //返回0,表示不进入命令行
}

void BootLoader_Info(void)
{
	Serial_Printf("\r\n");
	Serial_Printf("[1]擦除A区程序\r\n");
	Serial_Printf("[2]串口IAP下载A区程序\r\n");
	Serial_Printf("[3]设置OTA版本号\r\n");
	Serial_Printf("[4]查询OTA版本号\r\n");
	Serial_Printf("[5]向外部Flash下载程序\r\n");
	Serial_Printf("[6]使用外部Flash内的程序\r\n");
	Serial_Printf("[7]重启\r\n");
}

void BootLoader_Event(uint8_t *data, uint16_t datalen)
{
	int temp;
	
	if(BootStatus == 0)
	{
		if((datalen == 1)&&(data[0] == '1'))
		{
			Serial_Printf("擦除A区程序\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
		}
		else if((datalen == 1)&&(data[0] == '2'))
		{
			Serial_Printf("使用Xmodem协议,串口IAP下载A区程序,请添加bin格式文件\r\n");
			MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
			BootStatus |= IAP_XMODEMC_FLAG;  //置位BootStatus的IAP_XMODEMC_FLAG标志,表示有发送字符C事件等待处理			
			BootStatus |= IAP_XMODEMD_FLAG;  //置位BootStatus的IAP_XMODEMD_FLAG标志,表示有接收数据事件等待处理
			UpDataA.XmodemNB = 0;      //接收数据包个数重置为0
			UpDataA.XmodemTimer = 0;
		}
		else if((datalen == 1)&&(data[0] == '3'))
		{
			Serial_Printf("设置版本号\r\n");
			BootStatus |= SET_VERSION_FLAG;  //置位BootStatus的SET_VERSION_FLAG标志,表示有设置OTA版本号事件等待处理
		}
		else if((datalen == 1)&&(data[0] == '4'))
		{
			Serial_Printf("查询版本号\r\n");
			AT24C02_ReadOTAInfo();    //读出EEPROM中的版本号
			Serial_Printf("版本号:%s\r\n",OTA_Info.OTA_ver);
			BootLoader_Info();          //重新打印命令菜单
		}
		else if((datalen == 1)&&(data[0] == '5'))
		{
			Serial_Printf("向外部FLASH下载程序,请输入需要使用的块编号,范围1~9\r\n");
			BootStatus |= DOWNLOAD_TOFALSH;  //置位BootStatus的DOWNLOAD_TOFALSH标志,表示有程序包要写入外部Flash的事件等待处理
		}
		else if((datalen == 1)&&(data[0] == '6'))
		{
			Serial_Printf("使用外部Flash内的程序,请输入需要使用的块编号,范围1~9\r\n");
			BootStatus |= UPLOAD_FROMFLASH;  //置位BootStatus的UPLOAD_FROMFLASH标志,表示有从外部Flash写程序入内部FLASH的事件等待处理
		}
		else if((datalen == 1)&&(data[0] == '7'))
		{
			Serial_Printf("重启\r\n");
			Delay_ms(100);
			NVIC_SystemReset();    //重启单片机
		}
	}
	else if(BootStatus & IAP_XMODEMD_FLAG)   //判断是否有接收数据事件未处理
	{
		if((datalen== 133) && (data[0] == 0x01))  //判断数据包是否合法
		{
			BootStatus &= ~IAP_XMODEMC_FLAG;   //清除发送字符C事件标志
			UpDataA.XmodemCRC = Xmodem_CRC16(&data[3], 128);  //根据接收数据计算CRC校验码
			if(UpDataA.XmodemCRC == data[131]* 256 + data[132])
			{
				Serial_Printf("\x06");        //通过CRC校验,发送ACK
				UpDataA.XmodemNB++;    //接收数据包数加1
				
				memcpy(&UpDataA.Updatabuff[(UpDataA.XmodemNB - 1)%(STM32_PAGE_SIZE/128)* 128],&data[3], 128);   //接收的有效数据拷贝至缓冲区
				if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))== 0)   //判断接收的数据是否足够1KB
				{
					MyFLASH_WriteFlash(STM32_A_SADDR + (UpDataA.XmodemNB/(STM32_PAGE_SIZE/128) - 1) * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);   //写入A区
				}
			}
			else
			{
				Serial_Printf("\x15");        //未通过CRC校验,发送NAK
			}
		}
		if((datalen == 1) && (data[0] == 0x04))  //判断接收的内容是否为EOT
		{
			Serial_Printf("\x06");        //发送ACK
			if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))!= 0)   //判断是否有剩余不足够1KB的数据未写入A区
			{
				MyFLASH_WriteFlash(STM32_A_SADDR + (UpDataA.XmodemNB/(STM32_PAGE_SIZE/128)) * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,(UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))*128);
			}
			BootStatus &= ~IAP_XMODEMD_FLAG;   //清除接收数据事件标志
			Delay_ms(100);
			NVIC_SystemReset();    //重启单片机
		}
	}
	else if(BootStatus & SET_VERSION_FLAG) //判断是否有设置OTA版本号事件未处理
	{
		if(datalen == 26)  //判断版本号长度是否合法
		{
			if(sscanf((char*)data,"VER-%d.%d.%d-%d/%d/%d-%d:%d",&temp,&temp,&temp,&temp,&temp,&temp,&temp,&temp)== 8)    //判断版本号格式是否合法
			{
				memset(OTA_Info.OTA_ver, 0, 32);
				memcpy(OTA_Info.OTA_ver, data, 26);     //将请求设置的版本号赋给OTA_ver
				AT24C02_WriteOTAInfo();                 //版本号存入EEPROM
				Serial_Printf("设置版本号成功\r\n");
				BootStatus &= ~SET_VERSION_FLAG;     //清除设置OTA版本号事件标志位
				BootLoader_Info();    //重新打印命令菜单
			}
			else
			{
				Serial_Printf("设置版本号格式错误\r\n");
			}
		}
		else
		{
			Serial_Printf("设置版本号长度错误\r\n");
		}
	}
	else if(BootStatus & DOWNLOAD_TOFALSH)   //判断是否有向外部Flash下载程序事件未处理
	{
		if((datalen== 133) && (data[0] == 0x01))    //判断数据包是否合法
		{
			BootStatus &= ~IAP_XMODEMC_FLAG;   //清除发送字符C事件标志
			UpDataA.XmodemCRC = Xmodem_CRC16(&data[3], 128);  //根据接收数据计算CRC校验码
			if(UpDataA.XmodemCRC == data[131]* 256 + data[132])
			{
				Serial_Printf("\x06");        //通过CRC校验,发送ACK
				UpDataA.XmodemNB++;    //接收数据包数加1
				
				memcpy(&UpDataA.Updatabuff[(UpDataA.XmodemNB - 1)%(STM32_PAGE_SIZE/128)* 128],&data[3], 128);   //接收的有效数据拷贝至缓冲区
				if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))== 0)   //判断接收的数据是否足够1KB
				{
					for(uint8_t i = 0;i < 4;i++)
					{
						W25Q64_PageProgram((UpDataA.XmodemNB / 8 - 1)* 1024 + i * 256 + UpDataA.W25Q64_BlockNumber * 64 * 1024,&UpDataA.Updatabuff[i * 256], 256);  //写入外部FLASH
					}
				}
			}
			else
			{
				Serial_Printf("\x15");        //未通过CRC校验,发送NAK
			}
		}
		else if((datalen == 1) && (data[0] == 0x04))  //判断接收的内容是否为EOT
		{
			Serial_Printf("\x06");        //发送ACK
			if((UpDataA.XmodemNB%(STM32_PAGE_SIZE/128))!= 0)   //判断是否有剩余不足够1KB的数据未写入外部FLASH
			{
				for(uint8_t i = 0;i < 4;i++)
				{
					W25Q64_PageProgram((UpDataA.XmodemNB / 8)* 1024 + i * 256 + UpDataA.W25Q64_BlockNumber * 64 * 1024,&UpDataA.Updatabuff[i * 256], 256);  //写入外部FLASH
				}
			}
			BootStatus &= ~DOWNLOAD_TOFALSH;   //清除向外部Flash下载程序事件标志
			OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] = UpDataA.XmodemNB * 128;  //记录程序字节数
			AT24C02_WriteOTAInfo();   //将程序字节数保存在EEPROM中
			Delay_ms(100);
			BootLoader_Info();         //重新打印命令菜单
		}
		else    //上位机发送的不是数据包,则应该是块编号
		{
			if((datalen == 1)&&(data[0]>= 0x31)&&(data[0]<= 0x39))  //判断块编号是否合法(由于取值1~9,则可根据长度判断)
			{
				UpDataA.W25Q64_BlockNumber = data[0] - 0x30;    //记录块编号
				UpDataA.XmodemTimer = 0;
				UpDataA.XmodemNB = 0;
				OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] = 0;
				W25Q64_Erase64K(UpDataA.W25Q64_BlockNumber);    //擦除对应块的原始数据
				Serial_Printf("使用Xmodem协议,向外部FLASH第%d块下载程序,请添加bin格式文件\r\n",UpDataA.W25Q64_BlockNumber);
				BootStatus |= IAP_XMODEMC_FLAG;  //触发发送字符C事件,与上位机完成握手
			}
			else
			{
				Serial_Printf("块编号不合法\r\n");
			}
		}
	}
	else if(BootStatus & UPLOAD_FROMFLASH)   //判断是否有使用外部Flash内的程序事件未处理
	{
		if((datalen == 1)&&(data[0]>= 0x31)&&(data[0]<= 0x39))  //判断块编号是否合法(由于取值1~9,则可根据长度判断)
		{
			UpDataA.W25Q64_BlockNumber = data[0] - 0x30;    //更新操作的块编号
			BootStatus |= UPDATA_A_FLAG;         //触发更新A区程序事件
			BootStatus &= ~UPLOAD_FROMFLASH;  //清除使用外部Flash内的程序事件标志
		}
	}
}

__asm void MSR_SP(uint32_t addr)
{
	MSR MSP, r0
	BX r14             //主调函数返回,相当于return语句
}

void BootLoader_Clear(void)
{
	USART_DeInit(USART1);
	GPIO_DeInit(GPIOA);
	GPIO_DeInit(GPIOB);
}

void LOAD_A(uint32_t addr)
{
	/* 先判断addr索引得到的__initial_sp是否在RAM的地址范围中,是则对SP指针和PC指针赋初始值 */
	if((*(uint32_t *)addr >= 0x20000000)&&((*(uint32_t *)addr <= 0x20004FFF)))
	{
		MSR_SP(*(uint32_t *)addr);      //对SP指针赋初始值
		load_A = (load_a)(*(uint32_t *)(addr + 4));   //取出复位中断服务函数的地址
		BootLoader_Clear();             //恢复外设默认状态
		load_A();    //直接访问复位中断服务函数,修改PC指针
	}
	else
	{
		Serial_Printf("跳转A分区失败\r\n");
	}
}

uint16_t Xmodem_CRC16(uint8_t *data, uint16_t datalen)
{
	uint8_t i;
	uint16_t Crcinit = 0x0000;     //初始值
	uint16_t Crcipoly = 0x1021;   //生成多项式
	
	while(datalen--)
	{
		Crcinit = (*data << 8) ^ Crcinit;
		for(i = 0;i < 8;i++)
		{
			if(Crcinit & 0x8000)
				Crcinit = (Crcinit << 1) ^ Crcipoly;
			else
				Crcinit = (Crcinit << 1);
		}
		data++;
	}
	return Crcinit;
}

2、Boot.h文件

#ifndef __BOOT_H
#define __BOOT_H

typedef void (*load_a)(void);   //定义函数指针

void BootLoader_Branch(void);
__asm void MSR_SP(uint32_t addr);
void LOAD_A(uint32_t addr);
void BootLoader_Clear(void);
uint8_t BootLoader_Enter(uint8_t timeout);
void BootLoader_Info(void);
void BootLoader_Event(uint8_t *data, uint16_t datalen);
uint16_t Xmodem_CRC16(uint8_t *data, uint16_t datalen);

#endif

3、main.c文件

#include "stm32f10x.h"                  // Device header
#include "Serial.h"
#include "MyDMA.h"
#include "Delay.h"
#include "MyI2C.h"
#include "W25Q64.h"
#include "AT24C02.h"
#include "MyFLASH.h"
#include "main.h"
#include "Boot.h"

OTA_InfoCB OTA_Info;
UpDataA_CB UpDataA;

uint32_t BootStatus;
uint8_t i = 0;

int main(void)
{
	/*串口模块初始化*/
	Serial_Init();
	U0Rx_PtrInit();
	MyDMA_Init();
	/*AT24C02模块初始化*/
	MyI2C_Init();
	/*W25Q64模块初始化*/
	W25Q64_Init();
	
	AT24C02_ReadOTAInfo();    //读取OTA信息相关结构体
	BootLoader_Branch();      //判断是否进行OTA更新
	
	while (1)
	{
		Delay_ms(10);
		if(U0CB.URxDataOUT != U0CB.URxDataIN)   //判断是否有接收数据未处理
		{
			BootLoader_Event(U0CB.URxDataOUT->start, U0CB.URxDataOUT->end - U0CB.URxDataOUT->start + 1);
			
			U0CB.URxDataOUT++;    //读出一组数据,OUT指针右移
			if(U0CB.URxDataOUT == U0CB.URxDataEND)
			{
				U0CB.URxDataOUT = &U0CB.URxDataPtr[0];   //OUT指针越界回滚
			}
		}
		if(BootStatus & IAP_XMODEMC_FLAG)   //判断是否有发送字符C事件未处理
		{
			if(UpDataA.XmodemTimer >= 100)  //判断计时时间是否达到1s
			{
				Serial_Printf("C");             //发送字符C
				UpDataA.XmodemTimer = 0;   //重置XmodemTimer
			}
			else
			{
				UpDataA.XmodemTimer++;         //XmodemTimer递增计时
			}
		}
		
		if(BootStatus & UPDATA_A_FLAG)  //判断是否为A区程序更新状态
		{
			Serial_Printf("程序长度%d字节\r\n",OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber]);
			
			if(OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 4 == 0)  //判断程序长度是否合法
			{
				MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);  //擦除Flash的A区
				for(i = 0;i < OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber]/STM32_PAGE_SIZE;i++)
				{
					W25Q64_ReadData(i * 1024 + UpDataA.W25Q64_BlockNumber * 64 * 1024, UpDataA.Updatabuff, STM32_PAGE_SIZE);  //读取程序片段,暂存更新程序片段结构体中
					MyFLASH_WriteFlash(STM32_A_SADDR + i * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE);  //按页写入Flash的A区空间
				}
				if(OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 1024 != 0)  //判断1024整除程序大小后是否有余数
				{
					W25Q64_ReadData(i * 1024 + UpDataA.W25Q64_BlockNumber * 64 * 1024, UpDataA.Updatabuff, OTA_Info.APPlen[UpDataA.W25Q64_BlockNumber] % 1024);  //读取最后一段,暂存更新程序片段结构体中
					MyFLASH_WriteFlash(STM32_A_SADDR + i * STM32_PAGE_SIZE, (uint32_t*)UpDataA.Updatabuff,STM32_PAGE_SIZE); //将最后一段写入Flash的A区空间
				}
				if(UpDataA.W25Q64_BlockNumber == 0)  //判断是否属于OTA更新
				{
					OTA_Info.OTA_Flag = 0;    //清除OTA Flag
					AT24C02_WriteOTAInfo();   //将OTA Flag保存在24C02中
				}
				NVIC_SystemReset();    //重启单片机
			}
			else
			{
				Serial_Printf("程序长度错误\r\n");
				BootStatus &= ~UPDATA_A_FLAG;      //清除A区程序更新状态
			}
		}
	}
}

4、main.h文件

#ifndef __MAIN_H
#define __MAIN_H

#define STM32_FLASH_SADDR   0x08000000          	//FLASH程序区起始地址
#define STM32_PAGE_SIZE     	1024               	//FLASH一个扇区的字节数
#define STM32_PAGE_NUM      	64                  //FLASH的扇区数(页数)
#define STM32_B_PAGE_NUM    	20                 	//B区所占页数
#define STM32_A_PAGE_NUM    STM32_PAGE_NUM - STM32_B_PAGE_NUM //A区所占页数
#define STM32_A_STAET_PAGE	STM32_B_PAGE_NUM	//A区第一个扇区的编号
#define STM32_A_SADDR			STM32_FLASH_SADDR + STM32_A_STAET_PAGE * STM32_PAGE_SIZE  //A区起始地址

#define UPDATA_A_FLAG			0x00000001	//A区程序更新状态
#define IAP_XMODEMC_FLAG		0x00000002	//Xmodem协议中发送字符C事件标志
#define IAP_XMODEMD_FLAG		0x00000004	//Xmodem协议中接收数据事件标志
#define SET_VERSION_FLAG		0x00000008	//OTA版本号设置事件标志
#define DOWNLOAD_TOFALSH	0x00000010	//向外部Flash下载程序事件标志
#define UPLOAD_FROMFLASH	0x00000020	//使用外部Flash内的程序事件标志

#define OTA_SET_FLAG     0xAABB1122   	//OTA Flag为该值时代表OTA Flag置位
typedef struct{
	uint32_t OTA_Flag;
	uint32_t APPlen[11];
	uint8_t OTA_ver[32];
}OTA_InfoCB;

typedef struct{
	uint8_t  Updatabuff[STM32_PAGE_SIZE];
	uint32_t W25Q64_BlockNumber;   	//本次更新程序在W25Q64中存储的块的编号
	uint32_t XmodemTimer;         	//字符C发送间隔
	uint32_t XmodemNB;             	//当前接收的数据包个数
	uint32_t XmodemCRC;            	//接收数据包的CRC校验码
}UpDataA_CB;

extern OTA_InfoCB OTA_Info;
extern UpDataA_CB UpDataA;

extern uint32_t BootStatus;

#endif

5、说明

(1)本章中的代码是为了实现逻辑而实现,使用的是事件触发和事件处理的编程思想,代码架构可能并不是最优的,在实际开发中,对每一个复杂的功能,应该建立状态机(满足特定条件才能从某种状态流转到其它特定状态,每个状态下有对应的行为),才能提高代码的可维护性。

(2)本教程中的代码仅实现了基础功能,在实际开发中,应考虑数据传输过程中的所有潜在差错,软件中应有充分的检错算法和相应的后处理措施,如中断传输、请求重传等。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐