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

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

1、产生问题

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

2、现有的硬件接口

现在的产品(野火的STM32F103ZET6开发板)有一个USB接口,硬件连接图如下:
在这里插入图片描述
如图所示,当PD3为低电平的时候,USB接口供电,即可用,这一点在上一篇文章已经讲解了,我们在STM32CubeMX把这个管脚默认拉低即可。

3、分析问题

STM32CubeMX支持了与USB相关的诸多配置功能,请看如下:
在这里插入图片描述
由于我们需要使用USB接口来更新程序,所以我们需要在配置USB设备模式的时候给它选择Download Firmware Update Class(DFU)。

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

1.1 烧写原理

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

1.2 程序存储分区

在这里插入图片描述
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时钟

在这里插入图片描述
在这里插入图片描述

4.1.2 配置串行调试接口

在这里插入图片描述

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

在这里插入图片描述
调试灯选择的是PB1,低电平点亮,具体可以看原理图:
在这里插入图片描述
在这里插入图片描述
USB使能管脚默认为低电平。
在这里插入图片描述
选用USART2作为调试打印输出。
在这里插入图片描述

4.1.5 配置USB相关的选项

在这里插入图片描述
配置的基本参数默认即可,不需要改变。
在这里插入图片描述
在中断设置这里,将USB优先级调低,可以避免一些默认其妙不稳定的现象。
接下来配置USB设备相关的选项。
在这里插入图片描述
类参数有一个字段比较重要:
在这里插入图片描述
这个参数的具体含义描述如下:

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

    扫描二维码关注公众号,回复: 9300978 查看本文章
  • /:用于区域之间的分隔符

  • 每个地址以“ 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 生成工程

在这里插入图片描述
这里默认不让它自动生成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的频率进行翻转吧。
在这里插入图片描述
配置过程(略)太简单了,代码如下:

 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启动的地址在这里插入图片描述
2、更改中断向量表偏移

在这里插入图片描述
接下来编译生成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

默认安装即可。

安装成功后得到两个软件。
在这里插入图片描述
Dfu file manager是把bin文件或者hex文件生成 .dfu后缀的文件, .dfu后缀的文件就是我们的固件。
DfuSe_Demo是烧录 文件后缀 .dfu 软件。

烧录步骤:

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

在这里插入图片描述
在这里插入图片描述
生成后可以看到效果:
在这里插入图片描述

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

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

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

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

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

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

发布了597 篇原创文章 · 获赞 1061 · 访问量 182万+

猜你喜欢

转载自blog.csdn.net/morixinguan/article/details/104403413