要實現在線升級,MCU代碼須分爲 : bootloader和 用戶代碼App 兩個部分。
用戶啓動區域(UBC): (可理解爲用戶自定義的bootloader的存放區域)
包含有復位和中斷向量表,它可用於存儲IAP及通訊程序。UBC有一個兩級保護結構可保護用戶代碼及數據在IAP編程中免於無意的擦除或修改。這意味着該區域總是寫保護的,而且寫保護不能通過使用MASS密鑰來解鎖。它的大小可通過配置option bytes 設置。
1. 中斷向量表
STM8的中斷向量表的地址是固定的,位於0x8000~0x8080,即發生中斷時,程序將會跳轉至0x8000~0x8080的中斷向量表中尋找中斷入口地址,繼而跳轉至對應的中斷服務函數中執行中斷程序。
若是要實現IAP,而0x8000~0x8080
的中斷向量表將會位於UBC區域,被bootloader所佔用。如此一來,UBC區域中的bootloader程序與MAIN PROGRAM
區域中的App發生中斷時,程序同樣都是跳轉至UBC區域中的中斷向量表中尋找中斷入口地址。所以,若App要執行App自己的中斷服務函數,則須在App中建立自己的中斷向量表,並對bootloader中的中斷向量表進行重定向,將程序跳轉至App的中斷向量表。如圖2、圖3所示。
中斷向量表的重定向:(以STVD環境下的官方例程AN2659
爲例)
以下重定向即可使App程序能夠找到自己對應的中斷服務函數,但是bootloader的中斷將不可使用
struct interrupt_vector const _vectab[] = {
{0x82, (interrupt_handler_t)_stext}, /* reset */
{0x82, NonHandledInterrupt}, /* trap */
{0x82, NonHandledInterrupt}, /* irq0 */
{0x82, NonHandledInterrupt}, /* irq1 */
{0x82, NonHandledInterrupt}, /* irq2 */
{0x82, NonHandledInterrupt}, /* irq3 */
{0x82, NonHandledInterrupt}, /* irq4 */
{0x82, NonHandledInterrupt}, /* irq5 */
{0x82, NonHandledInterrupt}, /* irq6 */
{0x82, NonHandledInterrupt}, /* irq7 */
{0x82, NonHandledInterrupt}, /* irq8 */
{0x82, NonHandledInterrupt}, /* irq9 */
{0x82, NonHandledInterrupt}, /* irq10 */
{0x82, NonHandledInterrupt}, /* irq11 */
{0x82, NonHandledInterrupt}, /* irq12 */
{0x82, NonHandledInterrupt}, /* irq13 */
{0x82, NonHandledInterrupt}, /* irq14 */
{0x82, NonHandledInterrupt}, /* irq15 */
{0x82, NonHandledInterrupt}, /* irq16 */
{0x82, NonHandledInterrupt}, /* irq17 */
{0x82, NonHandledInterrupt}, /* irq18 */
{0x82, NonHandledInterrupt}, /* irq19 */
{0x82, NonHandledInterrupt}, /* irq20 */
{0x82, NonHandledInterrupt}, /* irq21 */
{0x82, NonHandledInterrupt}, /* irq22 */
{0x82, NonHandledInterrupt}, /* irq23 */
{0x82, NonHandledInterrupt}, /* irq24 */
{0x82, NonHandledInterrupt}, /* irq25 */
{0x82, NonHandledInterrupt}, /* irq26 */
{0x82, NonHandledInterrupt}, /* irq27 */
{0x82, NonHandledInterrupt}, /* irq28 */
{0x82, NonHandledInterrupt}, /* irq29 */
};
改爲
struct interrupt_vector const UserISR_IRQ[32] @ MAIN_USER_RESET_ADDR; //MAIN_USER_RESET_ADDR爲主程序區的首地址 的宏定義
//redirected interrupt table
struct interrupt_vector const _vectab[] = {
{0x82, (interrupt_handler_t)_stext}, /* reset */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 1)}, /* trap */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 2)}, /* irq0 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 3)}, /* irq1 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 4)}, /* irq2 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 5)}, /* irq3 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 6)}, /* irq4 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 7)}, /* irq5 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 8)}, /* irq6 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+ 9)}, /* irq7 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+10)}, /* irq8 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+11)}, /* irq9 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+12)}, /* irq10 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+13)}, /* irq11 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+14)}, /* irq12 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+15)}, /* irq13 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+16)}, /* irq14 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+17)}, /* irq15 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+18)}, /* irq16 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+19)}, /* irq17 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+20)}, /* irq18 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+21)}, /* irq19 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+22)}, /* irq20 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+23)}, /* irq21 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+24)}, /* irq22 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+25)}, /* irq23 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+26)}, /* irq24 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+27)}, /* irq25 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+28)}, /* irq26 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+29)}, /* irq27 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+30)}, /* irq28 */
{0x82, (interrupt_handler_t)(UserISR_IRQ+31)}, /* irq29 */
};
其中,
typedef void @far (*interrupt_handler_t)(void); //定義中斷函數地址類型
struct interrupt_vector {
unsigned char interrupt_instruction; //指令,其中0x82爲跳轉指令
interrupt_handler_t interrupt_handler; //函數地址
};
2. FLASH塊編程程序的存放
對於stm8的塊編程,代碼必須在Ram中運行,因爲在塊編程時Flash程序將會停止運行。因此,存儲在Flash中的代碼(與Flash編程相關的代碼)必須拷貝至Ram中進行編譯、鏈接、運行。這樣,程序將在Ram中執行塊編程,塊編程時Flash的狀態也不會影響到Ram中的程序繼續運行。
方法如下:
1. 代碼的編寫
將Flash編程相關的代碼存放至FLASH_CODE
段:
#pragma section (FLASH_CODE) //將代碼存放至RAM
void Write_Flash(void)
{
//....
}
void Erase_Flash(void)
{
//....
}
#pragma section () //代碼放置至默認段
注意,在調用這些函數之前,必須把這些代碼拷貝至RAM中, STVD的cosmic編譯器可以使用內置函數
int _fctcpy(char name)實現此功能。
int _fctcpy(char name) //name爲定義的段名首字母
如本例中Flash塊編程所在的段名爲FLASH_CODE
,其首字母爲 ‘F’ 故在調用這些函數之前須執行
_fctcpy('F');
2. STVD的設置
在STVD--Settings--Linker
選項卡下,選擇目錄Category
下的Input
,在Ram
下新建Section
,Option
填-ic
,表示可移至RAM
。
3. 程序的跳轉
若bootloader程序要跳轉到app,須將程序跳轉至主程序區的首地址。
可使用匯編指令完成:
{
//reset stack pointer (lower byte - because compiler decreases SP with some bytes)
_asm("LDW X, SP ");
_asm("LD A, $FF");
_asm("LD XL, A ");
_asm("LDW SP, X ");
//跳轉至App程序 :
_asm("JPF $A000");//這裏假設app首地址爲0x00A000。
/***或者可以***/
_asm("JPF [_MainUserApplication]");//跳轉至app程序首地址,其中_MainUserApplication的定義在下面有所解釋
}
其中:
//typedef @far void (*)(void) TFunction;
typedef @far void (*TFunction)(void);
//main application code (user reset) - init user code start - to interrupt table reset jump
const TFunction MainUserApplication = (TFunction)MAIN_USER_RESET_ADDR;
4. 參考資料
官方資料(STVD開發環境):例程AN2659,《 AN2659 Application Note.pdf 》