STM32固件升级设计——SPIFLASH模拟U盘升级固件

目录

概述

一、功能描述

1、BootLoader部分:

2、APP部分:

二、BootLoader程序制作

1、分区定义

2、 主函数

3、配置USB

4、配置fatfs文件系统

 5、程序跳转

三、APP程序制作

四、工程配置(默认KEIL5)

五、运行测试 

六、 结束语


概述

        IAP(In Application Programming)即在应用中编程,允许在应用程序运行时更新或切换固件。STM32通过修改MSP(主堆栈指针)和PC(程序计数器)实现从不同地址启动,包括Flash或RAM地址。默认情况下,嵌入式程序以连续二进制形式烧录到STM32的可寻址Flash区域。若Flash容量足够存储多个完整程序,每个程序独立且完整,上电后可通过修改MSP值选择不同程序入口,从而实现多固件切换或升级。

         BootLoader(引导加载程序)是嵌入式系统或计算机启动时运行的一段小型程序,负责初始化硬件、加载操作系统内核并将其控制权移交。它是系统从关机状态到操作系统完全运行之间的桥梁。

所以,固件升级的基本思路是将stm32的flash划分为若干个区域,其中包括BootLoader区域和APP区等,将各自的程序写到对应的flash区域里。

一、功能描述

        使用STM32的USB总线及外置W25Q64实现USB模拟U盘升级程序功能。
分区介绍:
        本文使用stm32f103vet6,flash是512k,sector是1k,BootLoader整个代码编译下来有23K左右,所以使用0x08000000~0x00007FFF(如果想更大化地利用flash,可以不要setting区域,具体看自己如何写了),APP整个代码使用剩下的0x08009000~0x0807FFFF 区域。

区域起始地址区域大小功能
BOOT0x080000000x00008000(32k)存放BootLoader程序
SETTING0x080080000x00001000(4k)存放升级标志位/其它掉电不丢失标志位
APP0x080090000x00077000(476k)存放产品主程序

 

1、BootLoader部分:

        运行程序时首先从SETTING区域读取升级标志位,如果需要升级就进入识别U盘程序,否则就直接跳转到APP。上电长按KEY1并复位,电脑上即可模拟出U盘,识别到U盘后拖拽固件bin文件到U盘,然后将W25Q64里的fatfs文件系统的升级文件拷贝到APP起始地址,即可实现升级程序,具体请查看本文源码。

2、APP部分:

        该部分只需要设置中断向量跳转指针就行,如果通过串口等下发升级标志位也可以设置SETTING区域后复位进入BootLoader升级。

二、BootLoader程序制作

        需要包含USB Device中的Mass_Strorage和w25q64以及fatfs文件系统的驱动代码。(这部分是需要仔细研究做好的,我是根据正点原子和野火的教程移植的,具体流程不做了)

1、分区定义
#define FLASH_SECTOR_SIZE           1024   //MCU sector size
#define FLASH_SECTOR_NUM            512    // 512K
#define FLASH_START_ADDR            ((uint32_t)0x8000000)
#define FLASH_END_ADDR              ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))#define BOOT_SECTOR_ADDR            0x08000000      // BOOT sector start address 
#define BOOT_SECTOR_SIZE            0x8000
#define SETTING_SECTOR_ADDR         0x08008000      //APP设置的boot升级标志位
#define SETTING_SECTOR_SIZE         0x1000
#define APP_SECTOR_ADDR             0x08009000      // APP sector start address 
#define APP_SECTOR_SIZE             0x77000#define APP_ERASE_SECTORS           (APP_SECTOR_SIZE / FLASH_SECTOR_SIZE) typedef enum {NONE = 0,START_PROGRAM,    //进入APP主程序或者有更新就执行更新UPDATE_PROGRAM,   //进入更新UPDATE_SUCCESS    //更新成功写标志位
}Update_Process;      //更新状态
2、 主函数

        这部分包含了升级的所有状态,具体看代码。

Update_Process bootupdate_process;static void iap_process(void)
{uint8_t offline_cnt=0;uint8_t tct=0;uint8_t USB_STA;uint8_t Device_STA; // 定义最大重试次数和每次等待间隔const uint8_t max_retries = 5;const uint8_t wait_interval_ms = 100;uint8_t retry_count = 0;switch (bootupdate_process) {case NONE:break;case START_PROGRAM:spiflash_update();printf("start app...\r\n");delay_ms(50);if ((((*(vu32*)(APP_SECTOR_ADDR+4))&0xFF000000)==0x08000000)&&(!iap_load_app(APP_SECTOR_ADDR))) {printf("no program\r\n");delay_ms(1000);}printf("start app failed\r\n");break;case UPDATE_PROGRAM:MAL_Init(0);Max_Lun=0;	USB_Interrupts_Config();/*设置USB时钟为48M*/Set_USBClock();USB_Init();
//			while (bDeviceState != CONFIGURED);	 //等待配置完成// 等待配置完成,最多重试 5 次while (bDeviceState != CONFIGURED && retry_count < max_retries) {delay_ms(wait_interval_ms);retry_count++;}// 若 5 次后仍未配置完成,进入 START_PROGRAM 状态if (bDeviceState != CONFIGURED) {printf("USB 配置失败,尝试 5 次后退出,进入 START_PROGRAM 状态。\r\n");bootupdate_process = START_PROGRAM;return;}while(1){delay_ms(1);if(USB_STA!=USB_STATUS_REG)//状态改变了 {if(USB_STATUS_REG&0x01)//正在写{printf("USB Writing...\r\n");//提示USB正在写入数据	 }if(USB_STATUS_REG&0x02)//正在读{printf("USB Reading...\r\n");//提示USB正在读出数据  		 }		if(USBD_User_App() == FR_OK)  //检测到有对应的bin文件{bootupdate_process=UPDATE_SUCCESS;delay_ms(500);break;}if(USB_STATUS_REG&0x04)printf("USB Write Err\r\n");//提示写入错误	  if(USB_STATUS_REG&0x08)printf("USB Read Err\r\n");//提示读出错误 USB_STA=USB_STATUS_REG;//记录最后的状态}if(Device_STA!=bDeviceState) {if(bDeviceState==CONFIGURED){LED1_ON;//提示USB连接已经建立}else {LED1_OFF;//提示USB被拔出了}Device_STA=bDeviceState;}tct++;if(tct==200){tct=0;LED1_TOGGLE;//提示系统在运行if(USB_STATUS_REG&0x10){LED1_ON;offline_cnt=0;//USB连接了,则清除offline计数器bDeviceState=CONFIGURED;}else//没有得到轮询 {LED1_OFF;offline_cnt++;  if(offline_cnt>10)bDeviceState=UNCONNECTED;//2s内没收到在线标记,代表USB被拔出了}USB_STATUS_REG=0;}}break;case UPDATE_SUCCESS:bootupdate_process=START_PROGRAM;write_setting_boot_state(bootupdate_process);NVIC_SystemReset();break;default:break;}
}int main(void)
{USART_Config();LED_GPIO_Config();printf("\r\n 使用指南者底板时 左上角排针位置 不要将PC0盖有跳帽 防止影响PC0做SPIFLASH片选脚 \r\n");bootupdate_process=(Update_Process)read_setting_boot_state();if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == 1){bootupdate_process=UPDATE_PROGRAM;}while (1){iap_process();}
}
3、配置USB

        这部分根据野火代码移植而来,usb硬件配置自己看代码理解就好,一般没什么问题,w25q64最高有8Mbyte,前面2M用作其它用途,后面6M用做U盘,所以需要偏移2M,w25q64的底层读写函数需要特别注意,如果U盘出bug,大概率是以下几个函数有误。

uint16_t MAL_Init(uint8_t lun)
{ u16 Status=MAL_OK;   switch (lun){case 0:		W25QXX_Init();				//初始化W25Q64break;			   case 1:				 break;		  default:return MAL_FAIL;}return Status;
}uint16_t MAL_Write(uint8_t lun, uint64_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{switch (lun)		//这里,根据lun的值确定所要操作的磁盘{case 0:		 	//磁盘0为 SPI FLASH盘	Memory_Offset+=(512*4096);//扇区偏移,外部Flash文件系统空间放在外部Flash后面6M空间W25QXX_Write((u8*)Writebuff, Memory_Offset, Transfer_Length);   		  printf("Memory_Offset=%llu\r\n",Memory_Offset);break; case 1:			//磁盘1为SD卡		  break;							  default:return MAL_FAIL;}return MAL_OK; 
}uint16_t MAL_Read(uint8_t lun, uint64_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length)
{switch (lun)		//这里,根据lun的值确定所要操作的磁盘{case 0:			//磁盘0为 SPI FLASH盘	 Memory_Offset+=(512*4096);//扇区偏移,外部Flash文件系统空间放在外部Flash后面6M空间W25QXX_Read((u8*)Readbuff, Memory_Offset, Transfer_Length);   		  break;	  case 1:			//磁盘1为SD卡		    break;			    default:return MAL_FAIL;}return MAL_OK;
}uint16_t MAL_GetStatus (uint8_t lun)
{switch(lun){case 0:if(W25QXX_TYPE!=W25Q64)    //最高只能到8Mbyteprintf("W25Q64 Error!\r\n");//检测spiflash错误else //SPI FLASH 正常{   															  Mass_Block_Size[0] =4096;			//设置SPI FLASH的操作扇区大小为4096Mass_Block_Count[0]=1536;Mass_Memory_Size[0]=Mass_Block_Size[0]*Mass_Block_Count[0];	//总字节
//				printf("SPI FLASH Size:%dMB\r\n",(uint8_t)(Mass_Memory_Size[0]/1024/1024));}return MAL_OK;case 1:return MAL_OK; default:return MAL_FAIL;} 
}
4、配置fatfs文件系统

        这部分也是根据野火代码移植而来,需要特别注意扇区大小和数量需要和USB配置的一样。

//初始化磁盘
DSTATUS disk_initialize (BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{DSTATUS status = STA_NOINIT;	    uint16_t i;switch(pdrv){case SD_CARD://SD卡break;case EX_FLASH://外部flash/* 初始化SPI Flash */W25QXX_Init();/* 延时一小段时间 */i=500;while(--i);	/* 唤醒SPI Flash */W25QXX_WAKEUP();/* 获取SPI Flash芯片状态 */status=disk_status(EX_FLASH);break;default:status=STA_NOINIT; }		 return status;
} 
//读扇区
DRESULT disk_read (BYTE pdrv,		/* Physical drive nmuber to identify the drive */BYTE *buff,		/* Data buffer to store read data */DWORD sector,	/* Sector address in LBA */UINT count		/* Number of sectors to read */
)
{DRESULT status = RES_PARERR;  	 switch(pdrv){case SD_CARD://SD卡break;case EX_FLASH://外部flash/* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面6MB空间 */sector+=512;      W25QXX_Read(buff, sector <<12, count<<12);status = RES_OK;break;default:status = RES_PARERR; }return status;
}
//写扇区
DRESULT disk_write (BYTE pdrv,			/* Physical drive nmuber to identify the drive */const BYTE *buff,	/* Data to be written */DWORD sector,		/* Sector address in LBA */UINT count			/* Number of sectors to write */
)
{uint32_t write_addr;  DRESULT status = RES_PARERR;if (!count) {return RES_PARERR;		/* Check parameter */}switch(pdrv){case SD_CARD://SD卡break;case EX_FLASH://外部flashsector+=512;write_addr = sector<<12;    W25QXX_Write((u8 *)buff,write_addr,count<<12);status = RES_OK;break;default:status = RES_PARERR; }return status;
}
//其他表参数的获得
DRESULT disk_ioctl (BYTE pdrv,		/* Physical drive nmuber (0..) */BYTE cmd,		/* Control code */void *buff		/* Buffer to send/receive control data */
)
{DRESULT status = RES_PARERR;switch (pdrv) {case SD_CARD:	/* SD CARD */status = RES_OK;break;case EX_FLASH:switch (cmd){/* 扇区数量:1536*4096/1024/1024=6(MB) */case GET_SECTOR_COUNT:*(DWORD * )buff = 1536;		break;/* 扇区大小  */case GET_SECTOR_SIZE :*(WORD * )buff = 4096;break;/* 同时擦除扇区个数 */case GET_BLOCK_SIZE :*(DWORD * )buff = 1;break;        }status = RES_OK;break;default:status = RES_PARERR;}	return status;
}
5、程序跳转

        跳转这部分网上也很多,基本没什么区别,关于中断可能要注意一下,可能跳转之前,某些外设中断是开启的,跳转之后,中断产生了,但是APP代码中没有处理对应该中断的中断处理函数,所以就可能会直接死机

uint8_t iap_load_app(u32 appxaddr)
{uint8_t i;uint32_t jump_addr;if (((*(__IO uint32_t*)appxaddr) & 0x2FFE0000 ) == 0x20000000) {  jump_addr = *(__IO uint32_t*) (appxaddr + 4);  jump2app = (iapfun)jump_addr;  /* 关闭所有中断,清除所有中断挂起标志 */  for (i = 0; i < 8; i++){NVIC->ICER[i]=0xFFFFFFFF;NVIC->ICPR[i]=0xFFFFFFFF;}	__set_MSP(*(__IO uint32_t*)appxaddr);  jump2app();return 1;}return 0;
}	

三、APP程序制作

这部分设置一下flash的偏移量就行。

NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x9000);

四、工程配置(默认KEIL5)

BootLoader部分:0x08000000~0x00007FFF

APP部分:0x08009000~0x800FFFFF

五、运行测试 

        长按KEY1点击复位(断电重启)可以看到识别到U盘(H:),大小也正常,然后往里面复制一个固件firmware.bin(注意这里的固件名一定要是唯一的,不然程序识别不到),复制进去就会执行升级程序,可以看到打印信息显示固件更新成功。

结束语

以上SPIFLASH模拟U盘升级固件功能已实现,这只是其中的一种升级方式,后面大家看到的也希望可以得到大家的指点。主要的USB库和fatfs库移植教程就不给出来了,网上很多,基本都能实现。

完整代码下载地址:SPIFLASH模拟U盘升级固件资源-CSDN下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87348.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87348.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解锁阿里云日志服务SLS:云时代的日志管理利器

引言&#xff1a;开启日志管理新篇 在云计算时代&#xff0c;数据如同企业的血液&#xff0c;源源不断地产生并流动。从用户的每一次点击&#xff0c;到系统后台的每一个操作&#xff0c;数据都在记录着企业运营的轨迹。而在这些海量的数据中&#xff0c;日志数据占据着至关重…

Keye-VL-8B-Preview:由快手 Kwai Keye 团队精心打造的尖端多模态大语言模型

&#x1f525; News 2025.06.26 &#x1f31f; 我们非常自豪地推出Kwai Keye-VL&#xff0c;这是快手Kwai Keye团队精心打造的前沿多模态大语言模型。作为快手先进技术生态中的核心AI产品&#xff0c;Keye在视频理解、视觉感知和推理任务方面表现卓越&#xff0c;树立了新的性…

Web前端之JavaScript实现图片圆环、圆环元素根据角度指向圆心、translate、rotate

MENU 前言效果HtmlStyleJavaScript 前言 代码段创建了一个由6个WiFi图标组成的圆形排列&#xff0c;每个图标均匀分布在圆周上。 效果 Html 代码 <div class"ring"><div class"item"><img class"img" src"../image/icon/W…

1 Studying《Computer Vision: Algorithms and Applications 2nd Edition》11-15

目录 Chapter 11 Structure from motion and SLAM 11.1 几何内禀校准 11.2 姿态估计 11.3 从运动中获得的双帧结构 11.4 从运动中提取多帧结构 11.5 同步定位与建图&#xff08;SLAM&#xff09; 11.6 额外阅读 Chapter 12 Depth estimation 12.1 极点几何 12.2 稀疏…

phpstudy 可以按照mysql 数据库

phpstudy 可以按照mysql 数据库 PHPStudy&#xff08;小皮面板&#xff09;是一款专为开发者设计的集成环境工具&#xff0c;涵盖服务器配置、开发环境搭建、网站部署等多项功能。以下是其核心用途及优势的详细解析&#xff1a; 一、开发环境快速搭建 一站式集成环境集成Apa…

Python搭建HTTP服务,如何用内网穿透快速远程访问?

Python的内置HTTP服务模块是开发者工具箱中的瑞士军刀&#xff0c;只需一行命令即可启动一个功能完备的Web服务器。无论是前端工程师调试页面、数据科学家共享Jupyter Notebook&#xff0c;还是后端开发者快速验证API原型&#xff0c;Python HTTP服务都能以零配置的方式满足需求…

拨号音识别系统的设计与实现

拨号音识别系统的设计与实现 摘要 本文设计并实现了一个完整的拨号音识别系统&#xff0c;该系统能够自动识别电话号码中的数字。系统基于双音多频(DTMF)技术原理&#xff0c;使用MATLAB开发&#xff0c;包含GUI界面展示处理过程和结果。系统支持从麦克风实时录音或加载音频文…

数据结构-树详解

树简介 树存储和组织具有层级结构的数据&#xff08;例&#xff1a;公司职级&#xff09;&#xff0c;就是一颗倒立生长的树。 属性&#xff1a; 递归n个节点有n-1个连接节点x的深度&#xff1a;节点x到根节点的最长路径节点x的高度&#xff1a;节点x到叶子节点的最长路径 …

【安卓Sensor框架-2】应用注册Sensor 流程

注册传感器的核心流程为如下&#xff1a;应用层调用 SensorManager注册传感器&#xff0c;framework层创建SensorEventQueue对象&#xff08;事件队列&#xff09;&#xff0c;通过JNI调用Native方法nativeEnableSensor()&#xff1b;SensorService服务端createEventQueue()创建…

新版本没有docker-desktop-data分发 | docker desktop 镜像迁移

在新版本的docker desktop中&#xff08;如4.42版本&#xff09;&#xff0c;镜像迁移只需要更改路径即可。如下&#xff1a; 打开docker desktop的设置&#xff08;图1&#xff09;&#xff0c;将图2的原来的地址C:\Users\用户\AppData\Local\Docker\wsl修改为你想要的空文件…

EtherCAT SOEM源码分析 - ec_init

ec_init SOEM主站一切开始的地方始于ec_init, 它是EtherCAT主站初始化的入口。初始化SOEM 主站&#xff0c;并绑定到socket到ifname。 /** Initialise lib in single NIC mode* param[in] ifname Dev name, f.e. "eth0"* return >0 if OK* see ecx_init*/ in…

84、原理解析-SpringApplication创建初始化流程

84、原理解析-SpringApplication初始化流程 # SpringApplication创建初始化流程原理解析 SpringApplication的创建和初始化是Spring Boot应用启动的关键步骤&#xff0c;主要包括以下过程&#xff1a; ## 1. 创建SpringApplication实例 ### 1.1 调用构造函数 - 当调用SpringApp…

【数理逻辑】 选择公理与集值映射

目录 选择公理1. 有限指标集 I I I2. 可数无限指标集 I I I &#xff08;简称为 ACC 或 ACω&#xff09;3. 不可数无限指标集 I I I4. 选择公理的层级与数学应用5. 选择公理的深层意义 集值映射的选择函数1. 选择公理的核心作用2. 不同情况下的依赖性分析3. AC 的必要性证明…

微信小程序使用wx.chooseImage上传图片时进行压缩,并添加时间水印

在微信小程序的开发过程&#xff0c;经常会使用自带的api(wx.chooseImage)进行图片拍照或选择图片进行上传&#xff0c;有时图片太大&#xff0c;造成上传和下载时过慢&#xff0c;现对图片进行压缩后上传&#xff0c;以下是流程和代码 一、小程序的版本选择了3.2.5&#xff0…

RAII简介

&#x1f4e6; 一、技术原理简介&#xff1a;RAII是个“托管狂魔” 想象你有个健忘的朋友&#xff0c;每次借东西都会忘记归还。RAII&#xff08;Resource Acquisition Is Initialization&#xff0c;资源获取即初始化&#xff09;就是C派来的“超级管家”&#xff1a; “你负…

微信小程序入门实例_____打造你的专属单词速记小程序

上次通过天气查询小程序&#xff0c;我们初探了微信小程序开发的世界。这次&#xff0c;咱们再挑战一个有趣又实用的项目 ——“单词速记小程序”。无论是学生党备考&#xff0c;还是上班族提升英语&#xff0c;都能用得上&#xff01;接下来就跟着我&#xff0c;一步一步把它做…

gateway白名单存储nacos,改成存储数据库

前言 很久没写博客了&#xff0c;csdn都开始ai润色了&#xff0c;之前都是看相应框架的源码看了个遍&#xff0c;感觉底层原理都差不多&#xff0c;这阵子着手改造了下gateway中的白名单&#xff0c;之前白名单存储到nacos&#xff0c;要改成存到数据库。里面涉及到浅浅的源码…

ubentu服务器版本安装Dify

Docker 中安装Dify 首先安装Docker 1. 克隆Dify代码仓库 从github克隆 Dify 源代码至要本地环境。 我的ubentu服务器版本&#xff0c;我把源代码下载到 /var/下 在var文件夹下执行 git clone https://github.com/langgenius/dify.git执行成功后&#xff0c;进入Dify源代码的…

Redis分布式锁实战:从入门到生产级方案

目录 一、为什么需要分布式锁&#xff1f; 二、Redis分布式锁核心特性 三、实现方案与代码详解 方案1&#xff1a;基础版 SETNX EXPIRE 原理 代码示例 问题 方案2&#xff1a;Redisson框架&#xff08;生产推荐&#xff09; 核心特性 代码示例 优势 方案3&#xff…

【Redis】StringRedisTemplate 和 RedisTemplate 的区别

StringRedisTemplate 和 RedisTemplate 是 Spring Data Redis 提供的两种用于操作 Redis 的模板类&#xff0c;它们的核心区别在于 序列化方式 和 操作的数据类型。以下是两者的主要区别和使用建议&#xff1a; ✅ 1. 数据类型支持 类名支持的数据类型说明RedisTemplate支持所…