1 BootLoader
BootLoader大家应该都听过用过或者自己设计过,进入应正式应用程序前的一个很小的程序,检测应用程序是否需要更新,当需要的时候执行擦除、写入、校验等操作,并跳转到应用程序,一般的做法都如下图所示。就这样看似简单的一个功能,大家在设计过程中有没有遇到这样的问题呢,每做一个项目或者每换一个芯片型号,BootLoader和相应的上位机都要重做一套,重复地苦力搬砖。本文针对这个问题,采用数据操作层与底层脱离的方法,设计了一个相对通用的BootLoader仅供参考,不妥之处,敬请斧正!
!
2 思路与实现
BootLoader最主要的功能就是接收数据、存储数据。数据来源、Flash的擦写是跟平台底层紧密相关的,无法通用,而具体去操作数据收发和擦写Flash的**方法**是可以通用的,这里大家有没有想到什么呢?函数指针!函数指针是可以将操作方法与具体实现脱离的一种设计,设计如下:
整体分为两部分四个文件,boo.c、boot.h是操作方法,写好后就不需要再改动,而boot\_device.c、boot\_device.h是跟平台相关的。
最终实现的效果为:移植时上位机和boo.c、boot.h不需要改动,boot\_device.c、boot\_device.h只需要实现对应平台的Flash擦写操作,以及定义好Flash存储区域即可。
!
2.1 在boot.h中定义回调函数结构体
主要包括数据接收、发送、Flash擦除、写入、读取,延时和喂狗根据项目而定,可以为空,这个结构体主要用于初始化时将函数指针与平台具体操作相关联:
typedef struct
{
//CallBack
void *(*GetData)(void); //获取数据,返回数据首地址指针
void (*SendData)(void *data, uint32_t len); //发送数据,主要回复上位机
BootStatus_t (*FlashErase)(uint32_t startAddr, uint32_t len);
BootStatus_t (*FlashWrite)(uint32_t startAddr, void *data, uint32_t len);
BootStatus_t (*FlashRead)(uint32_t startAddr, void *data, uint32_t len);
void (*SysDelay)(uint32_t num); //获取系统延时函数
void (*FeedDog)(void); //喂狗
}BootCallback_TypeDef;
//只提供两个函数供外部调用
void BootInit(BootCallback_TypeDef *boot); //关联回调函数
BootStatus_t BootStart(); //启动Boot,成功或超时后退出
2.2 在boot.c中实现每个方法的操作流程
1)实现初始化函数 BootInit(),这里考虑到如果系统有sysTick这类的时钟,那就直接调用,可以精确延时,但是有的平台没有提供,所以延时只能在自己内部计数实现,延时需要根据主频调整:
BootCallback_TypeDef *_boot; //函数操作接口
void BootDelay(uint32_t num)
{
uint32_t i = 0;
for(i = 0; i < num*1000; i++);
}
void BootInit(BootCallback_TypeDef *boot)
{
_boot = boot;
if(_boot->SysDelay == NULL)
{
_boot->SysDelay = &BootDelay;
}
}
2)初始化完成后即可调用BootStart(),进入Boot中不断检测数据(这里我的数据是一个FIFO类型),进行相应处理,完成或超时后退出:
//启动Boot
BootStatus_t BootStart(void)
{
//进入boot,完成或超时后返回
PackResult_TypeDef *res;
_bootFifo = (FIFO_TypeDef*)(_boot->GetData()); //获取数据接口,相当于把数据托管给Boot自行检测处理
while(1)
{
_boot->FeedDog(); //喂狗,没有开看门狗实际函数数为空即可
BootData_Process(_bootFifo); //查询解析数据
_boot->SysDelay(2); //适当延时
if(_nonRecivedTime > 1000) break; //boot成功后主动置nonRecivedTime为一个大于1000的数,退出
if(_nonRecivedTime++ > 500) goto boottimeout; //若nonRecivedTime 自己累加到500,则1秒钟超时
}
res = PackData(CMD_LOG, "Boot Over!!\n", 12); //打包数据发往上位机
_boot->SendData(res->Pack, res->PackLen);
return Boot_OK;
boottimeout:
res = PackData(CMD_LOG, "Boot TimeOut!!\n", 15);
_boot->SendData(res->Pack, res->PackLen);
return Boot_TimeOut;
}
3)在BootData\_Process()里面解析处理数据,具体解析方法根据数据类型自己定义:
//查询是否有数据需要处理
void BootData_Process(FIFO_TypeDef *bootFifo)
{
uint32_t count = FIFO_DataCount(bootFifo);
uint16_t packLen = 0;
uint8_t *pData;
if(count > 4) //当满足一定条件时进行解析
{
pData = FIFO_PDataOutPre(bootFifo,count); //预取数据进行判断
packLen = pData[2] + (pData[3]<<8);
if(packLen <= count) //接收完成
{
if(CheckCrc(pData,packLen)) //校验正确
{
Boot_Unpack(pData,packLen); //解析
FIFO_PDataOut(bootFifo,packLen); //取出已处理的数据
}
else
{
bootFifo->OutputIndex = bootFifo->InputIndex; //清空数据
return;
}
}
}
}
//数据包进行解析
void Boot_Unpack(void *src, uint32_t len)
{
TransPack_TypeDef *pack = (TransPack_TypeDef *)src;
_nonRecivedTime = 0;
switch(pack->Cmd)
{
case CMD_BOOT_START:
//todo:boot前准备
break;
case CMD_BOOT_EREASE:
BootEraseFlash(src, len);
break;
case CMD_BOOT_RECIVE:
BootSaveData(src, len);
break;
case CMD_BOOT_STOP:
BootStop(src, len);
break;
case CMD_BOOT_RUNAPP:
break;
default:
break;
}
}
4)对每一个命令进行具体实现
//擦除Flash,擦除大小由上位机控制
BootStatus_t BootEraseFlash(void *src, uint32_t len)
{
BootStatus_t status;
PackResult_TypeDef *res;
TransPack_TypeDef *pack = (TransPack_TypeDef *)src;
//todo: erase flash
_bootConfig.AppLen = *(uint32_t*)(&pack->Data[0]); //擦除指定大小flash区域
status = _boot->FlashErase(BOOTDATAFLASH_ADDR, _bootConfig.AppLen); //通过回调函数调用实际的擦除函数
res = PackData(CMD_BOOT_EREASE, &status,1); //数据打包
_boot->SendData(res->Pack, res->PackLen); //通过回调函数将处理结果返回上位机
return status;
}
//保存数据
BootStatus_t BootSaveData(void *src, uint32_t len)
{
BootStatus_t status = Boot_OK;
PackResult_TypeDef *res;
TransPack_TypeDef *pack = (TransPack_TypeDef *)src;
uint32_t dataLen = pack->Len - 2;
MemCyp(pack->Data, _bootDataTemp, dataLen); //非必要,防止没有字节对齐
//todo: save data
status = _boot->FlashWrite(BOOTDATAFLASH_ADDR + _flashWriteIndex, _bootDataTemp, dataLen); //通过回调写入数据
_flashWriteIndex += dataLen;
writeEnd:
_boot->SysDelay(5);
res = PackData(CMD_BOOT_RECIVE, &status,1);
_boot->SendData(res->Pack, res->PackLen);
return status;
}
//boot完成,方法同上,就不贴详细代码了
BootStatus_t BootStop(void *src, uint32_t len)
{
BootStatus_t status;
//同上操作
//校验--->拷贝
// crc = Crc16((uint8_t *)(BOOTDATAFLASH_ADDR), dataLen); //可以用这种方式直接访问Flash
//向上位机反馈结果
//若成功,置标志后退出
_nonRecivedTime = 10000; //成功,退出boot
return status;
}
至此,基本操作函数就己实现完成,下面实现回调函数。
2.3 在boot_device.h中定义平台
//选择平台
#define STM32L4_BOOT
//#define NXP_S32_BOOT
#ifdef STM32L4_BOOT
//该平台相存储区
#define APPFLASH_ADDR ((uint32_t)(0x08008000)) //32K后为APP
#define BOOTDATAFLASH_ADDR ((uint32_t)(0x08012800)) //42K后为back区
#define MAXAPPLEN ((uint32_t)(0x0000A800)) //96K
#define PAGE_SIZE 2048
#endif
//其他平台定义
#ifdef xxxxxxx
#endif
2.4 在boot_device.c中实现底层操作
下面以STM32L4为例,只需要实现该平台的底层Flash操作,移植时仅需要修改#ifdef … #endif 中的底层函数:
void *GetBootData(void);
void SendBootData(void *data, uint32_t len);
BootStatus_t Flash_Erase(uint32_t addr, uint32_t len);
BootStatus_t Flash_Write(uint32_t addr, void *data, uint32_t len);
BootStatus_t Flash_Read(uint32_t startAddr, void *data, uint32_t len);
void SysDelay(uint32_t num);
void FeedDog(void);
//函数指针关联初始化
BootCallback_TypeDef BootLoader =
{
GetBootData,
SendBootData,
Flash_Erase,
Flash_Write,
Flash_Read,
SysDelay, //获取系统延时,如果没有则为NULL,boot内部则使用计数延时
FeedDog,
};
void FeedDog(void)
{
//开启看门狗时使用,没开看门狗 该函数断为空即可
HAL_IWDG_Refresh(&hiwdg);
}
void SysDelay(uint32_t num)
{
//获取系统延时
HAL_Delay(num);
}
//让bootloader获取数据,注意返回的是指针,是把这个数据区给bootloader,而不是直接返回数据
//我使用的是FIFO数据区
void *GetBootData(void)
{
//这里我使用的USB boot,直接返回usb数据区,托管给bootloader自行查询
return &_uart1_FIFO;
}
void SendBootData(void *data, uint32_t len)
{
USBSendData(data, len); //通过usb将数据返馈给上位机
}
#ifdef STM32L4_BOOT
typedef void (*pFunction)(void);
pFunction jump2app;
void (*jump2app)();
void RunApp()
{
if (((*(__IO uint32_t*)APPFLASH_ADDR) & 0x2FFE0000 ) == 0x20000000)
{
__disable_irq();
//根据情况 DeInit相关外设
SCB->VTOR = FLASH_BASE | 0x08008000;
jump2app = (void (*)())*(__IO uint32_t*) (APPFLASH_ADDR + 4);
__set_MSP(*(__IO uint32_t*) APPFLASH_ADDR);
jump2app();
}
}
BootStatus_t Flash_Erase(uint32_t addr, uint32_t len)
{
uint32_t pageError = 0;
HAL_StatusTypeDef status;
FLASH_EraseInitTypeDef flash_erase;
HAL_FLASH_Unlock();
flash_erase.TypeErase = FLASH_TYPEERASE_PAGES; //页擦除
flash_erase.NbPages = len / PAGE_SIZE + 1; //需要擦除的页数
flash_erase.Page = (addr - 0x08000000) / PAGE_SIZE; //擦除第xx页
status = HAL_FLASHEx_Erase(&flash_erase,&pageError);
HAL_FLASH_Lock();
if(status == HAL_OK) return Boot_OK;
else return Boot_EraseFlashError;
}
BootStatus_t Flash_Write(uint32_t addr, void *data, uint32_t len)
{
uint32_t writeIndex = 0;
uint8_t *pData = data;
HAL_StatusTypeDef status;
HAL_FLASH_Unlock();
while(writeIndex < len)
{
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr + writeIndex, *(uint64_t*)(&pData[writeIndex]));//写入数据
if(status != HAL_OK) break;
writeIndex += 8;
}
HAL_FLASH_Lock();
if(status == HAL_OK) return Boot_OK;
else return Boot_WriteFlashError;
}
BootStatus_t Flash_Read(uint32_t startAddr, void *data, uint32_t len)
{
//选择性使用,可以直接操作flash读取
}
#endif
2.5 在main中调用
void Main(void)
{
//先初始化其他外设
//完成后进行boot初始化
BootInit(&BootLoader); //参数BootLoader为boot_device.c中的初始化结构体
if(BootStart() == Boot_OK) //进入boot,在boot中自行查询数据,成功或超时后返回
{
//boot成功后的操作
}
//超时后的操作
RunApp();
}
好了,现在BootLoader已经设计完成了,再做一个简单的上位机来配合就可以boot一切了!https://blog.csdn.net/bingwueyi/article/details/126404204