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