Understanding RTT UART Device Driver Framework

Click on the above “Embedded Miscellaneous“, select “Pin to Top” to view programming notes at the first opportunity!

Introduction to UART

The STM32 chip has multiple USART peripherals for serial communication, which stands for Universal Synchronous Asynchronous Receiver and Transmitter. It can flexibly perform full-duplex data exchange with external devices.

Unlike USART, it also has a UART peripheral (Universal Asynchronous Receiver and Transmitter), which is a stripped-down version of USART that only supports asynchronous communication.

A simple way to differentiate between synchronous and asynchronous communication is to check whether a clock output is provided externally during communication. The serial communication we commonly use is basically UART.

As an asynchronous serial communication protocol, UART works by transmitting each character of the data one bit at a time.

The data transmission format is as follows:

Understanding RTT UART Device Driver Framework

UART Device Framework Study Notes

RT-Thread provides a simple I/O device model framework that lies between the hardware and the application, divided into three layers from top to bottom: I/O device management layer, device driver framework layer, and device driver layer:

Understanding RTT UART Device Driver Framework

The application program accesses the serial device interface:

Understanding RTT UART Device Driver Framework

Now let’s look at an example:

Using two serial ports (uart1 and uart3), uart1 serves as the system’s debugging serial port for printing some log information, while uart3 is the test serial port for this experiment, implementing send and receive tests with the serial debugging assistant.

After the uart3 device starts, it sends the string I am uart3 to the serial debugging assistant. Meanwhile, uart3 receives data using interrupts and outputs the data in a staggered manner; for instance, if it receives the ASCII character A, it will reply with B.

#define SAMPLE_UART_NAME    "uart3"          /* Serial device name           */
/* uart3 application function */
static int uart3_app(void)
{
    rt_err_t ret = RT_EOK;                      /* Function return value        */
    rt_thread_t tid;                            /* Dynamic thread handle      */
    char uart3_name[RT_NAME_MAX];               /* Save the device name for lookup  */
    char usart3_tx_str[] = "I am uart3.\r\n";   /* String sent by uart3 */

    rt_strncpy(uart3_name, SAMPLE_UART_NAME, RT_NAME_MAX);
    
    /* Find serial device */
    uart3_dev = rt_device_find(uart3_name);
    if (!uart3_dev)
    {
        rt_kprintf("find %s failed!\n", uart3_name);
        return RT_ERROR;
    }
    /* Initialize semaphore */
    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
    /* Open serial device in read/write and interrupt receive mode */
    rt_device_open(uart3_dev, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
    /* Set receive callback function */
    rt_device_set_rx_indicate(uart3_dev, uart3_rx_callback);
    /* Send string */
    rt_device_write(uart3_dev, 0, usart3_tx_str, (sizeof(usart3_tx_str) - 1));
    
    /* Create dynamic thread: priority 25, time slice 5 system ticks, thread stack 512 bytes */
    tid = rt_thread_create("uart3_rx_thread",
			static_uart3_rx_entry,
			RT_NULL,
			STACK_SIZE,
			THREAD_PRIORITY,
			TIMESLICE);
    /* Start dynamic thread if created successfully */
    if (tid != RT_NULL)
    {
	rt_thread_startup(tid);
    }
    return ret;
}

Our application program first looks up the device by the serial device name uart3, and after finding the device, it returns the serial device handle uart3_dev.

Why can the application program find the serial device named uart3? This is because our hardware driver layer has already registered the serial device named uart3 in the system:

Understanding RTT UART Device Driver Framework

Understanding RTT UART Device Driver Framework

The serial device registration function will call the generic device registration function:

Understanding RTT UART Device Driver Framework

This process involves the following functions:

  • uart3_app function: defined in main.c.
  • rt_hw_usart_init function: defined in drv_usart.c.
  • rt_hw_serial_register function: defined in serial.c.
  • rt_device_register function: defined in device.c.
  • rt_device_find function: defined in device.c.

In the RT-Thread driver framework block diagram above, it is divided into several layers, with the corresponding relationships as follows:

Understanding RTT UART Device Driver Framework

Here, the main.c file belongs to the application layer, and our application program is:

Understanding RTT UART Device Driver Framework

The drv_usart.c file belongs to the hardware device driver layer, which is provided by RT-Thread and is part of the board support package:

Understanding RTT UART Device Driver Framework

This layer is hardware-related and calls the underlying chip firmware library, such as:

Understanding RTT UART Device Driver Framework

The serial.c file belongs to the driver framework (driver abstraction layer), which is a component of the RT-Thread system:

Understanding RTT UART Device Driver Framework

Its position in the RT-Thread source code is as follows:

Understanding RTT UART Device Driver Framework

The device.c file provides the application program with interfaces for operating devices, and this file belongs to the RT-Thread kernel files. The RT-Thread kernel is designed with an object-oriented design philosophy, where devices belong to a category of objects. The inheritance relationship is as follows:

Understanding RTT UART Device Driver Framework

In this application program, we use semaphores (other synchronization mechanisms can also be used, such as events). Semaphores belong to one of the IPC mechanisms:

Understanding RTT UART Device Driver Framework

Semaphores are used for synchronization between threads and interrupts, and in our experiment, it is the synchronization between interrupts and threads.

In bare metal development, there is a scenario (interrupt receiving data, handling data in the main function):

In the serial reception interrupt function, data is received, and a global variable is used as an interrupt reception flag. When an interrupt is triggered, this flag variable is set; on the other hand, in the while loop of our main function, we check whether this flag has been set. If set, we perform the corresponding operation and reset the flag variable.

In the RT-Thread system, its IPC mechanism does similar things to what the flag variable does in the bare metal development (interrupt and thread synchronization). For example, in our experiment, if the receive interrupt of uart3 is triggered, it will trigger a callback function, such as:

Understanding RTT UART Device Driver Framework

In the callback function, the semaphore release operation is performed, and the thread blocks waiting for the reception semaphore:

Understanding RTT UART Device Driver Framework

After receiving the data, it sends the data in a staggered manner. Finally, our experimental results are as follows:

Understanding RTT UART Device Driver Framework

That concludes this note sharing. If there are any errors, please feel free to point them out. Thank you!

Conclusion

If you find the article good, sharing and liking is also our motivation to keep updating.

You May Also Like:

Using ARM Cortex-M Series MCU Error Code Auto-Trace Library

Experience with RT-Thread Studio: Truly Impressive!

[RT-Thread Notes] Partition Management for FLASH

[RT-Thread Notes] Critical Section Issues and IPC Mechanisms

Do You Really Know Serial Communication? Check Out These Experiences

Reply 【1024】 in the WeChat public account chat interface to get a free miscellaneous data package, including but not limited to: C/C++, Python, Linux, microcontrollers, FPGA, etc.

Understanding RTT UART Device Driver Framework

Leave a Comment