使用DFU方案实现STM32单片机的高级开发

2024-05-17  

什么是 DFU

DFU全称为Device Firmware update,是ST官方推出的一个通过USB接口进行IAP升级的方案,同串口ISP一样,他们都集成在了芯片内部的Bootloader区段,可以通过配置boot引脚来启动。(具体可参照ST文档:AN2606)。不过内置DFU的芯片大部分型号都比较新,如果你用的型号没有内置DFU程序,没关系我们也可以通过CubeMX来快速生成和移植一个DFU功能程序到你的Flash中来使用。


DFU方案完整的组件包括单片机DFU Demo代码、PC端升级程序、PC端Demo代码以及相关资料手册等。通过使用DFU方案,我们可以快速的集成升级功能到开发的产品中,同时还能够快速的开发与之配套的升级程序。

使用CubeMX生成初始工程

由于官方提供的DFU例程并不多,我们很难找到现成的可已使用DFU程序,但是通过CubeMX我们可以很快速的配置和生成DFU的Bootloader,下面我们正式开始。

新建CubeMX工程

首先选定好IC的型号,进入配置界面,由于只是Bootloader代码所以这里我们只需要配置USB功能和一个做Bootloader触发的引脚就可,其余的时钟等部分一切按照正常方式配置。

设置USB引脚功能

设定USB模式为Device(HS还是FS并不影响DFU的功能,按照应用选择就可)。


cae4ea6a-6eb1-11ed-8abf-dac502259ad0.png

开启DFU组件

在MiddleWares中加入USB DFU组件


caece1ca-6eb1-11ed-8abf-dac502259ad0.png

设置DFU参数

开启DFU组件后,CubeMX的程序设置窗口的MiddleWares中就会出现DFU程序设置按钮。

cb0498c4-6eb1-11ed-8abf-dac502259ad0.png

点开它将APP加载的地址改为0x0800_c000,这个加载地址根据你实际的应用设置,目前我们选择让flash的前三个sector为Bootloader的区域。

cb0e3366-6eb1-11ed-8abf-dac502259ad0.png

第二个全是字段的参数是用来在DFU连接升级软件式传输给软件用来获取Flash结构字符串数据,很好理解这个小协议的内容,点击设置后,下方的CubeMX的参数说明也写的很清晰,这里就不多说了。当然这些参数也在工程生成后在 usbd_conf.h 和 usbd_dfu_if.c 文件中修改。

cb21e7da-6eb1-11ed-8abf-dac502259ad0.png

最后的设置

最后我们添加一个外部的按键作为触发单片机启动时进入DFU的方式,按键按下后就启动DFU模式,否则直接加载后方APP程序,这里选用PA0引脚,给它设置个User Label 就叫 USER_BTN_GPIO_Port。


cb2daafc-6eb1-11ed-8abf-dac502259ad0.png


修改补全工程


实现 DFU 功能代码


打开 src 目录下的 usbd_dfu_if.c 文件补全其中的功能代码


Flash 解锁


uint16_tMEM_If_Init_HS(void)

{

HAL_FLASH_Unlock();

return(USBD_OK);

}

Flash 上锁


uint16_tMEM_If_DeInit_HS(void)

{

HAL_FLASH_Lock();

return(USBD_OK);

}

Flash 擦除


staticuint32_tGetSector(uint32_tAddress)

{

uint32_tsector=0;


if((Address< ADDR_FLASH_SECTOR_1) && (Address >=ADDR_FLASH_SECTOR_0))

{

sector=FLASH_SECTOR_0;

}


......


}

elseif((Address< ADDR_FLASH_SECTOR_23) && (Address >=ADDR_FLASH_SECTOR_22))

{

sector=FLASH_SECTOR_22;

}

else

{

sector=FLASH_SECTOR_23;

}

returnsector;

}


uint16_tMEM_If_Erase_HS(uint32_tAdd)

{

uint32_tstartsector=0;

uint32_tsectornb=0;

/*VariablecontainsFlashoperationstatus*/

HAL_StatusTypeDefstatus;

FLASH_EraseInitTypeDeferaseinitstruct;


/*Getthenumberofsector*/

startsector=GetSector(Add);


eraseinitstruct.TypeErase=FLASH_TYPEERASE_SECTORS;

eraseinitstruct.VoltageRange=FLASH_VOLTAGE_RANGE_3;

eraseinitstruct.Sector=startsector;

eraseinitstruct.NbSectors=1;

status=HAL_FLASHEx_Erase(&eraseinitstruct,§ornb);


if(status!=HAL_OK)

{

return(USBD_FAIL);

}

return(USBD_OK);

}

Flash 写入


uint16_tMEM_If_Write_HS(uint8_t*src,uint8_t*dest,uint32_tLen)

{

uint32_ti=0;


for(i=0;i< Len; i += 4)

{

/*Devicevoltagerangesupposedtobe[2.7Vto3.6V],theoperationwill

bedonebybyte*/

if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,(uint32_t)(dest+i),*(uint32_t*)(src+i))==HAL_OK)

{

/*Checkthewrittenvalue*/

if(*(uint32_t*)(src+i)!=*(uint32_t*)(dest+i))

{

/*Flashcontentdoesn'tmatchSRAMcontent*/

return(USBD_FAIL);

}

}

else

{

/*ErroroccurredwhilewritingdatainFlashmemory*/

return(USBD_FAIL);

}

}

return(USBD_OK);

}

Flash 读取


uint8_t*MEM_If_Read_HS(uint8_t*src,uint8_t*dest,uint32_tLen)

{

/*ReturnavalidaddresstoavoidHardFault*/

uint32_ti=0;

uint8_t*psrc=src;


for(i=0;i< Len; i++)

      {

        dest[i] = *psrc++;

      }

      /*ReturnavalidaddresstoavoidHardFault*/

return(uint8_t*)(dest);

}

获取 Flash 擦写时间参数


uint16_tMEM_If_GetStatus_HS(uint32_tAdd,uint8_tCmd,uint8_t*buffer)

{

/*USERCODEBEGIN11*/

uint16_ttime;


time=TimingTable[GetSector(Add)];


switch(Cmd)

{

caseDFU_MEDIA_PROGRAM:

buffer[1]=(uint8_t)time;

buffer[2]=(uint8_t)(time<< 8);

buffer[3]=0;

break;


caseDFU_MEDIA_ERASE:

default:

buffer[1]=(uint8_t)time;

buffer[2]=(uint8_t)(time<< 8);

buffer[3]=0;

break;

}

return(USBD_OK);

/*USERCODEEND11*/

}

usbd_dfu_if.h 文件添加的宏定义


/*Defineflashaddress*///BLANK1#defineADDR_FLASH_SECTOR_00x08000000#defineADDR_FLASH_SECTOR_10x08004000#defineADDR_FLASH_SECTOR_20x08008000#defineADDR_FLASH_SECTOR_30x0800C000#defineADDR_FLASH_SECTOR_40x08010000#defineADDR_FLASH_SECTOR_50x08020000#defineADDR_FLASH_SECTOR_60x08040000#defineADDR_FLASH_SECTOR_70x08060000#defineADDR_FLASH_SECTOR_80x08080000#defineADDR_FLASH_SECTOR_90x080A0000#defineADDR_FLASH_SECTOR_100x080C0000#defineADDR_FLASH_SECTOR_110x080E0000//BLANK2#defineADDR_FLASH_SECTOR_120x08100000#defineADDR_FLASH_SECTOR_130x08104000#defineADDR_FLASH_SECTOR_140x08108000#defineADDR_FLASH_SECTOR_150x0810C000#defineADDR_FLASH_SECTOR_160x08110000#defineADDR_FLASH_SECTOR_170x08120000#defineADDR_FLASH_SECTOR_180x08140000#defineADDR_FLASH_SECTOR_190x08160000#defineADDR_FLASH_SECTOR_200x08180000#defineADDR_FLASH_SECTOR_210x081A0000#defineADDR_FLASH_SECTOR_220x081C0000#defineADDR_FLASH_SECTOR_230x081E0000/*Flashopratetimefromdatasheetpage128*/#defineFLASH_SECTOR_16KB_WRITE_ERASE_TIME500//500usbframe,means500ms#defineFLASH_SECTOR_64KB_WRITE_ERASE_TIME1100#defineFLASH_SECTOR_128KB_WRITE_ERASE_TIME2000

修改Main文件


首先在main文件前添加几个用于加载APP程序的变量和函数定义


typedefvoid(*pFunction)(void);


pFunctionJumpToApplication;

uint32_tJumpAddress;1234

然后再 main 函数中加入 外部按键的判断、APP程序加载以及USB DFU初始化功能


intmain(void)

{

/*Resetofallperipherals,InitializestheFlashinterfaceandtheSystick.*/

HAL_Init();


/*Configurethesystemclock*/

SystemClock_Config();


/*Initializeallconfiguredperipherals*/

MX_GPIO_Init();


if(HAL_GPIO_ReadPin(USER_BTN_GPIO_Port,USER_BTN_Pin)==GPIO_PIN_SET)

{

HAL_GPIO_WritePin(GPIOG,LD3_Pin,GPIO_PIN_SET);//Fordebug

/*Testifusercodeisprogrammedstartingfromaddress0x0800C000*/

if(((*(__IOuint32_t*)USBD_DFU_APP_DEFAULT_ADD)&0x2FF80000)==0x20000000)

{

HAL_GPIO_WritePin(GPIOG,LD4_Pin,GPIO_PIN_SET);//Fordebug

/*Jumptouserapplication*/

JumpAddress=*(__IOuint32_t*)(USBD_DFU_APP_DEFAULT_ADD+4);

JumpToApplication=(pFunction)JumpAddress;


/*Resetofallperipherals*/

HAL_DeInit();


/*Setinterruptvectortoappcode*/

SCB->VTOR=USBD_DFU_APP_DEFAULT_ADD;


/*Initializeuserapplication'sStackPointer*/

__set_MSP(*(__IOuint32_t*)USBD_DFU_APP_DEFAULT_ADD);

JumpToApplication();

}

}


MX_USB_DEVICE_Init();


while(1)

{

}

}

编译程序下载进入单片机


使用DfuSe


从ST官网DfuSe的程序安装包,并安装。然后我们按下之前写的触发按键并复位单片机,让单片机初始 USB DFU 功能,这时如果你插着单片机的USB线,系统应该已经识别了。如果没有右键更新驱动程序,手动指定驱动搜索路径在DfuSe安装目录下的 BinDriver 内。如果直接无法识别USB设备,建议在CubeMx配置完工程后就编译下载测试一下,看看是不是你在移植过程中哪里写错了。


然后我们需要生成一个地址设定在0x0800_c000后的测试程序,就先编写一个 Blink LED 的程序吧,生成bin、hex或S19文件。然后我们打开DfuSe软件的Dfu file manager来生成DFU软件用的.dfu格式的文件。选择第一项,第二个是用来将.dfu反向变换回来的。大概的操作已经标在图片上了,操作比较简单就不做详细介绍了,记得把Address的地址改到偏移后的地址上否则下载会出错,其他参数可以不用修改。


然后我们打开DfuSe程序,在Upgrade中选择生成好的blink.dfu文件,勾选校验功能,下载程序。成功后复位单片机,LED开始闪烁,移植成功。


更多

仔细区看看DfuSe的安装目录,里面有DFU的资料文档,还有DFU的工程源代码,可以用来改写自己需要的DFU升级程序。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。