Redirecting printf to USB serial in Mbed OS 6

Wanted to capture this in case it helps others.

Problem: remapping printf() debugging output to a USB OTG Serial port (on a custom board but should also apply to standard supported boards.)

I thought this would be a simple problem that others had solved a long time ago as it must quite a common problem. Turned out it wasn’t so simple.

I was able to get some hints from the post Remaping printf on custom boards in Mbed 6 and the excellent Hitchhiker’s Guide to Printf in Mbed 6.

This got it basically working but not in all situations.

‘Blocking’ mode was fine: whenever debug output is flushed it waits for a terminal to be (re)connected before continuing the app. However, this means the board never runs without a terminal connected!

In ‘non-blocking’ mode, when the USB serial terminal is disconnected and reconnected the terminal would (re)connect but not get any data. Without a delay at the start of the app, there wasn’t time for the serial terminal to be connected for any output to be displayed. And disconnecting/reconnecting while the app is running stops any further output.

A further detail was that with the CCG compiler non-blocking mode worked fine. Seemed to be an issue with the Arm 6 compiler.

With help from Don Garnier at Arm, I now have a a solution that appears to work fine. The problem was that the Arm runtime library closes the console on the first error (CGG I assume just carries on.) The fix is to override the USBSerial _putc() method to always return a success.

Example code: add to e.g. main.cpp to redirect to USB Serial:

#include "USBSerial.h"

static constexpr auto UsbSerialBlocking = false;
static bool isConsoleInitialised = false;

class MyConsole : public USBSerial {
    using USBSerial::USBSerial;
    virtual int _putc(int c) override {
        return c;
static USBSerial* get_console() {
    static MyConsole console(UsbSerialBlocking);

    if(!UsbSerialBlocking && !isConsoleInitialised) {


        isConsoleInitialised = true;

    return &console;
namespace mbed
    FileHandle *mbed_override_console(int fd)
        return get_console();

The 5s delay is optional. It enables the terminal to be connected or reset before main() runs so the initial debug info isn’t missed when restarting the app/board.

Info: on Windows the _Tera Term _ serial terminal app works well as it handles the virtual COM port going away and coming back whenever the app/board is restarted by automatically reconnecting. Most other apps I tried (including Putty) just flag an error and need manual reconnecting every time.

Mbed OS 6.12.0
Board: STM32L4+ B-L4S5I-IOT01A


I found this little CLI tool I can recommend for COM port communication on Windows:

It does automatic reconnection and does not suck like TeraTerm :slight_smile:

I also had the issue with the delay in the beginning. Of course nobody wants to add a delay in their production app just for debugging purposes. I wonder if there is an API to detect if the USB cable is connected and delay accordingly?

1 Like

Regarding the delay, in my case some bytes must be printed to the console before delaying, otherwise the first bunch of messages after the delay are still lost. It does not matter if I wait 1s or 5s. For me, the best solution seems to be to print a couple of dummy bytes (which get lost), wait 1 second and then continue with the application, which prints out reliably.

As mentioned above, it would still be better to skip the delay if the USB cable is not connected. Any tips regarding this would be appreciated.

Thanks for sharing this! For my application, I need to be able to read from the USB OTG Serial port as well. However, when I try to scanf or getchar, the program hangs. Any ideas on how to make this work for serial input?

You can use USB VBUS detection using any GPIO pin with external interrupt. Connect a resister divider to USB +5v signal and input into any GPIO with interrupt. In software, at startup, check USB presence by pin level and connect accordingly. Setup an interrupt handler to re-connect if USB is re-plugged.