Debugging STM32 with ST-LINK
Affiliate Disclosure: Some links on this blog are affiliate links, meaning I earn a commission at no extra cost to you. I only recommend products and services I trust and use myself.

Semihosting is a debugging tool that is built in to most ARM-based microcontrollers. It allows you to use input and output functions on a host computer that get forwarded to your microcontroller over a hardware debugging tool (e.g. ST-LINK). Specifically, it allows you to use printf() debugging to output strings to your IDE’s console while running code on the microcontroller. Note that we’ll be doing this over the Serial Wire Debug (SWD) port.

Before I show you how to enable semihosting with STM32CubeIDE, note that the downside of semihosting is that it is resource intensive and very slow. This post on the Embedded in Rust blog shows how printing a simple sentence over semihosting can take over 100 ms! The upside is every ARM controller should have semihosting available, which can be a lifesaver if that’s all you have.

If you need printf-style debugging (because you can’t or don’t want to do step-through debugging), here are your options on STM32 parts:

  • Semihosting: should be built in to every ARM chip, but slow
  • Serial (UART): fast, but you need extra pins and hardware (such as a USB to Serial converter board)
  • Virtual COM Port (VCP): fast, but you need USB hardware (see my previous post for setting this up)
  • Instrumentation Trace Macrocell (ITM): fast output over dedicated SWO pin, but it’s only available on Cortex-M3+ parts (i.e. Cortex-M0 chips don’t have it)

Nucleo

If you are using a Nucleo board, semihosting will work, but I highly recommend using Serial (UART) instead. It’s faster, and every Nucleo board has a built-in Serial to USB converter (it’s part of the ST-LINK chip already on the board). If you look at the schematic of your Nucleo board, you should see TX and RX lines going from the target chip to the ST-LINK chip (circled in red below).

UART lines on Nucleo-32 board

Hardware Hookup

For this demonstration, I’ll be using the STM32F070 Breakout Board that I put together (project files can be found here). You will also need an STMicroelectronics ST-LINK to use as a programmer and debugger:

Connect the following pins from the ST-LINK debugger to your STM32 part:

  • 3.3 V
  • SWCLK
  • GND
  • SWDIO
  • RST

Plug the ST-LINK into your computer.

CAUTION! Do not plug in the STM32 board (or give it separate power), as we are using the ST-LINK to power our target board. Otherwise, you might damage the target board.

Debugging STM32 with ST-LINK

Start a New Project

Create a new project for your STM32 part (select STM32F070CB if you’re using the breakout board I mentioned earlier). You can leave everything in the graphical CubeMX portion as default.

STM32CubeIDE

Save and generate code.

Set Linker Parameters

Before writing any code, we need to tell our linker to use the newlib-nano libraries, which houses things like our printf() function. As a result, we also need to tell the linker to ignore the default syscalls.c file. If you forget this step, you will likely end up with several errors like: multiple definition of `_isatty’

To start, go to Project > Properties.

Go in to C/C++ General > Paths and Symbols. Click on the Source Location tab. Click on the arrow next to /<your project>/Src to view the filter. Select Filter (empty) and click the Edit Filter… button. Add syscalls.c to the Exclusion patterns list and click OK.

Adding syscalls.c to the exclusion list

Click Apply.

On the left-side pane, go into C/C++ Build and select the Tool Settings tab. Then, select MCU GCC Linker > Libraries. In the libraries pane, click the Add… button and enter rdimon. This enables librdimon for us to make system calls with semihosting.

Enable librdimon in STM32CubeIDE

Next, click on MCU GCC Linker > Miscellaneous while still in the Tool Settings tab. Click the Add… button and enter –specs=rdimon.specs into the dialog box.

Adding specs to linker

If you have used another STM32 IDE before and got semihosting to work, you probably needed to specify nano.specs and nosys.specs in the linker options. If you take a look at MCU Settings and MCU GCC Linker > General (within the Tool Settings tab), you will see that both of these have been set by default in STM32CubeIDE.

Click Apply and Close.

Add printf Code

Open Src/main.c. Above int main(void) , add the following line (I put mine in the USER CODE 0 section):

extern void initialise_monitor_handles(void);

In int main(void)  (before the while(1) loop), add the following line (I put mine in the USER CODE 1 section):

initialise_monitor_handles();

This configures the semihosting system calls for us.

Finally, inside the while(1) loop, add the following:

printf("Hello, World!\n");
HAL_Delay(1000);

To summarize, here is the section of code from main.c that you need to worry about:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern void initialise_monitor_handles(void);
/* USER CODE END 0 */

/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
initialise_monitor_handles();
/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* Configure the system clock */
SystemClock_Config();

/* USER CODE BEGIN SysInit */

/* USER CODE END SysInit */

/* Initialize all configured peripherals */
/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
printf("Hello, World!\n");
HAL_Delay(1000);
}
/* USER CODE END 3 */
}

Click Project > Build Project to compile and link everything.

Set Debug Configuration

Select Run > Debug As > STM32 MCU C/C++ Application. You should get a pop-up window asking you to “edit launch configuration properties.” If you have already done this once before, you will not get the launch configuration window. To get it, click on Run > Debug Configurations and select <your project>.elf under STM32 MCU Debugging.

Click on the Debugger tab. Change the Debug probe to ST-LINK (OpenOCD).

If you are using an official ST-LINK, you should be good to go. However, note that RST pin of the knockoff ST-LINK boards (such as the ST-LINK/V2 I listed above) may not work. As a result, you will need to use the software reset option when GDB starts.

To enable software reset, click Show generator options… in the Debugger tab (while still in the Debug Configurations window). Under Mode Setup, change Reset Mode to Software system reset.

Change OpenOCD to software reset in STM32CubeIDE

Go to the Startup tab and enter the following command: monitor arm semihosting enable

Enable semihosting session in gdb

This tells the GDB session to allow for semihosting. Click OK, and switch to the debugging perspective when asked.

Run It

In the debugging perspective, click Run > Resume, and you should see “Hello, World!” being printed at the bottom of the console once per second.

printf with semihosting on STM32CubeIDE

Semihosting can help you with your debugging efforts, should you need some kind of console output. Note that other standard commands, like scanf, should work (I have not tested it yet).

36 thoughts on “How to Use Semihosting with STM32

  1. Diego on September 24, 2019 at 5:48 pm Reply

    Hi Shawn, I’m trying to follow this tutorial but I get an “arm-none-eabi-gcc: error: specs=rdimon.specs: No such file or directory” error when I try to compile. I am on Linux Mint 18.2, STM32CubeIDE and a STM32F4-Discovery board. Am I missing a library or sth? Do you know how to fix it? I was searching online but I barely understand what people say about compilers, libraries and so on.

    I saw your first video about STM32 and Nucleo in the DigiKey YouTube channel and it’s great! Thank you for all the stuff you share!

    1. ShawnHymel on September 26, 2019 at 3:40 pm Reply

      Sounds like the IDE can’t find newlib on your Linux machine. I haven’t tried it on Linux, so I can’t verify. However, it seems to be somewhat common: https://stackoverflow.com/questions/41662366/issue-with-compling-arm-assembly-code. So, you might try:

      sudo apt install libnewlib-arm-none-eabi

      to see if that works.

    2. Chris Wilson on October 20, 2020 at 6:29 am Reply

      I think you’re missing the “–” at the begining of “–specs=rdimon.specs”

      1. ShawnHymel on October 20, 2020 at 2:23 pm Reply

        Good catch, thanks!

  2. Eric on May 15, 2020 at 4:11 pm Reply

    tested with my discovery board on windows machine, works very well

    thanks

    1. ShawnHymel on May 16, 2020 at 12:58 pm Reply

      Glad to hear it worked!

  3. Yuri on June 2, 2020 at 2:45 pm Reply

    Hi Shawn,
    I am trying to follow this tutorial on semihosting with NUCLEO-F072 board but debug kick out at the same step every time I attempt to launch a session. Can you point me where to look for the problem please (Win10, STM32cubeIDE v1.3). I did change the port 3333 to 6666 as the debug kicks out early with “Error: couldn’t bind tcl to socket on port 6666: No error”..

    The Console output is:

    Open On-Chip Debugger 0.10.0+dev-01193-g5ce997d (2020-02-20-10:57)
    Licensed under GNU GPL v2
    For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
    none separate
    Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
    adapter speed: 4000 kHz
    adapter_nsrst_delay: 100
    Info : Listening on port 9999 for tcl connections
    Info : Listening on port 7777 for telnet connections
    Info : clock speed 4000 kHz
    Info : STLINK V2J36S26 (API v2) VID:PID 0483:374B
    Info : using stlink api v2
    Info : Target voltage: 3.262549
    Info : SRST line released
    Info : STM32F072RBTx.cpu: hardware has 4 breakpoints, 2 watchpoints
    Error: couldn’t bind gdb to socket on port 6666: No error

  4. Yuri on June 2, 2020 at 4:36 pm Reply

    Ok, I tried another port number (1234) and the debug session went through as expected.. Thank you for this tutorial Shawn!

    1. ShawnHymel on June 5, 2020 at 1:54 pm Reply

      Glad it helped, and thanks for posting what you found! I hope it might help others on Windows.

    2. KHALED MOHSEN ATEF MOHAMED on September 1, 2020 at 5:06 pm Reply

      can anyone help me with this error pleas ?

      GNU MCU Eclipse 64-bits Open On-Chip Debugger 0.10.0+dev-00404-g20463c28 (2018-01-23-12:30)
      Licensed under GNU GPL v2
      For bug reports, read
      http://openocd.org/doc/doxygen/bugs.html
      WARNING: interface/stlink-v2.cfg is deprecated, please switch to interface/stlink.cfg
      Info : auto-selecting first available session transport “hla_swd”. To override use ‘transport select ‘.
      Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
      adapter speed: 1000 kHz
      adapter_nsrst_delay: 100
      none separate
      Started by GNU ARM Eclipse
      Error: couldn’t bind tcl to socket on port 6666: No error

  5. marco on August 13, 2020 at 1:14 pm Reply

    Hi Shawn,
    ii did and everything works. great. But I was wondering, since I have more than one emulator … with ST-Link it works, but with Jlink from Segger I couldn’t get it to work. It can be done ?

    1. ShawnHymel on August 19, 2020 at 3:48 pm Reply

      I don’t have a J-Link, so I can’t test it. In the Debug Configurations, there is an option to select the Segger J-Link as the “Debug Probe.” I recommend starting there and trying different settings in that Debug Configuration window.

      1. Chris Wilson on October 20, 2020 at 6:40 pm Reply

        If you have one of the ST dev boards, you can re-flash the ST-LINK as a J-Link using a utility from Segger https://www.segger.com/products/debug-probes/j-link/models/other-j-links/st-link-on-board/ (this is reversible in case you want to go back to the ST-LINK firmware)

        I was able to get semihosting to work with this J-Link firmware running on the ST-LINK hardware.
        1. Under “Debug Configuration” -> “Debugger” make sure “Debug Probe” is set to “SEGGER J-Link”.
        2. Under “Debug Configuration” -> “Startup” make sure “Initialization Commands” is set to “monitor semihosting enable”.

        By default, GDBServer will output semihosting terminal data from the target via a separate connection on port 2333.
        As a result, you won’t see the semihosting output in the Eclipse console. Instead, you have to launch an external TELNET session and connect to “localhost:2333” to see the output.

        GDB itself can handle (file) I/O operations, and it’s possible to print output via TELNET port (2333), GDB, or both by adding the initialization command “semihosting IOClient ” where is:
        – 1 for TELNET Client (Standard port 2333) (Default)
        – 2 for GDB Client
        – or 3 for both (Input via GDB Client)

        However, I don’t think STM32CubeIDE supports ClientMask 2 or 3 (I wasn’t able to get the output to show up inside the eclipse console with either of these options).

        1. Chris Wilson on October 21, 2020 at 3:35 am Reply

          It looks like the some of the text in angle brackets wasn’t shown in my previous comment above. It should read:

          “GDB itself can handle (file) I/O operations, and it’s possible to print output via TELNET port (2333), GDB, or both by adding the initialization command “semihosting IOClient [ClientMask]” where ClientMask is:”

          More details in https://www.segger.com/downloads/jlink/UM08001

    2. Tom on December 23, 2020 at 8:53 am Reply

      If you are using the JLink debugger (host), use “monitor semihosting enable” as opposed to (monitor *arm* semihosting enable)

  6. Kurt on September 17, 2020 at 6:56 pm Reply

    Hi Shawn, thanks for the demo! I am having a problem occur: “Error in final launch sequence: failed to execute MI command: monitor arm semihosting enable” I am wondering if you have any thoughts on the nature of this issue? I am using Nucleo L432 CubeIDE on Ubuntu. Thanks!

    1. ShawnHymel on September 27, 2020 at 10:43 pm Reply

      I am not familiar with that error. Maybe the STM32 forum would be able to help: https://community.st.com/s/question/0D50X0000BL8nc2/failed-to-execute-mi-command

  7. Nick on October 1, 2020 at 7:17 am Reply

    Hi, Shawn:

    This guide works great, thank you very much! However, I haven’t been able to verify this:

    “If you have used another STM32 IDE before and got semihosting to work, you probably needed to specify nano.specs and nosys.specs in the linker options. If you take a look at MCU Settings and MCU GCC Linker > General (within the Tool Settings tab), you will see that both of these have been set by default in STM32CubeIDE.”

    In the “General” tab of “MCU GCC Linker” (in STM32CubeIDE v1.4.2) I cannot find “nano.specs” or “nosys.specs” anywhere. I don’t know if they are essential because it worked just fine, but maybe they’ll give problems in the future.

    Thank you very much,
    Nick.

  8. Alvaro Ramirez on October 13, 2020 at 6:18 pm Reply

    I little inside of my experience, you need to add the terminator “\n” in order to see the message

  9. Diznh on October 15, 2020 at 2:51 pm Reply

    Thank for you.. Your tutorial guide is very helpful. I’ve meet the problem. The console only display to limit 4 printf. I don’t know why . Could you help.
    Info : Listening on port 6666 for tcl connections
    Info : Listening on port 4444 for telnet connections
    Info : STLINK V2J37S7 (API v2) VID:PID 0483:3748
    Info : Target voltage: 3.236508
    Info : Unable to match requested speed 8000 kHz, using 4000 kHz
    Info : Unable to match requested speed 8000 kHz, using 4000 kHz
    Info : clock speed 4000 kHz
    Info : stlink_dap_op_connect(connect)
    Info : SWD DPIDR 0x2ba01477
    Info : STM32F407VETx.cpu: hardware has 6 breakpoints, 4 watchpoints
    Info : starting gdb server for STM32F407VETx.cpu on 3333
    Info : Listening on port 3333 for gdb connections
    Info : accepting ‘gdb’ connection on tcp/3333
    Info : device id = 0x10076413
    Info : flash size = 512 kbytes
    undefined debug reason 8 – target needs reset
    target halted due to debug-request, current mode: Thread
    xPSR: 0x01000000 pc: 0x080007cc msp: 0x20020000
    configuring PLL
    Info : Unable to match requested speed 8000 kHz, using 4000 kHz
    Info : Unable to match requested speed 8000 kHz, using 4000 kHz
    Info : Padding image section 0 at 0x08000188 with 8 bytes
    target halted due to debug-request, current mode: Thread
    xPSR: 0x01000000 pc: 0x080007e8 msp: 0x20020000
    semihosting is enabled

    Welcome to Demo Task 0
    Welcome to Demo Task 1
    Welcome to Demo Task 2
    Welcome to Demo Task 3

    1. ShawnHymel on October 18, 2020 at 6:09 pm Reply

      “target halted due to debug-request” I’m guessing that something is stopping the program somewhere. I recommend using the debugger to track it down.

  10. Tim Huprich on October 26, 2020 at 1:56 pm Reply

    Hi,
    I am using the STM32CubeIDE with the Nucleo-L476RG (using this bord for a work project so I cant use anything else) and I get this error message:

    arm-none-eabi-gcc: error: specs=rdimon.specs: No such file or directory
    make: *** [makefile:44: semihosting printf.elf] Error 1

    Can someone help me with this problem?

    1. Doug on November 2, 2020 at 6:06 pm Reply

      @Tim Huprich
      Looks like you have “specs=rdimon.specs” for your linker argument. Try adding a ‘-‘ (dash) at the beginning, ala “-specs=rdimon.specs”. Also, please see first few comments above for similar issue.

  11. Stewart on December 24, 2020 at 5:20 am Reply

    In my STM32CubeIDE environment, only using “–specs=rdimon.specs” can work, a single dash ‘-‘ will not work.
    and Alvaro’s comment is right, only when “\n” termination is added in the print content, the string can display in the console.

  12. Tomislav Hooij on February 15, 2021 at 11:27 am Reply

    Hi Shawn,
    Thanks a lot for your great instructions, I am always impressed and helped further in a process.

    I would like to point out that to make this work on my board (STM32L152C-discovery (RCT6) I had to include #include to recognise the printf function. Maybe reading this will help someone else out 🙂

    Thanks a lot for the instructions!

    Kind regards,

    Tomislav Hooij

    1. ShawnHymel on February 15, 2021 at 7:13 pm Reply

      Glad it helped!

  13. Madavan Viswanathan on March 14, 2021 at 6:18 pm Reply

    Hi Shawn,
    I am a newbie o STM32 and learning the ropes , I have a STM32F4 discovery kit with STLINK , the printf doesn’t print anything on SWV console , its a clean build , the downloads successfully , run without error in debug , but prints nothing on ITM data console. Please help resolve this – Thanks

  14. Braeden on March 22, 2021 at 4:39 am Reply

    Just wanted to say thanks so much — this is an amazing tutorial! I stumbled upon stm32 semihosting on stackoverflow, while I was dreading setting up UARTs for printf. I found this article and you made it easy.

    This was super straight forward to follow and worked perfectly on Linux! The only change was the filter is now “Src/syscalls.c” uder the /ProjectName/Core.

    Really appreciate the article and hope you keep posting great ones like this!

  15. TJM on May 8, 2021 at 5:22 pm Reply

    This works on Linux (DebianV10) with my stm32f4-discovery and stm32F407vgt6 with just the USB mini (not micro) connection. My question is why won’t the SWV ITM data console work instead? Is the function just not available with this board or are extra connections necessary to make the SWV work? I thought I had tried everything but needed to revert to semihosting instead.

    1. ShawnHymel on May 9, 2021 at 2:06 pm Reply

      I haven’t tried the SWV yet, so I’m not sure. Have you tried the steps in this article? http://www.embedded-communication.com/en/misc/english-printf-using-st-link-debug-interface-swd-itm-view/

  16. ePiccolo Engineering on July 26, 2021 at 1:44 am Reply

    Hello,

    Thanks for in-depth tutorial. Actually, I will have an STm32 project later this year so this helps. But right now I’m working on the cyclone V Soc development kit and trying to compile freertRTOS on ARM Cortex A9, and getting some errors from the assembler that may be due to not enabling semihosting. I think there is a special trick to enable semihosting on this processor toolchain. Something like defining a global variable – correct me if I’m wrong:

    int _semihosting;

    Do you happen to know about this, or how does it relate to the debugger setup you described?
    Thank you

    1. ShawnHymel on July 26, 2021 at 5:34 pm Reply

      Sorry, I don’t know anything about the Cyclone/Altera/Intel development environment. Your best bet would probably be to ask on the Intel forums (https://community.intel.com/) to see if anyone there knows.

  17. deecode on May 1, 2023 at 10:06 am Reply

    Hi I have tried this on STM32CubeIDE Version: 1.12.0 running mac os 13.3.1 and I receive the error:
    arm-none-eabi-gcc: error: specs=rdimon.specs: No such file or directory

    Do I need to install another library for this to work?

  18. Igor on July 13, 2023 at 1:37 pm Reply

    Hi Shawn,thank you very much for such a detailed article, almost every step was performed without much effort. However, in the end there was such a problem:

    Open On-Chip Debugger 0.12.0-00017-gb153daa14 (2023-02-03-14:58) [https://github.com/STMicroelectronics/OpenOCD]
    Licensed under GNU GPL v2
    For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
    Info : Listening on port 6666 for tcl connections
    Info : Listening on port 4444 for telnet connections
    Info : STLINK V2J41S7 (API v2) VID:PID 0483:3748
    Info : Target voltage: 3.099551
    Info : Unable to match requested speed 8000 kHz, using 4000 kHz
    Info : Unable to match requested speed 8000 kHz, using 4000 kHz
    Info : clock speed 4000 kHz
    Info : stlink_dap_op_connect(connect)
    Info : SWD DPIDR 0x1ba01477
    Info : [STM32F103C8Tx.cpu] Cortex-M3 r1p1 processor detected
    Info : [STM32F103C8Tx.cpu] target has 6 breakpoints, 4 watchpoints
    Info : starting gdb server for STM32F103C8Tx.cpu on 3333
    Info : Listening on port 3333 for gdb connections
    Info : accepting ‘gdb’ connection on tcp/3333
    Info : device id = 0x20036410
    Info : flash size = 64 KiB
    Warn : GDB connection 1 on target STM32F103C8Tx.cpu not halted
    undefined debug reason 8 – target needs reset
    Info : accepting ‘gdb’ connection on tcp/3333
    Warn : GDB connection 2 on target STM32F103C8Tx.cpu not halted
    undefined debug reason 8 – target needs reset
    Error: Failed to write memory at 0x08010584
    Error: Failed to write memory at 0x08010588
    Info : dropped ‘gdb’ connection
    shutdown command invoked
    Info : dropped ‘gdb’ connection

    I use STM32F103C8 blue pill in STM32CubeIDE 1.12.1(Win10x64)

  19. Igor on July 14, 2023 at 9:28 am Reply

    Thank you very much, Shawn!
    In the mode ST-LINK GDB server I have everything goes without problems, but in the mode OPenOCD I get this error:
    Open On-Chip Debugger 0.12.0-00017-gb153daa14 (2023-02-03-14:58) [https://github.com/STMicroelectronics/OpenOCD]
    Licensed under GNU GPL v2
    For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
    Info : Listening on port 6666 for tcl connections
    Info : Listening on port 4444 for telnet connections
    Info : STLINK V2J41S7 (API v2) VID:PID 0483:3748
    Info : Target voltage: 3.102627
    Info : clock speed 4000 kHz
    Info : stlink_dap_op_connect(connect)
    Info : SWD DPIDR 0x1ba01477
    Info : [STM32F103C8Tx.cpu] Cortex-M3 r1p1 processor detected
    Info : [STM32F103C8Tx.cpu] target has 6 breakpoints, 4 watchpoints
    Info : starting gdb server for STM32F103C8Tx.cpu on 3333
    Info : Listening on port 3333 for gdb connections
    Info : accepting ‘gdb’ connection on tcp/3333
    Info : device id = 0x20036410
    Info : flash size = 64 KiB
    Warn : GDB connection 1 on target STM32F103C8Tx.cpu not halted
    undefined debug reason 8 – target needs reset
    Info : accepting ‘gdb’ connection on tcp/3333
    Warn : GDB connection 2 on target STM32F103C8Tx.cpu not halted
    undefined debug reason 8 – target needs reset
    Error: Failed to write memory at 0x08010584
    Error: Failed to write memory at 0x08010588
    Info : dropped ‘gdb’ connection
    shutdown command invoked
    Info : dropped ‘gdb’ connection

    Reset Mode: Software system reset.
    I have tried other modes(Reset Mode) and frequencies(change Connecction Setup), so far nothing has helped. Tell me where the problem is.

  20. Ralf on August 18, 2024 at 9:05 am Reply

    Thanks for this tutorial,
    it worked for me with my jLink and the changes from the comments.
    And i was able to open a command shell console inside stm32CubeIde 1.6.0 to open a telnet connection
    to localhost on port 2333

Leave a Comment

Your email address will not be published. Marked fields are required.