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, 能够实现固件的下载及加载

经过进一个月的分析,不断尝试,自己写一个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

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

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

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

 

  • - USB接口实现及其配置

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的时候会用到。

  • -基于ChibiOS/RT v16.1.2的实时系统实现的bootloader

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工具还可以指定固件类型:

2016_11_24_stlink_fw_upgrade_adv

  • 相关的参考文档
  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

《STM32: 做一个与ST-LINK/V2-1调试器兼容的bootloader》有20个想法

    1. 1. volume name以”NUCLEO”, “EAL”, “DIS”, “NODE”开始: reserved for ST boards
      2. volume name最长为11个字符,少于11个字符ST-LINK firmware upgrade工具会报错,超过会被截断:
      $ java -jar ~/Downloads/stsw-link007/AllPlatforms/STLinkUpgrade.jar -volume brobwind -force_prog
      libusb: warning [darwin_open] USBDeviceOpen: another process has device opened for exclusive access
      Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 8
      at com.st.stlinkupgrade.core.e.a(SourceFile:191)
      at com.st.stlinkupgrade.app.a.a(SourceFile:280)
      at com.st.stlinkupgrade.app.b.a(SourceFile:76)
      at com.st.stlinkupgrade.app.MainApp.main(SourceFile:16)

      $ java -jar ~/Downloads/stsw-link007/AllPlatforms/STLinkUpgrade.jar -volume brobwind_com -force_prog
      libusb: warning [darwin_open] USBDeviceOpen: another process has device opened for exclusive access
      -volume NAME_OF_VOLUME: is limited to 11 characters and has been truncated
      libusb: warning [darwin_open] USBDeviceOpen: another process has device opened for exclusive access
      ....................Upgrade is successful.
      libusb: warning [darwin_open] USBDeviceOpen: another process has device opened for exclusive access
      libusb: warning [darwin_open] USBDeviceOpen: another process has device opened for exclusive access
      Version read: 2.28.16

      1. 你好 设置volume label可以通过ST-LINK firmware upgrade工具完成, 我看了你上面的回复 恕我愚钝 具体要怎么操作 可以告诉我吗 谢谢哦

  1. 你好,请问具体来说,是怎样才可以用自己的bootloader驱动stlink的程序?是设置好USB,然后检查固件完整性后,就可以直接跳到0x8004000了?
    我尝试弄一个bootloader,可以跳转到stlink或者black magic probe,这样就可以一个东西兼容STM8与CORTEX-M器件在LINUX上调试或者下载程序了。
    请问可以给出USB部分的参考代码吗?
    谢谢

    1. 对于使用自制bootloader加载st-link/v2-1固件,需要在bootloader的0x08000100开始的地方,写入0x15, 0x3c, 0xa5, 0x47这几个值。
      gdb+openocd+ST-LINK/v2-1去调试USB相关的代码时会有性能瓶颈,但下载固件却是非常好用。
      我看这几天要是有空就整一个出来。

      1. 用black magic probe调试并不需要开openocd,直接用gdb连接调试器提供的虚拟串口就可以了。就是下载程序麻烦点,需要弄个gdb的脚本

        1. 是的,black magic probe并不需要openocd,但我也没怎么用这东西。一般调试使用gdb+openocd+ST-LINK/V2-1还算稳定

      2. 还有个问题就是,升级ST-LINK的固件会比较麻烦。除非BOOTLOADER是ST-LINK兼容的

        1. 你可以试试做个ST-LINK兼容的bootloader, 加密、解密算法都在提供给你的参考代码里了。
          由于ST-LINK性能不是太好,而且如果ST-LINK出现bug什么的,你也没有办法修改。
          还是应该去尝试看看新的开源调试器,如BMP等

          1. 感谢。我是想用BMP来调试的,但是有些项目使用STM8,所以还是离不开ST-LINK

      3. 0x08000100这个地址应该是vector table里的,0x15, 0x3c, 0xa5, 0x47是不是一个中断服务的入口?我试了,在自己写的BOOTLOADER里,只做跳转到0x08004000,生成的bin用HEX编辑器把这四个数据写到便宜0x100处,然后把BIN写到0x08000000,还是无法启动STLINK,是不是还要做其他初始化设置?

        1. 这里提供代码给你参考(不一定能正常工作):https://github.com/brobwind/chibios_bro_dbg_link_v2_1
          关于0x15, 0x3c, 0xa5, 0x47, ST-LINK/V2-1固件需要这几个值才能正常工作,ST-LINK/V2就没有这样的问题
          即便是ST-LINK/V2-1固件能正常工作了,但是通过Stlink Utility工具进行升级操作的时候,还是会有问题(不能进入bootloader: 假设Stlink Utility执行升级命令的时候会重启进入bootloader或者是通过跳转进入bootloader)

          1. 非常感谢。原来这几个值是针对V2_1版本的。我因为要下载调试STM8,所以用了其他的版本,不过我也试过把那个版本在偏移0x100处的数值写到我自己的BOOTLOADER对应位置,还是无法启动ST-LINK的固件。我再研究研究。

  2. 我现在烧录了STLINK的固件,U盘显示盘符为“undifined”,我想让盘符显示自己产品的名称,能详细讲一下如何修改盘符吗?

      1. 我按照你的步骤,但是显示如下,怎么改?
        C:\stsw-link007V2J28M18RC2\AllPlatforms>java -jar STLinkUpgrade.jar -volume Modular2_S -force_prog
        Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: 10
        at com.st.stlinkupgrade.core.e.a(SourceFile:186)
        at com.st.stlinkupgrade.app.a.a(SourceFile:304)
        at com.st.stlinkupgrade.app.b.a(SourceFile:84)
        at com.st.stlinkupgrade.app.MainApp.main(SourceFile:16)

        1. volume的长度必须大于10个字节,但程序会将其截断成11字节,也就是最长只能有11个字符

发表评论

电子邮件地址不会被公开。 必填项已用*标注