在完成內存映射的內容后,接下來我們將進入一個簡單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單片機為例):

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
這段內存分塊中可以看出,全部的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關鍵詞將其定位至不同的存儲空間

1 #ifndef _FLASH_LIB_H 2 #define _FLASH_LIB_H 3 4 #include <mc9s12g128.h> 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
其中Flash區域的擦除與寫入函數我們將其放在ROM的Flash區域,這兩個函數都將會被復制到RAM中運行,復制ROM中的函數到RAM中我們只需要將每個字節都對應的復制過去即可。代碼復制函數及其調用方式如下:

1 void MoveCodeIntoRam(byte *source, byte *dest, unsigned int size) 2 { 3 while (size --) 4 { 5 *dest ++ = *source ++; 6 } 7 }
此函數以復制的源起始地址,目標空間起始地址,復制地址內容大小為參數,調用格式為:
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記錄文件的解析
(未完待續)
注: 本系列文章均為原創,如有轉載引用請標明來源