Part1前言
當(dāng)我們要下載編譯好的鏡像到Flash時(shí),首先要做的一步就是選擇合適的Flash下載算法,而這個(gè)算法本身就是一個(gè)FLM文件:
代碼既可以下載到內(nèi)部flash,也可以下載到外部flash,或者一部分下載到內(nèi)部,一部分下載到外部。
Part2一、將代碼中的圖片資源下載到外部flash
在UI設(shè)計(jì)中往往需要大量的圖片和字體,圖片和字體資源在代碼中以靜態(tài)數(shù)組的形式存在,這些大數(shù)組在內(nèi)部flash中一般存放不下,所以需要把這些占用資源比較大的數(shù)組放在外部flash中,然后通過(guò)QSPI地址映射的方式訪問(wèn),或者通過(guò)SPI將flash中的資源分批讀取到RAM緩存中使用。
通過(guò)MDK打開(kāi)分散加載文件,配置“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這個(gè)數(shù)組已經(jīng)被分配到了0X90138690這個(gè)地址了,這個(gè)地址正是LR_EROM1 所在的區(qū)間。
Part3二、MDK下載算法原理
1程序能夠通過(guò)下載算法下載到芯片的原理
通過(guò)MDK創(chuàng)建一批與地址信息無(wú)關(guān)的函數(shù),實(shí)現(xiàn)的功能主要有初始化,擦除,編程,讀取,校驗(yàn)等,然后MDK調(diào)試下載階段,會(huì)將算法文件加載到芯片的內(nèi)部RAM里面(加載地址可以通過(guò)MDK設(shè)置),然后MDK通過(guò)與這個(gè)算法文件的交互,實(shí)現(xiàn)程序下載,調(diào)試階段數(shù)據(jù)讀取等操作。
2算法程序中擦除操作執(zhí)行流程
算法程序中擦除操作執(zhí)行流程
加載算法到芯片RAM。
執(zhí)行初始化函數(shù)Init。
執(zhí)行擦除操作,根據(jù)用戶的MDK配置,這里可以選擇整個(gè)芯片擦除或者扇區(qū)擦除。
執(zhí)行Uinit函數(shù)。
操作完畢。
3制作FLM文件步驟
將ARM:CMSIS Pack文件夾(通常是C:KeilARMPackARMCMSIS version Device_Template_Flash)中的工程復(fù)制到一個(gè)新文件夾中,取消文件夾的只讀屬性,重命名項(xiàng)目文件NewDevice.uvprojx以表示新的flash 設(shè)備名稱,例如MyDevice.uvprojx。
打開(kāi)工程,從工具欄中,使用下拉選擇目標(biāo)來(lái)選擇處理器架構(gòu)。
打開(kāi)對(duì)話框Project - Options for Target - Output并更改Name of Executable字段的內(nèi)容以表示設(shè)備,例如MyDevice。
調(diào)整文件FlashPrg中的編程算法。
調(diào)整文件FlashDev中的設(shè)備參數(shù)。
使用Project - Build Target生成新的 Flash 編程算法。
以上步驟是利用官方的工程模板修改代碼,這種方式網(wǎng)上已有很多教程(推薦使用這種方法),不再重復(fù)介紹,接下來(lái)介紹一種不使用模板工程制作的方法,目的是為了了解其實(shí)現(xiàn)原理。
Part4三、使用STM32CubeMX新建工程
4新建工程
硬件平臺(tái): RT-Thread官方ART-PI H750開(kāi)發(fā)版
軟件: STM32CubeMX,MDK
選擇MCU型號(hào)(STM32H750XBH6)
選擇MCU型號(hào)
配置SPI
配置SPI
配置UART
配置UART
配置時(shí)鐘樹(shù)
配置時(shí)鐘樹(shù)
設(shè)置調(diào)試接口
設(shè)置調(diào)試接口
設(shè)置工程并生成工程
生成工程
52. 移植SFUD串行 Flash 通用驅(qū)動(dòng)庫(kù)
SFUD 是什么
SFUD(https://github.com/armink/SFUD) 是一款開(kāi)源的串行 SPI Flash 通用驅(qū)動(dòng)庫(kù)。由于現(xiàn)有市面的串行 Flash 種類居多,各個(gè) Flash 的規(guī)格及命令存在差異, SFUD 就是為了解決這些 Flash 的差異現(xiàn)狀而設(shè)計(jì),讓我們的產(chǎn)品能夠支持不同品牌及規(guī)格的 Flash,提高了涉及到 Flash 功能的軟件的可重用性及可擴(kuò)展性,同時(shí)也可以規(guī)避 Flash 缺貨或停產(chǎn)給產(chǎn)品所帶來(lái)的風(fēng)險(xiǎn)。
主要特點(diǎn):支持 SPI/QSPI 接口、面向?qū)ο螅ㄍ瑫r(shí)支持多個(gè) Flash 對(duì)象)、可靈活裁剪、擴(kuò)展性強(qiáng)、支持 4 字節(jié)地址
資源占用
標(biāo)準(zhǔn)占用:RAM:0.2KB ROM:5.5KB
最小占用:RAM:0.1KB ROM:3.6KB
設(shè)計(jì)思路:
什么是 SFDP :它是 JEDEC (固態(tài)技術(shù)協(xié)會(huì))制定的串行 Flash 功能的參數(shù)表標(biāo)準(zhǔn),最新版 V1.6B (點(diǎn)擊這里查看)。該標(biāo)準(zhǔn)規(guī)定了,每個(gè) Flash 中會(huì)存在一個(gè)參數(shù)表,該表中會(huì)存放 Flash 容量、寫(xiě)粒度、擦除命令、地址模式等 Flash 規(guī)格參數(shù)。目前,除了部分廠家舊款 Flash 型號(hào)會(huì)不支持該標(biāo)準(zhǔn),其他絕大多數(shù)新出廠的 Flash 均已支持 SFDP 標(biāo)準(zhǔn)。所以該庫(kù)在初始化時(shí)會(huì)優(yōu)先讀取 SFDP 表參數(shù)。
不支持 SFDP 怎么辦 :如果該 Flash 不支持 SFDP 標(biāo)準(zhǔn),SFUD 會(huì)查詢配置文件 ( /sfud/inc/sfud_flash_def.h ) 中提供的 Flash 參數(shù)信息表 中是否支持該款 Flash。如果不支持,則可以在配置文件中添加該款 Flash 的參數(shù)信息。獲取到了 Flash 的規(guī)格參數(shù)后,就可以實(shí)現(xiàn)對(duì) 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移植所需的接口及數(shù)據(jù)*/ 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); }
測(cè)試SFUD
在main.c中添加測(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*/
運(yùn)行如下:
63.制作下載算法
重新生成不帶main函數(shù)的工程
添加修改編程算法文件FlashPrg.c
模板工程里面提供了FlashOS.h和FlashPrg.c ,復(fù)制到此工程中,然后對(duì)FlashPrg.c 代碼進(jìn)行填充。
#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 ,復(fù)制到此工程中,然后對(duì)代碼進(jìn)行修改。
#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選項(xiàng)里面會(huì)識(shí)別出這個(gè)名字。0x90000000是MDK分散加載文件中定義的外部flash起始地址。
地址無(wú)關(guān)代碼實(shí)現(xiàn)
C和匯編的配置勾選上:
ROPI地址無(wú)關(guān)實(shí)現(xiàn)
如果程序的所有只讀段都與位置無(wú)關(guān),則該程序?yàn)橹蛔x位置無(wú)關(guān)(ROPI, Read-only position independence)。ROPI段通常是位置無(wú)關(guān)代碼(PIC,position-independent code),但可以是只讀數(shù)據(jù),也可以是PIC和只讀數(shù)據(jù)的組合。選擇“ ROPI”選項(xiàng),可以避免用戶不得不將代碼加載到內(nèi)存中的特定位置。這對(duì)于以下例程特別有用:
(1)加載以響應(yīng)運(yùn)行事件。
(2)在不同情況下使用其他例程的不同組合加載到內(nèi)存中。
(3)在執(zhí)行期間映射到不同的地址。
RWPI數(shù)據(jù)無(wú)關(guān)實(shí)現(xiàn)使用Read-Write position independence同理,表示的可讀可寫(xiě)數(shù)據(jù)段。使用RWPI編譯代碼,解決RW段即全局變量的加載。首先編譯的時(shí)候會(huì)為每一個(gè)全局變量生成一個(gè)相對(duì)于r9寄存器的偏移量,這個(gè)偏移量會(huì)在.text段中。
在加載elf階段,將RW段加載到RAM當(dāng)中之后,需要將r9寄存器指向此片內(nèi)存的基地址,然后接下來(lái)就可以跳轉(zhuǎn)到加載的elf的代碼中去執(zhí)行,就可以實(shí)現(xiàn)全局變量的加載了。這也就是利用MDK的FLM文件生成通用flash驅(qū)動(dòng)中提到的需要在編譯選項(xiàng)中添加-ffixed-r9的原因。
綜上所述,勾選ROPI和RWPI選項(xiàng),可以實(shí)現(xiàn)elf文件的動(dòng)態(tài)加載,還遺留的一個(gè)小問(wèn)題是elf模塊如何調(diào)用系統(tǒng)函數(shù),這與此文無(wú)關(guān),留在以后再講。
特別注意:
由于模塊中不含中斷向量表,所以程序中不要開(kāi)啟任何中斷。
startup_stm32h750xx.s不再需要參與編譯
修改分散加載文件
復(fù)制一份新的分散加載文件到工程目錄中,然后修改成如下代碼
--diag_suppress L6305用于屏蔽沒(méi)有入口地址的警告信息。
;LinkerControlFile(scatter-loading) ; PRG0PI;ProgrammingFunctions { PrgCode+0;Code { *(+RO) } PrgData+0;Data { *(+RW,+ZI) } } DSCR+0;DeviceDescription { DevDscr+0 { FlashDev.o } }
將程序可執(zhí)行文件axf修改為flm格式
通過(guò)這個(gè)cmd.exe /C copy "!L" "..@L.FLM"命令可以將生成的axf可執(zhí)行文件修改為flm。
將生成的flm文件拷貝到...Keil_v5ARMFlash目錄,即可被MDK識(shí)別到。DEMO下載地址:https://gitee.com/Aladdin-Wang/STM32H7_W25QXXX
審核編輯:湯梓紅
-
FlaSh
+關(guān)注
關(guān)注
10文章
1678瀏覽量
151776 -
算法
+關(guān)注
關(guān)注
23文章
4709瀏覽量
95353 -
SPI
+關(guān)注
關(guān)注
17文章
1804瀏覽量
95905 -
MDK
+關(guān)注
關(guān)注
4文章
211瀏覽量
32682 -
編寫(xiě)
+關(guān)注
關(guān)注
0文章
29瀏覽量
8612
原文標(biāo)題:從零編寫(xiě)STM32H7的MDK SPI FLASH下載算法
文章出處:【微信號(hào):嵌入式應(yīng)用研究院,微信公眾號(hào):嵌入式應(yīng)用研究院】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
STM32H7的內(nèi)部Flash和QSPI Flash混合執(zhí)行程序的優(yōu)勢(shì)
【STM32H7教程】第19章 STM32H7的GPIO應(yīng)用之按鍵FIFO

STM32H7學(xué)習(xí)之路繼續(xù)(stm32H7系列3) GPIO

【STM32H7】第20章 ThreadX GUIX漢字顯示(QSPI Flash全字庫(kù))

【STM32H7教程】第21章 STM32H7的NVIC中斷分組和配置(重要)

"STM32H7學(xué)習(xí)繼續(xù)(STM32H7系列5)第十七章比較實(shí)用,以后寫(xiě)程序的時(shí)候會(huì)用到"

【STM32H7教程】第8章 STM32H7的終極調(diào)試組件Event Recorder

【STM32H7教程】第14章 STM32H7的電源,復(fù)位和時(shí)鐘系統(tǒng)

STM32H7以太網(wǎng)的MMC中斷

評(píng)論