RT thread transplantation BSP driven uart

Posted by hasanpor on Wed, 09 Feb 2022 18:44:52 +0100

RT thread transplantation BSP driven uart

brief introduction

As one of the commonly used peripherals, UART driver is essential for the use of msh components of RT thread and some external UART modules. This article will introduce how to write DRV based on serial framework of RT thread_ uart. C and drv_uart.h file, until the last step to achieve the use of msh components.

Before reading this article, make sure you are familiar with it Use of RT thread And running RT thread on an stm32 or other development board transplanted with BSP, and you are already familiar with it UART How to use the device driven framework.

As a commonly used peripheral, uart usually uses only two wires: tx rx; When in use, tx of the master device is connected to rx of the slave device, and rx of the master device is connected to tx of the slave device. Generally, it is used as asynchronous mode, that is, tx and rx are irrelevant to each other. If you want to do half duplex communication, you can only connect tx or rx. Of course, uart has many extended communication modes, such as hardware flow control and clock synchronization, but they are not used in general, and will not be discussed here.

transplant

Prepare template file

First, prepare a template file, drv_uart.c and drv_uart.h. There are only function bodies in these two files, and there is no function implementation. Adding these two files to the project can generally be compiled.

drv_uart.c

/*
 * Copyright (C) 2020, Huada Semiconductor Co., Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-05-18     xph          first version
 */

/*******************************************************************************
 * Include files
 ******************************************************************************/
#include <rtdevice.h>
#include <rthw.h>

#include "drv_usart.h"
#include "board_config.h"

#ifdef RT_USING_SERIAL

#if !defined(BSP_USING_UART1) && !defined(BSP_USING_UART2) && !defined(BSP_USING_UART3) && \
    !defined(BSP_USING_UART4) && !defined(BSP_USING_UART5) && !defined(BSP_USING_UART6) && \
    !defined(BSP_USING_UART7) && !defined(BSP_USING_UART8) && !defined(BSP_USING_UART9) && \
    !defined(BSP_USING_UART10)
#error "Please define at least one BSP_USING_UARTx"
/* UART instance can be selected at menuconfig -> Hardware Drivers Config -> On-chip Peripheral Drivers -> Enable UART */
#endif



static rt_err_t hc32_configure(struct rt_serial_device *serial,
                                struct serial_configure *cfg)
{


    return RT_EOK;
}

static rt_err_t hc32_control(struct rt_serial_device *serial, int cmd, void *arg)
{
    struct hc32_uart *uart;
 

    return RT_EOK;
}

static int hc32_putc(struct rt_serial_device *serial, char c)
{


    return 1;
}

static int hc32_getc(struct rt_serial_device *serial)
{
    int ch= -1;
    return ch;
}



static const struct rt_uart_ops hc32_uart_ops =
{
    .configure = hc32_configure,
    .control = hc32_control,
    .putc = hc32_putc,
    .getc = hc32_getc,
    .dma_transmit = RT_NULL
};

int hc32_hw_uart_init(void)
{
    rt_err_t result = RT_EOK;

    return result;
}

INIT_BOARD_EXPORT(hc32_hw_uart_init);

#endif /* RT_USING_SERIAL */

/*******************************************************************************
 * EOF (not truncated)
 ******************************************************************************/

drv_uart.h

/*
 * Copyright (C) 2020, Huada Semiconductor Co., Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-05-18     xph          first version
 */
 

#ifndef __DRV_USART_H__
#define __DRV_USART_H__

/*******************************************************************************
 * Include files
 ******************************************************************************/
#include <rtthread.h>
#include "rtdevice.h"

#include "uart.h"


/* C binding of definitions if building with C++ compiler */
#ifdef __cplusplus
extern "C"
{
#endif

/*******************************************************************************
 * Global type definitions ('typedef')
 ******************************************************************************/

/*******************************************************************************
 * Global pre-processor symbols/macros ('#define')
 ******************************************************************************/

/*******************************************************************************
 * Global variable definitions ('extern')
 ******************************************************************************/

/*******************************************************************************
 * Global function prototypes (definition in C source)
 ******************************************************************************/
int rt_hw_uart_init(void);

#ifdef __cplusplus
}
#endif

#endif /* __DRV_USART_H__ */

/*******************************************************************************
 * EOF (not truncated)
 ******************************************************************************/

Add these two files to your project, and then ensure that the compilation passes. If the compilation fails, delete everything that can be deleted, and just keep the function.

".\output\release\rt-thread.axf" - 0 Error(s), 0 Warning(s).

Add function body

Then the play comes. We need to implement the function body of each function separately. The implementation of these function bodies is related to the uart driver of your target MCU. The general idea is to transplant the uart driver of the target MCU. Here I will analyze my idea of transplantation.

According to the design idea of RT thread, we generally analyze the driver file from DRV_ xxx. The last line of C starts the analysis. Here it is

INIT_BOARD_EXPORT(hc32_hw_uart_init);

Then, find this function, which registers the uart device in the serial driver framework, mainly connecting the driver layer and device layer of the device. When the upper layer calls RT_ device_ When find, DRV will be executed_ xxx. C inside the hardware configuration function, function implementation, see the following code notes.

int hc32_hw_uart_init(void)
{
    rt_err_t result = RT_EOK;
    /* How many serial port devices are currently used in rtconfig BSP in H_ USING_ Uartx macro to determine how many serial ports are used*/
    rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct hc32_uart_t); 
	/* The default configuration parameters of serial port are baud rate, data bit, stop bit, parity bit, etc */
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
	/* Put these configuration parameters in the uart management result body, and register the uart device like the device layer */
    for (int i = 0; i < obj_num; i++)
    {
        uart_obj[i].config = &uart_config[i];
        uart_obj[i].serial.ops = &hc32_uart_ops; // The upper layer operation function will eventually enter the drv layer to operate the specific hardware, so the implementation of ops of drv is the focus of transplantation
        uart_obj[i].serial.config = config;

        /* register UART device */
        result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
                                       RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX, NULL);
        RT_ASSERT(result == RT_EOK);
    }
    return result;
}

Then we will implement various ops functions, which is the focus and difficulty of our transplantation. Each meaning of ops operation function is as follows:

static const struct rt_uart_ops hc32_uart_ops =
    {
        .configure = hc32_configure,   // Configuration and initialization functions 
        .control = hc32_control,	  // Reconfiguring the serial port operation function is commonly used to change the baud rate
        .putc = hc32_putc,		      // Serial port sends a byte
        .getc = hc32_getc,			 // The serial port receives a byte
        .dma_transmit = RT_NULL       // dma transmission can not be realized at the beginning
    };

Firstly, two important structures are introduced

struct hc32_uart_config
{
    const char *name;                 // Serial port name
    M0P_UART_TypeDef *Instance;       // Corresponding serial port hardware interface
    IRQn_Type irq_type;               // interrupt type

};

struct hc32_uart_t
{
    stc_uart_cfg_t stcCfg;                   // Related to the specific MCU, configure the structure
    struct hc32_uart_config *config;         // General configuration of serial port
    struct rt_serial_device serial;          // Necessary for docking uart device

};

Then implement the config function. The two input parameters of this function are the serial of the docking device driver framework. Through the serial, we can find which serial port is currently operating. The other is to configure cfg. The parameters in this cfg are the default initialization parameter RT when we register the serial device above_ SERIAL_ CONFIG_ DEFAULT. The specific implementation logic of the function is annotated in the function body.

static rt_err_t hc32_configure(struct rt_serial_device *serial,
                               struct serial_configure *cfg)
{
    struct hc32_uart_t *uart;
    RT_ASSERT(serial != RT_NULL);
    RT_ASSERT(cfg != RT_NULL);
    uart = rt_container_of(serial, struct hc32_uart_t, serial);   // Find uart through serial, and then operate the uart
    /* Here, the interface interface is used to determine which serial port implementation mode is related to each MCU. You can refer to simple in MCU SDK */
    if (uart->config->Instance == M0P_UART1) 
    {
        stc_gpio_cfg_t stcGpioCfg;
        DDL_ZERO_STRUCT(stcGpioCfg);
		/* Enable clock */
        Sysctrl_SetPeripheralGate(SysctrlPeripheralUart1, TRUE); ///< enable uart1 module clock
        Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);  //Enable GPIO module clock
		/* Initialize IO */
        ///<TX
        stcGpioCfg.enDir = GpioDirOut;
        Gpio_Init(GpioPortA, GpioPin2, &stcGpioCfg);
        Gpio_SetAfMode(GpioPortA, GpioPin2, GpioAf1); //Configure PA02 port as URART1_TX

        ///<RX
        stcGpioCfg.enDir = GpioDirIn;
        Gpio_Init(GpioPortA, GpioPin3, &stcGpioCfg);
        Gpio_SetAfMode(GpioPortA, GpioPin3, GpioAf1); //Configure PA03 port as URART1_RX
		/* The specific serial port parameter configuration varies from MCU manufacturer to manufacturer */
        uart->stcCfg.enRunMode = UartMskMode3; ///< mode 3
        uart->stcCfg.stcBaud.u32Baud = cfg->baud_rate;
        uart->stcCfg.stcBaud.enClkDiv = UartMsk8Or16Div;      ///< channel sampling frequency division configuration
        uart->stcCfg.stcBaud.u32Pclk = Sysctrl_GetPClkFreq(); ///< get peripheral clock (PCLK) frequency value

        switch (cfg->stop_bits)
        {
        case STOP_BITS_1:
            uart->stcCfg.enStopBit = UartMsk1bit;
            break;
        case STOP_BITS_2:
            uart->stcCfg.enStopBit = UartMsk2bit;
            break;
        default:
            uart->stcCfg.enStopBit = UartMsk1_5bit;
            break;
        }

        switch (cfg->parity)
        {
        case PARITY_NONE:
            uart->stcCfg.enMmdorCk = UartMskDataOrAddr;
            break;
        case PARITY_ODD:
            uart->stcCfg.enMmdorCk = UartMskOdd;
            break;
        case PARITY_EVEN:
            uart->stcCfg.enMmdorCk = UartMskEven;
            break;
        default:
            uart->stcCfg.enMmdorCk = UartMskDataOrAddr;
            break;
        }
    }
    Uart_Init(uart->config->Instance, &(uart->stcCfg)); ///< serial port initialization

    ///< UART interrupt enable
    Uart_ClrStatus(uart->config->Instance,UartRC);                ///< clear receive request
    Uart_EnableIrq(uart->config->Instance,UartRxIrq);             ///< enable serial port receive interrupt   
    EnableNvic(uart->config->irq_type, IrqLevel3, TRUE);       ///< system interrupt enable


    return RT_EOK;
}

Next, putc and getc are easy to implement and relatively simple

static int hc32_putc(struct rt_serial_device *serial, char c)
{
    struct hc32_uart_t *uart;
    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct hc32_uart_t, serial);
    Uart_SendDataPoll(uart->config->Instance, c);
    return 1;
}

static int hc32_getc(struct rt_serial_device *serial)
{
    int ch = -1;
    struct hc32_uart_t *uart;
    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct hc32_uart_t, serial);
    if (Uart_GetStatus(uart->config->Instance, UartRC)) //UART1 data receiving
    {
        Uart_ClrStatus(uart->config->Instance, UartRC);     // Clear interrupt status bit
        ch = Uart_ReceiveData(uart->config->Instance);      // Receive data bytes
    }
    return ch;
}

Finally, we analyze the interrupt reception of serial port.

According to the serial port interrupt receiving simple in MCU SDK, first find the interrupt service function and put it in drv_uart.c, as follows:

#if defined(BSP_USING_UART1) || defined(BSP_USING_UART3)
//UART1 interrupt function
void UART1_3_IRQHandler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();
#if (INT_CALLBACK_ON == INT_CALLBACK_UART1)  
    uart_isr(&(uart_obj[UART1_INDEX].serial));
#endif
    /* leave interrupt */
    rt_interrupt_leave();
}

#endif

Then call UART in the interrupt service function_ ISR function, which is also the function we need to implement, as follows:

/**
 * Uart common interrupt process. This need add to uart ISR.
 *
 * @param serial serial device
 */
static void uart_isr(struct rt_serial_device *serial)
{
    struct hc32_uart_t *uart;
    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct hc32_uart_t, serial);

    /* UART in mode Receiver -------------------------------------------------*/
    if(Uart_GetStatus(uart->config->Instance , UartRC))         //UART1 data receiving
    {
        rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND);      // Clear interrupt in getc
    }
    if(Uart_GetStatus(uart->config->Instance, UartTC))         //UART1 data transmission
    {
        Uart_ClrStatus(uart->config->Instance, UartTC);        //Clear interrupt status bit
    }
}

In this function, we need to analyze what interrupts are currently occurring and deal with the corresponding interrupts. Common interrupts include receive interrupt, send interrupt, DMA interrupt and so on. Here, we only deal with receiving interrupts and call rt_ during receiving interrupts. hw_ serial_ ISR function to notify the serial framework of the occurrence of an interrupt and the interrupt type, and then rt_hw_serial_isr will call the getc function implemented in our driver to receive a character. In getc, it should be noted that after receiving a character, the interrupt flag bit should be cleared.

After these operations are completed, the serial driver should be able to run.

 \ | /
- RT -     Thread Operating System
 / | \     4.0.3 build May 19 2021
 2006 - 2021 Copyright by rt-thread team
Os is Start!!! 
msh >
msh >
msh >
msh >

Of course, if you want to change serial port configuration parameters such as serial port baud rate or check bit data bit, you need to implement the control function. There is no more introduction here.

Problem & summary

  • The key point of serial driver implementation is to connect the serial driver function to the serial device framework, and then implement each function
  • When doing serial driver, we should first be familiar with the serial driver functions in the SDK of the target development board, and then transplant it on this basis
  • If you encounter a problem, you should debug it step by step, mainly to see uart = rt_container_of(serial, struct hc32_uart_t, serial); Did you get the correct UART here
  • For MCU of the same series, compatibility should be considered, but the realization goal shall prevail before reconstruction and optimization

Topics: rt-thread