STM32F103ZET6 - USB_DFU 升级
前言:
记录下碰到的坑!!
USBD_Stop(&hUsbDeviceFS);// 重点: 此处需关闭USB功能, 需要时再打开.
示例详解: (参考 : https://blog.csdn.net/yhl_sophia/article/details/84337048)
基于硬件平台: STM32F103ZET6正点原子的精英板, 使用stm32cubemx 工具自动产生的配置工程,使用KEIL5编译代码。
1. STM32CubeMX生成代码过程如下:
1>. 打开SWD - debug调试模式, STM32CubeMX生成的代码默认是没打开debug调试模式的 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191222213429503.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
2>. 外部时钟配置, HSE选择为外部晶振![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212190806689.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
3>. CLOCK Configuration 配置如下: 选择HSE,PLL倍频为9, USB分频为1.5=48M![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212191124166.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
4>. USB功能选中![在这里USB插入图片描述](https://img-blog.csdnimg.cn/2019121219145053.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
5>. USB_DEVICE :
APP地址设置为:0X801000
DFU接口: @Internal Flash /0x08000000/32 * 02Ka,224 * 02Kg
因为我的MCU是STM32F103ZET6,1个扇区为2k, bootloader占64KB空间,APP占448K。32 * 02Ka,其中“a”代表只读,224 * 02Kg,其中"g"读写。![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212192444586.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
6>. 生成代码配置![在这里插入图片描述](https://img-blog.csdnimg.cn/20191222213919754.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
点击生成代码,在keil中开始编辑BootLoader代码,用户要编辑的主要是usbd_dfu_if.c文件,在这个文件中提供了Flash的初始化、反初始化、擦除、编程、读取以及获取状态几个函数接口,我们要做的就是实现这些函数的具体操作。
static uint16_t MEM_If_Init_FS(void);
static uint16_t MEM_If_Erase_FS(uint32_t Add);
static uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len);
static uint8_t *MEM_If_Read_FS(uint8_t *src, uint8_t *dest, uint32_t Len);
static uint16_t MEM_If_DeInit_FS(void);
static uint16_t MEM_If_GetStatus_FS(uint32_t Add, uint8_t Cmd, uint8_t *buffer);
1、Init和Deinit里面主要是解锁Flash和上锁Flash
/* Private functions ---------------------------------------------------------*/
/**
/**
2、Flash的擦除操作,这里擦除的是存放APP程序的整个空间
/**
#define USBD_DFU_APP_END_ADD 0x08080000//512K
#define USBD_DFU_APP_DEFAULT_ADD 0x08010000
#define FLASH_PAGE_SIZE 0x800U //2k
//Flash的擦除操作,这里擦除的是存放APP程序的整个空间
uint16_t MEM_If_Erase_FS(uint32_t Add)
{
/* USER CODE BEGIN 2 */
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 1;
return (USBD_OK);
/* USER CODE END 2 */
}
3、写Flash操作
/**
@brief Memory write routine.
@param src: Pointer to the source buffer. Address to be written to.
@param dest: Pointer to the destination buffer.
@param Len: Number of data to be written (in bytes).
@retval USBD_OK if operation is successful, MAL_FAIL else.
*/
//写Flash操作
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
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
{
return 1;
}
}
return (USBD_OK);
/* USER CODE END 3 */
}
4、读Flash操作
/**
@brief Memory read routine.
@param src: Pointer to the source buffer. Address to be written to.
@param dest: Pointer to the destination buffer.
@param Len: Number of data to be read (in bytes).
@retval Pointer to the physical address where data should be read.
*/
//读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
dest[i] = psrc++;
}
return (uint8_t)(dest);;
// return (uint8_t*)(USBD_OK);
/* USER CODE END 4 */
}
5、获取Flash状态
/**
//这个只是一个擦除或编程时间的设定
#define FLASH_ERASE_TIME (uint16_t)50
#define FLASH_PROGRAM_TIME (uint16_t)50
//获取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:
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 */
}
usbd_dfu_if.c文件编写完成,之后就是main函数中的操作
首先定义APP程序入口指针和存放入口地址的变量
//首先定义APP程序入口指针和存放入口地址的变量
typedef void (*pFunction)(void);
pFunction JumpToApplication;
uint32_t JumpAddress;
读取KEY0电平决定是否进入APP以及判断APP程序入口地址是否存在或正确
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) != GPIO_PIN_SET)
{
if((((__IO uint32_t)USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000) == 0x20000000)
{
JumpAddress = (__IO uint32_t)(USBD_DFU_APP_DEFAULT_ADD +4);
JumpToApplication = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*)USBD_DFU_APP_DEFAULT_ADD);
JumpToApplication();
}
}
点击魔术棒设置程序起始地址,和大小
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212194230555.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212194300178.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
至此,BootLoader程序就编写好了,接下来是制作APP程序
二、用keil编写一个闪灯的APP程序,点击魔术棒设置程序起始地址
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212194356343.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019121219442063.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
另外,在APP程序的还要进行中断向量表的重映射,只要更改system_stm32fxx.c为以下配置即可。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212194543355.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
主循环while内写个简单的灯闪烁代码
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212194702860.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
附:APP生成.bin文件配置
C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o .\ledtest\ledtest.bin .\ledtest\ledtest.axf
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212194838646.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
编译生成app.hex文件,接下来需要将hex文件转换为DFU文件,去ST官网上搜索DFuse,下载stsw-stm32080这个代号的压缩包,里面是DFuse工具。
DFUse软件自带有将hex文件转换成DFU文件的工具 DFU File Manager,在电脑开始菜单中搜索dfu即可找到这个软件,运行选择“I Want to GENERATE a DFU file from s19,HEX or BIN files ”,进入以下界面,这里的Target ID有不同的值,其中0代表片内Flash;1代表外部Flash;2代表外部Nor Flash。因此我们这里选择0,点击S19 or Hex选择hex文件即可生成DFU文件。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212195050222.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
之后打开DFuse软件,可以看到软件检测到了我们的USB DFU设备,Choose刚刚生成的DFU文件,点击Upgrade进行升级。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191212195218421.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbW82MDg2,size_16,color_FFFFFF,t_70)
至此,USB_DFU程序完成, 切换USB_232供电后, 按下KEY0,程序会跳至APP运行,此时两个LED将会以500ms间隔闪烁.