STM32F7 IAP在线刷写简述 (STM32CUBE+freeRTOS)

文章详细介绍了在STM32F767ZGT6硬件平台上,使用HAL库和KEIL5开发环境中实现固件的In-ApplicationProgramming(IAP)。IAP通过RS232等接口更新程序,避免使用下载器。文中提到了两种IAP实现方式,分别是APP接收和BOOT接收新程序,并分析了它们的优缺点。此外,还阐述了从HEX到BIN的转换方法,以及APP和BOOT在接收、存储和跳转新程序时的代码实现和设置。同时,文章强调了中断管理、CRC校验和中断初始化等关键点,以确保程序安全更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

硬件平台:STM32F767ZGT6,这个差别不大,用了STM32CUBE+freeRTOS,这个也没啥太大差别。

软件平台:KEIL5

使用库:使用的CUBE的HAL库

1.理论知识

1)IAP方法

IAP一般指的就是不使用官方下载器(如STLINK/ULINK/JLINK等),而是借助RS232、RS485、CAN、以太网、USB等等接口来实现程序的编写,具体采用哪种接口对于IAP过程来说没什么区别。

IAP程序一般包括bootloader程序(这个轻易不改的)和app程序(一般改这个),而下载逻辑有两种:

一般新程序的接收代码都是放到APP中,APP接收验证完毕后把新程序存到新程序缓存flash区域,然后软件复位,然后再由boot程序把新程序数据提取到RAM,然后覆盖到正常程序的FALSH区域,然后跳转。。

但是也可以由BOOT直接接收并存储新程序,流程是:APP收到新程序下载申请后写个FLASH标志然后软件复位到boot,boot查询到FALSH更新标志位后发出允许发送程序的握手指令,然后开始接收数据到RAM,接收完毕并校验后直接覆盖到正常程序的FALSH区域,然后跳转。。

两种方式差异如下:

 BOOT接收APP接收
RAM占用不占用APP中RAM需要定义RAM来接收APP数据
FLASH占用FLASH不需要缓存程序区,节省FLASH需要把FLASH分为3部分:boot+正常APP区+缓存APP区
允许APP大小原则上等于整个FLASH区域减去boot大小小于整个FLASH的一半
接收新程序数据时其他程序能否正常执行不可以可以,适合通信线路不稳定、低速等需要大量传输时间的场景
boot程序大小稍大,包含通讯初始化很小,可以只用到FLASH+时钟等,大概十几K以内
整个流程复杂度简单,只需要RAM读取,然后写入FLASH稍复杂,先APP的RAM读取并写入备份FLASH,然后BOOT再RAM读取并写入正常程序FLASH

这两种方法一般APP接收用的比较多。实际编程差别不大。

2)程序格式

我们一般编译出来的是hex,但flash中存储的其实是跟bin一样的,因此我们需要把hex转bin,这有两种方式:

①用keil直接生成bin,上位机读取bin直接发送就可以

keil集成了转bin的功能,我们直接用就可以

红框位置填写: $K\ARM\ARMCC\bin\fromelf.exe --bin --output=@L.bin !L

并把前面打钩

这样每次编译程序会在工程文件目录生成bin,如:

 

 ②用上位机读取hex然后自己转bin

具体方法就不说了,这主要关系到上位机的知识了

2.主要代码及设置方法:

1)FLASH分配

bootloader一般都是从默认FLASH首地址,即0x0800 0000,下图为参考手册截图,预留多大需要自己计算下,如果是使用APP进行数据接收,那么boot不需要太多外设,一般也就十几KB,可以给分sector 0

如果使用App接收,那么需要分为正常APP区域和缓存APP区域,比如正常APP用sector4+5,即0x0802 0000--0x0807 FFFF,缓存APP用sector4+6+7 。

而如果使用boot接收,就不需要缓存APP了。

2)地址偏移

APP程序想正常执行需要设置地址偏移,程序很简单,就是main最开始用这句话:

SCB->VTOR = FLASH_BASE | 0x20000;//设置偏移量,即0x0802 0000

另外需要KEIL设置flash地址:

3)APP定义一个RAM,用来存储通讯外设中断接收的数据

#define BOOT_REC_LEN              256*1024//定义最大接收字节数

uint8_t BOOT_RX_BUF[BOOT_REC_LEN] __attribute__ ((at(0X20021000)));//接收缓冲

4)设立握手信号和逻辑

一般使用握手信号来管理通讯两端的节奏和时序,以下以app接收为例

①.上位机向mcu发送下载申请

②mcu反馈允许发送数据

③上位机开始下载程序,格式要约定好

④mcu接收数据并存到ram

⑤上位机发送数据发送完成的信号,并包含crc等

⑥mcu开始对ram中的数据进行校验,如果正确则反馈一下

⑦mcu把ram中的数据保存到缓存flash

⑧mcu软复位到boot

⑨boot查案缓存flash有无数据,如有则保存到正常app的flash区域,然后清缓存并跳转到正常app,如果缓存flash中无数据,则直接跳转到正常app。

5)app中接收新程序到RAM

这个得看具体用什么外设了,一般用中断,直接接收放到BOOT_RX_BUF中就可以

6)app中把RAM中新程序数据保存到flash

            if(((*(vu32*)(0x20021000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
            {     
                iap_write_appbin(FLASH_APP1_ADDR,BOOT_RX_BUF,applenth);//更新FLASH代码   
                printf("app 固件保存完成!\r\n");    
            }else 
            {   
                printf("非FLASH应用程序!\r\n");
            }    

#define FLASH_APP1_ADDR        0x08080000 

第一句为查询ram中的数据是否是程序的格式,0x20021000即为定义的RAM的地址

其中用到的函数定义如下:

//从指定地址开始写入指定长度的数据
//特别注意:因为STM32F7的扇区实在太大,没办法本地保存扇区数据,所以本函数
//         写地址如果非0XFF,那么会先擦除整个扇区且不保存扇区数据.所以
//         写非0XFF的地址,将导致整个扇区数据丢失.建议写之前确保扇区里
//         没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写. 
//该函数对OTP区域也有效!可以用来写OTP区!
//OTP区域地址范围:0X1FF0F000~0X1FF0F41F
//WriteAddr:起始地址(此地址必须为4的倍数!!)
//pBuffer:数据指针
//NumToWrite:字(32位)数(就是要写入的32位数据的个数.) 
void STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint32_t NumToWrite)	
{ 
    FLASH_EraseInitTypeDef FlashEraseInit;
    HAL_StatusTypeDef FlashStatus=HAL_OK;
    uint32_t SectorError=0;
	uint32_t addrx=0;
	uint32_t endaddr=0;	
    if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return;	//非法地址
    
 	HAL_FLASH_Unlock();             //解锁	
	addrx=WriteAddr;				//写入的起始地址
	endaddr=WriteAddr+NumToWrite*4;	//写入的结束地址
    
    if(addrx<0X1FF00000)
    {
        while(addrx<endaddr)		//扫清一切障碍.(对非FFFFFFFF的地方,先擦除)
		{
			if(STMFLASH_ReadWord(addrx)!=0XFFFFFFFF)//有非0XFFFFFFFF的地方,要擦除这个扇区
			{   
                FlashEraseInit.TypeErase=FLASH_TYPEERASE_SECTORS;       //擦除类型,扇区擦除 
                FlashEraseInit.Sector=STMFLASH_GetFlashSector(addrx);   //要擦除的扇区
                FlashEraseInit.NbSectors=1;                             //一次只擦除一个扇区
                FlashEraseInit.VoltageRange=FLASH_VOLTAGE_RANGE_3;      //电压范围,VCC=2.7~3.6V之间!!
                if(HAL_FLASHEx_Erase(&FlashEraseInit,&SectorError)!=HAL_OK) 
                {
                    break;//发生错误了	
                }
                SCB_CleanInvalidateDCache();                            //清除无效的D-Cache
			}else addrx+=4;
            FLASH_WaitForLastOperation(FLASH_WAITETIME);                //等待上次操作完成
        }
    }
    FlashStatus=FLASH_WaitForLastOperation(FLASH_WAITETIME);            //等待上次操作完成
	if(FlashStatus==HAL_OK)
	{
		while(WriteAddr<endaddr)//写数据
		{
            if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,WriteAddr,*pBuffer)!=HAL_OK)//写入数据
			{ 
				break;	//写入异常
			}
			WriteAddr+=4;
			pBuffer++;
		} 
	}
	HAL_FLASH_Lock();           //上锁
} 


//appxaddr:应用程序的起始地址
//appbuf:应用程序CODE.
//appsize:应用程序大小(字节).
void iap_write_appbin(uint32_t appxaddr,uint8_t *appbuf,uint32_t appsize)
{
	uint32_t t;
	uint16_t i=0;
	uint32_t temp;
	uint32_t fwaddr=appxaddr;//当前写入的地址
	uint8_t *dfu=appbuf;
	for(t=0;t<appsize;t+=4)
	{						   
		temp=(uint32_t)dfu[3]<<24;   
		temp|=(uint32_t)dfu[2]<<16;    
		temp|=(uint32_t)dfu[1]<<8;
		temp|=(uint32_t)dfu[0];	  
		dfu+=4;//偏移4个字节
		iapbuf[i++]=temp;	    
		if(i==512)
		{
			i=0; 
			STMFLASH_Write(fwaddr,iapbuf,512);
			fwaddr+=2048;//偏移2048  512*4=2048
		}
	} 
	if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.  
}


7)app复位跳转到boot

HAL_NVIC_SystemReset();

8)boot查看缓存程序flash区域是否有程序,如有则存到RAM,然后存到正常程序flash

其中BOOT_RX_BUF跟app中定义的一样

		if(((*(vu32*)(0x08080000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
		{	  					
			for(i=0;i<USART_REC_LEN;i++){
				BOOT_RX_BUF[i] = *(__IO uint32_t *)(0x08080000+i);
			}
 			
			iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,USART_REC_LEN);//更新FLASH代码   
			printf("boot固件更新完成!\r\n");
			for(i=0;i<USART_REC_LEN;i++){
				USART_RX_BUF[i]=0;
			}
			
			STMFLASH_Write(0x08080000,(u32*)USART_RX_BUF,USART_REC_LEN/4);//清空缓存flash
			
			printf("boot固件缓存清除完成!\r\n");						
		}

9)boot跳转到APP

		if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
		{	 
			printf("boot开始执行FLASH用户代码!!\r\n");
			iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
		}else 
		{
			printf("boot非FLASH应用程序,无法执行!\r\n");  
		}	
iapfun jump2app; 
u32 iapbuf[512]; 	//2K字节缓存  

//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI  
__asm void WFI_SET(void)
{
	WFI;		  
}
//关闭所有中断(但是不包括fault和NMI中断)
__asm void INTX_DISABLE(void)
{
	CPSID   I
	BX      LR	  
}
//开启所有中断
__asm void INTX_ENABLE(void)
{
	CPSIE   I
	BX      LR  
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr) 
{
	MSR MSP, r0 			//set Main Stack value
	BX r14
}


//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{ 
	if(((*(vu32*)appxaddr)&0x2FF00000)==0x20000000)	//检查栈顶地址是否合法.
	{ 
		INTX_DISABLE();
		HAL_Init();		
        HAL_RCC_DeInit();		
		HAL_DeInit();//不加此句时,CAN4_INT关闭会导致卡死
        __set_PRIMASK(0);//开启总中断,不加此句可能导致外部中断无法进入
		__set_CONTROL(0);

		jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();									//跳转到APP.
	}
}
typedef  void (*iapfun)(void);				//定义一个函数类型的参数.   
#define FLASH_APP1_ADDR		0x08020000  	//第一个应用程序起始地址(存放在FLASH)

3 优化流程

1)app中可以在接收并保存完新app数据后写一个状态来告诉boot可以更新了,或者boot更新前计算一下crc,但这样同样需要保存APP中计算的CRC。这样可以防止app数据写了一半后异常断电导致的死机现象。

2)。。。

4 注意事项

①.boot如果有中断,那么在跳转到app前必须关闭中断,否则跳转到app后还按照boot的向量表来执行就跑飞了

②.boot跳转到app前最好HAL_RCC_DeInit();应该是防止boot和root设置不一样导致的死机

③.如果app使用了freeRTOS,那么最好不要在freeRTOS初始化之前使用HAL_Delay,否则会卡死,测试发现app中freeRTOS初始化之前定时器中断无法进入,而HAL_Delay是依托定时器的,所以卡死,而如果不用app的话HAL_Delay可以正常使用,具体原因不知道,请知道具体原因的朋友告诉我下(实测评论中调整优先级的办法不能解决,搜到不少帖子说也是系统运行之前不能用HAL_Delay)。

④.boot接收新程序数据的流程差不多,使用的代码也差不多,就不多说了

⑤.boot不要使用操作系统,不然可能会导致app内部运行异常(比如某个外部中断无法进入但其他功能都正常,曾经让我掉了不少头发。。。)

由于程序集成在项目里了,就不上传代码了。上面这些应该足够了

另外我使用kvaser进行的can刷写测试的。自己写了个简单的上位机软件,截图供大家理解流程:

另有项目使用串口下载,编写的上位机界面如下:

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值