最近開發的一個小項目需要支持藍牙在線升級,今天便詳細地了解一番。藍牙在線升級的方式,流程如圖
1.jpg (14.37 KB, 下載次數: 58)
下載附件
2019-3-24 12:06 上傳
流程解釋:產品的最新程序放在云端的服務器上,并將程序更新的提醒通過手機APP推送給用戶,當用戶點擊程序更新時,APP將程序下載至手機上,并通過藍牙傳輸到STM32上,這時單片機解析到的指令為程序更新,便觸發IAP在線刷新程序。
要實現這一功能,必須通過單片機的串口IAP在線升級功能。 1.什么是IAP?
IAP(In Application Programming)即在應用編程,IAP 是用戶自己的程序在運行過程中對User Flash 的部分區域進行燒寫,目的是為了在產品發布后可以方便地通過預留的通信口對產品中的固件程序進行更新升級。
所以要實現IAP功能,固件程序須分為兩個代碼,即引導程序(BootLoader)和用戶程序(APP)。 1.引導程序(BootLoader):只執行串口數據的接收、燒寫程序并將程序執行地址跳轉至用戶程序段(此代碼只能通過JTAG或SWD燒寫) 2.用戶程序(APP):執行用戶所要實現的程序(此代碼通過串口接收,IAP燒寫入單片機flash中)
2.STM32程序的啟動方式
為什么能將程序分成兩個程序分別下載,要理解這個,我們有必要了解一下STM32的啟動方式,因為我用的是STM32F103C8T6,所以就以這個型號為例: STM32上電或者復位后,代碼區始終從0x00000000開始,三種啟動模式其實就是將各自存儲空間的地址映射到0x00000000中。其啟動模式由BOOT0和BOOT1引腳的電平高低來控制。
2.jpg (33.78 KB, 下載次數: 57)
下載附件
2019-3-24 12:06 上傳
1、BOOT1=x BOOT0=0:從Flash啟動,將主Flash地址0x08000000映射到0x00000000,這樣代碼啟動之后就相當于從0x08000000開始,這是正常的工作模式。 2、BOOT1=0 BOOT0=1:從系統存儲器啟動。首先控制BOOT0 BOOT1管腳,復位后,STM32與上述兩種方式類似,從系統存儲器地址0x1FFF F000開始執行代碼,這種模式啟動的程序功能由廠家設置。 3、BOOT1=1 BOOT0=1:從RAM啟動,將RAM地址0x20000000映射到0x00000000,這樣代碼啟動之后就相當于從0x20000000開始,這種模式可以用于調試。 這里,我默認使用的模式是主閃存控制器啟動,也就是從Flash啟動。
3. Flash在STM32內存空間的定義
STM32單片機內存空間有明確的定義,
3.jpg (56.08 KB, 下載次數: 64)
下載附件
2019-3-24 12:06 上傳
查看STM32F103C8T6可知,其Flash是分配在空間從0x0800 0000到0x0801FFFF,最大的空間為127KByte。所以在主閃存控制器啟動模式下,STM32一上電,單片機先將0x0800 0000映射到代碼區,然后從0x0800 0000開始執行程序。 在進入main函數前,單片機還做了以下處理(不需要自己編寫代碼,由單片機內部自動執行)
4.jpg (45.74 KB, 下載次數: 96)
下載附件
2019-3-24 12:06 上傳
Cortex-M3上電后來到復位中斷(已將前4個字節的值存入MSP堆棧指針),轉到__main標號,完成RW段的移動、ZI段的初始化,建立堆棧,初始化庫函數,然后跳轉到main函數,自此就開始執行我們編寫的C程序。
4.引導程序和用戶程序內存空間的劃分
我們知道,單片機默認是從0x0800 0000開始執行的,其過程:
5.jpg (57.77 KB, 下載次數: 64)
下載附件
2019-3-24 12:06 上傳
1.STM32 在復位后,先從 0X08000004 地址取出復位中斷向量的地址,并跳轉到復位中斷服務程序,如圖標號①所示; 2.在復位中斷服務程序執行完之后,會跳轉到我們的main 函數,如圖標號②所示; 3.而我們的 main 函數一般都是一個死循環,在 main 函數執行過程中,如果收到中斷請求(發生重中斷),此時 STM32 強制將 PC 指針指回中斷向量表處,如圖標號③所示; 4.然后,根據中斷源進入相應的中斷服務程序,如圖標號④所示; 5.在執行完中斷服務程序以后,程序再次返回 main 函數執行,如圖標號⑤所示。
6.jpg (9.87 KB, 下載次數: 70)
下載附件
2019-3-24 12:06 上傳
我們將FLash的內存空間分為引導程序和用戶程序,其0x0800 0000~0x0800 2000,8KByte的空間作為BootLoader,將0x0800 2000 ~0x0801 FFFF,共120KByte作為用戶空間(STM32F103C8T6實際只有64KByteFlash,用戶空間為56KByte)。
通過此種方式后,STM32執行程序的流程變為:
7.jpg (85.17 KB, 下載次數: 63)
下載附件
2019-3-24 12:06 上傳
1.STM32 復位后,還是從 0X08000004 地址取出復位中斷向量的地址,并跳轉到復位中斷服務程序,在運行完復位中斷服務程序之后跳轉到 IAP 的 main 函數,如圖標號①所示。 2.此部分同圖 47.1.1 一樣;在執行完 IAP 以后(即將新的 APP 代碼寫入 STM32的 FLASH,灰底部分。 3.新程序的復位中斷向量起始地址為 0X08000004+N+M),跳轉至新寫入程序的復位向量表,取出新程序的復位中斷向量的地址,并跳轉執行新程序的復位中斷服務程序,隨后跳轉至新程序的 main 函數,如圖標號②和③所示, 4.同樣 main 函數為一個死循環,并且注意到此時 STM32 的 FLASH,在不同位置上,共有兩個中斷向量表。
5.引導程序的設計 (1)首先,實現串口中斷函數,藍牙通過串口將數據傳輸到單片機上,因為STM32F103C8T6的RAM只有8KByte,所以上位機每次發送的數據為1KByte,單片機燒寫完后再發送下1KByte數據。
extern uint8_t usart_buf[1024+8]; //數據長度(2B) 數據(1KB) 序號(2B) [CRC(4B)] extern uint16_t buf_cnt; extern uint8_t bootStatus;
void USART1_IRQHandler(void) { uint8_t temp = 0; if( (USART_GetFlagStatus(USART1, USART_IT_RXNE) != RESET) ) { temp = (uint8_t)USART_ReceiveData(USART1); if(++buf_cnt < (1024+8) && ((buf_cnt & 0x8000) != 0x8000)) { usart_buf[buf_cnt - 1] = temp; } else buf_cnt |= 0x8000; } } (2)在main函數中,對接收到的數據進行判斷
if(flag) //是否串口是否有接收到數據 { printf("開始更新固件r\n"); // 判斷APP程序的起始地址是否為0X08XXXXXX if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000) { iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASHFLASH代碼 printf("固件更新成功\r\n"); } else { printf("非固件程序\r\n"); } flag = 0; }
(3)如果檢測到APP程序,進入FLASH燒寫函數
// 從addr起燒錄程序 void Iap_Write(uint32_t addr) { uint16_t temp = 0; uint16_t data_size = 0; //燒錄大小 uint16_t data_len = 0; //數據長度 uint16_t index = 0; //數據塊索引 uint32_t addr_now = addr; //寫入地址 uint8_t data_write[1024] = {0};//數據緩沖 //準備燒錄,數據大小為1K while(1) { while((buf_cnt & 0x8000) == 0);//等待數據接收完畢 //解析數據 data_len = (uint16_t)usart_buf[1] << 8 | usart_buf[0];//獲取data有效長度 for(temp = 2; temp < data_len + 2; temp++)//從第二位開始拷貝數據 { data_write[temp - 2] = usart_buf[temp]; } index = usart_buf[1024 + 3] << 8 + usart_buf[1024 + 2];//獲取索引 //開始寫數據 if(data_len < 1024)//寫入剩下的數據 { if(data_len % 2 != 0)data_len += 1; STMFLASH_Write(addr_now, (uint16_t *)data_write, data_len / 2);// data_size += 1; STMFLASH_Write(IAP_INFO, &data_len, 1); putString("OK\n"); break; } else { STMFLASH_Write(addr_now, (uint16_t*)data_write, data_len / 2); data_size += 1; addr_now += 1024;//下一個1K buf_cnt = 0; for(temp = 0; temp < USART_BUF_SIZE; temp++) usart_buf[temp] = 0; putString("Next\n"); } }//燒錄完成 //清空串口緩存 buf_cnt = 0; for(temp = 0; temp < USART_BUF_SIZE; temp++)usart_buf[temp] = 0; }
(4)燒錄好APP程序后,引導程序將PC指針地址指向APP程序的起始地址,即0x0800 2000;要對PC指針進行操作,需使用MSR匯編指令來操作。
//設置棧頂地址 //addr:棧頂地址 __asm void MSR_MSP(u32 addr) { MSR MSP, r0 //set Main Stack value BX r14 }
然后執行PC指針跳轉函數
void Iap_load(uint32_t addr) { if(((*(vu32*)addr) & 0x2FFE0000) == 0x20000000) { jump = (iapfun) *(vu32*)(addr + 4);//強制轉化為函數 MSR_MSP(*(vu32*)addr); jump(); } else { printf("Error\n"); while(1); } }
(5)最后,main函數的實現就很簡單了,以輪詢的方式讀取串口數據,然后燒寫FLASH,最后跳轉至APP程序
while(1) { while((readBootTime() != 0) && (flag == 0))//以輪詢的方式讀取串口 { if(Iap_wait() == 8) { USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); flag = 1; } } if(flag) { IAP_WRITE(); flag = 0; } else IAP_LOAD(); //跳轉至APP程序 }
6.用戶程序APP的實現
用戶程序實現比較簡單,只需要對地址進行設計一下就可以,所以我就使用點亮LED來作為APP程序
8.jpg (43.92 KB, 下載次數: 62)
下載附件
2019-3-24 12:06 上傳
圖中 IROM1 的起始地址(Start)一般為 0X08000000,大小(Size)為 0X10000,即從 0X08000000 開始的64K 空間為我們的程序存儲(因為我們STM32F103C8T6 的 FLASH大小是 64K)。而圖中,我們設置起始地址(Start)為 0X0800 2000,即偏移量為 0X2000字節),因而,留給 APP 用的 FLASH 空間(Size)只有0X8000(56K 字節)大小了。設置好 Start 和 Szie,就完成 APP 程序的起始地址設置。
(2) 設置APP程序的中斷向量表的偏移量 在 systemInit 函數中的,設置中斷向量表的偏移量
SCB->VTOR = FLASH_BASE | 0x2000;
以上設置完成之后,點擊rebuild按鈕重新編譯,生成LED.hex (3)生成APP程序bin文件 我們通過 MDK 自帶的格式轉換工具 fromelf.exe,來實現.axf 文件到.bin 文件的轉換。該工具在 MDK 的安裝目錄\ARM\BIN40 文件夾里面
9.jpg (55.7 KB, 下載次數: 55)
下載附件
2019-3-24 12:06 上傳
我們就可以在 MDK 編譯成功之后,調用 fromelf.exe,根據當前工程的 LED.axf,生成一個 LED.bin 的文件。
7.APP程序的藍牙在線升級
由于公司云端服務器還沒搭好,我就先自己的電腦藍牙與產品藍牙連接,然后通過用QT寫的串口調試助手發送至STM32中,完成APP程序的升級。
10.jpg (42.11 KB, 下載次數: 53)
下載附件
2019-3-24 12:06 上傳
至此,基于藍牙的STM32 IAP在線升級就完成了!
|