软件环境:windows7旗舰版,IAR V6105(EWARM-EV-WEB-6105) ARM芯片:飞思卡尔K60N512VMD100 (cortex-m4核心) 示例程序:飞思卡尔官方的 KINETIS512_SC ======================
最近分析了一下飞思卡尔官方提供的k60系列demo程序在IAR上的启动流程,现写一下笔记,以备以后参考。先看一下K60N512VMD100内部存储器的分布情况,飞思卡尔K60N512VMD100有512K的flash和128k的SRAM.其中:
Flash地址空间: 0x00000000--0x00080000,共512k SRAM地址空间: SRAM1 0x1FFF0000--0x20000000 64k SRAM2 0x20000000--0x20010000 64k 总共的SRAM大小是128k
我要在RAM中调试代码,下面以代码的执行过程为顺序分析一下启动流程。
首先看一下源文件中提供的128KB_Ram.icf文件。*.icf文件是IAR中的分散描述文件,相当于ADS中的*.src文件或keil中的*.sct文件或GNU中的*.lds链接脚本文件。 这个文件中前面部分是各个变量的定义,关键看后面部分: - /*128KB_Ram.icf后面部分*/
- ***********
-
place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
-
place at address mem:__code_start__ { readonly section .noinit };
-
-
place in RAM_region { readonly, block CodeRelocate };
-
-
place in RAM_region { readwrite, block CodeRelocateRam,
-
block CSTACK, block HEAP };
-
************
①place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec } 这段代码表示要把.intvec代码段中的只读部分放在存储空间(mem,前面已定义的名称)中__ICFEDIT_intvec_start__ 地址上,前面部分已经定义__ICFEDIT_intvec_start__=0x1fff0000,是SRAM的起始地址。也就是先把向量表放到内存中的最前面。 .intvec 这个段是在vectors.c文件中出现的, - /*vectors.c片段*/
- typedef void (*vector_entry)(void);
-
- #pragma location = ".intvec"
-
const vector_entry __vector_table[] = //@ ".intvec" =
-
{
-
VECTOR_000, /* Initial SP */
-
VECTOR_001, /* Initial PC */
-
VECTOR_002,
-
VECTOR_003,
- ......(中间省略)
-
VECTOR_254,
-
VECTOR_255,
-
CONFIG_1,
-
CONFIG_2,
-
CONFIG_3,
-
CONFIG_4,
-
-
};
从源文件中可以看到这里定义了一个向量表__vector_table(前面的const 很重要不能省,这样才能保证向量表是只读的),向量表中的每一项都是一个指向函数的指针,这里总共有256+4=260个指针,所以占据空间为260*4=1040=0x410. 所以SRAM空间的前0x410的空间已经被向量表占据。即占据了0x1fff0000--0x1fff0410.
②place at address mem:__code_start__ { readonly section .noinit } 这段代码表示要把 .noinit段中的只读部分放到地址空间 __code_start__ 开始的地址上,前面有定义 __code_start__= 0x1fff0410 ,也就是把 .noinit段放到0x1fff0410开始的地址上。所以在内存中代码就连续了,先是向量表,接着的是.noinitd 段。 .noinit 段在crt0.s汇编文件中出现: - SECTION .noinit : CODE
- EXPORT __startup
-
__startup
-
- MOV r0,#0 ; Initialize the GPRs
- MOV r1,#0
- MOV r2,#0
- MOV r3,#0
- MOV r4,#0
- MOV r5,#0
- MOV r6,#0
- MOV r7,#0
- MOV r8,#0
- MOV r9,#0
- MOV r10,#0
- MOV r11,#0
- MOV r12,#0
- CPSIE i ; Unmask interrupts
- import start
- BL start ; call the C code
-
__done
- B __done
-
-
- END
这段代码算是芯片复位后执行的第一段代码(如果没有其他异常的话)。作为一个通常的规则,推荐先把通用寄存器(R0-R12)清零。然后是使能中断,跳转到start标号(或函数)处继续执行。
========================
在start.c文件中找到了start函数:
- /*start.c片段*/
-
void start(void)
-
{
-
/* Disable the watchdog timer */
-
wdog_disable();
-
-
/* Copy any vector or data sections that need to be in RAM */
-
common_startup();
-
-
/* Perform processor initialization */
-
sysinit();
-
-
printf("\n\n");
-
-
/* Determine the last cause(s) of reset */
-
if (MC_SRSH & MC_SRSH_SW_MASK)
-
printf("Software Reset\n");
-
if (MC_SRSH & MC_SRSH_LOCKUP_MASK)
-
printf("Core Lockup Event Reset\n");
-
if (MC_SRSH & MC_SRSH_JTAG_MASK)
-
printf("JTAG Reset\n");
-
-
if (MC_SRSL & MC_SRSL_POR_MASK)
-
printf("Power-on Reset\n");
-
if (MC_SRSL & MC_SRSL_PIN_MASK)
-
printf("External Pin Reset\n");
-
if (MC_SRSL & MC_SRSL_COP_MASK)
-
printf("Watchdog(COP) Reset\n");
-
if (MC_SRSL & MC_SRSL_LOC_MASK)
-
printf("Loss of Clock Reset\n");
-
if (MC_SRSL & MC_SRSL_LVD_MASK)
-
printf("Low-voltage Detect Reset\n");
-
if (MC_SRSL & MC_SRSL_WAKEUP_MASK)
-
printf("LLWU Reset\n");
-
-
-
/* Determine specific Kinetis device and revision */
-
cpu_identify();
-
-
/* Jump to main process */
-
main();
-
-
/* No actions to perform after this so wait forever */
-
while(1);
-
}
start函数中,首先执行 wdog_disable()函数来禁用看门狗,然后调用 common_startup()函数初始化RAM(复制向量表、清零.bss段等,为C语言运行环境做准备),接着执行sysinit()函数初始化芯片(时钟、用到的外设等)。下面依次分析这3个函数。 ①wdog_disable() 对系统的设定无非是对各个寄存器值的修改。wdog_disable()函数在wdog.c文件中 - /*wdog.c片段*/
- void wdog_disable(void)
-
{
-
/* First unlock the watchdog so that we can write to registers */
-
wdog_unlock();
-
-
/* Clear the WDOGEN bit to disable the watchdog */
-
WDOG_STCTRLH &= ~WDOG_STCTRLH_WDOGEN_MASK;
-
}
-
void wdog_unlock(void)
-
{
-
/* NOTE: DO NOT SINGLE STEP THROUGH THIS */
-
/* There are timing requirements for the execution of the unlock. If
-
* you single step through the code you will cause the CPU to reset.
-
*/
-
-
/* This sequence must execute within 20 clock cycles, so disable
-
* interrupts will keep the code atomic and ensure the timing.
-
*/
-
DisableInterrupts;
-
-
/* Write 0xC520 to the unlock register */
-
WDOG_UNLOCK = 0xC520;
-
-
/* Followed by 0xD928 to complete the unlock */
-
WDOG_UNLOCK = 0xD928;
-
-
/* Re-enable interrupts now that we are done */
-
EnableInterrupts;
-
}
禁用看门狗流程很简单:先是解锁寄存器,然后更改看门狗寄存器里面的值来禁用看门狗。解锁看门狗寄存器:向解锁寄存器里连续写入0xC520和0xD928,两次写入的时间必须小于20个时钟周期。所以在解锁过程中不能单步运行,期间也不能被中断打断,解锁函数是 wdog_unlock()。上面DisableInterrupts和EnableInterrupts已经在arm_cm4.h中定义过: #define DisableInterrupts asm(" CPSID i"); #define EnableInterrupts asm(" CPSIE i"); 解锁看门狗寄存器后,向看门狗寄存器里写入适当的值就可以禁用看门狗了。 也就是把WDOG_STCTRLH 寄存器(地址是0x40052000)的第0位置0.
②common_startup 初始化RAM(复制向量表、清零.bss段等,为C语言运行环境做准备)。 - 1 /* File: startup.c */
-
2 #include "common.h"
-
3 #pragma section = ".data"
-
4 #pragma section = ".data_init"
-
5 #pragma section = ".bss"
-
6 #pragma section = "CodeRelocate"
-
7 #pragma section = "CodeRelocateRam"
-
-
8 /********************************************************************/
-
9 void
-
10 common_startup(void)
-
11 {
-
-
12 /* Declare a counter we'll use in all of the copy loops */
-
13 uint32 n;
-
-
14 /* Declare pointers for various data sections. These pointers
-
15 * are initialized using values pulled in from the linker file
-
16 */
-
17 uint8 * data_ram, * data_rom, * data_rom_end;
-
18 uint8 * bss_start, * bss_end;
-
-
19 /* Addresses for VECTOR_TABLE and VECTOR_RAM come from the linker file */
-
20 extern uint32 __VECTOR_TABLE[];
-
21 extern uint32 __VECTOR_RAM[];
-
-
22 /* Copy the vector table to RAM */
-
23 if (__VECTOR_RAM != __VECTOR_TABLE)
-
24 {
-
25 for (n = 0; n < 0x410; n++)
-
26 __VECTOR_RAM[n] = __VECTOR_TABLE[n];
-
27 }
-
-
28 /* Point the VTOR to the new copy of the vector table */
-
29 write_vtor((uint32)__VECTOR_RAM);
-
-
30 /* Get the addresses for the .data section (initialized data section) */
-
31 data_ram = __section_begin(".data");
-
32 data_rom = __section_begin(".data_init");
-
33 data_rom_end = __section_end(".data_init");
-
34 n = data_rom_end - data_rom;
-
-
35 /* Copy initialized data from ROM to RAM */
-
36 while (n--)
-
37 *data_ram++ = *data_rom++;
-
-
38 /* Get the addresses for the .bss section (zero-initialized data) */
-
39 bss_start = __section_begin(".bss");
-
40 bss_end = __section_end(".bss");
-
-
41 /* Clear the zero-initialized data section */
-
42 n = bss_end - bss_start;
-
43 while(n--)
-
44 *bss_start++ = 0;
-
-
45 /* Get addresses for any code sections that need to be copied from ROM to RAM.
-
46 * The IAR tools have a predefined keyword that can be used to mark individual
-
47 * functions for execution from RAM. Add "__ramfunc" before the return type in
-
48 * the function prototype for any routines you need to execute from RAM instead
-
49 * of ROM. ex: __ramfunc void foo(void);
-
50 */
-
51 uint8* code_relocate_ram = __section_begin("CodeRelocateRam");
-
52 uint8* code_relocate = __section_begin("CodeRelocate");
-
53 uint8* code_relocate_end = __section_end("CodeRelocate");
-
-
54 /* Copy functions from ROM to RAM */
-
55 n = code_relocate_end - code_relocate;
-
56 while (n--)
-
57 *code_relocate_ram++ = *code_relocate++;
-
58 }
在IAR中, #pragma section="NAME" [align] 用来在C语言中指定一个名称是NAME的段,align指定对齐方式。指定的段可以被段操作符来引用,段操作符包括 __section_begin, __section_end, 和 __section_size. 个人理解.date、.date_init和.bss应该是IAR中保留的段名称,.date代表数据段中的常量,.date_init代表数据段中已初始化的变量,.bss代表未初始化的变量(zero)。 上面代码中,先是指定了5个不同名称的段(前3个是保留段名称,代表这些段是从这里开始的),CodeRelocate和CodeRelocateRam是在*.icf文件中定义的块(block): define block CodeRelocate { section .textrw_init };
define block CodeRelocateRam { section .textrw }; quote: The __ramfunc keyword makes a function execute in RAM. Two code sections will be created: one for the RAM execution (.textrw), and one for the ROM initialization (.textrw_init).
外部变量引用 extern uint32 __VECTOR_TABLE[];
extern uint32 __VECTOR_RAM[]; 来自IAR的链接文件(.icf),在.icf文件中已经定义了变量 __VECTOR_TABLE 和 __VECTOR_RAM 其值都是0x1fff0000."Copy the vector table to RAM"这段代码进行判断,如果向量表不在RAM中,则把向量表拷贝到RAM开始的地址上,这里由于在RAM中调试,代码是直接下载到RAM中的,所以不用拷贝。 向量表已经在RAM中了,接下来要重定向向量表,以便在发生异常时到RAM中取得异常入口地址(默认情况下是在0x0取)。 write_vtor((uint32)__VECTOR_RAM) 这个函数用来写向量表偏移寄存器(VTOR,地址0xE000_ED08),这里写入的是RAM起始地址0x1FFF0000。注意这个地址是有要求的,并不是所有地址都能作为向量表起始地址,0x1FFF0000满足要求(这个要求就是:必须先求出系统中共有多少个向量,再把这个数字向上增大到是 2 的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有 32 个中断,则共有 32+16(系统异常)=48个向量,向上增大到 2的整次幂后值为 64,因此地址地址必须能被 64*4=256整除,从而合法的起始地址可以是:0x0,0x100,0x200 等----参见ARM Contex-M3权威指南)。另外,如果向量表在RAM区(相对于code区),需把bit[29]置位,这里0x1FFF0000也满足要求。 后面的代码是拷贝数据到RAM中,搭建好C语言运行环境。
③sysinit()函数 (待续。。。) |