How Does printf() Integrate with UART Driver Functions?

This is definitely a good article. Printing with this function is sometimes more useful than any debugging tool, and the kernel’s prink is a true gem. But how does the printf function connect with the UART driver? This article will answer that for you. Like, share, and bookmark it for when you need it.

Today, I am sharing about The Debug Output Mechanism of Hardware UART in IAR.

In the embedded world, outputting print information is a very common auxiliary debugging method. With the help of print information, we can easily locate and analyze program issues. There are many ways to implement print information output in embedded application design, and this series will introduce the methods step by step using the IAR environment as an example for ARM Cortex-M core MCUs.

This is the first article, and we will first introduce the most common method of outputting print information, which is to utilize the hardware UART peripheral within the MCU chip. This article is not specifically about how to use the UART peripheral module, but rather focuses on analyzing how printf(), defined in the C standard header file stdio.h, connects with the underlying driver functions of the UART peripheral in the IAR environment.

  • Note: The version of IAR EWARM software used in this article is v9.10.2.

1. Overall Block Diagram of Print Output

First, let’s introduce the overall software and hardware block diagram for the print output method. On the hardware side, it mainly consists of a PC host, an MCU target board, and a connecting line (there are two connection schemes: one is an RS232 serial line, and the other is a TTL serial to USB module board).

On the software side, the PC needs to install a serial debugging assistant software, and the MCU application program on the target board needs to include the relevant code for print output. Once the MCU program is running, the UART peripheral inside the chip drives the physical transmission of print character data (hello world.) and the print information can be seen in the serial debugging assistant software on the PC.

How Does printf() Integrate with UART Driver Functions?

The MCU application program in the above diagram is compiled and linked in the IAR environment, so our focus is on how printf() in the stdio.h header file connects with the UART driver functions in IAR.

2. C Standard Header File stdio.h

Friends familiar with embedded engineering should know that the stdio.h header file is not in the user project folder, and we do not need to manually add this file to the project directory; it is a standard header file defined by C and is automatically provided by the toolchain.

stdio.h is the standard library header file provided by C for input and output. Its predecessor was the “Portable Input Output Library” written by Mike Lesk in the 1970s. All input and output in C language is accomplished through abstract byte streams, and access to files is also done through associated input or output streams.

  • Prototype of stdio.h: https://cplusplus.com/reference/cstdio/

Most people learn C language generally in the Visual Studio / C++ environment, where the underlying implementation of the functions defined in stdio.h is directly handled by the Visual Studio software, and we usually do not need to care about its implementation details.

In the embedded IAR environment, most of these standard C defined header files are also supported. We can find them in the following IAR software directory. When we add #include <stdio.h> and other statements in the project code, we are actually adding files from the IAR software directory into the project for compilation.

\IAR Systems\Embedded Workbench 9.10.2\arm\inc\c\stdio.h

However, we need to pay attention to the specific implementation of functions like printf() defined in the stdio.h file under the IAR directory, as it is necessary to compile and link to generate the specific machine code to be downloaded into the MCU for execution. But where is the prototype of the printf() function? Let’s leave that as a suspense for now.

3. UART Peripheral Driver Functions

Speaking of UART peripheral driver functions, everyone should be quite familiar with them. We will take the NXP i.MXRT1060 model (ARM Cortex-M7 core) as an example to introduce it specifically. In its official SDK package, there are corresponding LPUART driver files:

\SDK_2.11.0_EVK-MIMXRT1060\devices\MIMXRT1062\drivers\fsl_lpuart.h
\SDK_2.11.0_EVK-MIMXRT1060\devices\MIMXRT1062\drivers\fsl_lpuart.c

The LPUART driver library includes functions like LPUART_WriteBlocking() and LPUART_ReadBlocking() that can send and receive user data packets. In fact, using just the LPUART_WriteBlocking() function can also achieve print information output, but it does not have the powerful formatting output capabilities of the printf() function.

status_t LPUART_Init(LPUART_Type *base, const lpuart_config_t *config, uint32_t srcClock_Hz)
status_t LPUART_WriteBlocking(LPUART_Type *base, const uint8_t *data, size_t length)
status_t LPUART_ReadBlocking(LPUART_Type *base, uint8_t *data, size_t length)

4. IAR’s Support for C Standard I/O Library

IAR clearly supports the C standard I/O library; otherwise, we wouldn’t be able to use the printf() function in the project. But how can we easily discover this support? Today, I will teach you a method: look at the .map file generated after the project compiles and links. This map file will list all the functions in the project.

4.1 Introducing the Low-Level Interface __write()

We will take the project located at \SDK_2.11.0_EVK-MIMXRT1060\boards\evkmimxrt1060\demo_apps\hello_world\iar as an example. We need to make a simple modification to the main() function in the hello_world.c file by deleting all the original code (the original print output involves the encapsulation of the NXP SDK, which is unnecessary for this article). We only need the following line of print:

#include &lt;stdio.h&gt;
int main(void)
{
    printf("hello world.\r\n");
    while (1);
}

Then pay attention to the following three settings in the project options related to Library implementation. The Library option configures the functionality of the runtime lib, with Normal and Full options (select as needed); the Printf formatter option determines the details of the formatting output functionality, with Full, Large, Small, and Tiny options (select as needed).

The Library low-level interface implementation option determines the low-level I/O implementation; here we select None, which means the user will implement it.

How Does printf() Integrate with UART Driver Functions?

After configuring the Library, compiling the project will result in the following error. Based on this error, we can infer that dl7M_tln.a is the C/C++ library compiled by IAR, which implements the printf() function and its dependent putchar() function. The putchar() function provides the low-level I/O interface function, which is called __write(). This is the sending function that users need to implement in conjunction with the chip’s UART peripheral.

 Error[Li005]: no definition for "__write" [referenced from putchar.o(dl7M_tln.a)]

In the IAR directory, we can find the path to the dl7M_tln.a file. After testing, the Library settings Normal and Full actually correspond to whether dl7M_tln.a or dl7M_tlf.a is linked into the user project.

How Does printf() Integrate with UART Driver Functions?

4.2 DLIB Low-Level I/O Interface Design

We found the __write() function, but what is its prototype? How do we implement it? At this point, we need to check the all-powerful \IAR Systems\Embedded Workbench 9.10.2\arm\doc\EWARM_DevelopmentGuide.ENU manual. By searching for __write in it, we can find the following design. It turns out that all the C standard I/O interfaces we call in the code are implemented by the pre-compiled DLIB provided by IAR, and this DLIB library also leaves the lowest level hardware-related interface functions for users to implement.

How Does printf() Integrate with UART Driver Functions?

IAR has created template source files for the lowest level I/O interface functions in DLIB, where we can find their prototypes. Thus, we found the prototype of __write() and its example implementation in the write.c file.

size_t __write(int handle, const unsigned char * buffer, size_t size)
How Does printf() Integrate with UART Driver Functions?

4.3 DLIB Library I/O Related Source Code Implementation

With the prototype and example code of __write(), we can easily implement it using the LPUART_WriteBlocking() function. We can add this code to the hello_world project for compilation, and this time there will be no errors (of course, to actually test the print functionality on the board, we still need to add the LPUART initialization code in the main function).

#include "fsl_lpuart.h"
size_t __write(int handle, const unsigned char *buf, size_t size)
{
    // Assume using LPUART1 for output
    (void)LPUART_WriteBlocking(LPUART1, buf, size);

    return 0;
}

After the project compiles successfully, we can check the generated hello_world.map file to find information about the dl7M_tln.a section, which shows that it consists of many .o files (with rich functionality), and these .o files can be found in the IAR installation directory for their source code.

*******************************************************************************
*** MODULE SUMMARY
***

    Module                ro code  ro data  rw data
    ------                -------  -------  -------
dl7M_tln.a: [10]
    abort.o                     6
    exit.o                      4
    low_level_init.o            4
    printf.o                   40
    putchar.o                  32
    xfail_s.o                  64                 4
    xprintfsmall_nomb.o     1'281
    xprout.o                   22
    -----------------------------------------------
    Total:                  1'453                 4

The source code related to I/O in the DLIB library is located in the following directory, and those interested can check its specific implementation. Here, it is particularly noted that there are many different source file implementations under the formatter folder, which actually correspond to the different configurations in the project option Printf formatter.

\IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\dlib\file
\IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\dlib\formatters
How Does printf() Integrate with UART Driver Functions?

Thus, the debugging output mechanism of hardware UART in IAR has been introduced.

How Does printf() Integrate with UART Driver Functions?

END

Source: Pi Zi Heng Embedded
Copyright belongs to the original author. If there is any infringement, please contact for deletion.

How Does printf() Integrate with UART Driver Functions?

How Does printf() Integrate with UART Driver Functions?

Leave a Comment