freescale飞思卡尔 HCS12 系列单片机bootloader详解(一)

  最近在做freescale HCS12系列单片机的bootloader,毕竟是新手,对这方面的入门知识还是欠缺。尽管NXP官网已经有了相当丰富的文档与例程,可是无奈例程太过复杂,不知道如何下手第一行代码。这里先感谢经验丰富的某位高人醍醐灌顶的指导,为了让再入门的新学者能够有个参考,所以就将本次开发bootloader的过程学习过程记录下来以其能够对大家有所启发。因为本人自己也是新学者,所以有问题可以留言讨论。

  这里单片机的bootloader与我们广义的Linux以及windows的bootloader是不同的。这种大型操作系统的bootloader是为了做操作系统与硬件之间的桥梁:初始化硬件系统、为操作系统的引导做准备。但是单片机的bootloader主要作用却是为烧录程序提供一种新的方式,即可以让单片机可以通过串口,蓝牙,CAN, LIN等通讯方式更新烧写在Flash中的程序以及参数。这样做的好处很多,比如一个串口烧录的bootloader可以通过串口向单片机的Flash烧录程序而不需要再额外用专用的烧录器来操作,丰富了修改程序的方式,使烧录更加方便。我们常使用的慧净51系列单片机以及Arduino能够通过一根简单的串口线就烧录程序原理都是如此,在这些板子出厂时的原程序是通过专门烧录器烧录的,但由于bootloader的存在,后续用户的程序就可以通过串口烧录。再比如单片机的bootloader在实际使用时可以远程更新单片机程序,为实用的场景提供程序更新的功能,尽管做到这一点对通用计算机非常简单,无非是卸载程序重新安装,但是对于Flash空间较小的单片机而言,就需要通过bootloader这样的程序来提供这种功能。

  单片机的bootloader基本概念实际上也非常的简单,就是把单片机的存储空间(Memory,包括RAM以及Flash)都合理的分配。把Flash的程序存储空间分为bootloader区域以及user application区域。bootloader程序一般是不修改的,一次烧录后可以一直使用(当然也可以有bootloader程序自身的更新,但是原理是一样的),但是user application区域却是可以重复由bootloader修改的。这里的user application区域就是我们通常的应用代码,它是为了实现单片机的功能的主要代码。bootloader区域代码却是为了实现对user application区域的控制而存在的,它的程序逻辑是明确的:与上位机建立bootloader专用的通讯,通过串口等简单通讯方式修改Flash或者EEPROM中的程序内容,完成修改后跳出bootloader执行user application的程序内容。

  这里笔者要假设读者对freescale的HCS12单片机有了一定程度的了解,关于Bootloader的内容以及文本中的部分实例程序均来自NXP公开的AN3275以及AN4258文档以及文档的样例程序,更多的介绍可以去NXP官网下载。

  一个HCS12系列单片机的工程是由多个文件类型构成的。

  .c ——程序文件

  .h ——头文件

      .prm ——内存划分,内存块名称声明以及中断向量声明文件

      所以这里很明显的,开发bootloader最重要的就是对.prm文件进行操作了。这里使用AN3275的例程中prm文件

NAMES END

SEGMENTS
    //RAM = READ_WRITE 0x5000 TO 0x57FF;
    RAM = READ_WRITE 0x5000 TO 0x5322;
    FLASHROUTINES = READ_WRITE 0x5323 TO 0x53C8;  //157 bytes Flash_Cmd & Unsecure
    /* unbanked FLASH ROM */
    //ROM_4000 = READ_ONLY  0x4000 TO 0x7FFF;
    ROM_UPDATE = READ_ONLY 0xF000 TO 0xF0AF RELOCATE_TO 0xE000;     /*Update Section, 224 bytes*/
    ROM_ERASELOADER = READ_ONLY 0xF0B0 TO 0xF0BF;
    ROM_C000 = READ_ONLY  0xF0C0 TO 0xFE50;         /* Code may reach only up to 0xFE50 because of limitation on
                                                * the amount of Flash that is backed-up 
                                                */
    
    /* banked FLASH ROM */
/*    PAGE_3F = READ_ONLY  0x3F8000 TO 0x3FBFFF; not used: equivalent to ROM_C000 */
END

PLACEMENT
    _PRESTART,                   /* Used in HIWARE format: jump to _Startup at the code start */
    STARTUP,                     /* startup data structures */
    ROM_VAR,                     /* constant variables */
    STRINGS,                     /* string literals */
    VIRTUAL_TABLE_SEGMENT,       /* C++ virtual table segment */
    NON_BANKED,                  /* runtime routines which must not be banked */
    DEFAULT_ROM,
    COPY,                        /* copy down information: how to initialize variables */
                                 /* in case you want to use ROM_4000 here as well, make sure
                                    that all files (incl. library files) are compiled with the
                                    option: -OnB=b */
                                 INTO  ROM_C000/*, ROM_4000*/;
//    DEFAULT_ROM                  INTO  PAGE_30,PAGE_31,PAGE_32,PAGE_33,PAGE_34,PAGE_35,PAGE_36,PAGE_37,PAGE_38,PAGE_39,PAGE_3A,PAGE_3B,PAGE_3C,PAGE_3D;
    DEFAULT_RAM                  INTO  RAM;
    UPDATE_SECTION               INTO  ROM_UPDATE;
    ERASELOADER_SECTION          INTO  ROM_ERASELOADER;
    ROUTINESINRAM                INTO FLASHROUTINES;
END

ENTRIES
     vfnpUpdateFn;
     gi16FlashSecurity;
END

STACKTOP 0x53FF

VECTOR 0 _Startup /* reset vector: this is the default entry point for a C/C++ application. */
//VECTOR 0 Entry  /* reset vector: this is the default entry point for a Assembly application. */
//INIT Entry      /* for assembly applications: that this is as well the initialisation entry point */

 

  这里主要先介绍内存划分的内容:

  1)SEGMENTS关键词包含的内容是对Memory(存储空间)的分配,先看第一句:

RAM = READ_WRITE 0x5000 TO 0x5322;

  这个第一句是将所有存储空间0x5000到0x5322的部分命名为RAM, 这部分的操作方式为读写(READ_WRITE)

    FLASHROUTINES = READ_WRITE 0x5323 TO 0x53C8;  //157 bytes Flash_Cmd & Unsecure
    /* unbanked FLASH ROM */
    //ROM_4000 = READ_ONLY  0x4000 TO 0x7FFF;
    ROM_UPDATE = READ_ONLY 0xF000 TO 0xF0AF RELOCATE_TO 0xE000;     /*Update Section, 224 bytes*/
    ROM_ERASELOADER = READ_ONLY 0xF0B0 TO 0xF0BF;
    ROM_C000 = READ_ONLY  0xF0C0 TO 0xFE50;

  后面部分是一致的,关于RELOCATE_TO语句,help文档中搜索可以得到如下说明

Defining a Relocation Rule 
Use the relocation rule if a segment is moved to a different location at runtime. With the relocation rule, you instruct the linker to use different runtime addresses for all objects in a segment. 

当一个segment在运行时需要移动到一个不同的位置可以使用重定位规则。通过使用重定位规则,你可以指示连接器让某个segment中的程序在运行时使用不同的程序地址

This is useful when at runtime the code is copied and executed at a different address than the linked location. One example is a Flash programmer which must run out of RAM. Another example is a boot loader, which moves the actual application to a different address before running it. 


Specify a relocation rule as follows: 

RELOCATE_TO Address
Use <Address> to specify the runtime address of the object. 

Example 
SEGMENTS
    CODE_RELOC  = READ_ONLY 0x8000 TO 0x8FFF RELOCATE_TO 0x1000;
...
END
In this example, references to functions in CODE_RELOC use addresses from 0x1000 to 0x1FFF area, but the code is programmed from 0x8000 to 0x8FFF. 

在这个例子中,CODE_RELOC中的内容在运行时使用地址0x1000到0x1FFF的区域,但实际程序是放在0x8000到0x8FFF的区域中

With RELOCATE_TO, you can execute code at an address different from where it was allocated. The code need not be position independent (PIC), however, non-PIC code may not run at its allocation address, as all references in the code refer to the RELOCATE_TO address.  

  2)在SEGMENTS模块中将存储空间分配以后,在PLACEMENTS中对已经定义的SEGMENTS进行进一步的定义,这里先看PLACEMENTS中的第一项

    _PRESTART,                   /* Used in HIWARE format: jump to _Startup at the code start */
    STARTUP,                     /* startup data structures */
    ROM_VAR,                     /* constant variables */
    STRINGS,                     /* string literals */
    VIRTUAL_TABLE_SEGMENT,       /* C++ virtual table segment */
    NON_BANKED,                  /* runtime routines which must not be banked */
    DEFAULT_ROM,
    COPY,                        /* copy down information: how to initialize variables */
                                 /* in case you want to use ROM_4000 here as well, make sure
                                    that all files (incl. library files) are compiled with the
                                    option: -OnB=b */
                                 INTO  ROM_C000/*, ROM_4000*/;

  上述的_PRESTART,STARTUP等内容是预定义的程序块,这条语句实际上就是指示连接器将上述的这些定义的块放在SEGMENTS中已经定义过的ROM_C000区域。当中的NON_BANKED块熟悉S12的读者应该都知道,我们通常定义的中断函数都放在这个区域内。PALCEMENTS中其余语句为:

    DEFAULT_RAM                  INTO  RAM;
    UPDATE_SECTION               INTO  ROM_UPDATE;
    ERASELOADER_SECTION          INTO  ROM_ERASELOADER;
    ROUTINESINRAM                INTO FLASHROUTINES;

  即,将SEGMENTS中定义的RAM,ROM_UPDATE等区域依次命名为DEFAULT_RAM,UPDATE_SECTION等块。

    到这里,实际上我们基本上已经明确了SEGMENTS语句以及PLACEMENTS语句的作用了,这里总结如下:

           1)SEGMENTS语句按照程序的存储空间定义(datasheet中有物理空间对应存储器分布)将存储空间声明为不同的存储区域

      2)PLACEMENTS语句按照SEGMENTS中的定义对各块进行进一步的划分以及布置

  这也就是bootloader的第一步,划分储存空间。

  那么,存储空间划分好了以后我们要怎么做才能将我们的程序放到这些块中并让它们依次执行呢?

       (未完待续)

      注: 本系列文章均为原创,如有转载引用请标明来源

 

posted @ 2018-06-02 22:42  花花世界1202  阅读(6827)  评论(0编辑  收藏  举报