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

Bootloader 如何在STM32 F072上的软件中跳转到引导加载程序(DFU模式)?,bootloader,stm32,dfu,Bootloader,Stm32,Dfu,STM32应用说明2606对此进行了讨论,但没有简单的代码示例。此答案已在STM32F072核子板上使用IAR温水进行了测试。这个答案使用“STM32标准外设库”而不是其他任何东西 请注意,验证您是否成功处于引导加载程序模式(DFU模式)的最佳/最简单的方法是在线路PA_9(USART1_TX)和PA_10(USART1_RX)上连接USB-2-UART转换器(从Sparkfun这里购买一个:价格为15美元)(别忘了连接接地)。我无法使用NucleoUSART2默认连接(/dev/ttyACM0

STM32应用说明2606对此进行了讨论,但没有简单的代码示例。

此答案已在STM32F072核子板上使用IAR温水进行了测试。这个答案使用“STM32标准外设库”而不是其他任何东西

请注意,验证您是否成功处于引导加载程序模式(DFU模式)的最佳/最简单的方法是在线路PA_9(USART1_TX)和PA_10(USART1_RX)上连接USB-2-UART转换器(从Sparkfun这里购买一个:价格为15美元)(别忘了连接接地)。我无法使用NucleoUSART2默认连接(/dev/ttyACM0),因此无法使用外部USB-2-USART连接。然后创建一个简单的C程序,在USART连接上写入0x7F。如果您处于DFU模式,它将以一个字节:0x79进行应答。我使用Ubuntu,所以我的测试程序在Linux上编译和运行

此外,测试引导加载程序模式(也称为DFU模式)的最简单方法是将引导0线跨接至+3.3V。它们在核子上紧挨着

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

在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
#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();
}
创建一个简单的实用程序,查看您是否处于引导加载程序模式(也称为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;
}
//
//裸体实用程序:测试STM32是否处于DFU模式
//(又名引导加载程序模式,又名固件更新模式)。
//
//如果它处于DFU模式,您可以通过UART端口发送0x7F,然后
//将返回0x79。
//
//有关详细信息,请参阅STM32 DFU USART规范。
//
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括//errno
#定义默认设备“/dev/ttyUSB0”
//#定义默认设备“/dev/ttyACM0”
int main(int argc,字符**argv)
{
int fd,波特=B9600;
char*sername=默认的\u SERDEVICE;
结构termios oldsertio,newsertio;
无符号字符mydata[2]={0};
mydata[0]=0x7F;
mydata[1]=0;
/*不是控制型tty:CTRL-C不应该杀了我们*/
fd=开放(sername,O|u RDWR | O|u NOCTTY);
如果(fd<0)
{
佩罗尔(sername);
出口(-1);
}
tcgetattr(fd和oldsertio);/*保存当前调制解调器设置*/
/*
*8个数据,偶数奇偶校验,1个停止位。忽略调制解调器控制线。启用
*接收。设置适当的波特率。无硬件流量控制!
*/
newsertio.c|cflag=熟的|波特| CS8 | CLOCAL | CREAD | PARENB;
/*原始输入。忽略错误和中断*/
newsertio.c_iflag=IGNBRK | IGNPAR;
/*原始输出*/
newsertio.c_of lag=OPOST;
/*没有回声也没有信号*/
newsertio.c_lflag=0;
/*阻止读取直到1个字符到达*/
newsertio.c_cc[VMIN]=1;
newsertio.c_cc[VTIME]=0;
/*现在清洁调制解调器线路并激活调制解调器的设置*/
tcflush(fd,TCIFLUSH);
tcsetattr(fd、TCSANOW和newsertio);
//这就是魔法发生的地方
写入(fd和mydata[0],1);
int red=读取(fd,&mydata[1],1);
如果(红色<0){
fprintf(stderr,“错误:read()失败,错误号[%d],strerrer[%s]\n”,
厄尔诺,斯特雷罗(厄尔诺));
}
tcsetattr(fd、TCSANOW和oldsertio);
关闭(fd);
printf(“读取[%d]字节:[0x%x]\n],红色,mydata[1]);
返回0;
}

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

CubeMX HAL定义为

void __attribute__((weak)) __initialize_hardware_early(void);
在我的例子中,它只调用SystemInit()

因此,您可以直接覆盖此函数:

// 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
#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();
}
#包括
#包括“stm32f0xx_hal.h”
#定义系统成员重置向量0x1FFC804
#定义重置\u到\u引导加载程序\u魔术\u代码0xDEADBEEF
#定义引导加载程序\堆栈\指针0x20002250
uint32将dfu重置为引导加载程序;
无效\uuu初始化\uu硬件\uu提前(无效)
{
if(dfu_reset_to_bootloader_magic==reset_to_bootloader_magic_代码){
void(*bootloader)(void)=(void(*)(void))(*((uint32_t*)SYSMEM_RESET_VECTOR));
dfu_重置_至_引导加载程序_magic=0;
__设置\u MSP(引导加载程序\u堆栈\u指针);
引导加载程序();
而(42),;
}否则{
SystemInit();
}
}
void dfu_run_bootloader()
{
dfu_reset_to_bootloader_magic=reset_to_bootloader_magic_代码;
NVIC_系统重置();
}

解释得非常清楚,谢谢。当你说测试DFU最简单的方法是用3.3V短接BOOT0时,那只是单独测试DFU,对吗?不使用您共享的代码进行测试?您是如何建立引导加载程序\u堆栈\u指针的?据我所知,这是不可能的!您可以跳转到系统引导加载程序,但它仍会检查BOOT0行并继续运行您的应用程序。如果有人真的在BOOT0与GND绑定时让它工作,我会很感兴趣。在任何情况下,都无法让STM32CubeProgrammer与我的STM32L072一起工作,它会立即连接,然后失去连接。在这上面花了太多时间。