Debugging Marlin with ST-Link V2

If you ever used a Marlin Firmware on your 3D printer, it's very likely you or someone else has built the firmware for your printer. However Marlin is a treasure full of features and it's very unlikely you'll get the exact gem you were expecting.

For example, if you modded your printer, then you'll have to build a specialized version of Marlin with the new option flipped on.

Building a firmware with Marlin

Let's consider two possible scenario here:

You'd built your firmware previously

In that case, you already know about customizing the Configuration.h and Configuration_adv.h file. In that case, you can skip the next section.

You were using a pre-built firmware

In that case, you probably don't know about how to build Marlin, or what are the Configuration.h and Configuration_adv.h's files. I'm not going to rephrase the excellent tutorial from Marlin's here, even though it's a big complex for newcomers. To simplify this to the maximum, I advice you to install Visual Studio Codium and PlatformIO first. Then, download (or better, clone with git if you know how to do that) the source code of Marlin from their repository by clicking the green Code button.

In the downloaded archive/folder, you'll find a directory called Marlin containing 2 files of interest: Configuration.h and Configuration_adv.h. The best advice I could give here is to search, in Github search box, for a fork of Marlin for your printer (for example, by entering your printer name in the search box and Marlin). Then extract the configuration headers from this fork. Please make sure the fork is not too old, or you'll have to rework the changes in the configuration files when Marlin improved version after version.

Once this is done, run platformio run in the main folder and fix any issue until it's building correctly. If you have Visual Studio Codium and PlatformIO, it requires specifying the platform you are building for in PIO Home and then clicking on this icon:

Icon to run the build

If it fails to build, please refer to Marlin's documentation above or ask on this page comments.

Running the firmware on your printer

Ok, now you have a binary to install on your printer. You'll need to follow your printer's upgrade firmware procedure to install it (every printer manufacturer provides it own process here, but it usually resume to putting one or more files on a SD Card and starting the printer).

If you are lucky and someone else already followed the same road as you did and fixed all bugs, then you are done, congratulations!

However, I'm pretty sure you are reading this page because it does work as smooth as you expected. When this happens, you'll need to debug the firmware and, unlike your computer, your printer's mainboard is very very limited rendering debugging quite hard (but not impossible).

Debugging a firmware

On your printer, you can have any kind of CPU. If the CPU is too limited (like, for example, an AVR), debugging will be too limited too and you'll end up inserting printf or serial.write into the code and it'll be a PITA.

In this article, I'll only explain the case when running on 32 bits ARM case, and in particular, on a STM32's CPU. Those are very common and they are very cheap to debug.

Hardware

You'll need to use a ST-Link V2 USB dongle like one found in the affiliate link below. You'll also need the schematic of your printer's motherboard, and locate the pins called 3.3V, GND, SWDIO, SWCLK. You'll need to connect JST wire from the programmer to your printer motherboard.

This part is not too hard, but double check the connections and connect the wire while the printer is powered off and without your computer connected to the USB dongle (you might need a USB extender cable, it's better to extend the USB cable than the 4 wires).

Software

Once you have everything plugged in, you'll need to download a software called OpenOCD. It exists for both Linux and Windows, and I strongly advice to use the Linux version if you can since it's easier to configure IMHO. If you have used PlatformIO and VSCodium, then it's probably already downloaded for you (in that case, you'll find the binary and script in your home directory, in ~/.platformio/packages/tool-openocd/ folder). If running under Windows, the path will be different, search on your harddrive for a tool-openocd folder. If you don't have this installed, you can add this line to your mainboard definition in platformio.ini:

#
# MKS Robin Nano (STM32F103VET6)
#
[env:mks_robin_nano35]
platform        = ${common_stm32f1.platform}
extends         = common_stm32f1
board           = genericSTM32F103VE
platform_packages = tool-stm32duino
extra_scripts   = ${common.extra_scripts} buildroot/share/PlatformIO/scripts/mks_robin_nano35.py
build_flags     = ${common_stm32f1.build_flags} -DMCU_STM32F103VE -DSS_TIMER=4
# \/   Add this here  \/
debug_tool      = stlink
# /\   Add this here /\

OpenOCD is a daemon/server that talks to the ST-Link V2 dongle and instruct/monitor the STM32 CPU on your motherboard. Thus, it's required that both the server and the dongle are recognized and are allowed to run with your user's permission. Under linux, this means that you'll need to make sure you are allowed to access the USB device (if not, you'll need to run OpenOCD as root with sudo as a prefix in the next commands). On most system, you'll need to enter a "usb hardware" group, by adding your user to the same group as the detected USB device's group.

For now, this is not very important, provided you'll run the server as root, but if you intend to debug continuously, you'll gain time by setting up proper access to the device.

On recent OpenOCD version, there is no distinction anymore between ST-Link and ST-Link V2 configuration, so I'll skip the old tutorials that instructed to specify which configuration file to use.

Starting the server and debugging the printer

On many printers, the embedded CPU does not run your firmware directly. Instead, it runs a small software first that's in-charge of loading your firmware, and also checking if a new version is present on the SD card and flashing it in that case. This small software is called a bootloader and you must not overwrite it, else you won't be able to update your firmware via SD card anymore (you'll be restricted to use the ST-Link dongle).

In most case, the bootloader is small, so you only need to figure out where to put your firmware. Again, in most case for STM32, it'll be at address 0x8007000 but beware that some boards use 0x8008000 for the offset. If unsure, open the file called platformio.ini in Marlin's source folder and search for your board in a [env:something] section. If you board contains a board_build.offset key, you'll need to use this as the base for computing the offset. So, for example, the Lerdge board reads:

#
# Lerdge base
#
[lerdge_common]
platform           = ${common_stm32.platform}
extends            = common_stm32
board              = LERDGE
board_build.offset = 0x10000

This means that the code will be run at address 0x08010000

Again, most of the time, the bootloader can't be debugged via OpenOCD since it'll be protected (and you are probably not interested in its code anyway). This is painful, since it means you can't reset the board within the debugger and expect the debugged to behave correctly (since it doesn't have any symbol for the bootloader).

So, in order to continue debugging, you'll need to run the debugger like a post mortem debugging (or pseudo post mortem debugging, see below). In this mode, we'll ask OpenOCD not to reset the board upon connecting it and we'll connect it once the firmware is loaded and running.

The command line to start OpenOCD will more or less look like this:

$  ~/.platformio/packages/tool-openocd/bin/openocd -d2 -s ~/.platformio/packages/tool-openocd/scripts -f interface/stlink.cfg -f target/stm32f1x.cfg -c "reset_config none separate"

You'll need to adjust the CPU type of your motherboard (for me, it's stm32f103vet6, so I've chose target/stm32f1x.cfg). If you need to run OpenOCD as root, you'll need to start the command prefixed with sudo and replace the ~/ path with absolute path /home/you/ in both places.

Once started, it should output something like this:

xPack OpenOCD, x86_64 Open On-Chip Debugger 0.10.0+dev-00378-ge5be992df (2020-06-26-09:27)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
debug_level: 2

none separate

Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 1000 kHz
Info : STLINK V2J29S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.079592
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f1x.cpu on 3333
Info : Listening on port 3333 for gdb connections

Then you'll need to go your Marlin source folder and run the debugger on your firmware ELF file like this:

$ ~/.platformio/packages/toolchain-gccarmnoneeabi/bin/arm-none-eabi-gdb /path/to/Marlin/.pio/build/board/firmware.elf

Replace board with your board or platform as instructed in platformio.ini

Once GDB has started, you'll need to connect to remote OpenOCD via:

> tar ext:3333
> si

The first command is a shortcut to target extended remote localhost:3333 with the 3333 port being the port listed in the OpenOCD output above. The second command is a shortcut to step into command to have GDB fetch/synchronize with the current CPU state. Once you are here, the usual GDB command are available, like list to list the source code around the current CPU instruction, info local to list the local variable in the current function, bt to get a stack trace and b file.cpp:123 to place a breakpoint on line 123 of file file.cpp. Beware not to run run since it would mean restarting the application (in that case, the CPU) and this would fail with a bootloader.

Forbidden GDB commands

In order to avoid breaking your system, you'll need to avoid any command instructed to the remote target (like monitor reset halt), or those inducing flashing the firmware (like load) since GDB does not know how to do that correctly, and OpenOCD does not know either and can break things trying.

On some platforms, there is no hardfault handler setup correctly, so when the CPU locks up, you can't get a stack trace (those still using libmaple code are known to break uglily in that case). libmaple is deprecated and will be removed somewhere in the future, but meanwhile, if it happens, you'll probably need to put some volatile bool loop = true; while(loop); in the code before it locks so the CPU loops where you want it and thus you'll be able to make progress by changing the loop variable in the debugger via set loop = false in GDB once it's stopped on the loop. I've submitted a PR for this and hopefully, it'll be accepted and this won't be an issue anymore.

If you have a MKS Robin Nano v1.2 board (the board in a Sapphire Plus printer), you can even fix this by following my tutorial here

Here be dragons

Debugging can cause fire if you don't understand what your are doing. Unplug the heaters (both hotbed and hotend) if unsure.

Beware however that debugging via GDB stops many failsafe guard in the code and thus this should never happen while printing (else you probably won't have thermal monitoring of your hotend and it can burn and catch fire!!!). So if the bug you are trying to debug happens during printing, don't use the method above to debug it, fall back to good old printf instead.

Sponsored links:

In order to pay for infrastructure cost for this blog, I'm listing some affiliated links about the hardware listed above:

Previous Post Next Post