ARM Cortex-M底层技术(2)—单片机的启动代码详解

   日期:2021-01-09     浏览:110    评论:0    
核心提示:提示:若转载请备注来源,谢谢文章目录启动文件一、pandas是什么?二、使用步骤1.引入库2.读入数据总结启动文件提示:这里可以添加本文要记录的大概内容:例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。提示:以下是本篇文章正文内容,下面案例可供参考一、pandas是什么?示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。二、使用步骤1.引入库代码如下(示例):import

提示:若转载请备注来源,谢谢

文章目录

  • 启动文件
    • 1. 什么是启动代码
    • 2. 启动代码主要干了什么
  • 启动文件分析
    • 一、设置堆栈
    • 二、定义中断向量表
    • 三、初始化系统时钟
    • 总结

启动文件

1. 什么是启动代码

启动代码是系统上电或者复位后运行的第一段代码,是进入C 语言的main 函数之前需要执行的那段汇编代码。或者说用户程序运行之前对系统硬件及软件环境进行必要的初始化并在最后使程序跳转到用户程序
将启动文件理解为一种描述性的代码,不要拘泥于它的实现机制,我之前一直在想启动代码中汇编代码的执行顺序,后来没明白,估计和keil工具有关系,只需要记住一个道理,程序上电执行的第一条代码(我们可以控制的代码),就是启动文件中Reset Handler函数,Reset Handler函数 的地址存放在flash的0x00000004处。

2. 启动代码主要干了什么

对于 Cortex-M系列的芯片而言,启动代码大同小,明白一个几乎所有的也就都明白了。

  1. 设置堆栈
  2. 定义中断向量表
  3. 初始化系统时钟(Reset_Handler中做
  4. 初始化堆栈,有的启动代码不一定做Reset_Handler中做
  5. 执行_main,准备c语言的运行环境,初始化程序计数器指针PC指向main,进而来到c语言的世界

启动文件分析

一、设置堆栈

; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000800

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000100

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit


                PRESERVE8
                THUMB

以上仅仅定义了堆栈的大小,堆栈的起始地址在keil中设置方式有两种,
第一种:

第二种,分散加载文件(将keil的Use Memory Layout from Target Dialog去掉勾选就会有该文件),在实际项目中,我通常使用该文件,可以理解问链接文件

在上图(第一幅图片中),启动文件的最后几行,有关于Use MicoLIB的描述,还有关于堆栈的设置,代码为,主要是初始化堆栈的地址范围:

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
                 IF      :DEF:__MICROLIB
                
                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit
                
                 ELSE
                
                 IMPORT  __use_two_region_memory
                 EXPORT  __user_initial_stackheap
                 
__user_initial_stackheap

                 LDR     R0, =  Heap_Mem
                 LDR     R1, =(Stack_Mem + Stack_Size)
                 LDR     R2, = (Heap_Mem +  Heap_Size)
                 LDR     R3, = Stack_Mem
                 BX      LR

                 ALIGN

                 ENDIF

二、定义中断向量表

1. 当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR的入口地址, 内核使用了―向量表查表机制‖(是ARM的实现机制)。这里使用一张向量表。向量表其实是一个WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。
2. 向量表在地址空间中的位置默认存放在0x00000000处,但是还可以设置,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。

代码为:

; Vector Table Mapped to Address 0 at Reset

                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors

__Vectors       DCD     __initial_sp              ; Top of Stack
                DCD     Reset_Handler             ; Reset Handler
                DCD     NMI_IRQHandler            ; NMI Handler
                DCD     HardFault_IRQHandler      ; Hard Fault Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
				......
				......
                AREA    |.text|, CODE, READONLY

附加:常常在涉及到升级loader中使用

  1. 向量表重定位 + 跳转代码如下:
__asm void SoftReset(u32 address)
{ 
	LDR		R1, =0xE000ED08				// 0xE000ED08 重定位地址
	STR		R0,	[R1,	#0]					// R0为形参address
    
	LDR r1, [r0]        // address内前4个直接存放的 Top of Stack 
	msr MSP,r1  		// 给MSP赋值

	ADDS r0, r0, #4    	// Reset_Handler 的地址 
	LDR r1, [r0]            
	NOP
	BX R1			// 执行 Reset_Handler
	NOP
	NOP
	NOP 
}
  1. 若仅仅想实现向量表重定位,例如将向量表定位到0x00000200,代码如下:
// SCB->VTOR就等于 0xE000ED08
//SCB->VTOR = 0x00000000; // 等价于0xE000ED08 = 0x00000000;
SCB->VTOR = 0x00000200;

三、初始化系统时钟

还是要在重申一下,复位子程序Reset_Handler是系统上电后第一个执行的程序。这里分析两个Reset handler,一个是STM32 demo上的,一个是我们使用的thk88芯片的
stm32代码为:

; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit				// 系统初始化,里面包含了初始化时钟
                BLX     R0               
                LDR     R0, =__main		// 最后跳转到main()
                BX      R0
                ENDP

thk88代码为

Reset_Handler   PROC			

                EXPORT  Reset_Handler             [WEAK]
				IMPORT	main
				
				; 开启内部时钟30M
  				LDR		R3, =SCUBASEADDR
				LDR		R0, =0x00000000
				STR		R0, [R3,#SCUSCK]
				LDR		R0, =0x00000000
				STR		R0, [R3,#SCUINCKD]

				; 关闭USB PHY,禁止DP上拉电阻                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
				LDR		R0, =0x00000100
				STR     R0, [R3, #SCUCM3]       ;OPEN USB CLK
				LDR		R2, =USBBASEADDR
				LDR		R0, =0x00000001
				STR		R0, [R2,#USBPCON]		; 关闭USB PHY
				LDR		R0, =0x00000000
				LDR 	R2, =USBPHYCON 
				STR		R0, [R2]  				;禁止DP上拉
				;STR		R0, [R2,#USBPHYCON] ;禁止DP上拉

				; 屏蔽 NMI 中断
				LDR 	R0, =0xFFFFFFFF
				LDR		R2,	=SCUNMI
				STR		R0, [R3,R2]	

				; DMA使用event方式
				LDR		R2, = 0x40000100	;SCUEVTSET1 = 0x02;DMA event mode
				LDR		R0, = 0x00000002
				STR		R0, [R2]
				
				;初始化用到的RAM区,4字节对齐,包括RW和ZI区域
				BL 		CLEAR_ZI
				
				IMPORT __main
				LDR R0,= __main
				BX R0
                ENDP

每款芯片的启动代码大同小异,熟悉一个其他的都可以看懂。

总结

具体启动流程如图,两种方式

  1. 启动流程1(使用标准库,不使用Microlib)如下图:
  2. 启动流程2(使用Microlib)如下图:

    假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x00000000(映射到0x8000000,两个是一个东西),则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处(复位地址在栈顶地址4字节后)。当STM32上电后,遇到复位信号,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。

重申:

  1. 绝大部分ARM-M协议的芯片,复位之后先进入厂商boot,此时所有的用户均无法接入处理器;厂商boot主要负责芯片最初级的初始化,对MCU进行一些差异性设置等,BOOT完成后,会把主动权交给用户,也就是启动代码;
  2. 启动代码(执行汇编语言不需要此启动代码),在启动文件中,会设置MSP(主堆栈指针)和PC(程序计数器)的值,MSP的地址默认是0x00000000,PC的地址默认是0x00000004,这两个地址可以通过CORTEX-M中的VTOR寄存器来进行重映射,修改。

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服