1 前言
DFU用來做IAP是很方便的,可以直接通過USB來對APP進行升級,因此,掌握DFU的制作還是挺有好處,特別是使用CubeMx工具可以快速制作,本文將基于STM3240G-EVL評估板來一步一步實現一個DFU的IAP工程。
2 制作CubeMx工程
新建一個STM32F407IGHx工程:Pinout:
Peripherals:RCC->High Speed Clock(HSE):Crystal/Ceramic ResonatorSYS->Debug:Serial WireUSB_OTG_FS->Mode:Device_Only
MiddleWaresUSB_DEVICE->Class For FS IP:Download Firmware Update Class(DFU)
再配置PG15腳為GPIO_Input模式。
Clock Configuration:
圖1 時鐘樹設置
如上圖,STM3240G-EVAL評估板使用的是25M HSE。Configuration:NVIC中將USB中斷優先級調為5,PG15的標簽設置為USER_BTN,此外還需要設置中間件USB DFU參數,如下圖:
圖2 USB DFU參數設置
如上圖,紅色框內為需要修改的代碼,0x0800C000為需要為用戶程序APP燒錄的起始地址,字符串“@Internal Flash /0x08000000/03016Ka,01016Kg,01064Kg,07128Kg”實際為USB DFU類的interface字符串描述符,在USB DFU標準文件中有提到可選接口可以使用一個對應的接口字符串來表示此可選接口對應的目標設備的存儲塊信息,但如何具體規定的,DFU標準(DFU_1.1)并沒有要求,是開放的,如下:
圖3 DFU標準對接口字符串定義的描述
由此可見,接口字符串定義是可以自由定義的,那么在這里,由于使用到ST工具軟件DfuSe Demo(v3.0.5),那么這個工具與USB DFU設備就有一個自定義的接口字符串定義,用來表示當前MCU內部的FLASH組織結構。
接下來我們來看看MCU內部FLASH的組織,由于這里的MCU是STM32F407IGHx,找到其參考文檔,并查看其內部FLASH組織結構:
圖4 STM32F407內部FLASH的組織結構
如上圖,STM32F407內部FLASH包含4個16K扇區+1個64K扇區+7個128K扇區,并且起始地址為0x0800 0000,所以它對應的接口字符串表示為: “@Internal Flash /0x08000000/03016Ka,01016Kg,01064Kg,07128Kg”, Internal Flash為在工具軟件顯示的名稱,0x08000000為起始地址,03016Ka表示3個16K大小只讀的扇區,01064Kg表示1個64K大小的可讀寫扇區,07*128Kg表示7個128K大小的可讀寫扇區,后綴a表示只讀,后綴g表示可讀寫。這個就是工具軟件DfuSe Demo(v3.0.5)與DFU設備之間的約定。如下:
圖5 DfuSeDemo軟件中所顯示的內部FLASH的可讀寫屬性
知道了這些信息后,我們再回過頭來看APP的起始地址0x0800C000,那么APP的起始地址該如何得來的?有什么要求?與這個接口字符串之間是否有關系?
到目前為止,我們可以確定地是,APP_DEFAULT_ADD的地址必須是位于接口字符串表示的可讀寫的地址范圍內,也就是第4個扇區起(前3個扇區都是只讀的),不然是燒錄不進去的。其他問題我們先暫且放一放,后續我們回過頭來會回答這個問題。
Project Setting :堆設置為0x500,棧大小設置為0x2000。
圖6 堆棧設置
另外,在高級設置中,設置先不調用對USB DFU的初始化:
圖7 高級設置
最后生成代碼。
3 代碼完善
對生成后的代碼是可以直接編譯通過的,我們這里使用的是IAR,當然你也可以使用MDK,由于不同編譯器編譯的最終文件大小有所差異,而APP的偏移地址在一定程度上也是有考慮到這個DFU本身代碼大小的,接下來我們都將以IAR為例。
打開usbd_duf_if.c文件,這個文件就是USB DFU CLASS與本地對接的接口實現文件,我們需要對這個源文件內沒有接口填充其具體實現內容,當然,我們主要的目的是想借助DFU這個IAP來實現對APP的升級和跳轉,而這些接口就是實現對FLASH讀寫的操作。
uint16_tMEM_If_Init_FS(void) { /*USERCODEBEGIN0*/ HAL_FLASH_Unlock(); return(USBD_OK); /*USERCODEEND0*/ }
如上,初始化實現對FALSH的解鎖。
uint16_tMEM_If_DeInit_FS(void) { /*USERCODEBEGIN1*/ HAL_FLASH_Lock(); return(USBD_OK); /*USERCODEEND1*/ }
對應地,反初始化時,實現對FALSH的上鎖。
uint16_tMEM_If_Erase_FS(uint32_tAdd) { /*USERCODEBEGIN2*/ uint32_tstartsector=0; uint32_tsectornb=0; /*VariablecontainsFlashoperationstatus*/ HAL_StatusTypeDefstatus; FLASH_EraseInitTypeDeferaseinitstruct; /*Getthenumberofsector*/ startsector=GetSector(Add); eraseinitstruct.TypeErase=FLASH_TYPEERASE_SECTORS; eraseinitstruct.VoltageRange=FLASH_VOLTAGE_RANGE_3; eraseinitstruct.Sector=startsector; eraseinitstruct.NbSectors=1; status=HAL_FLASHEx_Erase(&eraseinitstruct,§ornb); if(status!=HAL_OK) { return1; } return0; /*USERCODEEND2*/ }
如上,實現對FLASH擦除操作。對應的GetSector函數實現如下:
staticuint32_tGetSector(uint32_tAddress) { uint32_tsector=0; if((Address=ADDR_FLASH_SECTOR_0)) { sector=FLASH_SECTOR_0; } elseif((Address=ADDR_FLASH_SECTOR_1)) { sector=FLASH_SECTOR_1; } elseif((Address=ADDR_FLASH_SECTOR_2)) { sector=FLASH_SECTOR_2; } elseif((Address=ADDR_FLASH_SECTOR_3)) { sector=FLASH_SECTOR_3; } elseif((Address=ADDR_FLASH_SECTOR_4)) { sector=FLASH_SECTOR_4; } elseif((Address=ADDR_FLASH_SECTOR_5)) { sector=FLASH_SECTOR_5; } elseif((Address=ADDR_FLASH_SECTOR_6)) { sector=FLASH_SECTOR_6; } elseif((Address=ADDR_FLASH_SECTOR_7)) { sector=FLASH_SECTOR_7; } elseif((Address=ADDR_FLASH_SECTOR_8)) { sector=FLASH_SECTOR_8; } elseif((Address=ADDR_FLASH_SECTOR_9)) { sector=FLASH_SECTOR_9; } elseif((Address=ADDR_FLASH_SECTOR_10)) { sector=FLASH_SECTOR_10; } else { sector=FLASH_SECTOR_11; } returnsector; }
寫操作:
uint16_tMEM_If_Write_FS(uint8_t*src,uint8_t*dest,uint32_tLen) { /*USERCODEBEGIN3*/ uint32_ti=0; for(i=0;i
如上,實現對FLASH的寫操作。
uint8_t*MEM_If_Read_FS(uint8_t*src,uint8_t*dest,uint32_tLen) { /*ReturnavalidaddresstoavoidHardFault*/ /*USERCODEBEGIN4*/ uint32_ti=0; uint8_t*psrc=src; for(i=0;i
讀FLASH接口實現。
uint16_tMEM_If_GetStatus_FS(uint32_tAdd,uint8_tCmd,uint8_t*buffer) { /*USERCODEBEGIN5*/ switch(Cmd) { caseDFU_MEDIA_PROGRAM: buffer[1]=(uint8_t)FLASH_PROGRAM_TIME; buffer[2]=(uint8_t)(FLASH_PROGRAM_TIME<8); ????buffer[3]?=?0; ????break; ??case?DFU_MEDIA_ERASE: ????buffer[1]?=?(uint8_t)FLASH_ERASE_TIME; ????buffer[2]?=?(uint8_t)(FLASH_ERASE_TIME?<8); ????buffer[3]?=?0; ??default: ????break; ??} ??return??(USBD_OK); ??/*?USER?CODE?END?5?*/?? }
獲取狀態接口實現。
接下來實現從DFU跳轉到APP的功能,在main函數中 :
/*USERCODEBEGIN2*/ if(HAL_GPIO_ReadPin(USER_BTN_GPIO_Port,USER_BTN_Pin)==GPIO_PIN_SET) { /*Testifusercodeisprogrammedstartingfromaddress0x0800C000*/ if(((*(__IOuint32_t*)USBD_DFU_APP_DEFAULT_ADD)&0x2FFE0000)==0x20000000) { /*Jumptouserapplication*/ JumpAddress=*(__IOuint32_t*)(USBD_DFU_APP_DEFAULT_ADD+4); JumpToApplication=(pFunction)JumpAddress; /*Initializeuserapplication'sStackPointer*/ __set_MSP(*(__IOuint32_t*)USBD_DFU_APP_DEFAULT_ADD); JumpToApplication(); } } MX_USB_DEVICE_Init(); /*USERCODEEND2*/
這樣代碼就大體修改完了,再次編譯下,生成最終可執行文件。我們得到IAR如下編譯信息:
18170bytesofreadonlycodememory 290bytesofreadonlydatamemory 12517bytesofreadwritedatamemory
那么DFU這個IAP本身所占ROM大小為(18170+290 )/1024 =18.02K,從圖4中我們可以得知,它需要占用兩個扇區(扇區0和1都是16K大小),那么APP至少應該是從扇區2開始。
此時,我們回過頭去看之前提到的APP偏移地址的問題,此處結合之前說到的APP必須是第4個扇區起,那么最終APP的地址應該設置在第4個扇區的起始位置,也就是扇區3的位置,從圖4可知,扇區3的起始位置為0x0800C000,這樣我們回到CubeMx中將其設置,這也就是為什么APP地址設置為0x0800C000的原因。
重新編譯并燒錄進MCU,接下來連接USB到PC,接可是識別這個DFU設備,并通過DfuSeDemo這個軟件升級APP了。
4 制作APP工程需要注意事項
不同編譯器設置方式略有不同,在IAR中:首先將system_stm32f4xx.c文件中找到VECT_TAB_OFFSET宏定義 :
#defineVECT_TAB_OFFSET0xC000 1
即將中斷向量表的偏移位置相應偏移0xC000.接下來修改連接選項 :
圖8 IAR鏈接設置
MDK中:?首先也是修改system_stm32f4xx.c文件中的VECT_TAB_OFFSET宏定義.接著 :
圖9 Target設置
相應設置好了接可以了。
5 測試
最后就是通過ST的軟件Dfu File Manager這個軟件將APP的HEX文件或BIN文件轉化成dfu文件,然后通過DfuSeDemo這個軟件導入dfu文件,最終燒錄APP到0x0800C000這個地址了,最終驗證是可以運行的。
6 總結
APP的起始地址應該設置為扇區的起始地址,且即使沒有重疊,也不能放在IAP的所在扇區。
APP的起始地址必須在USB DFU CLASS接口字符串所描述的可讀寫扇區范圍內。
審核編輯:湯梓紅
-
APP
+關注
關注
33文章
1569瀏覽量
72384 -
IAP
+關注
關注
2文章
163瀏覽量
24253 -
CubeMx
+關注
關注
0文章
30瀏覽量
1327
原文標題:如何使用CubeMx生成一個DFU工程
文章出處:【微信號:技術讓夢想更偉大,微信公眾號:技術讓夢想更偉大】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論