Flash分区 是以STM32F103RET6为主控做的flash分区,主要功能:
boot区:0x0800 0000 到 0x0800 b7FF 地址的flash块划分给bootloader,用于升级固件,大小是46kb 用户参数区:0x0800 B800 到 0x0800 BFFF 的flash块划分为用户参数区(parameters),用于存储用户的一些参数,大小是2Kb APP区:0x0800 C000 到 0x0804 3FFF 的flash块划分为APP区 ,(application)用于存放用户功能应用代码,大小是224Kb APP缓存区:0x0804 4000 到 0x0807 BFFF 的flash块划分为APP缓存区 (update region),用于暂存下发的固件,大小跟应用程序区一样 224kb 未定义:0x0807 C000 到 0x0807 FFFF 的flash块划分未定义区,可以根据具体用途定义,大小是16Kb
代码实现硬件: - fallingstar-board(已开源,打板验证)
软件: 内部flash读写操作这部分比较简单,直接上代码: 读flash操作: - /************************************************************
- * @brief 读取2字节数据
- * @param[in] uint32_t faddr
- * @return NULL
- * @github
- * @date 2021-xx-xx
- * @version v1.0
- * @NOTE NULL
- ***********************************************************/
- uint16_t BSP_FLASH_ReadHalfWord(uint32_t raddr)
- {
- return *(__IO uint16_t*)raddr;
- }
- /************************************************************
- * @brief 读取n(uint16_t)字节数据
- * @param[in] uint32_t ReadAddr
- * @param[out] uint16_t *pBuffer
- * @param[in] uint16_t len
- * @return NULL
- * @github
- * @date 2021-xx-xx
- * @version v1.0
- * @note NULL
- ***********************************************************/
- void BSP_FLASH_Read (uint32_t ReadAddr, uint16_t *pBuffer, uint16_t len )
- {
- uint16_t i;
- for(i=0;i<len;i++)
- {
- pBuffer=BSP_FLASH_ReadHalfWord(ReadAddr); //读取2个字节.
- ReadAddr+=2; //偏移2个字节.
- }
- }
复制代码
写操作,注意写之前要保证是没有写过的区域即可: - /************************************************************
- * @brief 写入n(uint16_t)字节数据
- * @param[in] uint32_t ReadAddr
- * @param[out] uint16_t *pBuffer
- * @param[in] uint16_t len
- * @return NULL
- * @github
- * @date 2021-xx-xx
- * @version v1.0
- * @note NULL
- ***********************************************************/
- void BSP_FLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t len )
- {
- uint16_t i;
- for(i=0;i<len;i++)
- {
- HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer);
- WriteAddr+=2; //地址增加2.
- }
- }
- /************************************************************
- * @brief 写入n(uint16_t)字节数据
- * @param[in] uint32_t WriteAddr
- * @param[in] uint16_t *pBuffer
- * @param[in] uint16_t len
- * @return NULL
- * @github
- * @date 2021-xx-xx
- * @version v1.0
- * @note NULL
- ***********************************************************/
- void BSP_FLASH_Write(uint32_t WriteAddr,uint16_t * pBuffer,uint16_t len )
- {
- uint32_t SECTORError = 0;
- uint16_t sector_off; //扇区内偏移地址(16位字计算)
- uint16_t sector_remain; //扇区内剩余地址(16位字计算)
- uint16_t i;
- uint32_t secor_pos; //扇区地址
- uint32_t offaddr; //去掉0X08000000后的地址
- if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
- HAL_FLASH_Unlock(); //解锁
- offaddr=WriteAddr-FLASH_BASE; //实际偏移地址.
- secor_pos=offaddr/STM_SECTOR_SIZE; //扇区地址 0~127 for STM32F103RBT6
- sector_off=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(2个字节为基本单位.)
- sector_remain=STM_SECTOR_SIZE/2-sector_off; //扇区剩余空间大小
- if(len<=sector_remain)sector_remain=len;//不大于该扇区范围
- while(1)
- {
- BSP_FLASH_Read(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
- for(i=0;i<sector_remain;i++)//校验数据
- {
- if(STMFLASH_BUF[sector_remain+i]!=0XFFFF)
- break;//需要擦除
- }
- if(i<sector_remain)//需要擦除
- {
- //擦除这个扇区
- /* Fill EraseInit structure*/
- EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
- EraseInitStruct.PageAddress = secor_pos*STM_SECTOR_SIZE+FLASH_BASE;
- EraseInitStruct.NbPages = 1;
- HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
- for(i=0;i<sector_remain;i++)//复制
- {
- STMFLASH_BUF[i+sector_off]=pBuffer;
- }
- BSP_FLASH_Write_NoCheck(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区
- }
- else
- BSP_FLASH_Write_NoCheck(WriteAddr,pBuffer,sector_remain);//写已经擦除了的,直接写入扇区剩余区间.
- if(len==sector_remain)
- break;//写入结束了
- else//写入未结束
- {
- secor_pos++; //扇区地址增1
- sector_off=0; //偏移位置为0
- pBuffer+=sector_remain; //指针偏移
- WriteAddr+=sector_remain; //写地址偏移
- len-=sector_remain; //字节(16位)数递减
- if(len>(STM_SECTOR_SIZE/2))
- sector_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
- else
- sector_remain=len;//下一个扇区可以写完了
- }
- };
- HAL_FLASH_Lock();//上锁
- }
复制代码
串口DMA+空闲中断接收不定长数据在上篇文章的基础上,我们对结构体做点修改,增加bin文件数据总长度记录,以及bin文件接收完成标志(小伙伴们可以采用其他办法,不必拘泥于我教程中的方式): - #define Max_RecLen 1024*3
- typedef struct{
- uint8_t RxBuffer[Max_RecLen]; //DMA接收缓冲区
- uint16_t RecDat_len; //单包数据长度
- uint32_t Cur_WriteAddr; //APP缓冲区地址
- uint16_t BinLen; //bin文件数据长度
- uint8_t rec_endFlag; //单包数据接收结束标志
- uint8_t Binrec_endFlag; //bin文件接收结束标志
- uint16_t DMA_TIMCNT; //bin文件下发超时计数器
- }UserUartDMA_Typedef;
复制代码
然后在串口中断中: - /**
- * @brief This function handles USART1 global interrupt.
- */
- void USART1_IRQHandler(void)
- {
- /* USER CODE BEGIN USART1_IRQn 0 */
- uint32_t idle_flag_temp = 0;
- uint16_t len_temp = 0;
- /* USER CODE END USART1_IRQn 0 */
- HAL_UART_IRQHandler(&huart1);
- /* USER CODE BEGIN USART1_IRQn 1 */
- idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
- if(idle_flag_temp)
- {
- __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);
- HAL_UART_DMAStop(&huart1);
- UserUartDma.DMA_TIMCNT = 0;
- UserUartDma.Binrec_endFlag=0;
- len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
- UserUartDma.RecDat_len = Max_RecLen - len_temp;
- UserUartDma.BinLen+=UserUartDma.RecDat_len;
- UserUartDma.rec_endFlag = 1;
- }
- __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
- HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
- /* USER CODE END USART1_IRQn 1 */
- }
复制代码
IAP代码设计扇区擦除,用于写入前擦除相应扇区 - /******************************************************
- * Brief : 擦除APP区
- * Parameter :
- * *startaddr:APP起始地址
- * *pages :要擦除的page = APPSIZE/PAGESIZE
- * Return :None.
- *******************************************************/
- void APPReigion_Erase(uint32_t startaddr,uint16_t pages)
- {
- uint32_t SECTORError = 0;
- //擦除APP区
- /* Fill EraseInit structure*/
- EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
- EraseInitStruct.PageAddress = startaddr;
- EraseInitStruct.NbPages = pages;
- HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
- }
复制代码
升级标志获取与擦除 - /******************************************************
- * Brief : 写入升级标志
- * Parameter :
- * *addr:标志存放地址
- * *pdata:写入的数据
- * Return :None.
- *******************************************************/
- void APP_UpdateFlag_Write(uint32_t addr,uint16_t *pdata)
- {
- BSP_FLASH_Write(addr,pdata,1);
- }
- /******************************************************
- * Brief : 获取升级标志
- * Parameter :
- * *addr:标志存放地址
- * Return :None.
- *******************************************************/
- uint16_t APP_UpdateFlag_Read(uint32_t addr)
- {
- uint16_t flag_temp;
- BSP_FLASH_Read(addr,&flag_temp,1);
- return flag_temp;
- }
复制代码
接下来就是APP缓冲区数据写入,APP区与APP缓冲区数据倒腾了 - /******************************************************
- * Brief : Bin文件写入app缓冲区
- * Parameter :
- * StartAddr: 起始地址
- * *pBin_DataBuf: 要传输的数据
- * packBufLength:单包数据长度
- * Return : None.
- *******************************************************/
- void IAP_WriteBin(uint32_t StartAddr,uint8_t * pBin_DataBuf,uint32_t packBufLength)
- {
- uint16_t pack_len, packlen_Ctr=0, dataTemp;
- uint8_t * pData = pBin_DataBuf;
- for (pack_len = 0; pack_len < packBufLength; pack_len += 2 )
- {
- dataTemp = ( uint16_t ) pData[1]<<8;
- dataTemp += ( uint16_t ) pData[0];
- pData += 2; //偏移2个字节
- ulBuf_Flash_App [ packlen_Ctr ++ ] = dataTemp;
- }
- BSP_FLASH_Write ( UserUartDma.Cur_WriteAddr, ulBuf_Flash_App, packlen_Ctr );
- UserUartDma.Cur_WriteAddr += (packlen_Ctr*2); //偏移packlen_Ctr 16=2*8.所以要乘以2.
- packlen_Ctr = 0;
- }
- /******************************************************
- * Brief : Bin文件从app缓冲区写入app区
- * Parameter :
- * SrcStartAddr: app缓冲区起始地址
- * DstStartAddr: APP区起始地址
- * BinLength:bin文件长度
- * Return : None.
- *******************************************************/
- void IAP_WriteBinToAPPReigon(uint32_t SrcStartAddr,uint32_t DstStartAddr,uint32_t BinLength)
- {
- uint16_t data_temp = 0;
- uint32_t count=0;
- HAL_FLASH_Unlock(); //解锁
- APPReigion_Erase(APP_START_ADDR,APPSIZE/PAGESIZE);
- HAL_Delay(10);
- for(count=0;count<BinLength;count=count+2)
- {
- BSP_FLASH_Read (SrcStartAddr, &data_temp, 1);
- BSP_FLASH_Write_NoCheck(DstStartAddr, &data_temp,1);
- SrcStartAddr+=2;
- DstStartAddr+=2;
- }
- //BSP_FLASH_Write_NoCheck(DstStartAddr, ,1);
- HAL_FLASH_Lock();//上锁
- }
复制代码
最后,要设置APP程序的运行地址 - /******************************************************
- * Brief : 设置栈顶指针
- * Parameter :
- * ulAddr:
- * Return : None.
- *******************************************************/
- __asm void MSR_MSP ( uint32_t ulAddr )
- {
- MSR MSP, r0 //set Main Stack value
- BX r14
- }
- /******************************************************
- * Brief : IAP执行
- * Parameter :
- * ulAddr_App: APP起始地址
- * Return : None.
- *******************************************************/
- void IAP_ExecuteApp ( uint32_t ulAddr_App )
- {
- pIapFun_TypeDef pJump2App;
- if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.
- {
- pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)
- MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
- pJump2App (); //跳转到APP.
- }
- }
复制代码
IAP相关的代码就这些,主要是数据的倒腾,其他倒也没什么复杂的 目前小飞哥对整包bin文件传输完成判断,单包采用DMA+空闲中断的方式,整包文件传输采用的是串口中断在5s内没有数据过来,认为一包数据接收完成,置位接收标志,这部分小伙伴可以自行判断 - static void Systick_Config(void)
- {
- HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); //1ms
- HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
- HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
- }
- void HAL_SYSTICK_Callback(void)
- {
- UserUartDma.DMA_TIMCNT++;
- if(UserUartDma.DMA_TIMCNT>5000)
- {
- UserUartDma.Binrec_endFlag = 1;
- UserUartDma.DMA_TIMCNT = 0;
- }
- TIMX_IRQHandler_user();
- }
复制代码
bin文件传输完成后,写入需要更新标志,重新跳转至boot区,检查是否APP代码需要更新: - uint32_t Task_02()
- {
- if(UserUartDma.rec_endFlag)
- {
- PRINT_INFO("update firmware\n");
- PRINT_INFO("APP 长度:%d字节\n", UserUartDma.RecDat_len);
- UserUartDma.rec_endFlag = 0;
- IAP_WriteBin(UserUartDma.Cur_WriteAddr,UserUartDma.RxBuffer,UserUartDma.RecDat_len);
- memset(UserUartDma.RxBuffer,0,UserUartDma.RecDat_len);
- UserUartDma.RecDat_len = 0;
- }
- if(UserUartDma.BinLen!=0)
- {
- if(UserUartDma.Binrec_endFlag)
- {
- UserUartDma.Binrec_endFlag = 0;
- APP_UpdateFlag_Write(APP_Len_ADDR,&UserUartDma.BinLen); //写入升级标志
- APP_UpdateFlag_Write(APP_UpdateFlag_ADDR,&APP_UPDATE_FLAG); //写入升级标志
- UserUartDma.BinLen = 0;
- IAP_ExecuteApp(FLASH_BASE); //跳转8000000,重新启动
- HAL_Delay(2000);
- }
- }
- }
复制代码
MCU复位之后,初始化执行过程中,对APP升级标志进行检测: - PRINT_INFO("-----IAP Menu--------------\n");
- PRINT_INFO("-----Download APP BIN------\n");
- PRINT_INFO("-----Restart To RUN APP----\n");
- PRINT_INFO("\n\n\n");
- if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
- {
- PRINT_INFO("-----Restart.....5-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....4-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....3-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....2-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....1-------\n");
- HAL_Delay(1000);
- PRINT_INFO("the device need update\n");
- IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
- PRINT_INFO("firmware write OK\n");
- PRINT_INFO("please double click the button the execute the application\n");
- HAL_FLASH_Unlock(); //解锁
- APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE); //擦除APP缓冲区
- APPReigion_Erase(Flash_UNUSED,1); //清除APP更新标志
- HAL_FLASH_Lock(); //上锁
- PRINT_INFO ( "开始执行 APP\n" );
- //执行FLASH APP代码
- IAP_ExecuteApp(APP_START_ADDR);
- }
复制代码
倒计时5秒后,代码更新,并执行 - PRINT_INFO("-----IAP Menu--------------\n");
- PRINT_INFO("-----Download APP BIN------\n");
- PRINT_INFO("-----Restart To RUN APP----\n");
- PRINT_INFO("\n\n\n");
- if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
- {
- PRINT_INFO("-----Restart.....5-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....4-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....3-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....2-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....1-------\n");
- HAL_Delay(1000);
- PRINT_INFO("the device need update\n");
- IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
- PRINT_INFO("firmware write OK\n");
- PRINT_INFO("please double click the button the execute the application\n");
- HAL_FLASH_Unlock(); //解锁
- APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE); //擦除APP缓冲区
- APPReigion_Erase(Flash_UNUSED,1); //清除APP更新标志
- HAL_FLASH_Lock(); //上锁
- PRINT_INFO ( "开始执行 APP\n" );
- //执行FLASH APP代码
- IAP_ExecuteApp(APP_START_ADDR);
- }
- else//不需要更新,执行APP
- {
- IAP_ExecuteApp(APP_START_ADDR);
- }
复制代码
bin文件生成
输入: C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o IAP.bin UART_CircleQueueTest\UART_CircleQueueTest.axf
参数意义: 输出bin文件名称 - UART_CircleQueueTest\UART_CircleQueueTest.axf:
axf文件目录及文件 要注意boot区的地址范围,根据前面的设计,46K,拿出计算器....
|