基于 LPC1114 的 M0 启动流程分析

基于LPC1114的M0启动流程分析

一、.s 启动进程

首先关注 LPC1114 的启动文件,为了低成本的需要,使用芯片内部的晶振,所以这里需要对启动文件进行修改。

先看一下 startup_LPC11xx.s 文件,其代码如下。

;/**************************************************************************//**
; * @file     startup_LPC11xx.s
; * @brief    CMSIS Cortex-M0 Core Device Startup File
; *           for the NXP LPC11xx/LPC11Cxx Device Series
; * @version  V1.10
; * @date     24. November 2010
; *------- <<< Use Configuration Wizard in Context Menu >>> ------------------
; *
; * @note
; * Copyright (C) 2009-2010 ARM Limited. All rights reserved.
; *
; * @par
; * ARM Limited (ARM) is supplying this software for use with Cortex-M 
; * processor based microcontrollers.  This file can be freely distributed 
; * within development tools that are supporting such ARM based processors. 
; *
; * @par
; * THIS SOFTWARE IS PROVIDED "AS IS".  NO WARRANTIES, WHETHER EXPRESS, IMPLIED
; * OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
; * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE.
; * ARM SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR
; * CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
; *
; ******************************************************************************/


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

Stack_Size      EQU     0x00000400

                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     0x00000000

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


                PRESERVE8
                THUMB


; 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_Handler               ; NMI Handler
                DCD     HardFault_Handler         ; Hard Fault Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     SVC_Handler               ; SVCall Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     PendSV_Handler            ; PendSV Handler
                DCD     SysTick_Handler           ; SysTick Handler

                ; External Interrupts
                DCD     WAKEUP_IRQHandler         ; 16+ 0: Wakeup PIO0.0
                DCD     WAKEUP_IRQHandler         ; 16+ 1: Wakeup PIO0.1
                DCD     WAKEUP_IRQHandler         ; 16+ 2: Wakeup PIO0.2
                DCD     WAKEUP_IRQHandler         ; 16+ 3: Wakeup PIO0.3
                DCD     WAKEUP_IRQHandler         ; 16+ 4: Wakeup PIO0.4
                DCD     WAKEUP_IRQHandler         ; 16+ 5: Wakeup PIO0.5
                DCD     WAKEUP_IRQHandler         ; 16+ 6: Wakeup PIO0.6
                DCD     WAKEUP_IRQHandler         ; 16+ 7: Wakeup PIO0.7
                DCD     WAKEUP_IRQHandler         ; 16+ 8: Wakeup PIO0.8
                DCD     WAKEUP_IRQHandler         ; 16+ 9: Wakeup PIO0.9
                DCD     WAKEUP_IRQHandler         ; 16+10: Wakeup PIO0.10
                DCD     WAKEUP_IRQHandler         ; 16+11: Wakeup PIO0.11
                DCD     WAKEUP_IRQHandler         ; 16+12: Wakeup PIO1.0
                DCD     CAN_IRQHandler            ; 16+13: CAN
                DCD     SSP1_IRQHandler           ; 16+14: SSP1
                DCD     I2C_IRQHandler            ; 16+15: I2C
                DCD     TIMER16_0_IRQHandler      ; 16+16: 16-bit Counter-Timer 0
                DCD     TIMER16_1_IRQHandler      ; 16+17: 16-bit Counter-Timer 1
                DCD     TIMER32_0_IRQHandler      ; 16+18: 32-bit Counter-Timer 0
                DCD     TIMER32_1_IRQHandler      ; 16+19: 32-bit Counter-Timer 1
                DCD     SSP0_IRQHandler           ; 16+20: SSP0
                DCD     UART_IRQHandler           ; 16+21: UART
                DCD     USB_IRQHandler            ; 16+22: USB IRQ
                DCD     USB_FIQHandler            ; 16+24: USB FIQ
                DCD     ADC_IRQHandler            ; 16+24: A/D Converter
                DCD     WDT_IRQHandler            ; 16+25: Watchdog Timer
                DCD     BOD_IRQHandler            ; 16+26: Brown Out Detect
                DCD     FMC_IRQHandler            ; 16+27: IP2111 Flash Memory Controller
                DCD     PIOINT3_IRQHandler        ; 16+28: PIO INT3
                DCD     PIOINT2_IRQHandler        ; 16+29: PIO INT2
                DCD     PIOINT1_IRQHandler        ; 16+30: PIO INT1
                DCD     PIOINT0_IRQHandler        ; 16+31: PIO INT0


                IF      :LNOT::DEF:NO_CRP
                AREA    |.ARM.__at_0x02FC|, CODE, READONLY
CRP_Key         DCD     0xFFFFFFFF
                ENDIF


                AREA    |.text|, CODE, READONLY


; Reset Handler

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  SystemInit
                IMPORT  __main
                LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP


; Dummy Exception Handlers (infinite loops which can be modified)                

NMI_Handler     PROC
                EXPORT  NMI_Handler               [WEAK]
                B       .
                ENDP
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler         [WEAK]
                B       .
                ENDP
SVC_Handler     PROC
                EXPORT  SVC_Handler               [WEAK]
                B       .
                ENDP
PendSV_Handler  PROC
                EXPORT  PendSV_Handler            [WEAK]
                B       .
                ENDP
SysTick_Handler PROC
                EXPORT  SysTick_Handler           [WEAK]
                B       .
                ENDP

Default_Handler PROC

                EXPORT  WAKEUP_IRQHandler         [WEAK]
                EXPORT  CAN_IRQHandler            [WEAK]
                EXPORT  SSP1_IRQHandler           [WEAK]
                EXPORT  I2C_IRQHandler            [WEAK]
                EXPORT  TIMER16_0_IRQHandler      [WEAK]
                EXPORT  TIMER16_1_IRQHandler      [WEAK]
                EXPORT  TIMER32_0_IRQHandler      [WEAK]
                EXPORT  TIMER32_1_IRQHandler      [WEAK]
                EXPORT  SSP0_IRQHandler           [WEAK]
                EXPORT  UART_IRQHandler           [WEAK]
                EXPORT  USB_IRQHandler            [WEAK]
                EXPORT  USB_FIQHandler            [WEAK]
                EXPORT  ADC_IRQHandler            [WEAK]
                EXPORT  WDT_IRQHandler            [WEAK]
                EXPORT  BOD_IRQHandler            [WEAK]
                EXPORT  FMC_IRQHandler            [WEAK]
                EXPORT  PIOINT3_IRQHandler        [WEAK]
                EXPORT  PIOINT2_IRQHandler        [WEAK]
                EXPORT	PIOINT1_IRQHandler        [WEAK]
                EXPORT	PIOINT0_IRQHandler        [WEAK]

WAKEUP_IRQHandler
CAN_IRQHandler
SSP1_IRQHandler
I2C_IRQHandler
TIMER16_0_IRQHandler
TIMER16_1_IRQHandler
TIMER32_0_IRQHandler
TIMER32_1_IRQHandler
SSP0_IRQHandler
UART_IRQHandler
USB_IRQHandler
USB_FIQHandler
ADC_IRQHandler
WDT_IRQHandler
BOD_IRQHandler
FMC_IRQHandler
PIOINT3_IRQHandler  
PIOINT2_IRQHandler 
PIOINT1_IRQHandler
PIOINT0_IRQHandler

                B       .

                ENDP


                ALIGN


; User Initial Stack & Heap

                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


                END

1.1 堆栈配置

STM32栈stack 堆栈 注意事项 Stack_Size EQU 0x00000400

keil 环境下 AREA SUN ,NOINIT, READONLY , ALIGN=3 分别表示什么意思

关于堆栈

为什么要加PRESERVE8栈的8字节对齐

栈 stack 是一块程序运行时用来存储临时变量的内存 RAM 空间。栈一般静态分配,并且后进先出,栈的生命周期从程序的起始直到程序结束。一个函数返回,其用到的栈空间就被释放给后续函数使用。

一般默认是 Stack_Size EQU 0x00000400,表示工程中栈大小是 1024 字节,即局部变量不能大于1024字节。

Stack_Size      EQU     0x00000400  // EQU 是等于的意思;

                AREA    STACK, NOINIT, READWRITE, ALIGN=3

// AREA 命令指示汇编器汇编一个新的代码段或数据段;
// 这里使用伪指令 AREA,开辟一段内存空间,段名是 STACK;
// NOINIT,指定此数据段仅仅保留了内存单元,而没有将各初始值写入内存单元,或者将各个内存单元值初始化为 0;
// READWRITE 可读可写;
// ALIGN = 3,设置对齐方式,2 的 3 次方表示以 8 字节对齐;
// STACK 和 HEAP 只是两个 section 的名字,程序就是由各种代码和数据 section 组成的,最后由链接器排开并生成一个映像文件。

Stack_Mem       SPACE   Stack_Size

// 分配连续 Stack_Size 字节的存储单元并初始化为 0;

__initial_sp    // 必须顶格写,这个标号,会被链接器解析成这个名为 STACK 的 section 结束后的地址。
                // 向量表的第一条定义为:
                // __Vectors       DCD     __initial_sp              ; Top of Stack
                // 这才确定了 MSP 的初始值为 __initial_sp;

Heap_Size       EQU     0x00000000

                // LPC1114 里面没有使用堆,这里大小为 0;

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3

                // 堆段,malloc用的地方,不一定连续空间;
__heap_base     // 表示堆空间起始地址;
Heap_Mem        SPACE   Heap_Size   // 分配堆空间;
__heap_limit    // 表示堆空间结束地址与__heap_base配合限制堆的大小;

// 同理,堆也是一样,通过 __heap_base 和 __heap_limit 这两个标号,最终变成两个地址,就是堆的起止范围;

                PRESERVE8   // 指定当前文件保持栈的八字节对齐;
                THUMB       // 指定 THUMB 指令集,THUMB 必须位于使用新语法的任何Thumb代码之前;
// 在启动文件的最后,通过这个例程,来实现和 C 语言库的对接,才初始化了 HEAP。

; User Initial Stack & Heap

                IF      :DEF:__MICROLIB     // 可在 keil 的配置项中勾选这个宏;
                
                EXPORT  __initial_sp
                EXPORT  __heap_base
                EXPORT  __heap_limit
                
                ELSE
                
                IMPORT  __use_two_region_memory
                EXPORT  __user_initial_stackheap
__user_initial_stackheap

1.2 中断向量表

STM32之 启动文件详细解析(V3.5.0)

startup_LPC17xx.s 里面的 CRP_key 是起什么作用的

; Vector Table Mapped to Address 0 at Reset

// 实际上是在 CODE 区(假设 LPC1114 从 FLASH 启动,则此中断向量表起始地址即为 0x0000000);

                AREA    RESET, DATA, READONLY

                // 定义一块数据段,只可读,段名字是 RESET,复位段,只包含数据,只读;

                EXPORT  __Vectors

                // EXPORT 命令声明一个符号,可由链接器用于解释各个目标和库文件中的符号引用,相当于声明了一个全局变量; 
                // GLOBAL 和 EXPORT 的作用相同;
                // 标号输出,中断向量表开始;
                // EXPORT在程序中声明一个全局的标号 __Vectors,该标号可在其他的文件中引用;

                // 以下为向量表,在复位时被映射到 FLASH 的 0 地址;

                // DCD 命令分配一个或多个字的存储器,在四个字节的边界上对齐,并定义存储器的运行时初值;
                
__Vectors       DCD     __initial_sp              ; Top of Stack
            
                // 栈顶指针,被放在向量表的开始,FLASH的0地址,复位后首先装载栈顶指针;
                
                DCD     Reset_Handler             ; Reset Handler

                // 复位异常,装载完栈顶后,第一个执行的,并且不返回;

                DCD     NMI_Handler               ; NMI Handler

                // NMI Handler 不可屏蔽中断;

                DCD     HardFault_Handler         ; Hard Fault Handler

                // Hard Fault Handler 硬件错误中断;

                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     SVC_Handler               ; SVCall Handler

                // SVCall Handler 系统调用异常,主要是为了调用操作系统内核服务;

                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     PendSV_Handler            ; PendSV Handler

                // PendSV Handler 挂起异常,此处可以看见用作了 uCOS-II的 上下文切换异常,推荐使用;
                // 因为 Cortex-M0 会在异常发生时自动保存 R0 - R3,R1,R13(堆栈指针SP),R14(链接地址,也叫返回地址LR,在异常返回时使用);
                // R15(程序计数器 PC,为当前应用程序 +4)和中断完成时自动回复,只需保存 R4 - R11;
                // 大大减少了中断响应和上下文切换的时间

                DCD     SysTick_Handler           ; SysTick Handler
                
                // SysTick Handler 滴答定时器,为操作系统内核时钟;

---------------------------------------------------------------------------------------------------
                // 以上都是 Coretex M0 内核自带的,以下为外部中断向量表;

                ; External Interrupts
                DCD     WAKEUP_IRQHandler         ; 16+ 0: Wakeup PIO0.0
                DCD     WAKEUP_IRQHandler         ; 16+ 1: Wakeup PIO0.1
                DCD     WAKEUP_IRQHandler         ; 16+ 2: Wakeup PIO0.2
                DCD     WAKEUP_IRQHandler         ; 16+ 3: Wakeup PIO0.3
                DCD     WAKEUP_IRQHandler         ; 16+ 4: Wakeup PIO0.4
                DCD     WAKEUP_IRQHandler         ; 16+ 5: Wakeup PIO0.5
                DCD     WAKEUP_IRQHandler         ; 16+ 6: Wakeup PIO0.6
                DCD     WAKEUP_IRQHandler         ; 16+ 7: Wakeup PIO0.7
                DCD     WAKEUP_IRQHandler         ; 16+ 8: Wakeup PIO0.8
                DCD     WAKEUP_IRQHandler         ; 16+ 9: Wakeup PIO0.9
                DCD     WAKEUP_IRQHandler         ; 16+10: Wakeup PIO0.10
                DCD     WAKEUP_IRQHandler         ; 16+11: Wakeup PIO0.11
                DCD     WAKEUP_IRQHandler         ; 16+12: Wakeup PIO1.0
                DCD     CAN_IRQHandler            ; 16+13: CAN
                DCD     SSP1_IRQHandler           ; 16+14: SSP1
                DCD     I2C_IRQHandler            ; 16+15: I2C
                DCD     TIMER16_0_IRQHandler      ; 16+16: 16-bit Counter-Timer 0
                DCD     TIMER16_1_IRQHandler      ; 16+17: 16-bit Counter-Timer 1
                DCD     TIMER32_0_IRQHandler      ; 16+18: 32-bit Counter-Timer 0
                DCD     TIMER32_1_IRQHandler      ; 16+19: 32-bit Counter-Timer 1
                DCD     SSP0_IRQHandler           ; 16+20: SSP0
                DCD     UART_IRQHandler           ; 16+21: UART
                DCD     USB_IRQHandler            ; 16+22: USB IRQ
                DCD     USB_FIQHandler            ; 16+24: USB FIQ
                DCD     ADC_IRQHandler            ; 16+24: A/D Converter
                DCD     WDT_IRQHandler            ; 16+25: Watchdog Timer
                DCD     BOD_IRQHandler            ; 16+26: Brown Out Detect
                DCD     FMC_IRQHandler            ; 16+27: IP2111 Flash Memory Controller
                DCD     PIOINT3_IRQHandler        ; 16+28: PIO INT3
                DCD     PIOINT2_IRQHandler        ; 16+29: PIO INT2
                DCD     PIOINT1_IRQHandler        ; 16+30: PIO INT1
                DCD     PIOINT0_IRQHandler        ; 16+31: PIO INT0


                IF      :LNOT::DEF:NO_CRP
                AREA    |.ARM.__at_0x02FC|, CODE, READONLY
CRP_Key         DCD     0xFFFFFFFF
                ENDIF
                
                // 代码读保护,也就是加密的关键字;
                // 加密分成几个等级,等级3最高,经过3级加密后,芯片再也无法擦除;
                // 除非之前烧写的程序带有 IAP,IAP 可以使芯片进入 ISP 模式;

                AREA    |.text|, CODE, READONLY

                // |.text| 用于表示由 C 编译程序产生的代码段,或用于以某种方式与 C 库关联的代码段;
                // 定义 C 编译器源代码的代码段,只读;
                // 定义一个代码段,可读,段名字是.text;

1.3 定义中断服务函数

; Reset Handler

// 利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰;

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]    

                // WEAK 声明其他的同名标号优先于该标号被引用;
                // 就是说如果外面声明了的话,会调用外面的;
                // 表示弱定义,在外部没有定义该符号时导出该符号 Reset_Handler;

                IMPORT  SystemInit
                IMPORT  __main

                // 伪指令用于通知编译器要使用的标号在其他的源文件中定义;
                // 但要在当前源文件中引用,而且无论当前源文件是否引用该标号;
                // 该标号均会被加入到当前源文件的符号表中;

                LDR     R0, =SystemInit

                // 装载寄存器指令;

                BLX     R0

                // 带链接的跳转,切换指令集;

                LDR     R0, =__main

                // __main为运行时库提供的函数;
                // 完成堆栈,堆的初始化等工作,会调用下面定义的 __user_initial_stackheap;

                BX      R0

                // 切换指令集,main函数不返回,跳到__main,进入 C;

                ENDP

; Dummy Exception Handlers (infinite loops which can be modified)                

NMI_Handler     PROC
                EXPORT  NMI_Handler               [WEAK]
                B       .
                ENDP
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler         [WEAK]
                B       .
                ENDP
SVC_Handler     PROC
                EXPORT  SVC_Handler               [WEAK]
                B       .
                ENDP
PendSV_Handler  PROC
                EXPORT  PendSV_Handler            [WEAK]
                B       .
                ENDP
SysTick_Handler PROC
                EXPORT  SysTick_Handler           [WEAK]
                B       .
                ENDP

Default_Handler PROC

// 输出异常向量表标号,方便外部实现异常的具体功能 , 是弱定义的意思;
// 如果外部定义了,优先执行外部定义,否则下面的函数定义;

                EXPORT  WAKEUP_IRQHandler         [WEAK]
                EXPORT  CAN_IRQHandler            [WEAK]
                EXPORT  SSP1_IRQHandler           [WEAK]
                EXPORT  I2C_IRQHandler            [WEAK]
                EXPORT  TIMER16_0_IRQHandler      [WEAK]
                EXPORT  TIMER16_1_IRQHandler      [WEAK]
                EXPORT  TIMER32_0_IRQHandler      [WEAK]
                EXPORT  TIMER32_1_IRQHandler      [WEAK]
                EXPORT  SSP0_IRQHandler           [WEAK]
                EXPORT  UART_IRQHandler           [WEAK]
                EXPORT  USB_IRQHandler            [WEAK]
                EXPORT  USB_FIQHandler            [WEAK]
                EXPORT  ADC_IRQHandler            [WEAK]
                EXPORT  WDT_IRQHandler            [WEAK]
                EXPORT  BOD_IRQHandler            [WEAK]
                EXPORT  FMC_IRQHandler            [WEAK]
                EXPORT  PIOINT3_IRQHandler        [WEAK]
                EXPORT  PIOINT2_IRQHandler        [WEAK]
                EXPORT	PIOINT1_IRQHandler        [WEAK]
                EXPORT	PIOINT0_IRQHandler        [WEAK]

// 如下只是定义一个空函数;

WAKEUP_IRQHandler
CAN_IRQHandler
SSP1_IRQHandler
I2C_IRQHandler
TIMER16_0_IRQHandler
TIMER16_1_IRQHandler
TIMER32_0_IRQHandler
TIMER32_1_IRQHandler
SSP0_IRQHandler
UART_IRQHandler
USB_IRQHandler
USB_FIQHandler
ADC_IRQHandler
WDT_IRQHandler
BOD_IRQHandler
FMC_IRQHandler
PIOINT3_IRQHandler  
PIOINT2_IRQHandler 
PIOINT1_IRQHandler
PIOINT0_IRQHandler

                B       .

                ENDP

                ALIGN   默认是字对齐方式;

1.4 堆栈初始化

一文看懂LR寄存器及 BX LR 指令的两种用途

扫描二维码关注公众号,回复: 11542776 查看本文章

B BL BLX BX详解

; User Initial Stack & Heap

                IF      :DEF:__MICROLIB
                
                // 判断是否使用 DEF: __MICROLIB (Micro lib);

                EXPORT  __initial_sp    
                
                // 使用的话则将栈顶地址,堆始末地址赋予全局属性,使外部程序可以使用;

                EXPORT  __heap_base
                EXPORT  __heap_limit
                
                ELSE    // 如果使用默认C库运行时;
                
                IMPORT  __use_two_region_memory

                定义全局标号 __use_two_region_memory;

                EXPORT  __user_initial_stackheap

                // 声明全局标号 __user_initial_stackheap,这样外程序也可调用此标号;
                // 则进行堆栈和堆的赋值,在 __main 函数执行过程中调用;

__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

                // 跳转到LR寄存器里的地址执行;

                ALIGN

                ENDIF

                // END 命令指示汇编器,已到达一个源文件的末尾。

1.5 启动总结

  1. 对栈和堆的大小进行定义;
  2. 在代码区的起始处建立中断向量表,其第一个表项是栈顶地址,第二个表项是复位中断服务入口地址;
  3. 在复位中断服务程序中跳转 C / C++ 标准实时库的 __main函数。

假设 LPC1114 被设置为从内部FLASH启动中断向量表起始地位为 0x0000000,则栈顶地址存放于 0x0000000处,而复位中断服务入口地址存放于0x8000004处。当 LPC1114 遇到复位信号后,则从 0x0000004 处取出复位中断服务入口地址继而执行复位中断服务程序,然后跳转 __main 函数,最后来到 C 的程序中。

二、.c 启动进程

SystemInit()函数详解

在运行了 .s 启动文件之后,系统基本的中断进程就被声明好了,接着这两个函数成为了重点关注对象:SystemInit 和 __main。

; Reset Handler

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  SystemInit
                IMPORT  __main
                LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP

main 函数目前不作分析,这里只分析 SystemInit 的实现过程。

2.1 SystemInit

void SystemInit (void)
{
    uint32_t i;
    U32 _i = 0;	
	
    LPC_SYSCON->PDRUNCFG     &= ~(1 << 5);          /* Power-up System Osc      */
    
    // 掉电配置寄存器 (PDRUNCFG, 地址 0x4004 8238);
    // 给系统振荡器上电;

    LPC_SYSCON->SYSOSCCTRL    = SYSOSCCTRL_Val;

    // 系统振荡器控制寄存器 (SYSOSCCTRL, 地址 0x4004 8020);
    // #define SYSOSCCTRL_Val        0x00000000
    // BYPASS       系统振荡器不被旁路;
    // FREQRANGE    为低功耗振荡器确定频率范围,1 - 20 MHz 频率范围;

    for (i = 0; i < 200; i++) __NOP();  // 使用 CMSIS 内部函数来等待一段时间;

    LPC_SYSCON->SYSPLLCLKSEL  = SYSPLLCLKSEL_Val;   /* Select PLL Input         */

    // 系统 PLL 时钟源选择寄存器 (SYSPLLCLKSEL, 地址 0x4004 8040);
    // #define SYSPLLCLKSEL_Val      0x00000000
    // 系统 PLL 时钟源选择:
    // SEL  系统 PLL 时钟源 :IRC 振荡器;

    LPC_SYSCON->SYSPLLCLKUEN  = 0x01;               /* Update Clock Source      */
    
    // 系统 PLL 时钟源更新允许寄存器 (SYSPLLCLKUEN, 地址 0x4004 8044);
    // 该寄存器在 SYSPLLCLKSEL 寄存器被写入以后使用新的输入时钟来更新系统 PLL 时钟源。
    // 为了使更新生效,需要先往 SYSPLLUEN 寄存器中先写 0 再写 1。
    // 更新时钟源;
    
    LPC_SYSCON->SYSPLLCLKUEN  = 0x00;               /* Toggle Update Register   */

    // 无变化;

    LPC_SYSCON->SYSPLLCLKUEN  = 0x01;

    while (!(LPC_SYSCON->SYSPLLCLKUEN & 0x01));     /* Wait Until Updated       */
        
    // 等待时钟源更新完成;
    

    LPC_SYSCON->SYSPLLCTRL    = SYSPLLCTRL_Val;     /* System PLL Setup         */

    // 系统 PLL 控制寄存器 (SYSPLLCTRL, 地址 0x4004 8008);
    // #define SYSPLLCTRL_Val        0x00000023;
    // 0010 0011
    // 0-4  MSEL     反馈分频器的值  3
    // 5-6  PSEL     后分频器值 P    1
    // 分频器的值 M 是 MSEL 的值 + 1,即为 4;

    LPC_SYSCON->PDRUNCFG     &= ~(1 << 7);          /* Power-up SYSPLL          */
	// SYSPLL_PD 系统 PLL 上电;

    while (!(LPC_SYSCON->SYSPLLSTAT & 0x01))	    /* Wait Until PLL Locked    */
    // 系统 PLL 状态寄存器 (SYSPLLSTAT, 地址 0x4004 800C);
    // 0    LOCK     PLL 锁定状态;
    // 等待系统 PLL 的时钟被锁定;

    LPC_SYSCON->MAINCLKSEL    = MAINCLKSEL_Val;     /* Select PLL Clock Output  */

    // #define MAINCLKSEL_Val        0x00000003
    // 主时钟源选择寄存器 (MAINCLKSEL, 地址 0x4004 8070);
    // 0-1  SEL 主时钟的时钟源  系统 PLL 的输出时钟

    LPC_SYSCON->MAINCLKUEN    = 0x01;               /* Update MCLK Clock Source */

    // 主时钟源更新允许寄存器 (MAINCLKUEN, 地址 0x4004 8074);
    // 0    ENA 允许主时钟源更新
    // MAINCLKUEN 寄存器必须从低切换到高才能使更新生效;

    LPC_SYSCON->MAINCLKUEN    = 0x00;               /* Toggle Update Register   */
    LPC_SYSCON->MAINCLKUEN    = 0x01;

    while (!(LPC_SYSCON->MAINCLKUEN & 0x01))        /* Wait Until Updated       */

    // 等待主时钟源更新完成;

    LPC_SYSCON->SYSAHBCLKDIV  = SYSAHBCLKDIV_Val;
    
    // 系统 AHB 时钟分频寄存器 (SYSAHBCLKDIV, 地址 0x4004 8078);
    
    LPC_SYSCON->SYSAHBCLKCTRL = AHBCLKCTRL_Val;
    
    // 系统 AHB 时钟控制寄存器 (SYSAHBCLKCTRL, 地址 0x4004 8080);
    // AHBCLKCTRL 寄存器用于允许时钟提供给独立的系统和外设模块

    LPC_SYSCON->SSP0CLKDIV    = SSP0CLKDIV_Val;
    LPC_SYSCON->UARTCLKDIV    = UARTCLKDIV_Val;
    LPC_SYSCON->SSP1CLKDIV    = SSP1CLKDIV_Val;

    // #define SYSAHBCLKDIV_Val      0x00000001
    // #define AHBCLKCTRL_Val        0x0001005F
    // #define SSP0CLKDIV_Val        0x00000001
    // #define UARTCLKDIV_Val        0x00000001
    // #define SSP1CLKDIV_Val        0x00000001

    SystemFrequencyUpdate();                      /* Get Core Clock Frequency      */
}

2.2 SystemFrequencyUpdate

在这里插入图片描述

void SystemFrequencyUpdate (void)               /* Get Core Clock Frequency      */
{
    SystemFrequency = __IRC_OSC_CLK * ((LPC_SYSCON->SYSPLLCTRL & 0x01F) + 1);

    // #define __IRC_OSC_CLK    (12000000UL)   /* Internal RC oscillator frequency */
    // #define SYSPLLCTRL_Val   0x00000023
    // LPC_SYSCON->SYSPLLCTRL = SYSPLLCTRL_Val
    // = 12M * (4 + 1)

    SystemFrequency /= LPC_SYSCON->SYSAHBCLKDIV;  

    // 48,000,000
}

三、编译后的代码容量分析

3.1 编译信息分析

compiling system_LPC11xx.c...
linking...
Program Size: Code=19636 RO-data=1000 RW-data=200 ZI-data=6320  
FromELF: creating hex file...
After Build - User command #1: D:\KEIL5\ARM\ARMCC\bin\fromelf.exe --bin -o ./RTU.bin C:\Users\xiechen\Documents\RTU\RTU_code\RTU_ADS1115_IO\platform\LPC1114FBD48_302\project\Obj\RTU.axf
".\Obj\RTU.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed:  00:00:02

Program Size: Code=19636 RO-data=1000 RW-data=200 ZI-data=6320

项目名称 说明
Code 为程序代码部分;
RO-data 表示程序定义的常量 const temp;
RW-data 表示已初始化的全局变量;
ZI-data 表示未初始化的全局变量;

它们存放的位置关系如下。

项目名称 存放位置
Code, RO-data,RW-data Flash
RW-data, ZIdata RAM

在这里插入图片描述

根据 《LPC1114用户手册(英文)》第 16 页的 Table 4. LPC111x memory configuration,使用的 CPU 是 LPC1114F/302,Flash 有 32 kB,RAM 有 8 kB。

3.2 map 文件分析

3.2.1 存储分析

在使用 keil 进行编译后,查看其 map 文件。在路径:C:\Users\xiechen\Documents\0.AI\RTU_code\学习代码\platform\LPC1114FBD48_302\project\Obj 下。

Total RO  Size (Code + RO Data)                20636 (  20.15kB)
Total RW  Size (RW Data + ZI Data)              6520 (   6.37kB)
Total ROM Size (Code + RO Data + RW Data)      20836 (  20.35kB)

工程代码使用的 Flash 和 RAM 均小于 CPU 的额定大小,系统工作正常。

3.2.2 堆栈分析

stm32学习笔记之堆栈的理解

Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object
0x100008fc        -       0x00000024   Zero   RW          375    .bss                exadc_aisample.o
0x10000920        -       0x00000190   Zero   RW          547    .bss                usart.o
0x10000ab0        -       0x00000038   Zero   RW          622    .bss                scheduler.o
0x10000ae8        -       0x000009c8   Zero   RW          667    .bss                usartqueue.o
0x100014b0        -       0x00000084   Zero   RW          740    .bss                ads1115.o
0x10001534        -       0x00000040   Zero   RW          788    .bss                flash.o
0x10001574   0x00005164   0x00000004   PAD
0x10001578        -       0x00000000   Zero   RW          810    HEAP                startup_lpc11xx.o
0x10001578        -       0x00000400   Zero   RW          809    STACK               startup_lpc11xx.o

.bass 为堆数据,STACK 为栈数据。栈:向低地址扩展;堆:向高地址扩展。

3.2.2.1 栈

存函数的临时变量,即局部变量,函数返回时随时有可能被其他函数栈用。所以栈是一种分时轮流使用的存储区,编译器里定义的 Stack_Size,是为了限定函数的局部数据活动的范围,操过这么范围有可以跑飞,也就是栈溢出。

Stack_Size 不影响 Hex,更不影响 Hex 怎么运行的,只是在 Debug 调试时会提示错。如果写代码在函数里定义一个大数组 int buf[8192],栈要是小于 8192 是直接跑飞,进入硬件错误中断。

3.2.2.2 堆

存的是全局变量,这变量理论上是所有函数都可以访问的,全局变量有的有初始值,但这个值不是存在 RAM 里的,是存在 Hex 里,下载到 Flash 里,上电由代码(编译器生成的汇编代码)搬到 RAM 去。

猜你喜欢

转载自blog.csdn.net/JeromeCoco/article/details/107690892