如何使用CubeMx生成一个DFU工程

发布时间:2023-03-28  

1 前言

DFU用来做IAP是很方便的,可以直接通过USB来对APP进行升级,因此,掌握DFU的制作还是挺有好处,特别是使用CubeMx工具可以快速制作,本文将基于STM3240G-EVL评估板来一步一步实现一个DFU的IAP工程。

2 制作CubeMx工程

新建一个STM32F407IGHx工程:Pinout:

  • Peripherals: RCC->High Speed Clock(HSE):Crystal/Ceramic Resonator SYS->Debug:Serial Wire USB_OTG_FS->Mode:Device_Only

  • MiddleWares USB_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_t MEM_If_Init_FS(void)

  /* USER CODE BEGIN 0 */ 

    HAL_FLASH_Unlock();

  return (USBD_OK);

  /* USER CODE END 0 */ 

}

如上,初始化实现对FALSH的解锁。


uint16_t MEM_If_DeInit_FS(void)

  /* USER CODE BEGIN 1 */ 

    HAL_FLASH_Lock();

  return (USBD_OK);

  /* USER CODE END 1 */ 

}

对应地,反初始化时,实现对FALSH的上锁。


uint16_t MEM_If_Erase_FS(uint32_t Add)

{

  /* USER CODE BEGIN 2 */ 

  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, &sectornb);


  if (status != HAL_OK)

  {

    return 1;

  }

  return 0;

  /* USER CODE END 2 */ 

}

如上,实现对FLASH擦除操作。对应的GetSector函数实现如下:


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

  {

    sector = FLASH_SECTOR_11;

  }

  return sector;

}

写操作:


uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len)

{

  /* USER CODE BEGIN 3 */ 

  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 2;

      }

    }

    else

    {

      /* Error occurred while writing data in Flash memory */

      return 1;

    }

  }

  return 0;

  /* USER CODE END 3 */ 

}

如上,实现对FLASH的写操作。


uint8_t *MEM_If_Read_FS (uint8_t *src, uint8_t *dest, uint32_t Len)

{

  /* Return a valid address to avoid HardFault */

  /* USER CODE BEGIN 4 */ 

  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);

  /* USER CODE END 4 */ 

}

读FLASH接口实现。


uint16_t MEM_If_GetStatus_FS (uint32_t Add, uint8_t Cmd, uint8_t *buffer)

{

  /* USER CODE BEGIN 5 */ 

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

}

获取状态接口实现。


接下来实现从DFU跳转到APP的功能,在main函数中 :


/* USER CODE BEGIN 2 */

    if(HAL_GPIO_ReadPin(USER_BTN_GPIO_Port,USER_BTN_Pin) ==GPIO_PIN_SET)

    {

    /* Test if user code is programmed starting from address 0x0800C000 */

    if(((*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000 ) == 0x20000000)

    {

      /* 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);

      JumpToApplication();

    }

    }

    MX_USB_DEVICE_Init();

  /* USER CODE END 2 */

这样代码就大体修改完了,再次编译下,生成最终可执行文件。我们得到IAR如下编译信息:


18 170 bytes of readonly  code memory

     290 bytes of readonly  data memory

  12 517 bytes of readwrite data memory

那么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宏定义 :

#define VECT_TAB_OFFSET  0xC000
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 总结

  1. APP的起始地址应该设置为扇区的起始地址,且即使没有重叠,也不能放在IAP的所在扇区。

  2. APP的起始地址必须在USB DFU CLASS接口字符串所描述的可读写扇区范围内。

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>