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

简介: stm32cubeMX学习、USB DFU(Download Firmware Update)固件更新

本程序编写基于秉火霸道STM32F103ZET6运行环境。

640.jpg

最近,特地将自己大部分硬件资源全部用热胶抢焊到了一起,以便以后自己复习和学习,当然还有很多,弄不上来了,只能等以后有机会再重新搞一块!我还是非常舍得花钱买设备的!哈哈!这是一个STM32+Linux+51的大杂烩开发平台!

640.jpg

1、产生问题

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

2、现有的硬件接口

现在的产品(野火的STM32F103ZET6开发板)有一个USB接口,硬件连接图如下:


640.png

如上图所示,当PD3为低电平的时候,USB接口供电,即可用,这一点在上一篇文章已经讲解了,我们在STM32CubeMX把这个管脚默认拉低即可。

3、分析问题

STM32CubeMX支持了与USB相关的诸多配置功能,请看如下:

640.png

于我们需要使用USB接口来更新程序,所以我们需要在配置USB设备模式的时候给它选择Download Firmware Update Class(DFU)。

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

1.1 烧写原理

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

1.2 程序存储分区

640.png

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时钟

640.jpg

640.png

4.1.2 配置串行调试接口

640.jpg

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

640.jpg

调试灯选择的是PB1,低电平点亮,具体可以看原理图:

640.png

640.jpg

USB使能管脚默认为低电平。

640.jpg

选用USART2作为调试打印输出。

640.jpg

4.1.4 配置USB相关的选项

640.jpg

配置的基本参数默认即可,不需要改变。

640.png

在中断设置这里,将USB优先级调低,可以避免一些默认其妙不稳定的现象。接下来配置USB设备相关的选项。

640.jpg

类参数有一个字段比较重要:

640.png

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

这个参数的具体含义描述如下:


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


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


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


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


  • 扇区数的最大2位数字


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


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


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


  • 扇区类型的1位数字,如下所示:
– a(0x41):可读
– b(0x42):可擦除
– c(0x43):可读和可擦除
(0x44):可写
– e(0x45):可读写
–f(0x46):可擦除和可写
–g(0x47):可读写,可写

4.1.5 生成工程

640.jpg

这里默认不让它自动生成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

定义调试打印接口,这里我用的是USART2

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(500);
}

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的频率进行翻转吧。

640.jpg

配置过程(略)太简单了,应用APP的核心代码如下:

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启动的地址

640.png

2、更改中断向量表偏移

640.jpg

接下来编译生成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

默认安装即可。


安装成功后得到两个软件。

640.png

Dfu file manager是把bin文件或者hex文件生成 .dfu后缀的文件, .dfu后缀的文件就是我们的固件。DfuSe_Demo是烧录 文件后缀 .dfu 软件。

烧录步骤:

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

640.png

640.png

生成后可以看到效果:

640.png

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

看到没有识别DFU

640.png

我们需要手动给它更新下驱动程序,直接就是刚刚下载的DfuSe安装的目录下找对应系统版本的驱动就好了。

640.jpg

640.jpg

最后可以看到该模式被识别了:

640.png

接下来打开DfuSeDemo这个软件,可以看到开发板现在已经被识别了。

640.png

接下来将刚刚生成的APP_TEST1.dfu加载进来。

640.jpg

640.png

点击Upgrade进行升级。

640.png

640.png

升级成功!接下来点击Leave DFU mode,程序则会自动开始执行。

640.png

这时候APP已经跑起来了,灯在以500ms的频率不断闪烁。

640.jpg

至此USB DFU固件成功!


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

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

如果有兴趣的话,还可以把我之前写的串口屏BootLoader那个程序继续升级一下!


带串口屏显示的Bootloader

往期精彩

带串口屏显示的Bootloader


为Linux应用构造有限状态机


编程修养(精品文,建议认真品读并实践)


嵌入式C语言代码优化方案(深度好文,建议花时间研读并收藏)

目录
相关文章
|
6月前
|
芯片 存储 C语言
STM32F103标准外设库——固件库 (三)
STM32F103标准外设库——固件库 (三)
410 0
STM32F103标准外设库——固件库 (三)
|
27天前
stm32学习 3-2 LED流水灯
stm32学习 3-2 LED流水灯
58 4
|
27天前
stm32学习3-1 LED闪烁
stm32学习3-1 LED闪烁
32 4
|
6月前
|
IDE 编译器 开发工具
学习STM32,该用哪款开发工具?
学习STM32,该用哪款开发工具?
126 1
|
6月前
stm32f4外设学习篇(代码集合)(三)
stm32f4外设学习篇(代码集合)
113 0
|
6月前
stm32f4外设学习篇(代码集合)(二)
stm32f4外设学习篇(代码集合)
|
6月前
|
芯片
stm32f4外设学习篇(代码集合)(一)
stm32f4外设学习篇(代码集合)
129 0
No.0 个人与固件库工程文件分析 结构(STM32F429/F767/H743)
No.0 个人与固件库工程文件分析 结构(STM32F429/F767/H743)
|
6月前
|
存储 C语言 芯片
C/C++ stm32基础知识超详细讲解(系统性学习day14)
C/C++ stm32基础知识超详细讲解(系统性学习day14)
|
6月前
|
存储 编译器 API
大神们分享STM32的学习方法
大神们分享STM32的学习方法
79 0