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鏈接腳本文件。
這個文件中前面部分是各個變量的定義,關鍵看後面部分:
①place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec }
這段代碼表示要把.intvec代碼段中的只讀部分放在存儲空間(mem,前面已定義的名稱)中__ICFEDIT_intvec_start__ 地址上,前面部分已經定義__ICFEDIT_intvec_start__=0x1fff0000,是SRAM的起始地址。也就是先把向量表放到內存中的最前面。 .intvec 這個段是在vectors.c文件中出現的,
從源文件中可以看到這裏定義了一個向量表__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彙編文件中出現:
這段代碼算是芯片復位後執行的第一段代碼(如果沒有其他異常的話)。作爲一個通常的規則,推薦先把通用寄存器(R0-R12)清零。然後是使能中斷,跳轉到start標號(或函數)處繼續執行。
========================
在start.c文件中找到了start函數:
start函數中,首先執行 wdog_disable()函數來禁用看門狗,然後調用 common_startup()函數初始化RAM(複製向量表、清零.bss段等,爲C語言運行環境做準備),接着執行sysinit()函數初始化芯片(時鐘、用到的外設等)。下面依次分析這3個函數。
①wdog_disable()
對系統的設定無非是對各個寄存器值的修改。wdog_disable()函數在wdog.c文件中
禁用看門狗流程很簡單:先是解鎖寄存器,然後更改看門狗寄存器裏面的值來禁用看門狗。解鎖看門狗寄存器:向解鎖寄存器裏連續寫入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語言運行環境做準備)。
在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語言運行環境。
示例程序:飛思卡爾官方的 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鏈接腳本文件。
這個文件中前面部分是各個變量的定義,關鍵看後面部分:
- ***********
- 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文件中出現的,
-
-
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文件中
-
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語言運行環境。