How printf() Works with UART Peripheral Driver Functions

Today, I will share with you the hardware UART peripheral debugging output mechanism under 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 of printing information output under the ARM Cortex-M core MCU using the IAR environment.

This is the first article, and we will first introduce the most common method of printing output information, which is to use the hardware UART peripheral within the MCU chip. This article does not aim to specifically introduce the usage of the UART peripheral module, but rather to analyze how the printf() function defined in the C standard header file stdio.h connects with the underlying driver functions of the UART peripheral under IAR.

  • Note: The IAR EWARM software version 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 of the print output method. The hardware mainly consists of a PC host, MCU target board, and a connecting wire (there are two types of connecting wires: one is an RS232 serial cable, 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 then the target board MCU application program needs to include the relevant code for print output. Once the MCU program runs, the UART peripheral inside the chip drives the physical transmission of printed character data (hello world) so that the print information can be seen in the serial debugging assistant software on the PC.

How printf() Works with UART Peripheral Driver Functions

The MCU application program shown in the figure is compiled and linked in the IAR environment. Therefore, our focus is on how printf() in the stdio.h header file connects with the UART peripheral driver function under IAR.

2. C Standard Header File stdio.h

Friends familiar with embedded engineering should know that the stdio.h header file is not located in the user project folder, and there is no need for us to manually add this file to the project directory. This file is a standard header defined by the C standard, provided automatically by the toolchain.

stdio.h is the standard library header file provided by the C language for input and output. Its predecessor was the “Portable I/O 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 conducted through associated input or output streams.

  • stdio.h prototype: https://cplusplus.com/reference/cstdio/

Most people learn C language in the Visual Studio/C++ environment, where the underlying implementation of the functions defined in stdio.h is directly handled by Visual Studio software, and we usually do not need to care about the 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 the files from the IAR software directory into the project 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 such as printf() defined in the stdio.h file under IAR, as it needs to be compiled and linked to generate specific machine code for downloading to the MCU for execution. But where is the prototype of the printf() function? Let’s keep this a suspense for now.

3. UART Peripheral Driver Functions

Speaking of UART peripheral driver functions, everyone should be very 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 contains functions like LPUART_WriteBlocking() and LPUART_ReadBlocking() that can complete the sending and receiving of user data packets. In fact, using the LPUART_WriteBlocking() function alone 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

It is clear that IAR supports the C standard I/O library; otherwise, we would not 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 is compiled and linked, which lists 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 us to care about its implementation). We only need the following print statement:

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

Then pay attention to the following three settings related to the Library implementation in the project options. 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 function, 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, meaning the user will implement it.

How printf() Works with UART Peripheral Driver Functions

After configuring the Library, compiling the project will result in the following error. Based on this error, we can guess 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 requires the user 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 project Library settings for Normal and Full options actually determine whether to link dl7M_tln.a or dl7M_tlf.a into the user project.

How printf() Works with UART Peripheral Driver Functions

4.2 DLIB Low-level I/O Interface Design

We found the __write() function, but what is its prototype? How should we implement it? At this point, we need to refer to the comprehensive manual located at \IAR Systems\Embedded Workbench 9.10.2\arm\doc\EWARM_DevelopmentGuide.ENU. Searching for the term __write in it reveals the following design: the C standard I/O interfaces we call in the code are implemented by the precompiled DLIB provided by IAR, which leaves the lowest-level hardware-related interface functions for users to implement.

How printf() Works with UART Peripheral Driver Functions

IAR has created template source files for those lowest-level I/O interface functions in DLIB. In these template files, we can find their prototypes, so 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 printf() Works with UART Peripheral Driver Functions

4.3 DLIB Library I/O Related Source Code Implementation

With the __write() prototype and example code, we can easily implement it using the LPUART_WriteBlocking() function. Adding this code to the hello_world project for compilation will not produce errors (of course, to truly test the print function on the board, we also need to add 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, check the generated hello_world.map file to find information about the dl7M_tln.a section. You can see that it consists of many .o files (with rich functionalities), and the source code for these .o files can be found in the IAR installation directory.

*******************************************************************************
*** 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. If you are interested, you can check its specific implementation. Here, I would like to specifically mention that the formatter folder contains many different source file implementations for xprintf, which correspond to the different configurations in the Printf formatter option in the project settings.

\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 printf() Works with UART Peripheral Driver Functions
Thus, the hardware UART peripheral debugging output mechanism under IAR has been introduced.

Source: Pi Zi Heng Embedded

Warm Reminder:

Due to the recent changes in the WeChat public platform push rules, many readers have reported that they did not see the updated articles in a timely manner. According to the latest rules, it is recommended to frequently click on “Recommended Reading, Share, Collect”, etc., to become regular readers.

Recommended Reading:

  • Qualcomm Delivers “Peace of Mind”: Continues to Supply Huawei!

  • Bill Gates’ Latest Prediction: ChatGPT Will Bring These Changes

  • Embarrassing! Google’s ChatGPT Launch Flops, Losing 717.2 Billion Overnight

Please click 【View】 to give the editor a thumbs up

How printf() Works with UART Peripheral Driver Functions

Leave a Comment