Printing strings on a Virtual COM port with an STM32 and ChibiOS


This article contains some simple examples to understand how to print escaped strings when you are approaching STM32 and ChibiOS. Escaped strings are very useful while developing because you can use them to print data while the application is running. Strings are widely used for debugging purposes when a debugger is not available but this is not the case.

Every STM32 development kit is equipped with a STLink debugger. Starting from STLink V2-1, the debugger offers a virtual COM port and, in this article, we are going to use this feature to print data avoiding to put an effort into making additional connections.


To understand this article with proficiency, you should match few requirements:

Additionally, you should even take a look to STM32 GPIO peripheral and ChibiOS’ PAL driver. More in detail you should take confidence with LEDs and push-buttons. In case you don’t, we have a detailed article on GPIO (Using STM32 GPIO with ChibiOS PAL Driver) and some examples on LEDs and push-buttons (Dealing with LEDs using an STM32Dealing with Push-Buttons using an STM32).

Notes about the embedded STLink

All the STM32 based development kit are equipped with a debugger. When the STM32 Nucleo-64 F401RE went on the market in late 2013, ST has launched a newly embedded debugger known as STLink V2-1 which offers some interesting features like the STLink Virtual COM Port even shorted as VCP. Connecting a development board equipped with a VCP a new device will appear in device manager as ST-Link Virtual COM port.

From the device side, the ST-Link is physically connected to a couple of pin of the STM32 which can be rerouted as UART TX and UART RX via GPIO Alternate Functions. The information about this connection is on the board schematic and thus reported in the board User Manual. For example looking at the STM32 Nucleo-64 User Manual you will find out that ST-Link is connected to USART2 through the pin PA2 (Arduino connector D1) and PA3 (Arduino connector D0). Looking ad STM32F3 Discovery (starting from revision MB1035C or later) the STLink is connected to UART1 through the pin PC4 and PC5.

Such kind of information is very important especially when things seem to be not working.


Hello world

Create a single thread project that prints a sentence over the STLink Virtual COM port on button press with a baudrate of 115200 bit per second.

For this example, we are going to use STM32F3 Discovery (revision C or higher). To start let us use the edge detection explained in the article Dealing with push-buttons using an STM32 as starting point. More precisely we are going to use the event-based implementation using the event/wait paradigm.

Let us take a look at the code from that example

Note that:

  • The PAL API related to event/wait becomes available when PAL_USE_WAIT switch is enabled in halconf.h.
  • The functions we are going to use to perform the string print are blocking. This means that we cannot use the event/callback paradigm because no blocking functions are allowed in the callback as they are executed in ISR context.
  • The main thread lays in a sleep state and it is woken up by a button line falling edge: when the thread resumes it can perform its normal activities before to go to sleep again (e.g. the palTogglePad(GPIOE, 8U))

To print a string on the STLink VCP we will relay on ChibiOS Serial driver. To complete the task we have to do basically three things:

  1. Check to which USART peripheral the VCP pins are connected to. We have also to understand which is the proper GPIO configuration to re-route these pins correctly.
  2. Enable the Serial Driver and assign a peripheral to it acting on the halconf.h and mcuconf.h files.
  3. Startup the Serial Driver with a proper configuration once before the loop.
  4. Replace the palTogglePad() with a serialWrite()
USART RX and TX lines configuration

After some investigations, it will sort out that STLink VCP is physically connected to the STM32F3 UART1 through the GPIOs PC4 and PC5. Such information is available on the STM32F3 Discovery board user manual. The alternate function table to check how to reroute USART1_TX and USART1_RX on PC4 and PC5 is available on the STM32F303VC Datasheet.

Well, it will sort out that configuring the working mode of PC4 and PC5 as Alternate Function 7, the USART1 RX/TX lines will be re-routed on them.

How to configure STM32F3 Discovery GPIOs to deal with STLink Virtual COM port

For our discovery kit, such kind of initialization is already performed at board level (it would be better you had read GPIO/PAL article). Indeed, the following code box is an excerpt of the board file of the STM32F3 Discovery: we can notice that PC4 and PC5 have been labelled as LINE_VCP_RX and LINE_VCP_TX and that they have been configured as AF7

For the sake of the knowledge, let us assume for this example that board files are not initializing these GPIOs correctly. In such case, we can put a remedy to this changing the board file or using the following code snippet after the halInit()

This snippet, executed once after the halInit() would overwrite the GPIO configuration. Note that to modify the board files is not suggested as they are shared between every project based on that development kit. Before to act on them we should create our own copy placing it inside our project folder editing the makefile to use these instead of the shared one. For now, let’s keep things simpler and let’s use the code snippet inside our main.

Serial driver enabling and peripheral allocation

I would assume that you already read the article about USART peripheral/Serial driver. This article shows that to use the Serial Driver you have to enable it in halconf.h setting the  HAL_USE_SERIAL switch as TRUE and you have to allocate a peripheral to it in mcuconf.h. In this case, we would use the USART1 thus we will allocate it to the serial driver

Note that if we have started from the default STM32F3 Discovery demo, these switches would be already in the right position as default demo uses STLink VCP to print the test suite results.

Serial driver start and configuration

Every driver except PAL shall be configured before to be used. Even the serial driver shall be properly initialized and configured. This operation is carried out by the sdStart() function. I anyway as a quick recap:

  • The sdStart() function receives two parameters: a pointer to the driver and a pointer to a configuration structure.
  • There are multiple instances of the Serial Driver, one for each available USART, but each instance becomes available only when a peripheral is assigned to the driver
  • The instance associated to USART1 is SD1
  • The configuration structure of a driver could change from subfamily to subfamily. Up to now, this is not true for Serial Driver configuration which is the same across all the STM32 subfamilies
  • The configuration structure is composed of 4 fields: speed, cr1, cr2 and cr3. The speed parameter is a 32-bit unsigned integer representing the baud rate of our serial driver, the other three are the initialization values of the control registers 1, 2 and 3 of our USART and are 16-bit unsigned integers.
  • For the description of UART control registers, we should relay on the STM32F3 reference manual. For a more detailed explanation about how to deal with configuration structure, I suggest you read the paragraph “Configuring Serial Driver” of the article about Serial driver.

For our task, we basically need a configuration structure that looks like

and the start call would look like

Writing using the serial driver

The last but not the least piece of code we need to use is that which writes the string. The function we are going to use is the sdWrite which receives a pointer to the driver, a buffer of 8-bit unsigned integers and the size of this buffer.

With a slight deviation from the original task, I gonna write “Hello PLAY embedded” plus I will add some escape character (i.e. “\r\n”). Thus the whole string size is 22 and the code would look like

Note that the explicit cast, in this case, is required to avoid a warning at compile time.

Connecting the dots: the solution

Now we have all the pieces to compose the puzzle. Even if the task requires a single thread application, I would add a second thread that blinks a LED: it will act as a cheap and quick monitor to check whereas the application is running or not. The following code box is how the main.c file will be after our edits

Connecting the dots one again: same project different board

What if we decide to use STM32 Nucleo-64 F401RE instead of STM32F3 Discovery? Well, the main difference is that on Nucleo board the VCP is connected to the STM32 through UART2 (PA2 and PA3) instead of USART1. This means we have to consider few differences related to the board layout:

  • On the Nucleo-64 boards, VCP is connected to UART2 instead of USART1.
  • LEDs and button are connected to different GPIOs.

This leads to some changes in our code:

  • In muconf.h, we have to assign USART2 to the Serial Driver instead of USART1
  • In serial related function calls, we have to use SD2 instead of SD1
  • In main, we have to configure PA2 and PA3 instead of PC4 and PC5 (alternate functions could be different).
  • In blinker thread, we have to blink a different line (LINE_LED_GREEN instead of LINE_LED3_RED).
  • In PAL event/wait shall be configured on PC13 instead of PA0

The following code box is how the main.c file will be we are using an STM32 Nucleo-64

Interacting with the STM32

Create a project that is able to manage a LED getting a character from the terminal.

‘0’ -> turn off the LED

‘1’ -> turn on the LED

‘2’ -> blinks the LED

Let’s do this using again an STM32F3 Discovery. The goal is to get a character from the terminal and consequently take an action. To make things a little bit more challenging we are going to split our problem into two separate parts associating a thread to each one:

  • getting the information from the Serial Driver
  • taking an action

This approach aims to create two independent blocks which are able to communicate with each other. This offers a tremendous advantage: even if a block changes completely things will continue to work smoothly if the communication mechanism does not change. Do not worry if this is not completely clear know: it will be by the end of this example.

Getting an input

One of the parts of this problem is to get a character from a terminal and this can be easily done. Let us assume that this part would be performed by a thread that manages everything is related to the USART: this thread would be a well-defined entity that works independently. Thus, in here we have to configure everything is related to the USART plus we have to perform our reading operation.

There is nothing new in the code outside the loop. Let’s thus concentrate on the loop of the thread. Basically, this code tries to get a character from the serial driver with a timeout. If it has effectively received a character it echoes and if the character is one of those actually allowed it updates a variable with the new value.

There are few things to point up about this code. Let’s start from the most important: the concurrent access to the variable state. This variable is declared with the static keyword. This means that is allocated statically in memory and this basically means that such a variable is placed at a specific memory address and never moved. It will be there for a lifetime (i.e. for the whole execution time). This variable is even global as it is declared outside any function and this means it can be seen from all the functions in this sheet. As threads are effectively functions, such a variable can be accessed from every thread declared in main.c. Considering this specific case we are planning to use this variable from two separate threads and this is what is called concurrent access. If not properly regulated, concurrent access can lead to variable value corruption. To avoid this we have placed the variable update in a Critical Zone: all the operation in this zone are atomic i.e. these operations cannot be interrupted for example by a context switch.

Another interesting note is about the thread timing: the function sdGetTimeout() is blocking and will internally put the calling thread in a sleep state until it does not receives a character or a timeout occurs: this means that no sleep is needed in this thread because context switch in favour of lower priority threads is guaranteed by this function.

To conclude, let’s take a look to the returning value of sdGetTimeout(): it could return MSG_TIMEOUT in case of operation timeout and this requires an additional check to ensure that we have actually received a character before to do something.

Taking an action

We can use another thread, like the main, to take an action. This thread has simply to access periodically the state variable and change the status of the LED consequently.

Note that a critical zone shall be as small as possible in term of execution time and in term of the number of instruction executed in it. Copying the value of the shared variable in a local variable before to use it we can reduce the extension of the critical zone.

The solution

Now we can put things together and, as we have up to 8 LEDs on this board, we can even add the dummy blinker thread as a monitor that application is running.

We decided to use two threads to create two independent blocks which are able to communicate with each other as this would offer an advantage in code reuse. To point out better this point let us assume that instead of driving a LED we would use the information received from serial driver to change the speed of a DC motor whereas 0 means stopped, 1 half speed and 2 full speed. In such case we just need to act on the loop of our main instead of changing the whole application and the serial thread as well as blinker thread can be copied as are.

Printing formatted strings

Create a project that prints current system time over the STLink Virtual COM port on button press.

For this task, we are going to need the chprintf(). We already introduced the “streams” module here. The quick recap is: to print formatted strings we need chprintf, to use chprintf we have to include streams to our project, to do that we have to edit the include section of our makefile.

Editing the makefile to include chprintf

To do that we have to identify the include section opening our makefile. We would easily identify it because it begins with a comment which states

In here there is also the variable CHIBIOS we have to edit every time we duplicate a default demo. Well, right below there are many include statement. Here we have to add one more to include the streams module

and our makefile would look like this

At this point, we can include the chprintf.h in the main file and use it.

The starting point

This problem is quite similar to the first exercise of this article. We can start from that solution to achieve our goal: we need to configure Serial Driver, we need the event/wait paradigm and eventually a dummy blinker thread. So our starting code would look like this

Note that here we have performed few dummy edits:

  • We have removed VCP related GPIO configuration because this initialization is actually performed in board files. We added that code just to provide a practical example about what to do in case we need to change the GPIO configuration at the application level.
  • In the event\wait APIs, we have used line instead of port and pad. Note that the identifier LINE_BUTTON is declared as usual in the board.h file as

Now we need to add some specific code:

  • we have to include the chprintf.h, nothing easier of that.
  • we will get the current system time, and for this, we are going to use an API we have already used in the push-button long press/short press exercise: the chVTGetSystemTimeX() which return the current system time as system tick. We would even need of a macro to convert the value from system ticks to milliseconds.
  • we will print a formatted string through the chprintf over the Serial Driver. We have already seen here that the chprintf() is identical to a C standard printf() but requires an additional parameter representing the stream on which it would write. It expects a pointer to BaseSequentialStream but actually, SerialDriver implements this interface thus we have everything we need to complete this task.

Let’s take a look at the solution which would clarify the previous statements

Help and share

Do you need help with exercises? Something is unclear? Comment below or subscribe to our forum. Help us to spread the knowledge sharing this article: share is caring!

Replies to Printing strings on a Virtual COM port with an STM32 and ChibiOS

  • Hi,

    using this code

    reliably crashes an F042 chip. When I comment out either chprintf line, all is good. Tried to increase serial buffers, but no help. Anything I am doing wrong?


    • Ciao,
      enable debug options in chprintf and check whereas the operating system detects an anomaly and stops in a controlled way.

      to me, it sounds like a buffer overflow. Check the adcSamples size.

  • Hi Rocco,

    thanks for the response. The issue was solved by switching back to gcc 5.xx as suggested by chibios 19 for an M0 core. Solved a lot of other issues as well. It might be worthwhile to warn users even more clearly that newer gcc does not work with these chips.


    • Thanks for commenting… I have never noticed this problem because I tend to use the latest version of ChibiStudio and in the latest version we have GCC 5.4 and 7.0. I suppose your comment would be helpful to many readers.

  • Hi Rooco,

    No matter what I tried, I could not manage the work for it stm32f407vg-disc1 board. Basically I am using the code of “Connecting the dots one again: same project different board” section which must be identical when it comes to USART2.

    Do you have any example serial communication sketch for my board? Thank you.

    By the way I appreciate the work you guys have done. It is fantastic. Keep up the good work.



    • Hey there,
      I am not 100% sure but the f407 discovery lacks the connection between a STM32 usart and the STLink VCP. Maybe you need to bridge them with duponts. This info is on the user manual of your board.

Leave a Reply