printf() and scanf() with GNU ARM Libraries

In “Semihosting with Kinetis Design Studio” I’m using the debugger with semihosting to output text with printf(). But how to use a physical serial connection instead?

printf() and scanf() in action

printf() and scanf() in action

This post is about how to enable and use printf() and scanf() with GNU ARM libraries. I show it both for the Freescale Kinetis Design Studio (KDS) and for stock Eclipse Kepler with the GNU GCC ARM Embedded (launchpad) toolchain and libraries. The principles are the same, just the details are different ;-).

Tools Used

I’m using here the following tools:

Printf() with GNU Libraries

By default, the GNU newlib and newlib-nano libraries in KDS have semihosting included (at least in V1.0.1 beta which has been released in May 2014). So when I do a printf() or one of its family members, it tries to communicate with the debugger. In order to use my communication channel for input and output, I need to overwrite the following methods of the GNU library:

int _write (int fd, const void *buf, size_t count);
int _read (int fd, const void *buf, size_t count);

So if I use printf() in my code, it actually will end up at the _write() function to write the characters. In a similar way, if I call scanf(), it will use _read() to get the data from the input stream.

The interface to both functions is rather simple: the return value tells the caller how many bytes have been written or read. The buf parameter points to a buffer for the data, and count indicates the buffer size.

The concept is both the same for KDS and stock Eclipse with GNU ARM Embedded, but there are enough important details between the two tool chains to describe it separately.

Kinetis Design Studio

Both _read() and _write() functions are already implemented in the library supplied by KDS for semihosting, and they are marked as ‘weak’ functions. That means I can overwrite them with my own application specific functions.

The easiest way to use printf() with KDS is for a Processor Expert project. In that case I simply add the ConsoleIO component to it. And if you do not want to use Processor Expert, you simply can use or copy-paste that code anyway.

❗ Note that things are very different for Kinetis SDK projects. SDK projects is not a topic covered here.

KDS Project with ConsoleIO component

KDS Project with ConsoleIO component

ConsoleIO Settings

With the ConsoleIO component added, I can configure in the component

  1. If it shall wait until a character is received or not.
  2. How input (Rx) and output (Tx) new line character sequences are handled.
ConsoleIO Configuration

ConsoleIO Configuration

The settings for Rx and Tx new line is important to match the settings used in the host terminal program. With the wait setting I specify that the _read() method will block until a newline is detected.

❗ It is important to note that the ConsoleIO implements the interface to the UART in a polling (non-interrupt) mode.

UART Settings

In the Serial_LDD sub-component I configure the hardware settings: which UART to use, the baud rate and the pin settings. As said before, interrupts are not used. The screenshot below shows the setting for the FRDM-KL25Z board:

UART Configuration

UART Configuration

_read() and _write() Methods

The ConsoleIO component does not show any methods listed, as the _read() and _write() methods are internally used by printf() and scanf(). But I can inspect the code generated with a context menu:

Inspecting Generated Code

Inspecting Generated Code

And actually that generated code has two bugs (at least with Kinetis Design Studio V1.0.1beta): both functions should return the number of characters read or written.

For the _read() function see my fix at the return statement at the end of the function:

int _read (int fd, const void *buf, size_t count)
{
  size_t CharCnt = 0x00;

  (void)fd;                            /* Parameter is not used, suppress unused argument warning */
  for (;count > 0x00; --count) {
    if ((UART0_PDD_ReadInterruptStatusReg(UART0_BASE_PTR) & UART0_S1_RDRF_MASK) == 0x00) { /* Any data in receiver buffer */
      /* Clear error flags */
      UART0_PDD_ClearInterruptFlags(UART0_BASE_PTR,0x1FU);
      if (CharCnt != 0x00) {           /* No, at least one char received? */
        break;                         /* Yes, return received char(s) */
      } else {                         /* Wait until a char is received */
        while ((UART0_PDD_ReadInterruptStatusReg(UART0_BASE_PTR) & UART0_S1_RDRF_MASK) == 0x00) {};
      }
    }
    CharCnt++;                         /* Increase char counter */
    /* Save character received by UARTx device into the receive buffer */
    *(uint8_t*)buf = (unsigned char)UART0_PDD_GetChar8(UART0_BASE_PTR);
    /* Stop reading if CR (Ox0D) character is received */
    if (*(uint8_t*)buf == 0x0DU) {     /* New line character (CR) received ? */
      *(uint8_t*)buf = '\n';           /* Yes, convert LF to '\n' char. */
      break;                           /* Stop loop and return received char(s) */
    }
    (uint8_t*)buf++;                   /* Increase buffer pointer */
  }
  //return 1; /* WRONG! */
  return CharCnt;
}

In a similar way, the _read() function needs to be patched:

int _write (int fd, const void *buf, size_t count)
{
  size_t CharCnt = 0x00;

  (void)fd;                            /* Parameter is not used, suppress unused argument warning */
  for (;count > 0x00; --count) {
    /* Wait until UART is ready for saving a next character into the transmit buffer */
    while ((UART0_PDD_ReadInterruptStatusReg(UART0_BASE_PTR) & UART0_S1_TDRE_MASK) == 0) {};
    if (*(uint8_t*)buf == '\n') {
      /* Send '\r'(0x0D) before each '\n'(0x0A). */
      /* Save a character into the transmit buffer of the UART0 device */
      UART0_PDD_PutChar8(UART0_BASE_PTR, 0x0DU);
      /* Wait until UART is ready for saving a next character into the transmit buffer */
      while ((UART0_PDD_ReadInterruptStatusReg(UART0_BASE_PTR) & UART0_S1_TDRE_MASK) == 0) {};
    }
    /* Save a character into the transmit buffer of the UART0 device */
    UART0_PDD_PutChar8(UART0_BASE_PTR, (unsigned char)*(uint8_t*)buf);
    (uint8_t*)buf++;                   /* Increase buffer pointer */
    CharCnt++;                         /* Increase char counter */
  }
  //return count; /* WRONG! */
  return CharCnt; /* CORRECT */
}

But my change will be overwritten by the next code generation. So to keep my patch, I need to disable code generation with the context menu:

Inspecting Generated Code

Inspecting Generated Code

Disabled Code Generation

Disabled Code Generation

💡 Once this is fixed in the ConsoleIO component provided by Freescale, of course that patch is not necessary any more.

Test Program

To test the functionality, I use this small program which writes and reads from the console:

#include "Application.h"
#include 

void APP_Run(void) {
  int value;
  char buffer[64];

  for(;;) {
    printf("Hello world!\r\n");
    printf("Please enter a name:\n\r");
    scanf("%s", buffer);
    printf("  I have received: '%s'\r\n", buffer);

    printf("Please enter a number:\r\n");
    scanf("%i", &value);
    printf("  I have received: '%i'\r\n", value);
  }
}

Code and Data Size

As already mentioned in “Why I don’t like printf()“, there is a price to pay. In KDS V1.0.0beta, this means the above simple program needs 40 KByte Flash!

   text       data        bss        dec        hex    filename
  39340       2284       1608      43232       a8e0    FRDM-KL25Z_printf.elf

Looking at the linker options, it is using the newlib library, and not the smaller and more efficient newlib-nano. Adding -nanolibc to the linker options is the solution:

-nanolibc added to linker options

-nanolibc added to linker options

And this indeed helps: the code size is down to below 10 KByte:

   text       data        bss        dec        hex    filename
   9884        704       1304      11892       2e74    FRDM-KL25Z_printf.elf

There is one big problem with the printf() and scanf() family of function: they typically use a lot of stack. I recommend to have at least 1 KByte of RAM as stack if using these functions. If you see weird behaviour, try to increase your stack size and see if this helps.

 Eclipse Kepler with GNU ARM Embedded

In the Processor Expert Driver Suite 10.4, there is no ConsoleIO component (not sure why?). But this is not a big deal as I can do the same thing as in KDS:

  1. Adding the Serial_LDD component and configure it (settings shownforFRDM-KL25Z):

    Serial_LDD Configuration

    Serial_LDD Configuration

  2. Adding ConsoleIO.c (file name not matters) with the _read() and _write() functions copied from the KDS version.
#include "IO_Map.h"
#include "stdio.h"
#include
#include "UART0_PDD.h"

/*
** ===================================================================
**     Method      :  CsIO1__read (component ConsoleIO)
**
**     Description :
**         _read
**         This method is internal. It is used by Processor Expert only.
** ===================================================================
*/
int _read (int fd, const void *buf, size_t count)
{
  size_t CharCnt = 0x00;

  (void)fd;                            /* Parameter is not used, suppress unused argument warning */
  for (;count > 0x00; --count) {
    if ((UART0_PDD_ReadInterruptStatusReg(UART0_BASE_PTR) & UART0_S1_RDRF_MASK) == 0x00) { /* Any data in receiver buffer */
      /* Clear error flags */
      UART0_PDD_ClearInterruptFlags(UART0_BASE_PTR,0x1FU);
      if (CharCnt != 0x00) {           /* No, at least one char received? */
        break;                         /* Yes, return received char(s) */
      } else {                         /* Wait until a char is received */
        while ((UART0_PDD_ReadInterruptStatusReg(UART0_BASE_PTR) & UART0_S1_RDRF_MASK) == 0x00) {};
      }
    }
    CharCnt++;                         /* Increase char counter */
    /* Save character received by UARTx device into the receive buffer */
    *(uint8_t*)buf = (unsigned char)UART0_PDD_GetChar8(UART0_BASE_PTR);
    /* Stop reading if CR (Ox0D) character is received */
    if (*(uint8_t*)buf == 0x0DU) {     /* New line character (CR) received ? */
      *(uint8_t*)buf = '\n';           /* Yes, convert LF to '\n' char. */
      break;                           /* Stop loop and return received char(s) */
    }
    (uint8_t*)buf++;                   /* Increase buffer pointer */
  }
  //return 1; /* WRONG! */
  return CharCnt;
}

/*
** ===================================================================
**     Method      :  CsIO1__write (component ConsoleIO)
**
**     Description :
**         _write
**         This method is internal. It is used by Processor Expert only.
** ===================================================================
*/
int _write (int fd, const void *buf, size_t count)
{
  size_t CharCnt = 0x00;

  (void)fd;                            /* Parameter is not used, suppress unused argument warning */
  for (;count > 0x00; --count) {
    /* Wait until UART is ready for saving a next character into the transmit buffer */
    while ((UART0_PDD_ReadInterruptStatusReg(UART0_BASE_PTR) & UART0_S1_TDRE_MASK) == 0) {};
    if (*(uint8_t*)buf == '\n') {
      /* Send '\r'(0x0D) before each '\n'(0x0A). */
      /* Save a character into the transmit buffer of the UART0 device */
      UART0_PDD_PutChar8(UART0_BASE_PTR, 0x0DU);
      /* Wait until UART is ready for saving a next character into the transmit buffer */
      while ((UART0_PDD_ReadInterruptStatusReg(UART0_BASE_PTR) & UART0_S1_TDRE_MASK) == 0) {};
    }
    /* Save a character into the transmit buffer of the UART0 device */
    UART0_PDD_PutChar8(UART0_BASE_PTR, (unsigned char)*(uint8_t*)buf);
    (uint8_t*)buf++;                   /* Increase buffer pointer */
    CharCnt++;                         /* Increase char counter */
  }
  //return count; /* WRONG! */
  return CharCnt; /* CORRECT */
}

Code and Data Size

Checking the code size with the linker option -specs=nosys.specs gives this:

   text       data        bss        dec        hex    filename
  54188       2240       1152      57580       e0ec    FRDM-KL25Z_Printf.elf

which is even higher than with newlib in KDS. Using the newlib-nano (-specs=nano.specs -specs=nosys.specs) gives

   text       data        bss        dec        hex    filename
  10256        128       1108      11492       2ce4    FRDM-KL25Z_Printf.elf

the code size is still a bit higher than in KDS. But the interesting part is that the GNU ARM Embedded library needs much less RAM. And RAM is very much the limiting factor of the application.

Newlib-nano and Floating Point

To use printf() and scanf() in the newlib-nano, more options are needed:

To use printf() with floating point, the following option needs to be added to the linker options:

-u _printf_float

To use scanf() with floating point, a similar option is needed:

-u _scanf_float

Do not add these options, as it adds roughly 15 KByte of code (yikes!).

The other point is: to use printf() with float values, it requires some stack and heap space: so be sure you have enough stack and heap defined. Plus make sure that _start() is called from the startup code to initialize the library functionality.

Summary

To use printf() and scanf() with a physical serial port, I need to implement a _read() and _write() function implementation on the application side. This is easier with KDS and a Processor Expert component for this, but not hard to do in Eclipse Kepler. While the KDS code size is smaller than the GNU ARM Embedded version, KDS needs more RAM. Knowing about _read() and _write() functions means I can do anything with it: instead writing to a UART, I could use it for sending/receiving text over USB, using Ethernet, using a LCD or sending stuff to a printer: it is so flexible that it be used with pretty much anything :-).

Happy Printing 🙂

Advertisements

8 thoughts on “printf() and scanf() with GNU ARM Libraries

  1. Pingback: Switching ARM GNU Tool Chain and Libraries in Kinetis Design Studio | MCU on Eclipse

  2. Pingback: printf() for the FRDM-K64F Board and Kinetis Design Studio | MCU on Eclipse

  3. hello Erich,
    I create a project with frdm-ke04z use KDS1.1.1 , it appears ” #warning Because RAM size of this device is not enough to fit in the heap requirements of newlib/newlib-nano, startup code is by-passed!” , and then i only write “printf(“hello\n”);” it errors .

    About the warning ,what should i do ?
    Thank you !
    Alice

    Like

  4. Pingback: Comparing CodeWarrior with Kinetis Design Studio | MCU on Eclipse

  5. Hi Erich. I am trying to get working with a FRDM22F and want to output to the console. The issues is we do not want to use the KSDK. I have set up a project using the K22F board, not using the KSDF, PE and changed the heap size. The project runs but nothing shows up in the console. When setting up the console IO I had to use UART1 not UART0 because I am using the GDB PEMicro for debugging.

    Any idea what I am doing wrong?. Is there a better way to get data to the console?

    Like

    • The simplest way is to use PEx and using the UART through the K20/OpenSDA/USB CDC to the host, without printf(). Or use my Shell component which offers functions to print strings and numbers?

      Like

      • Thanks. I found my issue. I was looking at PE and notice the timing was set for the ADC but there was a warning about the ADC clock. The PE project generated and run but communications did not work. Changed timing the warning went away and communications worked.

        Looking at the system timing it was obvious that the communications was polling interrupt driven which is very surprising for me.. I looked at IO1:Serial_LLD and see where I can turn interrupts on. Is there an issue with using the interrupt driven IO? I will be trying interrupts shortly.
        Thanks for all the support you offer.

        Like

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s