如何在 STM32 F072 的软件中跳转到引导加载程序(DFU 模式)?

Posted

技术标签:

【中文标题】如何在 STM32 F072 的软件中跳转到引导加载程序(DFU 模式)?【英文标题】:How do you jump to the bootloader (DFU mode) in software on the STM32 F072? 【发布时间】:2015-04-02 00:45:18 【问题描述】:

STM32 App Note 2606 对此进行了讨论,但没有简单的代码示例。

【问题讨论】:

我可以说这是不可能的!您可以跳转到系统引导加载程序,但它仍会检查 BOOT0 行并继续运行您的应用程序。如果有人在 BOOT0 连接到 GND 时真正让它工作,会很感兴趣。并且在任何情况下都无法让 STM32CubeProgrammer 与我的 STM32L072 一起工作,它连接然后立即失去连接。在这上面花了太多时间。 【参考方案1】:

此答案已使用 IAR EWARM 在 STM32F072 Nucleo 板上进行了测试。此答案使用“STM32 标准外设库”,仅此而已。

请注意,验证您是否成功进入引导加载程序模式(DFU 模式)的最佳/最简单方法是在 PA_9 行(USART1_TX ) 和 PA_10 (USART1_RX) (不要忘记接地)。我无法使用 Nucleo USART2 默认连接 (/dev/ttyACM0),因此无法使用外部 USB-2-USART 连接。然后创建一个简单的 C 程序在 USART 连接上写入 0x7F。如果您处于 DFU 模式,它将回复一个字节:0x79。我使用的是 Ubuntu,所以我的测试程序可以在 Linux 上编译和运行。

此外,测试引导加载程序模式(也称为 DFU 模式)的最简单方法是将 BOOT0 线跳线到 +3.3V。这些在 Nucleo 上彼此相邻。

添加到 main.c main() 例程:

// Our STM32 F072 has:
// 16k SRAM in address 0x2000 0000 - 0x2000 3FFF
*((unsigned long *)0x20003FF0) = 0xDEADBEEF;

// Reset the processor
NVIC_SystemReset();

在 SystemInit() 函数开头的 Libraries/sysconfig/system_stm32f0xx.c 中添加一些代码:

// Define our function pointer
void (*SysMemBootJump)(void);

void SystemInit (void)

  // Check if we should go into bootloader mode.
  //
  // Set the main stack pointer __set_MSP() to its default value.  The default
  // value of the main stack pointer is found by looking at the default value
  // in the System Memory start address. Do this in IAR View -> Memory.  I
  // tried this and it showed address: 0x200014A8 which I then tried here.
  // The IAR compiler complained that it was out of range.  After some
  // research, I found the following from "The STM32 Cortex-M0 Programming
  // Manual":
  //         Main Stack Pointer (MSP)(reset value). On reset, the processor
  //         loads the MSP with the value from address 0x00000000.
  //
  // So I then looked at the default value at address 0x0 and it was 0x20002250
  //
  // Note that 0x1fffC800 is "System Memory" start address for STM32 F0xx
  //
  if ( *((unsigned long *)0x20003FF0) == 0xDEADBEEF ) 
       *((unsigned long *)0x20003FF0) =  0xCAFEFEED; // Reset our trigger
      __set_MSP(0x20002250);
                                                     // 0x1fffC800 is "System Memory" start address for STM32 F0xx
      SysMemBootJump = (void (*)(void)) (*((uint32_t *) 0x1fffC804)); // Point the PC to the System Memory reset vector (+4)
      SysMemBootJump();
      while (1);
  

  ... // The rest of the vanilla SystemInit() function

创建一个简单的实用程序来查看您是否处于引导加载程序模式(也称为 DFU 模式)。这在 Linux 上编译和运行。确保你的串口正确。它可能是 /dev/ttyUSB0,如下所示。

//
// A bare-bones utility: Test if the STM32 is in DFU mode
// (aka bootloader mode, aka firmware update mode).
//
// If it is in DFU mode, you can send it 0x7F over a UART port and it
// will send 0x79 back.
//
// For details, see the STM32 DFU USART spec.
//

#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>  // errno

#define DEFAULT_SERDEVICE  "/dev/ttyUSB0"
//#define DEFAULT_SERDEVICE  "/dev/ttyACM0"

int main(int argc, char **argv)

    int              fd, cooked_baud = B9600;
    char            *sername = DEFAULT_SERDEVICE;
    struct termios   oldsertio, newsertio;
    unsigned char mydata[2] = 0;

    mydata[0] = 0x7F;
    mydata[1] = 0;

    /* Not a controlling tty: CTRL-C shouldn't kill us. */
    fd = open(sername, O_RDWR | O_NOCTTY);
    if ( fd < 0 )
    
        perror(sername);
        exit(-1);
    

    tcgetattr(fd, &oldsertio); /* save current modem settings */

    /*
     * 8 data, EVEN PARITY, 1 stop bit. Ignore modem control lines. Enable
     * receive. Set appropriate baud rate. NO HARDWARE FLOW CONTROL!
     */
    newsertio.c_cflag = cooked_baud | CS8 | CLOCAL | CREAD | PARENB;

    /* Raw input. Ignore errors and breaks. */
    newsertio.c_iflag = IGNBRK | IGNPAR;

    /* Raw output. */
    newsertio.c_oflag = OPOST;

    /* No echo and no signals. */
    newsertio.c_lflag = 0;

    /* blocking read until 1 char arrives */
    newsertio.c_cc[VMIN]=1;
    newsertio.c_cc[VTIME]=0;

    /* now clean the modem line and activate the settings for modem */
    tcflush(fd, TCIFLUSH);
    tcsetattr(fd,TCSANOW,&newsertio);

    // Here is where the magic happens
    write(fd,&mydata[0],1);
    int red = read(fd,&mydata[1],1);
    if (red < 0) 
        fprintf(stderr, "Error: read() failed, errno [%d], strerrer [%s]\n",
                errno, strerror(errno));
    

    tcsetattr(fd,TCSANOW,&oldsertio);
    close(fd);

    printf("Read [%d] bytes: [0x%x]\n", red, mydata[1]);

    return 0;

【讨论】:

解释得很清楚,谢谢。当您说测试DFU的最简单方法是将BOOT0与3.3V短接时,那仅用于独立测试DFU对吗?不使用您共享的代码进行测试?【参考方案2】:

在我的项目中,我基本上和 Brad 做的一样,但没有修改 SystemInit() 函数。

CubeMX HAL 定义为

void __attribute__((weak)) __initialize_hardware_early(void);

在我的情况下,除了调用 SystemInit();

所以你可以直接覆盖这个函数:

#include <stdint.h>
#include "stm32f0xx_hal.h"

#define SYSMEM_RESET_VECTOR            0x1fffC804
#define RESET_TO_BOOTLOADER_MAGIC_CODE 0xDEADBEEF
#define BOOTLOADER_STACK_POINTER       0x20002250

uint32_t dfu_reset_to_bootloader_magic;

void __initialize_hardware_early(void)

    if (dfu_reset_to_bootloader_magic == RESET_TO_BOOTLOADER_MAGIC_CODE) 
        void (*bootloader)(void) = (void (*)(void)) (*((uint32_t *) SYSMEM_RESET_VECTOR));
        dfu_reset_to_bootloader_magic = 0;
        __set_MSP(BOOTLOADER_STACK_POINTER);
        bootloader();
        while (42);
     else 
        SystemInit();
    


void dfu_run_bootloader()

    dfu_reset_to_bootloader_magic = RESET_TO_BOOTLOADER_MAGIC_CODE;
    NVIC_SystemReset();

【讨论】:

你是如何建立 BOOTLOADER_STACK_POINTER 的?

以上是关于如何在 STM32 F072 的软件中跳转到引导加载程序(DFU 模式)?的主要内容,如果未能解决你的问题,请参考以下文章

通过应用程序跳转到 STM32 中的引导加载程序,即在引导模式下从用户闪存使用引导 0 和引导 1 引脚

stm32f103c8 跳转到应用程序不起作用

在引导加载程序中跳转到0x7C00不会引起无限循环

如何让STM32F429NI中的bootloader跳转到外部Nor Flash

如何从微信浏览器中跳转到APP指定页面?

STM32 USB编程,跳转到DFU的bootloader