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) 存储的地址,r14(LR)存储的是 函数的返回地址。
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来实现。



170

被折叠的 条评论
为什么被折叠?



