STM32 MCU固件升级系列之bootloader编写

本系列将从升级流程、boot代码编写、APP代码编写以及固件打包来介绍,硬件选用STM32F407ZGT6(手里只有),来完成这系列教程。

前言

开发STM32固件升级并编写Bootloader时,需要注意以下几个关键点:

  1. 熟悉硬件和数据手册:在开发过程中,确保充分理解STM32微控制器的特性和功能。阅读相关数据手册,了解其内存布局、外设接口以及其他重要信息。

  2. 选择合适的通信接口:根据项目需求选择合适的通信接口进行固件升级,如串口、I2C、SPI、USB等。确保所选接口可以与外部设备(如PC)正常通信。(后续会使用CAN UART)

  3. 定义固件升级协议:设计一个简单且可靠的通信协议,用于在Bootloader和外部设备之间传输数据。协议应包括命令、地址、数据长度、数据包校验等信息。

  4. 保留足够的Bootloader空间:为Bootloader预留足够的程序存储空间。Bootloader的大小可能会随着功能的增加而增大,因此预留一定的余量非常重要。

  5. 安全和鲁棒性:确保Bootloader代码具有良好的异常处理和错误检测能力。避免因意外情况导致的设备损坏或不可恢复状态。

  6. 可扩展性:在设计Bootloader时考虑到未来可能的功能扩展。保持代码结构清晰,易于维护和升级。

  7. 测试与验证:在实际硬件上对Bootloader进行充分测试,确保其下载、擦除、写入等操作的正确性和稳定性。

遵循以上关键点,在开发过程中保持耐心和细致,能有效编写出一个可靠、高效的STM32 Bootloader。

编写Bootloader程序

在编写前我需要确定几个地方:

  • bootloader

  • 确定bootloader存放地址 0x08000000
  • 配置bootloader中断向量表
  • 实现串口或USB等通信接口 UART
  • 编写flash擦除、编程函数
  • 确定应用程序存放地址 0x08000000 + Boot_size + PARAM_SIZE
  • 跳转到应用程序入口 跳转指令
  • 注意事项

  • 确认芯片型号和数据手册,了解芯片的Flash大小和布局
  • 确定应用程序和bootloader的存放地址
  • 确定bootloader的触发方式,如按键触发、超时触发等
  • bootloader需要配置中断向量表,以便跳转到应用程序时正确执行
  • bootloader需要实现串口或USB等通信接口,以便与上位机进行通信
  • bootloader需要编写flash擦除、编程函数,以便将应用程序下载到Flash中
  • bootloader需要检查应用程序的合法性,如校验和、签名等
  • bootloader需要跳转到应用程序入口,启动应用程序
  • 在MCU中,bootloader主要作用引导进入APP1程序,检测升级标注位是否需要将备份APP2覆盖到APP1中(升级新程序)或者接收升级包进行升级(可以是CAN、UART通信或者读取SD卡获取升级包)。bootloader程序尽量保持简洁,不需要用到资源统统去掉(很容易出现问题,往往初始化的外设资源,最好都去初始化(保留原来的状态)),保证小巧、稳定和扩展性。
    需要实现的功能:flash擦除接口和通信接口,主要这两个,还需要添加状态和标注位。

    Flash读写和擦除

    Flash 功能接口

    u32 STMFLASH_ReadWord(u32 faddr)
    {
    	return *(vu32*)faddr; 
    }  
    
    uint16_t STMFLASH_GetFlashSector(u32 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 STMFLASH_Write(u32 WriteAddr,u32 *pBuffer,u32 NumToWrite)	
    { 
      FLASH_Status status = FLASH_COMPLETE;
    	u32 addrx=0;
    	u32 endaddr=0;	
      if(WriteAddr<STM32_FLASH_BASE||WriteAddr%4)return;	//非法地址
    	FLASH_Unlock();									//解锁 
      FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存
     		
    	addrx=WriteAddr;				//写入的起始地址
    	endaddr=WriteAddr+NumToWrite*4;	//写入的结束地址
    	if(status==FLASH_COMPLETE)
    	{
    		while(WriteAddr<endaddr)//写数据
    		{
    			if(FLASH_ProgramWord(WriteAddr,*pBuffer)!=FLASH_COMPLETE)//写入数据
    			{ 
    				break;	//写入异常
    			}
    			WriteAddr+=4;
    			pBuffer++;
    		} 
    	}
      FLASH_DataCacheCmd(ENABLE);	//FLASH擦除结束,开启数据缓存
    	FLASH_Lock();//上锁
    } 
    
    void STMFLASH_Read(u32 ReadAddr,u32 *pBuffer,u32 NumToRead)   	
    {
    	u32 i;
    	for(i=0;i<NumToRead;i++)
    	{
    		pBuffer[i]=STMFLASH_ReadWord(ReadAddr);//读取4个字节.
    		ReadAddr+=4;//偏移4个字节.	
    	}
    }
    

    实现在SD卡寻找APP程序,进行升级。

    	FIL fnew;                         /* 文件对象 */
    	FRESULT res_sd = FR_OK;                   /* 文件操作结果 */
    	UINT fnum;                        /* 文件成功读写数量 */
    	BYTE ReadBuffer[512]={0};        /* 读缓冲区 */
    	
    	u8 checknum = 0;
    	int writeSum = 0;
     	while(SD_Init())
    	{
    
    	}
     	exfuns_init();									 
      f_mount(fs[0],"0:",1); 					
    	Flash_Erase(APP1_ADDRESS);
    	while(1)
    	{
    		t++; 
    		res_sd = f_open(&fnew, "0:APP2.bin", FA_OPEN_EXISTING | FA_READ);
    		if(res_sd == FR_OK)//有升级文件
    		{
    			printf("open bootloader\r\n");
    			fnum = 1;
    			do
    			{
    				res_sd = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
    				if(res_sd == FR_OK)
    				{
    						printf("##");
    						if(fnum!=0)
    						STMFLASH_Write(APP1_ADDRESS+writeSum,(u32*)ReadBuffer,fnum/4);
    						writeSum += fnum;
    						for(int i = 0 ; i < fnum;i++)
    						{
    							checknum ^= ReadBuffer[i];
    							printf("0x%2x ",ReadBuffer[i]);
    						}
    						printf("\r\n");
    						memset(ReadBuffer,0,sizeof(ReadBuffer));
    				}
    				else
    				{
    						
    						printf("read file failth(%d)\n", res_sd);
    				}
    			}while(fnum > 0);
    			/* 不再读,关闭文件 */
            f_close(&fnew);
    				u8 readBuf[4];
    				u8 readCheckCrc = 0;
    				for(int i = 0;i < writeSum ; i++)
    				{
    					STMFLASH_Read(APP1_ADDRESS+i*4,(u32*)readBuf,1);
    					for(int j = 0;j<4;j++)
    					readCheckCrc ^= readBuf[j];
    				}
    				printf("boot jump app\r\n");
    				if(readCheckCrc == checknum && readCheckCrc!=0)
    				{
    					printf("check sum success jump APP1\r\n");
    					//跳转
    					jump_to_app();
    				}
    				else
    				{
    					printf("check sum Failth\r\n");
    				}
    				while(1);
    				
    		}
    		else
    		{
    			printf("not found bootloader \r\n");
    		}
    		LED0=!LED0;  //状态灯  bootloader状态
    	}
    

    上面还需要进行优化,需要添加一些标志位可以在一个扇区或者在备份寄存器标记,来做升级标记。

    跳转指令 重点

    #define  APP1_ADDRESS  0x8020000
    __asm void start_app(uint32_t r0_msp, uint32_t r1_pc)
    {
        MOV SP, R0    //R0的数值   其实就是参数r0_msp
        BX R1             //R1               其实就是r1_pc
    }
    void jump_to_app(void)
    {
    	start_app((*(uint32_t *)(APP1_ADDRESS)),(*(uint32_t *)(APP1_ADDRESS + 4)));
    }
    

    在实际操作过程中,遇到跳转,没能正常运行APP。
    我进行分析,排除问题,把问题范围缩小,可能会出现的问题:1、Flash读写接口有问题 2、FAT32文件系统读取数据有问题。
    看前面的代码,我都加了日志信息输出,把文件系统读到的数据输出,和真实的bin文件内容对比,对比问题是正确,说明问题出现在Flash擦除。
    看下面的截图,左边是都是FF没做写入前做擦除,右边是擦除后再写,是写入成功的。

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 MCU固件升级系列之bootloader编写

    发表评论