Hitchhiker's Guide to Printf in Mbed 6

I have a similar problem with USBSerial. I override the console to use the USBSerial and printf works without issues. However with scanf and getchar, it seems to buffer the inputs and return them all at once (around 1000 chars). It works when I use the USBSerial::getc() method instead. So it seems to be an issue with the combination of getc() redirection and USBSerial. Does anybody have an idea what is causing this?

1 Like

Thanks for the great posts. very helpful.
One question mbed-app.json does not seem to be used when compiling a library (e.g. mbed compile --library --source=mbed-os …). Looking at the source mbed.py it is only included when building a complete application. Is the only option to manual change in the different mbed_lib.json files or is there an other possibility?

1 Like

The mbed_app.json seems to be used when building a library too.

mbed-os/tools/build_api.py:

def build_library(src_paths, build_path, target, toolchain_name,
                  dependencies_paths=None, name=None, clean=False,
                  archive=True, notify=None, macros=None, inc_dirs=None, jobs=1,
                  report=None, properties=None, project_id=None,
                  remove_config_header_file=False, app_config=None,
                  build_profile=None, ignore=None, resource_filter=None, coverage_patterns=None):

    ...

    Keyword arguments:
    dependencies_paths - The location of libraries to include when linking

    ...

    app_config - location of a chosen mbed_app.json file

    ...

    # Pass all params to the unified prepare_toolchain()
    toolchain = prepare_toolchain(
        src_paths, build_path, target, toolchain_name, macros=macros,
        clean=clean, jobs=jobs, notify=notify, app_config=app_config,
        build_profile=build_profile, ignore=ignore, coverage_patterns=coverage_patterns)

    ...
1 Like

I realize I should have been more specific about the Library. Mbed is having 2 “versions.” I mend that Mbed_app.json is not included when you build a static library (.a / .ar) instead of a linked executable. You then use the command: mbed compile --library. It is then using build.py to create one

The build_api.py build_library seems to be creating a source library that can be included in a future project .lib file.

Any help/advice on including the changes in a static library appreciated.

regards,
Paul

1 Like

Hello Paul,

Now I see your point. However, since it doesn’t directly relate to printf, in order to avoid kidnapping this thread I’d suggest to open a new one and continue there, if you don’t mind.

Best regards, Zoltan

1 Like

Thanks.

It is related, as the changes to enable printing to serial/printf must be done now be in mbed_app.json which is NOT included in a static library. However it is a separate and much broader issue than only this serial/printf issue.

i’ll raise a new issue.
regards,
Paul

1 Like

Great post! Is there a way to get an ´mbed::FileHandle´ or similar out of a ´std::FILE´, that I can e.g. use together with int ´FATFileSystem::file_sync(fs_file_t file)´ ?

You can get the FileHandle of std by defining your own console.

For example:

...
FileHandle* stdFileHandle;  // declare a global variable
...
FileHandle* mbed::mbed_override_console(int)
{
    static BufferedSerial myConsole(STDIO_UART_TX, STDIO_UART_RX, 115200);
    stdFileHandle = &myConsole;  // get the std file handle
    return &myConsole;
}
...
int main()
{
   // FileHandle of std is available as 'stdFileHandle`
2 Likes

How have you override the console to use USBSerial with printf ?
Any USBSerial methode doesn’t give you BufferedSerial class/object.

mbed_override_console is the function that redirects printf and it requires a FileHandle, not a BufferedSerial.

USBSerial itself derives from FileHandle, so you can just return the USBSerial object in that function.

1 Like

As explained by @ boraozgen :

#include "mbed.h"
#include "USBSerial.h"

FileHandle* mbed::mbed_override_console(int)
{
    static USBSerial   myConsole(false, 0x1f00, 0x2012, 0x0001);
    return &myConsole;
}

Edit: To make this work

  • either create the myConsole as myConsole(true, 0x1f00, 0x2012, 0x0001);
  • or call myConsole.connect(); before returning from the function.

See below @ boraozgen’s tip.

I was my problem. Thanks for your answers.
Have you solved the getc redirection ?

I am using v6.12 and have found the same effect with getc.

I don’t remember solving it, it was just a PoC and I didn’t work further on it.

Another tip on USBSerial:

Constructing USBSerial with connect_blocking = true (first parameter) blocks in the first printf call until the COM port is opened.

mbed::FileHandle *mbed::mbed_override_console(int)
{
    static USBSerial serial;
    return &serial;
}

Constructing with false and calling connect() does not block and the messages will be printed after opening the COM port. The COM port is not available if I do not call connect after constructing with false. Isn’t this the case for you @hudakz?

mbed::FileHandle *mbed::mbed_override_console(int)
{
    static USBSerial serial(false);
    serial.connect();
    return &serial;
}

Thank you Bora for pointing this out! Yes, it works the same way on my side. No /dev/ttyACMx port is available (on Linux) when the USBSerial object is created with connect_blocking = false unless the connect() method is called as you suggest.

Actually, no ITM initialization is needed for STM targets. It seems that it’s sufficient to define a dummy ‘itm_init’ function as below and redirect the Mbed CONSOLE to the SWO:

#include "mbed.h"
#include "SerialWireOutput.h"

DigitalOut  led1(LED1);

extern "C" void itm_init() { }

FileHandle* mbed::mbed_override_console(int)
{
    static SerialWireOutput swo;
    return &swo;
}

int main()
{
    printf("Hello, SWO!\r\n");

    while (true) {
        led1 = !led1;
        printf("blink\r\n");
        ThisThread::sleep_for(500);
    }
}

After adding an ITM device in the mbed_app.json file as below, printf should print over the SWO:

{
    "target_overrides": {
        "*": {
            "target.device_has_add": ["ITM"]
        }
    }
}

A modified STM-Link V 2 probe can be used to program and debug STM custom target boards. The SWO wire shall be connected to the target MCU’s PB_3 pin (but it’s better to check also the related datasheet).

On the NUCLEO boards the SWO wire is available at the pin #6 on the CN4 (SWD) connector. To print over the SWO connect it to the PB_3 pin with a wire.

Printf messages can be then displayed with the STM32CubeProgrammer by switching to the SWV page.

2 Likes

Thats correct, I don’t know why I didn’t mention this before :slight_smile:

Thanks for the insightful elaboration on console behavior in Mbed OS 6.

How would you test the std console input for readability? Here’s the example from Mbed OS 5:

#include "mbed.h"
 
Serial          pc(USBTX, USBRX);
 
int main()
{
    while (true)
    {
        if(pc.readable()) {
            pc.getchar();
                 ......
        }
    }
}

Hello Hans,

You can try:

#include "mbed.h"

int main()
{
    FileHandle* console_in = mbed_file_handle(STDIN_FILENO);
    FileHandle* console_out = mbed_file_handle(STDOUT_FILENO);
    const int   buffer_size = 256;
    char        buffer[buffer_size];
    int         read_count;

    while(true) {
        while (console_in->readable()) {
            read_count = console_in->read(buffer, buffer_size);
            console_out->write(buffer, read_count);  // echo
            ...
        }
        ...
    }
}

If you’d like to do it byte by byte:

int main()
{
    FileHandle* console_in = mbed_file_handle(STDIN_FILENO);
    FileHandle* console_out = mbed_file_handle(STDOUT_FILENO);
    char        c;
    
    while(true) {
        while (console_in->readable()) {
            console_in->read(&c, 1);
            console_out->write(&c, 1);  // echo
            ...
        }
        ...
    }
}

However, the first method should be more efficient.

1 Like

Came up with another hack… this is if you want to switch between console UART pins (as defined in pinnames.h) and RX/TX pins of your choosing without having to change code between the two in main.

#include "mbed.h"

#define USE_MYUART_INTERFACE 0

#if USE_MYUART_INTERFACE
    BufferedSerial otherUART(P3_1,P3_0,MBED_CONF_PLATFORM_STDIO_BAUD_RATE);
    FILE* myTX = fdopen(&otherUART, "r+");
    #define printf(f_, ...) fprintf((f_), __VA_ARGS__);
#else
    FILE* myTX = NULL;
    #define printf(f_, ...) printf( __VA_ARGS__);
#endif


// main() runs in its own thread in the OS
int main()
{
    printf(myTX,"Hello New World\r\n");

    while (true) {

    }
}