本文介紹stm32上usb的常用功能虛擬串口和DFU(Download Firmware Update)
文章目錄
- 前言
- 一、usb
- 二、虛擬串口
- 1.cubemx配置
- 1.我們選用高速usb,然后選擇內部低速的phy,這樣使用的usb,最高速度為12Mbit每秒。
- 2.USB_DEVICE cdc類配置
- 3.時鐘配置,USB需要使能外設時鐘,且好像只能倍頻為48M
- 4.最小的堆棧大小也設置的大一點,不然端口識別會出現錯誤。
- 5.發送和接收函數
- 2.使用usb的dfu實現iap
- 1.cubemx
- 3.代碼
- tips
前言
USB虛擬串口不僅簡化了硬件設計,避免了傳統串口電平轉換芯片的使用,還能提供更穩定的通信速率和更遠的傳輸距離。對于調試信息輸出、數據采集與監控等應用場景來說,USB虛擬串口是一種非常實用的技術方案。此外,通過USB實現DFU功能,使得用戶可以在不依賴額外硬件工具的情況下,方便快捷地對STM32設備進行固件更新,極大地提高了產品的可維護性和用戶體驗。
提示:以下是本篇文章正文內容,下面案例可供參考
一、usb
關于usb可以看這篇文章
STM32-USB學習系列(一) :USB與USB庫的介紹
我們需要用到的USB的CDC(Communications Device Class)是一種用于通過USB接口實現設備間通信的協議。它旨在為各種類型的通信接口提供標準化的方法,包括但不限于虛擬串口、調制解調器等。CDC通常被用來讓計算機與外部設備進行數據交換,例如連接到計算機的手機、網絡攝像頭或其他外圍設備。
CDC類可以進一步分為多個子類,其中最常見的是抽象控制模型(Abstract Control Model, ACM),這通常用于模擬傳統的串行端口通信。這對于需要通過串行通信來傳輸數據的設備特別有用,比如一些物聯網設備、嵌入式系統或者需要固件更新的硬件。
使用CDC的一個主要優點是,它可以使得基于串行通信的設備無需物理串行端口即可與現代計算機系統通信,因為很多新式的計算機已經去除了傳統的RS-232串行接口。此外,許多操作系統對CDC有內置的支持,這意味著不需要額外安裝驅動程序就可以識別和使用這些設備,簡化了用戶的操作體驗。
二、虛擬串口
1.cubemx配置
我的實驗板子是野火的f429挑戰者v1,他比較奇怪的一點是他用的應該是全速的usb,但是引腳用的是高速usb的引腳,然后有id信號線,看起來像是高速的,但是我又沒看到高速的usb phy。是我的理解有問題嗎,有沒有知道的能解答一下。
1.我們選用高速usb,然后選擇內部低速的phy,這樣使用的usb,最高速度為12Mbit每秒。
其他的選項不做處理。
2.USB_DEVICE cdc類配置
需要注意的是,要確定自己的是大存儲芯片還是小存儲芯片,如果是f103c8t6,這里的TX 和 RX buffer size 應該是1024
3.時鐘配置,USB需要使能外設時鐘,且好像只能倍頻為48M
我一開始倍頻為45M時,會顯示時鐘錯誤。而且時鐘最好在使能usb之后再去配置,我遇到過先配置了時鐘,再配置usb,然后后面時鐘再配置的時候,usb外設時鐘一直沒有使能。
4.最小的堆棧大小也設置的大一點,不然端口識別會出現錯誤。
5.發送和接收函數
接收函數
static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
{/* USER CODE BEGIN 11 */USBD_CDC_SetRxBuffer(&hUsbDeviceHS, &Buf[0]);USBD_CDC_ReceivePacket(&hUsbDeviceHS);// CDC_Transmit_HS(Buf,*Len);user_usb_vpc_func(Buf,*Len);//用戶定義函數return (USBD_OK);/* USER CODE END 11 */
}
發送函數
void user_usb_vpc_func(uint8_t* Buf, uint32_t Len)
{CDC_Transmit_HS(Buf,Len);//數據回環發送
}
2.使用usb的dfu實現iap
dfu:Device Firmware Upgrade
iap:In-Application Programming,IAP指的是在不使用外部編程器的情況下,在設備正在運行的應用程序中直接對存儲在其內部或外部閃存中的固件進行編程的能力
我們常用的iap手段有
串口:優點是方便,硬件涉及簡單,缺點是速度慢,速度在幾KB撐死了,而且在出品后的現場環境中,串口往往沒有引出來。
sd:優點是速度快,就算是普通的sd卡接口,也能到25M,使用簡單,不需要上位機。缺點,如果需要頻繁的升級則需要頻繁的插拔和sd卡中的固件更新。而且需要額外的器件(sd卡)。
usb:優點是速度較快,全速在12M,高速可以上百兆(單片機會限速),而且使用方便,不用頻繁的插拔。缺點:需要上位機。
網絡:優點是速度快,幾十兆肯定能做到,使用方便,不用插拔。缺點,硬件成本較,技術要求相對較高,而且現場環境未必能支持你做網絡升級。
我在h7上做開發的時候,因為程序非常大,燒錄很慢,所以我就是用高速usb燒錄,然后仿真不擦除的去做調試。
要使用iap,我們需要兩個工程,一個是正常的應用程序,一個引導程序。
引導程序(bootloader)是用來做接收新固件程序和擦除改寫應用程序的flash的。
關于iap的介紹可以看這個
STM32 之八 在線升級(IAP)超詳細圖解 及 需要注意的問題解決
簡單來舉例說明,我需要兩個工程APP(用戶應用程序)和bootloader,stm32f429的flash程序起始地址為0x08000000,大小為0x100000。
其中一半用來做bootloader的程序地址,一半用來做app的程序地址。(具體分配可以隨意來,app的大小可以稍微大一點。但是注意flash的地址是按頁劃分的,所以兩個程序分配的大小也需要按頁來分。)
bootloader addr:0x08000000,size:0x100000(因為他需要訪問app的flash地址并作擦除和寫入操作)
app addr:0x08080000,size:0x80000
1.cubemx
usb配置和虛擬串口一樣。
這是軟件實現流程
3.代碼
usbd_dfu_if.c
/* USER CODE BEGIN PRIVATE_DEFINES */
#define FLASH_ERASE_TIME (uint16_t)50
#define FLASH_PROGRAM_TIME (uint16_t)50
// APP存放的結束地址
#define USBD_DFU_APP_END_ADD 0x08100000
// FLASH頁大小
#define FLASH_PAGE_SIZE 0x800U // 2K#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base address of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base address of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base address of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base address of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base address of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base address of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base address of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base address of Sector 7, 128 Kbytes */
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base address of Sector 8, 128 Kbytes */
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base address of Sector 9, 128 Kbytes */
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base address of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base address of Sector 11, 128 Kbytes */
/* USER CODE END PRIVATE_DEFINES */
/*** @}*//** @defgroup USBD_DFU_Private_Macros* @brief Private macros.* @{*//* USER CODE BEGIN PRIVATE_MACRO *//* USER CODE END PRIVATE_MACRO *//*** @}*//** @defgroup USBD_DFU_Private_Variables* @brief Private variables.* @{*//* USER CODE BEGIN PRIVATE_VARIABLES *//* USER CODE END PRIVATE_VARIABLES *//*** @}*//** @defgroup USBD_DFU_Exported_Variables* @brief Public variables.* @{*/extern USBD_HandleTypeDef hUsbDeviceHS;/* USER CODE BEGIN EXPORTED_VARIABLES *//* USER CODE END EXPORTED_VARIABLES *//*** @}*//** @defgroup USBD_DFU_Private_FunctionPrototypes* @brief Private functions declaration.* @{*/static uint16_t MEM_If_Init_HS(void);
static uint16_t MEM_If_Erase_HS(uint32_t Add);
static uint16_t MEM_If_Write_HS(uint8_t *src, uint8_t *dest, uint32_t Len);
static uint8_t *MEM_If_Read_HS(uint8_t *src, uint8_t *dest, uint32_t Len);
static uint16_t MEM_If_DeInit_HS(void);
static uint16_t MEM_If_GetStatus_HS(uint32_t Add, uint8_t Cmd, uint8_t *buffer);/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
static uint32_t GetSector(uint32_t Address);/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION *//*** @}*/#if defined(__ICCARM__) /* IAR Compiler */
#pragma data_alignment = 4
#endif__ALIGN_BEGIN USBD_DFU_MediaTypeDef USBD_DFU_fops_HS __ALIGN_END ={(uint8_t *)FLASH_DESC_STR,MEM_If_Init_HS,MEM_If_DeInit_HS,MEM_If_Erase_HS,MEM_If_Write_HS,MEM_If_Read_HS,MEM_If_GetStatus_HS};/* Private functions ---------------------------------------------------------*//*** @brief Memory initialization routine.* @retval USBD_OK if operation is successful, MAL_FAIL else.*/
uint16_t MEM_If_Init_HS(void)
{/* USER CODE BEGIN 6 */// unlock inter flashHAL_FLASH_Unlock();// clear the flash flag bit__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);return (USBD_OK);/* USER CODE END 6 */
}/*** @brief De-Initializes Memory.* @retval USBD_OK if operation is successful, MAL_FAIL else.*/
uint16_t MEM_If_DeInit_HS(void)
{/* USER CODE BEGIN 7 */HAL_FLASH_Lock();return (USBD_OK);/* USER CODE END 7 */
}/*** @brief Erase sector.* @param Add: Address of sector to be erased.* @retval USBD_OK if operation is successful, MAL_FAIL else.*/
uint16_t MEM_If_Erase_HS(uint32_t Add)
{/* USER CODE BEGIN 8 *//*擦除整個APP程序存放的空間,即是0x08080000-0x08010000*//*因為起始地址是0x8000000,而Size是0x100000,所以MCU存放代碼的最后一個區域的地址為0x8100000。而DFU占了其中的0x80000的空間。*/uint32_t UserStartSector;uint32_t SectorError;FLASH_EraseInitTypeDef pEraseInit;// Unlock the Flash to enable the flash control register accessMEM_If_Init_FS();UserStartSector = GetSector(Add);pEraseInit.TypeErase = TYPEERASE_SECTORS;pEraseInit.Sector = UserStartSector;pEraseInit.NbSectors = 4; // 需要擦除的扇區數,扇區8-11pEraseInit.VoltageRange = VOLTAGE_RANGE_3;if (HAL_FLASHEx_Erase(&pEraseInit, &SectorError) != HAL_OK){/* Error occurred while page erase */return (USBD_FAIL);}return (USBD_OK);/* USER CODE END 8 */
}/*** @brief Memory write routine.* @param src: Pointer to the source buffer. Address to be written to.* @param dest: Pointer to the destination buffer.* @param Len: Number of data to be written (in bytes).* @retval USBD_OK if operation is successful, MAL_FAIL else.*/
uint16_t MEM_If_Write_HS(uint8_t *src, uint8_t *dest, uint32_t Len)
{/* USER CODE BEGIN 9 */uint32_t i = 0;for (i = 0; i < Len; i += 4){if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)(dest + i), *(uint32_t *)(src + i)) == HAL_OK){if (*(uint32_t *)(src + i) != *(uint32_t *)(dest + i)){return USBD_FAIL;}}else{return USBD_FAIL;}}return (USBD_OK);/* USER CODE END 9 */
}/*** @brief Memory read routine.* @param src: Pointer to the source buffer. Address to be written to.* @param dest: Pointer to the destination buffer.* @param Len: Number of data to be read (in bytes).* @retval Pointer to the physical address where data should be read.*/
uint8_t *MEM_If_Read_HS(uint8_t *src, uint8_t *dest, uint32_t Len)
{/* Return a valid address to avoid HardFault *//* USER CODE BEGIN 10 */uint32_t i = 0;uint8_t *psrc = src;for (i = 0; i < Len; i++){dest[i] = *psrc++;}return (uint8_t *)(dest);/* USER CODE END 10 */
}/*** @brief Get status routine.* @param Add: Address to be read from.* @param Cmd: Number of data to be read (in bytes).* @param buffer: used for returning the time necessary for a program or an erase operation* @retval 0 if operation is successful*/
uint16_t MEM_If_GetStatus_HS(uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{/* USER CODE BEGIN 11 */switch (Cmd){case DFU_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 11 */
}/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION *//*** @brief 根據輸入的地址給出它所在的sector* 例如:uwStartSector = GetSector(FLASH_USER_START_ADDR);uwEndSector = GetSector(FLASH_USER_END_ADDR);* @param Address:地址* @retval 地址所在的sector*/
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_2) && (Address >= ADDR_FLASH_SECTOR_1)){sector = FLASH_SECTOR_1;}else if ((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2)){sector = FLASH_SECTOR_2;}else if ((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3)){sector = FLASH_SECTOR_3;}else if ((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4)){sector = FLASH_SECTOR_4;}else if ((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5)){sector = FLASH_SECTOR_5;}else if ((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6)){sector = FLASH_SECTOR_6;}else if ((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7)){sector = FLASH_SECTOR_7;}else if ((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8)){sector = FLASH_SECTOR_8;}else if ((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9)){sector = FLASH_SECTOR_9;}else if ((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10)){sector = FLASH_SECTOR_10;}else // Address > ADDR_FLASH_SECTOR_11{sector = FLASH_SECTOR_11;}return sector;
}/************************************************************************* * *@brief 跳轉到應用程序* * *
************************************************************************/
void JumpToApp(void)
{typedef void (*pFunction)(void);static pFunction JumpToApplication;static uint32_t JumpAddress;/* Test if user code is programmed starting from USBD_DFU_APP_DEFAULT_ADD * address */if (((*(__IO uint32_t *)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000) == 0x20000000){//bootmod key not push ,go to app/* Jump to user application */JumpAddress = *(__IO uint32_t *)(USBD_DFU_APP_DEFAULT_ADD + 4);JumpToApplication = (pFunction)JumpAddress;/* Initialize user application's Stack Pointer */__set_MSP((*(__IO uint32_t *)USBD_DFU_APP_DEFAULT_ADD));__disable_irq(); //shut down interruptJumpToApplication();}
}
main.c
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();// MX_USB_DEVICE_Init();/* USER CODE BEGIN 2 */if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET){JumpToApp();}printf("press key2 to download");/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_SET){MX_USB_DEVICE_Init();}}/* USER CODE END 3 */
}