在完成内存映射的内容后,接下来我们将进入一个简单Bootloader的实际设计中来。在第一节内容中,我们已经简单介绍了bootlaoder的作用,它实际上就是在单片机重启过程中的一个步骤:如果有bootloader的启动信号,则进入bootloader模式开始新程序的接收与flash的擦写,若没有bootloader的启动信号,则直接进入用户程序执行用户程序内容。
bootloader的启动信号一般有如下两种:
1)外部引脚接地或者拉高电平;每次启动时先监测某一已经设定的引脚是否已经被操作到了bootloader启动电平位,如果是希望bootloader启动的电平,则需要跳转入bootloader程序,否则直接进入用户程序。
2)重启后先开启通讯,通过串口,LIN或者CAN,网络等方式先于外部设备交互,若能够完成已经设计好的握手内容,则进入bootloader继续后续操作,否则等待一段时间比如10ms后直接跳转至用户程序。
读取引脚信息跳转bootloader与单片机的类型紧密相关,因而这里仅介绍开启通讯后的bootloader实现(实际上实现的过程大同小异,可以举一反三)。
这里讲解的内容即为上述(2)中方式,这个方式的开机后处理逻辑为:
1. 初始化总线时钟
2. 初始化通讯方式(初始化串口,CAN通讯,网络通讯或其他)
3. 初始化Flash擦写内容(这一步也可以在确认要进入bootloader后进行)
4. 将Flash擦写必须的程序从ROM中的复制到RAM中
5. 进入大循环中从通讯方式中发出握手信号判断是否进入Bootloader,握手成功则进入Bootloader否则进入用户程序
在了解整个开机后处理逻辑后,首先就需要考虑划分存储空间的事。下面是我的一个例程的prm文件的设置内容(本例程使用飞思卡尔HC9S12G128单片机为例):
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /* This is a linker parameter file for the MC9S12G128 */
2 NAMES END /* CodeWarrior will pass all the needed files to the linker by command line. But here you may add your own files too. */
3
4 SEGMENTS /* Here all RAM/ROM areas of the device are listed. Used in PLACEMENT below. */
5
6 /* Register space */
7 /* IO_SEG = PAGED 0x0000 TO 0x03FF; intentionally not defined */
8
9 /* RAM */
10 RAM = READ_WRITE 0x2000 TO 0x3BFF;
11 CODE_RAM = READ_WRITE 0x3C00 TO 0x3FFF; /*1 kB for flash read and write*/
12
13 /* D-Flash */
14 DFLASH = READ_ONLY 0x000400 TO 0x0013FF;
15
16 /* non-paged FLASHs */
17 ROM_1400 = READ_ONLY 0x1400 TO 0x1FFF;
18
19 ROM_BOOT = READ_ONLY 0x4000 TO 0x43FF; // 1KB for boot loader
20 ROM_FLASH = READ_ONLY 0X4400 TO 0x47FF RELOCATE_TO 0x3C00; // 1KB for necessary flash operation
21
22 ROM_C000 = READ_ONLY 0xC000 TO 0xFEFF;
23 /* VECTORS = READ_ONLY 0xFF00 TO 0xFFFF; intentionally not defined: used for VECTOR commands below */
24 //OSVECTORS = READ_ONLY 0xFF80 TO 0xFFFF; /* OSEK interrupt vectors (use your vector.o) */
25
26 /* paged FLASH: 0x8000 TO 0xBFFF; addressed through PPAGE */
27 PAGE_08 = READ_ONLY 0x088000 TO 0x08BFFF;
28 PAGE_09 = READ_ONLY 0x098000 TO 0x09BFFF;
29 PAGE_0A = READ_ONLY 0x0A8000 TO 0x0ABFFF;
30 PAGE_0B = READ_ONLY 0x0B8000 TO 0x0BBFFF;
31 PAGE_0C = READ_ONLY 0x0C8000 TO 0x0C93FF;
32 PAGE_0C_A000 = READ_ONLY 0x0CA000 TO 0x0CBFFF;
33 PAGE_0E = READ_ONLY 0x0E8000 TO 0x0EBFFF;
34 /* PAGE_0D = READ_ONLY 0x0D8000 TO 0x0DBFFF; not used: equivalent to ROM_4000 */
35 /* PAGE_0F = READ_ONLY 0x0F8000 TO 0x0FBEFF; not used: equivalent to ROM_C000 */
36 END
37
38 PLACEMENT /* here all predefined and user segments are placed into the SEGMENTS defined above. */
39 _PRESTART, /* Used in HIWARE format: jump to _Startup at the code start */
40 STARTUP, /* startup data structures */
41 ROM_VAR, /* constant variables */
42 STRINGS, /* string literals */
43 VIRTUAL_TABLE_SEGMENT, /* C++ virtual table segment */
44 //.ostext, /* OSEK */
45 NON_BANKED, /* runtime routines which must not be banked */
46 COPY /* copy down information: how to initialize variables */
47 /* in case you want to use ROM_4000 here as well, make sure
48 that all files (incl. library files) are compiled with the
49 option: -OnB=b */
50 INTO ROM_C000/*, ROM_1400, ROM_4000*/;
51
52 BOOTLOADER INTO ROM_BOOT;
53 FLASH_CODE INTO ROM_FLASH;
54
55
56 USER_APP INTO PAGE_08;
57 TEST_AREA INTO PAGE_09; /* physical address from 0x2_4000 */
58 DEFAULT_ROM INTO PAGE_0A, PAGE_0B, PAGE_0C, PAGE_0C_A000, PAGE_0E ;
59
60 //.stackstart, /* eventually used for OSEK kernel awareness: Main-Stack Start */
61 SSTACK, /* allocate stack first to avoid overwriting variables on overflow */
62 //.stackend, /* eventually used for OSEK kernel awareness: Main-Stack End */
63 DEFAULT_RAM INTO RAM;
64
65 //.vectors INTO OSVECTORS; /* OSEK */
66 END
67
68 ENTRIES /* keep the following unreferenced variables */
69 /* OSEK: always allocate the vector table and all dependent objects */
70 //_vectab OsBuildNumber _OsOrtiStackStart _OsOrtiStart
71 END
72
73 STACKSIZE 0x100
74
75 VECTOR 0 _Startup /* reset vector: this is the default entry point for a C/C++ application. */
76 //VECTOR 0 Entry /* reset vector: this is the default entry point for an Assembly application. */
77 //INIT Entry /* for assembly applications: that this is as well the initialization entry point */
78
79 VECTOR ADDRESS 0xFFD6 SCI0_INT_receive
View Code
这段内存分块中可以看出,全部的RAM空间地址从0x2000-0x3FFF。其中最高位的1KB (地址从 0x3C00到0x3FFF)内容用于运行Flash读写时的程序(注意,Flash的读写不能同步进行,仅当Flash不在写入时才能从中读取程序)。我们将ROM逻辑地址中未分页的区域0x4400-0x47FF定义为Flash写入必不可少的程序存储区域,并使用 RELOCATE_TO 语句将其映射至0x3C00。这样的话,存储在ROM_FLASH区域中的内容将在运行时使用CODE_RAM中的地址(当然程序需要在运行以前从ROM中先复制到RAM里,单片机不会自动帮你完成)。除了以上的操作外,我们分别定义了bootloader的存储区域ROM_BOOT并定义了相关存储区域的名称。
我们通过如下代码的方式定义了Flash擦写的库函数,通过#pragma关键词将其定位至不同的存储空间
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #ifndef _FLASH_LIB_H
2 #define _FLASH_LIB_H
3
4 #include
5
6 typedef enum
7 {
8 NoError = 0,
9 FlashProgramError = 1,
10 FlashEraseError = 2
11 } FlashMsg;
12
13 #pragma CODE_SEG BOOTLOASER
14
15 void Init_Flash(void);
16
17 #pragma CODE_SEG DEFAULT
18
19 #pragma CODE_SEG FLASH_CODE
20
21 FlashMsg Flash_Program(unsigned long address, unsigned int *ptr);
22
23 FlashMsg Flash_EraseSector(unsigned long address);
24
25
26 #pragma CODE_SEG DEFAULT
27
28 #endif
View Code
其中Flash区域的擦除与写入函数我们将其放在ROM的Flash区域,这两个函数都将会被复制到RAM中运行,复制ROM中的函数到RAM中我们只需要将每个字节都对应的复制过去即可。代码复制函数及其调用方式如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 void MoveCodeIntoRam(byte *source, byte *dest, unsigned int size)
2 {
3 while (size --)
4 {
5 *dest ++ = *source ++;
6 }
7 }
View Code
此函数以复制的源起始地址,目标空间起始地址,复制地址内容大小为参数,调用格式为:
1 MoveCodeIntoRam((byte *)0x4400, (byte *)0x3C00, 0x400);
在main()函数中添加如下的内容:
1 Init_PLL(); // 初始化时钟
2
3 Init_SCI0(); // 初始化串口
4
5 Init_Flash(); // 初始化Flash擦写
6
7 MoveCodeIntoRam((byte *)0x4400, (byte *)0x3C00, 0x400); // 从ROM中复制程序至RAM
8
9 for (;;)
10 {
11 // 串口发送握手信息判断是否进入bootloader,若握手失败,进入用户程序
12 // 若进入bootloader在此处擦写Flash
13 }
主要的操作思路为上述所示,再有就是关于用户程序的跳转,可以直接通过指针函数的形式将用户程序调用。
如下所示在分页区域中定义了用户程序:
1 #pragma CODE_SEG USER_APP
2 void application(void)
3 {
4 /*Do application works here*/
5
6 for (;;)
7 {
8 // 用户大循环
9 }
10
11 }
12 #pragma CODE_SEG DEFAULT
跳转时可以先定义函数指针,然后调用即可:
1 void (*__far ifunction)(void) = application;
总结一下本篇的内容:
1. 简要的给出了具体的HC9S12的Bootloader设计思路并提供了具体实现的方法
2. 给出了从ROM中复制地址到RAM中运行的方法及其简单解释
3. 给出了bootloader程序跳转至用户程序的函数指针方法
后续我们会再介绍HCS12系列单片机Bootloader中的重中之重:Flash擦写思路,S19记录文件的解析
(未完待续)
注: 本系列文章均为原创,如有转载引用请标明来源
|