Programming with multiple ST-Link programmers

Introduction

It was inevitable that after a while I would hook up multiple ST-Link adapters to my PC. I've been busy with RIOT-os for a few weeks now on a nucleo-f401 board, and most of the time I only needed a single board with my experiments. Almost all of the OpenOCD commands are then perfectly handled by the default OpenOCD scripts. No problems there.

It al started when I wanted to run some tests where I would need multiple boards. Apparently I was naive enough to believe some environment variable would be used to specify the serial port. Well, After some tries, I was only able to flash one of the two boards. So I started digging.

With two devices attached, I got the following information:

hydrazine@dystopia ~ % lsusb
Bus 003 Device 008: ID 0483:374b STMicroelectronics
Bus 003 Device 007: ID 0483:374b STMicroelectronics
hydrazine@dystopia ~/dev/RIOT/examples/hello-world % make list-ttys
/sys/bus/usb/devices/3-14: STMicroelectronics STM32 STLink serial: '066AFF555654725187093847', tty(s): ttyACM0
/sys/bus/usb/devices/3-4.4: STMicroelectronics STM32 STLink serial: '0671FF494956805087161523', tty(s): ttyACM1

Both programmers are recognized, and both programmers have the same USB VID and PID.

Makefiles

I've tried to find something in the makefiles of RIOT-os which would indicate a serial port that would be used to program the STM microcontrollers. The most promising thing was the PORT variable. You can find this variable (and many other) in makefile.vars in the root of a RIOT checkout. Unfortunately, this only switched the serial console such that a 'PORT=/dev/ttyACM1 make term' switched the serial console to /dev/ttyACM1, but OpenOCD would still program the board at /dev/ttyACM0.

So I started looking at the OpenOCD flash process. RIOT uses a simple shell script at dist/tools/openocd/openocd.sh for this. This is mostly a wrapper around OpenOCD specifying a number of commands such as flash, debug and reset. Nothing here indicated a port and none of the variables used there seem to contain any port settings. Thus, another layer deeper.

OpenOCD

Looking at OpenOCD, the first file is from RIOT. For me (nucleo-f401) this was in boards/nucleo-f401/dist/openocd.cfg. Single line there:

source [find board/st_nucleo_f4.cfg]

This directive means that OpenOCD will look in its directories for a file with this name. Most of OpenOCD is build around TCL-like scripts specifying settings for different boards and programmers. On linux these can most of the time be found at /usr/share/openocd/scripts. After reading some documentation and examining these scripts, I found out that I was looking for settings of the interface. If I could set a serial port there in addition to the PID an VID it might work.

The board/st_nucleo_f4.cfg referenced above contains the following:

# This is for all ST NUCLEO with any STM32F4. Known boards at the moment:
# STM32F401RET6
# http://www.st.com/web/catalog/tools/FM116/SC959/SS1532/LN1847/PF260000
# STM32F411RET6
# http://www.st.com/web/catalog/tools/FM116/SC959/SS1532/LN1847/PF260320

source [find interface/stlink-v2-1.cfg]

transport select hla_swd

source [find target/stm32f4x.cfg]

reset_config srst_only

Mainly two files are sourced here. One for the ST-Link programmer and one for the stm32f4x boards. The ST-Link one is responsible for setting up the interface used by OpenOCD, selecting the correct interface type and settings. This file contains the following:

#
# STMicroelectronics ST-LINK/V2-1 in-circuit debugger/programmer
#

interface hla
hla_layout stlink
hla_device_desc "ST-LINK/V2-1"
hla_vid_pid 0x0483 0x374b

# Optionally specify the serial number of ST-LINK/V2 usb device.  ST-LINK/V2
# devices seem to have serial numbers with unreadable characters.  ST-LINK/V2
# firmware version >= V2.J21.S4 recommended to avoid issues with adapter serial
# number reset issues.
# eg.
#hla_serial "\xaa\xbc\x6e\x06\x50\x75\xff\x55\x17\x42\x19\x3f"

Visible here are the interface selection (hla), and some detection settings. Unfortunately, only the device description and the USB VID and PID parameters are used to select the interface. From the OpenOCD documentation, I found out that there's also a hla_serial setting using the long serial identifier.

This OpenOCD script could be adjusted with additional settings to select an interface based on the serial. I consider overriding packaged files bad practice though. The nice thing about the source directive from OpenOCD is that it looks in multiple directories, first ~/.openocd and later in /usr/share/openocd/scripts. Because of this it is possible to override this script by placing a new one in ~/.openocd/interface/stlink-v2-1.cfg. I put the following in mine:

#
# STMicroelectronics ST-LINK/V2-1 in-circuit debugger/programmer
#

interface hla
hla_layout stlink
if { [info exists ::env(SERIAL) ] } {
        hla_serial  $::env(SERIAL)
}
hla_device_desc "ST-LINK/V2-1"
hla_vid_pid 0x0483 0x374b

# Optionally specify the serial number of ST-LINK/V2 usb device.  ST-LINK/V2
# devices seem to have serial numbers with unreadable characters.  ST-LINK/V2
# firmware version >= V2.J21.S4 recommended to avoid issues with adapter serial
# number reset issues.
# eg.
#hla_serial "\xaa\xbc\x6e\x06\x50\x75\xff\x55\x17\x42\x19\x3f"

The adjustment is that this script checks whether there's an environment variable named SERIAL and sets the hla_serial setting to that value if it exists. This way the script keeps its backwards compatibility while it enables us to select a serial interface.i

Testing

For example, we could use udevadm to query the serial id:

hydrazine@dystopia ~ % udevadm info /dev/ttyACM1
P: /devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4.4/3-4.4:1.2/tty/ttyACM1
N: ttyACM1
S: serial/by-id/usb-STMicroelectronics_STM32_STLink_0671FF494956805087161523-if02
S: serial/by-path/pci-0000:00:14.0-usb-0:4.4:1.2
E: DEVLINKS=/dev/serial/by-path/pci-0000:00:14.0-usb-0:4.4:1.2 /dev/serial/by-id/usb-STMicroelectronics_STM32_STLink_0671FF494956805087161523-if02
E: DEVNAME=/dev/ttyACM1
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4.4/3-4.4:1.2/tty/ttyACM1
E: ID_BUS=usb
E: ID_MM_CANDIDATE=1
E: ID_MODEL=STM32_STLink
E: ID_MODEL_ENC=STM32\x20STLink
E: ID_MODEL_FROM_DATABASE=9 Series Chipset Family USB xHCI Controller
E: ID_MODEL_ID=374b
E: ID_PATH=pci-0000:00:14.0-usb-0:4.4:1.2
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_4_4_1_2
E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI
E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
E: ID_REVISION=0100
E: ID_SERIAL=STMicroelectronics_STM32_STLink_0671FF494956805087161523
E: ID_SERIAL_SHORT=0671FF494956805087161523
E: ID_TYPE=generic
E: ID_USB_CLASS_FROM_DATABASE=Miscellaneous Device
E: ID_USB_DRIVER=cdc_acm
E: ID_USB_INTERFACES=:ffffff:080650:020201:0a0000:
E: ID_USB_INTERFACE_NUM=02
E: ID_USB_PROTOCOL_FROM_DATABASE=Interface Association
E: ID_VENDOR=STMicroelectronics
E: ID_VENDOR_ENC=STMicroelectronics
E: ID_VENDOR_FROM_DATABASE=STMicroelectronics
E: ID_VENDOR_ID=0483
E: MAJOR=166
E: MINOR=1
E: SUBSYSTEM=tty
E: TAGS=:systemd:
E: USEC_INITIALIZED=3256252

The ID_SERIAL_SHORT is what we're looking for. In this case '0671FF494956805087161523'. With this we should be able do do:

hydrazine@dystopia ~/dev/RIOT/examples/hello-world (git)-[master] % SERIAL=0671FF494956805087161523 BOARD=nucleo-f401 make reset
/home/hydrazine/dev/RIOT/dist/tools/openocd/openocd.sh reset
### Resetting Target ###
Open On-Chip Debugger 0.9.0 (2016-11-08-19:05)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
none separate
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Info : STLINK v2 JTAG v23 API v2 SWIM v6 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 3.237027
Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
shutdown command invoked

Resetting one of the boards. With an incorrect serial, we get:

hydrazine@dystopia ~/dev/RIOT/examples/hello-world (git)-[master] % SERIAL=1234 BOARD=nucleo-f401 make reset
/home/hydrazine/dev/RIOT/dist/tools/openocd/openocd.sh reset
### Resetting Target ###
Open On-Chip Debugger 0.9.0 (2016-11-08-19:05)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
none separate
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Error: open failed
in procedure 'init'
in procedure 'ocd_bouncer'

/home/hydrazine/dev/RIOT/examples/hello-world/../../Makefile.include:363: recipe for target 'reset' failed
make: *** [reset] Error 1

Clearly an error.

With this bit of replacement script, we get an easy way to configure the serial port for OpenOCD without breaking compatibility.

Pages

Categories

Tags