一、串口IAP簡介
1.1 什么是IAP
IAP,英文全稱In Application Programming,在應用中編程。很好理解,就是在程序運行過程中我們進行程序的燒寫,或者叫升級。
1.2 STM32下載程序
我們都知道,STM32可以利用串口下載程序,這是因為ST公司在產線上就在產品中內嵌了自舉程序。所謂的自舉程序,實際就是支持我們通過串口下載程序的代碼。自舉程序被存放在系統存儲區,因此如果我們需要通過串口下載程序,需要將Boot0接高電平,Boot1接低電平,讓程序從系統存儲器開始運行,運行自舉程序。下載完成后我們再將Boot0接地,讓程序從主閃存存儲器開始運行。自舉程序是我們用戶無法修改的。
二、串口IAP有什么作用
上面我們介紹了什么是IAP,那么這個IAP到底有什么作用呢?
首先介紹兩個詞——Bootloader和 Application 。Bootloader實際就是一段引導程序,單片機上電后先執行Bootloader程序,然后再執行用戶編寫的應用程序Application。介紹完這兩個詞,我們來介紹一下IAP有什么作用。比如我們生產了A,B兩款產品。A產品是某個精密器件的一部分,B產品是一款物聯網產品。我們的產品銷售范圍很廣,遠銷海外。
某天A產品的某個客戶反映了一個Bug,我們編寫好了程序,需要進行程序更新,或者叫升級。利用IAP,我們可以在程序運行時,通過預留的通信接口直接燒寫程序。而不需要再把整個設備拆開,像我們調試時那樣下載程序。甚至我們可以直接給客戶郵寄一個小設備,客戶直接進行傻瓜式升級。
又過了一段時間,B產品的程序出現了一個Bug,風險等級比較低,但是依舊需要全體升級程序。我們總不能挨個產品派人去升級,成本極大。這時候又輪到我們的IAP出場了。它可以在所有設備在線運行的情況下,直接通過網絡下發升級程序,實現在線升級,節約了大量的人力成本。
通過上面這兩個例子,大家應該能夠基本了解IAP的用處,使用IAP讓我們不需要再使用調試器進行下載,甚至實現設備的在線升級。
三、啟動流程
在介紹如何實現IAP之前,我們先來簡單了解以下STM32的啟動流程。
3.1 正常啟動流程
這里的正常啟動流程指的是,沒有添加IAP的流程。
正常啟動流程
程序啟動時首先開辟棧空間,配置棧頂指針。然后配置堆空間。配置完成后,建立中斷向量表,在中斷向量表中找到復位中斷,開始執行復位中斷服務函數,然后跳轉到main函數中,執行用戶代碼。當用戶代碼中有中斷請求時,會回到中斷向量表,根據中斷源執行相應的中斷服務函數。
3.2 加入IAP后的啟動流程
下面是加入IAP之后的啟動流程。
加入IAP啟動流程
可以看到,與上面不同的是,加入IAP后,執行完復位中斷服務函數后直接進入IAP的main函數。在執行完IAP之后,跳轉至新寫入程序的復位向量表,取出新程序的復位中斷向量的地址,并跳轉執行新程序的復位中斷服務程序,隨后跳轉至新程序的main 函數。
由上面的兩個啟動過程我們可以看出
? 新程序必須在 IAP 程序之后的某個偏移量為 x 的地址開始。
? 必須將新程序的中斷向量表相應的移動,移動的偏移量為 x。
四、必備知識
44.1 修改程序運行起始地址
點擊魔術棒,選擇“Target”,修改運行起始地址和代碼大小。
修改App運行起始地址
4.2 設置中斷向量表偏移
VTOR 寄存器存放的是中斷向量表的起始地址。如果要設置中斷向量表偏移,只需要在main函數最開始添加如下語句即可
SCB- >VTOR = FLASH BASE | 偏移量:
4.3 生成.bin文件
點擊魔術棒,選擇“User”,按照如下配置,輸入下面的內容
fromelf --bin -o "$L@L.bin" "#L"
生成.bin文件配置
點擊編譯,不報錯就可以,去設置的輸出文件夾中就可以找到對應的.bin文件。
編譯提示
五、串口IAP實現
本次的目標是實現一個串口IAP,也就是編寫Bootloader,在程序運行過程中實現程序的下載。Bootloader程序應該可以通過串口接收上位機發來的.bin文件(App程序),檢查后將.bin文件寫入到Flash特定位置,然后跳轉到App程序運行。
5.1 串口中斷服務函數
本次的串口中斷服務函數與之前不同,這里單獨貼出來。需要定義一個接收數組,接收數組的起始地址限制為0X20001000。
接收數組最多可以接收55K字節,可以根據需要調整。但是需要注意的是,數組的大小需要比App程序要大,而且不能超過芯片的SRAM空間大小。
/*
*==============================================================================
*函數名稱:USART1_IRQHandler
*函數功能:USART1中斷服務函數
*輸入參數:無
*返回值:無
*備 注:無
*==============================================================================
*/
u32 gReceCount = 0; // 接收計數變量
// 接收數組
// 限制起始地址為0X20001000
// 保證偏移量為 0X200的倍數
// 是為了給App留SRAM空間
u8 gReceFifo[USART_RECE_MAX_LEN]__attribute__ ((at(0X20001000)));
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收到一個字節
{
if (gReceCount < USART_RECE_MAX_LEN)
{
gReceFifo[gReceCount++] = USART1- >DR;
}
else
{
printf ("APP code out of memory!rn");
}
}
}
5.2 Flash寫入程序
關于Flash程序,這里就不在贅述,只是貼一下帶檢查的寫入程序。其他具體內容可以到博主STM32速成筆記專欄查看。
/*
*==============================================================================
*函數名稱:Med_Flash_Write
*函數功能:從指定地址開始寫入指定長度的數據
*輸入參數:WriteAddr:寫入起始地址;pBuffer:數據指針;
NumToRead:寫入(半字)數
*返回值:無
*備 注:對內部Flash的操作是以半字為單位,所以讀寫地址必須是2的倍數
*==============================================================================
*/
// 根據中文參考手冊,大容量產品的每一頁是2K字節
#if STM32_FLASH_SIZE < 256
#define STM32_SECTOR_SIZE 1024 // 字節
#else
#define STM32_SECTOR_SIZE 2048
#endif
// 一個扇區的內存
u16 STM32_FLASH_BUF[STM32_SECTOR_SIZE / 2];
void Med_Flash_Write (u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u32 secpos; // 扇區地址
u16 secoff; // 扇區內偏移地址(16位字計算)
u16 secremain; // 扇區內剩余地址(16位計算)
u16 i;
u32 offaddr; // 去掉0X08000000后的地址
// 判斷寫入地址是否在合法范圍內
if (WriteAddr < STM32_FLASH_BASE || (WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
{
return; // 非法地址
}
FLASH_Unlock(); // 解鎖
offaddr = WriteAddr - STM32_FLASH_BASE; // 實際偏移地址
secpos = offaddr / STM32_SECTOR_SIZE; // 扇區地址
secoff = (offaddr % STM32_SECTOR_SIZE) / 2; // 在扇區內的偏移(2個字節為基本單位)
secremain = STM32_SECTOR_SIZE / 2 - secoff; // 扇區剩余空間大小
if (NumToWrite <= secremain)
{
secremain = NumToWrite; // 不大于該扇區范圍
}
while (1)
{
// 讀出整個扇區的內容
Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
// 校驗數據
for (i = 0;i < secremain;i ++)
{
// 需要擦除
if (STM32_FLASH_BUF[secoff + i] != 0XFFFF)
{
break;
}
}
// 需要擦除
if (i < secremain)
{
FLASH_ErasePage(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE); // 擦除這個扇區
// 復制
for (i = 0;i < secremain;i ++)
{
STM32_FLASH_BUF[i + secoff] = pBuffer[i];
}
// 寫入整個扇區
Med_Flash_Write_NoCheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
}
else
{
// 寫已經擦除了的,直接寫入扇區剩余區間
Med_Flash_Write_NoCheck(WriteAddr,pBuffer,secremain);
}
if (NumToWrite == secremain)
{
break; // 寫入結束了
}
// 寫入未結束
else
{
secpos ++; // 扇區地址增1
secoff = 0; // 偏移位置為0
pBuffer += secremain; // 指針偏移
WriteAddr += secremain; // 寫地址偏移
NumToWrite -= secremain; // 字節(16位)數遞減
if (NumToWrite > (STM32_SECTOR_SIZE / 2))
{
secremain = STM32_SECTOR_SIZE / 2; // 下一個扇區還是寫不完
}
else
{
secremain = NumToWrite; // 下一個扇區可以寫完了
}
}
}
FLASH_Lock(); // 上鎖
}
5.3 IAP程序
IAP程序包含兩部分,一部分是將接收到的.bin文件寫入Flash,另一部分是跳轉到App執行。
/*
*==============================================================================
*函數名稱:iap_write_appbin
*函數功能:寫入.bin文件
*輸入參數:appxaddr:App程序起始地址;appbuf:緩存App程序的數組;
appsize:App程序大小
*返回值:無
*備 注:無
*==============================================================================
*/
// 對Flash操作的最小單位是16位
u16 iapbuf[1024]; // 要寫入Flash的內容
void iap_write_appbin (u32 appxaddr,u8 *appbuf,u32 appsize)
{
u16 t; // 臨時循環變量
u16 i = 0; // 自增索引
u16 temp; // 臨時計算變量
u32 fwaddr = appxaddr; // 當前寫入的地址
u8 *dfu = appbuf; // 指向App代碼數組首地址的指針
// for循環,2K為單位進行寫入
for(t = 0;t < appsize;t += 2)
{
temp = (u16)dfu[1] < < 8;
temp += (u16)dfu[0];
dfu += 2; // 偏移2個字節
iapbuf[i++] = temp;
if(i == 1024)
{
i = 0;
Med_Flash_Write (fwaddr,iapbuf,1024);
fwaddr += 2048; // 偏移2048 16=2*8所以要乘以2
}
}
if(i)
{
Med_Flash_Write (fwaddr,iapbuf,i); // 將最后的一些內容字節寫進去
}
}
/*
*==============================================================================
*函數名稱:iap_load_app
*函數功能:跳轉到App
*輸入參數:appxaddr:App程序起始地址
*返回值:無
*備 注:無
*==============================================================================
*/
iapfun jump2app;
void iap_load_app (u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) // 檢查棧頂地址是否合法
{
jump2app=(iapfun)*(vu32*)(appxaddr+4); // 用戶代碼區第二個字為程序開始地址(復位地址)
MSR_MSP(*(vu32*)appxaddr); // 初始化APP堆棧指針(用戶代碼區的第一個字用于存放棧頂地址)
jump2app(); // 跳轉到APP
}
}
5.4 main函數
main函數設計如下
int main(void)
{
u32 curRecCunt = 0; // 當前接收數量
// 設置NVIC中斷分組2:2位搶占優先級,2位響應優先級
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(); // 延時初始化
uart_init(115200); // 串口初始化
printf ("Enter the Bootloader Code!rn");
while(1)
{
// 如果接收到內容且傳輸完成
if (curRecCunt == gReceCount && gReceCount != 0)
{
printf ("App Code reception complete!rn");
printf ("The length of the received App code is %d!rn",gReceCount);
// 開始準備寫入Flash
if(((*(vu32*)(0X20001000+4)) & 0xFF000000) == 0x08000000) // 判斷是否為0X08XXXXXX.
{
iap_write_appbin(FLASH_APP1_ADDR,gReceFifo,gReceCount); // 更新FLASH代碼
gReceCount = 0; // 清零接收計數變量
printf("Update complete!rn");
}
else
{
printf("Update error!rn");
}
// 準備跳轉App執行
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000) //判斷是否為0X08XXXXXX.
{
printf("Enter App!rn");
iap_load_app(FLASH_APP1_ADDR); //執行FLASH APP代碼
}else
{
printf("Enter error!rn");
}
}
else // 未傳輸完成
{
curRecCunt = gReceCount; // 更新當前接收數量
// 延時一定要有,給串口中斷留出時間
// 如果沒有會導致接收不完全
delay_ms(10);
}
}
}
六、注意事項
需要注意的是,上面的程序中App程序是從0x8010000開始,需要配置好程序起始地址和中斷向量表偏移
。
-
寄存器
+關注
關注
31文章
5419瀏覽量
123241 -
STM32
+關注
關注
2289文章
11010瀏覽量
362179 -
上位機
+關注
關注
27文章
960瀏覽量
55560 -
中斷向量
+關注
關注
0文章
14瀏覽量
9071 -
flash內存
+關注
關注
0文章
5瀏覽量
2236
發布評論請先 登錄
STM32L152的IAP移植筆記
[筆記]|[stm32]|[寄存器存儲器區別]|[PWM]|[串口]|[Timer]stm32f103筆記
![[<b class='flag-5'>筆記</b>]|[<b class='flag-5'>stm32</b>]|[寄存器存儲器區別]|[PWM]|[<b class='flag-5'>串口</b>]|[Timer]<b class='flag-5'>stm32</b>f103<b class='flag-5'>筆記</b>](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
STM32實現IAP功能的學習筆記

評論