STM32启动方式分析

嵌入式,boot,STM32

Posted by elmagnifico on March 20, 2017

STM32F767启动方式分析

一般嵌入式开发流程就是先建立一个工程,再编写源文件,然后进行编译,把所有的.s文件和.c文件编译成一个.o文件,再对目标文件进行链接和定位,编译成功后会生成一个.hex文件和调试文件,接下来要进行调试,如果成功的话,就可以将它固化到flash里面去。

启动代码是用来初始化电路以及用来为高级语言写的软件作好运行前准备的一小段汇编语言,是任何处理器上电复位时的程序运行入口点。

比如,刚上电的过程中,会将系统运行频率锁定在一个固定的值,这个锁定频率的过程就是在启动代码中进行的。设置完后,程序开始运行,程序都是运行在内存中的,那么这里就需要把一些源代码段从flash里面copy到内存中,对他们进行初始化读写,可能还有频率的设置。

初始化完成后,我们又要设置一些堆栈,要跳到C语言的main函数里面运行。这就需要堆栈,对普通的ARM CPU有这样一个要求:在绝对地址为零的地方要放置一个异常向量表,但并不是所有的ARM CPU都留有这个一个空间,这就需要用到映射的功能。我们可以将其它地方的一些空间映射到绝对地址里面。当发生异常时,ARM核来读取异常中断表的时候,它会使用映射之后的那个表,这样就可以接着往下执行,否则在绝对地址零的地方找不到任何信息,程序就会死掉。这些运行的环境全部建立好后,程序就会跳转到我们的main函数里面。

总之,启动代码,就是对最小系统的初始化。包括晶振,CPU频率,时钟等。

环境

编译环境:keil 5.23

固件库:Keil.STM32F7xx_DFP.2.9.0

开发板:STM32F767

Cortex M3/4/7的启动方式不同

以前的Cortex M3内核,有三种启动方式:

  • 通过boot引脚设置可以将中断向量表定位于SRAM区,即起始地址为0x2000000,同时复位后PC指针位于0x2000000处
  • 通过boot引脚设置可以将中断向量表定位于FLASH区,即起始地址为0x8000000,同时复位后PC指针位于0x8000000处
  • 通过boot引脚设置可以将中断向量表定位于内置Bootloader区,

Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。Cortex-M3内核是固定了中断向量表的位置而起始地址是可变化的.

但是这种启动方式有一个问题,多数情况下boot引脚其实已经固定死了,切换boot模式是有硬件成本的,而且如果想要有自己的更新程序就势必要多加一个boot程序,然后生成流程上多一个烧写第一个bootloader的过程。

M4和M3基本一样,但是到了M7的时候,这里就不同了,M7的boot引脚只有一个了,那么这里boot也就变成只有2种模式了?并不是。

从哪里启动,不再是光boot引脚就能决定的了,需要参考对应的BOOT_ADD0和BOOT_ADD1来决定

BOOT 启动地址选项字节 启动地址
0 BOOT_ADD0[15:0] 由用户选项字节BOOT_ADD0[15:0]决定启动地址,ST 出厂默认的启动地址为:0X0020 0000 的 ITCM 上的 Flash
1 BOOT_ADD1[15:0] 由用户选项字节BOOT_ADD1[15:0]决定启动地址,ST 出厂默认的启动地址为:0X0010 0000 的 System bootloader

boot引脚的值在复位以后系统时钟的第四个上升沿锁存。

BOOT_ADD0和BOOT_ADD1允许程序从0x0000 0000到0x3FFF FFFF启动,包括下面这些地方

  • 映射到ITCM or AXIM 接口的FLASH地址空间
  • 映射到AXIM 接口的ITCM, DTCM RAMs and SRAMs的地址空间
  • The System memory bootloader

比如: BOOT_ADD1 = 0x0000: Boot from ITCM RAM(0x0000 0000) BOOT_ADD1 = 0x0040: Boot from system memory bootloader (0x0010 0000) BOOT_ADD1 = 0x0080: Boot from Flash on ITCM interface (0x0020 0000) BOOT_ADD1 = 0x2000: Boot from Flash on AXIM interface (0x0800 0000) BOOT_ADD1 = 0x8000: Boot from DTCM RAM (0x2000 0000) BOOT_ADD1 = 0x8004: Boot from SRAM1 (0x2001 0000) BOOT_ADD1 = 0x8013: Boot from SRAM2 (0x2004 C000)

正点原子的pdf中关于boot的说明是错误的,他看的是75x系列的, 767和77x系列略有不同,也就是说目前如果想要切boot,要么程序里写切换boot地址,要么就是通过预先置入两套bootloader程序,然后通过切引脚来选择从哪里启动。

也就是说,只要你配置好了 boot address, ST 的硬件会自动修改向量表的偏移地址寄存器 VTOR,使之与 boot address 相匹配。不需要再通过软件修改 VTOR 寄存器。

所以对于 STM32F7 来说,默认状态下,复位后它并不是从 0 地址从开始执行,而是从 0x0020 0000 或者 0x0010 0000 开始执行,所以与 ITCM-RAM 从 0 地址处开始并不冲突。如果你非要将向量表放在 0 地址开始的位置(修改 VTOR 寄存器),也不是不可以。如果你还要在 ITCM-RAM 里面跑别的程序的话,就要注意向量表不要与其他程序的地址重叠了。

其实因为上面的boot有一个默认地址,我们完全可以利用片子自带的system boot来作为我们自己的boot的一部分来用。

比如空白片子,boot接高电平,这个时候系统从system boot启动,等待ISP烧写,那么第一次烧写结束以后,程序会运行一次,那么这个运行第一步就是修改BOOT_ADD1让他reset以后从用户区我们的代码开始执行。

这个时候虽然再用ISP烧写,虽然写不进去了,但是我们可以预先在程序里设置一个标志位或者中断什么的,然后触发之后,让程序修改BOOT_ADD1,让他再次从system boot启动,进而可以烧写,这样将system boot 作为了我们自己的boot的一部分,既可以帮我们更新固件,也可以正常跑业务,我个人感觉还是蛮好的。

内部启动程序-system boot

内部启动程序代码位于系统存储器中, 在芯片生产期间由 ST 编程。它用于通过以下串行接 口重新编程 Flash:

  • 引脚 PA9/PA10 上的 USART1 以及引脚 PB10/PB11 和 PC10/PC11 上的 USART3。
  • 引脚 PB5/PB13 上的 CAN2。
  • USB OTG FS(引脚 PA11/PA12 上)从设备模式(DFU:器件固件升级)。
  • 引脚 PB6/9 上的 I2C1、引脚 PF0/PF1 上的 I2C2 和引脚 PA8/PC9 上的 I2C3。

这里就是 system boot 了,我们只要接了这些引脚配合BOOT_ADD1的位置就可以进行ISP烧写。

ISP与IAP

再谈谈stm32的ISP和IAP区别和联系。

ISP(In-System Programming)在系统可编程,指电路板上的空白器件可以编程写入最终用户代码, 而不需要从电路板上取下器件,已经编程的器件也可以用ISP方式擦除或再编程。

IAP(In-Application Programming)指MCU可以在系统中获取新代码并对自己重新编程,即可用程序来改变程序。

ISP和IAP的工作原理

ISP的实现相对要简单一些,一般通用做法是内部的存储器可以由上位机的软件通过串口来进行改写。对于单片机来讲可以通过SPI或其它的串行接口接收上位机传来的数据并写入存储器中。所以即使我们将芯片焊接在电路板上,只要留出和上位机接口的这个串口,就可以实现芯片内部存储器的改写,而无须再取下芯片。

IAP的实现相对要复杂一些,在实现IAP功能时, 单片机内部一定要有两块存储区,一般一块被称为BOOT区,另外一块被称为存储区。单片机上电运行在BOOT区,如果有外部改写程序的条件满足,则对存储区的程序进行改写操作。如果外部改写程序的条件不满足,程序指针跳到存储区,开始执行放在存储区的程序,这样便实现了IAP功能。

ISP和IAP的优点
  • ISP技术的优势是不需要编程器就可以进行单片机的实验和开发,单片机芯片可以直接焊接到电路板上,调试结束即成成品,免去了调试时由于频繁地插入取出芯片对芯片和电路板带来的不便。

  • IAP技术是从结构上将Flash存储器映射为两个存储体,当运行一个存储体上的用户程序时,可对另一个存储体重新编程,之后将程序从一个存储体转向另一个。

  • ISP的实现一般需要很少的外部电路辅助实现, 而IAP的实现更加灵活,通常可利用单片机的串行口接到计算机的RS232口,通过专门设计的固件程序来编程内部存储器,可以通过现有的INTERNET或其它通讯方式很方便地实现远程升级和维护。

IAP的编写流程
设计思路

由Bootloader负责检测是否存在新的bin文件,或者说是否存在新的版本,如果存在新版本,那么就开始更新固件,否则跳转到固件的开始部位执行,当然如果有多块单片机联合使用的话,自然可以通过某一块的bootloader来更新其他单片机的固件。

STM32内部FLASH的起始地址为0X08000000,Bootloader程序文件就从此地址开始写入,存放APP程序的首地址设置在紧跟Bootloader之后。当程序开始执行时,首先运行的是Bootloader程序,此时Bootloader检测是否接收到了新程序,是否需要更新其关联的单片机的固件。

固件更新结束后还需要跳转到APP程序开始执行新的程序,完成这最后这一步要了解Cortex-M7的启动过程以及启动文件,之前有过分析:

程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,当复位中断程序运行完成后才跳转到main函数。由此可见,在最后一步的设计中需要根据存放APP程序的起始地址以及中断向量表来设置栈顶地址,并获取复位中断地址跳转到复位中断程序。

Bootloader程序设计
  1. 确定存放APP程序的首地址
  2. Bootloader检测是否有固件更新
  3. 复制新固件内容到指定地址(其实这里就是对片上FLASH的读写,有例程可以参考,但是读写速度其实相当的慢)
  4. 跳转到新程序运行,更新完程序后就需要跳转到新程序开始运行,关键代码如下:
typedef void (*iapfun)(void);
//定义一个函数类型的参数,其实就是一个跳转函数
iapfun jump2app;
__asm void MSR_MSP(u32 addr)
//设置堆栈指针
{
      MSR MSP, r0
      BX r14
}
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
      if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)
	  //检查栈顶地址是否合法.
      {
			//其实进入这里需要关一下外设中断,防止出现意外情况。
            jump2app = (iapfun)*(vu32*)(appxaddr+4);
			//第二个字为reset_handler函数,从这里开始执行程序
            MSR_MSP(*(vu32*)appxaddr);
			//第一个字为初始化堆栈指针
            jump2app();
			//跳转到APP
      }
}
ISP

其实如果能有一套网络/无线设备,而且没有过多的其他串联设备在其中的情况下,完全可以通过ISP无线下载,同时对多个板子进行下载,这里就不需要人写的bootloader了,硬件自身的就足够了。

总结

目前的STM32F767大致启动方式就是这样,可以自定义多一些,不像之前的M3/4那么死板。

Quote

http://blog.csdn.net/norains/article/details/6052029

http://www.worlduc.com/blog2012.aspx?bid=7329962

http://blog.chinaunix.net/uid-20734916-id-4007594.html