ARM Cortex-M底层技术(四)编写自己的启动代码 & ARM Cortex-M底层技术(七)KEIL MDK 分散加载-1-分散加载的结构

ARM Cortex-M底层技术(四)编写自己的启动代码

编写自己的启动代码

    上一篇扯了一些关于启动代码的应用方面的内容,列举了4种我自己遇到过的常见的启动代码应用,当然实际的应用肯定不止上一篇文章中提到的那几种,关键是大家懂了原理后根据实际的需求添加自己的应用,这个才是关键。

    这一片文章主要分享下如何编写自己的启动代码,你可能会说这种脱了裤子放屁的事情没必要的,CMSIS规范了标准的启动代码模板,各个厂商都会提供自己MCU的启动代码,而且很稳定,干嘛要自己写?装B嘛?没错~装B当然是一个主要目的,我一直认为不想在自己的代码里面装个B的程序猿是缺乏上进心的,虚荣心往往是一个人前进的动力,另外更重要的一点,就是不自己亲自动手做过一遍,真正的知识&技能有时候很难被真正掌握。我自认为对启动代码很了解,但真的自己去写的时候,还是遇到了不少问题,当我真正的写出来并且反复测试OK之后,我发现里面用到的很多编程技术是相当实用的,它扩展了我再编写应用代码时的很多手段,废话不多说了,开始装B……

1、首先标准的CMSIS启动代码都是汇编语言的,但是为了装B,小编决定用C来编写(真实的情况是老子不会写汇编o(╥﹏╥)o)。

    用C来编写启动代码之前,我们需要先构思一下C能不能用来编写启动代码?我们回顾下第二篇文章中的启动代码原理&其主要功能:

    (a)正确的在存储空间中摆放异常&中断向量表

    (b)分散加载&C Library的初始化(调用 __main函数)

    (c)堆栈初始化

    其中(a)功能在标准的汇编启动代码中是通过声明一个数据段(就是汇编指令DCD xxx)来实现的,这个我们在C中可以用数组来做,关键是要摆放到指定的位置上,这点需要用分散加载来配合,所以(a)应该可以实现;

    其中(b)最好实现,直接调用__main就好了,没什么可说的;

    麻烦的是(c),虽然通过读标准的CMSIS汇编启动代码也可以知道堆栈是怎样初始化的,读下代码可以知道(以LPC54608为例,早期的芯片会有不同的处理方式,比如大家都很熟悉的STM32F103系列,大家可以参照之前的文章(二)去对比参考下,只要你读懂了文章(二)就是启动代码原理分析那一篇后,你可以很轻松的搞清楚其原理,以及与我示例中展示的LPC54608启动代码的区别),是需要启动代码与分散加载配合完成的,在C中是否能搞得通我满就要试试看了。

2、先把异常&中断向量表搞定

    之前我们说这个可以用数组来实现,但大家只要仔细想一下就知道,普通数组肯定不行,因为这些向量的本质是中断服务函数的入口,也就是“函数指针”所以这个数组必须得是函数指针数组:

    所以我们先声明一个函数指针类型:

typedef void ( *__vector )( void );
    然后定义一个函数指针型数组取名为__vector_table:

__vector __vector_table[] = {};
    这个数组好做,但是最重要的是摆放问题,必须确保这个向量表被摆放到Flash的0x00000000地址上去(在LPC54608这颗芯片中默认的向量表地址),这个才是问题的关键。C与汇编是无法解决这个问题的,管程序在存储空间内摆放的是编译器开发环境中的链接器(不懂的童鞋去参考“编译原理”),指导链接器工作的是分散加载的脚本文件,这里需要分散加载的介入。 这里简单介绍下编译原理的基础知识:

附加一:强势插入的编译原理基础知识

    比如大家常用的开发MCU的各种开发环境(Keil、IAR、Eclipse……等)其中真正起到编译作用的就是编译内核,Keil的编译内核是ARMCC(有时写成CCARM)、IAR编译内核是ICCARM、Eclipse一般大多是GCC,编译内核实际上是分成4个功能模块:预处理器(Preprocessor)、编译器(Compiler)、汇编器(Assembler)、链接器(Linker);如上图所示:预处理器负责把各种头文件与对应的C文件结合在一起,负责删除注释、展开宏定义等工作;编译器负责把C语言转换为对应的汇编语言;汇编器负责把汇编语言转换为可重定位的目标文件*.o,这个就已经是二进制文件了;链接器负责把所有.o文件链接成一个整体并分配真实的物理地址(就是上文反反复复提到的分散加载)。之后我会单独开一篇文章专门介绍这部分内容,这里大家有个概念就好。

    好回归正题,如何摆放异常&中断向量表呢?

    这里以Keil MDK为例(IAR、GCC在这部分语法不同,不过原理相同),指导链接器如何工作有一个专门的描述脚本文件,在Keil中此文件以.sct/.scf为扩展名,在这里可以找到分散加载描述文件:

    说明:勾选1,则使用系统默认生成的分散加载文件,好处是你不用自己动手写,缺点是,只能生成最简单的分散加载,没有办法适应用户需要的很多功能;2的红色方框内就是分散加载的路径,这里是我自己工程的路径,大家可以根据自己需求去弄,点击Edit可以编辑;

    这里直接列出来我针对自己写的启动代码编写的分散加载文件,关于分散加载以后我会专门写几篇文章来教大家如何使用,这是非常非常有用的功能,这里直接列出最简单的写法,比较暴力,在后续的文章中我们再去完善这部分内容:

load_rom 0x00000000 0x00080000
{
    vector_rom 0x00000000 0x400
    {
        *( vector_table, +first)        
    }
    
    execute_rom 0x400 FIXED 0x0007FC00
    {
        *( InRoot$$Sections )
        .any( +ro )
    }
    
    execute_data 0x20000000 0x00010000   
    {
        .any ( +rw +zi )
    }
    
    ARM_LIB_HEAP  +0 empty 0x400 {}
    ARM_LIB_STACK 0x20020000  empty -400 {}
}
    其中用*( vector_table, +first)中的+first来确保项链表被放置到Flash地址的最前端,这里需要注意的是,分散加载文件中的语言是脚本语言,既不是C也并非汇编,这个文件无法编译更无法调试,如果写错了,只能通过经验来修改不能DEBUG。如果你看不懂,不要着急,我们以后专门来介绍这部分内容。

    我们要放置一个数组,那么我们需要声明一个数据段Symbol,然后用分散加载来指定这个数据段Symbol的放置方式也就是+first。所以我们要先让这个数组生成一个段:

const __vector __vector_table[] __attribute__( ( section ( "vector_table" ), used ) ) = {};
    于是我们使用__attribute__来实现。

    堆栈的指定我们在分散加载中完成,即:

    ARM_LIB_HEAP  +0 empty 0x400 {}
    ARM_LIB_STACK 0x20020000  empty -400 {}
    具体细节以后再介绍,这里简单说一下,这两句话的意思就是声明一个大小为0x400的堆和一个大小为0x400的栈,注意堆和栈是不同的,简单来说函数的调用会占用栈空间,而使用malloc这样的函数则会产生堆分配,具体细节大家自己去百度。

*( InRoot$$Sections )
    这部分指定了C Library以及分散加载部分代码的放置

    总之,这个分散加载文件最终把代码在存储空间内放置成了类似下图的形态:

    
附加二:__attribute__是纳尼????

    __attribute__关键字用于指定函数、变量等Symbol的属性,是编译器可以识别的C语言关键字,当然这个要看使用的是什么编译器,不同的C编译器语法上略有差别,有的C编译器甚至还不支持__attribute__关键字,以下是Keil的帮助文档里面的关于__attribute__关键字可实现的功能:

     __attribute__关键字具体玩法大家可以去keil的帮助文档里面找,很多有用的功能。

    回归正题:__attribute__( ( section ( "vector_table" ), used ) ),其中的“section("vector_table")”意思是在elf(可重定位的目标文件)文件中放置一个名字为vector_table的section。used关键字指定了编译器该变量要在OBJ文件中保持为static类型。再配合分散加载的*( vector_table, +first)的+first属性我们就成功的定义了一个放置在Flash最前端地址0x00000000地址上并且不会被编译器优化掉,数值不会被改掉,名字为vector_table的向量数据段

    写到这里小编已累吐血,但我们还得继续;

    还记得栈顶如何处理吗:

    

    这个是编译器识别的特殊栈标号,可以在分散加载或者汇编或者C语言中使用,还记得我们在第二篇文档里面讲到的,0x00000000地址(向量表首地址)放置主堆栈指针MSP,0x00000004地址放置PC指针,所以我们先导入这个标号:

    CSDN的编辑器会把两个放在一起的美元符号识别为一个关键字改变之后的字体以及段落形态,所以有些代码部分我只能贴图片出来,郁闷中……

    

0x00000004放置PC指针,我们需要把复位向量放到此处,所以代码是这样的:

    上面代码省略了中断向量表部分。ECRP为LPC54608的加密位,0xffffffff为不加密。这里不同的MCU处理方式不同,不具备什么代表性。

3、搞定__reset_handler复位函数

    这个比较简单,最重要的是调用__main,另外记得我们在上一篇文档中介绍的启动代码的使用吗?有一些初始化要放到__main之前的,LPC54608的FPU以及一些SRAM的开关要放到__main之前,读过上一篇文档的童鞋就明白下面的代码为什么这么写了;

    首先导入__main函数:

extern void __main( void );
    然后是__reset_handle的函数体:

void __reset_handler ( void )
{
#if ( ( __FPU_PRESENT == 1 ) && ( __FPU_USED == 1 ) )
    SCB->CPACR |= ( ( 3UL << 10 * 2 ) | ( 3UL << 11 * 2 ) );       // set CP10, CP11 Full Access 
#endif  
    SCB->VTOR = ( uint32_t ) 0x00000000;
    SYSCON->ARMTRACECLKDIV = 0;
    SYSCON->AHBCLKCTRLSET[ 0 ] = SYSCON_AHBCLKCTRL_SRAM1_MASK | SYSCON_AHBCLKCTRL_SRAM2_MASK | SYSCON_AHBCLKCTRL_SRAM3_MASK;
    
    __main();
}
    因为是C语言的,在__main之前加入自己需要的函数体部分会更容易,大家根据自己需求添加就好了,这里使用的是最简单的通关方式。

4、搞定默认的异常&中断处理函数,还记得启动代码原理那篇文档吗?弱函数,对使用弱函数!

    以其中一个向量为例:(USB0中断)

    函数体部分:

void usb0_irqhandler              ( void )                 
{
    while( 1 );
}   
    然后再前面声明为“弱函数”,依然使用__attribute__来指定其属性为“weak”:

extern void usb0_irqhandler          ( void ) __attribute__( ( weak ) );    
    还记得之前文章2中提到的弱函数吗?如果你不写中断服务函数,但程序却进了这个中断,程序就会进入默认的中断服务函数,如果你自己写了一个,你自己的中断服务函数会覆盖被声明为“weak”的函数,当然前提是函数名一致。我们上大学的时候教C语言的老师都说在C语言中函数名必须是唯一,不能重名,但是实际上是可以重名的,但其中一个必须是“弱函数”,强函数会覆盖弱函数,这点大家可以自己做实验验证一下

    然后把所有异常&中断服务函数全都按照上述方式来写,就搞定了;   

5、高潮来了,LPC54608的完整启动代码C语言实现版,附带其配套的简陋版分散加载脚本如下:


//

// 定义中断向量表类型

//

typedef void ( *__vector )( void );


//

// 导入__main

//

extern void __main( void );


//

// 导入主堆栈栈顶指针

//


//

// Exceptions Handler Defination

//

extern void __reset_handler ( void ) __attribute__( ( weak ) );

extern void __nmi_handler   ( void ) __attribute__( ( weak ) );                                       

extern void __hard_fault_handler   ( void ) __attribute__( ( weak ) );                                      

extern void __mem_manage_handler   ( void ) __attribute__( ( weak ) );                                  

extern void __bus_fault_handler    ( void ) __attribute__( ( weak ) );                                    

extern void __usage_fault_handler  ( void ) __attribute__( ( weak ) );

extern void __svc_handler      ( void ) __attribute__( ( weak ) );                             

extern void __debug_handler    ( void ) __attribute__( ( weak ) );                                                                                               

extern void __pend_sv_handler   ( void ) __attribute__( ( weak ) );                                

extern void __systick_handler  ( void ) __attribute__( ( weak ) );


//

// Interrupt Handler Defination

//

extern void wdt_bod_irqhandler                    ( void ) __attribute__( ( weak ) );

extern void dma0_irqhandler ( void ) __attribute__( ( weak ) );

extern void gint0_irqhandler ( void ) __attribute__( ( weak ) );

extern void gint1_irqhandler  ( void ) __attribute__( ( weak ) );

extern void pin_int0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void pin_int1_irqhandler  ( void ) __attribute__( ( weak ) );

extern void pin_int2_irqhandler  ( void ) __attribute__( ( weak ) );

extern void pin_int3_irqhandler  ( void ) __attribute__( ( weak ) );

extern void utick0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void mrt0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void ctimer0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void ctimer1_irqhandler  ( void ) __attribute__( ( weak ) );

extern void sct_irqhandler  ( void ) __attribute__( ( weak ) );

extern void ctimer3_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm1_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm2_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm3_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm4_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm5_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm6_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm7_irqhandler  ( void ) __attribute__( ( weak ) );

extern void adc0_seqa_irqhandler  ( void ) __attribute__( ( weak ) );

extern void adc0_seqb_irqhandler  ( void ) __attribute__( ( weak ) );

extern void adc0_thcmp_irqhandler  ( void ) __attribute__( ( weak ) );

extern void dmic0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void hwvad0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void usb0_needclk_irqhandler         ( void ) __attribute__( ( weak ) );

extern void usb0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void rtc_irqhandler         ( void ) __attribute__( ( weak ) );

extern void reserved46_irqhandler  ( void ) __attribute__( ( weak ) );

extern void reserved47_irqhandler  ( void ) __attribute__( ( weak ) );

extern void pin_int4_irqhandler  ( void ) __attribute__( ( weak ) );

extern void pin_int5_irqhandler  ( void ) __attribute__( ( weak ) );

extern void pin_int6_irqhandler  ( void ) __attribute__( ( weak ) );

extern void pin_int7_irqhandler  ( void ) __attribute__( ( weak ) );

extern void ctimer2_irqhandler  ( void ) __attribute__( ( weak ) );

extern void ctimer4_irqhandler  ( void ) __attribute__( ( weak ) );

extern void rit_irqhandler  ( void ) __attribute__( ( weak ) );

extern void spifi0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm8_irqhandler  ( void ) __attribute__( ( weak ) );

extern void flexcomm9_irqhandler    ( void ) __attribute__( ( weak ) );

extern void sdio_irqhandler  ( void ) __attribute__( ( weak ) );

extern void can0_irq0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void can0_irq1_irqhandler  ( void ) __attribute__( ( weak ) );

extern void can1_irq0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void can1_irq1_irqhandler  ( void ) __attribute__( ( weak ) );

extern void usb1_irqhandler  ( void ) __attribute__( ( weak ) );

extern void usb1_needclk_irqhandler         ( void ) __attribute__( ( weak ) );

extern void ethernet_irqhandler  ( void ) __attribute__( ( weak ) );

extern void ethernet_pmt_irqhandler         ( void ) __attribute__( ( weak ) );

extern void ethernet_maclp_irqhandler         ( void ) __attribute__( ( weak ) );

extern void eeprom_irqhandler  ( void ) __attribute__( ( weak ) );

extern void lcd_irqhandler  ( void ) __attribute__( ( weak ) );

extern void sha_irqhandler  ( void ) __attribute__( ( weak ) );

extern void smartcart0_irqhandler  ( void ) __attribute__( ( weak ) );

extern void smartcart1_irqhandler  ( void ) __attribute__( ( weak ) );

//

// Vector Table

//

const __vector __vector_table[] __attribute__( ( section ( "vector_table" ), used ) ) = 

{

//

// Exceptions Handler Defination

//

       

        __reset_handler, 

        __nmi_handler,                                          

        __hard_fault_handler,                                          

        __mem_manage_handler,                                        

        __bus_fault_handler,                                          

        __usage_fault_handler,                                       

        0,                                                                  

        ( __vector )0xFFFFFFFF,    // ECRP                                                              

        0,                                                                  

        0,                                                                

        __svc_handler,                                    

        __debug_handler,                                           

        0,                                                               

        __pend_sv_handler,                                         

        __systick_handler,   


//

// Interrupt Handler Defination

//

wdt_bod_irqhandler,

dma0_irqhandler,

gint0_irqhandler,

gint1_irqhandler,

pin_int0_irqhandler,

pin_int1_irqhandler,

pin_int2_irqhandler,

pin_int3_irqhandler,

utick0_irqhandler,

mrt0_irqhandler,

ctimer0_irqhandler,

ctimer1_irqhandler,

sct_irqhandler,

ctimer3_irqhandler,

flexcomm0_irqhandler,

flexcomm1_irqhandler,

flexcomm2_irqhandler,

flexcomm3_irqhandler,

flexcomm4_irqhandler,

flexcomm5_irqhandler,

flexcomm6_irqhandler,

flexcomm7_irqhandler,

adc0_seqa_irqhandler,

adc0_seqb_irqhandler,

adc0_thcmp_irqhandler,

dmic0_irqhandler,

hwvad0_irqhandler,

usb0_needclk_irqhandler,

usb0_irqhandler,

rtc_irqhandler,

reserved46_irqhandler,

reserved47_irqhandler,

pin_int4_irqhandler,

pin_int5_irqhandler,

pin_int6_irqhandler,

pin_int7_irqhandler,

ctimer2_irqhandler,

ctimer4_irqhandler,

rit_irqhandler,

spifi0_irqhandler,

flexcomm8_irqhandler,

flexcomm9_irqhandler,

sdio_irqhandler,

can0_irq0_irqhandler,

can0_irq1_irqhandler,

can1_irq0_irqhandler,

can1_irq1_irqhandler,

usb1_irqhandler,

usb1_needclk_irqhandler,

ethernet_irqhandler,

ethernet_pmt_irqhandler,

ethernet_maclp_irqhandler,

eeprom_irqhandler,

lcd_irqhandler,

sha_irqhandler,

smartcart0_irqhandler,

smartcart1_irqhandler,


};


#include "lpc54608.h"


//

// Default Exceptions Handler 

//

void __reset_handler ( void )

{

#if ( ( __FPU_PRESENT == 1 ) && ( __FPU_USED == 1 ) )

    SCB->CPACR |= ( ( 3UL << 10 * 2 ) | ( 3UL << 11 * 2 ) );

#endif  

    SCB->VTOR = ( uint32_t ) 0x00000000;

    SYSCON->ARMTRACECLKDIV = 0;

    SYSCON->AHBCLKCTRLSET[ 0 ] = SYSCON_AHBCLKCTRL_SRAM1_MASK | SYSCON_AHBCLKCTRL_SRAM2_MASK | SYSCON_AHBCLKCTRL_SRAM3_MASK;

    

    __main();

}


void __nmi_handler   ( void ) 

{

    while( 1 );

}    


void __hard_fault_handler           ( void ) 

{

    while( 1 );

}    


void __mem_manage_handler           ( void ) 

{

    while( 1 );

}    


void __bus_fault_handler            ( void )  

{

    while( 1 );

}    


void __usage_fault_handler          ( void )

{

    while( 1 );

}    


void __svc_handler      ( void )  

{

    while( 1 );

}    


void __debug_handler    ( void )  

{

    while( 1 );

}    


void __pend_sv_handler   ( void )  

{

    while( 1 );

}    


void __systick_handler  ( void )

{

    while( 1 );

}    


//

// Default Irq Handler, Weak Functions

//

void wdt_bod_irqhandler                    ( void )

{

    while( 1 );

}


void dma0_irqhandler ( void )

{

    while( 1 );

}


void gint0_irqhandler ( void )

{

    while( 1 );

}


void gint1_irqhandler  ( void )

{

    while( 1 );

}


void pin_int0_irqhandler  ( void )

{

    while( 1 );

}


void pin_int1_irqhandler  ( void )

{

    while( 1 );

}


void pin_int2_irqhandler  ( void )

{

    while( 1 );

}


void pin_int3_irqhandler  ( void )

{

    while( 1 );

}


void utick0_irqhandler  ( void )

{

    while( 1 );

}


void mrt0_irqhandler  ( void )

{

    while( 1 );

}


void ctimer0_irqhandler  ( void )

{

    while( 1 );

}    


void ctimer1_irqhandler  ( void )

{

    while( 1 );

}    


void sct_irqhandler  ( void )

{

    while( 1 );

}    


void ctimer3_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm0_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm1_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm2_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm3_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm4_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm5_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm6_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm7_irqhandler  ( void )

{

    while( 1 );

}    


void adc0_seqa_irqhandler  ( void )

{

    while( 1 );

}    


void adc0_seqb_irqhandler  ( void )

{

    while( 1 );

}    


void adc0_thcmp_irqhandler          ( void )

{

    while( 1 );

}    


void dmic0_irqhandler  ( void )

{

    while( 1 );

}    


void hwvad0_irqhandler  ( void )

{

    while( 1 );

}    


void usb0_needclk_irqhandler         ( void )

{

    while( 1 );

}    


void usb0_irqhandler  ( void )

{

    while( 1 );

}    


void rtc_irqhandler ( void )

{

    while( 1 );

}    


void reserved46_irqhandler          ( void )

{

    while( 1 );

}    


void reserved47_irqhandler          ( void )

{

    while( 1 );

}    


void pin_int4_irqhandler  ( void )

{

    while( 1 );

}    


void pin_int5_irqhandler  ( void )

{

    while( 1 );

}    


void pin_int6_irqhandler  ( void )

{

    while( 1 );

}    


void pin_int7_irqhandler  ( void )

{

    while( 1 );

}    


void ctimer2_irqhandler  ( void )

{

    while( 1 );

}    


void ctimer4_irqhandler  ( void )

{

    while( 1 );

}    


void rit_irqhandler  ( void )

{

    while( 1 );

}    


void spifi0_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm8_irqhandler  ( void )

{

    while( 1 );

}    


void flexcomm9_irqhandler    ( void )

{

    while( 1 );

}    


void sdio_irqhandler  ( void )

{

    while( 1 );

}    


void can0_irq0_irqhandler  ( void )

{

    while( 1 );

}    


void can0_irq1_irqhandler  ( void )

{

    while( 1 );

}    


void can1_irq0_irqhandler  ( void )

{

    while( 1 );

}    


void can1_irq1_irqhandler  ( void )

{

    while( 1 );

}    


void usb1_irqhandler  ( void )

{

    while( 1 );

}    


void usb1_needclk_irqhandler         ( void )

{

    while( 1 );

}    


void ethernet_irqhandler  ( void )

{

    while( 1 );

}    


void ethernet_pmt_irqhandler         ( void )

{

    while( 1 );

}    


void ethernet_maclp_irqhandler         ( void )

{

    while( 1 );

}    


void eeprom_irqhandler  ( void )

{

    while( 1 );

}    


void lcd_irqhandler  ( void )

{

    while( 1 );

}    


void sha_irqhandler  ( void )

{

    while( 1 );

}   


void smartcart0_irqhandler          ( void )

{

    while( 1 );

}   


void smartcart1_irqhandler          ( void )

{

    while( 1 );

}


/**

 **************************************************************************************************

    End of file

 **************************************************************************************************

 */

    对应的分散加载为:

load_rom 0x00000000 0x00080000
{
    vector_rom 0x00000000 0x400
    {
        *( vector_table, +first)        
    }
    
    execute_rom 0x400 FIXED 0x0007FC00
    {
        *( InRoot$$Sections )
        .any( +ro )
    }
    
    execute_data 0x20000000 0x00010000   
    {
        .any ( +rw +zi )
    }
    
    ARM_LIB_HEAP  +0 empty 0x400 {}
    ARM_LIB_STACK 0x20020000  empty -400 {}
}

 

ARM Cortex-M底层技术(七)KEIL MDK 分散加载-1-分散加载的结构

KEIL MDK 分散加载的结构

    

1、我们先来解剖一只麻雀    

    很多人会说我做项目时没用过分散加载啊,可能有些人甚至都不知道它的存在。事实上,开发环境会默认生成一个分散加载文件(或者叫链接器描述文件),你使用的可能就是这个默认的分散加载文件,先来看一下Keil默认生成的分散加载文件,使用LPC54608随便找了一个示例代码用Keil生成了一个,如下图所示:

    

    这个分散加载是keil自己生成的分散加载,可以说是最简单的分散加载,简单到RO、RW、ZI基本都是随意摆放的;里面红色字体的是各个段的起始地址以及大小,这些段的起始地址以及大小是由下图的红框框定的部分决定的,你可以把上图以及下面这张图合起来看,地址是一致的。

    

    这个分散加载文件的基本意思如下图:

    

    这里面Flash以及SRAM的地址以及大小都是可以修改的,其他的也可以修改,但起始地址以及大小都要在芯片真实存在的有效物理地址上,这部分需要参看芯片的用户手册里面的Memory MAP一节的内容,如下图就是LPC54608的Memory MAP,参看分散加载里面的地址,Flash以及SRAM的起始地址以及大小都落在有效的空间上了。如果你定义的Flash起始地址在0x40000000,那么肯定会出错,因为与AHB总线地址冲突了。

           
2、分散加载的基本结构

    我们举个例子来说明分散加载文件的基本机构,如下:
LOAD_ROM_1 0x0000              【加载域描述】这段是要告诉链接器,你的程序是存在哪里?我从哪里去找需要执行的代码。
{                                                                        
    EXEC_ROM_1 0x0000           【运行域描述】这段是要告诉链接器,你的程序在哪里执行,在ARM Cortex-M系列的绝大多
    {                                                                    数MCU中加载域以及运行域是在同一个空间上的,即片内Flash。
        program1.o (+RO)            【输入节描述】就是告诉链接器,具体把哪一个以及怎么把这一个obj文件放到运行域里面
    }
    DRAM 0x18000 0x8000        【运行域描述】这里指的是RAM空间的运行域,下面会解释为什么这里会有两个运行域
    {
        program1.o (+RW,+ZI)     【输入节描述】告诉链接器,去哪里找执行程序是需要使用的变量以及数据
    }
}

LOAD_ROM_2 0x4000                【另一个加载域描述】同一个工程可以有多个加载域,就好像同一台电脑可以装几个操作系统
{
    EXEC_ROM_2 0x4000             【另一个运行域描述】
    {
        program2.o (+RO)             【另一个输入节】
    }
    SRAM 0x8000 0x8000            【另一个运行域描述】
    {
        program2.o (+RW,+ZI)      【另一个输入节】
    }
}
    其实Keil的官方帮助文档里面有官方的对分散加载的全面介绍与使用指导,但是里面的东西语言太官方了,搞了一大堆定义,然后又例子又很少,一大堆的专业术语,小编我最开始学这部分内容就是啃的这部分,真是想死的心情停不了啊~。好吧,下面我尽量多举例子,尽量少扯那些乱七八糟的术语,下面我们开始:
    你可能会问,这个分散加载文件是用什么语言编写的?是一种脚本语言,不是C语言也不是汇编,所以分散加载不能调试,它是一种叫做BNF的东东,官方文档里面把这东西描述的极其神秘,反正我是没太看懂这个所谓的BNF到底是个啥东东(据说是一个符号推导语言之类的一般存在于各种专业论文里面用于装B的东西),但是经过多个工程的磨练,小编我也基本弄明白了这东西该怎么用了,至于理论层面我没有深究,这个东东貌似做分散加载的描述脚本有些大材小用了,它能做的事情远不止于此,当然其他的跟我们没关系啦。

    对于上面这个例子的几点说明:
    <1>分散加载的根本功能是指定程序在存储空间上面的存储分配以及运行空间的分配~,所有要有加载域和运行域来分别指定程序存储空间以及程序运行空间。一般来说程序的运行空间是在芯片的ROM类存储器里面,在Cortex-M里面基本就是芯片内部的Flash空间;
    <2>运行域就有意思了,由于MCU内部的Flash(几乎都是Nor-Flash)是可以运行代码的,但是不能用于变量也就是RW与ZI的加载,主要原因是变量需要经常修改,几个小时就可能连续改变几十万次,但是目前Flash工艺的写寿命介于10万次~100万次之间,如果把RW和ZI放在Flash上,那就是灾难,Flash会因为写次数的限制很快就会挂掉,而且Flash只能按块操作,开销太大,所以一般都是放到SRAM里面,所以你会看到在这个例子里面,运行域分成两个部分,RO数据段放在内部Flash里面,RW与ZI放到片内SRAM中去执行。这点与电脑是不同的,电脑的硬盘是完全不能执行程序的,所以如果你把电脑看成MCU的话,用Keil来编写程序的话,那么电脑的RO、RW、ZI段都是放到内存上执行的,也就是说电脑实际上只有一个执行域就在内存上,可以类似理解为MCU的片内SRAM上。其实很多市面上的A7、A8、A9、A53等内核的应用处理器运行Linux与Android也可以类比为电脑,所有的东西都要加载到RAM上运行。
    <3>所以分散加载可以简单理解为的最基本结构就是至少3个域(这个事实上不对,但是对于大多数Cortex-M系列MCU的分散加载可以这样简单理解):至少一个加载域、建议两个运行域(一个RO运行域、一个RW+ZI运行域),就是你要告诉链接器至少3个信息,即:从哪里加载程序(至少一个域)、在哪里运行程序(至少一个域)、在哪里读写程序运行中用到的变量(至少一个域,实际上也可以跟运行程序的域在一起,但强烈建议分开)。如果你自己编写分散加载文件,先把这把这3个结构写出来,我们再来看之前的麻雀:            
    

    LR_IROM1就是【加载域】,指定了用户程序存储在0x00000000起始大小为0x80000的地址上,用户程序从这个区域内加载;

    ER_IROM1就是【运行域】,是RO的运行域,因为LPC54608内部的Flash可以运行代码,所以用户代码可以从这个区域内运行;

    RW_IRAM1就是【运行域】,是RW+ZI的运行域,这里指向的是片内SRAM的地址。

    这样一解构,整个分散加载的基本结构就比较清晰了,下面我们再来看一个复杂一点的,同样的LPC54608的工程的分散加载

                

    这个大家仔细看下这个结构,基本上上面所说的3个域结构的扩展。这里有几点不同的独特地方,我们解释下:

    1、USB单独搞了两个运行域出来是干嘛的?

        是这样的,USB由于要大量的数据吞吐,单独开辟一块SRAM作为数据BUFFER,所以你看到加载域2和加载域3的地址是在片内SRAM上,你可以对照一下上面的LPC5460的Memory MAP就会发现这两块地址是在片内SRAM上的。

    2、分散加载还可以指定堆栈????

        没错,确实可以,上面这个例子就在分散加载里面指定了堆栈其中:

        ARM_LIB_HEAP域ARM_LIB_STACK是两个编译器之前就定义好的标号(Symbol),用于处理堆栈分配。当然堆栈也可以在启动代码里面指定分配,这是两种不同的方式,上面这个例子是在分散加载中指定堆栈。

        关于分散加载,我们要讲的远不止于此,接下来,我们还会用几篇文档的篇幅来深入探讨这部分的技术。
--------------------- 
作者:Solaris_超 
来源:CSDN 
原文:https://blog.csdn.net/weixin_39118482/article/details/79895378 
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值