首先关注 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
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
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;
; 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 默认是字对齐方式;
一文看懂LR寄存器及 BX LR 指令的两种用途
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 命令指示汇编器,已到达一个源文件的末尾。
假设 LPC1114 被设置为从内部FLASH启动中断向量表起始地位为 0x0000000,则栈顶地址存放于 0x0000000处,而复位中断服务入口地址存放于0x8000004处。当 LPC1114 遇到复位信号后,则从 0x0000004 处取出复位中断服务入口地址继而执行复位中断服务程序,然后跳转 __main 函数,最后来到 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 的实现过程。
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 */
}
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
}
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。
在使用 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 的额定大小,系统工作正常。
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 为栈数据。栈:向低地址扩展;堆:向高地址扩展。
存函数的临时变量,即局部变量,函数返回时随时有可能被其他函数栈用。所以栈是一种分时轮流使用的存储区,编译器里定义的 Stack_Size,是为了限定函数的局部数据活动的范围,操过这么范围有可以跑飞,也就是栈溢出。
Stack_Size 不影响 Hex,更不影响 Hex 怎么运行的,只是在 Debug 调试时会提示错。如果写代码在函数里定义一个大数组 int buf[8192],栈要是小于 8192 是直接跑飞,进入硬件错误中断。
存的是全局变量,这变量理论上是所有函数都可以访问的,全局变量有的有初始值,但这个值不是存在 RAM 里的,是存在 Hex 里,下载到 Flash 里,上电由代码(编译器生成的汇编代码)搬到 RAM 去。