STM32 IAP固件升級(二)


章節說明

STM32 IAP固件升級實驗分為一下的章節(加粗的字體是本章節的內容):

一、Flash和RAM的區域划分、工程建立、程序分散加載、程序燒寫
二、Stm32 bootloader、application、firmware 程序的分析和編寫
三、使用DMA收發串口的不定長數據
四、通信協議的設計
五、STM32 IAP程序的設計
六、上位機的程序的編寫

一、STM32F1XX的啟動過程

在《Cortex-M3權威指南》有講述:芯片復位后首先會從向量表里面取出兩個值(下圖來自Cortex-M3權威指南);

  • 0x0000 0000地址取出MSP(主堆棧寄存器)的值
  • 0x0000 0004地址取出PC(程序計數器)的值
  • 然后取出第一條指令執行

Cortex-M3權威指南 復位序列

二、STM32啟動文件的分析

1、啟動文件源代碼分析

分析startup_stm32f10x_hd.s啟動文件時會涉及到到一些匯編指令,如果不認識的指令可以到mdk集成開發工具help -> μVision Help 里面搜索;如下圖:

代碼塊如下:

;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
;* File Name          : startup_stm32f10x_hd.s
;* Author             : MCD Application Team
;* Version            : V3.5.0
;* Date               : 11-March-2011
;* Description        : STM32F10x High Density Devices vector table for MDK-ARM 
;*                      toolchain. 
;*                      This module performs:
;*                      (上電復位后會做下面的幾件事情)
;*                      - Set the initial SP(設置堆棧,就是設置MSP的值)
;*                      - Set the initial PC == Reset_Handler(設置PC的值)
;*                      - Set the vector table entries with the exceptions ISR address(設置中斷向量表的地址)
;*                      - Configure the clock system and also configure the external (設置系統時鍾;如果芯片外部由掛載SRAM,還需要配置SRAM,默認是沒有掛外部SRAM的)
;*                        SRAM mounted on STM3210E-EVAL board to be used as data 
;*                        memory (optional, to be enabled by user)
;*                      - Branches to __main in the C library (which eventually      (調用C庫的__main函數,然后調用main函數執行用戶的)
;*                        calls main()).
;*                      After Reset the CortexM3 processor is in Thread mode,
;*                      priority is Privileged, and the Stack is set to Main.
;* <<< Use Configuration Wizard in Context Menu >>>   
;*******************************************************************************
; THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
; WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE TIME.
; AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT,
; INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE
; CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING
; INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
;*******************************************************************************

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

; ------------------分配棧空間----------------
Stack_Size      EQU     0x00000400      ;EQU指令是定義一個標號;標號名是Stack_Size; 值是0x00000400(有點類似於C語言的#define)。Stack_Size標號用來定義棧的大小
                AREA    STACK, NOINIT, READWRITE, ALIGN=3  ;AREA指令是定義一個段;這里定義一個 段名是STACK,不初始化,數據可讀可寫,2^3=8字節對齊的段(詳細的說明可以查看指導手冊)
Stack_Mem       SPACE   Stack_Size   ;SPACE匯編指令用來分配一塊內存;這里開辟內存的大小是Stack_Size;這里是1K,用戶也可以自己修改
__initial_sp      ;在內存塊后面聲明一個標號__initial_sp,這個標號就是棧頂的地址;在向量表里面會使用到

                                           
; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
; ------------------分配堆空間----------------
;和分配棧空間一樣不過大小只是512字節
Heap_Size       EQU     0x00000200
                AREA    HEAP, NOINIT, READWRITE, ALIGN=3

__heap_base        ;__heap_base堆的起始地址
Heap_Mem        SPACE   Heap_Size      ;分配一個空間作為堆空間,如果函數里面有調用malloc等這系列的函數,都是從這里分配空間的
__heap_limit       ;__heap_base堆的結束地址

                PRESERVE8 ;PRESERVE8 指令作用是將堆棧按8字節對齊
                THUMB;THUMB作用是后面的指令使用Thumb指令集

; ------------------設置中斷向量表----------------
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY      ;定義一個段,段名是RESET的只讀數據段
                ;EXPORT聲明一個標號可被外部的文件使用,使標號具有全局屬性
                EXPORT  __Vectors          ;聲明一個__Vectors標號允許其他文件引用          
                EXPORT  __Vectors_End      ;聲明一個__Vectors_End標號允許其他文件引用
                EXPORT  __Vectors_Size     ;聲明一個__Vectors_Size標號允許其他文件引用


                
;DCD 指令是分配一個或者多個以字為單位的內存,並且按四字節對齊,並且要求初始化

;__Vectors 標號是 0x0000 0000 地址的入口,也是向量表的起始地址
__Vectors       DCD     __initial_sp               ;* Top of Stack     定義棧頂地址;單片機復位后會從這里取出值給MSP寄存器,
                                                   ;* 也就是從0x0000 0000 地址取出第一個值給MSP寄存器 (MSP = __initial_sp) 
                                                   ;* __initial_sp的值是鏈接后,由鏈接器生成

                DCD     Reset_Handler              ;* Reset Handler    定義程序入口的值;單片機復位后會從這里取出值給PC寄存器,
                                                   ;* 也就是從0x0000 0004 地址取出第一個值給PC程序計數器(pc = Reset_Handler)
                                                   ;* Reset_Handler是一個函數,在下面定義
                ;后面的定義是中斷向量表的入口地址了這里就不多介紹了,想要了解的可以參考《STM32中文手冊》和《Cortex-M3權威指南》
                DCD     NMI_Handler                ; NMI Handler      
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler

                ; External Interrupts
                DCD     WWDG_IRQHandler            ; Window Watchdog
                DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
                DCD     TAMPER_IRQHandler          ; Tamper
                DCD     RTC_IRQHandler             ; RTC
                DCD     FLASH_IRQHandler           ; Flash
                DCD     RCC_IRQHandler             ; RCC

                .....由於文件太長這里省略了部分向量表的定義,完整的可以查看工程里的啟動文件

                DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1
                DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2
                DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
                DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End                                    ;__Vectors_End向量表的結束地址

__Vectors_Size  EQU  __Vectors_End - __Vectors   ;定義__Vectors_Size標號,值是向量表的大小

                AREA    |.text|, CODE, READONLY  ;定義一個代碼段,段名是|.text|,屬性是只讀

;PROC指令是定義一個函數,通常和ENDP成對出現(標記程序的結束)               
; Reset handler
Reset_Handler   PROC                                      ;定義 Reset_Handler函數;復位后賦給PC寄存器的值就是Reset_Handler函數的入口地址值。也是系統上電后第一個執行的程序

                EXPORT  Reset_Handler             [WEAK]  ;*[WEAK]指令是將函數定義為弱定義。所謂的弱定義就是如果其他地方有定義這個函數,
                                                          ;*編譯時使用另一個地方的函數,否則使用這個函數
                                                
                                                          ;*IMPORT   表示該標號來自外部文件,跟 C 語言中的 EXTERN 關鍵字類似
                IMPORT  __main                            ;*__main 和 SystemInit 函數都是外部文件的標號
                IMPORT  SystemInit                        ;* SystemInit 是STM32函數庫的函數,作用是初始化系統時鍾
                LDR     R0, =SystemInit
                BLX     R0              
                LDR     R0, =__main                       ;* __main是C庫的函數,主要是初始化堆棧和代碼重定位,然后跳到main函數執行用戶編寫的代碼
                BX      R0
                ENDP
                
; Dummy Exception Handlers (infinite loops which can be modified)
;下面定義的都是異常服務函中斷服務函數
NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
.....由於文件太長這里省略了部分函數的定義,完整的可以查看工程里的啟動文件
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP

Default_Handler PROC

                EXPORT  WWDG_IRQHandler            [WEAK]
                EXPORT  PVD_IRQHandler             [WEAK]
                .....由於文件太長這里省略了部分中斷服務函數的定義,完整的可以查看工程里的啟動文件
                EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]

WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
.....由於文件太長這里省略了部分標號的定義,完整的可以查看工程里的啟動文件
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
                B       .

                ENDP

                ALIGN    ;四字節對齊

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
;下面函數是初始化堆棧的代碼
                 IF      :DEF:__MICROLIB     
                 ;如果定義了__MICROLIB宏編譯下面這部分代碼,__MICROLIB在MDK工具里面定義
                 ;這種方式初始化堆棧是由 __main 初始化的
                 EXPORT  __initial_sp   ;棧頂地址 (EXPORT將標號聲明為全局標號,供其他文件引用)
                 EXPORT  __heap_base    ;堆的起始地址
                 EXPORT  __heap_limit   ;堆的結束地址
                
                 ELSE
                 ;由用戶初始化堆
                 ;否則編譯下面的
                 IMPORT  __use_two_region_memory      ;__use_two_region_memory 由用戶實現
                 EXPORT  __user_initial_stackheap
                 
__user_initial_stackheap
                 
                 LDR     R0, =  Heap_Mem              ;堆的起始地址
                 LDR     R1, =(Stack_Mem + Stack_Size);棧頂地址
                 LDR     R2, = (Heap_Mem +  Heap_Size);堆的結束地址
                 LDR     R3, = Stack_Mem              ;棧的結束地址
                 BX      LR

                 ALIGN

                 ENDIF

                 END

;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****

2、小結

STM32的啟動步驟如下:

  1. 上電復位后,從 0x0000 0000 地址取出棧頂地址賦給MSP寄存器(主堆棧寄存器),即MSP = __initial_sp。這一步是由硬件自動完成的
  2. 0x0000 0004 地址取出復位程序的地址給PC寄存器(程序計數器),即PC = Reset_Handler。這一步也是由硬件自動完成
  3. 調用SystemInit函數初始化系統時鍾
  4. 跳到C庫的__main函數初始化堆棧(初始化時是根據前面的分配的堆空間和棧空間來初始化的)和代碼重定位(初始RW 和ZI段),然后跳到main函數執行應用程序

三、bootloader分析

1.bootloader分析

由於芯片復位啟動后執行的是bootloader程序,而bootloader的主要作用之一就是可以啟動application。又因為application的空間和bootloader的空間是獨立的。所以要順利啟動applicationbootloader還要做以下處理:

  1. 設置applicationMSP的值; 從0x0800 C000地址取出棧頂地址的值賦給MSP。(0x0800 C000application的起始地址 )
  2. 0x0800 C004 地址取出application第一個執行的函數地址賦值給函數指針fun_point
  3. 使用fun_point函數針跳到application執行
  4. 跳到application后需要設置中斷向量表的偏移;也就是設置VTOR寄存器的值(VTOR寄存器可以到《Cortex-M3權威指南》查看)。為什么要設置向量表偏移?原因也很簡單。因為發生中斷后,cpu要到application的空間找到中斷服務程序的;如果不設置偏移,默認是從bootloader空間找到中斷服務程序的。

簡單總結一下
其實從bootloader跳到application執行和上電復位啟動bootloader差不多。兩者的差別就是前者需要開發者自己設置,后者是由硬件自動完成的。

2.bootloader 程序

typedef void(*App_Fun_t)(void);
typedef void(*Firm_Reload_Fun_t)(void);

/* 定義一個函數指針,主要用於跳到app */
App_Fun_t app_main ;

/* 定義一個函數指針,主要用於調用firmware區的重定位 */
Firm_Reload_Fun_t firm_fun;
int main(void)
{
	/* 初始化串口和滴答定時器 */
	Sys_Init();
	
        
	firm_fun = (Firm_Reload_Fun_t)*(vu32*)(0x0807c000);
	/* frimware的RW和ZI段重定位 */
	firm_fun();
	/* 串口輸出一些信息 */
	Sys_Printf("hello bootloader\r\n");
       
	Sys_Delay_ms(10);
        /* app的起始地址是0x0800C000 */
        /* 簡單的判斷一下app區域的向量表的數據正不正確 */
	if(((*(vu32*)(0x0800C000+4))&0xFF000000)==0x08000000){

            /* 設置應用程序的堆棧 */
            /* 
             * MSR_MSP 是一個函數,原型如下:
             * __asm void MSR_MSP(u32 addr)  //__asm的作用是將告訴編譯器,函數體內部的代碼是匯編指令 
             * {   
             *     // 由 ATPCS 規則,匯編跟C語言傳參使用的是R0-R3寄存器(參數小於等於4個的情況下),所以r0的值就是addr	
             *     MSR MSP, r0 //MSR指令是將r0寄存器的值賦給MSP(主堆棧寄存器)	
             *     BX r14 //r14是lr寄存器(連接寄存器),保存返回地址的。這條指令的作用是函數執行完返回
             * }
             */
	    MSR_MSP(*(vu32*)(0x0800C000));


            /* 從0x0800C004地址取出app第一個執行的函數入口 */
            app_main = (App_Fun_t)*(vu32*)(0x0800C004);

            /* 設置中斷向量表偏移 */
            /* 其實中斷向量的偏移值在這里設置也可以,不過需要注意的是在app的SystemInit函數又重新設置為0了,
             * 所以可以注釋掉app的 SystemInit 的 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;語句,
             * 或者將VECT_TAB_OFFSET宏定義為: #define VECT_TAB_OFFSET  0xC000 
             */
            SCB->VTOR = (FLASH_BASE | 0xC000);


            /* 跳轉到應用程序執行 */
            /* 這里跳過去就不會回來了 */
	    app_main();

	}else{
		Sys_Printf("boot addr error\r\n");
	}
	while(1);

三、application

bootloader跳到application后,application的程序也會做下面的幾件事情:

  1. 執行SystemInit函數(這個是application空間的函數了)
  2. 執行__main(這個也是application空間的函數),然后跳到main函數執行業務程序

application 需要注意的就是,如果使用官方的啟動文件,需要注釋 SystemInit 函數的 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;語句。或者修改VECT_TAB_OFFSET的值。否者在bootloader設置的向量表偏移又會被設置為0了。然后app程序根據業務編寫程序就好。

四、firmware

1、firmware程序的分和實現

通過上面分析啟動文件后,編寫firmware也是比較簡單了。firmwareapplicationbootloader都不在一個同一個工程,所以不能通過函數名調用firmware的區域的函數。要調用firmware區的函數時只能通過函數指針調用。所以在編寫完firmware區域的函數后,還需要將函數的入口地址暴露出來。怎么暴露出來才能讓其他的地方能正確的調用到呢? 其實我們也可以仿照中斷向量表的方式,將函數firmware的所有函數定義成一張表,這張表放在firmware區域的前面地址。函數表的定義如下代碼塊:

                PRESERVE8
                THUMB


; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size


;__Vectors 是firmware的起始地址
;使用DCD指令將函數的入口地址存放在表中。
;下面是我自己實現的函數,感興趣的可以自己定義或者修改

__Vectors       DCD     RW_And_ZI_Init              ;初始化RW段和ZI段
                DCD     Num_Inc                     ;數字自增
                DCD     Num_Dec                     ;數字自減
                DCD     Get_Num                     ;獲取數字的值
                DCD     Get_Num1
                DCD     Get_Num_Addr
                DCD	    Get_Flag
                DCD     Swap_Num                    ;交換數據
                DCD     My_Men_Copy                 ;內存copy					
__Vectors_End

__Vectors_Size  EQU  __Vectors_End - __Vectors

;使用AREA指令定義一個代碼段。然后定義所有的函數在下面。

                AREA    |.text|, CODE, READONLY
                EXPORT  RW_And_ZI_Init            [WEAK]
                EXPORT  Num_Inc                   [WEAK]
                EXPORT  Num_Dec                   [WEAK]
                EXPORT  Get_Num                   [WEAK]
                EXPORT  Get_Num1                  [WEAK]
                EXPORT  Get_Num_Addr              [WEAK]
                EXPORT	Get_Flag                  [WEAK]
                EXPORT  Swap_Num                  [WEAK]
                EXPORT  My_Men_Copy               [WEAK]
                   
RW_And_ZI_Init  	
Num_Inc
Num_Dec
Get_Num
Get_Num1
Get_Num_Addr
Get_Flag
Swap_Num
My_Men_Copy

                B   .
                END

2、firmware注意事項

a.注意事項一

由於firmware沒有初始化堆所以firmware區域的函數不能使用malloccalloc這一系列的函數。同時firmware區域的函數是由application或者bootloader調用執行的,firmware區域不需要初始化和設置棧。

b.注意事項二

因為firmware區域不會調用C庫__main,如果有使用全局變量,則需要自己實現代碼重定位函數。並且需要在application或者bootloader調用執行一次。重定位的程序如下代碼塊:

unsigned int flag;
void RW_And_ZI_Init (void)
{
    /* flag是一個全局變量,但是在執行if判斷的時候並沒有進行重定位,所以這個值是一個隨機值(不確定的) */
    /* flag的作用是防止 RW_And_ZI_Init函數 被調用 */ 
    
    if(flag!=0xf55faa55){   /* 一般第一次執行的時候不會等於0xf55faa55。如果你執行的時候等於0xf55faa55,那么恭喜你,這運氣你可以去買彩票了 */
        /**********這些變量都是是由鏈接器鏈接的時候生成確定的***********/
        extern unsigned char Image$$ER_IROM1$$Limit;      //&Image$$ER_IROM1$$Limit;只讀段的末尾地址,也是可讀可寫數據段的起始地址
        extern unsigned char Image$$RW_IRAM1$$Base;       //&Image$$RW_IRAM1$$Base是可讀可寫數據段的重定位的起始地址
        extern unsigned char Image$$RW_IRAM1$$RW$$Limit;  //&Image$$RW_IRAM1$$Base是RW數據段的重定位的結束地址,也是ZI數據段的重定位的起始地址
        extern unsigned char Image$$RW_IRAM1$$ZI$$Limit;  //&Image$$RW_IRAM1$$ZI$$Limit 是ZI數據段的重定位的結束地址
        /**********這些變量都是是由鏈接器鏈接的時候生成確定的***********/
        
        unsigned char * psrc, *pdst, *plimt;
        
        psrc  = (unsigned char *)&Image$$ER_IROM1$$Limit;
        
        pdst  = (unsigned char *)&Image$$RW_IRAM1$$Base;
        plimt = (unsigned char *)&Image$$RW_IRAM1$$RW$$Limit;
        /* 數據copy,也就是將存在flash空間的數據copy到RAM空間 */
        while(pdst < plimt){
            *pdst++ = *psrc++;
        }
        psrc  = (unsigned char *)&Image$$RW_IRAM1$$RW$$Limit;
        plimt = (unsigned char *)&Image$$RW_IRAM1$$ZI$$Limit;
        /* 初始化ZI段 */
        while(psrc < plimt){
        	*psrc++ = 0;
        }
        /* 執行到這里flag的值和一些未初始化全局變量的值都是0 */
        
        flag=0xf55faa55; //將flag的值設置0xf55faa55防止再次被重定位
    }
}  

c.注意事項三

在調用firware區域的函數時,需要定義的函數指針的格式(即參數和返回值)一定要一致,否則可能發生一些不可預判的錯誤

五、

最后將工程的鏈接附上--->點我project!!!


免責聲明!

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



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