STM32的BootLoader 从SD卡更新固件

发布者:chinalisa最新更新时间:2020-08-11 来源: 51hei关键字:STM32  BootLoader  SD卡  更新固件 手机看文章 扫描二维码
随时随地手机看文章

1. 前言自从几个月前接触到有Bootloader这回事,就有一种强烈的冲动,想写一个BootLoader出来。很快在飞思卡尔的Cortex-M4单片机上实现,已经是好几个月前的事情了。然后关于BootLoader的事搁在一边好久了,这次弄个STM32的BootLoader出来,Cortex-M3的,顺便发表下博客,跟大家分享一下。

又过了大半年了吧,慢慢对BootLoader的认识也有点长进啦。特别是跟网友讨论后发现BootLoader的实现还是需要靠BootLoader程序和App程序的配合才能正常使用。在这里特别感谢网友cary_yingj ,对本BootLoader的研究后发现App程序需要重定位中断向量表,才能正常工作。
在其他网友的反馈下,本人准备再将次文档完善,把不够详细的地方写得再详细,并且力求通俗易懂一点。希望对学习BootLoader的同学们有所帮助。


2. 初识BootLoader可能有的同学听说过BootLoader,有的同学没有听说过,这个都很正常。关于BootLoader的概念大家可以上网查一下,有比较详细的说明,我在这里说说我自己比较片面的理解,并且是针对CortexM3说明的,实现平台为STM32F103VET6。
2.1       百度百科的BootLoader这里借用一下百度百科对BootLoader的解释。在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。在一个基于ARM7TDMIcore的嵌入式系统中,系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的BootLoader程序。
2.2       BootLoader的简单理解BootLoader就是单片机启动时候运行的一段小程序,这段程序负责单片机固件更新,也就是单片机选择性的自己给自己下程序。可以更新也可以不更新,更新的话,BootLoader更新完程序后,跳转到新程序运行;不更新的话,BootLoader直接跳转到原来的程序去运行。

  需要注意的是:BootLoader下载新程序后并不擦除自己(BootLoader程序还在),下次启动依然先运行BootLoader程序,又可以选择性的更新或者不更新程序,所以BootLoader就是用来管理单片机程序的更新。

  这是本人的大概理解,大家有不明白请网上搜索一下更详细介绍吧。

2.3       BootLoader的作用BootLoader使单片机能自己给自己下载程序,所以在程序升级方面非常有作用。比如我们的BootLoader是通过GSM更新程序的,我们在升级单片机程序的时候,只要把新程序通过GSM发送给单片机,单片机自己实现程序更新,然后跳转到新程序执行,这样就省去我们很多升级的功夫啦。
可以想象一下如果把单片机安装在非常高的地方,或者危险的工业现场,或者封装得很难拆下来,我们很难直接给单片机下载程序,那么BootLoader的作用就体现出来了。简单的说,有了BootLoader,我们更新程序的话是省心又省力。
想想是不是很高级,还带点小兴奋哈哈。不用急,下面我们会继续介绍,让大家都能自己实现BootLoader。至于是通过什么方式升级,这个大家自由发挥,相信会设计出丰富多彩的BootLoader升级方式呢。


3.BootLoader预备知识我们这里是为ARM的Cortex-M3单片机写的BootLoader,需要了解一下M3内核的架构,并且要了解M3单片机是怎么启动的等等。这个方面的知识,可以参考《Cortex-M3权威指南》,这里的话我只是为了实现BootLoader简单介绍一下,大家有什么不清楚的请参考权威指南。并且这里是以STM32为例说明问题的,使用的开发环境是RVMDK(Keil)。
3.1       复位序列这里参考的是《Cortex-M3权威指南》的3.8节,复位序列。
M3单片机复位后,从0x00000000取栈指针(SP),从0x00000004取复位向量(PC),有了栈指针和复位向量后,单片机就按照正常流程运行了,在BootLoader里面,我们更新完程序后需要做的步骤之一就是设置栈指针,跳转到复位向量。
3.1.1   栈指针栈是一种数据结构,后进先出LIFO。借用百度百科的解释:栈由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。它使用的是一级缓存,他们通常都是被调用时处于存储空间中,调用完毕立即释放。
3.1.2   复位向量复位向量是一个函数地址,在CortexM3单片机里是复位函数的地址。也就是单片机启动后第一个执行的函数。
3.2       重定位中断向量表这里参考《Cortex-M3权威指南》的7.3节,向量表。
BootLoader是一个完整的程序,下载的新程序(以下称为App)也是一个完整的程序。都包含中断向量表,所以的话,我们是有两个中断向量表,相信因为有两个向量表,大家都知道我们应该需要对这两个向量表做点什么吧。
3.2.1   STM32的中断向量表我们只看前16个向量,因为其余的向量属于外设使用,与CortexM3内核无关。
__Vectors      DCD    __initial_spTop          ;Top of Stack
               DCD    Reset_Handler            ; Reset Handler
               DCD    NMI_Handler              ; NMI Handler
               DCD    HardFault_Handler        ; Hard Fault Handler
               DCD    MemManage_Handler        ; MPU Fault Handler
               DCD    BusFault_Handler         ; Bus Fault Handler
               DCD    UsageFault_Handler       ; Usage Fault Handler
               DCD    0                        ; Reserved
               DCD    0                        ; Reserved
               DCD    0                        ; Reserved
               DCD    0                        ; Reserved
               DCD    SVC_Handler              ; SVCall Handler
               DCD    DebugMon_Handler         ; Debug Monitor Handler
               DCD    0                        ; Reserved
               DCD    PendSV_Handler           ; PendSV Handler
               DCD    SysTick_Handler          ; SysTick Handler

__initial_spTop就是栈指针,Reset_Handler是复位向量。这里只显示了16个向量,CortexM3单片机的话总共有256个向量,也就是从栈指针的地址开始有1KB的区域属于中断向量表。

单片机启动默认先运行BootLoader,所以默认的中断向量表位置是BootLoader的中断向量表。为了App可以正常运行,下载完App后,我们还需要把中断向量表重新定位到App程序那里。根据《CortexM3权威指南》,介绍一下怎样重定位中断向量表。
3.2.2   设置中断向量表偏移Cortex-M3单片机有一个管理中断向量表的寄存器,叫做向量表偏移量寄存器(VTOR)(地址:0xE000_ED08)。具体可以看看截图:

STM332程序的起始地址一般在0x08000000。所以BootLoader程序是在0x08000000,不是在0x00000000是因为STM32的重映射技术(不符合Cortex-M3的设计,有点搞另类的感觉)。所以BootLoader的中断向量表在0x08000000那里。如果我们的App程序起始地址在0x08070000,并且App的中断向量表在起始地址,那么BootLoader程序下载App后,为了App程序能正确运行,开始App程序的运行后第一步,就要把中断向量表重定位到0x08070000那里。
具体实现下面会再介绍,接下来介绍分散加载文件相关内容。
3.3       分散加载文件相关这一节涉及的内容主要属于分散加载文件,大家具体上网了解,这里只是介绍了能够实现BootLoader的一小部分。
3.3.1   C语言的函数地址我们知道C语言的函数名就是函数的地址,并且STM32单片机ROM的起始地址是在0x08000000,那么使用编译器编译程序的话(这里使用的是RVMDK),函数的地址默认都在以0x08000000为首的一段ROM里面了。比如我们一个函数Delay(),它的地址可以是0x08000167(CortexM3中函数的地址0bit位一般是1),也就是Delay函数的代码在0x08000167,C语言函数调用Delay时,就是执行0x08000167的代码。
3.3.2   BootLoader占用的ROM我们需要注意的问题是,如果不修改程序默认的起始地址的话,那么BootLoader和新App程序的起始地址都是0x08000000,也就是他们重叠了(代码重叠),这样的话肯定相互之间有影响,程序是不能正常工作的。
这里的解决方法是,BootLoader程序依然占用0x08000000为首的那段ROM,因为STM32的默认就是从0x08000000运行程序的。保持BootLoader程序先能正确运行。然后App使用除BootLoader占用ROM以外的空间。这里需要知道BootLoader到底占用了多少ROM,很简单,查看MAP文件就行了。这里以我的BootLoader的MAP文件为例说明一下,看截图:
Memory Map of the image
  Image Entry point :0x08000131
  Load Region LR_IROM1 (Base: 0x08000000,Size: 0x00006da4, Max: 0x00080000, ABSOLUTE)
   Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x00006d54, Max:0x00080000, ABSOLUTE)
主要是这句话“Base:0x08000000, Size: 0x00006da4, Max: 0x00080000”,这句话说明了我的BootLoader程序是从0x08000000开始,占用了0x00006DA4大小。只要我们的App不要和BootLoader程序占用的空间冲突就可以了。我的App程序的起始地址选择为0x08070000,不与BootLoader程序冲突。具体怎么修改ROM起始地址,下面介绍。
3.3.3   修改ROM起始地址编译新程序的时候,我们要修改程序的起始地址,我的修改方法如下(开发环境是RVMDK):打开TargetOption...,切换到Target选项卡,如下

修改IROM1的起始地址和长度:
比如,为了不产生地址冲突,我将起始地址0x08000000修改成0x0807000,将ROM长度0x80000修改成0x10000。如下图所示(左图为修改前、右图为修改后):

  

注意:BootLoader程序是不需要修改的,只是App需要修改(App就是使用BootLoader下载的程序)。
3.4       hex文件和bin文件3.4.1   hex文件平时我们用j-Link或者串口下载程序的话,都是打开hex文件下载的,因为hex文件包含地址信息,下载程序的时候知道程序下载到ROM的哪个区域。从另一个角度上说,也就是hex文件是不能直接写进ROM的,一边写需要一边转换(解码出地址信息,将对应内容写入ROM)。
3.4.2   bin文件bin文件的话,很好理解,是直接的可执行代码。也就是bin文件的内容跟下载ROM里面的内容是一样的。bin文件是没有包含地址信息的,所以在下载之前要知道bin文件是要下载到ROM的那个区域。
我们的BootLoader下载的是bin文件,直接写进STM32的Flash里面,地址信息的话就是上一节的IROM,0x08070000,从0x08070000开始连续写入,中间不间断。
3.5       Bin文件生成默认情况下编译后生成的是hex文件,不过很轻松可以生成bin文件。介绍具体怎么生成bin文件,工具的话是使用fromelf.exe(目录一般是在Keil安装目录里面,本人的fromelf.exe目录是在C:KeilARMARMCCbin),我们是使用fromelf工具将axf文件转换为bin文件。
熟悉命令行的同学可能会选择直接敲命令,不过这里介绍使用RVMDK提供的用户命令(编译时可以自动生成bin,省去每次生成bin文件都要敲命令的过程)。
打开TargetOption...,切换到User选项卡,如下

主要是在运行用户命令,Run#1

具体命令是(记得在Run#1前打勾,才会在编译后执行用户命令生成bin文件):

C:KeilARMARMCCbinfromelf.exe

--bin

-o

.OutputMY_STM32.bin

.OutputMY_STM32.axf

命令可以分为五部分,简化后是fromelf --bin  -o xxx.bin  xxx.axf,需要注意的是命令的五个部分之间要有空格。还需要说明的是路径问题,这里的路径都是相对.uvproj文件的,下面是我的目录(注意MY_STM32.uvproj文件和Output文件夹)。

我的bin文件和axf文件都在Output文件夹里面,并且路径是相对MY_STM32.uvproj的,Output文件夹里的bin文件(MY_STM32.bin)相对于MY_STM32.uvproj应该写成“.OutputMY_STM32.bin”。
l      第一部分
这部分是fromelf.exe文件的路径,根据自己的安装目录而变。我这里因为Keil是安装在C盘的,所以我的路径如下所示。
参考命令:C:KeilARMARMCCbinfromelf.exe
l      第二部分
这部分是固定的,--bin表示生成bin文件。
参考命令:--bin
l      第三部分
这部分也是固定的,-o表示输出。
参考命令:-o
l      第四部分
这部分是生成文件的目录和文件名,我是输出在Output文件夹的,也就是bin文件在Output文件夹里面。
参考命令:.OutputMY_STM32.bin
l      第五部分
这部分是axf文件的目录和文件名,我们的bin文件是根据axf文件生成的,也就是说axf文件相当于输入,bin文件相当于输出。我的axf文件也在Output文件夹的。
参考命令:.OutputMY_STM32.axf
介绍了这些基本知识后,我们可以来实现BootLoader了。


4. 分几步实现BootLoader有了前面的基础知识后,应该是比较容易理解BootLoader需要怎么实现了。这一章,我们分几个步骤,一步一步实现BootLoader。
4.1       跑FAT文件系统我们的BootLoader是从SD卡更新程序的,把在电脑上编译后的App程序,也就是bin文件,复制到SD卡中,然后让单片机读取相应的bin文件,就可以实现程序的更新。需要注意的是,App程序需要修改ROM的起始地址,再编译,并且要生成bin文件才支持正常下载。
我跑的文件系统是FATFS_R0.07c,很经典的一个版本。如果大家对文件系统方面不了解的话,请自己网上查找教程,或者说很多同学对这一步应该已经很熟悉啦。
只要单片机上实现读取bin文件,结合Flash写入程序,就可以实现程序更新。下面介绍读写Flash。
4.2       读写Flash程序要实现BootLoader,还有一个前提是可以写入Flash了。如果是STM32单片机的话是很容易实现的,因为我们有官方库。本人使用的是3.0.0版本,参考官方例程,很容易实现Flash的读写,这里同样是为了实现BootLoader简单介绍一下。
4.2.1   Flash写入步骤l      解锁Flash
l      擦除Flash
l      写入Flash
l      验证读写是否正确
4.2.2   读写Flash调用的库函数l      voidFLASH_Unlock(void)                                     Flash解锁
l      FLASH_Status FLASH_ErasePage(uint32_tPage_Address)           Flash擦除
l      FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_tData)  Flash写入
4.2.3   实现Flash读写稍微封装一下STM32的官方库函数,就能实现Flash的读写,并验证读写是否正确,具体我实现的接口函数为以下截图,大家可以参考一下:

[1] [2]
关键字:STM32  BootLoader  SD卡  更新固件 引用地址:STM32的BootLoader 从SD卡更新固件

上一篇:MAX30102基于STM32F103C8T6的程序源码
下一篇:基于STM32执行的MQTT协议

推荐阅读最新更新时间:2024-11-02 06:18

STM32 通用定时器的学习
使用STM32通用定时器产生中断的步骤: 1)TIM3时钟使能 2)设置TIM3_ARR和TIM3_PSC的值 通过这两个寄存器,我们来设置自动重装的值,以及分频系数。这两个参数加上时钟频率,就决定了定时器的溢出时间。 3)设置TIM3_DIER允许更新中断 因为我们要使用TIM3的更新中断,所以设置DIRE 的UIE位,并使能触发中断 4)允许TIM3工作 光配置定时器还不行,没有开启定时器,照样不能使用。我们在配置完后要开启定时器,通过TIM3_CR1的CEN位来设置。 TIM3_CR1是控制寄存器,控制定时器的开启,还有一些工作方式。 5)TIM3中断分组设置 在定时器配置完了以后,因为要产生中断,必不可少的
[单片机]
STM32 定时器1配置
//通用定时器中断初始化 //这里时钟选择为APB1的2倍,而APB1为36M //arr:自动重装值。 //psc:时钟预分频数 //这里使用的是定时器3! void TIM1_Int_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //时钟使能 TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的
[单片机]
STM32定时器及中断简单程序
//----------------------------main()-------------------- //stm32f103c8t6有3个普通1个高级定时器 //每次进入中断服务程序间隔时间为 //((1+TIM_Prescaler )/72M)*(1+TIM_Period )=((1+7199)/72M)*(1+9999)=1秒 #include #define D13_ON GPIO_ResetBits(GPIOC,GPIO_Pin_13) #define D13_OFF GPIO_SetBits(GPIOC,GPIO_Pin_13) void GPIO_Config(void); void TIM2_Co
[单片机]
STM32如何分配原理图IO
在画原理图之前,一般的做法是先把引脚分类好,然后才开始画原理图。 要想根据功能来分配 IO,那就得先知道每个 IO 的功能说明,这个我们可以从官方的数据手册里面找到。在学习的时候,有两个官方资料我们会经常用到,一个是参考手册(英文叫 Referencemanual),另外一个是数据手册(英文叫 Data Sheet)。两者的具体区别见下表。 数据手册主要用于芯片选型和设计原理图时参考,参考手册主要用于在编程的时候查阅。在数据手册中,有关引脚定义的部分在 Pinouts and pin description 这个小节中。数据手册中对引脚定义具体定义见下表。 对上表中引脚定义的解读,见下图。 举例,如果MCU 型号是 S
[单片机]
<font color='red'>STM32</font>如何分配原理图IO
STM32 之 EXTI
按键的硬件结构有一点一定要注意,要在GPIO段上拉电阻,否则GPIO设置成浮空输入后,会造成端口电平不稳定,中断效果不理想。 另外EXTI的映射关系可以看下图,是和管脚号对应的,比较好记 (1)Main C语言: Codee#14817 /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 实验平台 : ST 官方三合一套件 + 硬件 : STM32F103C8T6 + 开发平台 : IAR For ARM 5.40 + 仿真器 : J-Link + 日期 : 2010-11-4 + 频率
[单片机]
<font color='red'>STM32</font> 之 EXTI
FPGA和STM32的区别是什么 stm32与fpga的优缺点分析
FPGA基本原理和内部结构 一、FPGA原理 FPGA中的基本逻辑单元是CLB模块,一个CLB模块一般包含若干个基本的查找表、寄存器和多路选择器资源,因此FPGA中的逻辑表达式基于LUT的。 FPGA内部的编程信息一般存储在SRAM单元中,因此通常的FPGA都是基于SRAM的,所以掉电后信息会丢失,下次上电需要先配置才能使用。 着重介绍Xilinx FPGA, 二、FPGA产品的速度等级 速度等级一般反映一款芯片的性能,速度等级越高,说明芯片内的逻辑延时和布线延时越小,设计的性能要求也越容易达到,随之付出的成本也越大。 对Xilinx FPGA,速度等级一般有“-1”、“-2”、“-3”等,数字越大,速度等级越高,芯片价钱
[单片机]
FPGA和<font color='red'>STM32</font>的区别是什么 <font color='red'>stm32</font>与fpga的优缺点分析
自己写一个最简单的bootloader_jz2440
写在前面: 我的博客已迁移至自建服务器:博客传送门,CSDN博客暂时停止,如有机器学习方面的兴趣,欢迎来看一看。 此外目前我在gitHub上准备一些李航的《统计学习方法》的实现算法,目标将书内算法全部手打实现,欢迎参观并打星。GitHib传送门 正文 boot是为了启动内核,本质上也就是一个裸板程序,就是为了引导内核的启动。所以打算自己写一个boot,功能只有引导内核启动。 首先是汇编的代码段,是为了关闭看门狗,设置时钟以及代码的重定位,这些都是在main函数之前执行的。之前学习单片机的时候,我们只看到main函数,实际上是main之前的执行步骤都被包起来了。 整个汇编文件的开头要写上 .text @这是为了表
[单片机]
STM32局部变量的数组最大能到多少?
当在一个函数中声明一个较大的局部数组变量时,程序可以编译通过,但运行时,老是进入hardfaulthandler,导致出现改问题的原因可能是栈溢出。因为局部变量是存放在栈区的,而全局变量在全局区(静态区),如果栈区较小,会产生溢出。 解决这种问题的方法: 方法1:启动文件里面对栈的大小有固定的值。函数里面的数组是在调用该函数的时候,才给你分配空间。将启动文件下的堆栈改大 Stack_Size EQU 0x00000400 。 Stack Size,一般小工程0X400足够,大工程才设置0X1000就够用,所以默认无需设置太大。 方法2:用静态数组代替普通局部数组。 方法3:数组过大,栈溢出了,有可能覆盖了其他全局变量的值
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved