笔者今天简单谈谈BootLoader的理解,本文基于CortexM3/M4来讲讲。
1、BootLoader与APP
- BootLoader、APP(用户应用)都是一个程序,其功能不同。
- BootLoader 功能就是更新用户程序,用户程序就是实现功能的程序(比如记录温度等数据),BootLoader主要功能就是程序引导,在程序引导前可能会做一些硬件初始化,做好了之后再进入用户程序,这样的话 用户程序就直接运行就OK。
- 常见的BootLoader 比如在linux当中用的Uboot(Universal BootLoader),Windows当中的BIOS程序,都是引导程序
- Bootloader当前可能还有些其他功能,比如加密这种等等。
- BootLoader更新用户程序,BootLoader可以通过Nand、eMMC、U盘和SD卡进行程序更新, 比如windows 从U盘驱动,然后更新C盘的系统,从而执行程序的更新。
- BootLoader的更新方式也有很多种,通过USB/CAN/串口/SPI/I2C/TCPIP网络通信的方式这种去更新。
2、BootLoader升级的文件格式
上位机通过与BootLoader交互把程序写到固定位置,程序进入BootLoader之后,直接跳到对应的地址就可以执行。
(IAP 在线升级)程序的download 文件格式一般 有两种方式,一种bin文件格式,一种hex文件格式。
2.1 bin文件格式升级
bin文件没什么格式,完全就是二进制数据,对,就是数据,比如Code 段、ro data段 、rw data段,BootLoader接收到数据之后,写到约定的地址就可以。
**注意:**如果有多个段地址 比如data的地址和Code的地址有区别 Code在0x0800 0000 Data在0x2000 0000,那么生成的bin 文件只是写到0x0800 0000 data的数据地址 不正确,程序如何运行起来的呢?这就是要靠分散加载来实现。
将Code段搬到对应的地址执行,BSS段初始化为0,RW初始化为相应的值。
详情:分散加载启动
2.2 hex文件格式升级
bin升级的一个不好的地方就是要指定烧录地址,即加载地址,因为bin 都是raw data,没有任何信息,而hex文件则不一样 其带地址信息,同时有校验信息,更加准确。
hex文件烧录的时候 上位机需要解析hex文件 提取出数据 然后再download 进行。hex文件格式参考。
3、BootLoader程序的跳转与执行
- BootLoader可以跳转内部SRAM、外部SRAM或者DDR上面去执行程序
- BootLoader可以跳转 到 Nor Flash、QSPI Flash以及支持并口的Nor Flash运行程序APP
- 高阶的玩法直接动态加载APP,APP程序运行与处理器无关,全部采用系统API,比如windows上直接运行exe程序。
- BootLoader程序的跳转也很方便,直接将PC指针指向APP程序地址即可(嵌入式程序一般是中断向量表地址)
- app 地址首地址一般是栈的地址(SP),(SP+4)是属于复位向量的地址,可以去执行,最终跳到app的main函数。
- app 需要 设置中断向量表的地址,同时链接脚本指定code的存放位置。
以stm32的为示例:
(1)设置指定code的链接地址(0x08000000+ 8000 (offset))。
LR_IROM1 0x08008000 0x00080000 { ; load region size_region
ER_IROM1 0x08008000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 NOCOMPRESS 0x00010000 { ; RW data
.ANY (+RW +ZI)
}
}
(2)设置中断向量表的偏移地址
#define VECT_TAB_OFFSET 8000
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
(3)bootloader跳转程序(到app)如下:
u32 ApplicationAddress = 0x08008000; //app 地址
typedef void (*pFunction)(void); //函数指针
void Jump_Used_Main(void)
{
printf("\r\nboot2-------- Jump APP --------- \r\n");
/* 判断栈顶 是否位于 0x20000000 128K */
if ( ((*(__IO uint32_t*)ApplicationAddress) >= 0x20000000) &&((*(__IO uint32_t*)ApplicationAddress) <= 0x20010000))
{
JumpAddress = *(__IO uint32_t*) (ApplicationAddress +4);
Jump_To_Application = (pFunction) JumpAddress;
/*close the interrupt*/
_disable_interrupr();
__set_MSP(*(__IO uint32_t*) ApplicationAddress);
Jump_To_Application();
}
else
{
while(1)
{
printf("\r\nboot2 error\r\n");
}
}
}
4、CortexM4的异常向量表
CortexM4 程序一般从0x0地址开始运行,取出堆栈地址即栈顶,然后进入reset handler 函数,进行初始化,进行分散加载,及程序开始执行的地址,最后进入main函数。
中断向量表的地址可以进行偏移,所以APP程序的中断向量表可以通过偏移来实现中断地址的获取,中断Code 段的链接地址,同时也需要偏移,这是通过链接脚本指定。
总结下来就两点
- 中断向量表偏移
- Code段的地址偏移,通过链接脚本放在指定位置(对于APP来说)
5、CortexM4的内存映射布局
Cortex M4的的memory 布局如上图,对于32位地址来说,各区域的地址都有对应的布局,不可以随便放。
- 0~0x1FFF FFFF 主要是放Code ,
- 0 ~ 0x 000F FFFF 可能是Flash 或者 系统存储空间(系统Boot 代自带码,适用于串口烧录) 或者SRAM空间(方便从SRAM启动代码)
- 0x0800 0000 ~0x 080F FFFF Flash空间 Max是1M,正好与0 ~ 0x 000F FFFF地址对应,所以STM32的起始地址从0x08000000 做了地址重映射。
- 0x1000 0000 ~ 0x 1000 FFFF CCM RAM空间,(方可以放DATA数据)
- 0x2000 0000 ~ 0x3FFF FFFF 是SRAM空间 (128K)
- 0x2000 0000 ~ 0x2001 BFFF 112K RAM空间
- 0x2000 C000 ~ 0x2001 FFFF 16K RAM空间
- 0x4000 0000 ~ 0x5FFF FFF 外设地址空间
- ADC UART GPIO 等外设接口
6、扇区的理解
7、代码的实现
BootLoader与APP的关系
比如实现的过程中
- 先烧录bootloader bin,烧录地址指定(0x800 0000),编译链接时链接脚本指定Code和DATA位置 (同样是0x800 0000)
- 再烧录APP bin,烧录地址指定(0x800 0000 + offset)编译链接时链接脚本指定Code和DATA位置(同样是0x800 0000 + offset)
- 程序执行在bootloader得时候,先判断APP是否存在,然后在跳转,不然直接跳转,程序直接飞掉了
- APP程序复位得时候,会跳到bootloader,这是因为程序从0地址开始执行
- 当然可以将APP 和 bootloader 的代码打包在一起,然后一起烧录到固件当中,当然这种是通过烧录工具去烧录
- 在bootloader的时候,可以APP的Code 可以被更新,之后APP上电时,上电的时分散加载会把数据段加载到SRAM中
当然也可以有多个APP程序(只要Flash空间大)
具体代码实现:
bootloader工程
APP工程
8、附录
本文简单介绍一下NXP LPC54XX系列的启动方式。
(1)存储空间分布图
-
0x0000 0000 to 0x1FFF FFFF:
- SRAMX:位于指令和数据总线上面(I-Code D-Code),可以执行CortexM4 code,可以用于堆栈区域。192KB。
- BootROM:内部的Flash区域,存储厂商的boot code。64KB。
- SPIFI:SPIFI 内存映射区域,可以直接通过总线访问,一般是外部的Flash区域。128MB。
-
0x2000 0000 to 0x3FFF FFFF:
- Main SRAM:主内存区域,用于存放data数据,比如rw+bss段
- SRAM bit band:SRAM位带区,
-
0x4000 0000 to 0x7FFF FFFF:
- 外设地址区域(包括外设位带区)
位带区解释:
Cortex™-M4 存储器映像包括两个位带(bit-band)区,一个是SRAM区,另一个是片上外设区。由于不能直接对一个位进行操作,为了实现对寄存器进行快速的位操作,设计了两个别名区。两个位带区将别名区中的每个字映射到位带区的一个位,在别名区写入一个字具有对位带区的目标位执行读-改-写操作的相同效果。
- 外设地址区域(包括外设位带区)
-
0x8000 0000 to 0xDFFF FFFF:片外的memory,通过外部内存控制器控制(EMC),支持RAM、ROM以及Flash,还包括SDRAM。
-
0xE000 0000 to 0xE00F FFFF:Cortex-M4私有的外设总线
(2)启动方式
- EMC boot 模式,例如外部并行flash启动
- ISP 模式,利用Boot rom的code,通过usb/uart/iic/spi 的通信方式下载到sram,从而启动。
- flash模式,通过uart/iic/spi 或者spifi,将外部flash的code搬到sram运行。
注意:
1、nxp的LPC64系列,内存flash只够存放boot rom的code,所以需要外部flash来存放code,不然一掉电数据就丢了,一般是到sramx 运行,sram0-3用来存放数据。
2、sramx用来执行code,总共就192k的区域,如果code空间超过这个,那么就放不下code,需要另谋他路了。
3、如果从spi flash启动执行,sramx 以及 sram0-3用来存放code,那么这样空间就足够了,就是需要从外部flash启动,并读取code。
-
boot rom从外部flash般code 的时候,也会做相关的校验,比如header以及crc。
-
首先会从外部flash般512byte,核对Image marker,在中断向量表的0x24位置,该位置恰好保留,可以用户自定义,0xEDDC94BD,特定字符。然后0x28 指向了一段特定数据区域的地址,存放nxp自己的校验数据等,比如CRC这些。
-
特定数据区域同样有header,
- Header Value:0xFEED5A5A
- Image Type:0 :CRC Check,1:No CRC Check。
- Load address:sramx启动:0x0 sram0-3:0x20000000,外部SPI Flash启动:0x10000000 (SPIFI)并行Flash启动:0x80000000
- Image length:Image Length-4
- CRC:crc check value。
(3)中断向量表
-
经过上面解释就知道了,为什么中断向量表中有这么多奇怪的数据,是NXP厂商自定义的,本身CortexM4架构中就保留了这些字段。
-
用户外设的中断向量表(非异常向量表)的个数也不一定都是0-255,个数也是厂商定义的。
__attribute__ ((used, section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
// Core Level - CM4
&_vStackTop, // The initial stack pointer
ResetISR, // The reset handler
NMI_Handler, // The NMI handler
HardFault_Handler, // The hard fault handler
MemManage_Handler, // The MPU fault handler
BusFault_Handler, // The bus fault handler
UsageFault_Handler, // The usage fault handler
__valid_user_code_checksum, // LPC MCU checksum
0, // ECRP
(void (*)(void))0xEDDC94BD, // Reserved
(void (*)(void))0x160, // Reserved
SVC_Handler, // SVCall handler
DebugMon_Handler, // Debug monitor handler
0, // Reserved
PendSV_Handler, // The PendSV handler
SysTick_Handler, // The SysTick handler
// Chip Level - LPC54018
WDT_BOD_IRQHandler, // 16: Windowed watchdog timer, Brownout detect
DMA0_IRQHandler, // 17: DMA controller
GINT0_IRQHandler, // 18: GPIO group 0
GINT1_IRQHandler, // 19: GPIO group 1
PIN_INT0_IRQHandler, // 20: Pin interrupt 0 or pattern match engine slice 0
PIN_INT1_IRQHandler, // 21: Pin interrupt 1or pattern match engine slice 1
PIN_INT2_IRQHandler, // 22: Pin interrupt 2 or pattern match engine slice 2
PIN_INT3_IRQHandler, // 23: Pin interrupt 3 or pattern match engine slice 3
UTICK0_IRQHandler, // 24: Micro-tick Timer
MRT0_IRQHandler, // 25: Multi-rate timer
CTIMER0_IRQHandler, // 26: Standard counter/timer CTIMER0
CTIMER1_IRQHandler, // 27: Standard counter/timer CTIMER1
SCT0_IRQHandler, // 28: SCTimer/PWM
CTIMER3_IRQHandler, // 29: Standard counter/timer CTIMER3
FLEXCOMM0_IRQHandler, // 30: Flexcomm Interface 0 (USART, SPI, I2C, FLEXCOMM)
FLEXCOMM1_IRQHandler, // 31: Flexcomm Interface 1 (USART, SPI, I2C, FLEXCOMM)
FLEXCOMM2_IRQHandler, // 32: Flexcomm Interface 2 (USART, SPI, I2C, FLEXCOMM)
FLEXCOMM3_IRQHandler, // 33: Flexcomm Interface 3 (USART, SPI, I2C, FLEXCOMM)
FLEXCOMM4_IRQHandler, // 34: Flexcomm Interface 4 (USART, SPI, I2C, FLEXCOMM)
FLEXCOMM5_IRQHandler, // 35: Flexcomm Interface 5 (USART, SPI, I2C,, FLEXCOMM)
FLEXCOMM6_IRQHandler, // 36: Flexcomm Interface 6 (USART, SPI, I2C, I2S,, FLEXCOMM)
FLEXCOMM7_IRQHandler, // 37: Flexcomm Interface 7 (USART, SPI, I2C, I2S,, FLEXCOMM)
ADC0_SEQA_IRQHandler, // 38: ADC0 sequence A completion.
ADC0_SEQB_IRQHandler, // 39: ADC0 sequence B completion.
ADC0_THCMP_IRQHandler, // 40: ADC0 threshold compare and error.
DMIC0_IRQHandler, // 41: Digital microphone and DMIC subsystem
HWVAD0_IRQHandler, // 42: Hardware Voice Activity Detector
USB0_NEEDCLK_IRQHandler, // 43: USB Activity Wake-up Interrupt
USB0_IRQHandler, // 44: USB device
RTC_IRQHandler, // 45: RTC alarm and wake-up interrupts
FLEXCOMM10_IRQHandler, // 46: Flexcomm Interface 10 (SPI, FLEXCOMM)
Reserved47_IRQHandler, // 47: Reserved interrupt
PIN_INT4_IRQHandler, // 48: Pin interrupt 4 or pattern match engine slice 4 int
PIN_INT5_IRQHandler, // 49: Pin interrupt 5 or pattern match engine slice 5 int
PIN_INT6_IRQHandler, // 50: Pin interrupt 6 or pattern match engine slice 6 int
PIN_INT7_IRQHandler, // 51: Pin interrupt 7 or pattern match engine slice 7 int
CTIMER2_IRQHandler, // 52: Standard counter/timer CTIMER2
CTIMER4_IRQHandler, // 53: Standard counter/timer CTIMER4
RIT_IRQHandler, // 54: Repetitive Interrupt Timer
SPIFI0_IRQHandler, // 55: SPI flash interface
FLEXCOMM8_IRQHandler, // 56: Flexcomm Interface 8 (USART, SPI, I2C, FLEXCOMM)
FLEXCOMM9_IRQHandler, // 57: Flexcomm Interface 9 (USART, SPI, I2C, FLEXCOMM)
SDIO_IRQHandler, // 58: SD/MMC
CAN0_IRQ0_IRQHandler, // 59: CAN0 interrupt0
CAN0_IRQ1_IRQHandler, // 60: CAN0 interrupt1
CAN1_IRQ0_IRQHandler, // 61: CAN1 interrupt0
CAN1_IRQ1_IRQHandler, // 62: CAN1 interrupt1
USB1_IRQHandler, // 63: USB1 interrupt
USB1_NEEDCLK_IRQHandler, // 64: USB1 activity
ETHERNET_IRQHandler, // 65: Ethernet
ETHERNET_PMT_IRQHandler, // 66: Ethernet power management interrupt
ETHERNET_MACLP_IRQHandler, // 67: Ethernet MAC interrupt
Reserved68_IRQHandler, // 68: Reserved interrupt
LCD_IRQHandler, // 69: LCD interrupt
SHA_IRQHandler, // 70: SHA interrupt
SMARTCARD0_IRQHandler, // 71: Smart card 0 interrupt
SMARTCARD1_IRQHandler, // 72: Smart card 1 interrupt
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
0, // Reserved
(void (*)(void))0xFEEDA5A5, // Header Marker
#if defined (ADD_CRC)
(__imghdr_imagetype - 1), // (0x04) Image Type
__imghdr_loadaddress, // (0x08) Load_address
#else
__imghdr_imagetype, // (0x04) Image Type
__imghdr_loadaddress, // (0x08) Load_address
#endif
(void (*)(void))(((unsigned)_image_size) - 4), // (0x0C) load_length, exclude 4 bytes CRC field.
0, // (0x10) CRC value (only applicable to NON Non-secure images).
0, // (0x14) Version (only applicable to DUAL_ENH image type.
0, // (0x18) EMC static memory configuration settings, required for EMC boot
(void (*)(void))IMG_BAUDRATE, // (0x1C) image baudrate
0, // (0x20) reserved
(void (*)(void))0xEDDC94BD, // (0x24) Image_marker
0, // (0x28) SBZ
0, // (0x2C) reserved
#ifdef W25Q128JVFM
/* SPIFI Descriptor - W25Q128JVFM */
(void (*)(void))0x00000000, // 0xFFFFFFFF to default 1-bit SPI mode ;DevStrAdr
(void (*)(void))0x001870EF, // mfgId + extCount
(void (*)(void))0x00000000, // extid 0-3
(void (*)(void))0x00000000, // extid 4-7
(void (*)(void))0x0001001D, // caps
(void (*)(void))0x00000100, // Blks + RESV1
(void (*)(void))0x00010000, // blkSize
(void (*)(void))0x00000000, // subBlks + subBlkSize
(void (*)(void))0x00000100, // pageSize + RESV2
(void (*)(void))0x00003F00, // maxReadSize
(void (*)(void))0x68506850, // maxClkRate,maxReadRate,maxHSReadRate,maxProgramRate
(void (*)(void))0x04030050, // maxHSProgramRate,initDeInitFxId,clearStatusFxId,getStatusFxId,
(void (*)(void))0x14110D09, // setStatusFxId,setOptionsFxId,getReadCmdFxId,getWriteCmdFxId
#endif
#ifdef MXL12835F
/* SPI Descriptor - MXL12835F */
(void (*)(void))0x00000000, // 0xFFFFFFFF to default 1-bit SPI mode ;DevStrAdr
(void (*)(void))0x001820C2, // mfgId + extCount
(void (*)(void))0x00000000, // extid 0-3
(void (*)(void))0x00000000, // extid 4-7
(void (*)(void))0x0001001D, // caps
(void (*)(void))0x00000100, // Blks + RESV1
(void (*)(void))0x00010000, // blkSize
(void (*)(void))0x00000000, // subBlks + subBlkSize
(void (*)(void))0x00000100, // pageSize + RESV2
(void (*)(void))0x00003F00, // maxReadSize
(void (*)(void))0x68506850, // maxClkRate,maxReadRate,maxHSReadRate,maxProgramRate
(void (*)(void))0x06030050, // maxHSProgramRate,initDeInitFxId,clearStatusFxId,getStatusFxId
(void (*)(void))0x14110F0B, // setStatusFxId,setOptionsFxId,getReadCmdFxId,getWriteCmdFxId
#endif
}; /* End of g_pfnVectors */
(4)链接脚本
gcc 链接脚本语法,待完善