jicheng0622

【原创】从零入手Kinetis系统开发(四)之启动代码分析

0
阅读(11430)

    在对K60开发应用程序或编写硬件模块的驱动程序之前,我们需要对其启动流程有所了解。也许对一些简单的8位或者16位单片机进行系统开发时,我们往往不用去关心其启动代码部分,一般都是直接使用开发环境默认给出的启动代码,没必要去改。但是对于像ARM这类的复杂的32位片上系统来说,在启动代码部分,需要通过软件对一些硬件资源进行配置和设置一定的工作状态,这样我们就不得不去认真的了解它了。下面就以飞思卡尔tower系统上的片子MK60N512VMD100为例分析官方提供的Demo程序的启动流程。

    简单的概括下K60的启动流程,主要分为四个部分(咳咳,我自作主张的分的,大家不要拍砖啊,呵呵):

    (1)初始化K60的通用寄存器(R0~R12),使能全局中断,跳转到start函数;

    (2)关闭看门狗,在调试阶段一般关闭它,毕竟老是频繁的喂狗也是挺麻烦的;

    (3)复制中断向量表、初始化的数据和以__ramfunc声明的子函数到RAM区(一定程度上提高了代码执行速度),并清零零初始化数据区;

    (4)初始化系统时钟;

    在逐步分解介绍之前,必须要首先了解下*.icf文件,我默认采用128KB_Pflash配置模式,所以这里打开128KB_Pflash.icf文件,由于这个代码较多,所以就挑重要的说了:

    /*******************************用到的,捡重要的说,可能不是挨着的语句*******************************

     define symbol __ICFEDIT_intvec_start__ = 0x00000000;//这个是声明,中断向量表的默认存放地址

     define symbol __code_start__ = 0x00000410;//声明程序代码开始地址

     define exported symbol __VECTOR_TABLE = 0x00000000;//默认的中断向量表存放地址=楼上,呵呵

     define exported symbol __VECTOR_RAM = 0x1fff8000;//需要复制到RAM去的中断向量表的地址 
 
 
     place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };// 把.intvec代码段中的只读部分放在存储空间mem中的__ICFEDIT_intvec_start__ 地址上
 
      place at address mem:__code_start__ { readonly section .noinit };//把 .noinit段中的只读部分放到地址空间 __code_start__开始的地址上

    ************************************************************************************************************/

.intvec 这个段可以在vectors.c文件中找得到,上图了又:

这里可以看到,系统默认是把中断向量表放到了.intvec段里,由上面可以看到也就是默认放到了0x00000000地址。

(1)下面逐步分解,其中第一步可以在crt0.s文件里找到,如下:

   ;AREA   Crt0, CODE, READONLY      ; name this block of code

  SECTION .noinit : CODE      ; 下面这部分汇编代码放到.noinit段里地址为0x00000410(从上面分析可以看到)
 
        EXPORT  __startup
__startup                                   ;  __startup标号,其实这个是复位向量,在中断向量表里可以查到为vector001
          MOV     r0,#0                   ; 清零所有通用寄存器
           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                       ; 打开全局中断
        import start
        BL      start                       ; 跳转到start的C函数
__done
        B       __done
        END
可能有些人会迷惑为什么CPU会复位之后从__startup标号开始执行,这里我以CPU的角度走一遍这个流程,CPU上电复位或者其他方式复位之后,它会首先从0x00000000地址读取堆栈指针到SP,然后再从0x00000004地址(注意地址总线为32位,所以每次跳4个字节才能读取下个地址)读取程序指针到PC,最后CPU就跳到PC指针所指向的地址开始执行程序了,至于从0x00000004读取的PC指针指向的地址是哪呢,那就是上面所说的__startup标号指向的程序地址了,这个我们可以在vector.h文件里找到,如下:
(2)接下来跳到了start.c文件的start函数,首先执行关闭看门狗功能,也就是第二步了:
/* Disable the watchdog timer */
wdog_disable();//关闭看门狗功能,打开wdog.c文件,找到wdog_disable函数,可以找到
 
 /********************************************************************************************************
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)
{
 
        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个时钟周期,期间不允许中断),然后禁止看门狗使能就OK了。
(3)关闭看门狗之后,进入第三步,复制向量表:
 /* Copy any vector or data sections that need to be in RAM */
common_startup();//复制向量表、初始化的数据和以__ramfunc声明的子函数到RAM区,打开startup.c文件找到该函数
 
/********************************************************************************************************
void  common_startup(void)
{
    /* Declare a counter we'll use in all of the copy loops */
    uint32 n;
 
    /* 这两个全局变量在上面.icf文件里可以找到,里面定义了它们的地址*/  
    extern uint32 __VECTOR_TABLE[];
    extern uint32 __VECTOR_RAM[];
 
    /* Copy the vector table to RAM */
    /*如果它们地址不一样(我用的是128KB_Pflash所以是不一样的),则复制flash内的向量表到RAM区得向量表*/
    if (__VECTOR_RAM != __VECTOR_TABLE)    
    {
        for (n = 0; n < 0x410; n++)
            __VECTOR_RAM[n] = __VECTOR_TABLE[n];
    }
    /* Point the VTOR to the new copy of the vector table */
    /*当然单单复制到RAM区不行,还需要告诉系统我把向量表地址改变了,还需要写VTOR*/
    write_vtor((uint32)__VECTOR_RAM);    
    
   /*下面就是复制初始化数据区到RAM了*/
    /* Get the addresses for the .data section (initialized data section) */
    uint8* data_ram = __section_begin(".data");
    uint8* data_rom = __section_begin(".data_init");
    uint8* data_rom_end = __section_end(".data_init");
    
    /* Copy initialized data from ROM to RAM */
    n = data_rom_end - data_rom;
    while (n--)
      *data_ram++ = *data_rom++;
 
   /*清零零初始化数据区*/
    /* Get the addresses for the .bss section (zero-initialized data) */
    uint8* bss_start = __section_begin(".bss");
    uint8* bss_end = __section_end(".bss");
    
    /* Clear the zero-initialized data section */
    n = bss_end - bss_start;
    while(n--)
      *bss_start++ = 0;    
    
    /*复制以__ramfunc声明的子函数到RAM区(CodeRelocate和CodeRelocateRam这两个都可以在.icf文件里找到)*/
    uint8* code_relocate_ram = __section_begin("CodeRelocateRam");
    uint8* code_relocate = __section_begin("CodeRelocate");
    uint8* code_relocate_end = __section_end("CodeRelocate");
    
    /* Copy functions from ROM to RAM */
    n = code_relocate_end - code_relocate;
    while (n--)
      *code_relocate_ram++ = *code_relocate++;
}
 ************************************************************************************************************************/
(4)下面就进行最后一个步骤了,就是系统时钟的初始化了,这里涉及到MCG的各个模式,就不细说了,以后单独说说。下面简单介绍一下时钟初始化:
 /* Perform processor initialization */
sysinit();                     //打开system.c文件找到该函数
/************************************************************************************************************************
void sysinit (void)
{
        /*使能PORT口的时钟,注意如果需要对port进行相关设置,这些PORT口的时钟必须要打开*/
        SIM_SCGC5 |= (SIM_SCGC5_PORTA_MASK
                      | SIM_SCGC5_PORTB_MASK
                      | SIM_SCGC5_PORTC_MASK
                      | SIM_SCGC5_PORTD_MASK
                      | SIM_SCGC5_PORTE_MASK );
 
  /* Ramp up the system clock */
  core_clk_mhz = pll_init(CORE_CLK_MHZ, REF_CLK); //使用锁相环设置相应的内核时钟,例程中CORE_CLK_MHZ=PLL96,                                                                                                           //REF_CLK=XTAL8,这里不详细介绍了,以后单独介绍
 
  core_clk_khz = core_clk_mhz * 1000;
  periph_clk_khz = core_clk_khz / (((SIM_CLKDIV1 & SIM_CLKDIV1_OUTDIV2_MASK) >> 24)+ 1); //得到外设时钟
 
/*下面两句是初始化总线频率和内核频率到相应的IO上通过设置 pin muxing options,用来验证系统频率是否设置正确,挺有用的*/
  trace_clk_init();
  fb_clk_init();
//..............................此处.省略N个语句,不重要就不提了.........................................
}
***********************************************************************************************************************/
呼呼,到此处,K60的系统启动部分就完工,然后调用main()进入到应用程序主函数,之后就是咱们正常的开发流程了,呵呵,还是挺简单的哈,具体地方还需自己琢磨摸索一下,觉着.icf文件很重要,可以仔细看看研究一下。
    从上面可以看到飞思卡尔官方的启动代码真够麻烦的,各个文件之间跳,估计耐心差的都扛不住啊,呵呵。所以我重新整理简化了一下它的启动代码,以前传到了该网站的飞思卡尔的创意嘉年华的小组讨论区,虽然只是部分,不过有兴趣的可以看看。未完待续~