Part1前言
当我们要下载编译好的镜像到Flash时,首先要做的一步就是选择合适的Flash下载算法,而这个算法本身就是一个FLM文件:
代码既可以下载到内部flash,也可以下载到外部flash,或者一部分下载到内部,一部分下载到外部。
Part2一、将代码中的图片资源下载到外部flash
在UI设计中往往需要大量的图片和字体,图片和字体资源在代码中以静态数组的形式存在,这些大数组在内部flash中一般存放不下,所以需要把这些占用资源比较大的数组放在外部flash中,然后通过QSPI地址映射的方式访问,或者通过SPI将flash中的资源分批读取到RAM缓存中使用。
通过MDK打开分散加载文件,配置“ExtFlashSection”段:
;************************************************************* ;***Scatter-LoadingDescriptionFilegeneratedbyuVision*** ;************************************************************* LR_IROM10x080000000x00020000{;loadregionsize_region ER_IROM10x080000000x00020000{;loadaddress=executionaddress *.o(RESET,+First) *(InRoot$$Sections) .ANY(+RO) .ANY(+XO) } RW_IRAM10x200000000x00020000{;RWdata .ANY(+RW+ZI) } RW_IRAM20x240000000x00080000{ .ANY(+RW+ZI) } } LR_EROM10x900000000x01000000{;loadregionsize_region ER_EROM10x900000000x01000000{;loadaddress=executionaddress *.o(ExtFlashSection) *.o(FontFlashSection) *.o(TextFlashSection) } }
添加LR_EROM1 段,起始地址为0x90000000 ,大小为0x01000000 。
在代码中将图片资源分配到ExtFlashSection段
#defineLOCATION_ATTRIBUTE(name)__attribute__((section(name)))__attribute__((aligned(4))) KEEPexternconstunsignedcharimage_watch_seconds[]LOCATION_ATTRIBUTE("ExtFlashSection")=//4x202ARGB8888pixels. { 0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0x00, 0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0xff, 0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0x00, 0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0x00, 0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0xff, 0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0xff, 0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0x00, 0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0x00, 0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0xff, 0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0x00, 0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0xff, 0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0xff, 0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0xff,0xf8,0xfc,0xf8,0x00, 0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0x00,0xf8,0xfc,0xf8,0x00, 0xf8,0xfc,0xf8,0x00 };
编译代码
查看map文件,image_watch_seconds这个数组已经被分配到了0X90138690这个地址了,这个地址正是LR_EROM1 所在的区间。
Part3二、MDK下载算法原理
1程序能够通过下载算法下载到芯片的原理
通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。
2算法程序中擦除操作执行流程
算法程序中擦除操作执行流程
加载算法到芯片RAM。
执行初始化函数Init。
执行擦除操作,根据用户的MDK配置,这里可以选择整个芯片擦除或者扇区擦除。
执行Uinit函数。
操作完毕。
3制作FLM文件步骤
将ARM:CMSIS Pack文件夹(通常是C:KeilARMPackARMCMSIS version Device_Template_Flash)中的工程复制到一个新文件夹中,取消文件夹的只读属性,重命名项目文件NewDevice.uvprojx以表示新的flash 设备名称,例如MyDevice.uvprojx。
打开工程,从工具栏中,使用下拉选择目标来选择处理器架构。
打开对话框Project - Options for Target - Output并更改Name of Executable字段的内容以表示设备,例如MyDevice。
调整文件FlashPrg中的编程算法。
调整文件FlashDev中的设备参数。
使用Project - Build Target生成新的 Flash 编程算法。
以上步骤是利用官方的工程模板修改代码,这种方式网上已有很多教程(推荐使用这种方法),不再重复介绍,接下来介绍一种不使用模板工程制作的方法,目的是为了了解其实现原理。
Part4三、使用STM32CubeMX新建工程
4新建工程
硬件平台: RT-Thread官方ART-PI H750开发版
软件: STM32CubeMX,MDK
选择MCU型号(STM32H750XBH6)
选择MCU型号
配置SPI
配置SPI
配置UART
配置UART
配置时钟树
配置时钟树
设置调试接口
设置调试接口
设置工程并生成工程
生成工程
52. 移植SFUD串行 Flash 通用驱动库
SFUD 是什么
SFUD(https://github.com/armink/SFUD) 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。
主要特点:支持 SPI/QSPI 接口、面向对象(同时支持多个 Flash 对象)、可灵活裁剪、扩展性强、支持 4 字节地址
资源占用
标准占用:RAM:0.2KB ROM:5.5KB
最小占用:RAM:0.1KB ROM:3.6KB
设计思路:
什么是 SFDP :它是 JEDEC (固态技术协会)制定的串行 Flash 功能的参数表标准,最新版 V1.6B (点击这里查看)。该标准规定了,每个 Flash 中会存在一个参数表,该表中会存放 Flash 容量、写粒度、擦除命令、地址模式等 Flash 规格参数。目前,除了部分厂家旧款 Flash 型号会不支持该标准,其他绝大多数新出厂的 Flash 均已支持 SFDP 标准。所以该库在初始化时会优先读取 SFDP 表参数。
不支持 SFDP 怎么办 :如果该 Flash 不支持 SFDP 标准,SFUD 会查询配置文件 ( /sfud/inc/sfud_flash_def.h ) 中提供的 Flash 参数信息表 中是否支持该款 Flash。如果不支持,则可以在配置文件中添加该款 Flash 的参数信息。获取到了 Flash 的规格参数后,就可以实现对 Flash 的全部操作。
移植SFUD
将下载到sfud源代码放置在工程目录中
将sfud添加到工程目录:
修改sfud_port.c文件:
#include#include #include #include"gpio.h" #include"spi.h" typedefstruct{ SPI_HandleTypeDef*spix; GPIO_TypeDef*cs_gpiox; uint16_tcs_gpio_pin; }spi_user_data,*spi_user_data_t; staticspi_user_dataspi1; staticcharlog_buf[256]; voidsfud_log_debug(constchar*file,constlongline,constchar*format,...); externintrt_vsnprintf(char*buf,intsize,constchar*fmt,va_listargs); externintrt_kprintf(constchar*fmt,...); staticvoidspi_lock(constsfud_spi*spi) { } staticvoidspi_unlock(constsfud_spi*spi) { } /*about100microseconddelay*/ staticvoiddelay_100us(void){ uint32_tdelay=2000; while(delay--); } /** *SPIwritedatathenreaddata */ staticsfud_errspi_write_read(constsfud_spi*spi,constuint8_t*write_buf,size_twrite_size,uint8_t*read_buf, size_tread_size) { sfud_errresult=SFUD_SUCCESS; /** *addyourspiwriteandreadcode */ spi_user_data_tspi_dev=(spi_user_data_t)spi->user_data; HAL_GPIO_WritePin(spi_dev->cs_gpiox,spi_dev->cs_gpio_pin,GPIO_PIN_RESET); if(write_size){ HAL_SPI_Transmit(spi_dev->spix,(uint8_t*)write_buf,write_size,1); } if(read_size){ HAL_SPI_Receive(spi_dev->spix,read_buf,read_size,1); } exit: HAL_GPIO_WritePin(spi_dev->cs_gpiox,spi_dev->cs_gpio_pin,GPIO_PIN_SET); returnresult; } sfud_errsfud_spi_port_init(sfud_flash*flash) { sfud_errresult=SFUD_SUCCESS; switch(flash->index){ caseSFUD_W25Q128_DEVICE_INDEX:{ spi1.spix=&hspi1; spi1.cs_gpiox=GPIOA; spi1.cs_gpio_pin=GPIO_PIN_4; /*同步Flash移植所需的接口及数据*/ flash->spi.wr=spi_write_read; flash->spi.lock=spi_lock; flash->spi.unlock=spi_unlock; flash->spi.user_data=&spi1; /*about100microseconddelay*/ flash->retry.delay=delay_100us; /*adout60secondstimeout*/ flash->retry.times=60*10000; break; } } returnresult; } voidsfud_log_debug(constchar*file,constlongline,constchar*format,...){ va_listargs; /*argspointtothefirstvariableparameter*/ va_start(args,format); rt_kprintf("[SFUD](%s:%ld)",file,line); /*mustusevprintftoprint*/ rt_vsnprintf(log_buf,sizeof(log_buf),format,args); rt_kprintf("%s ",log_buf); va_end(args); } voidsfud_log_info(constchar*format,...){ va_listargs; /*argspointtothefirstvariableparameter*/ va_start(args,format); rt_kprintf("[SFUD]"); /*mustusevprintftoprint*/ rt_vsnprintf(log_buf,sizeof(log_buf),format,args); rt_kprintf("%s ",log_buf); va_end(args); }
测试SFUD
在main.c中添加测试代码:
/*USERCODEENDHeader*/ /*Includes------------------------------------------------------------------*/ #include"main.h" #include"spi.h" #include"usart.h" #include"gpio.h" /*Privatefunctionprototypes-----------------------------------------------*/ voidSystemClock_Config(void); staticvoidMPU_Config(void); /*USERCODEBEGINPFP*/ externintrt_kprintf(constchar*fmt,...); #include"sfud.h" /*USERCODEENDPFP*/ /*Privateusercode---------------------------------------------------------*/ /*USERCODEBEGIN0*/ #defineSFUD_DEMO_TEST_BUFFER_SIZE1024 staticuint8_tsfud_demo_test_buf[SFUD_DEMO_TEST_BUFFER_SIZE]; /** *SFUDdemoforthefirstflashdevicetest. * *@paramaddrflashstartaddress *@paramsizetestflashsize *@paramsizetestflashdatabuffer */ staticvoidsfud_demo(uint32_taddr,size_tsize,uint8_t*data){ sfud_errresult=SFUD_SUCCESS; constsfud_flash*flash=sfud_get_device_table()+0; size_ti; /*preparewritedata*/ for(i=0;i< size; i++) { data[i] = i; } /* erase test */ result = sfud_erase(flash, addr, size); if (result == SFUD_SUCCESS) { rt_kprintf("Erase the %s flash data finish. Start from 0x%08X, size is %d. ", flash->name,addr, size); }else{ rt_kprintf("Erasethe%sflashdatafailed. ",flash->name); return; } /*writetest*/ result=sfud_write(flash,addr,size,data); if(result==SFUD_SUCCESS){ rt_kprintf("Writethe%sflashdatafinish.Startfrom0x%08X,sizeis%d. ",flash->name,addr, size); }else{ rt_kprintf("Writethe%sflashdatafailed. ",flash->name); return; } /*readtest*/ result=sfud_read(flash,addr,size,data); if(result==SFUD_SUCCESS){ rt_kprintf("Readthe%sflashdatasuccess.Startfrom0x%08X,sizeis%d.Thedatais: ",flash->name,addr, size); rt_kprintf("Offset(h)000102030405060708090A0B0C0D0E0F "); for(i=0;i< size; i++) { if (i % 16 == 0) { rt_kprintf("[%08X] ", addr + i); } rt_kprintf("%02X ", data[i]); if (((i + 1) % 16 == 0) || i == size - 1) { rt_kprintf(" "); } } rt_kprintf(" "); } else { rt_kprintf("Read the %s flash data failed. ", flash->name); } /*datacheck*/ for(i=0;i< size; i++) { if (data[i] != i % 256) { rt_kprintf("Read and check write data has an error. Write the %s flash data failed. ", flash->name); break; } } if(i==size){ rt_kprintf("The%sflashtestissuccess. ",flash->name); } } /*USERCODEEND0*/ intmain(void) { /*MPUConfiguration--------------------------------------------------------*/ MPU_Config(); /*MCUConfiguration--------------------------------------------------------*/ /*Resetofallperipherals,InitializestheFlashinterfaceandtheSystick.*/ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); MX_UART4_Init(); /*USERCODEBEGIN2*/ if(sfud_init()==SFUD_SUCCESS){ sfud_demo(0,sizeof(sfud_demo_test_buf),sfud_demo_test_buf); } while(1) { } } #endif/*USE_FULL_ASSERT*/
运行如下:
63.制作下载算法
重新生成不带main函数的工程
添加修改编程算法文件FlashPrg.c
模板工程里面提供了FlashOS.h和FlashPrg.c ,复制到此工程中,然后对FlashPrg.c 代码进行填充。
#include"FlashOS.H" #include"sfud.h" #include"gpio.h" #include"usart.h" #include"spi.h" staticuint32_tbase_adr; /* *InitializeFlashProgrammingFunctions *Parameter:adr:DeviceBaseAddress *clk:ClockFrequency(Hz) *fnc:FunctionCode(1-Erase,2-Program,3-Verify) *ReturnValue:0-OK,1-Failed */ #ifdefinedFLASH_MEM||definedFLASH_OTP intInit(unsignedlongadr,unsignedlongclk,unsignedlongfnc) { MX_GPIO_Init(); MX_UART4_Init(); MX_SPI1_Init(); base_adr=adr; if(sfud_init()==SFUD_SUCCESS){ return0; }else{ return1; } } #endif /* *De-InitializeFlashProgrammingFunctions *Parameter:fnc:FunctionCode(1-Erase,2-Program,3-Verify) *ReturnValue:0-OK,1-Failed */ #ifdefinedFLASH_MEM||definedFLASH_OTP intUnInit(unsignedlongfnc) { return(0); } #endif /* *ErasecompleteFlashMemory *ReturnValue:0-OK,1-Failed */ intEraseChip(void) { intresult=0; constsfud_flash*flash=sfud_get_device_table(); /*AddyourCode*/ result=sfud_erase(flash,0,flash->chip.capacity); if(result==SFUD_SUCCESS) return0; else returnresult;//FinishedwithoutErrors } /* *EraseSectorinFlashMemory *Parameter:adr:SectorAddress *ReturnValue:0-OK,1-Failed */ #ifdefFLASH_MEM intEraseSector(unsignedlongadr) { intresult=0; uint32_tblock_start; constsfud_flash*flash; flash=sfud_get_device_table(); block_start=adr-base_adr; result=sfud_erase(flash,block_start,4096); if(result==SFUD_SUCCESS) return0; else returnresult; } #endif /* *ProgramPageinFlashMemory *Parameter:adr:PageStartAddress *sz:PageSize *buf:PageData *ReturnValue:0-OK,1-Failed */ #ifdefinedFLASH_MEM||definedFLASH_OTP intProgramPage(unsignedlongblock_start,unsignedlongsize,unsignedchar*buffer) { constsfud_flash*flash=sfud_get_device_table()+0; uint32_tstart_addr=block_start-base_adr; if(sfud_write(flash,start_addr,size,buffer)==SFUD_SUCCESS) return0; else return1; } #definePAGE_SIZE4096 uint8_taux_buf[PAGE_SIZE]; unsignedlongVerify(unsignedlongadr,unsignedlongsz,unsignedchar*buf) { inti; constsfud_flash*flash=sfud_get_device_table(); sfud_read(flash,adr-base_adr,sz,aux_buf); for(i=0;i< PAGE_SIZE; i++) { if (aux_buf[i] != buf[i]) return (adr + i); // Verification Failed (return address) } return (adr + sz); // Done successfully } #endif
在工程中定义FLASH_MEM宏
添加修改配置文件FlashDev.c
模板工程里面提供了FlashDev.c ,复制到此工程中,然后对代码进行修改。
#include"FlashOS.H" #ifdefFLASH_MEM structFlashDeviceconstFlashDevice={ FLASH_DRV_VERS,//DriverVersion,donotmodify! "STM32H750-ARTPI",//DeviceName EXTSPI,//DeviceType 0x90000000,//DeviceStartAddress 0x08000000,//DeviceSizeinBytes(128MB) 0x00001000,//ProgrammingPageSize4096Bytes 0x00,//Reserved,mustbe0 0xFF,//InitialContentofErasedMemory 10000,//ProgramPageTimeout100mSec 6000,//EraseSectorTimeout6000mSec //SpecifySizeandAddressofSectors 0x1000,0x000000,//SectorSize4kB SECTOR_END }; #endif//FLASH_MEM
特别注意:"STM32H750-ARTPI"就是MDK的Option选项里面会识别出这个名字。0x90000000是MDK分散加载文件中定义的外部flash起始地址。
地址无关代码实现
C和汇编的配置勾选上:
ROPI地址无关实现
如果程序的所有只读段都与位置无关,则该程序为只读位置无关(ROPI, Read-only position independence)。ROPI段通常是位置无关代码(PIC,position-independent code),但可以是只读数据,也可以是PIC和只读数据的组合。选择“ ROPI”选项,可以避免用户不得不将代码加载到内存中的特定位置。这对于以下例程特别有用:
(1)加载以响应运行事件。
(2)在不同情况下使用其他例程的不同组合加载到内存中。
(3)在执行期间映射到不同的地址。
RWPI数据无关实现使用Read-Write position independence同理,表示的可读可写数据段。使用RWPI编译代码,解决RW段即全局变量的加载。首先编译的时候会为每一个全局变量生成一个相对于r9寄存器的偏移量,这个偏移量会在.text段中。
在加载elf阶段,将RW段加载到RAM当中之后,需要将r9寄存器指向此片内存的基地址,然后接下来就可以跳转到加载的elf的代码中去执行,就可以实现全局变量的加载了。这也就是利用MDK的FLM文件生成通用flash驱动中提到的需要在编译选项中添加-ffixed-r9的原因。
综上所述,勾选ROPI和RWPI选项,可以实现elf文件的动态加载,还遗留的一个小问题是elf模块如何调用系统函数,这与此文无关,留在以后再讲。
特别注意:
由于模块中不含中断向量表,所以程序中不要开启任何中断。
startup_stm32h750xx.s不再需要参与编译
修改分散加载文件
复制一份新的分散加载文件到工程目录中,然后修改成如下代码
--diag_suppress L6305用于屏蔽没有入口地址的警告信息。
;LinkerControlFile(scatter-loading) ; PRG0PI;ProgrammingFunctions { PrgCode+0;Code { *(+RO) } PrgData+0;Data { *(+RW,+ZI) } } DSCR+0;DeviceDescription { DevDscr+0 { FlashDev.o } }
将程序可执行文件axf修改为flm格式
通过这个cmd.exe /C copy "!L" "..@L.FLM"命令可以将生成的axf可执行文件修改为flm。
将生成的flm文件拷贝到...Keil_v5ARMFlash目录,即可被MDK识别到。
DEMO下载地址:https://gitee.com/Aladdin-Wang/STM32H7_W25QXXX
-
FlaSh
+关注
关注
10文章
1635浏览量
148021 -
算法
+关注
关注
23文章
4612浏览量
92889 -
SPI
+关注
关注
17文章
1706浏览量
91580 -
MDK
+关注
关注
4文章
209浏览量
32066 -
编写
+关注
关注
0文章
29浏览量
8447
原文标题:从零编写STM32H7的MDK SPI FLASH下载算法
文章出处:【微信号:嵌入式应用研究院,微信公众号:嵌入式应用研究院】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论