欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新

程序员文章站 2022-06-08 20:49:57
...

本程序编写基于秉火霸道STM32F103ZET6运行环境。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
最近疫情期间,特地将自己大部分硬件资源全部用热胶抢焊到了一起,以便以后自己复习和学习,当然还有很多,弄不上来了,只能等以后有机会再重新搞一块!我还是非常舍得花钱买设备的!哈哈!这是一个STM32+Linux+51的大杂烩开发平台!
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新

1、产生问题

公司的产品,每次生产烧写程序都得把机器拆开,然后插上串行线或者ST-Link进行烧写,产品量产的情况下数量很多,所以生产每次都需要花费很长去时间去给机器烧程序(这里我们用野火的开发板来模拟)。

2、现有的硬件接口

现在的产品(野火的STM32F103ZET6开发板)有一个USB接口,硬件连接图如下:
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
如图所示,当PD3为低电平的时候,USB接口供电,即可用,这一点在上一篇文章已经讲解了,我们在STM32CubeMX把这个管脚默认拉低即可。

3、分析问题

STM32CubeMX支持了与USB相关的诸多配置功能,请看如下:
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
由于我们需要使用USB接口来更新程序,所以我们需要在配置USB设备模式的时候给它选择Download Firmware Update Class(DFU)。

1、USB烧写原理及流程分析

1.1 烧写原理

这点与IAP升级是大同小异的,只不过这里我们使用了USB来烧写,之前写过类似的一篇文章:
带串口屏显示的BootLoader程序开发
在这篇文章里面也介绍了相应的原理,这里就不再重复描述,我们负责把这篇文章里提到的几点实现就可以了。

1.2 程序存储分区

stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
STM32F103ZET6的FLASH容量一共有512KB。
所以,我给BootLoader的大小是64K,也就是0x10000
0x10000转十进制为65536,65536/1024 = 64K
把剩下的空间全部分配给APP,也就是0x70000
0x70000转十进制为458752,458752/1024 = 448K

4、解决问题

4.1 配置编写BootLoader程序的CubeMX工程

4.1.1 配置RCC时钟

stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新

4.1.2 配置串行调试接口

stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新

4.1.4 配置按键、调试灯、调试串口、USB使能管脚

stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
调试灯选择的是PB1,低电平点亮,具体可以看原理图:
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
USB使能管脚默认为低电平。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
选用USART2作为调试打印输出。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新

4.1.5 配置USB相关的选项

stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
配置的基本参数默认即可,不需要改变。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
在中断设置这里,将USB优先级调低,可以避免一些默认其妙不稳定的现象。
接下来配置USB设备相关的选项。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
类参数有一个字段比较重要:
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
这个参数的具体含义描述如下:

  • @:检测到这是一个特殊的映射描述符(避免解码标准描述符)

  • /:用于区域之间的分隔符

  • 每个地址以“ 0x”开头的最大8位数字

  • /:用于区域之间的分隔符

  • 扇区数的最大2位数字

  • *:用于扇区数和扇区大小之间的分隔符

  • 扇区大小在0到999之间的最大3位

  • 扇区大小乘数的1位数字。有效条目为:B(字节),K(千),M(兆)

  • 扇区类型的1位数字,如下所示:

    – a(0x41):可读
    – b(0x42):可擦除
    – c(0x43):可读和可擦除
    (0x44):可写
    – e(0x45):可读写
    –f(0x46):可擦除和可写
    –g(0x47):可读写,可写

@Internal Flash   /0x08000000/03*016Ka,01*016Kg,01*064Kg,07*128Kg,04*016Kg,01*064Kg,07*128Kg

其余参数不用动。

4.1.6 生成工程

stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
这里默认不让它自动生成main函数,main函数我们自己写。
在配置USB设备参数里,USBD_DFU_XFER_SIZE参数:USB数据pack大小,越大配置速度越快。默认配置1024Bytes. 1024Bytes使用的是堆空间,故堆空间要大于1024Bytes. 原因:代码如下。

#define USBD_malloc         malloc
 /* Allocate Audio structure */
  pdev->pClassData = USBD_malloc(sizeof (USBD_DFU_HandleTypeDef));

参考:添加链接描述
所以这里的堆我把它配置成0x1000。(个人习惯)

4.2 编写BootLoader程序

4.2.1 实现usbd_dfu_if.c中相关的接口

宏定义一些参数

//FLASH的擦写实现
#define FLASH_ERASE_TIME    (uint16_t)50
#define FLASH_PROGRAM_TIME  (uint16_t)50
//APP存放的结束地址
#define USBD_DFU_APP_END_ADD 0x08080000
//FLASH页大小
#define FLASH_PAGE_SIZE 0x800U //2K

实现如下接口:

MEM_If_Init_FS,       闪存初始化,解锁内部flash。
MEM_If_DeInit_FS,     闪存反(取消)初始化,上锁内部flash。
MEM_If_Erase_FS,      闪存擦除。
MEM_If_Write_FS,      闪存写入。
MEM_If_Read_FS,       闪存读取。
MEM_If_GetStatus_FS   获取闪存状态,返回写入或擦除操作所需的时间。

闪存初始化,解锁内部flash。

uint16_t MEM_If_Init_FS(void)
{
  /* USER CODE BEGIN 0 */
  //解锁内部FLASH
    HAL_FLASH_Unlock();
    //清除FLASH的一些标志,可以避免一些莫名其妙的问题
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR);
    return (USBD_OK);
  /* USER CODE END 0 */
}

闪存反(取消)初始化,上锁内部flash。

uint16_t MEM_If_DeInit_FS(void)
{
  /* USER CODE BEGIN 1 */
  //给FLASH上锁
    HAL_FLASH_Lock();
    return (USBD_OK);
  /* USER CODE END 1 */
}

闪存擦除。

uint16_t MEM_If_Erase_FS(uint32_t Add)
{
  /* USER CODE BEGIN 2 */
    /*擦除整个APP程序存放的空间,即是0x08080000-0x08010000*/
    /*
        因为起始地址是0x8000000,而Size是0x80000,所以MCU存放代码的最后一个区域的地址为0x8080000。
        而DFU占了其中的0x10000的空间。
    */
    uint32_t NbOfPages = 0 ;
    uint32_t PageError = 0 ;
    FLASH_EraseInitTypeDef pEraseInit ;
    NbOfPages = (USBD_DFU_APP_END_ADD - USBD_DFU_APP_DEFAULT_ADD)/FLASH_PAGE_SIZE ;
    pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
    pEraseInit.PageAddress = USBD_DFU_APP_DEFAULT_ADD;
    pEraseInit.NbPages = NbOfPages;      //erase all pages of APP
    if(HAL_FLASHEx_Erase(&pEraseInit,&PageError)!= HAL_OK)
        return USBD_FAIL ;
    return (USBD_OK);
  /* USER CODE END 2 */
}

闪存写入。

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)
    {
        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 3 */
}

闪存读取。

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 (uint8_t*) (dest);
  /* USER CODE END 4 */
}

获取闪存状态,返回写入或擦除操作所需的时间。

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;
            break ;
        default:
           
            break;
    }

    return (USBD_OK);
  /* USER CODE END 5 */
}

4.2.1 实现main.c

定义调试打印接口

int fputc(int ch, FILE* FILE)
{
    HAL_UART_Transmit(&huart2, (uint8_t*)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

跳转到APP实现

static 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)
    {
        /* 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();
    }
}

在正常启动过程中,如果APP区域存放有数据,我们不希望去启动USB,在刚开始的时候我们可以把USB的功能给失能掉,如果检测到APP区域没有数据,则再初始化USB功能,所以在这里编写一个USB的失能函数。

static void USB_GPIO_DeInit(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* GPIO Ports Clock Enable */
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11 | GPIO_PIN_12, GPIO_PIN_RESET);

    /*Configure GPIO pin*/
    GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    HAL_Delay(100);
}

main函数实现

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    USB_GPIO_DeInit();
    MX_USART2_UART_Init();
    /*如果没有按下按键,则自动跳转到APP区,如果跳转不过去,则代表区域无APP*/
    if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) != GPIO_PIN_SET)
    {
        JumpToApp();
        printf("跳转失败,开始进入DFU模式\r\n");
    }
    //进入DFU模式
    MX_USB_DEVICE_Init();
    printf("Bruce.Yang DFU\n");
    //调试灯常亮,代表此时在DFU模式
    HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin,GPIO_PIN_RESET);
    while(1)
    {
        HAL_Delay(1000);
    }
}

实现完毕,接下来可以编译程序,下载到开发板,由于没有APP,所以开发板上PB1的灯常亮。

4.2.2 编写APP程序

APP程序很简单,就让PB1灯以500ms的频率进行翻转吧。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
配置过程(略)太简单了,代码如下:

 while (1)
  {
    /* USER CODE END WHILE */
    HAL_GPIO_TogglePin(BLUE_LED_GPIO_Port,BLUE_LED_Pin);
    HAL_Delay(500);
    /* USER CODE BEGIN 3 */
  }

接下来主要是在工程里需要做一些设置。

1、点击魔术棒设置APP启动的地址stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
2、更改中断向量表偏移

stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
接下来编译生成APP_TEST.hex文件,我们用一个工具来将它烧写到板子上。

安装DFU烧录软件:DfuSe_Demo

官网下载链接:

https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-programmers/stsw-stm32080.html#resource

默认安装即可。

安装成功后得到两个软件。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
Dfu file manager是把bin文件或者hex文件生成 .dfu后缀的文件, .dfu后缀的文件就是我们的固件。
DfuSe_Demo是烧录 文件后缀 .dfu 软件。

烧录步骤:

1、将.hex文件转化成.dfu后缀的文件

stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
生成后可以看到效果:
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新

2、连接USB到开发板的设备端口到PC

看到没有识别DFU
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
我们需要手动给它更新下驱动程序,直接就是刚刚下载的DfuSe安装的目录下找对应系统版本的驱动就好了。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
最后可以看到该模式被识别了:
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
接下来打开DfuSeDemo这个软件,可以看到开发板现在已经被识别了。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
接下来将刚刚生成的APP_TEST1.dfu加载进来。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
点击Upgrade进行升级。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
升级成功!stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
接下来点击Leave DFU mode,程序则会自动开始执行。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
这时候APP已经跑起来了,灯在以500ms的频率不断闪烁。
stm32cubeMX学习十九、USB DFU(Download Firmware Update)固件更新
至此USB DFU固件成功!

Bootloader代码以及APP代码在这里下载:

链接:https://pan.baidu.com/s/1zRv7j4E8SXgCV5F6RbSo1Q 
提取码:5539 

如果有兴趣的话,还可以把我之前写的串口屏BootLoader那个程序继续升级一下!
stm32cubeMX学习九、带串口屏显示的BootLoader程序开发(基于野火STM32F103ZET6霸道开发板)

时候不早了,现在是2020年2月20日凌晨1:41分,我还没复工,洗洗睡吧!

相关标签: stm32cubeMX