小伙伴们的智能之旅

STM32: 做一个与ST-LINK/V2-1调试器兼容的bootloader

ST官方提供了ST-LINK/V2-1调试器的原理图,PCB图及相关的固件更新工具(Stlink Utility或者ST-LINK firmware upgrade)当然也可以通过第三方的工具对调试器进行固件的升级操作(STLinkReflash)。但如果你在使用ST-LINK/V2-1的过程中不慎将原有的固件破环或者是MCU烧毁,使得ST-LINK/V2-1无法正常使用,这时候你就需要一个可用的bootloader进行修复。

经过进一个月的分析,不断尝试,自己写一个bootloader实现固件下载更新及加载也是可能的。

完整的固件包含如下这几个部分:

Flash size: 128KiB
bootloader: 15KiB system parameter: 1KiB                              firmware: 111KiB manifest: 1KiB

由于STLinkReflash命令在固件升级过程中,可以显示升级过程中发送及接收的数据的相关内容,并且结合OpenOCD源代码中关于ST-LINK通信相关的代码,可以很容易地猜测出固件升级的流程:

STM32: ST-LINK/V2与STLINK/V2-1 DFU协议分析

NOTE:

STLinkReflash工具在写入ST-LINK/V2-1的时候,没有去更新固件的配置信息(位于flash 0x08003c00 ~ 0x08003ffff)。这使得如果bootloader不包启配置信息(只包含flash前15KiB的数据),那么通过该工具写入的ST-LINK/V2-1固件是无法使用的(USB无法正常枚举),而必须先通过STLink Utility工具进行固件更新。

加密算法,通过分析STLinkReflash工具,我们知道固件在传送过程中,会进行加密,我们需要相关的加密算法来实现:

STM32: 实现Advanced Encryption Standard(AES) – 128-bit加密算法

有了固件升级相关的通信协议及相关的参考文档,获取官方的bootloader也不是什么难事。

原以为有了ST-LINK/V2-1的固件并且知道固件的起始地址,就可以写一个简单的bootloader加载固件,可事实并非如此(加载JLink固件可以正常运行,但是对于ST-LINK/V2-1的固件,就没有那么走运了),还是需要分析调试bootloader代码,以发现其中的差异:

STM32: 从STLinkReflash提取jlink与ST-LINK/V2-1固件

 

ST-LINK/V2-1通过USB接口与PC机进行通信,在bootloader模式下接口的相关配置信息在linux/ubuntu下可通过lsusb命令查看:

$ lsusb -d 0483: -v

Bus 001 Device 014: ID 0483:3748 STMicroelectronics ST-LINK/V2
Couldn't open device, some information will be missing
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  idVendor           0x0483 STMicroelectronics
  idProduct          0x3748 ST-LINK/V2
  bcdDevice            1.00
  iManufacturer           1 
  iProduct                2 
  iSerial                 3 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           39
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           3
      bInterfaceClass       255 Vendor Specific Class
      bInterfaceSubClass    255 Vendor Specific Subclass
      bInterfaceProtocol    255 Vendor Specific Protocol
      iInterface              4 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0

有了这些相关信息,我们就可以完成USB接口配置的相关代码了。这里我们会用到EP 1 IN 与 EP 2 OUT而EP 3 IN在ST-LINK进行debug/trace的时候会用到。

1. 系统上电之后会检查固件是否完整,固件完整则跳转执行固件代码否则进入DFU模式:

/*
 * Application entry point.
 */
int __attribute__((noreturn)) main(void) {
  /*
   * System initializations.
   * - HAL initialization, this also initializes the configured device drivers
   *   and performs the board-specific initializations.
   */
  halInit();

  do {
    uint32_t flashSize, magicValue;

    /* Check the firmware intergrity */
    flashSize = (*(volatile uint32_t *)0x1FFFF7E0 & 0xffff) << 10;
    /* Magic value locate at the last 4 bytes in the flash */
    magicValue = *(volatile uint32_t *)(flashSize - 4);
    if (magicValue != 0xa50027d3) {
        break;
    }

    /* Check power on reason */
    if ((RCC->CSR & RCC_CSR_SFTRSTF) != 0 && BKP->DR1 != 0xfeed) {
        /* Software reset occurred */
        break;
    }

    if (BKP->DR1 == 0xfeed) BKP->DR1 = 0x0000;

    /* Clear reset flag */
    RCC->CSR |= RCC_CSR_RMVF;

    JumpToUserApp(0x08004000);
  } while (0);

  /*
   * System initializations.
   * - Kernel initialization, the main() function becomes a thread and the
   *   RTOS is active.
   */
  chSysInit();

  // ...
}

NOTE:

这里可以看到固件检查及跳转是在halInit()之后及chSysInit()之前。

2. 跳转时要重新设置线程工作模式: 在特权模式下运行并使用Main Stack:

void JumpToUserApp(uint32_t pAppAddr) {
  volatile uint32_t *pMspAddr;
  volatile uint32_t *pJmpAddr;

  /* Get main stack address from the application vector table */
  pMspAddr = (volatile uint32_t *)(pAppAddr + 0);
  /* Get jump address from application vector table */
  pJmpAddr = (volatile uint32_t *)(pAppAddr + 4);

  /* Set stack pointer as in application's vector table */
  __set_MSP(*pMspAddr);

  /* Privileged and using main stack */
  __set_CONTROL(0);

  /* Jump to the new application */
  (*(void (*)(void))*pJmpAddr)();
}

NOTE:

ChibiOS/RT初始化时,会使MCU工作在特权模式下,但使用的stack为thread stack。相关的代码在:os/common/ports/ARMCMx/compilers/GCC/crt0_v7m.s

/**
 * @brief   Control special register initialization value.
 * @details The system is setup to run in privileged mode using the PSP
 *          stack (dual stack mode).
 */
#if !defined(CRT0_CONTROL_INIT) || defined(__DOXYGEN__)
#define CRT0_CONTROL_INIT                   (CONTROL_USE_PSP |              \
                                             CONTROL_MODE_PRIVILEGED)
#endif

// ...
Reset_Handler:
                /* Interrupts are globally masked initially.*/
                cpsid   i

                /* PSP stack pointers initialization.*/
                ldr     r0, =__process_stack_end__
                msr     PSP, r0

#if CRT0_INIT_FPU == TRUE
                /* FPU FPCCR initialization.*/
                movw    r0, #CRT0_FPCCR_INIT & 0xFFFF
                movt    r0, #CRT0_FPCCR_INIT >> 16
                movw    r1, #SCB_FPCCR & 0xFFFF
                movt    r1, #SCB_FPCCR >> 16
                str     r0, [r1]
                dsb
                isb

                /* CPACR initialization.*/
                movw    r0, #CRT0_CPACR_INIT & 0xFFFF
                movt    r0, #CRT0_CPACR_INIT >> 16
                movw    r1, #SCB_CPACR & 0xFFFF
                movt    r1, #SCB_CPACR >> 16
                str     r0, [r1]
                dsb
                isb

                /* FPU FPSCR initially cleared.*/
                mov     r0, #0
                vmsr    FPSCR, r0

                /* FPU FPDSCR initially cleared.*/
                movw    r1, #SCB_FPDSCR & 0xFFFF
                movt    r1, #SCB_FPDSCR >> 16
                str     r0, [r1]

                /* Enforcing FPCA bit in the CONTROL register.*/
                movs    r0, #CRT0_CONTROL_INIT | CONTROL_FPCA

#else
                movs    r0, #CRT0_CONTROL_INIT
#endif

                /* CONTROL register initialization as configured.*/
                msr     CONTROL, r0
                isb
// ...

这里提供部分代码供参考:

https://github.com/brobwind/chibios_bro_dbg_link_v2_1

相关的固件可以从这里下载:2016_11_24_BRO-DBG-LINK-V2-1_BL-20161121.bin

己知问题:

- 下载ST-LINK/V2-1固件之后,无法再通过Stlink Utility,STLink firmware upgrade和STLinkReflash工具更新固件。

NOTE:

设置volume label可以通过ST-LINK firmware upgrade工具完成,label的最大长度为11字节。

ST-LINK firmware upgrade工具还可以指定固件类型:

  1. http://www.chibios.org/dokuwiki/doku.php
  2. http://www.st.com/zh/embedded-software/stsw-link007.html
  3. http://infocenter.arm.com/help/topic/com.arm.doc.dai0179b/ar01s02s06.html