(一)ARM 单片机开机启动


一、零地址映射

以 ARM Cortex-M 为例,CPU上电后就从地址 0x0000000 开始读取数据和指令。但是,片内 ROM 地址一般不会以 0x0000000 开始,这就需要芯片把片内 ROM 的真实地址映射到 0x0000000 上。如下图:
在这里插入图片描述
图中,芯片上电时自动根据 BOOT 引脚电平,可以选择把系统区 ROM(0x1FFFF000) 和用户编程区 ROM(0x08000000) 中的一个映射到 0x0000000 地址。其中,系统存储区已经被厂家烧录了一个固定程序,用来支持串口、USB等烧录功能,以方便用户(在没有 jlink 工具的情况下)烧录程序。
芯片上电时, 0x0000000 处存储的数据格式,是被 Cortex-M 核规定好的,由启动代码生成。

二、栈指针与复位函数

Cortex-M 核对 0x0000000 处的前8个字节(两个32位int值)的数据是有要求的:第一个int值会被赋给栈指针SP,第二个int值是复位函数指针,指向上电后第一个被执行的函数。
这两个int值是由启动代码决定的,启动代码一般由芯片厂家的SDK库或者编程工具按芯片型号自动提供,例如下面的代码片段:

__Vectors       DCD     __initial_sp                        ; 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

上述代码,指定了最终生成的bin文件的开头,是一个SP栈指针和一张向量表。其中,复位函数 Reset_Handler 的地址就是复位向量,该函数主要负责系统初始化、C环境初始化,然后进入 main 函数,例如:

Reset_Handler   PROC
                EXPORT  Reset_Handler                       [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP
  • SystemInit 函数一般是由芯片厂家的SDK库提供的C函数,负责初始化时钟。
  • __main 这个函数除了初始化C环境,还会干一些别的事情,不同的编译工具、不同的厂家SDK库都会不尽相同。例如KEIL编程工具,它还会干一件比较重要的事情就是分散加载,如果你需要把程序加载到RAM里跑,这个分散加载功能就会很有用。

三、程序加载与重定位

另外,关于加载地址,网上很多人说是bin文件的存储地址,应该是不够准确的,容易引起误会。

加载地址是指存储器(例如ram、flash或硬盘)里的一段程序运行之前将要被(系统或引导程序)搬运到哪里去。一般,我们需要把一个程序搬运到它的链接地址上,才能正确运行,但是也有例外,比如:

  • 像一些简单的程序,是天然可重定位的(指令访问的地址都是基于PC的相对值),那么它被加载到哪里都是能跑的;
  • 像linux内核这种,加载到哪里都能跑,因为它自己的镜像文件头里有链接地址,内核会自行把自己重新复制到正确的链接地址上跑;
  • 像uboot这种引导程序,它被编译成一个可重定位程序,无论它被加载到哪里,那么uboot都可以把自己重定位到任何地址上跑。

可见,存储地址不一定是加载地址,加载地址不一定是链接地址,链接地址也不一定是运行地址。

一般,对于单片机而言,存储地址、加载地址、链接地址 、运行地址都是一样的,比如 0x08000000,或者再加上一个偏移 0x0800xxxx。当你使用分散加载功能把单片机程序放在RAM里跑时,存储地址就不再等于加载地址了,但是加载地址和链接地址还是要一样的;如果你还希望程序像uboot一样可重定位(就是同一个bin既能在A地址上跑,也能在B地址上跑,即运行地址不等于链接地址),那么这将比uboot重定位要复杂得多,因为KEIL支持的可重定位是一个阉割版,使用时还有一些限制,具体要参考KEIL手册。

为了方便,除非必要,不建议使用分散加载和重定位功能!更简单的代码逻辑,工程上就很有优势。
所以,对于启动代码,我们就使用SDK库给定的文件,最好不要再修改。

总结

  • 单片机一上电,就有能力读取并执行我们烧录到 0x08000000 处的程序,无需任何引导;
  • 启动代码文件由芯片厂商SDK库提供,我们尽量不要修改和关注,写好自己的 main 函数即可;
  • 为了简单和高效执行,程序尽量放在片内ROM上跑,避免出现加载到RAM甚至重定位;
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值