记录 STM32 IAP踩坑记录
记录一下研究IAP(在线升级)的踩坑与注意方向
最后一次编辑日期:2024/10/27
前言
以上记录自己踩坑过程,只是实验一下,不是完整的东西。这个东西实验一下后出差了一段时间,文章是回来后整理,可能会有疏漏,欢迎大家指正。
一.进度
二.注意事项以及踩坑
三.待续~~~
一.开发进度
截至2024/10/27日,已经完成:
- 从SD卡读出指定程序并写入Flash内。
- IAP程序向APP程序跳转运行。
待开发功能:
- 使用上位机串口发送数据,并写入单片机
- 升级文件选择功能
- 待续~~~
二.注意事项以及踩坑
-
单片机配置
使用芯片型号STM32F407ZGT6、144-Pin,反客开发板。
按键-PA15、LED-PC13。
SD卡配置为4线模式PC8-11为SDIO、PC12-CK、PD2-CMD。
串口USART1。
其余正常设置,高速时钟.
使用HAL库自带FATFS读取SD卡数据。
![]()
-
单片机内存分配
STM32F40x系列内存如手册
![]()
Flash地址分配如下:
| name | size | Addr |
|---|---|---|
| Bootloader | 32k | 0x8000000--0x8007FFF |
| Params1 | 16K | 0x8008000--0x800BFFF |
| APP1 | 424K | 0x800C000--0x8075FFF |
| APP2 | 424K | 0x8076000--0x80DFFFF |
| Reserve | 128K | 0x80E0000--0x80FFFFF |
请注意,这个地址分配会导致一个问题。向Flash写入数据时,由于地址分配是跨扇区的,而STM32只能写0,如果需要写入1则需要整个扇区擦除,所以使用这个地址分配需要在擦除前先读取部分数据在擦除后再写入。
bsp_aip.h
#ifndef __BSP_IAP_H__
#define __BSP_IAP_H__
#include "main.h"
#include "APP_Usart.h"
#define STM32_FLASH_BASE 0x08000000 //FLASH的起始地址
#define FLASH_SAVE_ADDR 0X0800C004 //存放起始地址,可自己改
//FLASH 扇区的起始地址
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) //扇区0起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) //扇区1起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) //扇区2起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) //扇区3起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) //扇区4起始地址, 64 Kbytes
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) //扇区5起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) //扇区6起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) //扇区7起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) //扇区8起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) //扇区9起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) //扇区10起始地址,128 Kbytes
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) //扇区11起始地址,128 Kbytes
// Bootloader rom siaze :32K
#define bootloader_StartAddr 0x8000000
#define bootloader_endAddr 0x8007FFF
// Params1 siaze :16K
#define Params1_StartAddr 0x8008000
#define Params1_endAddr 0x800BFFF
// APP1 siaze :424K
#define APP1_StartAddr 0x800C000
#define APP1_endAddr 0x8075FFF
// APP2 siaze :424K
#define APP2_StartAddr 0x8076000
#define APP2_endAddr 0x80DFFFF
// Reserve siaze :128K
#define Reserve_StartAddr 0x080E0000
#define Reserve_endAddr 0x080FFFFF
typedef struct param_info{
uint8_t usSAtartFlag;//启动标志 xA(H)正常启动到程序APPx ; 0x0B等待升级 ;
}strParams1;
void STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite) ;
void Flash_check(uint32_t AddresBegin, uint32_t AddresEnd);
void loadAPP(uint32_t unLoadAddr);
#endif
bsp_aip.c
#include "bsp_iap.h"
//从BOOT跳转APP函数
void loadAPP(uint32_t unLoadAddr)
{
void(*FunctionJumoAPP)(void);
uint32_t unJumpAddr;
//检查栈顶地址是否合法,
if( ((*(__IO uint32_t *)unLoadAddr)&0x2FFE0000) == 0x20000000){
printf("%s: ----------------------> run APP addr: 0x%x\r\n", __FUNCTION__, unLoadAddr);
//__disable_irq();
/* 用户代码区第5~8字节为程序开始地址(复位地址) */
unJumpAddr = *(__IO uint32_t *)(unLoadAddr + 4);
FunctionJumoAPP = (void (*)(void))unJumpAddr;
/* 初始化APP堆栈指针(用户代码区的前4个字节用于存放栈顶地址) */
__set_MSP(*(__IO uint32_t *)unLoadAddr);
FunctionJumoAPP();
}
else
{
printf("Addr:%x, &0x2FFE0000= %x",(*(__IO uint32_t *)unLoadAddr),((*(__IO uint32_t *)unLoadAddr)&0x2FFE0000));
printf("ERROR: %s: Stack top address is not valid! Can not run func!\r\n", __FUNCTION__);
while(1);
}
}
/******************************************** F407 ****************************************************/
//读取指定地址的半字(16位数据)
//faddr:读地址
//返回值:对应数据.
uint32_t STMFLASH_ReadWord(uint32_t faddr)
{
return *(volatile uint32_t *)faddr;
}
//获取某个地址所在的flash扇区
//addr:flash地址
//返回值:0~11,即addr所在的扇区
uint16_t STMFLASH_GetFlashSector(uint32_t addr)
{
if(addr<ADDR_FLASH_SECTOR_1)return FLASH_SECTOR_0;
else if(addr<ADDR_FLASH_SECTOR_2)return FLASH_SECTOR_1;
else if(addr<ADDR_FLASH_SECTOR_3)return FLASH_SECTOR_2;
else if(addr<ADDR_FLASH_SECTOR_4)return FLASH_SECTOR_3;
else if(addr<ADDR_FLASH_SECTOR_5)return FLASH_SECTOR_4;
else if(addr<ADDR_FLASH_SECTOR_6)return FLASH_SECTOR_5;
else if(addr<ADDR_FLASH_SECTOR_7)return FLASH_SECTOR_6;
else if(addr<ADDR_FLASH_SECTOR_8)return FLASH_SECTOR_7;
else if(addr<ADDR_FLASH_SECTOR_9)return FLASH_SECTOR_8;
else if(addr<ADDR_FLASH_SECTOR_10)return FLASH_SECTOR_9;
else if(addr<ADDR_FLASH_SECTOR_11)return FLASH_SECTOR_10;
return FLASH_SECTOR_11;
}
void Flash_check(uint32_t AddresBegin, uint32_t AddresEnd){
HAL_StatusTypeDef state;
uint32_t status =0xFFFFFFFFU;
uint32_t addrx=0;
uint32_t endaddr=0;
if(AddresBegin<STM32_FLASH_BASE||AddresBegin%4)return; //起始非法地址
//if(AddresBegin<AddresEnd||AddresEnd%4)return; //起始非法地址
addrx=AddresBegin; //写入的起始地址
endaddr=AddresEnd; //写入的结束地址
uint16_t Num1 = STMFLASH_GetFlashSector(addrx);
uint16_t Num2 = STMFLASH_GetFlashSector(endaddr);
HAL_FLASH_Unlock();
__HAL_FLASH_DATA_CACHE_DISABLE();
//FLASH_Unlock(); //解锁
//FLASH_DataCacheCmd(DISABLE); //FLASH擦除期间,必须禁止数据缓存
if(addrx<0X1FFF0000) //只有主存储区,才需要执行擦除操作!!
{
while(addrx<endaddr) //扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
{
if(STMFLASH_ReadWord(addrx)!=0xFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
{
FLASH_EraseInitTypeDef strFlash_Erase;
uint32_t SectorError;
strFlash_Erase.TypeErase = FLASH_TYPEERASE_SECTORS;//擦除方式
strFlash_Erase.Banks = FLASH_BANK_1;//407只有一个bank(flash_ex.h 316行)
strFlash_Erase.Sector = STMFLASH_GetFlashSector(addrx);
strFlash_Erase.NbSectors = 1;
strFlash_Erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
status = HAL_FLASHEx_Erase(&strFlash_Erase, &SectorError);
//status=FLASH_EraseSector(STMFLASH_GetFlashSector(addrx),VoltageRange_3);//VCC=2.7~3.6V之间!!
if(status!=0xFFFFFFFFU)break; //发生错误了
}else addrx+=4;
}
__HAL_FLASH_DATA_CACHE_ENABLE();
HAL_FLASH_Lock();
//FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存
//FLASH_Lock();//上锁
}
}
//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为4的倍数!!)
//pBuffer:数据指针
//NumToWrite:字(32位)数(就是要写入的32位数据的个数.)
void STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite)
{
HAL_StatusTypeDef state;
uint32_t status =0xFFFFFFFFU;
uint32_t addrx=0;
uint32_t endaddr=0;
if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return; //非法地址
HAL_FLASH_Unlock();
__HAL_FLASH_DATA_CACHE_DISABLE();
//FLASH_Unlock(); //解锁
//FLASH_DataCacheCmd(DISABLE); //FLASH擦除期间,必须禁止数据缓存
addrx=WriteAddr; //写入的起始地址
endaddr=WriteAddr+NumToWrite*4; //写入的结束地址
if(status==0xFFFFFFFFU)
{
while(WriteAddr<endaddr)//写数据
{
uint8_t number = 0;
state = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, WriteAddr,*pBuffer);
switch (state)
{
case HAL_OK://数据写入完成
WriteAddr+=4;
pBuffer++;
break;
case HAL_TIMEOUT://超时重试
if(number>3) break;
else{
number++;
state = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, WriteAddr,*pBuffer);
continue;
}
break;
default://其他消息是为读写错误
break;
break;
}
}
}
__HAL_FLASH_DATA_CACHE_ENABLE();
HAL_FLASH_Lock();
//FLASH_DataCacheCmd(ENABLE); //FLASH擦除结束,开启数据缓存
//FLASH_Lock();//上锁
}
//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToRead:字(4位)数
void STMFLASH_Read(uint32_t ReadAddr,uint32_t *pBuffer,uint32_t NumToRead)
{
uint32_t i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
ReadAddr+=4;//偏移4个字节.
}
}
/*---------------------------------------main.c---------------------------------------------*/
uint32_t flash1_rx_buff[100];//读取flash内容存放到该数组
uint32_t flash1_rx_len=10;
uint32_t flash1_tx_buff[100];//将该数组写入flash
uint32_t flash1_tx_len=10;
uint8_t flash2_rx_buff[100];//读取flash内容存放到该数组
uint8_t flash2_rx_len=10;
uint8_t flash2_tx_buff[100];//将该数组写入flash
uint8_t flash2_tx_len=10;
- 跳转APP函数
RAM地址是由0x20000000开始大小128kb,下面的if 判断读取出的地址是不是在0x20000000与0x2001FFFF之间,即栈顶地址。STM32单片机在每一次复位后都会强制将PC指针指向栈顶地址,这里存放的是整个程序的中断向量表,所以每次跳转至之前都需要判断APP的中断向量表是否合法。
if( ((*(__IO uint32_t *)unLoadAddr)&0x2FFE0000) == 0x20000000);
- APP程序地址
unJumpAddr = *(__IO uint32_t *)(unLoadAddr + 4);
"unLoadAddr " 是App程序存储区域头地址,因为前四位是mps初值,0x04开始是复位向量。
- 踩坑点
这里着重提示一下自己,需要在APP程序启、外设初始化动后,while循环之前加入一个
__enable_irq();//开启总中断
也有说法需要在APP跳转前关闭总中断,然后在使用开启中断的函数__disable_irq();。但是在我测试时发现在我这个开发板上会导致彻底无法开启总中断。
如果不重新开启总中断。会遇到包括但不限于:
- 卡死在hal_delay
- 卡死在定时器中断
- 等所有中断里
hal_delay卡死是因为这个函数需要使用Systick时钟(哪怕你设置的系统时钟不是滴答定时器,而是其他Time)。
针对这个问题,网上有解决方式: - 在main函数开始后使用
HAL_DeInit();(我这里无效) - 使用
__set_FAULTMASK(0);(我这里无效) - 还有一种说法,__enable_irq需要放在整个程序的最初是位置,即
HAL_Init();内第一个行。(我这里无效)
- STM32 Flash写入
这里使用hal库配置的FATFS读取SD卡内指定的文件,然后将bin文件写入单片机Flash内的APP区域。
至于为什么是bin而不是hex,这里也有一个踩坑点,hex是带有格式的文件,并不是完全的机器能执行的二进制代码,如果使用Notpad++(或者记事本)打开就可以看到其中每一行代码是有前、后引号的。这里我是用STM官方的“STM32CubeProgrammer”软件将hex文件写入后是可以看到我写入的头两个byte是引号,我直接从这个地址是无法跳转的。哪怕后移两位其他部分也会因为引号无法运行。
其次,hex并不是二进制的运行文件,而是assicll码显示,这会导致写入的数据和文本方式打开后的hex看起来除了引号以外没什么差别,但是就是无法运行。(这里当时没截图,大家可以自行试一试)
hex转bin的格式转软件有很多,搜索引擎都可以搜到。
以下代码,参考了野火的FATFS读写SD卡,因为只是实验和熟悉这个东西,没有写的很正式,全部放在了main函数里,也方便以后自己查看。
IAP升级APP代码
int fputc(int ch, FILE *f) //重写Printf函数
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100);
//HAL_UART_Transmit_IT(&huart1, (uint8_t *)&ch,1);
return (ch);
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_SDIO_SD_Init();
MX_FATFS_Init();
/* USER CODE BEGIN 2 */
//app_usart_Init(&uart1_queue1,4);
// uint16_t time;
// uint16_t Versions;
// printf("XXX Boot System startup successful\r\n");
// printf("Time:%d:%d",time,time);
// printf("System Versions:%d",Versions);
menus_usart();
uint8_t retUSER; /* Return value for USER */
char USERPath[4]; /* USER logical drive path */
FATFS USERFatFS; /* File system object for USER logical drive */
FIL USERFile; /* File object for USER */
res_sd = f_mount(&USERFatFS,"0",1);
if(res_sd == FR_NO_FILESYSTEM){
printf("》SD卡还没有文件系统,即将进行格式化...\r\n");
/* 格式化 */
res_sd=f_mkfs("0:",FM_FAT32,0,NULL,0);
if (res_sd == FR_OK) {
printf("》SD卡已成功格式化文件系统。\r\n");
/* 格式化后,先取消挂载 */
res_sd = f_mount(NULL,"0:",1);
/* 重新挂载 */
res_sd = f_mount(&USERFatFS,"0:",1);
} else {
printf("《《格式化失败。》》\r\n");
while (1);
}
}
else if (res_sd!=FR_OK) {
printf("!!SD卡挂载文件系统失败。(%d)\r\n",res_sd);
printf("!!可能原因:SD卡初始化不成功。\r\n");
}
else {
printf("》文件系统挂载成功,可以进行读写测试\r\n");
}
uint32_t add = 0x0800c000;
uint8_t Key = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while(1)
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
HAL_Delay(200);
HAL_UART_Receive(&app_USART1,&Key,1,0xff);
if(Key != 0){
//printf("按键值:%d",Key);
if(Key == 0xab){
printf("准备跳转_app1!,请注意观察");
loadAPP(APP1_StartAddr);
}
else if(Key == 0x12){
printf("开始清除Flash\r\n");
Flash_check(APP1_StartAddr,APP1_endAddr);
}
else if(Key == 0x7a){
res_sd=f_open(&USERFile,"0:F407_APP.hex",FA_OPEN_EXISTING|FA_READ);
if( res_sd == FR_OK ) {
printf("》打开F407_APP.hex文件成功。\r\n");
uint32_t data[256];
uint32_t fnum = 1024;
uint32_t WriteAddr = APP1_StartAddr;
uint32_t DATA[8] = {0};
FLASH_EraseInitTypeDef Flash_EraseHandle;
uint32_t state_e = 0;
while(fnum==1024){
res_sd = f_read(&USERFile, data, sizeof(data), &fnum);
if (res_sd==FR_OK) {
//printf("\r\nRead DATA number:%d\r\n",fnum);
HAL_UART_Transmit(&app_USART1,(uint8_t *)data,fnum,0xffff);
printf("\r\n");
//char_2_hex((uint8_t *)data,fnum);
STMFLASH_Write(WriteAddr,data,(fnum%4)? fnum/2+(4-((fnum/2)%4)): fnum/2);
WriteAddr+=fnum/2;
printf("Wr_addr:%x\r\n",WriteAddr);
}else{
printf("Error:%d\r\n",res_sd);
}
}//while
}
else {
printf("!!打开失败:文件不存在 或 文件损坏!\r\n");
}
}
Key = 0;
}
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
HAL_Delay(200);
//ExtiAnalysisData();
// printf("%x:%x\r\n",add,*(__IO uint32_t*)add);
// add = add+4;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}



浙公网安备 33010602011771号