STM32:USB 虚拟串口以及使用usb->dfu进行iap

本文介绍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 flash
      HAL_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 access
      MEM_If_Init_FS();
    
      UserStartSector = GetSector(Add);
    
      pEraseInit.TypeErase = TYPEERASE_SECTORS;
      pEraseInit.Sector = UserStartSector;
      pEraseInit.NbSectors = 4; // 需要擦除的扇区数,扇区8-11
      pEraseInit.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 interrupt
        JumpToApplication();
      }
    }
    

    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 */
    }
    

    tips

    作者:十一月海拒绝河

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32:USB 虚拟串口以及使用usb->dfu进行iap

    发表回复