STM32 CubeMX
STM32 CubeMX (串口IAP)
- STM32 CubeMX
- IAP有什么用?
- 整體思路
- 一、STM32 CubeMX 設置
- 時鐘樹
- UART使能
- UART初始化設置
- 二、代碼部分
- 文件移植
- 實驗效果
- APP程序返回IAP程序
IAP有什么用?
Iap,全名為in applacation programming,即在應用編程,與之相對應的叫做isp,in system programming,在系統編程,兩者的不同是isp需要依靠燒寫器在單片機復位離線的情況下編程,需要人工的干預,而iap則是用戶自己的程序在運行過程中對User Flash 的部分區域進行燒寫,目的是為了在產品發布后可以方便地通過預留的通信口對產品中的固件程序進行更新升級。在工程應用中經常會出現我們的產品被安裝在某個特定的機械結構中,更新程序的時候拆機很不方便,使用iap技術能很好地降低工作量.
整體思路
實現iap有兩個很重要的前提,首先,單片機程序能對自身的內部flash進行擦寫,第二,單片機要有能夠和外部進行通訊的方式,無論是網絡還是別的方式,只要能傳輸數據就行
要做iap首先我們要知道stm32的啟動流程,流程如下
- 單片機從0x80000000位置啟動,并將該地址當成系統棧頂地址
- 運行到中斷向量表中,默認的中斷向量表為0x80000004,該位置存放復位中斷
- 跳轉到復位中斷處理函數當中,進行系統初始化,然后運行main函數
兩種實現方式:
- 將IAP和APP程序放在一個程序中,會增加代碼的耦合性和難度;
- 要將單片機flash分為兩部份 IAP程序 和 APP程序 ;IAP是燒錄工具燒錄的(如 JTAG 或 ISP 燒入),App是通過串口傳遞bin文件(也可以是其他方式:如藍牙,網卡,CAN等等)然后芯片自己燒錄的
一定要劃分清楚IAP程序和APP程序的flash地址,不要重合了
#1.IAP程序設置,程序地址
#2.APP程序設置,程序地址
生成bin文件
fromelf.exe --bin -o "$L@L.bin" "#L
一、STM32 CubeMX 設置
時鐘樹
UART使能
UART初始化設置
二、代碼部分
移植正點原子的兩部分代碼,IAP和Fashl,也可以移植官方的代碼,有能實現的Dome都可以
文件移植
移植至CubeMX生成的文件夾
添加文件和路徑
修改IAP.C
#define FLASH_APP1_ADDR 0x8005000 //第一個應用程序起始地址(存放在FLASH)
iapfun jump2app;
u16 iapbuf[512];//**緩存區大小,因為stm32f103c8t6的flash一頁是1K的,所以要改小為512**
添加函數
//設置棧頂地址
//addr:棧頂地址
__asm void MSR_MSP(u32 addr)
{MSR MSP, r0 //set Main Stack valueBX r14
}
修改"stmflash.h"
//用戶根據自己的需要設置
#define STM32_FLASH_SIZE 64 //所選STM32的FLASH容量大小(單位為K)
#define STM32_FLASH_WREN 1 //使能FLASH寫入(0,不是能;1,使能)
#define FLASH_WAITETIME 50000 //FLASH等待超時時間//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址
修改UART
#include "usart.h"
#include "stdio.h"
int fputc(int ch, FILE *f) {HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);return ch;
}/* USER CODE BEGIN 0 */
#define USART_REC_LEN 15*1024 //定義最大接收字節數 55K
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
#define RXBUFFERSIZE 1 //緩存大小
uint8_t USART_RX_BUF[USART_REC_LEN] __attribute__ ( ( at ( 0X20001000 ) ) ); //接收緩沖,最大USART_REC_LEN個字節,起始地址為0X20001000.
//接收狀態
//bit15, 接收完成標志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字節數目
uint16_t USART_RX_STA = 0; //接收狀態標記
uint16_t USART_RX_CNT = 0; //接收的字節數
/* USER CODE END 0 */
u8 aRxBuffer[RXBUFFERSIZE];//HAL庫使用的串口接收緩沖UART_HandleTypeDef huart1;/* USART1 init function *///初始化IO 串口1
//bound:波特率
void uart_init(u32 bound)
{ //UART 初始化設置huart1.Instance=USART1; //USART1huart1.Init.BaudRate=bound; //波特率huart1.Init.WordLength=UART_WORDLENGTH_8B; //字長為8位數據格式huart1.Init.StopBits=UART_STOPBITS_1; //一個停止位huart1.Init.Parity=UART_PARITY_NONE; //無奇偶校驗位huart1.Init.HwFlowCtl=UART_HWCONTROL_NONE; //無硬件流控huart1.Init.Mode=UART_MODE_TX_RX; //收發模式HAL_UART_Init(&huart1); //HAL_UART_Init()會使能UART1HAL_UART_Receive_IT(&huart1, (u8 *)aRxBuffer, RXBUFFERSIZE);//該函數會開啟接收中斷:標志位UART_IT_RXNE,并且設置接收緩沖以及接收緩沖接收最大數據量}//UART底層初始化,時鐘使能,引腳配置,中斷配置
//此函數會被HAL_UART_Init()調用
//huart:串口句柄void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{//GPIO端口設置GPIO_InitTypeDef GPIO_Initure;if(huart->Instance==USART1)//如果是串口1,進行串口1 MSP初始化{__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA時鐘__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1時鐘__HAL_RCC_AFIO_CLK_ENABLE();GPIO_Initure.Pin=GPIO_PIN_9; //PA9GPIO_Initure.Mode=GPIO_MODE_AF_PP; //復用推挽輸出GPIO_Initure.Pull=GPIO_PULLUP; //上拉GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA9GPIO_Initure.Pin=GPIO_PIN_10; //PA10GPIO_Initure.Mode=GPIO_MODE_AF_INPUT; //模式要設置為復用輸入模式! HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA10#if EN_USART1_RXHAL_NVIC_EnableIRQ(USART1_IRQn); //使能USART1中斷通道HAL_NVIC_SetPriority(USART1_IRQn,3,3); //搶占優先級3,子優先級3
#endif }
}
extern u8 flag_1;//串口1中斷服務程序
void USART1_IRQHandler(void)
{ u8 Res;if((__HAL_UART_GET_FLAG(& huart1,UART_FLAG_RXNE)!=RESET)) //接收中斷(接收到的數據必須是0x0d 0x0a結尾){Res=USART1->DR; if(USART_RX_CNT<USART_REC_LEN){USART_RX_BUF[USART_RX_CNT]=Res;USART_RX_CNT++; }}flag_1=1;HAL_UART_IRQHandler(& huart1);
}
main.c
int main(void)
{/* USER CODE BEGIN 1 */u16 oldcount = 0; //老的串口接收數據值u16 applenth = 0; //接收到的app代碼長度u16 app_bin = 0;u16 app_enter = 0;HAL_Init();SystemClock_Config();MX_GPIO_Init();uart_init(115200);while ( 1 ){// 首先判斷app代碼的首地址是否為0x0800 000,是則進入app,否的話進行引導區。if ( ( ( * ( vu32* ) ( FLASH_APP1_ADDR + 4 ) ) & 0xFF000000 ) == 0x08000000 ) //判斷是否為0X08XXXXXX.{iap_load_app ( FLASH_APP1_ADDR ); //執行FLASH APP代碼}// 判斷app代碼棧頂是否為0x2000 000,不是則進入升級模式,代碼如下,其中 FLASH_APP1_ADDR=0x8005000;if ( ( ( ( * ( vu32* ) FLASH_APP1_ADDR ) & 0x2FFE0000 ) != 0x20000000 ) ){printf ( "/***** No APP! *****/ \r\n" );printf ( "stm32f103c8t6在線升級 \r\n" );printf ( "選擇對應的app bin文件 \r\n" );printf ( "輸入 A 發送bin文件 \r\n" );printf ( "輸入 E 進入app \r\n" );while ( 1 ){printf ( "滴答!\r\n" );if( flag_1==1){flag_1=0;printf ( "holle wored!\r\n" );}if ( USART_RX_CNT ){if ( oldcount == USART_RX_CNT ) //新周期內,沒有收到任何數據,認為本次數據接收完成.{applenth = USART_RX_CNT;oldcount = 0;USART_RX_CNT = 0;if ( applenth > 100 ){printf ( "用戶程序接收完成!\r\n" );printf ( "代碼長度:%dBytes\r\n", applenth );}}else oldcount = USART_RX_CNT;}HAL_Delay(1000);if ( USART_RX_BUF[0] == 'A' ){if ( applenth )printf ( "\r\n 請發送bin文件 \r\n" );app_bin = 1;applenth = 0;}else if ( app_bin ){if ( applenth ){printf ( "開始更新固件...\r\n" );printf ( "Copying APP2FLASH..." );//此處 0X20001000 地址為串口緩沖區開始接收數據地址if ( ( ( * ( vu32* ) ( 0X20001000 + 4 ) ) & 0xFF000000 ) == 0x08000000 ) //判斷是否為0X08XXXXXX. 串口是否接收到數據{iap_write_appbin ( FLASH_APP1_ADDR, USART_RX_BUF, applenth ); //更新FLASH代碼 printf ( "Copy APP Successed!!" );printf ( "固件更新完成!\r\n" );applenth = 0;app_bin = 0;}}}if ( USART_RX_BUF[0] == 'E' ){if ( applenth )printf ( "\r\n 將要執行APP \r\n" );app_enter = 1;applenth = 0;} if ( app_enter ){printf ( "開始執行FLASH用戶代碼!!\r\n" );if ( ( ( * ( vu32* ) ( FLASH_APP1_ADDR + 4 ) ) & 0xFF000000 ) == 0x08000000 ) //判斷是否為0X08XXXXXX.{iap_load_app ( FLASH_APP1_ADDR ); //執行FLASH APP代碼}else {printf ( "非FLASH應用程序,無法執行!\r\n" );printf ( "Illegal FLASH APP!" );}}}}}/* USER CODE END 3 */
}
實驗效果
APP程序返回IAP程序
執行下面代碼可以重回函數
SCB->VTOR = FLASH_BASE | 0x0000;HAL_NVIC_SystemReset();