STM8 IAP升級程序設計詳解 - IAR環境


1.STM8內存空間分配

首先我們在STM8L15x的官方手冊中查看一下CPU的內存空間分配:

 

除了系統預留的部分我們實際用到的內存空間並不多,下面簡單說明主要部分:
-- RAM 0x00 0000 - 0x00 0FFF(最大 4KB, 包含堆棧區,棧區最大513bytes):
-- Data EEPROM 0x00 1000 - 0x00 17FF(最大2KB):
STM8定義的專門用於保存掉電數據一塊區域,操作方法與內部Flash大致相同。只是可以不用擦除就能直接寫。
-- Option bytes 0x00 4800 - 0x00 48FF
-- GPIO and Perpheral registers 0x00 5000 - 0x00 57FF
GPIO 和 外設寄存器的的地址
-- Boot ROM 0x006000 - 0x67FF
-- CPU/SWIM/Debug/ITC Register 0x00 7F00 - 0x00 7FFF
-- Flash program memory 0x00 8000 - 0x01 7FFF (最大64KB)

小結:

在我們制作升級程序的時候需要將生成的 bootloader 和 app 的 bin 文件燒寫到  Flash program memory 這塊地址中去,其中 0x8000 - 0x807F 這塊區域是中斷向量表的地址,當發生中斷時會強制 pc 指針指向該地址。對於我們燒寫的 bin 文件,可以通過分析 .map 文件來了解其中的具體的內容。 對於每個完整的 bin 文件都應該由以下的段組成:
bin文件:中斷向量表 + rodata段(const常量) + 系統、堆棧等的初始化代碼 + 用戶代碼 + 初始值不為零的全局變量
當 Flash中只有一個 bin 文件時,Flash 中的內容和 bin 文件一致,但是 Flash 中可以存放多個 bin 文件。
順便我們也簡單說明一下程序運行時 Ram 中包含的內容:
RAM:初始值不為零的全局/靜態變量(由flash重定位) + 初始值零的全局/靜態變量 + 堆區 + 棧區 (降序棧,棧頂地址從sram的最高地址開始)
注意:
1. map 文件中,全局變量 / 靜態變量 /常量 的地址是指程序運行時的地址,每個變量的地址在鏈接時規定好,所以雖然 Flash 中也有全局變量,靜態變量等地址但是 map 文件中顯示的並不是 Flash 中的地址。所以在匯編文件中看到的讀寫某個變量的值時,實際上是讀寫某個地址內的內容。
2. 程序含有(反匯編文件):代碼段 + rodata段(const常量) + .data(數據段) + .bss(初始值零或者為初始化的全局/靜態變量) + comment段(注釋)
最后兩項不包含在bin文件中


補充說明一下C語言程序的內存分區:
棧區:編譯器自動分配釋放,存放函數的參數值,局部變量的值等。操作方式類似於數據結構中的棧。
堆區:一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表。
全局區:全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束后由系統釋放。
文字常量區:常量字符串就是放在這里的。程序結束后由系統釋放。
程序代碼區:存放函數體的二進制代碼

2.IAP升級程序設計流程

  IAP原理非常簡單,首先在 bootloader 程序中接收(串口、IIC、SPI等)第二個程序的代碼,並寫入Flash中,然后跳轉到第二個程序首地址,開始運行第二個程序,也就是說我們需要寫兩個程序:
1. BootLoader 程序      
2. 用戶APP

  Flash 中存在兩個 bin 文件時,程序是怎樣運行的呢?我們又是如何在一個程序運行結束之后跳轉到另一個程序中去呢?想要知道這些原理,首先我們需要先了解一下單片機的 中斷機制 啟動流程

2.1 STM8中斷機制

在官方手冊的第6章給出了 STM815xL 的中斷向量表的定義:

  在參考手冊中一般都會列出單片機系統所有的中斷向量及其對應的地址, 每個中斷向量都存放着4個字節的數據(8位的跳轉指令 + 24位的跳轉地址),在中斷發生時,會強制PC指針指向該中斷向量的地址,然后取出該地址中的指令執行。
  例如: 此時來了 USART2 的中斷,通過上表我們知道 USART2 的中斷向量存放在 0x00 8054 地址,此硬件會把PC指針強制 = 0x00 8054 也就是從這個地址里取指令執行,而這個地址中的內容是 0x82 + OFFSET_ADDR(16位), 0x82 是內部指令,意思是跳轉到后面的地址執行,OFFSET_ADDR,就是 USART2 的中斷服務函數的入口地址, 這樣最終就跳轉到了 USART2 的中斷服務函數中去執行。
  當然中斷發生時還會有一些入棧操作,保存程序當前運行的地址,一些變量的值到棧中,當中斷服務程序執行完成后會從棧中恢復到執行中斷前程序的運行狀態,從而保證主程序的正常運行。
注意:在 STM8 中,0x82 后面會跟着24位的地址(PCE + PCH + PCL),CPU 最大尋址 2^24 = 16M空間

2.2 單片機啟動流程

2.1.1 內核初始化

在單片機上電后首先會進行一系列內核的初始化,關於這部分工作我們只需要了解即可,在內核初始化的過程中主要做了以下幾件事情:
1.內核復位和 NVIC 寄存器部分清零
2.內核設置堆棧: 內核從向量表0地址讀出堆棧地址,並設置主堆棧指針(SP_main)
3.設置PC和LR寄存器
a. LR設置未初始復位值0xffff ffff
b. STM32F4 的內部硬件機制亦會自動將 PC 指針定位到“中斷向量表”處,把復位中斷 Reset_Handler 的地址賦值給PC指針

2.1.2 復位中斷函數 Reset_Handler   
可以看到在內核復位的最后一步,將PC指針指向了復位中斷向量,而復位中斷服務函數中的內容才是我們真正需要關心的內容。
我們可以在 STM32F4 的 .s 匯編啟動文件中看到以下內容:
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
     IMPORT  __main
     IMPORT  SystemInit
                 LDR     R0, =SystemInit        //加載 SystemInit的地址到寄存器 R0 中
                 BLX     R0                       //跳轉 R0 中的地址執行 (執行 SystemInit 函數)
                 LDR     R0, =__main            //加載 SystemInit的地址到寄存器 R0 中
                 BX      R0                       //跳轉 R0 中的地址執行 (執行 __main 函數)
                 ENDP
上面的代碼就是 Reset_Handler 的中斷服務函數,可以看到在服務中斷函數中先使用 IMPORT聲明了兩個函數 __main,SystemInit。
然后再跳轉執行 SystemInit 和 __main函數。下面我們再來了解一下這兩個函數具體干了些什么事情:
a. SystemInit 函數:
在 system_stm32f4xx.c 文件中我們可以看到該函數的定義,該函數主要干了以下兩件事情:
1.初始化時鍾(SYSCLK, HCLK, PCLK2 and PCLK1 prescalers)
2.配置中斷向量表(中斷向量表的定位是在 Flash 還是SRAM,是否需要偏移)
注意:可以通過 system_stm32f4xx 文件中的宏定義修改系統時鍾頻率(通過設置鎖相環的相關系數),中斷向量表的地址(位於SRAM還是Flsah,是否偏移,偏移地址多少等參數)
b. __main()函數(在IAR中是 __iar_program_start ):
該函數被封裝進了編譯器的庫中,所以不同的IDE該函數的名稱可以有所區別,但所實現的功能大致類似:
1.完成全局變量/靜態變量/常量的初始化和重定位工作.
跳轉進入__scatterload_rt2函數:通過設置四個寄存器來配置待copy內容(靜態變量、全局變量、常量)的的加載域和運行域,設置待copy內容的大小,為后續__scatterload_cpy()函數服務。
跳轉進入__scatterload_cpy函數,完成靜態變量、全局變量、常量的從flash到SRAM的重定位。
跳轉進入__scatterload_zeroinit函數,完成未初始化的全局變量的初始化,
2.初始化堆棧(這里指程序棧)和庫函數
跳轉進入 __user_steup_stackheap 函數,實現用戶的堆棧的配置
__user_steup_stackheap 完成了以下調用:
a. __user_libspac__user_libspace 為C庫保持了靜態數據。這是一個96字節,0初始化的數據塊,該塊由C庫創建。在C庫初始化期間可以用來當做臨時棧。
b. __user_initial_stackheap 用戶的初始化堆棧函數
_fp_init和__rt_fp_status_addr(C庫函數)兩個函數調用實現浮點運算的支持
3.程序的跳轉,進入main()函數。
跳轉進入用戶的main函數

注意:
1. 未初始化初始值為零 全局變量/靜態變量 一般在 RAM 中, 初始化值不為零的 全局變量/靜態變量 一般在 FLASH 中。
2. 因為 Flash 不能隨機寫(只能寫0,不能寫1),所以一般會在程序運行之前將初始值重定位到 RAM
3. 全局變量和常量的地址在編譯時都已經被分配好了(所以能夠在 .map 文件中看到), 而局部變量則是程序運行時在棧中創建的,棧空間大小可以在 IDE 中設置。
4. 單片機啟動時,不需要用將代碼從 ROM 搬移到 RAM ,而 ARM 則需要。我們先看看單片機程序執行的過程,單片機執行分三個步驟,取執行->分析指令->執行指令。取指令的任務是:根據 PC 的值從程序存儲器讀出指令,送到指令寄存器。然后分析執行執行。這樣單片機就從內部程序存儲器去代碼指令,從 RAM 存取相關數據。要知道 RAM 取數的速度是遠高於 ROM 的,但是單片機因為本身運行頻率不高,所以從 ROM 取指令慢並不影響。而 ARM 不同,CPU 運行的頻率高,遠大於從 ROM 讀寫的速度,所以一般有操作系統,都需要將代碼部分拷貝到 RAM 中再執行。

2.3 中斷重定向

  現在我們知道了,單片機在復位時會強制PC指針指向復位中斷服務函數,在復位中斷函數中完成一系列的初始化,為用戶准備好 C 語言的運行環境,最后再跳轉到我用戶的main 函數中執行我們自己編寫的程序。當我們在做 IAP 升級的時候,我們想要的程序執行順序應該是先執行 bootloader, 然后再跳轉執行 App。通常情況是先通過 bootloader 的復位中斷跳轉到 bootloader 中的 main 函數中執行,執行完成后再通過 app 的復位中斷函數中跳轉到 app 的 main 函數中。當 bootloader 和 app 發生中斷時都能單獨跳轉到各自的中斷向量表中執行自己的中斷服務函數。
  這本身沒有問題,問題是對於 STM8 而言當發生中斷時(不管哪個程序),PC 指針總會指向第一個中斷向量表相應的中斷向量的地址 (因為 STM8 中斷向量表固定在這里,並且不可以映射到別的地址,這是硬件決定的),當第二個程序發生了中斷時,此時斷服務程序肯定是在第二個程序里寫的,中斷服務函數的入口地址也在第二個程序范圍內,但是發生中斷時,PC指針不會指向第二個 bin 文件的中斷向量表,而是指向 0x8000-0x8080 ,也就無法執行第二個程序中的中斷服務函數。
  有什么辦法可以讓我的程序發生中斷時,PC指針指向我的中斷向量表呢?於是我們想到了 0x82 這個內部操作碼,0x82 + OFFSET 不是跳到 OFFSET 這個地址執行么?假設此時來了 USART2 的中斷,bootloader 程序 USART2 中斷向量地址是 0x00 8054,APP USART2 中斷向量地址是 0x9054(假設APP是從0x9000開始存放),此時PC指針一定等於0x00 8054,這時候就要讓它跳到 0x00 9054 就需要在 0x00 8054 這個地址放入:0x8200 9054,這樣PC指針又跳回了APP的中斷向量表,然后再從中斷向量表中取出 USART2的中斷服務函數並執行。這就是中斷重定向,重定向之后就能再APP程序中隨意使用中斷了。
  要實現中斷重定向,需要重新定義 bootloader 中的中斷向量表,原來 bootloader 中的中斷向量表中(0x8000 - 0x8080) 本來存放的是 bootloader 中各個中斷函數的入口地址,現在我們需要修改為 app 中斷向量表的地址,但需要注意的是 bootloader 的復位中斷向量不需要修改。如果這里修改為了 app 中的復位中斷向量地址,那就會直接跳轉到 app 的復位中斷函數中,然后再執行 app 的 mian 函數,就無法執行 bootloader 中的程序了。
注意:對於STM32來說可以設置中斷向量表的偏移地址,而STM8卻不能設置偏移,只能通過重定向來使得我們的APP程序能夠使用中斷,但是重定向后的 bootloader 中就不夠使用中斷了。

-- 在 IAR 中重定向中斷只需要在 bootloader 程序中定義以下數數組即可 (bootloader 為4K APP地址為 0x00 9000 時):

__root const long reintvec[]@".intvec"=  
       {   0x82008080,0x82009004,0x82009008,0x8200900c, 
           0x82009010,0x82009014,0x82009018,0x8200901c,
           0x82009020,0x82009024,0x82009028,0x8200902c,
           0x82009030,0x82009034,0x82009038,0x8200903c,
           0x82009040,0x82009044,0x82009048,0x8200904c,
           0x82009050,0x82009054,0x82009058,0x8200905c,
           0x82009060,0x82009064,0x82009068,0x8200906c,
           0x82009070,0x82009074,0x82009078,0x8200907c,
       };

2.4 IAR的 .ICF 鏈接文件

由於我們設計的APP程序需要設置起始地址為 0x9000 (假設預留的BootLoader空間為4k),這就需要我們修改 .icf鏈接文件中的一些內容。
下面簡單介紹一下 IAR 的 .icf 文件,
通常每個芯片開發商都會針對每款芯片來編寫一個 .icf 鏈接文件。通常這個.icf文件足以滿足你的工程需要。
但有時也會需要改動,比如當你的項目要 重設程序的地址、添加外部RAM、定義變量的絕對地址等 就要修改一下icf。

首先我們先打開開發商的官方 .icf 文件查看一下:

下面是截取的主要內容:

 

先選擇拷貝過來的 .icf文件為新的連接文件:

如果是 BootLoader 程序 .icf 文件需要做下面的修改(BootLoader 小於4KB時):

 

 如果 BootLoader 的程序不超過4KB(0x8000 - 0x8FFF),只需更改 0x80 為 0x100 { ro section .intvec };因為重新映射了中斷向量的定義,不然編譯會報錯。

如果是 APP 程序 .icf 文件則需要做以下修改 (STM8L151C8T6 是64KB的Flash,最大到0X17FFF) :

更多關於ICF文件的分析請參考文章 《IAR中ICF鏈接文件詳細分析》

2.5 編寫BootLoader 程序

  在鏈接文件中規定好了, bootloader 和 app 的地址之后就可以編寫我們的 bootload 程序了。前面已經介紹過了,bootloader 的實現可以很簡單,只需要判斷是否需要升級,如果不需要直接跳轉到 app 的地址執行。如果需要升級,則獲取 app 升級文件,然后寫入到 flash 中,最后再跳轉到 app 的地址執行。
這里我們需要解決4個問題:
1.  升級判斷條件
可以通過按鍵,主動獲取版本號比較等方式判斷是否需要升級
2. 下載升級文件
  傳輸bin文件,可以通過很多方式(串口,SPI,iic 等),這里我自己用 QT 寫了一個串口 bin 文件的傳輸工具,將bin文件分包發送給單片機,單片機接收到數據后直接寫入Flash即可。這里需要注意的是,普通的串口調試助手雖然能夠發送bin文件,但是無法分包,對於 stm8 來說一般一次只能接收2-4k的內容,接收完成后需要寫到flash中, 然后再去接收下一包,所以最好能自己寫一個下載工具,規定自己的傳輸協議。實在不行可以考慮使用有傳輸延時的串口工具或者超級終端,每發送完一行數據之后延時一定時間再發送下一包的數據,這樣才能保證接收到的數據能夠正確寫入flash中。
  另外需要注意,在 bootloader 中不能夠使用中斷,所以串口接收只能通過 while 循環判斷標志位來讀取串口接收的數據。
3.  寫入flash
  每接收到一包數據之后寫入flash,然后再偏移寫入地址,接收下一包數據繼續寫入,直到最后一包數據寫入完成后,再跳轉執行。
  這里需要注意的是,如果需要使用 stm8 的塊擦除 FLASH_EraseBlock 和 塊寫入 FLASH_ProgramBlock 等函數,需要注意庫函數中的這段注釋的內容:
 - For IAR Compiler:
    1- Use the __ramfunc keyword in the function declaration to specify that it
    can be executed from RAM.
    This is done within the stm8l15x_flash.c file, and it's conditioned by
    RAM_EXECUTION definition.
    2- Uncomment the "#define RAM_EXECUTION  (1)" line in the stm8l15x.h file, or
   define it in IAR compiler preprocessor to enable the access for the
   __ramfunc functions.
  這里提示了需要將 FLASH_EraseBlock, FLASH_ProgramBlock 等函數用 __ramfunc 關鍵字聲明,將這些函數定義到 ram 中,在 ram 中執行。
  這里還說明了要使用 __ramfunc 關鍵字需要 在 stm8l15x.h 文件中取消 #define RAM_EXECUTION (1) 這行的注釋
4. 跳轉到app地址執行
可以直接使用官方例程中提供的匯編跳轉代碼:
void _ASM_JumpTo_App(void)
{
        asm("LDW X,  SP ");
        asm("LD  A,  $FF");
        asm("LD  XL, A  ");
        asm("LDW SP, X  ");
        asm("JPF $9000");
}

總結

在前面補充講了很多知識點,其實如果只是想實現 IAP升級功能只需要以下幾個步驟即可:
1. 修改 .icf 鏈接文件,並且設置新的 .icf 鏈接文件為當前工程的鏈接文件;
2. 在 bootloader 中重定向中斷向量表;
3. 自己編寫或者下載一個 bin 文件的分包發送工具;
4. 接收下載的內容寫入到 Flash 中 ;
5. 跳轉執行app程序.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM