djyos 发表于 2009-8-14 12:06:09

stm32移植日志之三

看本文请参考《都江堰操作系统与嵌入式系统设计》第15章,该书可在www.djyos.com下载。
    做了许多功课以后,终于开始写代码了。
    移植操作系统,首先修改的永远是启动代码,启动代码是为后续程序准备最起码的运行环境的,每个OS都要有自己独特的要求,像IAR、MDK之类的工具自带的启动代码一般是不能用了,你把它翻译到gcc汇编也没用。Gcc自备的启动代码crt0.s呢,一般来说也没有用了,启动代码必须自己写,这点是不能偷懒的。
djyos过去的版本中,总有洋洋数百行的启动代码initcpu.s,令人望而生畏!不过,cm3是单片机级别的cpu,需要用汇编设置的东西比较少,initcpu.s只有了了几行。initcpu.s的内容如下:
    .extern   handler_msp   @ld文件中定义的主栈指针初始化值
    .extern   thread_psp      @ld文件中定义的线程栈指针初始化值
    .extern   cpu_init      @c写的cpu初始化函数
    .text      @代码段开始
    .global _start    @把符号_start输出给ld文件使用,以便在elf文件
         @中把_start标识为程序入口地址
    .code 16   @thumb2汇编
    .syntax unified             @使用cm3的16位和32位统一汇编架构
    .section reset_vect,"x"   @初始化代码段

_start:
    .word handler_msp
    .word reset_start            
    .word rst_fault_handler   @nmi_fault_handler               
    .word rst_fault_handler   @hard_fault_handler               
rst_fault_handler:
    b   .

reset_start:
    CPSID   I               @PRIMASK=1,关中断
    CPSID   F               @FAULTMASK=1,关异常
    ldr   r0,=thread_psp
    msr   psp,r0   @初始化线程栈指针,主栈指针是自动初始化的
   
    mov   r0,#0x20000000 @下面初始化ram中的中断向量表中系统异常部分。
    ldr   r1,=handler_msp
    str      r1,
    ldr   r1,=reset_start
    str   r1,
    ldr   r1,=rst_fault_handler
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
    str   r1,
   
    ldr   r1,=0xe000ed08             //NVIC.SCB.VTOR地址
    str   r0,
   
    bl      cpu_init      @ 用C实现的cpu初始化部分
    bl      load_preload   @开始操作系统预加载
    前几句跟gcc汇编的语法相关,注释很详细,这里就不多说明了。
    接下来三行放置了主栈指针地址,复位向量、nmi 和hardfault向量,这也是cortex-m3要求的,为的是能捕获启动时发生的nmi和hard异常。我们看到,nmi上只有一条死循环指令,在系统初始化完成之前,先给它搭一个临时避难所。cm3在复位后,nmi和hard异常是出于允许状态的,其异常向量必须在启动前准备好,否则如果在启动时发生nmi和hard异常的话,cpu将无所适从。至于除这两个异常以外的其他异常,我找了许多资料,都没写复位后的使能状态,依经验,暂且当他们是禁止的,等程序运行起来后再测试其使能状态,若使能,则必须向nmi和hard异常一样,要事先为他们准备好异常向量表。
    复位后,cpu实际上市从reset_start处开始执行指令的,首先关闭了所有中断和硬fault。接下来两句这里初始化了线程栈指针psp,djyos的异常(包括中断)用msp,线程用psp,就是在这里初始化的。接下来初始化了一个异常向量表,把所有系统异常塞进前面说说的临时避难所中。为什么要这样做呢?这个跟djyos的启动过程是有关的,djyos允许用在最紧急的控制系统中,在系统启动前,就加载了中断系统,并且允许用户在OS系统启动过程中用中断执行最紧急的控制。这使得用户代码可以在系统复位后几个微秒内获得关键控制权。这是djyos对用户的特别关怀,任何系统都可能因bug而崩溃,崩溃导致重新启动时,系统会短暂失效,用户肯定希望关键功能失效时间越短越好,djyos的危急代码加载和实时中断机制就是为这个量身定做的。初始化中断系统会修改PRIMASK和FAULTMASK寄存器,系统异常将被使能,如果系统从灾难中恢复,有些系统异常可能出于使能状态,而此时OS还没启动,异常处理的代码也还没有加载,只能先给他们临时安置在紧急避难所中。
    接下来就进入C语言环境继续初始化工作,完成后便开始系统加载了。cpu_init的代码如下:
void cpu_init(void)
{
    switch(tg_scb_reg.CPUID)
    {
      case cn_revision_r0p0:break;    //市场没有版本0的芯片
      case cn_revision_r1p0:
            tg_scb_reg.CCR |= bm_scb_ccr_stkalign;
            break;
      case cn_revision_r1p1:
            tg_scb_reg.CCR |= bm_scb_ccr_stkalign;
            break;
      case cn_revision_r2p0:break;    //好像没什么要做的
    }
    if((tg_rcc_reg.CR & cn_cr_check_mask) == cn_cr_check)   //检查时钟状态
    {
      if((tg_rcc_reg.CFGR & cn_cfgr_check_mask) == cn_cfgr_check)
            return ;    //时钟已经初始化好
    }
    //开始初始化时钟
    //step1:复位时钟控制寄存器
    tg_rcc_reg.CR |= (uint32_t)0x00000001;
    // 复位 SW, HPRE, PPRE1, PPRE2, ADCPRE MCO 位
    tg_rcc_reg.CFGR &= (uint32_t)0xF8FF0000;
    // 复位 HSEON, CSSON and PLLON 位
    tg_rcc_reg.CR &= (uint32_t)0xFEF6FFFF;
    // 复位 HSEBYP 位
    tg_rcc_reg.CR &= (uint32_t)0xFFFBFFFF;
    // 复位 PLLSRC, PLLXTPRE, PLLMUL and USBPRE 位
    tg_rcc_reg.CFGR &= (uint32_t)0xFF80FFFF;
    // 禁止所有中断
    tg_rcc_reg.CIR = 0x00000000;

    //step2:设置各时钟控制位以及倍频、分频值
    tg_rcc_reg.CFGR = cn_cfgr_set+(111<<24);   // set clock configuration register
    tg_rcc_reg.CR   = cn_cr_set;   // set clock control register

    while(bb_rcc_cr_hserdy ==0);
    while(bb_rcc_cr_pllrdy ==0);
}
    这里首先检查cpu版本,根据cpu版本做了相应的设置,代码很简单。
    接下来是时钟初始化,单片机经常用在实时控制中,这种系统要求快速启动,尤其是系统灾难恢复时,更加要求缩短系统失效时间。有硬件经验的人都知道,晶体起振和PLL稳定是需要比较长的时间的,在灾难恢复中,灾难事件往往没有破坏系统的时钟部分,这种情况就无需重新初始化时钟,以节约时间。所以在代码的开头,我们首先判断时钟状态,也就是判断RCC寄存器组的CR和CFGR两个寄存器中跟主时钟相关的成员的值是否等于我们的设定值。如果正常的话,则直接返回,否则把他们复位后再重新初始化。

icer1 发表于 2009-9-9 15:23:07

专门过来顶楼主的……
页: [1]
查看完整版本: stm32移植日志之三