打印
[资源共享]

通用BootLoader

[复制链接]
95|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
cashrwood|  楼主 | 2025-3-20 22:33 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

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

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

19

主题

1377

帖子

0

粉丝