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;iname,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;iname); } /*datacheck*/ for(i=0;iname); 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
在工程中定義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文章
1642瀏覽量
148685 -
算法
+關注
關注
23文章
4630瀏覽量
93364 -
SPI
+關注
關注
17文章
1722瀏覽量
92139 -
MDK
+關注
關注
4文章
209瀏覽量
32154 -
編寫
+關注
關注
0文章
29瀏覽量
8493
原文標題:從零編寫STM32H7的MDK SPI FLASH下載算法
文章出處:【微信號:嵌入式應用研究院,微信公眾號:嵌入式應用研究院】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
STM32H7的內部Flash和QSPI Flash混合執行程序的優勢
【STM32H7】第20章 ThreadX GUIX漢字顯示(QSPI Flash全字庫)
![【<b class='flag-5'>STM32H7</b>】第20章 ThreadX GUIX漢字顯示(QSPI <b class='flag-5'>Flash</b>全字庫)](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
【STM32H7教程】第21章 STM32H7的NVIC中斷分組和配置(重要)
![【<b class='flag-5'>STM32H7</b>教程】第21章 <b class='flag-5'>STM32H7</b>的NVIC中斷分組和配置(重要)](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
"STM32H7學習繼續(STM32H7系列5)第十七章比較實用,以后寫程序的時候會用到"
!["<b class='flag-5'>STM32H7</b>學習繼續(<b class='flag-5'>STM32H7</b>系列5)第十七章比較實用,以后寫程序的時候會用到"](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
【STM32H7教程】第8章 STM32H7的終極調試組件Event Recorder
![【<b class='flag-5'>STM32H7</b>教程】第8章 <b class='flag-5'>STM32H7</b>的終極調試組件Event Recorder](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評論