OTA升级中Bootloader的写法,stm32f405实现,B区跳转到A区,B区实现A区的代码更新

OTA实现第一步

​ 分区:Bootloader区域,和App区域,明确用什么方式来存储获取的新数据。我用的是SD卡来存储获取的数据,这里面,不同的用户可以实现不同的存储方式,比如你可以把FLASH分为三个区域,B区和两个A区,一个A1区存运行的代码,一个A2区域存获取的Bin文件代码。你也可以用不同的存储芯片来存储你的数据24C02,W25Q64等等。

​ 在这里,你要明确一个事情:单片机的Flash的擦除:单片机擦除某一个flash是按照块来擦除的,具体的flash分区可以去找自己用的单片机的型号对应的数据手册,在Flash章节会明确告诉你。我用的是STM32F405,如下所示

在这里插入图片描述

​ 这里F405把flash主存分为了12块,你可以定义你的B区和A区的范围,在这里,我定义B区从0x08000000------0x08040000,后续的为A区,对于我获取的新的更新的Bin文件,我存到SD卡中,后续进行更新写入的操作。

OTA升级的注意事项

OTA升级中,很多人弄不清到底是什么样的流程,什么时候该更新,什么时候应该跳转,OTA标志位怎么置位。

​ 无OTA事件的时候:系统上电—从掉电不丢失的内存中读取OTA_Flag— false表示无OTA事件发生—跳转到A区正常执行代码。

​ 出现OTA事件!:系统上电—从掉电不丢失的内存中读取OTA_Flag— true表示OTA事件发生—从掉电不丢失的内存中读取获取来的Bin文件—写入Flash----把OTA_Flag更改成false(这一点很重要)。

​ 获取Bin文件到内存中:后面细说!(要明确什么时候更改OTA_Flag)

B区实现

Bootloader实现原理分析

​ 这里我把0x08000000----0x08040000定义为Bootloader区,这个区域很关键,他决定了单片机能不能正常工作,因为上电最开始进入的就是Bootloader区,这个区的逻辑写不好,很容易导致单片机变成一块砖头。

​ 这里要读取一个掉电不丢失的标志位:OTA_Flag,我用的是SD卡实现

​ 这里要写跳转到A区 否则 更新的逻辑

​ 这里要设置MSP,用于跳转前的栈初始化

​ 这里要关闭中断,清理NVIC防止跳转的时候发生中断触发崩溃

跳转A区实现

这里先定义A区的入口地址:

#define STM32F4_A_SADDR			0x08040000

跳转到A区的代码用一个函数实现,传入参数为A区的首地址:

/*跳转A分支正常运行代码*/
LOAD_A(STM32F4_A_SADDR);

LOAD_A()实现

定义一个函数指针,用于在Bootloader跳转到用户程序时调用用户程序的入口地址

void (*load_a)(void)表示load_a是一个 指向函数的指针

声明一个 函数指针变量,名为 load_A,类型是 load_a

typedef	void (*load_a)(void);//在
load_a	load_A;
为什么要函数指针?

STM32 的启动流程

​ 上电后,CPU从FLASH的起始地址读取初始栈指针(MSP)

​ 紧接着4个字节存储Reset Handler 地址(即程序入口)。

Bootloader 跳转时

​ 需要手动设置MSP和PC(程序计数器)。

​ 通过函数指针 load_A,可以直接调用用户程序的入口,实现无缝跳转。

上LOAD_A代码!
void LOAD_A(uint32_t addr)
{
//	if (((*(__IO uint32_t*)addr) & 0x2FFE0000 ) == 0x20000000)
//	{
	 	__disable_irq(); // 关闭总中断
	    __set_PRIMASK(1);

	    SysTick->CTRL = 0X00;//禁止SysTick
	    SysTick->LOAD = 0;
	    SysTick->VAL = 0;

	    __set_PRIMASK(0);

	    __HAL_FLASH_PREFETCH_BUFFER_DISABLE();// 关闭预取缓冲区
	    __HAL_FLASH_INSTRUCTION_CACHE_DISABLE();// 关闭指令缓存
	    __HAL_FLASH_DATA_CACHE_DISABLE();// 关闭数据缓存
	    __HAL_FLASH_INSTRUCTION_CACHE_RESET();// 复位指令缓存
	    __HAL_FLASH_DATA_CACHE_RESET();// 复位数据缓存

		load_A = (load_a)*(uint32_t*)(addr + 4);// 获取用户程序的 Reset Handler
		MSR_MSP(*(uint32_t*)addr);// 设置用户程序的栈指针(MSP)

		BootLoader_Clear();
		load_A();

//	}

}

这里注释的部分是用来判断合法性的,其实一般都合法。。。

这里面还有要注意的就是:MSR_MSP函数的写法,用汇编语言的方式写,这里有可能编译不通过,可以查一下自己的编译器的标准写法。

ARM Cortex-M 中**r0 是第一个函数参数**(即 addr)。

跳转到 r14(即 LR,Link Register) 存储的地址,r14LR)存储的是 函数的返回地址

void MSR_MSP(uint32_t addr)
{
__ASM volatile("MSR MSP, r0");
__ASM volatile("BX r14");
}

再提供一下Bootloader_Clear()函数:

void BootLoader_Clear(void)
{
	HAL_DeInit();

	__set_CONTROL(0);

	for(int i = 0; i < 8; i++)
	{
		NVIC->ICER[i] = 0xFFFFFFFF;	/* 关闭中断*/
		NVIC->ICPR[i] = 0xFFFFFFFF;	/* 清除中断标志位 */
	}
}

至此就完成了A区的跳转代码的实现,那么想一下,这个LOAD_A函数写在哪里?我们之前是不是要先判断一下什么时候才能够跳转?

OTA事件触发,写FLASH

先展示整个OTA的B区实现的函数吧:

这里面就是判断OTA_flag这个标志位的状态,如果是1,就说明要更新A区的代码了,否则就跳转A区正常运行程序:

void BootLoader_Branch(void)
{

	HAL_Delay(100);
	if(OTA_Info.OTA_flag == 1)
	{
		/*可以进行OTA升级,读取SD中的信息写入flash过程*/
		 Erase_flash(FLASH_SECTOR_6,4);//从第6个开始到后4个,把A区擦了
		 WriteBinToFlash();

		 retSD = f_open(&file, OTA_FLG, FA_CREATE_ALWAYS | FA_WRITE);			//OTA升级标志位清零
		 retSD = f_write(&file, OTA_FLG_CLEAR, 1, (void *)&byteswritten);
		 retSD = f_close(&file);

			__set_FAULTMASK(1);
			NVIC_SystemReset();

	}
	else
	{
		/*跳转A分支正常运行代码*/
		LOAD_A(STM32F4_A_SADDR);
	}
}

这里面看到,程序进来后判断如果有OTA事件产生,那我就这样一个流程:

擦除Flash区域------写Bin文件到A区------OTA标志位清零-------Reset软复位

这里哪怕是在写Bin文件到A区这个过程中掉电了,其实我重新上电后检查到OTA标志位为1,我还是会写入Bin文件到A区,所以OTA标志位的清除时刻很重要,一定要擦除完写入完毕后再置位OTA_flag。

擦除写入函数的实现

擦除Flash区域的函数实现:

​ 定义从哪个区域开始擦sector_start,擦后面几个区域sector_end;具体代码解释下面看注释

uint8_t Erase_flash(uint32_t sector_start,uint32_t sector_end)	//3-4
{
	FLASH_EraseInitTypeDef EraseInitStruct;
	uint32_t SECTORError;
	uint32_t i;
	HAL_FLASH_Unlock(); //解锁内部flash
	//FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);

	EraseInitStruct.TypeErase     = FLASH_TYPEERASE_SECTORS; //以扇区进行擦除
	EraseInitStruct.VoltageRange  = FLASH_VOLTAGE_RANGE_3;
	EraseInitStruct.Sector        = sector_start;  //擦除扇区的首  如果要擦除第五扇区,此处就填入5
	EraseInitStruct.NbSectors     = sector_end; //擦除扇区的尾,如果擦除第五扇区,就填入1

	if (HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError) != HAL_OK)
	{
		return -1;
	}
	//address=add;

	HAL_FLASH_Lock();
	return 1;
}

写入Flash的函数也比较简单明了

这里我使用SD卡来存放的Bin文件,并且存储的Bin文件的文件名称为

char bin_t[]="OTA.bin";

这里面我在main.h函数中定义了结构体,每一块我是读取1024个字节的数据,然后写入flash中

typedef struct{
	uint8_t Update_Buff[1024];
	uint16_t UpdateCRC16_res;

}Update_InfoCB;

这里每次读取1024个字节的数据,存在Update_Info.Update_Buff中,然后调用自己写的WriteToFlash函数写入Flash内存,注意这里的address在每次写入后要+=4,不要写错了。

Update_InfoCB Update_Info;

void WriteBinToFlash()
{
	uint32_t FlashAddress = STM32F4_A_SADDR;
	retSD = f_open(&file, bin_t, FA_READ);//读取bin文件
	HAL_FLASH_Unlock(); //解锁内部flash
	if (retSD != FR_OK)	//
	{
		Error_Handler();
	}
    // 按块读取文件并写入Flash
    while (1) {
        retSD = f_read(&file, Update_Info.Update_Buff, sizeof(Update_Info.Update_Buff), &bytesRead);
        if (retSD != FR_OK || bytesRead == 0) {
            break; // 读取完成或出现错误,跳出循环
        }
        WriteToFlash(Update_Info.Update_Buff, bytesRead,FlashAddress);
        FlashAddress += bytesRead;
    }
	HAL_FLASH_Lock();
	f_close(&file);
}

void WriteToFlash(uint8_t *data, uint32_t size, uint32_t address)
{
    for (uint32_t i = 0; i < size; i += 4) {
        uint32_t word = *(uint32_t *)(data + i);
        if (HAL_FLASH_Program(TYPEPROGRAM_WORD, address, word) != HAL_OK) {
            Error_Handler();
        }
        address += 4;
    }
}

总结

​ 至此这里的B区的逻辑就写完了,实现A区的跳转和A区的更新,注意这里的OTA_Flag的标志位的清除和置位,这里有一些细节不要写错了,自己写Bootloader区的时候,要确定好B区的内存区域,确定好B区向A区跳转时的代码逻辑,跳转时一定注意把中断关掉,清除中断标志位。

​ 预告:相对A区 来说,B区的逻辑还是很好写的,因为这都默认Bin文件已经写入到我的存储空间中了,下一篇我会出A区的逻辑,数据传输方式为4g模块,用阿里云物联网平台来实现,数据传输协议用MQTT来实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值