
IAP,即在應用程式設計。很多單片機都支援這個功能,STM32F4 也不例外。在之前的 FLASH模拟 EEPROM 實驗裡面,我們學習了 STM32F4 的 FLASH 自程式設計,本章我們将結合 FLASH自程式設計的知識,通過 STM32F4 的序列槽實作一個簡單的 IAP 功能本章分為如下幾個部分:
33.1 IAP 簡介
33.2 硬體設計
33.3 軟體設計
33.4 下載下傳驗證
33.1 IAP 簡介IAP(In Application Programming)即在應用程式設計,IAP 是使用者自己的程式在運作過程中對User Flash 的部分區域進行燒寫,目的是為了在産品釋出後可以友善地通過預留的通信口對産品中的固件程式進行更新更新。 通常實作 IAP 功能時,即使用者程式運作中作自身的更新操作,需要在設計固件程式時編寫兩個項目代碼,第一個項目程式不執行正常的功能操作,而隻是通過某種通信方式(如 USB、USART)接收程式或資料,執行對第二部分代碼的更新;第二個項目代碼才是真正的功能代碼。這兩部分項目代碼都同時燒錄在 User Flash 中,當晶片上電後,首先是第一個項目代碼開始運作,它作如下操作:
1)檢查是否需要對第二部分代碼進行更新
2)如果不需要更新則轉到 4)
3)執行更新操作
4)跳轉到第二部分代碼執行
第一部分代碼必須通過其它手段,如 JTAG 或 ISP 燒入;第二部分代碼可以使用第一部分
代碼 IAP 功能燒入,也可以和第一部分代碼一起燒入,以後需要程式更新時再通過第一部分 IAP
代碼更新。
我們将第一個項目代碼稱之為 Bootloader 程式,第二個項目代碼稱之為 APP 程式,他們存放在 STM32 FLASH 的不同位址範圍,一般從最低位址區開始存放 Bootloader,緊跟其後的就是 APP 程式(注意,如果 FLASH 容量足夠,是可以設計很多 APP 程式的,本章我們隻讨論一個 APP 程式的情況)。這樣我們就是要實作 2 個程式:Bootloader 和 APP。
STM32 的 APP 程式不僅可以放到 FLASH 裡面運作,也可以放到 SRAM 裡面運作,本章,我們将制作兩個 APP,一個用于 FLASH 運作,一個用于 SRAM 運作。我們先來看看 STM32 正常的程式運作流程,如圖 33.1.1 所示:
圖 33.1.1 STM32 正常運作流程圖
STM32 的内部閃存(FLASH)位址起始于 0x08000000,一般情況下,程式檔案就從此位址開始寫入。此外 STM32 是基于 Cortex-M3 核心的微控制器,其内部通過一張“中斷向量表”來響應中斷,程式啟動後,将首先從“中斷向量表”取出複位中斷向量執行複位中斷程式完成啟動,而這張“中斷向量表”的起始位址是 0x08000004,當中斷來臨,STM32 的内部硬體機制亦會自動将 PC 指針定位到“中斷向量表”處,并根據中斷源取出對應的中斷向量執行中斷服務程式。
在圖 33.1.1 中,STM32 在複位後,先從 0X08000004 位址取出複位中斷向量的位址,并跳
轉到複位中斷服務程式,如圖示号①所示;在複位中斷服務程式執行完之後,會跳轉到我們的
main 函數,如圖示号②所示;而我們的 main 函數一般都是一個死循環,在 main 函數執行過程
中,如果收到中斷請求(發生重中斷),此時 STM32 強制将 PC 指針指回中斷向量表處,如圖
标号③所示;然後,根據中斷源進入相應的中斷服務程式,如圖示号④所示;在執行完中斷服
務程式以後,程式再次傳回 main 函數執行,如圖示号⑤所示。
當加入 IAP 程式之後,程式運作流程如圖 33.1.2 所示:
圖 33.1.2 加入 IAP 之後程式運作流程圖
在圖 33.1.2 所示流程中,STM32 複位後,還是從 0X08000004 位址取出複位中斷向量的位址,并跳轉到複位中斷服務程式,在運作完複位中斷服務程式之後跳轉到 IAP 的 main 函數,
如圖示号①所示,此部分同圖 33.1.1 一樣;在執行完 IAP 以後(即将新的 APP 代碼寫入 STM32
的 FLASH,灰底部分。新程式的複位中斷向量起始位址為 0X08000004+N+M),跳轉至新寫
入程式的複位向量表,取出新程式的複位中斷向量的位址,并跳轉執行新程式的複位中斷服務
程式,随後跳轉至新程式的 main 函數,如圖示号②和③所示,同樣 main 函數為一個死循環,
并且注意到此時 STM32 的 FLASH,在不同位置上,共有兩個中斷向量表。
在 main 函數執行過程中,如果 CPU 得到一個中斷請求,PC 指針仍強制跳轉到位址
0X08000004 中斷向量表處,而不是新程式的中斷向量表,如圖示号④所示;程式再根據我們設
置的中斷向量表偏移量,跳轉到對應中斷源新的中斷服務程式中,如圖示号⑤所示;在執行完
中斷服務程式後,程式傳回 main 函數繼續運作,如圖示号⑥所示。
通過以上兩個過程的分析,我們知道 IAP 程式必須滿足兩個要求:
1) 新程式必須在 IAP 程式之後的某個偏移量為 x 的位址開始;
2) 必須将新程式的中斷向量表相應的移動,移動的偏移量為 x;
本章,我們有 2 個 APP 程式,一個為 FLASH 的 APP,程式在 FLASH 中運作,另外一個
位 SRAM 的 APP,程式運作在 SRAM 中,圖 33.1.2 雖然是針對 FLASH APP 來說的,但是在
SRAM 裡面運作的過程和 FLASH 基本一緻,隻是需要設定向量表的位址為 SRAM 的位址。
1.APP 程式起始位址設定方法随便打開一個之前的執行個體工程,點選 Options for TargetTarget 頁籤,如圖 33.1.3 所示:
圖 33.1.3 FLASH APP Target 頁籤設定
預設的條件下,圖中 IROM1 的起始位址(Start)一般為 0X08000000,大小(Size)為 0X40000,
即從 0X08000000 開始的 256K 空間為我們的程式存儲區。而圖中,我們設定起始位址(Start)
為 0X08004000,即偏移量為 0X4000(16K 位元組),因而,留給 APP 用的 FLASH 空間(Size)
隻有 0X40000-0X4000=0X3C000(240K 位元組)大小了。設定好 Start 和 Szie,就完成 APP 程式
的起始位址設定。
這裡的 16K 位元組,需要大家根據 Bootloader 程式大小進行選擇,比如我們本章的 Bootloader
程式為 14K 左右,理論上我們隻需要確定 APP 起始位址在 Bootloader 之後,并且偏移量為 0X200
的倍數即可(相關知識,請參考:http://www.openedv.com/posts/list/392.htm)。這裡我們選擇
16K(0X4000)位元組,留了一些餘量,友善 Bootloader 以後的更新修改。
這是針對 FLASH APP 的起始位址設定,如果是 SRAM APP,那麼起始位址設定如圖 33.1.4
所示:
圖 33.1.4 SRAM APP Target 頁籤設定
這裡我們将 IROM1 的起始位址(Start)定義為:0X20001000,大小為 0X2800(10K 位元組),
即從位址 0X20000000 偏移 0X1000 開始,存放 APP 代碼。因為整個 STM32F411RCT6 的 SRAM
大 小 為 128K 字 節 , 所 以 IRAM1 ( SRAM ) 的 起 始 地 址 變 為 0X20003800
(0x20001000+0x2800=0X20003800) , 大小 隻 有 0X1C800( 114K 字 節 )。 這 樣 ,整 個
STM32F411RCT6 的 SRAM 配置設定情況為:最開始的 4K 給 Bootloader 程式使用,随後的 10K 存
放 APP 程式,最後 6K,用作 APP 程式的記憶體。這個配置設定關系大家可以根據自己的實際情況修
改,不一定和我們這裡的設定一模一樣,不過也需要注意,保證偏移量為 0X200 的倍數(我們
這裡為 0X1000)。
2.中斷向量表的偏移量設定方法之前我們講解過,在系統啟動的時候,會首先調用 systemInit 函數初始化時鐘系統,同時
systemInit 還完成了中斷向量表的設定,我們可以打開 systemInit 函數,看看函數體的結尾處有
這樣幾行代碼:
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
#endif
從 代 碼可 以 理 解 ,VTOR 寄 存 器存 放 的 是 中斷 向 量 表的 起 始 地 址。 默 認 的 情況
VECT_TAB_SRAM 是沒有定義,是以執行 SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
對于 FLASH APP,我們設定為 FLASH_BASE+偏移量 0x4000,是以我們可以在 FLASH APP
的 main 函數最開頭處添加如下代碼實作中斷向量表的起始位址的重設:
SCB->VTOR = FLASH_BASE | 0x4000;
以上是 FLASH APP 的情況,當使用 SRAM APP 的時候,我們設定起始位址為:
SRAM_bASE+0x1000,同樣的方法,我們在 SRAM APP 的 main 函數最開始處,添加下面代碼:
SCB->VTOR = SRAM_BASE | 0x1000;
這樣,我們就完成了中斷向量表偏移量的設定。
通過以上兩個步驟的設定,我們就可以生成 APP 程式了,隻要 APP 程式的 FLASH 和 SRAM
大小不超過我們的設定即可。不過 MDK 預設生成的檔案是.hex 檔案,并不友善我們用作 IAP
更新,我們希望生成的檔案是.bin 檔案,這樣可以友善進行 IAP 更新(至于為什麼,請大家自
行百度 HEX 和 BIN 檔案的差別!)。這裡我們通過 MDK 自帶的格式轉換工具 fromelf.exe,來
實作.axf 檔案到.bin 檔案的轉換。該工具在 MDK 的安裝目錄ARMBIN40 檔案夾裡面。
fromelf.exe 轉換工具的文法格式為:fromelf [options] input_file。其中 options 有很多選項可
以設定,詳細使用請參考CD光牒《mdk 如何生成 bin 檔案.doc》.
本章,我們通過在 MDK 點選 Options for TargetUser 頁籤,在 After Build/Rebuild 欄,
勾選 Run #1,并寫入:D:toolsmdk5.23ARMARMCCbinfromelf.exe --bin -o ..OBJLED.bin
..OBJLED.axf。如圖 33.1.6 所示:
圖 33.1.6 MDK 生成.bin 檔案設定方法
通過這一步設定,我們就可以在 MDK 編譯成功之後,調用 fromelf.exe(注意,我的 MDK
是安裝在 D:toolMDKMDK5.23 檔案夾下,
如果你是安裝在其他目錄,請根據你自己的目錄修 改 fromelf.exe 的路徑),根據目前工程的 LED.axf(如果是其他的名字,請記住修改,這個文
件存放在 OBJ 目錄下面,格式為 xxx.axf),生成一個 LED.bin 的檔案。并存放在 axf 檔案相同
的目錄下,即工程的 OBJ 檔案夾裡面。在得到.bin 檔案之後,我們隻需要将這個 bin 檔案傳送
給單片機,即可執行 IAP 更新。
最後再來 APP 程式的生成步驟:
1) 設定 APP 程式的起始位址和存儲空間大小對于在 FLASH 裡面運作的 APP 程式,我們可以按照圖 33.1.3 的設定。對于 SRAM 裡
面運作的 APP 程式,我們可以參考圖 33.1.4 的設定。
2) 設定中斷向量表偏移量這一步按照上面講解,重新設定 SCB->VTOR 的值即可。
3) 設定編譯後運作 fromelf.exe,生成.bin 檔案.通過在 User 頁籤,設定編譯後調用 fromelf.exe,根據.axf 檔案生成.bin 檔案,用于
IAP 更新。
以上 3 個步驟,我們就可以得到一個.bin 的 APP 程式,通過 Bootlader 程式即可實作更新。
大家可以打開我們CD光牒的兩個 APP 工程,熟悉這些設定。
33.2 硬體設計本章實驗(Bootloader 部分)功能簡介:開機的時候先顯示提示資訊,然後等待序列槽輸入
接收 APP 程式(無校驗,一次性接收),在序列槽接收到 APP 程式之後,即可執行 IAP。如果
是 SRAM APP,通過按下 KEY0 即可執行這個收到的 SRAM APP 程式。如果是 FLASH APP,
則需要先按下 KEY_UP 按鍵,将序列槽接收到的 APP 程式存放到 STM32 的 FLASH,之後再按
KEY1 既可以執行這個 FLASH APP 程式。DS0 用于訓示程式運作狀态。
本實驗用到的資源如下:
1) 訓示燈 DS0
2) 三個按鍵(KEY0/KEY1/WK_UP)
3) 序列槽
4) 數位管子產品
這些用到的硬體,我們在之前都已經介紹過,這裡就不再介紹了。
33.3 軟體設計本章,我們總共需要 3 個程式:1,Bootloader;2,FLASH APP;3)SRAM APP;其中,
我們選擇之前做過的數位管實驗(在第十七章介紹)來做為 FLASH APP 程式(起始位址為
0X08002800),選擇跑馬燈實驗(在第六章介紹)來做 SRAM APP 程式(起始位址為
0X20001000)。Bootloader 則是通過序列槽通信實驗(在第九章介紹)修改得來。本章,關于 SRAM
APP 和 FLASH APP 的生成比較簡單,我們就不細說,請大家結合CD光牒源碼,以及 33.1 節的介
紹,自行了解。本章軟體設計僅針對 Bootloader 程式。
打開本實驗工程,可以看到我們增加了 IAP 組,在組下面添加了 iap.c 檔案以及其頭檔案
isp.h。
打開 iap.c, 代碼如下:
iapfun jump2app;
u16 iapbuf[512];
//appxaddr:應用程式的起始位址
//appbuf:應用程式 CODE.
//appsize:應用程式大小(位元組).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{
u16 t;
u16 i=0;
u16 temp;
u32 fwaddr=appxaddr;//目前寫入的位址
u8 *dfu=appbuf;
for(t=0;t<appsize;t+=2)
{
temp=(u16)dfu[1]<<8;
temp+=(u16)dfu[0];
dfu+=2;//偏移 2 個位元組
iapbuf[i++]=temp;
if(i==512)
{
i=0;
STMFLASH_Write(fwaddr,iapbuf,512);
fwaddr+=1024;//偏移 1024 16=2*8.是以要乘以 2.
}
}
if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最後的一些内容位元組寫進去.
}
//跳轉到應用程式段
//appxaddr:使用者代碼起始位址.
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)//檢查棧頂位址是否合法.
{
jump2app=(iapfun)*(vu32*)(appxaddr+4);
//使用者代碼區第二個字為程式開始位址(複位位址)
MSR_MSP(*(vu32*)appxaddr);
//初始化 APP 堆棧指針(使用者代碼區的第一個字用于存放棧頂位址)
jump2app(); //跳轉到 APP.
}
}
該檔案總共隻有 2 個函數,其中,iap_write_appbin 函數用于将存放在序列槽接收 buf 裡面的
APP 程式寫入到 FLASH。iap_load_app 函數,則用于跳轉到 APP 程式運作,其參數 appxaddr
為 APP 程式的起始位址,程式先判斷棧頂位址是否合法,在得到合法的棧頂位址後,通過
MSR_MSP 函數(該函數在 sys.c 檔案)設定棧頂位址,最後通過一個虛拟的函數(jump2app)
跳轉到 APP 程式執行代碼,實作 IAPAPP 的跳轉。
打開 iap.h 代碼如下:
#ifndef __IAP_H__
#define __IAP_H__
#include "sys.h"
typedef void (*iapfun)(void); //定義一個函數類型的參數.
#define FLASH_APP1_ADDR
0x08004000 //第一個應用程式起始位址(存放在 FLASH)
//保留 0X08000000~0X08003FFF 的空間為 IAP 使用
void iap_load_app(u32 appxaddr);
//執行 flash 裡面的 app 程式
void iap_load_appsram(u32 appxaddr); //執行 sram 裡面的 app 程式
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 applen); //在指定位址開始,寫入 bin
#endif
這部分代碼比較簡單,。本章,我們是通過序列槽接收 APP 程式的,我們将 usart.c 和 usart.h
做了稍微修改,在 usart.h 中,我們定義 USART_REC_LEN 為 10K 位元組,也就是序列槽最大一次
可以接收 10K 位元組的資料,這也是本 Bootloader 程式所能接收的最大 APP 程式大小。然後新增
一個 USART_RX_CNT 的變量,用于記錄接收到的檔案大小,而 USART_RX_STA 不再使用。
打開 usart.c,可以看到我們修改 USART1_IRQHandler 部分代碼如下:
//序列槽 1 中斷服務程式
//注意,讀取 USARTx->SR 能避免莫名其妙的錯誤
u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));
//接收緩沖,最大 USART_REC_LEN 個位元組,起始位址為 0X20001000.
//接收狀态
//bit15, 接收完成标志
//bit14, 接收到 0x0d
//bit13~0,
接收到的有效位元組數目
u16 USART_RX_STA=0;
//接收狀态标記
u16 USART_RX_CNT=0;
//接收的位元組數
//序列槽 1 中斷服務程式
void USART1_IRQHandler(void)
{
u8 Res;
#if SYSTEM_SUPPORT_OS
//使用 OS
OSIntEnter();
#endif
if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET))
//接收中斷(接收到的資料必須是 0x0d 0x0a 結尾)
{
Res=USART1->DR;
if(USART_RX_CNT<USART_REC_LEN)
{
USART_RX_BUF[USART_RX_CNT]=Res;
USART_RX_CNT++;
}
}
HAL_UART_IRQHandler(&UART1_Handler);
#if SYSTEM_SUPPORT_OS
//使用 OS
OSIntExit();
#endif
}
這裡,我們指定 USART_RX_BUF 的位址是從 0X20001000 開始,該位址也就是 SRAM APP
程式的起始位址。然後在 USART1_IRQHandler 函數裡面,将序列槽發送過來的資料,全部接收
到 USART_RX_BUF,并通過 USART_RX_CNT 計數。代碼比較簡單,我們就不多說了。
最後我們看看 main 函數如下:
int main(void)
{
u8 t;
u8 key;
u16 oldcount=0;
//老的序列槽接收資料值
u16 applenth=0;
//接收到的 app 代碼長度
HAL_Init();
//初始化 HAL 庫
Stm32_Clock_Init(96,4,2,4);
//設定時鐘,96Mhz
delay_init(96);
//初始化延時函數
LED_Init();
//初始化 LED
uart_init(115200);
//初始化序列槽 115200
LED_Init();
//初始化與 LED 連接配接的硬體接口
KEY_Init();
//按鍵初始化
printf("NANO STM32rn");
printf("IAP TESTrn");
printf("WK_UP:Copy APP2FLASHrn");
printf("KEY0:Run SRAM APPrn");
printf("KEY1:Run FLASH APPrn");
while(1)
{
if(USART_RX_CNT)
{
if(oldcount==USART_RX_CNT)
//新周期内,沒有收到任何資料,認為本次資料接收完成.
{
applenth=USART_RX_CNT;
oldcount=0;
USART_RX_CNT=0;
printf("使用者程式接收完成!rn");
printf("代碼長度:%dBytesrn",applenth);
}else oldcount=USART_RX_CNT;
}
t++;
delay_ms(10);
if(t==30)
{
LED0=!LED0;//LED0 閃爍表示程式在運作
t=0;
}
key=KEY_Scan(0);
if(key==WKUP_PRES) //WK_UP 按鍵按下
{
if(applenth)
{
printf("開始更新固件...rn");
if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)
//判斷是否為 0X08XXXXXX.
{
iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);
//更新 FLASH 代碼
printf("固件更新完成!rn");
}else
{
printf("非 FLASH 應用程式!rn");
}
}else
{
printf("沒有可以更新的固件!rn");
}
}
if(key==KEY1_PRES) //KEY1 按鍵按下
{
printf("開始執行 FLASH 使用者代碼!!rn");
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)
//判斷是否為 0X08XXXXXX.
{
iap_load_app(FLASH_APP1_ADDR);//執行 FLASH APP 代碼
}else
{
printf("非 FLASH 應用程式,無法執行!rn");
printf("rn");
}
}
if(key==KEY0_PRES) //KEY0 按鍵按下
{
printf("開始執行 SRAM 使用者代碼!!rn");
if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x20000000)
//判斷是否為 0X20XXXXXX.
{
iap_load_app(0X20001000);//SRAM 位址
}else
{
printf("非 SRAM 應用程式,無法執行!rn");
printf("rn");
}
}
}
}
該段代碼,實作了序列槽資料處理,以及 IAP 更新和跳轉等各項操作。Bootloader 程式就設
計完成了,但是一般要求 bootloader 程式越小越好(給 APP 省空間),是以,本章我們把一些
不需要用到的.c 檔案全部去掉,最後得到工程截圖如圖 33.3.1 所示:
圖 33.3.1 Bootloader 工程截圖
從上圖可以看出,Bootloader 大小為 10K 左右,代碼量還是比較小的。至此,本實驗的軟
件設計部分結束。
FLASH APP 和 SRAM APP 兩部分代碼,根據 30.1 節的介紹,大家自行修改都比較簡單,
我們這裡就不介紹了,不過要提醒大家:FLASH APP 的起始位址必須是 0X08004000,而 SRAM
APP 的起始位址必須是 0X20001000。
33.4 下載下傳驗證在代碼編譯成功之後,打開序列槽調試助手,設定波特率為 115200,我們下載下傳代碼到
ALIENTEK NANO STM32F4 上,得到,如圖 33.4.1 所示:
圖 33.4.1 IAP 程式界面
此時,我們可以通過序列槽,發送 FLASH APP 或者 SRAM APP 到 NANO STM32F4,如圖
33.4.2 所示:
圖 33.4.2 序列槽發送 APP 程式界面
首先找到開發闆 USB 轉序列槽的序列槽号,打開序列槽(我電腦是 COM56),然後設定波特率
為 115200(圖中标号 1 所示),然後,點選打開檔案按鈕(如圖示号 2 所示),找到 APP 程
序生成的.bin 檔案(注意:檔案類型得選擇所有檔案!!預設是隻打開 txt 檔案的),最後點選
發送檔案(圖中标号 3 所示),将.bin 檔案發送給 NANO STM32F4,發送完成後,XCOM 會
提示檔案發送完畢。
開發闆在收到 APP 程式之後,我們就可以通過 KEY0/KEY1 運作這個 APP 程式了(如果
是 FLASH APP,則先需要通過 KEY_UP 将其存入對應 FLASH 區域)。