bootloader是TI串口转以太网代码的一小部分,位于Flash开始的4KB空间内。它的一个重要作用是在应用远程升级,可以通过串口、USB、IIC、以太网等通道进行远程固件升级。bootloader是CPU启动后最先执行的程序,它会把自己拷贝到SRAM,并判断是否有固件升级,如果有升级请求,则执行升级程序;反之,执行用户程序。 一.流程图
由于这里只考虑基于以太网的bootloader,其流程图如图2-1所示:
图2-1 二.配置文件
由于bootlaoder可以使用串口、USB、IIC、以太网等通道进行远程固件升级,那么怎么样配置才可以使用以太网呢?这就牵扯到bl_config文件。此文件是专门配置bootloader的。代码就不贴了,看一下这里面几个必须配置的选项:
1. 以下至少且只能定义一个,用于指明使用何种方式升级。
- CAN_ENABLE_UPDATE,
- ENET_ENABLE_UPDATE,
- I2C_ENABLE_UPDATE,
- SSI_ENABLE_UPDATE,
- UART_ENABLE_UPDATE,
- USB_ENABLE_UPDATE
复制代码2. 以下必须定义- APP_START_ADDRESS 用户程序启动地址
- VTABLE_START_ADDRESS 用户程序向量表起始地址
- FLASH_PAGE_SIZE Flash页大小,TI的目前为止都为1K
- STACK_SIZE 堆栈大小
复制代码3. 当选择了以太网升级后,以下必须定义- CRYSTAL_FREQ 目标板晶振频率
复制代码- ;******************************************************************************
- ;
- ; The NMI handler.
- ;
- ;******************************************************************************
- NmiSR
- if :def:_ENABLE_MOSCFAIL_HANDLER
- ;
- ; Grab the fault frame from the stack (the stack will be cleared by the
- ; processor initialization that follows).
- ;
- ldm sp, {r4-r11}
- mov r12, lr
- ;
- ; Initialize the processor.
- ;
- bl ProcessorInit
- ;
- ; Branch to the SRAM copy of the NMI handler.
- ;
- ldr pc, =NmiSR_In_SRAM
- else
- ;
- ; Loop forever since there is nothing that we can do about a NMI.
- ;
- b .
- endif
- ;******************************************************************************
- ;
- ; The hard fault handler.
- ;
- ;******************************************************************************
- FaultISR
- ;
- ; Loop forever since there is nothing that we can do about a hard fault.
- ;
- b .
- ;******************************************************************************
- ;
- ; The update handler, which gets called when the application would like to
- ; start an update.
- ; 升级服务函数,当应用程序想要开始升级时,调用这个函数.
- ;
- ;******************************************************************************
- UpdateHandler
- ;
- ; Initialize the processor. 初始化处理器
- ;
- bl ProcessorInit ;调用子程序
- ;
- ; Branch to the SRAM copy of the update handler.
- ;
- ldr pc, =UpdateHandler_In_SRAM
- ;******************************************************************************
- ;
- ; This portion of the file goes into the text section.
- ;
- ;******************************************************************************
- align 4
- area ||.text||, code, readonly, align=2
- Reset_Handler_In_SRAM
- ;
- ; Call the user-supplied low level hardware initialization function
- ; if provided.
- ; 如果用户提供了底层硬件初始化函数,则调用这个函数
- ;
- if :def:_BL_HW_INIT_FN_HOOK
- import $_BL_HW_INIT_FN_HOOK
- bl $_BL_HW_INIT_FN_HOOK
- endif
- ;
- ; See if an update should be performed.
- ; 检查是否有升级请求
- ;
- import CheckForceUpdate
- bl CheckForceUpdate
- cbz r0, CallApplication ;结果为零则转移(只能跳到下一行)
- ;
- ; Configure the microcontroller.
- ;
- EnterBootLoader
- if :def:_ENET_ENABLE_UPDATE
- import ConfigureEnet
- bl ConfigureEnet
- elif :def:_CAN_ENABLE_UPDATE
- import ConfigureCAN
- bl ConfigureCAN
- elif :def:_USB_ENABLE_UPDATE
- import ConfigureUSB
- bl ConfigureUSB
- else
- import ConfigureDevice
- bl ConfigureDevice
- endif
- ;
- ; Call the user-supplied initialization function if provided.
- ; 如果用户提供了初始化函数,则调用.
- ;
- if :def:_BL_INIT_FN_HOOK
- import $_BL_INIT_FN_HOOK
- bl $_BL_INIT_FN_HOOK
- endif
- ;
- ; Branch to the update handler.
- ; 进入升级处理程序
- ;
- if :def:_ENET_ENABLE_UPDATE
- import UpdateBOOTP
- b UpdateBOOTP
- elif :def:_CAN_ENABLE_UPDATE
- import UpdaterCAN
- b UpdaterCAN
- elif :def:_USB_ENABLE_UPDATE
- import UpdaterUSB
- b UpdaterUSB
- else
- import Updater
- b Updater
- endif
- ;
- ; This is a second symbol to allow starting the application from the boot
- ; loader the linker may not like the perceived jump.
- ;
- export StartApplication
- StartApplication
- ;
- ; Call the application via the reset handler in its vector table. Load the
- ; address of the application vector table.
- ;
- CallApplication
- ;
- ; Copy the application's vector table to the target address if necessary.
- ; Note that incorrect boot loader configuration could cause this to
- ; corrupt the code! Setting VTABLE_START_ADDRESS to 0x20000000 (the start
- ; of SRAM) is safe since this will use the same memory that the boot loader
- ; already uses for its vector table. Great care will have to be taken if
- ; other addresses are to be used.
- ; 如果必要的话,复制应用程序的向量表到目标地址.
- ; 请注意,不正确的boot loader配置会破坏整个程序!设置VTABLE_START_ADDRESS为
- ; 0x2000 0000(从SRAM启动)也是可以的,因为这将和boot loader使用同样的内存
- ;
- if (_APP_START_ADDRESS != _VTABLE_START_ADDRESS) ;看应用程序的起始地址是否和应用程序的向量表存储地址相同
- movw r0, #(_VTABLE_START_ADDRESS & 0xffff)
- if (_VTABLE_START_ADDRESS > 0xffff)
- movt r0, #(_VTABLE_START_ADDRESS >> 16)
- endif
- movw r1, #(_APP_START_ADDRESS & 0xffff)
- if (_APP_START_ADDRESS > 0xffff)
- movt r1, #(_APP_START_ADDRESS >> 16)
- endif
- ;
- ; Calculate the end address of the vector table assuming that it has the
- ; maximum possible number of vectors. We don't know how many the app has
- ; populated so this is the safest approach though it may copy some non
- ; vector data if the app table is smaller than the maximum.
- ; 计算向量表的结束地址,假设向量表有最大数目. 我们不知道应用程序使用了多少
- ; 向量表,但这样是最安全的
- ;
- movw r2, #(70 * 4)
- adds r2, r2, r0
- VectorCopyLoop
- ldr r3, [r1], #4
- str r3, [r0], #4
- cmp r0, r2
- blt VectorCopyLoop
- endif
- ;
- ; Set the vector table address to the beginning of the application.
- ; 将向量表重定位到应用程序开始处
- ;
- movw r0, #(_VTABLE_START_ADDRESS & 0xffff)
- if (_VTABLE_START_ADDRESS > 0xffff)
- movt r0, #(_VTABLE_START_ADDRESS >> 16)
- endif
- movw r1, #(NVIC_VTABLE & 0xffff) ;向量表偏移寄存器
- movt r1, #(NVIC_VTABLE >> 16)
- str r0, [r1]
- ;
- ; Load the stack pointer from the application's vector table.
- ; 从应用程序向量表装载用户堆栈.
- ;
- if (_APP_START_ADDRESS != _VTABLE_START_ADDRESS)
- movw r0, #(_APP_START_ADDRESS & 0xffff)
- if (_APP_START_ADDRESS > 0xffff)
- movt r0, #(_APP_START_ADDRESS >> 16)
- endif
- endif
- ldr sp, [r0]
- ;
- ; Load the initial PC from the application's vector table and branch to
- ; the application's entry point.
- ;
- ldr r0, [r0, #4]
- bx r0
- ;******************************************************************************
- ;
- ; The update handler, which gets called when the application would like to
- ; start an update.
- ; 升级处理函数,当用户程序想要开始升级时,调用此函数
- ;
- ;******************************************************************************
- UpdateHandler_In_SRAM
- ;
- ; Load the stack pointer from the vector table.
- ; 从boot loader向量表中装载堆栈指针
- ;
- if :def:_FLASH_PATCH_COMPATIBLE
- movs r0, #0x1000
- else
- movs r0, #0x0000
- endif
- ldr sp, [r0]
- ;
- ; Call the user-supplied low level hardware initialization function
- ; if provided.
- ; 调用用户提供的底层硬件初始化函数
- ;
- if :def:_BL_HW_INIT_FN_HOOK
- bl $_BL_HW_INIT_FN_HOOK
- endif
- ;
- ; Call the user-supplied re-initialization function if provided.
- ; 调用用户提供的初始化函数
- ;
- if :def:_BL_REINIT_FN_HOOK
- import $_BL_REINIT_FN_HOOK
- bl $_BL_REINIT_FN_HOOK
- endif
- ;
- ; Branch to the update handler.
- ; 进入升级例程
- ;
- if :def:_ENET_ENABLE_UPDATE
- b UpdateBOOTP ;在bl_enet.c中
- elif :def:_CAN_ENABLE_UPDATE
- import AppUpdaterCAN
- b AppUpdaterCAN
- elif :def:_USB_ENABLE_UPDATE
- import AppUpdaterUSB
- b AppUpdaterUSB
- else
- b Updater
- endif
- ;******************************************************************************
- ;
- ; The NMI handler.
- ; NMI异常服务例程,处理主振荡器失败
- ;
- ;******************************************************************************
- if :def:_ENABLE_MOSCFAIL_HANDLER
- NmiSR_In_SRAM
- ;
- ; Restore the stack frame.
- ;
- mov lr, r12
- stm sp, {r4-r11}
- ;
- ; Save the link register.
- ;
- mov r9, lr
- ;
- ; Call the user-supplied low level hardware initialization function
- ; if provided.
- ;
- if :def:_BL_HW_INIT_FN_HOOK
- bl _BL_HW_INIT_FN_HOOK
- endif
- ;
- ; See if an update should be performed.
- ;
- bl CheckForceUpdate
- cbz r0, EnterApplication
- ;
- ; Clear the MOSCFAIL bit in RESC.
- ;
- movw r0, #(SYSCTL_RESC & 0xffff)
- movt r0, #(SYSCTL_RESC >> 16)
- ldr r1, [r0]
- bic r1, r1, #SYSCTL_RESC_MOSCFAIL
- str r1, [r0]
- ;
- ; Fix up the PC on the stack so that the boot pin check is bypassed
- ; (since it has already been performed).
- ;
- ldr r0, =EnterBootLoader
- bic r0, #0x00000001
- str r0, [sp, #0x18]
- ;
- ; Return from the NMI handler. This will then start execution of the
- ; boot loader.
- ;
- bx r9
- ;
- ; Restore the link register.
- ;
- EnterApplication
- mov lr, r9
- ;
- ; Copy the application's vector table to the target address if necessary.
- ; Note that incorrect boot loader configuration could cause this to
- ; corrupt the code! Setting VTABLE_START_ADDRESS to 0x20000000 (the start
- ; of SRAM) is safe since this will use the same memory that the boot loader
- ; already uses for its vector table. Great care will have to be taken if
- ; other addresses are to be used.
- ;
- if (_APP_START_ADDRESS != _VTABLE_START_ADDRESS)
- movw r0, #(_VTABLE_START_ADDRESS & 0xffff)
- if (_VTABLE_START_ADDRESS > 0xffff)
- movt r0, #(_VTABLE_START_ADDRESS >> 16)
- endif
- movw r1, #(_APP_START_ADDRESS & 0xffff)
- if (_APP_START_ADDRESS > 0xffff)
- movt r1, #(_APP_START_ADDRESS >> 16)
- endif
- ;
- ; Calculate the end address of the vector table assuming that it has the
- ; maximum possible number of vectors. We don't know how many the app has
- ; populated so this is the safest approach though it may copy some non
- ; vector data if the app table is smaller than the maximum.
- ;
- movw r2, #(70 * 4)
- adds r2, r2, r0
- VectorCopyLoop2
- ldr r3, [r1], #4
- str r3, [r0], #4
- cmp r0, r2
- blt VectorCopyLoop2
- endif
- ;
- ; Set the application's vector table start address. Typically this is the
- ; application start address but in some cases an application may relocate
- ; this so we can't assume that these two addresses are equal.
- ;
- movw r0, #(_VTABLE_START_ADDRESS & 0xffff)
- if (_VTABLE_START_ADDRESS > 0xffff)
- movt r0, #(_VTABLE_START_ADDRESS >> 16)
- endif
- movw r1, #(NVIC_VTABLE & 0xffff)
- movt r1, #(NVIC_VTABLE >> 16)
- str r0, [r1]
- ;
- ; Remove the NMI stack frame from the boot loader's stack.
- ;
- ldmia sp, {r4-r11}
- ;
- ; Get the application's stack pointer.
- ;
- if (_APP_START_ADDRESS != _VTABLE_START_ADDRESS)
- movw r0, #(_APP_START_ADDRESS & 0xffff)
- if (_APP_START_ADDRESS > 0xffff)
- movt r0, #(_APP_START_ADDRESS >> 16)
- endif
- endif
- ldr sp, [r0, #0x00]
- ;
- ; Fix up the NMI stack frame's return address to be the reset handler of
- ; the application.
- ;
- ldr r10, [r0, #0x04]
- bic r10, #0x00000001
- ;
- ; Store the NMI stack frame onto the application's stack.
- ;
- stmdb sp!, {r4-r11}
- ;
- ; Branch to the application's NMI handler.
- ;
- ldr r0, [r0, #0x08]
- bx r0
- endif
- ;******************************************************************************
- ;
- ; The default interrupt handler.
- ;
- ;******************************************************************************
- IntDefaultHandler
- ;
- ; Loop forever since there is nothing that we can do about an unexpected
- ; interrupt.
- ;
- b .
- ;******************************************************************************
- ;
- ; Provides a small delay. The loop below takes 3 cycles/loop.
- ; 提供一个小的延时函数. 循环一次需要3个时钟周期.
- ;
- ;******************************************************************************
- export Delay
- Delay
- subs r0, #1
- bne Delay
- bx lr
- ;******************************************************************************
- ;
- ; This is the end of the file.
- ;
- ;******************************************************************************
- align 4
- end
复制代码- include bl_config.inc
复制代码包含bl_config.inc,这个文件是什么,从哪里来,有什么作用?再看bootloader工程Options---User---Run User Programs Before Build/Rebuild内的用户命令(见图2-2)又是什么?
图2-2 所有的一切,要从keil MDK的汇编器说起,在启动代码中要用到配置文件bl_config.h中定义的一些配置选项,但因为MDK汇编器不能通过C预处理器运行汇编代码,所以bl_config.h中的相关内容需要 转化为汇编格式并包含到MDK的启动代码中。这需要手动运行C预编译器进行格式转化。图2-2中红 {MOD}部分圈出的内容正是为了完成这个转换。在点击Build/Rebuild编译按钮之后,会先运行图2-2指定的命令,再进行编译。先来分析一下这条命令: armcc --device DLM -o bl_config.inc -E bl_config.c 这条命令的作用是将bl_config.c(包含bl_config.h文件)进行而且仅进行预编译处理,并生成bl_config.inc文件。 armcc是Keil MDK提供的C编译工具,语法为: armcc [Options] file1 file2 ... file n 介绍一下这里用到的Options选项: --device<dev>:设置目标的设备类型,DLM为Luminary的设备标识。 -I<directory> :目录列表 -E :仅执行预处理 -o<file> :指定输出文件的名字2. 看一下目标板上电后启动代码的运行流程 上电后程序先到Flash地址0x00处装载堆栈地址,这跟以前接触过的处理器不同,以前0x00处都是放置的复位处理代码,但Cortex M3内核却不是,0x00处是放置的堆栈地址,而不是跳转指令。 堆栈设置完成后,跳转到Reset处理程序处,调用处理器初始化函数ProcessorInit,该函数将bootloader从Flash拷贝到SRAM,将.bss区用零填充并将向量表重映射到SRAM开始处。 之后跳转到Reset_Handler_In_SRAM函数,在该函数中,如果用户提供了底层硬件初始化函数(在bl_config.h中使能),则调用这个函数。然后调用CheckForceUpdate函数,检查是否有升级请求。如果没有升级请求,跳转到CallApplication函数,在该函数中,将向量表重映射到应用程序开始处(这里为地址0x1000),装载用户程序堆栈地址,跳转到用户程序的Reset服务函数。 如果调用CheckForceUpdate函数检测到有升级请求,则配置以太网,跳转到升级程序UpdateBOOTP处执行。3. 如何在用户程序中调用升级程序 用户程序存在于Flash地址0x1000处,bootloader存放于Flash地址0x00处,并且用户程序在执行的时候已经将向量表重映射到了Flash地址0x1000处了,那么应用程序是如何调用位于bootloader中的升级程序呢? 再看bootloader启动代码的中断向量表,在Flash地址的0x2C中存放的是CPU SVC异常服务跳转地址: dcd UpdateHandler ; Offset 2C: SVCall handler 而bootloader正是用这个异常来处理升级请求的。那么,应用程序只要执行该地址处的跳转指令,就能进行一次程序升级,在应用程序中的swupdate.c中,使用了如下C代码来执行位于Flash地址0x2C内的跳转程序: (*((void (*)(void))(*(unsigned long *)0x2c)))(); 对C语言还没有入门的同学可能会比较的头痛,这像谜一样的语句是如何执行位于bootloader的SVC异常服务例程呢?还是分解一下吧: (*(unsigned long *)0x2c):将0x2C强制转化为unsigned long类型指针,并指向该地址所在的数据; void (*)(void) :函数指针,指针名为空,该函数参数为空,返回值为空 (void (*)(void))(*(unsigned long *)0x2c):将Flash地址0x2C中的内容强制转化为函数指针,该函数参数为空,返回值为空 (*((void (*)(void))(*(unsigned long *)0x2c)))();:调用函数,即开始从启动代码中的UpdateHandler标号处开始执行。
一周热门 更多>