STM32的IAP在线升级
IAP簡介(簡介部分copy自正點原子)
IAP(In Application Programming)即在應(yīng)用編程,IAP 是用戶自己的程序在運行過程中對User Flash 的部分區(qū)域進行燒寫,目的是為了在產(chǎn)品發(fā)布后可以方便地通過預(yù)留的通信口對產(chǎn)品中的固件程序進行更新升級。 通常實現(xiàn) IAP 功能時,即用戶程序運行中作自身的更新操作,需要在設(shè)計固件程序時編寫兩個項目代碼,第一個項目程序不執(zhí)行正常的功能操作,而只是通過某種通信方式(如 USB、USART)接收程序或數(shù)據(jù),執(zhí)行對第二部分代碼的更新;第二個項目代碼才是真正的功能代碼。這兩部分項目代碼都同時燒錄在 User Flash 中,當(dāng)芯片上電后,首先是第一個項目代碼開始運行,它作如下操作:
1)檢查是否需要對第二部分代碼進行更新
2)如果不需要更新則轉(zhuǎn)到 4)
3)執(zhí)行更新操作
4)跳轉(zhuǎn)到第二部分代碼執(zhí)行
第一部分代碼必須通過其它手段,如 JTAG 或 ISP 燒入;第二部分代碼可以使用第一部分代碼 IAP 功能燒入,也可以和第一部分代碼一起燒入,以后需要程序更新時再通過第一部分 IAP代碼更新。
我們將第一個項目代碼稱之為 Bootloader 程序,第二個項目代碼稱之為 APP 程序,他們存放在 STM32L433FLASH 的不同地址范圍,一般從最低地址區(qū)開始存放 Bootloader,緊跟其后的就是 APP 程序(注意,如果 FLASH 容量足夠,是可以設(shè)計很多 APP 程序的,本章我們只討論一個 APP 程序的情況)。這樣我們就是要實現(xiàn) 2 個程序:Bootloader 和 APP。
STM32L433 的 APP 程序不僅可以放到 FLASH 里面運行,也可以放到 SRAM 里面運行,
程序思路介紹
當(dāng)產(chǎn)品定型投產(chǎn)后每一次燒錄程序都必須去修改BOOT硬件引腳的電平很不方便所以我們需要自己編寫一套BootLoader程序,也就是在單片機FLASH中編寫兩套程序,利用第一套程序通過串口接收的方式將第二套程序?qū)懺谥付ǖ刂返膬?nèi)部FLASH上,然后強制跳轉(zhuǎn)單片機的PC指針實現(xiàn)運行第二套程序的方法,從理論上來說,單片機可以保存無數(shù)套程序并通過強制跳轉(zhuǎn)指針去運行,當(dāng)然前提是你的FLASH足夠大。
我們稱第一套BootLoader程序為引導(dǎo)程序,第二套真正要運行的為APP程序,引導(dǎo)程序只是在單片機上電時執(zhí)行一次,真正工作的是APP程序,下面我們來細說具體原理。
STM32L433內(nèi)部FLASH
我們從最底層的開始看,因為單片機內(nèi)部的FLASH被分配的地址為0x8000000到0x8040000,結(jié)束地址減去起始地址是40000,對應(yīng)的就是262444字節(jié),除以1024剛好是256K字節(jié),這也對應(yīng)著STM32L433的flash為256K。
所以,我們引導(dǎo)程序分配56K字節(jié),APP程序分配200字節(jié)。
單片機上電之后從0x8000000這個地址開始運行程序,STM32L4 的內(nèi)部閃存(FLASH)地址起始于 0x08000000,一般情況下,程序文件就從此地址開始寫入。此外 STM32L4 是基于 Cortex-M4 內(nèi)核的微控制器,其內(nèi)部通過一張“中斷向量表”來響應(yīng)中斷,程序啟動后,將首先從“中斷向量表”取出復(fù)位中斷向量執(zhí)行復(fù)位中斷程序完成啟動,而這張“中斷向量表”的起始地址是 0x08000004,當(dāng)中斷來臨,STM32L4 的內(nèi)部硬件機制亦會自動將 PC 指針定位到“中斷向量表”處,并根據(jù)中斷源取出對應(yīng)的中斷向量執(zhí)行中斷服務(wù)程序。在圖 中,STM32L4 在復(fù)位后,先從 0X08000004 地址取出復(fù)位中斷向量的地址,并跳轉(zhuǎn)到復(fù)位中斷服務(wù)程序,如圖標(biāo)號①所示;在復(fù)位中斷服務(wù)程序執(zhí)行完之后,會跳轉(zhuǎn)到我們的 main 函數(shù),如圖標(biāo)號②所示;而我們的 main 函數(shù)一般都是一個死循環(huán),在 main 函數(shù)執(zhí)行過程中,如果收到中斷請求(發(fā)生重中斷),此時 STM32L4 強制將 PC 指針指回中斷向量表處,如圖標(biāo)號③所示;然后,根據(jù)中斷源進入相應(yīng)的中斷服務(wù)程序,如圖標(biāo)號④所示;在執(zhí)行完中斷服務(wù)程序以后,程序再次返回 main 函數(shù)執(zhí)行,如圖標(biāo)號⑤所示。
如果假如了IAP程序,那么流程就會變成:
因為每次有中斷發(fā)生的時候都會跳轉(zhuǎn)到中斷向量表中,中斷向量表的起始地址為0x8000004,這個是程序強制執(zhí)行的,所以我們在APP程序的第一句,要對中斷向量表的地址進行移位。
SCB->VTOR = FLASH_BASE | 0x10000;
在APP程序的第一句加上或操作即對APP程序的中斷向量表進行的移位,當(dāng)中斷發(fā)生時,程序會自動跳轉(zhuǎn)至移位后的中斷向量表保證程序不會跑飛。
在引導(dǎo)程序中,接收的程序也是直接被存在單片機的SRAM中,STM32L433的SRAM有64K,所以限制了接收的最大字節(jié)數(shù),所以可以采用邊接收邊寫FLASH的辦法,最好也要對接收到的數(shù)據(jù)進行校驗。
程序部分
主函數(shù)
在主函數(shù)中我利用了AT指令的方法去進入BootLoader引導(dǎo)程序,程序上電后5秒沒有收到固定的AT指令就會強制跳轉(zhuǎn)指針到APP應(yīng)用程序,如果收到了AT+BOOTLOADER指令程序即關(guān)閉定時器并準(zhǔn)備接收APP程序,校驗辦法是我會在發(fā)程序之前先發(fā)送APP程序的字節(jié)數(shù),當(dāng)接收到相同字節(jié)的程序即認為接收成功,當(dāng)然這種辦法很蠢,最好的是CRC去校驗。
串口接收程序
#include "usart.h" #include "delay.h" #include "sys.h" #include "timer.h" tSysFlag gtSysFlag; extern TIM_HandleTypeDef TIM7_Handler; //定時器句柄 #if 1 #pragma import(__use_no_semihosting) //標(biāo)準(zhǔn)庫需要的支持函數(shù) struct __FILE {int handle; };FILE __stdout; /*** @brief 定義_sys_exit()以避免使用半主機模式** @param void** @return void*/ void _sys_exit(int x) {x = x; } /*** @brief 重定義fputc函數(shù)** @param ch 輸出字符量* @param f 文件指針** @return void*/ int fputc(int ch, FILE *f) {while((USART1->ISR & 0X40) == 0); //循環(huán)發(fā)送,直到發(fā)送完畢USART1->TDR = (u8) ch;return ch; } #endif#if EN_USART1_RX //如果使能了接收 //串口1中斷服務(wù)程序 //注意,讀取USARTx->SR能避免莫名其妙的錯誤 u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));//接收緩沖,最大USART_REC_LEN個字節(jié),起始地址為0X20001000. uint8_t u8RxBuf[20]; //接收狀態(tài) //bit15, 接收完成標(biāo)志 //bit14, 接收到0x0d //bit13~0, 接收到的有效字節(jié)數(shù)目 u16 USART_RX_STA = 0; //接收狀態(tài)標(biāo)記 u32 USART_RX_CNT=0; //接收的字節(jié)數(shù) uint16_t u16LoopData = 0;UART_HandleTypeDef UART1_Handler; //UART句柄void uart_init(u32 bound) {//UART 初始化設(shè)置UART1_Handler.Instance = USART1; //USART1UART1_Handler.Init.BaudRate = bound; //波特率UART1_Handler.Init.WordLength = UART_WORDLENGTH_8B; //字長為8位數(shù)據(jù)格式UART1_Handler.Init.StopBits = UART_STOPBITS_1; //一個停止位UART1_Handler.Init.Parity = UART_PARITY_NONE; //無奇偶校驗位UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; //無硬件流控UART1_Handler.Init.Mode = UART_MODE_TX_RX; //收發(fā)模式HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()會使能UART1__HAL_UART_ENABLE_IT(&UART1_Handler, UART_IT_RXNE); //開啟接收中斷HAL_NVIC_EnableIRQ(USART1_IRQn); //使能USART1中斷通道HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); //搶占優(yōu)先級3,子優(yōu)先級3 }/*** @brief HAL庫串口底層初始化,時鐘使能,引腳配置,中斷配置** @param huart 串口句柄** @return void*/ void HAL_UART_MspInit(UART_HandleTypeDef *huart) {__HAL_RCC_USART1_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/**USART1 GPIO ConfigurationPB6 ------> USART1_TXPB7 ------> USART1_RX*/GPIO_InitTypeDef GPIO_InitStruct = {0};RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF7_USART1;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); }/*** @brief 串口1中斷服務(wù)程序** @remark 下面代碼我們直接把中斷控制邏輯寫在中斷服務(wù)函數(shù)內(nèi)部* 說明:采用HAL庫處理邏輯,效率不高。** @param void** @return void*/ void USART1_IRQHandler(void) { uint8_t Res;static uint8_t u8Res = 0;if(gtSysFlag.u8GetProgramFlag == true){if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET)) {HAL_UART_Receive(&UART1_Handler,&Res,1,1000); if(USART_RX_CNT<USART_REC_LEN){ USART_RX_BUF[USART_RX_CNT]=Res;USART_RX_CNT++; } }} else if(gtSysFlag.u8GetProgramFlag == false){if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET)) {HAL_UART_Receive(&UART1_Handler,&Res,1,1000); u8RxBuf[u16LoopData]=Res; u16LoopData++;if(u16LoopData >= 20){u16LoopData = 0;}if(Res == 0x0D){u8Res = Res;} if((Res == 0x0A) && (u8Res == 0x0D)){u8Res = 0;u16LoopData = 0;gtSysFlag.u8GetDataFlag = true;} } } HAL_UART_IRQHandler(&UART1_Handler);__HAL_UART_ENABLE_IT(&UART1_Handler, UART_IT_RXNE); }#endif總結(jié)
以上是生活随笔為你收集整理的STM32的IAP在线升级的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单元素/组件的过渡以及过渡钩子的运用
- 下一篇: 当下的力量实践手册读书笔记(1.29)