STM32高階開發(17)-使用DFU方案
STM32 DFU 快速移植及使用教程
什麼是 DFU
DFU全稱為Device Firmware update,是ST官方推出的一個通過USB介面進行IAP升級的方案,同串列埠ISP一樣,他們都整合在了晶片內部的Bootloader區段,可以通過配置boot引腳來啟動。(具體可參照ST文件:AN2606)。不過內建DFU的晶片大部分型號都比較新,如果你用的型號沒有內建DFU程式,沒關係我們也可以通過CubeMX來快速生成和移植一個DFU功能程式到你的Flash中來使用。
DFU方案完整的元件包括微控制器DFU Demo程式碼、PC端升級程式、PC端Demo程式碼以及相關資料手冊等。通過使用DFU方案,我們可以快速的整合升級功能到開發的產品中,同時還能夠快速的開發與之配套的升級程式。
使用CubeMX生成初始工程
由於官方提供的DFU例程並不多,我們很難找到現成的可已使用DFU程式,但是通過CubeMX我們可以很快速的配置和生成DFU的Bootloader,下面我們正式開始。
新建CubeMX工程
首先選定好IC的型號,進入配置介面,由於只是Bootloader程式碼所以這裡我們只需要配置USB功能和一個做Bootloader觸發的引腳就可,其餘的時鐘等部分一切按照正常方式配置。
設定USB引腳功能
設定USB模式為Device(HS還是FS並不影響DFU的功能,按照應用選擇就可)。
開啟DFU元件
在MiddleWares中加入USB DFU元件
設定DFU引數
開啟DFU元件後,CubeMX的程式設定視窗的MiddleWares中就會出現DFU程式設定按鈕。
點開它將APP載入的地址改為0x0800_c000,這個載入地址根據你實際的應用設定,目前我們選擇讓flash的前三個sector為Bootloader的區域。
第二個全是欄位的引數是用來在DFU連線升級軟體式傳輸給軟體用來獲取Flash結構字串資料,很好理解這個小協議的內容,點選設定後,下方的CubeMX的引數說明也寫的很清晰,這裡就不多說了。當然這些引數也在工程生成後在 usbd_conf.h 和 usbd_dfu_if.c 檔案中修改。
最後的設定
最後我們新增一個外部的按鍵作為觸發微控制器啟動時進入DFU的方式,按鍵按下後就啟動DFU模式,否則直接載入後方APP程式,這裡選用PA0引腳,給它設定個User Label 就叫 USER_BTN_GPIO_Port。
修改補全工程
實現 DFU 功能程式碼
開啟 src 目錄下的 usbd_dfu_if.c 檔案補全其中的功能程式碼
Flash 解鎖
uint16_t MEM_If_Init_HS(void) { HAL_FLASH_Unlock(); return (USBD_OK); }
Flash 上鎖
uint16_t MEM_If_DeInit_HS(void) { HAL_FLASH_Lock(); return (USBD_OK); }
Flash 擦除
static uint32_t GetSector(uint32_t Address) { uint32_t sector = 0; if ((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0)) { sector = FLASH_SECTOR_0; } ...... } else if ((Address < ADDR_FLASH_SECTOR_23) && (Address >= ADDR_FLASH_SECTOR_22)) { sector = FLASH_SECTOR_22; } else { sector = FLASH_SECTOR_23; } return sector; } uint16_t MEM_If_Erase_HS(uint32_t Add) { uint32_t startsector = 0; uint32_t sectornb = 0; /* Variable contains Flash operation status */ HAL_StatusTypeDef status; FLASH_EraseInitTypeDef eraseinitstruct; /* Get the number of sector */ 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) { return (USBD_FAIL); } return (USBD_OK); }
Flash 寫入
uint16_t MEM_If_Write_HS(uint8_t *src, uint8_t *dest, uint32_t Len) { uint32_t i = 0; for (i = 0; i < Len; i += 4) { /* Device voltage range supposed to be [2.7V to 3.6V], the operation will be done by byte */ if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)(dest + i), *(uint32_t *)(src + i)) == HAL_OK) { /* Check the written value */ if (*(uint32_t *)(src + i) != *(uint32_t *)(dest + i)) { /* Flash content doesn't match SRAM content */ return (USBD_FAIL); } } else { /* Error occurred while writing data in Flash memory */ return (USBD_FAIL); } } return (USBD_OK); }
Flash 讀取
uint8_t *MEM_If_Read_HS(uint8_t *src, uint8_t *dest, uint32_t Len) { /* Return a valid address to avoid HardFault */ uint32_t i = 0; uint8_t *psrc = src; for (i = 0; i < Len; i++) { dest[i] = *psrc++; } /* Return a valid address to avoid HardFault */ return (uint8_t *)(dest); }
獲取 Flash 擦寫時間引數
uint16_t MEM_If_GetStatus_HS(uint32_t Add, uint8_t Cmd, uint8_t *buffer) { /* USER CODE BEGIN 11 */ uint16_t time; time = TimingTable[GetSector(Add)]; switch (Cmd) { case DFU_MEDIA_PROGRAM: buffer[1] = (uint8_t)time; buffer[2] = (uint8_t)(time << 8); buffer[3] = 0; break; case DFU_MEDIA_ERASE: default: buffer[1] = (uint8_t)time; buffer[2] = (uint8_t)(time << 8); buffer[3] = 0; break; } return (USBD_OK); /* USER CODE END 11 */ }
usbd_dfu_if.h 檔案新增的巨集定義
/* Define flash address */ // BLANK 1 #define ADDR_FLASH_SECTOR_0 0x08000000 #define ADDR_FLASH_SECTOR_1 0x08004000 #define ADDR_FLASH_SECTOR_2 0x08008000 #define ADDR_FLASH_SECTOR_3 0x0800C000 #define ADDR_FLASH_SECTOR_4 0x08010000 #define ADDR_FLASH_SECTOR_5 0x08020000 #define ADDR_FLASH_SECTOR_6 0x08040000 #define ADDR_FLASH_SECTOR_7 0x08060000 #define ADDR_FLASH_SECTOR_8 0x08080000 #define ADDR_FLASH_SECTOR_9 0x080A0000 #define ADDR_FLASH_SECTOR_10 0x080C0000 #define ADDR_FLASH_SECTOR_11 0x080E0000 // BLANK 2 #define ADDR_FLASH_SECTOR_12 0x08100000 #define ADDR_FLASH_SECTOR_13 0x08104000 #define ADDR_FLASH_SECTOR_14 0x08108000 #define ADDR_FLASH_SECTOR_15 0x0810C000 #define ADDR_FLASH_SECTOR_16 0x08110000 #define ADDR_FLASH_SECTOR_17 0x08120000 #define ADDR_FLASH_SECTOR_18 0x08140000 #define ADDR_FLASH_SECTOR_19 0x08160000 #define ADDR_FLASH_SECTOR_20 0x08180000 #define ADDR_FLASH_SECTOR_21 0x081A0000 #define ADDR_FLASH_SECTOR_22 0x081C0000 #define ADDR_FLASH_SECTOR_23 0x081E0000 /* Flash oprate time from datasheet page 128 */ #define FLASH_SECTOR_16KB_WRITE_ERASE_TIME 500 //500 usb frame,means 500ms #define FLASH_SECTOR_64KB_WRITE_ERASE_TIME 1100 #define FLASH_SECTOR_128KB_WRITE_ERASE_TIME 2000
修改Main檔案
首先在main檔案前新增幾個用於載入APP程式的變數和函式定義
typedef void (*pFunction)(void); pFunction JumpToApplication; uint32_t JumpAddress;
然後再 main 函式中加入 外部按鍵的判斷、APP程式載入以及USB DFU初始化功能
int main(void) { /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); if (HAL_GPIO_ReadPin(USER_BTN_GPIO_Port, USER_BTN_Pin) == GPIO_PIN_SET) { HAL_GPIO_WritePin(GPIOG, LD3_Pin, GPIO_PIN_SET); // For debug /* Test if user code is programmed starting from address 0x0800C000 */ if (((*(__IO uint32_t *)USBD_DFU_APP_DEFAULT_ADD) & 0x2FF80000) == 0x20000000) { HAL_GPIO_WritePin(GPIOG, LD4_Pin, GPIO_PIN_SET); // For debug /* Jump to user application */ JumpAddress = *(__IO uint32_t *)(USBD_DFU_APP_DEFAULT_ADD + 4); JumpToApplication = (pFunction)JumpAddress; /* Reset of all peripherals */ HAL_DeInit(); /* Set interrupt vector to app code */ SCB->VTOR = USBD_DFU_APP_DEFAULT_ADD; /* Initialize user application's Stack Pointer */ __set_MSP(*(__IO uint32_t *)USBD_DFU_APP_DEFAULT_ADD); JumpToApplication(); } } MX_USB_DEVICE_Init(); while (1) { } }
- 編譯程式下載進入微控制器
使用DfuSe
從ST官網DfuSe的程式安裝包,並安裝。然後我們按下之前寫的觸發按鍵並復位微控制器,讓微控制器初始 USB DFU 功能,這時如果你插著微控制器的USB線,系統應該已經識別了。如果沒有右鍵更新驅動程式,手動指定驅動搜尋路徑在DfuSe安裝目錄下的 \Bin\Driver 內。如果直接無法識別USB裝置,建議在CubeMx配置完工程後就編譯下載測試一下,看看是不是你在移植過程中哪裡寫錯了。
然後我們需要生成一個地址設定在0x0800_c000後的測試程式,就先編寫一個 Blink LED 的程式吧,生成bin、hex或S19檔案。然後我們開啟DfuSe軟體的Dfu file manager來生成DFU軟體用的.dfu格式的檔案。選擇第一項,第二個是用來將.dfu反向變換回來的。大概的操作已經標在圖片上了,操作比較簡單就不做詳細介紹了,記得把Address的地址改到偏移後的地址上否則下載會出錯,其他引數可以不用修改。
然後我們開啟DfuSe程式,在Upgrade中選擇生成好的blink.dfu檔案,勾選校驗功能,下載程式。成功後復位微控制器,LED開始閃爍,移植成功。
更多
仔細區看看DfuSe的安裝目錄,裡面有DFU的資料文件,還有DFU的工程原始碼,可以用來改寫自己需要的DFU升級程式。