NXP

ARM启动过程(Cortex-M4 NXP QN9080为例)

2019-07-12 12:29发布

CMSIS 到底是什么

CMSIS的意思是Cortex Micro-controller Software Interface Standard,微控制器软件接口标准, 是 Cortex-M 处理器系列的与供应商无关的硬件抽象层。CMSIS 可实现与处理器和外设之间的一致且简单的软件接口,从而简化软件的重用,缩短微控制器开发人员新手的学习过程,并缩短新设备的上市时间。  

如何使用CMSIS,需要哪些文件?

以Freescale Kinetis L系列举例。 
独立于编译器的文件: 
● Cortex-M3内核及其设备文件(core_cm3.h + core_cm3.c
─ 访问Cortex-M0内核及其设备:NVIC等 
─ 访问Cortex-M0的CPU寄存器和内核外设的函数
● 微控制器专用头文件(device.h) - MKL25Z4.h 
─ 指定中断号码(与启动文件一致) 
─ 外设寄存器定义(寄存器的基地址和布局) 
─ 控制微控制器其他特有的功能的函数(可选)
● 微控制器专用系统文件( system_device.c) – system_MKL25Z4.h + system_MKL25Z4 .c 
─ 函数SystemInit,用来初始化微控制器 
–函数 void SystemCoreClockUpdate (void); 用于获取内核时钟频率 
─SystemCoreClock,该值代表系统时钟频率 
─ 微控制器的其他功能(可选)
● 编译器启动代码(汇编或者C)( startup_device.s) - startup_MKL25Z4.s for Keil 
─ 微控制器专用的中断处理程序列表(与头文件一致)
 

以QN9080的启动文件进行ARM启动流程讲解

startup_QN908X.s PRESERVE8 //PRESERVE8指定了以下的代码位8字节对齐 THUMB //THUMB指定了接下来的代码为THUMB指令集 ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY //此语句声明RESET数据段 EXPORT __Vectors //导出向量表标号,EXPORT作用类似于C语言中的extern IMPORT |Image$$ARM_LIB_STACK$$ZI$$Limit|
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
__Vectors DCD |Image$$ARM_LIB_STACK$$ZI$$Limit| ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler DCD HardFault_Handler DCD MemManage_Handler DCD BusFault_Handler DCD UsageFault_Handler
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
; Reset Handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, =SystemInit //系统初始化 BLX R0 LDR R0, =__main BX R0 ENDP
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
QN908X.scf LR_m_text m_interrupts_start m_text_start+m_text_size-m_interrupts_start //加载区 名称 起始地址 地址范围 { ; load region size_region VECTOR_ROM m_interrupts_start m_interrupts_size //执行区名称 起始地址 地址范围 { ; load address = execution address //执行域和加载域一致 * (RESET,+FIRST) //将RESET代码放在区首,最开始执行 FIRST属性符表示放在最开始 } ER_m_text m_text_start m_text_size { ; load address = execution address//执行域和加载域一致 * (InRoot$$Sections) .ANY (+RO) //只读代码和数据放在此区域 } #if (defined(__ram_vector_table__)) VECTOR_RAM m_interrupts_ram_start EMPTY m_interrupts_ram_size { } #else VECTOR_RAM m_interrupts_start EMPTY 0 { } #endif RW_m_data m_data_start m_data_size-Stack_Size-Heap_Size { ; RW data .ANY (+RW +ZI) //RW,ZI 放入此处 *(in_ram) } ARM_LIB_HEAP +0 EMPTY Heap_Size { ; Heap region growing up //+0其实就是从前面一个域的末地址开始 } ;ARM_LIB_STACK m_data_start+m_data_size EMPTY -Stack_Size { ; Stack region growing down //-Stack_Size 栈由高地址向低地址 ARM_LIB_STACK +0 ALIGN 8 EMPTY Stack_Size { ; Stack region growing up } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
.map文件 ============================================================================== Memory Map of the image Image Entry point : 0x00000111 Load Region LR_m_text (Base: 0x00000000, Size: 0x00004ba0, Max: 0x0007d800, ABSOLUTE) Execution Region VECTOR_ROM (Base: 0x00000000, Size: 0x00000110, Max: 0x00000110, ABSOLUTE) Base Addr Size Type Attr Idx E Section Name Object 0x00000000 0x00000110 Data RO 4 RESET startup_qn908x.o Execution Region ER_m_text (Base: 0x00000110, Size: 0x000049ec, Max: 0x0007d6f0, ABSOLUTE) Base Addr Size Type Attr Idx E Section Name Object 0x00000110 0x00000000 Code RO 1650 * .ARM.Collect$$$$00000000 mc_w.l(entry.o) 0x00000110 0x00000004 Code RO 1782 .ARM.Collect$$$$00000003 mc_w.l(entry4.o) 0x00000114 0x00000004 Code RO 1785 .ARM.Collect$$$$00000004 mc_w.l(entry5.o) 0x00000118 0x00000000 Code RO 1787 .ARM.Collect$$$$00000008 mc_w.l(entry7b.o) 0x00000118 0x00000000 Code RO 1789 .ARM.Collect$$$$0000000A mc_w.l(entry8b.o) 0x00000118 0x00000008 Code RO 1790 .ARM.Collect$$$$0000000B mc_w.l(entry9a.o) 0x00000120 0x00000000 Code RO 1792 .ARM.Collect$$$$0000000D mc_w.l(entry10a.o) 0x00000120 0x00000000 Code RO 1794 .ARM.Collect$$$$0000000F mc_w.l(entry11a.o) 0x00000120 0x00000004 Code RO 1783 .ARM.Collect$$$$00002714 mc_w.l(entry4.o) 0x00000124 0x00000180 Code RO 5 .text startup_qn908x.o ...... Execution Region VECTOR_RAM (Base: 0x04000400, Size: 0x00000110, Max: 0x00000110, ABSOLUTE) Base Addr Size Type Attr Idx E Section Name Object 0x04000400 0x00000110 Zero RW 1 VECTOR_RAM.bss anon$$obj.o Execution Region RW_m_data (Base: 0x04000510, Size: 0x000001d0, Max: 0x0001f2f0, ABSOLUTE) Base Addr Size Type Attr Idx E Section Name Object 0x04000510 0x00000038 Data RW 334 .data multitimer.o 0x04000548 0x00000008 Data RW 506 .data fsl_debug_console.o 0x04000550 0x00000048 Data RW 615 .data pin_mux.o 0x04000598 0x00000001 Data RW 1129 .data fsl_clock.o 0x04000599 0x00000003 PAD 0x0400059c 0x0000000c Data RW 1530 .data fsl_qn_qdec_ex_function.o 0x040005a8 0x00000004 Data RW 1808 .data mc_w.l(mvars.o) 0x040005ac 0x00000004 Data RW 1813 .data mc_w.l(errno.o) 0x040005b0 0x00000100 Zero RW 114 .bss unity.o 0x040006b0 0x00000010 Zero RW 505 .bss fsl_debug_console.o 0x040006c0 0x00000020 Zero RW 889 .bss fsl_flexcomm.o Execution Region ARM_LIB_HEAP (Base: 0x040006e0, Size: 0x00000000, Max: 0x00000000, ABSOLUTE) Base Addr Size Type Attr Idx E Section Name Object 0x040006e0 0x00000000 Zero RW 2 ARM_LIB_HEAP.bss anon$$obj.o Execution Region ARM_LIB_STACK (Base: 0x040006e0, Size: 0x00000800, Max: 0x00000800, ABSOLUTE) Base Addr Size Type Attr Idx E Section Name Object 0x040006e0 0x00000800 Zero RW 3 ARM_LIB_STACK.bss anon$$obj.o
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
你可以对比的看.scf 和.map文件,.map文件就是依据.scf文件来进行加载域和执行域的确定的。 我们可以看到VECTOR_ROM和ER_m_text的两个加载域和执行域是一致的。 
这里写图片描述 
这个图是我网上找的,具体地址界限并不和上面的程序一致。 
下面我把具体的启动流程列出来:
  • 复位状态后,CM4的第一件事就是读取下列两个 32位整数的值: 
    (1)从地址0x0000,0000处取出 MSP 的初始值。 
    (2)从地址0x0000,0004处取出 PC的初始值——这个值是复位向量,LSB 必须是1 。 然后从这个值所对应的地址处取指。 
    这里写图片描述 
    注意,这与传统的ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的RM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。在 CM3中,0 地址处提供 MSP 的初始值,然后就是向量表(向量表在以后还可以被移至其它位置)。 
    CM3上电后的向量表 
    CM3上电后的向量表
  • 在复位函数里做一些系统的初始化: MSP赋值,SystemInit 
    arm的启动代码一般是用汇编写的,在堆栈建立以后才可以运行C代码,因为C函数调用需要把参数,函数返回地址入栈,堆栈没有建立是不能运行C代码的。 
    这里的SystemInit虽然在.c文件里,但内部代码全是对寄存器的操作,本身也没有参数和返回值,所有编译出来全是代码段,没有变量什么的。所以不会因为堆栈还没有建立就不能执行。
  • 然后调用系统函数__main(); (IAR跳转到__iar_program_start)
  • _main 直接跳转到 __scatterload,__scatterload 执行代码和数据复制以及 ZI 数据的清零。根据分散加载文件,拷贝RW数据到RAM,在RAM空间里建立ZI的数据空间,建立运行时的映像存储器映射.
  • 然后跳转到 __rt_entry(运行时的入口)则负责初始化 C 库。还设置应用程序的栈和堆,初始化库函数及其静态数据。
  • 这时应用程序的堆栈建立了,跳转到main()函数,运行用户代码。
这里写图片描述  

上面startup中的|Image$$ARM_LIB_STACK$$ZI$$Limit| 什么意思???

__Vectors 第一个DCD就是 |Image$$ARM_LIB_STACK$$ZI$$Limit| 
这是什么意思呢?这时候可以求助KEIL的Help工具。 
这里写图片描述
在Linker User Guide的7.1.4中讲解了如何在分散加载文件scatter中指定栈和堆。
当你在分散加载文件中定义了两个自定义的执行域ARM_LIB_HEAP和ARM_LIB_STACK,这会引起ARM的library去用 |Image$$ARM_LIB_STACK$$ZI$$Limit|的值来执行__user_setup_stackheap()函数。 |Image$$ARM_LIB_STACK$$ZI$$Limit| 
的意思是Address of the byte beyond the end of the ZI output section in the execution region. 
在执行域的ZI域后面初始化栈。