(直播)利用stm32cubemx实现USBDFU对芯片程序的更新升级(板卡stm32f4discovery)

DFU这个功能给客户现场升级程序
功能类似串口实现IAP
有时我们会考虑利用STM32芯片的USB模块进行程序代码的下载或升级。USB协议中有专门针对设备固件升级的类协议,即可以通过DFU类协议进行产品固件的加载或更新
 前面介绍F103的usb dfu功能,但是搁浅了.
这次介绍F4discovery板卡上面实现DFU功能的具体操作
 
打开stm32cubemx软件,选择板卡那项,选discovery卡

图片1.jpg

左侧选择RCC时钟为外部时钟
usb_otg_fs选择Device_only

图片2.jpg

参考串口iap我们需要一个按键来控制dfu
discoevry卡上面的按键是PA0

图片3.jpg

回到上面,usb_device选择DFU模式

图片4.jpg

点击上方的时钟选项卡,会自动弹出一个对话框,提示的大致意思是你使用了usb所以时钟需要修改
这时候我们就默认让它自动计算值好了.自动调整的结果如下

图片5.jpg

 
 
 
已邀请:

admin

赞同来自:

这时我们对具体的usb_device进行操作
由于我只是演示下怎么使用dfu,所以我们只需要点击usb_device,其他部分就默认即可

图片6.jpg

打开后我们看到

图片7.jpg
USBD_DFU_MEDIA Interface
USBD_DFU_MEDIA
Parameter Description:
The description of the flash (used by PC tool DFuSe) Each Alternate setting string descriptor must follow this memory mapping so that the PC Host Software can decode the right mapping for the selected device: ● @: To detect that this is a special mapping descriptor (to avoid decoding standard descriptor) ● /: for separator between zones ● Maximum 8 digits per address starting by “0x” ● /: for separator between zones ● Maximum of 2 digits for the number of sectors ● *: For separator between number of sectors and sector size ● Maximum 3 digits for sector size between 0 and 999 ● 1 digit for the sector size multiplier. Valid entries are: B (byte), K (Kilo), M (Mega) ● 1 digit for the sector type as follows: – a (0x41): Readable – b (0x42): Erasable – c (0x43): Readable and Erasabled (0x44): Writeable – e (0x45): Readable and Writeable – f (0x46): Erasable and Writeable – g (0x47): Readable, Erasable and Writeable Note: If the target memory is not contiguous, the user can add the new sectors to be decoded just after a slash"/" as shown in the following example: "@Flash /0xF000/1*4Ka/0xE000/1*4Kg/0x8000/2*24Kg"
上面的大致意思是:如果你使用dfu模式,那么要想让PC端的dfu软件能够识别必须参照一定的协议规则.
@之后是特殊的映射描述符(解码标准描述)/
0x之后是芯片flash起始地址 /
后面是描述这个芯片flash的Sector的使用,
例如03*016Ka 意思是从起始地址开始连续3个16K字节的空间,a的意思是Readable读使能,这里存放我们的DFU的程序.类似存放串口的iap程序块.即这块区域是系统区,操作后面的用户区的
逗号之后紧跟着的假如是01*016Kg,意思就是紧接着flash空间中的1个16K字节空间,g的意思是读和写和擦除都使能,即这块可用来做用户空间来存放擦除烧写的程序
后面的意思依次类推吧
上面的一定要完全理解,这个关系到你的代码是否能顺利烧写进入芯片
 
因为我们使用的是f4discovery板卡,芯片信号为stm32F407,所以我们要知道这个芯片的flash分配,就需要官方的参考手册,我们去下载一个
首先我们进入本站的首页,找到资源下载

图片8.jpg

看到里面有个百度网盘

图片9.jpg

我们打开这个链接
下载stmF4xx中文参考手册

图片10.jpg

接下来我打开这个手册,找到描述flash的页,看到如下

图片11.jpg

那么我们就知道了
主存储块,分为4个16KB的扇区,1个64KB的扇区和7个128KB的扇区
这个时候我们就可以安照我上面的介绍写下
@Internal Flash   /0x08000000/03*016Ka,01*016Kg,01*064Kg,07*128Kg
我把前三个扇区用来存放DFU程序,后面的所有剩余都用来存放用户代码
这样做了之后,我们需要修改
USBD_DFU_APP_DEFAULT_ADD的值为0x800C000了
至于为什么就看下面的图片
3*16*1024=49152=)XC000

图片12.jpg

好了,ok
 
 
 
 

admin

赞同来自:

下面我们来生成工程文件

图片13.jpg

注意里面的HEAP调的大一点
这个时候我们打开工程,
编译下,
找到usbd_dfu_if.c文件

图片14.jpg

下一步我们就开始添加一些针对flash的操作了
 

admin

赞同来自:

找到 uint16_t MEM_If_Init_FS(void) 函数
我们添加
    HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
解锁flash和清除一些flash的异常状态标识
把 uint16_t MEM_If_DeInit_FS(void) 函数里面增加一个
HAL_FLASH_Lock();
紧接着是flash 的写入操作
uint16_t MEM_If_Erase_FS(uint32_t Add)
{
/* USER CODE BEGIN 3 */
uint32_t UserStartSector;
uint32_t SectorError;
FLASH_EraseInitTypeDef pEraseInit;

/* Unlock the Flash to enable the flash control register access *************/
MEM_If_Init_FS();

/* Get the sector where start the user flash area */
UserStartSector = GetSector(Add);

pEraseInit.TypeErase = TYPEERASE_SECTORS;
pEraseInit.Sector = UserStartSector;
pEraseInit.NbSectors = 9;
pEraseInit.VoltageRange = VOLTAGE_RANGE_3;

if (HAL_FLASHEx_Erase(&pEraseInit, &SectorError) != HAL_OK)
{
/* Error occurred while page erase */
return (1);
}

return (USBD_OK);
/* USER CODE END 3 */
}
擦除操作,先解锁,然后清空所需flash所在sector
这个是直接参考串口的iap操作写的,其中的Bbsectors = 9 来自于前面提到的,3*16K用来放dfu
所以还剩下01+01+07=9个sector
嗯,就这样.
 
哦,对了,GetSector()函数需要自己写,我们之前在串口的iap里面已经写过了,所以复制过来用
我们先按照用户手册上写的在文件usbd_dfu_if.h中define一些需要用到的sector起始地址
#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbyte */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbyte */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbyte */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbyte */
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbyte */
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbyte */
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbyte */
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbyte */
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbyte */
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbyte */
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbyte */
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base @ of Sector 11, 128 Kbyte */

再在usbd_dfu_if.c文件末尾添加函数:
/**
* @brief Gets the sector of a given address
* @param Address: Flash address
* @retval The sector of a given address
*/
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;
}

admin

赞同来自:

关键点在写入操作
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 (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);
// return (uint8_t*)(USBD_OK);
/* USER CODE END 4 */
}
获取flash编程状态
uint16_t MEM_If_GetStatus_FS (uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{
/* USER CODE BEGIN 5 */
uint16_t FLASH_PROGRAM_TIME = 50;
uint16_t FLASH_ERASE_TIME = 50;
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:
default:
buffer[1] = (uint8_t)FLASH_ERASE_TIME;
buffer[2] = (uint8_t)(FLASH_ERASE_TIME << 8);
buffer[3] = 0;
break;
}
return (USBD_OK);
/* USER CODE END 5 */
}
ok这个文件里面也就这些东西需要增加.

admin

赞同来自:

回到主函数文件main.c中,头部增加
#include "usbd_dfu.h"
pFunction JumpToApplication;
uint32_t JumpAddress;
main()函数中,首先注释掉
//    MX_USB_DEVICE_Init();

然后增加下列代码:
    /* USER CODE BEGIN 2 */
if(HAL_GPIO_ReadPin( GPIOA , GPIO_PIN_0 ) == GPIO_PIN_RESET)
{
/* 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();
}
}

MX_USB_DEVICE_Init();
/* USER CODE END 2 */
上面的代码是不是很眼熟,这个就是前面教程里的串口iap的app跳转代码
只不过是跳转地址改为了USBD_DFU_APP_DEFAULT_ADD,ok,这个地址就是我们用stm32cubemx设置的0x800C000了
这个地址是用在用户APP程序中的,一定切记

admin

赞同来自:

编译,烧写到discovery板卡,
QQ截图20160713123917.png

usb的dfu已经烧写结束了.
下面开始对需要通过dfu进行烧写的app的操作
将cubemx另存为F4_USBDFU_app

QQ截图20160713124244.png

由于我测试不需要其他的,只是让led闪烁表示程序已经更新了,所以,我取消掉usb

QQ截图20160713124418.png

保存工程,生成代码.
keil打开工程配置

QQ图片20160713124712.jpg

修改Target中的额flash地址

QQ截图20160713124926.png


打开工程后,找到Drivers/CMSIS 目录下的
system_stm32f4xx.c 文件
在第100行附近,可以看到
#define VECT_TAB_OFFSET  0x00

这里就是直接修改中断向量表的地方,后面也提示必须是0x200整数倍
找到工程中的中断偏移量将其修改为
#define VECT_TAB_OFFSET  0xC000
,然后重新编译工程
在main.c文件中,main函数while中添加
        HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_12);
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_13);
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_14);
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_15);
如果更新成功,那么led的四个灯依次闪烁.
编译,生成hex文件
 
 

admin

赞同来自:

找到http://www.stm32cube.com/question/497 这个帖子,
按照帖子里面写的安装驱动的方式安装驱动,
然后,接楼上继续.
打开刚才安装的软件中的Dfu file manager.exe文件

QQ截图20160713125732.png

此时插上usb的OTG口,连接上电脑
同时按住discovery板卡上面的Reset键和User键,先松开Reset键,
可以看到电脑的设备管理器里面有个新增的usb设备

QQ截图20160713130048.png

得到这个设备的Vid和Pid分别是0x04830xDF11 设备的版本号是0x0200于是我们在相应地方写入
QQ截图20160713130243.png

此时电机[s19 or Hex]按钮,选择刚刚生成的F4_USBDFU_app的的Hex文件

QQ截图20160713130455.png

点击生成按钮
QQ截图20160713130527.png

另存为之后保存在一个地方
打开安装的DFU软件的DFUse DEMO.exe的软件

QQ截图20160713130850.png

如上图,点击Uptrade按钮进行烧写

QQ截图20160713131029.png

烧写成功后会有提示.然后点击Verify按钮进行校验是否烧写正确
下图是烧写成功的提示

QQ截图20160713131216.png

校验
QQ截图20160713131305.png

然后点击Leave dfumode来结束这次的烧写,
好了板卡重新上电吧
可以看到烧写的效果喽
 

QQ截图20160713131553.png

 
这次教程结束,有问题可以留言.
>>本帖所用代码可以在本站百度网盘中找到,名称:F4_USBDFU.zip

东东

赞同来自: 土拨鼠 just_hu

为什么我按照你写的内容一步一步走下去编译什么都正常,但插上USB线缆显示未知设备启动不正常(驱动已经安装),期待你的解答。谢谢。

lsx007

赞同来自:

驱动都正常找到了。就是烧写程序的时候 就是百分之0不动。。其实是卡死了。拔掉usb线

显示usb dfu驱动还在,只能重启电脑之后,才能继续用DFU。

用DFU 带的调试软件,基本的DFU命令可以收到。下载就死掉。擦除也好使。

这是怎么回事,驱动的正常识别到了。

nothing

赞同来自:

请问 烧录正常, 验证正常, 点击 “Leave DFU mode” 会出错。然后断电再上电跑用户程序 跑不起来

nothing

赞同来自:

你好 ,我用的 STM32F103CBT6  flash : 128*1K

Cbue 设置

@Internal Flash   /0x08000000/36*001Ka,92*001Kg


DFU中 因为FLASH 分段不同 修改 erase 程序

uint16_t MEM_If_Erase_FS(uint32_t Add)

{

  /* USER CODE BEGIN 2 */

  uint32_t wPAGEError;

  FLASH_EraseInitTypeDef pEraseInit;


  /* Unlock the Flash to enable the flash control register access *************/

  MEM_If_Init_FS();


  /* Get the sector where start the user flash area */


  pEraseInit.TypeErase   = FLASH_TYPEERASE_PAGES;

  pEraseInit.PageAddress = USBD_DFU_APP_DEFAULT_ADD; //0x8009000

  // User APP flash size 60K

  pEraseInit.NbPages     = 60;

  

  if (HAL_FLASHEx_Erase(&pEraseInit, &wPAGEError) != HAL_OK)

  {

    /* Error occurred while page erase */

    return (1);

  }

  return (USBD_OK);

  /* USER CODE END 2 */ 

}





用户程序地址 0x8009000

#define VECT_TAB_OFFSET  0x9000


用户程序只是一个LED 闪烁,如果在IDE 中讲ROM 改为 0X8000000, 单独烧录程序正常。


nothing

赞同来自:

将 DFU 启动后直接跳转用户程序地址, 用JLINK 直接在0X8009000烧录用户程序还是跑不起来。 好像是初始化卡死了。用户程序地址, 中断表地址偏移都是设置了,还有什么地方不对,是什么原因? 但是将用户程序起始地址改为0X8000000, 重新编译烧录,用户程序可以正常工作。

nothing

赞同来自:

用户程序也是CubeMx初始化源代码,是否使用CubeMx在DFU中初始化芯片后跳转再初始化芯片时钟之类的有问题?


DFU 初始化后使用的是 外部 8M 晶体,跳转到用户程序再重新来初始化一次时钟(CbueMX 自动生成的代码),这样会不会时钟出问题导致挂机?

nothing

赞同来自: sas

点击“Leave DFU mode”进度条到 49% 后弹出的错误窗口dfu.png


 
“Leave DFU mode”进度条是什么意思, 下载什么东西吗?为什么要下载?

木头

赞同来自:

你好,我跟你用的是一样的开发板,我用的是IAR开发环境,不能设置Flash Target地址,Dfu file manager加载.hex时。一直提示不能加载,所以采用.bin来生成dfu文件。

参照的是这个网址:http://bbs.armfly.com/read.php?tid=11185

blob.png

这里Address 填写的是0x800C000。其他步骤一一按照你的方法实现。

生成的dfu文件能成功upgrade和校验成功,但是LED没有闪烁。

LED灯程序没有问题,Debug DFU程序能运行到断点处。执行JumpToApplication();会出现

Program exit reached. 提示产生EXIT 0 (MemManage)中断。

blob.png

进度条卡死在擦除49%或者其他的情况,我修改了大神的代码后就解决了硬件是STM32F4Discovery。供参考。
uint16_t MEM_If_Erase_FS(uint32_t Add)
{
    /* USER CODE BEGIN 3 */
    uint32_t UserStartSector;
    uint32_t SectorError;
    FLASH_EraseInitTypeDef pEraseInit;

    /* Unlock the Flash to enable the flash control register access *************/
    MEM_If_Init_FS();

    /* Get the sector where start the user flash area */
    UserStartSector = GetSector(Add);
    pEraseInit.TypeErase = TYPEERASE_SECTORS;
    pEraseInit.Sector = UserStartSector;
    pEraseInit.NbSectors = 1; //这个原来是9,我修改成了1,一页一页的擦除。
    pEraseInit.VoltageRange = VOLTAGE_RANGE_3;

    if (HAL_FLASHEx_Erase(&pEraseInit, &SectorError) != HAL_OK)
    {
        /* Error occurred while page erase */
        return (1);
    }

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

之后下载了一个FreeRTOS的程序36KBytes+,程序正常下载并且正常运行。

尝试过后的小伙伴希望,你们也反馈效果。

wansaiyon

赞同来自: caoenq

请问下boot0与boot1应该跳线到什么电平

dave - 90后IT男

赞同来自:

有没有做过STM32L433的dfu升级的,大神的步骤不适用L433A 

无帝老三

赞同来自: 天问翼

app代码中不用重定向中断向量表吗?

wansaiyon

赞同来自: hklevejkl

我用的F407ZET6的片子(512KB FLASH),

KEIL App设置如下:

2.png

3.png

当我要升级程序时,DFUSE DEMO软件总是不是显示设备,可能会是什么原因导致的?

1.png

wansaiyon

赞同来自:

另外,怕硬件有差异 ,根据楼主的程序直接烧录进去F4DISC1板子,现在跟我自己做的板子是一样的。DFUSE DEMO3.0.6仍然识别不出来

cyue - 老it男

赞同来自:

F4 系列清空后不是Boot0拉高,Reset后就可以进入DFU模式了吗?然后直接烧到Flash起始位置就好了吧。。。。烧完后直接运行,若要Reset后Boot0放开就是User Code了。。。

要回复问题请先登录注册