早就听说stm32可以利用USB升级固件,脱离各种Link。趁有空搜了下相关资料,然后利用stm32CubeMX实现了这个功能。
参考了这个地址的资料:http://www.stm32cube.com/question/500
上面这个链接写得很详细,所以不再赘述,直接上重点以及注意点。
一、bootloader地址设置
在CubeMX里面,有两个参数要设置好,以上画框部分。
一个是APP的默认地址。
另一个是DFU接口。用来识别我们bootloader存放起始地址以及占用内存大小,还有内部FLASH的扇区划分方式。
因为我的MCU是STM32F407VET6,所以改成上图。bootloader占用第一扇区16KB空间。016Ka,其中“a”代表只读,"g"读写。
二、APP地址设置
APP像往常一样编写以及编译程序,需要改动的地址有两个。
1、中断向量表:“#define VECT_TAB_OFFSET 0x4000”
位于:“system_stm32f4xx.c ”,因为我bootloader占用了16KB,计算可知偏移量应为 “0x4000”
2、MDK魔术棒
三、bootloader占用内存
尝试着能不能缩减bootloader占用的内存,将RCC以及GPIO由HAL库换成LL库,生成的程序空间可以小约2KB。
但RCC使用LL库,发现生成的程序,USB设备无法枚举成功。如果GPIO使用LL库,RCC为HAL库,发现USB插头插上
PC后,需要较久时间才能反应过来,而且DFU下载程序时候的反应也变慢了。
所以,如果对内存占用较敏感,可能不适合用CubeMX。需要自己到ST官网下载DFU库修改,寄存器操作RCC以及GPIO。
注意点:
1、刚开始,发现功能能实现,但每次由DFU状态转到APP之后,复位MCU想要烧写另外的APP。发现PC上USB设备不会重新枚举,每次烧写都要重新插拔。后来查阅USB的资料,发现可以利用“D+”引脚使PC重新枚举USB。资料地址:https://blog.csdn.net/u011279649/article/details/41779767查看里面的第一个步骤。
1、帖子的代码里有一处地方需修改,在“MEM_If_Erase_FS(uint32_t Add)”函数里,“pEraseInit.NbSectors = 9;”此处应改为“pEraseInit.NbSectors = 1;”。这个函数,当一个地址被传入,擦除地址所在扇区。我试过改成1后,烧写200多KB程序正常运行。证明修改后市没问题的。
将程序修改如下,复位MCU后PC会重新枚举设备:
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = KEY1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);
/*拉低USB D+ 引脚 使PC重新枚举USB设备 */
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_12,GPIO_PIN_RESET);
}
主函数:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
HAL_Delay(1); //延时1MS等待USB稳定
if(HAL_GPIO_ReadPin( KEY1_GPIO_Port , KEY1_Pin ) == GPIO_PIN_RESET)
{
MX_USB_DEVICE_Init();
while(1);
}
/* 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();
}
while (1)
{
}
}
在“void MX_GPIO_Init(void)”中拉低了PA12,也就是“D+”,然后初始化USB时 “D+”恢复高电平,PC识别设备。
我生成了2个APP,APP1使D2闪烁,APP2使D3闪烁。
四、按键控制,烧写APP到不同地址。
目的是,按下不同按键,DFU烧写APP到不同地址,然后开机时按不同按键,运行不同的APP。
需要修改几处地方:
1、"usbd_conf.h" : "extern uint32_t APPAddrDevi;"
“#define USBD_DFU_APP_DEFAULT_ADD (0x08004000 + APPAddrDevi) ”
2、"main.c":申请一个全局变量“uint32_t APPAddrDevi = 0;”
主函数修改如下:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
HAL_Delay(1); //延时1MS等待USB稳定
if(HAL_GPIO_ReadPin( KEY1_GPIO_Port , KEY1_Pin ) == GPIO_PIN_RESET)
{
if(HAL_GPIO_ReadPin( KEY0_GPIO_Port , KEY0_Pin ) == GPIO_PIN_RESET) APPAddrDevi = 0x8000;
MX_USB_DEVICE_Init();
while(1);
}
if(HAL_GPIO_ReadPin( KEY0_GPIO_Port , KEY0_Pin ) == GPIO_PIN_RESET) APPAddrDevi = 0x8000;
/* 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();
}
while (1)
{
}
}
相应的,APP2魔术棒程序起始地址应设为“0x800C000”,大小为“0x74000”,
中断向量表:“#define VECT_TAB_OFFSET 0xC000”
开机按住KEY1跟KEY0,然后放开KEY1,APP会烧写到“0x800C000”这个地址。如果只按住KEY1不安KEY0,则会烧写到“0x8004000”这个地址
开机不按按键,运行“0x8004000”地址的APP,按下KEY0,运行“0x800C000”地址的APP。