Flashing and debugging code on an MCU

This guide uses a STM32 Nucleo development board to flash to an STM32 series microcontroller. The Nucleo acts as a SWD (Serial Wire Debug) programmer for the main microcontroller, which should be some STM32 series MCU on your PCB.

Prerequisite

Make sure you have set up your development environment as per Setting up an Embedded Development Environment.

SWD Interface

(resources: Introduction to JTAG and the Test Access Port (TAP), SWD – ARM’S ALTERNATIVE TO JTAG, STM Nucleo-64 UM1724 User Manual)

SWD is a two-wire protocol that is an alternative to JTAG. JTAG is the most common interface for debugging/accessing MCU registers, but it requires four pins to communicate while SWD only requires two, so many ARM microcontrollers will use SWD to ease pin requirements.

On the STM32 Nucleo development board (see STM Nucleo-64 UM1724 User Manual), there exists both an STM32 MCU as well as a portion reserved for the Embedded ST-LINK programming and debugging tool.

Red: ST-Link Portion for Debugging and Development

Blue: STM32 MCU and Peripherals

On most of the Solar boards, we will have a SWD interface that looks like the following:

And the Nucleo has pins that look like the following:

Pin CN4 Designation
1VDD_TARGETVDD from application
2SWCLKSWD clock
3GNDground
4SWDIO

SWD data I/O

5NRST

RESET of target STM32

6SWO

Reserved


Important Note about CN2 Jumpers

As defined in the documentation, the CN2 jumpers need to be off when trying to use SWD to interface with an external application (in this case, our circuit board's STM32).

If the CN2 jumpers are on, you will be programming the internal STM32 (the one on the Nucleo itself).

Software Tooling

The openocd and stlink packages are some software tools we use to flash and debug code on our board. They are two different options for doing the same thing (flashing and debugging via JTAG/SWD).

  • stlink (software package) is a tool developed by STMicroelectronics to interface with an ST-LINK device (see the STLink repository for more information) for programming and debugging purposes.
  • OpenOCD runs a GDB server, which allows us to debug remote targets via GDB. It also lets us write to flash with some extra configuration (see https://pfeerick.github.io/InfiniTime/doc/openOCD.html)

Currently, the OpenOCD GDB server is used to debug, while st-flash (one of the several tools in the stlink package) is used to flash code. We previously used st-flash and st-util (both from the stlink package) as our flashing tool and debugging tool respectively, but OpenOCD provided performance benefits over stlink's tools specifically for the GDB server. In the future, perhaps flashing via openocd could be implemented (see https://github.com/lhr-solar/Controls/issues/314) in order to consolidate tooling.

Nevertheless, the Controls/BPS Makefiles are set up with make flash as a target, which will run st-flash and flash code onto the target device. Note that the Linux system you are running on must recognize your ST-LINK device as connected (an easy way to verify this is to run lsusb and see if it appears in the listed devices). If running Linux natively, this is simple, but WSL2 or a VM may require additional steps (see https://learn.microsoft.com/en-us/windows/wsl/connect-usb to set up USBIP on WSL2).

The st-flash command that the Makefile currently runs is as follows:

st-flash write $(BUILD_DIR)/$(TARGET).bin 0x8000000 

This tells st-flash to write to flash memory starting at address 0x0800 0000 (which is the beginning of flash memory in the STM32F413; see RM0090).

Note that the $(TARGET).bin file is used to flash, because we are not flashing the .elf file (executable and loadable format) but rather the binary file directly (since the .elf file will include lots of extra information not necessary for flashing).

Steps to flash

  1. Connect the SWD interface on the Nucleo to your circuit board via jumper wires. Specifically, connect 3.3V, GND, SWDIO, and SWCLK.
  2. Ensure that the CN2 jumpers are removed from the Nucleo board.
  3. Plug in a USB A to USB mini-B connector into the Nucleo. This should provide power to the Nucleo and you should be able to see a green light.
  4. Power your board. For leaderboards this is most likely 12V power; for peripheral boards this may be 5V power.
  5. Verify via lsusb that your device is listed on your Linux environment. You may have to do some extra steps to get the device to be recognized by Linux if using WSL2, as specified in the link above.
  6. Compile your code via your Makefile. This should be simply calling make leader or make stm32f413  or whatever your desired target is as per specified in your Makefile. It may differ from system to system, but understanding how to compile your code is crucial.
    1. Verify that in your build output directory (will most likely be called Objects) you can see the build artifacts: specifically the (TARGET).bin file that you wish to flash.
  7. Run make flash and watch your firmware flash to your device. This will call the st-flash script as specified above.
  8. Press the reset button on the board to reset the MCU and your code should start running.

Steps to debug

  1. Run openocd in a terminal session on your Linux machine. If it detects your device it will stall and tell you that a GDB server has been started on port 3333.
  2. Open a second terminal session on your Linux machine and run gdb-multiarch Objects/(TARGET).elf . This is a version of gdb that widely supports many different architectures, and is the desired version ever since ARM deprecated their custom version provided by their toolchain (arm-none-eabi-gdb).
    1. Note that the elf file we compiled is specified as an input to gdb, since the elf file holds debug information along with the binary itself that is necessary for GDB to recognize the firmware's symbols.
  3. Now you should be in the GDB environment. Run target extended-remote :3333  to connect to the GDB server started in step 1.
  4. You should be able to debug and step through your code via GDB commands (for a full cheatsheet, see here).