本文是博主在學習OTA時參考的文章,原作者leafguo,獲得授權後整理髮布,原文鏈接如下:
STM32CubeMx開發之路—在線升級OTA(1/4)—基礎知識
簡介
本文主要講解在線升級(OTA)的基礎知識, 主要是針對IAP OTA
從原理分析
, 分區劃分
, 到代碼編寫
和實驗驗證
等過程闡述這一過程. 幫助大家加深對OTA的認識.
1. OTA基礎知識
什麼是BootLoader?
BootLoader
可以理解成是引導程序, 它的作用是啓動正式的App應用程序
.
換言之, BootLoader
是一個程序, App也是一個程序, BootLoader程序
是用於啓動App程序
的.
STM32中的程序在哪兒?
正常情況下, 我們寫的程序都是放在STM32片內Flash中(暫不考慮外擴Flash).
我們寫的代碼最終會變成二進制文件, 放進Flash中
感興趣的話可以在Keil
>>>Debug
>>>Memory
中查看, 右邊Memory窗口存儲的就是代碼
接下來就可以進入正題了.
進行分區
既然我們寫的程序都會變成二進制文件存放到Flash中, 那麼我們就可以進一步對我們程序進行分區.
我使用的是F103RB-NUCLEO開發板
,他的Flash一共128頁, 每頁1K.見下圖:
以它爲例, 我將它分爲三個區.BootLoader區
、 App1區
、 App2區(備份區)
具體劃分如下圖:
BootLoader區
存放啓動代碼App1區
存放應用代碼App2區
存放暫存的升級代碼
總體流程圖
- 先執行
BootLoader
程序, 先去檢查APP2
區有沒有程序, 如果有就將App2區(備份區)的程序拷貝到App1區
, 然後再跳轉去執行App1
的程序. - 然後執行
App1
程序, 因爲BootLoader
和App1
這兩個程序的向量表不一樣, 所以跳轉到App1
之後第一步是先去更改程序的向量表. 然後再去執行其他的應用程序. - 在應用程序裏面會加入程序升級的部分, 這部分主要工作是拿到升級程序, 然後將他們放到
App2區(備份區)
, 以便下次啓動的時候通過BootLoader
更新App1
的程序.
流程圖如下圖所示:
2. BootLoader的編寫
本節主要講解在線升級(OTA)的BooLoader
的編寫,我將以我例程的BootLoader爲例, 講解BootLoader
(文末會提供免費的代碼下載鏈接),其他的大體上原理都差不多。
流程圖分析
以我例程的BootLoader爲例:
我將App2區
的最後一個字節(0x0801FFFC
)用來表示App2區
是否有升級程序, STM32在擦除之後Flash的數據存放的都是0xFFFFFFFF
, 如果有, 我們將這個地址存放0xAAAAAAAA
. 具體的流程圖見下圖所示
程序編寫和分析
所需STM32的資源有:
- printf的使用, 可以參考以前的博文STM32CubeMx開發之路—3發送USART數據和printf重定向
- Flash的讀寫, 可以參考以前的博文STM32CubeMX開發之路—8Flash讀寫
- 程序跳轉指令:可以參考如下代碼:
/* 採用彙編設置棧的值 */
__asm void MSR_MSP (uint32_t ulAddr)
{
MSR MSP, r0 //設置Main Stack的值
BX r14
}
/* 程序跳轉函數 */
typedef void (*Jump_Fun)(void);
void IAP_ExecuteApp (uint32_t App_Addr)
{
Jump_Fun JumpToApp;
if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //檢查棧頂地址是否合法.
{
JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用戶代碼區第二個字爲程序開始地址(復位地址)
MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆棧指針(用戶代碼區的第一個字用於存放棧頂地址)
JumpToApp(); //跳轉到APP.
}
}
- 在需要跳轉的地方執行這個函數就可以了
IAP_ExecuteApp(Application_1_Addr);
- 其他的代碼請參考
BootLoader
源代碼
源碼
BootLoader源代碼
STM32F103rb_delay_us.zip
3. APP的編寫
本節主要講解在線升級(OTA)的App1
的編寫以及整個流程的說明,我將以我例程的App爲例, 採用Ymodem協議進行串口傳輸,講解App
的編寫(後面會提供免費的代碼下載鏈接), 其他的協議原理大體上都差不多, 都是通過某種協議拿到升級的代碼。
流程圖分析
以我例程的App1爲例:
- 先修改向量表, 因爲本程序是由BootLoader跳轉過來的, 不修改向量表後面會出現問題;
- 打印版本信息, 方便查看不同的App版本;
- 本例程的升級程序採用串口的Ymoderm協議進行傳輸bin文件. 具體的流程圖見下圖所示:
程序編寫和分析
所需STM32的資源有:
- printf的使用, 詳情可參考 STM32CubeMx開發之路—3發送USART數據和printf重定向
- Flash的讀寫, 詳情可參考 STM32CubeMX開發之路—8Flash讀寫
- 串口的DMA收發, 詳情可參考 STM32CubeMx開發之路—4採用DMA方式收發數據
- YModem協議相關的, 詳情可參考 YModem介紹
Ymodem協議
- 百度百科Ymodem協議
- 具體流程可自行查找相關文檔, 這兒提供一個我找到的 XYmodem.pdf.
- Ymodem協議相關介紹可參考我的這篇教程 YModem介紹.
代碼分析
-
代碼大多數都是通過串口實現Ymodem協議的接收, 這兒就不詳細說明
-
後面放了我的源代碼, 詳情請參考我的源代碼.
-
主函數添加修改向量表的指令
- 打印版本信息以及跳轉指令
- YModem相關的文件接收部分
/**
* @bieaf YModem升級
*
* @param none
* @return none
*/
void ymodem_fun(void)
{
int i;
if(Get_state()==TO_START)
{
send_command(CCC);
HAL_Delay(1000);
}
if(Rx_Flag) // Receive flag
{
Rx_Flag=0; // clean flag
/* 拷貝 */
temp_len = Rx_Len;
for(i = 0; i < temp_len; i++)
{
temp_buf[i] = Rx_Buf[i];
}
switch(temp_buf[0])
{
case SOH:///<數據包開始
{
static unsigned char data_state = 0;
static unsigned int app2_size = 0;
if(Check_CRC(temp_buf, temp_len)==1)///< 通過CRC16校驗
{
if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 開始
{
printf("> Receive start...\r\n");
Set_state(TO_RECEIVE_DATA);
data_state = 0x01;
send_command(ACK);
send_command(CCC);
/* 擦除App2 */
Erase_page(Application_2_Addr, 40);
}
else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 結束
{
printf("> Receive end...\r\n");
Set_Update_Down();
Set_state(TO_START);
send_command(ACK);
HAL_NVIC_SystemReset();
}
else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收數據
{
printf("> Receive data bag:%d byte\r\n",data_state * 128);
/* 燒錄程序 */
WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
data_state++;
send_command(ACK);
}
}
else
{
printf("> Notpass crc\r\n");
}
}break;
case EOT://數據包開始
{
if(Get_state()==TO_RECEIVE_DATA)
{
printf("> Receive EOT1...\r\n");
Set_state(TO_RECEIVE_EOT2);
send_command(NACK);
}
else if(Get_state()==TO_RECEIVE_EOT2)
{
printf("> Receive EOT2...\r\n");
Set_state(TO_RECEIVE_END);
send_command(ACK);
send_command(CCC);
}
else
{
printf("> Receive EOT, But error...\r\n");
}
}break;
}
}
}
- 其中部分函數未在以上代碼中展現, 詳情請參看下面的源代碼(免費).
源碼
App1源代碼
STM32F103rb_App1.zip
4. 整體測試
本節主要對前三節的教程做測試驗證 BootLoader
+ App
的升級功能。
源代碼
BootLoader源代碼
STM32F103rb_delay_us.zipApp1源代碼
STM32F103rb_App1.zip
代碼的下載
- 由下圖可知兩份代碼的下載區域是不一樣的,所以他們下載的區域也不一樣。
BootLoader的下載
- BootLoader的代碼默認是最開始的所以不需要特別設置代碼的下載位置
- 按照下圖, 修改擦除方式爲
Erase Sectors
, 大小限制在0X5000
(20K)
- 燒錄代碼
- 運行, 通過串口1打印輸出, 會看到以下打印消息
- 說明BootLoader已經成功運行
App1的下載
- App1稍微複雜一點, 需要將代碼的起始位置設置爲
0x08005000
- 同時也要修改擦除方式爲
Erase Sectors
, 見下圖
- 燒錄代碼
- 運行, 通過串口1打印輸出, 會看到以下打印消息
- 說明
BootLoader
已經成功跳轉到版本號爲0.0.1的App1
生成App2的.bin文件
-
Keil如何生成.bin文件, 請參考這篇博文 Keil如何生成.bin文件
-
修改代碼, 把版本號改爲0.0.2, 並且編譯並且生成.bin文件
-
生成好之後你會得到一個.bin結尾的文件, 這就是我們待會兒YModem要傳輸的文件
使用Xshell進行文件傳輸
- 打開Xshell
- 代碼中, 串口1進行調試信息的打印, 串口2進行YModem升級的
- 所以使用Xshell打開串口2進行文件傳輸, 串口1則可以通過串口調試助手查看調試消息, 具體過程如下
- 你會看到App的版本成功升級到0.0.2了.
- 如果你到了這一步.
- 那麼恭喜你! 你已經能夠使用在線升級了!
5. 總結
通過本幾節的教程, 想必你已經會使用在線升級了, 只要原理知道了其他的問題都可以迎刃而解了, 除了使用YModem協議傳輸.bin文件, 你還可以通過藍牙, WIFI,等其他協議傳輸, 只要能夠將.bin文件傳輸過去, 那其他的部分原理都差不多.
原作者備註:提供一下個人微信號 Hleafleafleaf,歡迎加好友,共同學習!共同進步!