关键技术:函数指针

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
{//CallBackvoid *(*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 datastatus = _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; //成功,退出bootreturn 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_BOOTtypedef  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一切了!

本文主要讲方法,功能自行扩展!

手把手教你设计一个通用BootLoader相关推荐

  1. 手把手教你设计一个翻译器—实现复制即翻译(基于百度翻译API)

    手把手教你设计一个翻译器 1 获取百度翻译 API 2 先简单实现调用百度翻译并打印翻译结果 3 实现复制即翻译功能 3.1 翻译剪贴板内容并在终端输出 3.2 翻译剪贴板内容并在屏幕输出 4 设计一 ...

  2. 每日一书丨手把手教你构建一个通用的智能风控平台

    随着互联网的发展,各种与互联网业务相关的风险不断滋生,例如娱乐.游戏.社交.电商.金融等行业的营销风险.支付风险.运营风险.欺诈风险.信用风险等,不仅种类多而且迭代快,这就需要有更高效.更智能的风险控 ...

  3. 手把手教你设计一个CSDN系统

    在CSDN发一个CSDN系统是什么体验? 大家都知道CSDN 有一个下载的模块,就是用户上传资源然后管理员会进行审核,上传资源的用户可以赚钱还可以赚积分. 那么个人可不可以开发这样的系统呢? 完全可以 ...

  4. 这本读者期待的芯片书《手把手教你设计CPU——RISC-V处理器》终于出版!

    点击关注异步图书,置顶公众号 每天与你分享IT好书 技术干货 职场知识 ​ ​点击图片购书​ 参与文末话题讨论,每日赠送异步图书 --异步小编 在摩尔定律减缓的今天,一味比拼硬件性能的技术竞赛变得越发 ...

  5. 后端思维篇:手把手教你写一个并行调用模板

    前言 36个设计接口的锦囊中,也提到一个知识点:就是使用并行调用优化接口.所以接下来呢,就快马加鞭写第二篇:手把手教你写一个并行调用模板~ 一个串行调用的例子(App首页信息查询) Completio ...

  6. 我的中国“芯”,手把手教你设计CPU

    ​点击关注异步图书,置顶公众号 每天与你分享 IT好书 技术干货 职场知识​ ​ ​参与文末话题讨论,每日赠送异步图书. --异步小编 永恒的热点--CPU 灯,等灯等灯-- -- Intel 如果要 ...

  7. 手把手教你写一个Matlab App(一)

    对于传统工科的学生用的最多的编程软件应该就是matlab,其集成度高,计算能力强,容易上手,颇受大众青睐.今天挖的这个新坑,主要是分享用matlab app designer设计GUI界面的一些方法和 ...

  8. 还没理解微前端?手把手教你实现一个迷你版

    大厂技术  高级前端  Node进阶 点击上方 程序员成长指北,关注公众号 回复1,加入高级Node交流群 最近看了几个微前端框架的源码(single-spa[1].qiankun[2].micro- ...

  9. 手把手教你编写一个上位机

    关注+星标公众号,不错过精彩内容 转自 | 嵌入式大杂烩 嵌入式开发,基本都会用到有一些上位机工具,比如串口助手就是最常用的工具之一. 那么,今天分享有一篇由ZhengN整理的用Qt写的简单上位机教程 ...

最新文章

  1. 2 s锁是什么_innodb存储引擎读书笔记:锁
  2. 软件架构设计最佳实践
  3. css3实现流星坠落效果
  4. 软件工程师的6年总结
  5. GitHub 上100个优质前端项目整理,值得收藏!
  6. 分布式id-数据库实现
  7. Dockerfile制作自定义镜像
  8. 可能是国内最火的开源项目 —— C/C++ 篇
  9. 机器学习—XGboost的原理、工程实现与优缺点
  10. 微软总部首席测试专家做客中关村图书大厦“说法”
  11. 2017年网站建设公司现状分析
  12. Java基础(彩票小程序)
  13. 如何卸载AutoCAD 2019,彻底卸载MAC版CAD教程
  14. 《linux核心应用命令速查》连载五:atq:显示目前使用at命令后待执行的命令队列...
  15. 使用Ps制作飞机路过的动画
  16. Containerd shim 进程 PPID 之谜
  17. mysql执行语句出来全是问号_解决MySQL中文输出变成问号的问题
  18. 一文看懂文旅地产数字转型三大战略
  19. [生而为人-思考] Knowledge Cooking -7th 分享会记录
  20. 选择优秀IT培训机构的终极建议!

热门文章

  1. 作者:王印海(1965-),男,美国华盛顿大学(西雅图)土木和环境工程系终身教授、博士生导师。...
  2. 安全管家安卓_手机丢失后可能背负巨额债务,腾讯手机管家提醒注意手机安全防护 -...
  3. CDH大数据平台 Error while compiling statement: FAILED: SemanticException No valid privileges User tianlin
  4. 2015智能手机操作系统
  5. 智能手机技术的发展与剖析
  6. 给手机充电时,边充边玩会爆炸?夸大其词哗众取宠
  7. 周大福向北京体育大学教育基金会捐赠1.2亿元设立“郑家纯冠军培养基金”
  8. MyDLNote-360camera: ECCV 2020 结合室内全景图像的三维布局和深度预测
  9. 计算机安装xp蓝屏怎么办,谁知道电脑装XP中途蓝屏怎么处理?
  10. 菜谱点菜c语言编程,菜单点菜并计算价格 C++改成C 运行成功100分