zoukankan      html  css  js  c++  java
  • eCos启动过程详解,基于Cortex-M架构

    mingdu.zheng <at> gmail <dot> com
    http://blog.csdn.net/zoomdy/article/details/12789535

    Cortex-M基础

    Cortex-M将执行模式分成handler模式和thread模式,进入异常或中断处理则进入handler模式,其他情况则为thread模式。Cortex-M有两个运行级别,分别为特权级和用户级(非特权级),handler模式总是运行在特权级,而thread模式可以运行在特权级也可以运行在用户级,这通过CONTROL特殊寄存器控制。Cortex-M的堆栈寄存器SP对应两个物理寄存器MSP和PSP,MSP为主堆栈,PSP为进程堆栈,handler模式总是使用MSP作为堆栈,thread模式可以选择使用MSP或PSP作为堆栈,同样通过CONTROL特殊寄存器控制。

    复位后,Cortex-M进入thread模式、特权级、使用MSP堆栈,并从向量表项0处取SP寄存器值,从向量表项1处取PC寄存器值,然后从PC寄存器值处开始执行,一般情况下默认向量表存储在地址0x00000000处,不同的变种其默认向量表可能不同。

    向量表

    Cortex-M复位后首先从默认向量表处读取SP初始值和PC初始值。为了让Cortex-M复位后立即有可用的复位向量,必须将向量表存储在ROM中,然后在初始化过程可选地将向量表地址映射到其它区域。eCos为Cortex-M准备的向量表主要是存储在RAM中,因为eCos需要支持修改向量表项,但是仍然要为复位动作准备存储在ROM中的向量表,ROM中的向量表仅有两项,分别为SP初始值和PC初始值,刚好满足Cortex-M复位的需求。

    // hal/cortexm/arch/<version>/src/vectors.S:79
            .section        ".vectors","ax"
            .global         hal_vsr_table
    hal_vsr_table_init:
            .long           hal_startup_stack 
            .long           hal_reset_vsr

    (2)通过.section伪指令将向量表存储在.vectors节,.vectors会在target.ld链接控制文件中特殊对待,将其定位到指定的地址,一般情况下为0x00000000,这是Cortex-M复位后的向量表起始地址。

    (5)向量表项0为堆栈地址,Cortex-M的堆栈为递减的满栈,因此这里的值应当为堆栈最高地址+1,例如堆栈空间分配在0x20001000~0x20001FFF,那么这里应当为0x20002000。hal_startup_stack符号是在target.ld中定义的。

    (6)向量表项1为复位向量,存储初始化函数地址,这里为hal_reset_vsr,是在hal_misc.c中定义的C函数。

    伪入口

    在调试过程中,不会产生复位来初始化SP和PC,为了能够配合调试软件模拟复位过程的SP和PC寄存器的初始化,eCos提供了伪入口,伪入口将初始化SP,并跳转到初始化函数hal_reset_vsr。

    // hal/cortexm/arch/<version>/src/vectors.S:100
            .type   reset_vector, %function
    reset_vector:
            ldr     sp,=hal_startup_stack
            b       hal_reset_vsr

    (2)reset_vector是个普通函数,不需要定位到特殊位置,target.ld将reset_vector设置为入口地址,调试器加载程序后会将PC指向reset_vector所在的地址。

    (4)模拟复位时读取向量0初始化SP的过程。

    (5)模拟复位时跳转到向量1所指函数的过程,这里是hal_reset_vsr。

    复位向量hal_reset_vsr

    整个初始化过程主要由hal_reset_vsr函数完成,hal_reset_vsr函数的基本流程:调用平台相同的系统初始化、初始化异常向量表、更改运行模式、初始化数据区、初始化中断向量表、调用变种初始化函数、调用平台初始化函数、调用全局静态对象构造函数、调用cyg_start进入用户代码。

    调用平台相同的系统初始化

    // hal/cortexm/arch/<version>/src/hal_misc.c:131
    void hal_reset_vsr( void )
    {
        hal_system_init();

    (4)hal_system_init函数通常由平台层HAL提供,主要进行基础设施的初始化,例如时钟、外扩存储器、IO配置等,后续的初始化过程将依赖于这些基础设施,因此必须最先被初始化,平台层HAL还提供非基础设置的初始化函数hal_platform_init,hal_platform_init函数将在初始化的末尾调用。

    初始化异常向量表

    // hal/cortexm/arch/<version>/src/hal_misc.c:157
        for( i = 2; i < 15; i++ )
            hal_vsr_table[i] = (CYG_ADDRESS)hal_default_exception_vsr;
    
        hal_vsr_table[CYGNUM_HAL_VECTOR_SERVICE] = (CYG_ADDRESS)hal_default_svc_vsr;
        hal_vsr_table[CYGNUM_HAL_VECTOR_PENDSV] = (CYG_ADDRESS)hal_pendable_svc_vsr;
    
        for( i = CYGNUM_HAL_VECTOR_SYS_TICK ;
             i < CYGNUM_HAL_VSR_MAX;
             i++ )
            hal_vsr_table[i] = (CYG_ADDRESS)hal_default_interrupt_vsr;
    
        HAL_WRITE_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_VTOR,
                          CYGARC_REG_NVIC_VTOR_TBLOFF(0)|
                          CYGARC_REG_NVIC_VTOR_TBLBASE_SRAM );

    (2)初始化异常向量表不包括堆栈指针和复位向量,因为这两项仅在系统复位时需要,而系统复位后默认从ROM中读取,因此这里不需要初始化,此外向量15为SysTick对应的向量,eCos将SysTick作为中断处理而不是异常,这是非常合理的。

    (3)初始化成默认的异常处理函数hal_default_exception_vsr。

    (5,6)SVC和PendSV使用专门的异常处理函数hal_default_svc_vsr和hal_pendable_svc_vsr。

    (8)从SysTick开始的向量作为中断处理,默认处理函数为hal_default_interrupt_vsr。

    (13)将初始化完成的向量表地址写入NVIC的VTOR寄存器,从这一刻起,Cortex-M将从hal_vsr_table读取异常向量。

    更改运行模式

    Cortex-M复位后,进入特权级thread模式,使用MSP堆栈。eCos希望初始化过程以及线程使用PSP,而把MSP留给异常或中断处理单独使用。因此接下来eCos需要进行一次运行模式的改变,将当前使用的SP由MSP更改为PSP,并且关闭中断,eCos初始化过程是要求关中断的。

    // hal/cortexm/arch/<version>/src/hal_misc.c:201
        hal_vsr_table[CYGNUM_HAL_VECTOR_SERVICE] = (CYG_ADDRESS)hal_switch_state_vsr;
        __asm__ volatile( "swi 0" );
        hal_vsr_table[CYGNUM_HAL_VECTOR_SERVICE] = (CYG_ADDRESS)hal_default_svc_vsr;

    (2)将SVC异常向量更改为hal_switch_state_vsr,这是汇编实现的异常处理函数,后面再解释。

    (3)插入swi指令,swi是系统调用指令,执行该指令将产生SVC异常,上一行代码刚刚SVC异常处理向量更改为hal_switch_state_vsr,因此执行swi指令后将会通过异常处理机制跳转到hal_switch_state_vsr执行。

    (4)恢复SVC向量。

    // hal/cortexm/arch/<version>/src/vectors.S:130
            .type   hal_switch_state_vsr, %function
    hal_switch_state_vsr:
    
            mov     r0,#CYGNUM_HAL_CORTEXM_PRIORITY_MAX
            msr     basepri,r0
    
            mov     r0,#2                   // Set CONTROL register to 2
            msr     control,r0
            isb                             // Insert a barrier
    
            mov     r0,sp
            msr     psp,r0                  // Copy SP to PSP
    
            orr     lr,#0xD                 // Adjust return link
            bx      lr                      // Return to init code on PSP now

    (2)hal_switch_state_vsr是个异常处理函数,在Cortex-M中异常处理函数跟普通函数没有区别,因为Cortex-M的异常处理机制会模拟C函数调用过程来调用异常处理函数,看起来就像是调用了一个普通函数。

    (6)首先将最高优先级写入basepri寄存器,这起到关中断的作用,因为所有优先级低于等于basepri的中断都被屏蔽了,在Cortex-M中优先级越高其对应的数值越小,因此从数值上讲,所有优先级数值大于等于basepri值的中断都被屏蔽,但是basepri不会屏蔽HardFault、NMI,因此初始化过程不可能会产生外部中断(包括SysTick异常)但是可能会产生异常,这也是需要在切换运行状态之前首先初始化向量表的原因。eCos没有使用primask和faultmask寄存器来关中断,因为eCos希望即使是在关中断的情况下也能处理异常。

    (9)将control寄存器设置为2,即:选择PSP作为thread模式指针,thread运行在特权级上。

    (10)插入isb指令,清洗流水线,确保前面的所有指令都执行完成后再执行后续的指令,这保证刚刚设置起作用后在执行后续的指令。

    (13)将MSP的值复制给PSP,这个函数是通过异常机制调用的,处理异常时使用handler模式,handler模式总是使用MSP作为堆栈指针,也就是说这里的SP实际上是映射到MSP的,SP的值就是MSP的值,至此,PSP的值与MSP的值一模一样,因此从MSP变更到PSP不会有任何问题。

    (16)从异常状态返回,因为lr的低4位被修改成0xD,因此返回的过程从PSP中处栈,回到thread模式后使用PSP作为堆栈寄存器。

    疑问:为什么要通过SVC系统调用异常来调用这个函数,如果按照普通函数的形式调用会有什么问题吗?

    初始化数据区

    更改模式后,继续回到hal_misc.c,接下来的代码是在特权级thread模式,使用PSP的情况下执行的。初始化数据过程将初始化数据从ROM拷贝到RAM,并将初始化为0的区域清除为0。初始化数据区的内容包括定义时带有初始值的全局变量或静态变量,不包括全局或静态的C++对象实例,全局或静态C++对象实例的初始化见后文。

    // hal/cortexm/arch/<version>/src/hal_misc.c:211
        {
            register cyg_uint32 *p, *q;
            for( p = &__ram_data_start, q = &__rom_data_start;
                 p < &__ram_data_end;
                 p++, q++ )
                *p = *q;
        }
    
        {
            register cyg_uint32 *p, *q;
            for( p = &__sram_data_start, q = &__srom_data_start;
                 p < &__sram_data_end;
                 p++, q++ )
                *p = *q;
        }
    
        {
            register cyg_uint32 *p;
            for( p = &__bss_start; p < &__bss_end; p++ )
                *p = 0;
        }

    (4)初始化RAM中.data段,初始化数据被存储在ROM中,因此将ROM中的初始化数据拷贝到RAM即可,这里RAM可能是芯片内部SRAM的一部分,也有可能是外部扩展的SRAM或DRAM,根据系统配置不同而不同。

    (12)初始化SRAM中的.data段,初始化数据被存储在ROM中,因此将ROM中的初始化数据拷贝到RAM即可,SRAM指的是芯片内部的SRAM。

    (20)将.bss段的数据清零,凡是存储在.bss段的数据,要么其定义时的初始值被赋值为0,要么没有定义初始值,这两种情况下,均将其清零。

    疑问:.data被分成内部RAM和外部RAM两种情况,为什么.bss字段没有考虑两部分呢?

    初始化中断向量表

    // hal/cortexm/arch/<version>/src/hal_misc.c:240
        {
            register int i;
    
            HAL_WRITE_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_SHPR0, 0x00000000 );
            HAL_WRITE_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_SHPR1, 0xFF000000 );
            HAL_WRITE_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_SHPR2, 0x00FF0000 );
    
            hal_interrupt_handlers[CYGNUM_HAL_INTERRUPT_SYS_TICK] = (CYG_ADDRESS)hal_default_isr;
    
            for( i = 1; i < CYGNUM_HAL_ISR_COUNT; i++ )
            {
                hal_interrupt_handlers[i] = (CYG_ADDRESS)hal_default_isr;
                HAL_WRITE_UINT8( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_PR(i-CYGNUM_HAL_INTERRUPT_EXTERNAL), 0x80 );
            }
        }

    (5)将存储器管理fault异常、总线fault异常、用法fault异常的优先级设置为0,在eCos中,中断的优先级至少大于0,因此这三个异常比任何中断的优先级都高,这也是合理的,异常比中断更要紧。优先级寄存器是8位的,这里使用32位方式写,一次写4个优先级。

    (6)将SVC异常优先级设置为0xFF,这是Cortex-M的最低优先级,也就是说SVC异常的优先级低于任何中断优先级。

    (7)将PendSVC异常优先级设置为0xFF,SysTick异常优先级设置为0。

    (9)设置SysTick的中断服务函数为默认中断服务函数hal_default_isr,上文提到过,eCos将Cortex-M中16个系统异常中的SysTick异常作为中断处理,而且是第0号中断。

    (13)将其余的中断服务函数均设置成默认中断服务函数hal_default_isr。

    (14)设置中断优先级初始值为0x80,这个优先级高于SVC异常和PendSVC异常的优先级,低于Fault异常的优先级。但SysTick的优先级被设置为0。

    疑问:SysTick是否应该和其它中断一致对待,设置其默认优先级为0x80?

    使能异常

    接下来使能用法fault异常、总线fault异常和存储器管理fault异常,这让eCos有机会捕获异常并通知调试器或内核。在fault异常屏蔽的情况,如果发生异常将会上访成硬件fault异常。

    // hal/cortexm/arch/<version>/src/hal_misc.c:278
        HAL_READ_UINT32( base+CYGARC_REG_NVIC_SHCSR, shcsr );
        shcsr |= CYGARC_REG_NVIC_SHCSR_USGFAULTENA;
        shcsr |= CYGARC_REG_NVIC_SHCSR_BUSFAULTENA;
        shcsr |= CYGARC_REG_NVIC_SHCSR_MEMFAULTENA;
        HAL_WRITE_UINT32( base+CYGARC_REG_NVIC_SHCSR, shcsr );

    变体初始化

    同样是基于Cortex-M,不同的芯片系列会有不同的外设,每个系列有其对应的变体HAL层,通过hal_variant_init初始化变体层HAL。

    // hal/cortexm/arch/<version>/src/hal_misc.c:287
        hal_variant_init();

    平台初始化

    相同的芯片还被应用于不同的目标机,每个目标机都有其对应的平台HAL层,通过hal_platform_init初始化平台层HAL,平台层初始化可以包括部分外围器件的初始化。

    // hal/cortexm/arch/<version>/src/hal_misc.c:288
        hal_platform_init();

    初始化滴答定时器

    滴答定时器是操作系统的脉搏,这里初始化滴答定时器,需要注意的是,这里进行的初始化工作进行设置合适的寄存器值,然后让滴答定时器自由运行,并不会产生中断,滴答定时器的中断向量安装和中断使能通过内核的时钟模块完成。

    // hal/cortexm/arch/<version>/src/hal_misc.c:291
        HAL_CLOCK_INITIALIZE( CYGNUM_HAL_RTC_PERIOD );

    初始化C++全局或静态对象

    参考《eCos组件初始化

    // hal/cortexm/arch/<version>/src/hal_misc.c:307
        cyg_hal_invoke_constructors();

    进入用户代码

    // hal/cortexm/arch/<version>/src/hal_misc.c:307
        cyg_start();
        for(;;);

    (2)进入用户代码cyg_start,如果用户代码入口不是cyg_start,那么eCos将提供默认实现的cyg_start,并在cyg_start中调用用户入口代码cyg_user_start或者main。

    (3)嵌入式系统中,进入用户代码后,无论有没有包括内核支持都不应该返回到这里,如果万一返回到这里,那么将进入死循环,除了消耗CPU周期啥也不做。

  • 相关阅读:
    串口调试助手的源码分析,子对话框部分
    vc 使用了SerialPort类的串口通信软件分析
    Web前端研发工程师编程能力飞升之路
    vc mscomm串口通信使用了CButtonST按钮类软件分析
    vc 串口精灵软件分析
    c#串口完全接收程序
    Windows Server 2003操作系统中禁用Internet Explorer增强的安全特性
    Window下查看进程端口号
    面向对象的设计原则(2)——依赖注入
    面向对象的设计原则(1)
  • 原文地址:https://www.cnblogs.com/phisy/p/3373573.html
Copyright © 2011-2022 走看看