STM32 學習:IAP 簡單的IAP例子


--- title: mcu-stm32-IAP-1-sample date: 2020-05-27 18:21:53 categories: tags: - iap - stm32 ---

章節概述

以一個最簡單的例子示范IAP程序(沒有文件通訊,沒有跳轉判斷),需要借助IDE進行分區數據的划分以及下載。

准備

IDE:keil-MDK 5

MCU: STM32F103ZET6為例(Flash地址為0x08000000—0x0807ffff,共512KB)。

BSP:STM32-HAL

啟動方式:FLASH啟動

  • 前32KB存放BootLoader程序(0x08000000 ~ 0x08007fff
  • 剩余的空間存放APP程序(0x08008000 ~ 0x0807ffff

假定跳轉的APP程序的實際物理地址為:0x08008000

分別新建2個工程,名字可以為:bootLoaderapp

從Bootloader跳轉到APP

Bootloader

為了區分執行分區的不同,添加串口打印功能。(略)

例如:printf("Hello Bootloader\r\n");

添加下面的代碼以實現跳轉功能:

節選自CubeMX的例程:STM32Cube\Repository\xxx\Projects\Applications\IAP

#define NVIC_VectTab_RAM             ((u32)0x20000000)
#define NVIC_VectTab_FLASH           ((u32)0x08000000)

#define PHYSICAL_ADDRESS_Flash	  (0x08000000) // 程序燒寫的物理地址
#define APPLICATION_POSADDR 	  (0x0000C000) // APP 偏移量
#define APPLICATION_ADDRESS 	  ((PHYSICAL_ADDRESS_Flash) | (APPLICATION_POSADDR)) // 最終跳轉的地址,實際上就是0x08008000
typedef  void (*pFunction)(void);
pFunction JumpToApplication;
uint32_t JumpAddress;

void NVIC_SetVectorTable(uint32_t base, uint32_t offset)
{
    /* close interruption*/
    __set_FAULTMASK(1);

    /* set vector table*/
    //NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0xffset);
    SCB->VTOR = base | offset;

    /* open interruption*/
    __set_FAULTMASK(0);
}

int main(void)
{
    // 在BootLoader程序的中斷向量表指向設置中應有這么一句:
    NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);      //設置中斷向量表指向
    
    /* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
    if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000) //①
    {
        /* Jump to user application */
        JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);// ②
        JumpToApplication = (pFunction) JumpAddress;//③
        /* Initialize user application's Stack Pointer */
        __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS); // ④
        JumpToApplication(); // ⑤
    }

    while (1)
    {}
}

解析:

①因為用戶程序開始位置(0x08008000處)的前4個字節存放的是堆棧的地址,堆棧地址必定是指向RAM空間的,而STM32的RAM空間起始地址為0x20000000,所以要進行判斷。

APPLICATION_ADDRESS存放的是用戶程序Flash的首地址

(*(volatile u32*)APPLICATION_ADDRESS)的意思是取用戶程序首地址里面的數據,這個數據就是用戶代碼的堆棧地址,堆棧地址指向RAM,而RAM的起始地址是0x20000000,因此上面的判斷語句執行:判斷用戶代碼的堆棧地址是否落在:0x20000000~0x2001ffff區間中,這個區間的大小為128K,筆者查閱STM32各型號的RAM大小,目前RAM最大的容量可以做到192K+4K,時鍾頻率為168MHZ。一般情況下,我們使用的芯片較多的落在<128K RAM的區間,因此上面的判斷語句是沒有太大問題的。

②程序跳轉地址的確認,前面已經說過0x08008004處的4個字節存放的是復位函數的入口地址,該句的意思為獲得ApplicationAddress + 4地址處的數據,即為獲得新的復位函數入口地址。

③令Jump_To_Application這個函數指針指向復位函數入口地址。

④堆棧的初始化,調用__set_MSP重新設定棧頂代地址,把棧頂地址設置為用戶代碼指向的棧頂地址。

⑤跳轉到新的復位函數。設置PC指針為復位地址。

App

為了區分執行分區的不同,添加串口打印功能。(略)

例如:printf("Hello App\r\n");

添加下面的代碼以配合跳轉功能:

#define NVIC_VectTab_RAM             ((u32)0x20000000)
#define NVIC_VectTab_FLASH           ((u32)0x08000000)

#define APPLICATION_POSADDR 	  (0x0000C000) // APP 偏移量

void NVIC_SetVectorTable(uint32_t base, uint32_t offset)
{
    /* close interruption*/
    __set_FAULTMASK(1);

    /* set vector table*/
    //NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0xffset);
    SCB->VTOR = base | offset;

    /* open interruption*/
    __set_FAULTMASK(0);
}

int main(void)
{
    // 在用戶程序的中斷向量表指向設置用應有這么一句:
    NVIC_SetVectorTable(NVIC_VectTab_FLASH, APPLICATION_POSADDR);      //設置中斷向量表指向

    ...;
}

工程設置

程序寫完了,但是編譯還需要進行設置。

BootLoader工程

為了確保Address Range不超過0x08008000,超過會導致app分區被覆蓋。

點擊:MDK-Options-Debug-Settings

  • Flash Download,勾選Reset and RunProgramReset and Run
  • 點擊列表中的Flash(如果沒有則需要點擊Add進行添加)
    • 修改Size為:0x08004000(只要不超過0x8000即可)

編譯,燒寫。

APP 工程

為了修改鏈接地址,以及燒錄范圍。

MDK-Options-Target-Read/Only Memory Areas

  • 勾選IROM1:填入0x08008000

MDK-Options-Target-Debug-Settings

  • 點擊Flash Download,勾選Reset and RunProgramReset and Run
  • 點擊列表中的Flash(如果沒有則需要點擊Add進行添加)
    • 修改Start為:0x08008000
    • 修改Size為:0x08004000(只要不超過0x80000即可)

編譯,燒寫。

現象

觀察到下面的打印信息。

Hello Bootloader
Hello app

注意到2段程序都有NVIC_SetVectorTable函數,可以設置將其注釋掉,然后做如下的實驗:

  • 配置BootLoader中打開了定時器中斷,寫好對應的處理函數,例如在中斷響應中打印"Boot"。

  • 配置User app中同樣打開了定時器中斷,寫好了對應的處理函數,例如在中斷響應中打印"User"。

觀察到,盡管兩邊都實現了中斷以及響應,但是都在執行着BootLoader的中斷響應。

IAP實現總結

STM32的IAP方案實現需要在進行用戶程序之前加一段Bootloader程序。

BootLoader程序的作用就是:

  • 要么:設置新的棧頂指針,直接跳轉到用戶程序。
  • 要么:刪除原有的用戶程序,讀取*.bin文件數據並將數據重新寫入新的用戶程序。

對於用戶程序相比普通的編程只需要做三步改動即可:

  • 改變代碼存放的地址空間
  • 改變中斷向量表
  • 修改生成*.bin文件

實際上的IAP工程

本文介紹了簡單的IAP調整,沒有對文件的傳輸以及寫入。

傳輸有很多方法,可以是先把hex文件轉換為bin。再通過通訊方式完成文件傳輸、寫入;或者直接傳輸HEX文件,再在MCU進行解析(難)

我們討論對FLASH的讀寫。

對FLASH的讀寫

問:如何進行對STM32的Flash進行擦除和寫入操作。

答:STM32-HAL庫的讀寫非常方便,但是要注意讀寫地址與頁的關系

使用STM32的固件庫函數,只需調用幾個庫函數即可輕松解決,使用的固件庫為stm32f10x_flash.c文件,對Flash的操作過程簡要為:Flash解鎖Flash擦除Flash寫入Flash上鎖。(對Flash編程的更詳細操作參考STM32F10xxx閃存編程手冊)

//解鎖Flash
FLASH_Unlock();            
FLASH_SetLatency(FLASH_Latency_2);            //因為系統時鍾為72M所以要設置兩個時鍾周期的延時

// 擦除
for(i=0;i<240;i++)
{
    if(FLASH_ErasePage(FLASH_ADDR+i*2048) != FLASH_COMPLETE)      //一定要判斷是否擦除成功
        return ERROR;
    /*
    說明:FLASH_ErasePage(uint32_t Page_Address)即為Flash擦除操作,按頁擦除,每頁2KB,Page_Address為頁的起始地址,如0x08000000是第一頁起始地址,0x08000800為第二頁起始地址,這里的操作擦除了0x08008000—0x0807ffff地址空間的Flash。
    */
}


#if 0
unsigned char buf[1024];            //假設待寫入的代碼數據
unsigned short temp;            //臨時數據

for(i=0;i<512;i++)
{
    temp = (buf[2*i+1]<<8) | buf[2*i];            //2個字節整合為1個半字

    if(FLASH_ProgramHalfWord(ADDR,temp) != FLASH_COMPLETE)      //判斷是否寫入成功
    {
        Return ERROR;
    }
    ADDR +=2;      //地址要加2,因為每次寫入的是2個字節(1個半字)
}
/*
說明:因為STM32的Flash寫入為雙字節(1個半字)寫入,FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)函數即為對地址為Address寫入1個半字的Data,每次寫入完成后地址要加2。
*/

#endif


FLASH_Lock();      //Flash 上鎖,一個固件庫函數即可實現。

問:怎么進行PC指針的強制跳轉,跳轉時需要做些什么。

答:關閉中斷是可選的,設置棧頂指針是必須的。

  • arm內核通用:disable_irq(); //關閉所有中斷 enable_irq();//打開所有中斷
  • 設置棧頂指針:__set_MSP();

附錄:存儲容量與起止地址的關系式

以字節(Byte)為計算單位:存儲容量 = 儲存單元數 × 儲存字長(字節) 。

以Kb為計算單位:儲存單元數 × 儲存字長(字節)÷ 1024 。

首尾地址的差再加一即是儲存單元的個數,每一個儲存單元存放一個字節,可以得到總儲存單元一共有多少字節;

再根據1M = 1024 K = 1024 × 1024Byte,那么就可以很容易地計算出三者的關系了。

例如:容量6K,首地址0x0000,末地址為?

解答:6K = 6 × 1024 Byte ,未地址 = 6 × 1024 + 000H = 1800H

附錄:確認代碼的鏈接地址

為什么在IDE中設置有關的地址呢,為了解答這個問題就需要我們知道:鏈接地址、運行地址、加載地址、存儲地址關系。

  • 運行地址 與 鏈接地址:是類似的。

  • 加載地址 與 存儲地址:是等價的。

鏈接地址

在程序編譯的時候,每個目標文件都是由源代碼編譯得到,最終多個目標文件鏈接生成一個最終的可執行文件,而鏈接地址就是指示鏈接器,各個目標文件的在可執行程序中的位置。

比如,一個可執行程序a.out由a.o、b.o、c.o組成,那么最終的a.out中誰在前、誰在中間、誰在結尾,都可以通過制定鏈接地址來決定。

鏈接地址是靜態的,在進行程序編譯的時候指定的。

注意,CPU不關心鏈接地址是物理地址還是虛擬地址。

總結:

1、鏈接地址是給編譯器用的,用來計算代碼中相關地址偏移的

2、只要和PC值相關的就是位置無關代碼(相對偏移),和PC無關的就是位置相關代碼(絕對值)

運行地址

程序實際在內存中運行時候的地址,比如CPU要執行一條指令,那么必然要通過給PC賦值,從對應的地址空間中去取出來,那么這個地址就是實際的運行地址。

運行地址是動態的,如果你將程序加載到內存中時,改變存放在內存的地址,那么運行地址也就隨之改變了。

注意,CPU同樣不關心運行地址是虛擬地址還是物理地址。

鏈接地址和運行地址區別

首先要區分開兩個概念:位置相關代碼和位置有關代碼。位置無關代碼:mov、b或bl等指令,在跳轉時,地址是PC+相對偏移量,這樣的指令沒有絕對地址,都是相對PC的偏移,這樣,及時運行地址和鏈接地址不一樣,也不影響實際代碼的執行。

位置相關代碼:ldr r0, =label,這里的label(標簽)實際就是鏈接地址,這條指令實質就是將標號的地址放到PC里實現跳轉,但是如果實際運行的地址和鏈接的地址不一樣,這樣就會由於跳轉的地址不是實際運行地址而出錯,這就叫做位置相關代碼。

條件相關代碼和條件無關代碼之間的本質區別就是:指令中相關地址是運行地址還是鏈接地址,如果是運行地址那么就是位置無關代碼,因為運行地址是變化的相對量;如果是加載地址,那么就是絕對量(鏈接時候指定好的),這就是位置相關代碼了。

總結,當鏈接指定的地址和實際運行的地址一樣的時候,鏈接地址==運行地址;當鏈接指定的地址和實際運行的地址不一樣的時候,如果整個代碼中的地址都是相對偏移量,那么整個程序仍然運行暢通無阻,否則,整個程序運行結果就會出錯(因為指定運行地址和實際運行地址不符)。

加載地址

每一個程序一開始都是存放在flash中的,而運行是在內存中,這個時候就需要從flash中將指令讀取到內存中(運行地址),flash的地址就是加載地址(存儲地址)。

指令在flash中存放的存儲地址,就是存儲地址。

所以,加載地址指的是將代碼從一個地址A搬移到地址B,這時候地址A就是加載地址。

附錄:STM32F0xx 實現中斷向量表重定義

在STM32F103等cortex-m3/m4內核的單片機上可以通過設置SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;該寄存器的值來實現中斷向量表的重定義。

在Coretext-M3與M4核中,在System Control Block中存在一個向量表偏移量寄存器 VTOR(0xE000ED08),系統產生中斷后,內核通過這個寄存器的值來找到中斷向量表的地址,進而執行中斷例程代碼。

當然,此寄存器的值是可以修改的,它的默認值為0。實際上在大部分的M3和M4的工程中,一般都是在SystemInit函數中對此寄存器的值進行設置。

由於STM32F0XX采用的是M0核,它是沒有這個VTOR寄存器的,那么它又是怎么找到中斷向量表的地址的呢?

如何將中斷向量表的尋找位置從0x0800 0000修改到0x0800 3000(假設為APP的地址)? 我們重新回顧之前的分析,可以得出有2種方法:

  • 修改寄存器VTOR的值(M3/M4 使用)
  • 內存重映射(M0使用)

通過將SRAM重映射到地址0x0000 0000,那么,M0系統產生中斷后,CPU還是從地址0x0000 0000尋找中斷入口,但是,實際上不再是尋址0x0800 0000,而是尋址0x2000 0000,這么一來,接下來我們就只需要將中斷向量表整個拷貝到SRAM上,也就是0x2000 0000上,就這樣,CPU就可以正常尋址中斷向量表了。

基於內存重映射實現的基本思想:

1、將中斷向量表放入到RAM的起始地址(只需要在應用程序中保留RAM其實地址的0x100大小不使用即可)。

2、在bootload中將應用程序的中斷向量表從Flash中拷貝到RAM中。

3、設置STM32F0xx中斷向量表位於RAM中,主要用到的寄存器如下:

具體實現代碼如下:

/*
 * Function: void clock_init(void)
 * Parameter: none
 * Return: none
*/
int main(void)
{
    memcpy((void*)0x20000000, (void*)APP_FLASHADDR, VECTOR_SIZE);
    SYSCFG->CFGR1 |= 0x03; // SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);

    JumpToApp();
    while (1);
}

memcpy((void*)0x20000000, (void*)APP_FLASHADDR, VECTOR_SIZE);

  • APP_FLASHADDR是應用程序的起址地址,從這里開始的VECTOR_SIZE字節,存放是的應用程序的中斷向量表。
  • VECTOR_SIZE是指中斷向量表的大小,具體多大可以在startup.s文件里計算得到。

VECTOR_SIZE 的計算:

根據startup.s中的語句可以推導出來。

每一個DCD都代表一個中斷向量,例如:

  DCD     USART1_IRQHandler              ; USART1
;這里的“USART1_IRQHandler"其實就是UART1中斷服務程序USART1_IRQHandler這個函數,同時,它也代表這個函數的入口地址。  

這張表包括45個元素,每個元素是一個長度為4字節的地址。除了第一個地址是SP(堆棧指針)外,其它的地址都是某個中斷服務程序的入口地址。

針對本例,就應該是45*4=180(0xB4)個字節。

在執行完以上兩行代碼后,若發生中斷,CPU就會去SRAM(即0x2000 0000處)取中斷向量了,所以,以0x20000000作為起始地址之后的VECTOR_SIZE個字節就不能被改動了。為了達到這VECTOR_SIZE個字節不被修改的目的,如下兩種方法可以實現。

在工程文件內修改SRAM的起始地址及長度:

MDK-Options-Target-Read/Write Memory Areas

  • 勾選IRAM1:填入0x020000B4(大於或等於0x020000B4即可)

如果使用了分散加載文件,則在分散加載文件中修改SRAM的起始地址及長度也能達到目的。


免責聲明!

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



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