M3芯片从Norflash启动流程

 

这几天看了一下M3芯片从Norflash启动流程,记录一下。

1. 上电的第一条指令

        CPU刚上电,需要从某一地址(下面以0x0为例说明)读取指令,但是内存肯定不行(由于刚上电,内存是没有数据的)。对于norflash(有地址总线),可以直接将norflash map到地址0x0位置,这样就可以从Norflash直接启动。相比于内存的读速度,norflash 还是太慢了,并且无法直接写。 通常情况,比较靠前的几条指令就会将flash中的代码段和数据段搬到memory中,然后跳转到memory中去执行。

2. 以boot loader为例

        bootloader 是一段引导程序,上电开始,先将boot loader加载到0x0,bootloader首先将自己搬运到memory,然后将真正的应用(且称为APP吧)搬到memory,搬运完成之后,再跳转到APP位置去执行。所以必须要求bootloader 在memory中的空间不能和APP有overlap,否则在搬运过程自己把自己给写乱了。通常情况相对于APP来说,bootloader代码都比较少,并且在APP启动之后,bootloader就没有必要存在于内存中(可以被APP给擦写),于是可以设计如下内存模型, 以512K 内存为例,高64K 运行bootloader,低448k用于运行APP。

 由于运行APP时候,不再需要bootloader,可以将APP的栈位置和bootloader的栈位置都设为0x80000(栈是从内存顶部往下分配的)

问题1. 如何将bootloader 的起始位置设置为0x70000,APP的起始位设置位0?

在bootloader链接脚本中指定:

在链接脚本中指定,在连接时候,将起始地址设置位0x70000

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x10000000, LENGTH = 8M
  RAM   (rwx) : ORIGIN = 0x00070000, LENGTH = 64K
}

ENTRY(Reset_Handler)

SECTIONS
{
        .text :
        {
                KEEP(*(.vectors))
                __Vectors_End = .;
                __Vectors_Size = __Vectors_End - __Vectors;
                __end__ = .;
......

    }

......

}

告诉链接器,生成的可执行文件基地址是0x70000,内存长度是64K, 第一条执行指令位置在:Reset_Handler。.text起始地址没有再特别指定,默认是从RAM ORIGIN :0x70000开始,其实就是告诉链接器代码段起始地址是0x70000. 

而APP的链接脚本:

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x10000000, LENGTH = 8M
  RAM   (rwx) : ORIGIN = 0x00000000, LENGTH = 512K
}

ENTRY(Reset_Handler)

SECTIONS
{
        .text :
        {
                KEEP(*(.vectors))
                __Vectors_End = .;
                __Vectors_Size = __Vectors_End - __Vectors;
                __end__ = .;
......

    }

......

}

告诉链接器,生成的可执行文件基地址是0x0,内存长度是512K, 第一条执行指令位置在:Reset_Handler(可以和bootloader用同一个函数,但是执行逻辑肯定不一样,可以用宏隔开,也可以是不同的启动汇编)。.告诉链接器代码段起始地址是0x0.

问题2. 刚刚不是说系统一启动,就从0x0位置开始执行,那现在bootloader的代码还是flash中,怎么办?

使用map方法,刚上电,将整个norflash地址空间(0~8M)map到0~0x800000,将RAM地址空间(0~512K) map到0x10000000 ~ 0x100080000,等bootloader起来之后,把自己搬运到0x10000000,然后再重新map(通过设置指定地址的值,而改变总线地址空间),重新map之后,RAM地址空间(0~512K) 被map到0~0x80000, Flash地址被map到0x10000000~0x10800000。

 

问题3. 之前bootloader的链接器将base地址设置位了0x70000,那上电之后,从0地址开始,找入口函数Reset_Handler,这个地址被map到0x75b80(相对于起始地址,偏移0x5b80),而实际上当PC跳到0x75b80 是个未知的地方,可能都不是代码段了. 那要如何处理?

                                 

可以在生成bootloader之后,将这个值手动减小0x70000.  这样CPU 去找Reset_Handler,就会找到0x5b80,就能正确执行指令。

问题4. 如何精确找到Reset_Handler位置并替换?

.section .vectors
	.align	2
	.globl	__Vectors
__Vectors:
	.long	__StackTop            /* Top of Stack */
	.long	Reset_Handler         /* Reset Handler */
	.long	NMI_Handler           /* NMI Handler */
	.long	HardFault_Handler     /* Hard Fault Handler */
	.long	MemManage_Handler     /* MPU Fault Handler */
	.long	BusFault_Handler      /* Bus Fault Handler */
	.long	UsageFault_Handler    /* Usage Fault Handler */
......

这个就是我们的中断向量表,结合前面的链接脚本就知道,_Vector的代码段地址就是0x70000 对应flash 的0x0地址,所以0x4地址,就是存放Reset_Handler地址。只需要将0x4位置的内容减去0x70000就可以了

build出来bootloader之后,执行这条语句,就可以将前面的80 5b 07 00 变成 80 5b 00 00 

echo -e -n "\x00" | dd of=bootloader_fw__.bin bs=1 count=1 conv=notrunc seek=6

 

问题5. 为什么只需要替换Reset_Handler?

在Reset_Handler中不要跳转到其他函数,并且在Reset_Handler中将代码段和数据段(我们在bootloader代码不用alloc,避免堆的使用,而bss段根本就不需要拷贝)搬运到0x70000位置就可以了.

问题6.如何copy 代码?

先从Reset_Handler开始:

Reset_Handler:
    ldr r0, =0x36303131 //随便写一个数,判断是否是第一次启动
    cmp r0, r8          //R8是默认值,第一次启动,肯定和0x36303131不相等
    beq .REMPAP_DONE    //如果相等,说明第二次map 已经做完了,可以直接跳到REMAP_DONE

    /* 将bootloader的代码段 copy到内存中,
将falsh的0x0(代码段起始位置)到etext,拷贝到内存中,
从0x0开始,前面我们有说过,第一次map,
内存会map为 0x10000000~ 0x10080000*/
    ldr	r0, =0
    ldr	r1, =__Vectors  //_Vectors = 0x70000
    ldr	r2, =__etext
    sub r2, r1           //计算代码段长度
    ldr r1, =0x10000000  //内存0x0即内存的起始位置
.L_cp_text_loop:
        ldr r3, [r0]
        str r3, [r1]
        adds r0, #4
        adds r1, #4
        cmp  r0, r2
        bne  .L_cp_text_loop

    /* 将bootloader的代码段和数据段,copy到内存中,
将falsh的0x0(代码段起始位置)到__data_end__,
拷贝到内存中,从0x70000开始 */
    ldr	r0, =0
    ldr	r1, =__Vectors   //_Vectors = 0x70000
    ldr	r2, =__data_end__
    ldr r4, =0x10000000  //内存0x0即内存的起始位置
    adds r1, r4          //r1 = 0x10070000
    adds r2, r4
.L_cp_bootloader_loop:
        ldr r3, [r0]
        str r3, [r1]
        adds r0, #4
        adds r1, #4
        cmp  r1, r2
        bne  .L_cp_bootloader_loop

这段代码执行完的效果:

问题7. 为什么要做copy两次呢?

    /* 将0x50b0003c 的值设为1,就完成了第二次map,具体要看硬件设计了*/
    ldr r0, =0x50b0003c
    ldr r1, =0x00000001
    str r1, [r0]
    ldr r1, [r0]
    nop
    nop

    /* 还记得前面有将 75b80 改为 05b80吧,现在因为把代码搬到了内存的0x70000位置了,所以要把Reset_Handler的地址还原*/
    ldr	r0, =__Vectors     /*__Vectors  = 0x70000,
此时的_Vector已经是内存中的Vector了,
(但是还是0x0地址的_Vector),不再是flash中的Vector,
因为已经完成了Remap ,这也是回答前面为什么要有两次copy了 */
    adds r0, #4            /*r0值为0x70004,前面知道0x7004是从flash的0x4 copy过来的,所以[r0]为0x5b80*/
    ldr  r1, [r0]          //r1值为5b80
    ldr	 r2, =__Vectors
    adds r1, r2            //r1为75b80
    str  r1, [r0]          //向0x70004地址写入75b80

    /* 跳转到bootloader的高地址执行(0x70000) ,
注意remap之后,为了保证正常运行,必须要有一份
和remap之前完全一样的代码段,否则程序就会拿到随机的内容,
这就是为什么需要有前面的第一次拷贝动作(将代码段拷贝到内存地址为0x0位置)*/
    ldr	r0, =__Vectors
    ldr r1, [r0]
    msr msp, r1 /*程序需要从内存的0x0跳转到内存0x70000处,
两个原因:1. 跳转到0x70000之后,代码段,数据段才和链接器指定的地址匹配,
如果不跳,除了当前执行的Reset_Handler(下一次连Reset_Handler也无法执行了,
因为Reset_Handler地址也被还原了),
程序无法跳转;2. 0x0将来要用来存放APP的代码段和数据段*/

    ldr r8, =0x36303131  //将R8设置为值,为了第二次直接进入.REMAP_DONE
    ldr r1, [r0, #4]     //r1为0x70004,即Reset_Handler
    bx  r1               //跳转到Reset_Handler执行

问题8. 第二次进入Reset_Handler,直接进入.REMAP_DONE,做了些什么呢?

.REMPAP_DONE:
    movs r8, 0
#ifndef __NO_SYSTEM_INIT
	bl	SystemInit
#endif

#ifndef __START
#define __START _start
#endif
	bl	__START

首先将r8设为0,r8之前存放0x36303131 (为了不再做一次remap, 用一个不常用的寄存器保存着这个值).

进入SystemInit函数,然后进入_start 函数,可以看出_start就是_mainCRTStartup

进入_mainCRTStartup,这个函数不细看了,令我们欣喜的是,看到了我们熟悉的main函数,

 

 

 

后面就是C语言了,从Flash中将APP load进memory 0x0位置,load完成之后,跳转到APP的Reset_Handler执行.自此,整个启动流程就分析完了

 

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值