嵌入式技术论坛
直播中

张磊

7年用户 520经验值
私信 关注
[经验]

如何基于rt-thread的serial框架来实现msh组件的使用

简介
uart驱动作为常用外设之一,对rt-thread的msh组件使用和一些外接uart模块来说必不可少,这篇文章将介绍如何基于rt-thread的serial框架来编写drv_uart.c和drv_uart.h文件,直到最后一步步实现msh组件的使用。
阅读本篇文章前,确定你已经熟悉了rt-thread的使用以及在一个STM32或者其他移植过BSP的开发板上跑起来了rt-thread,并且你已经熟悉了UART设备驱动框架的使用方法。
uart作为一种常用的外设,一般情况下只使用两根线 : tx rx ;在使用时主设备的tx接从设备的rx, 主设备的rx接从设备的tx。 一般情况下作为异步方式使用,即tx和rx互不相干,如果想做半双工通讯,可以只接tx或者rx。当然uart还有许多扩展的通讯方式,比如硬件流控和时钟同步等等,但是一般情况下都不用,这里不做讨论。
移植
准备模板文件
首先准备一个模板文件,drv_uart.c和drv_uart.h。这两个文件里面只有函数体,并没有函数实现,将这两个文件加入到工程中,一般情况下都是可以编译通过的。
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
#include
#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
#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)
******************************************************************************/
将这两个文件加入到你的工程中,然后保证编译通过,如果编译出错,把能删除的都删除,只保留函数就行了。
".outputreleasert-thread.axf" - 0 Error(s), 0 Warning(s).
添加函数体
然后重头戏来了,我们要分别实现每个函数的函数体。这些函数体的实现和你目标MCU的uart驱动有关系,大致思想就是将目标MCU的uart驱动给移植过来。这里我分析一下我的移植思路。
根据rt-thread的设计思想,我们一般情况下分析驱动文件是从drv_xxx.c的最后一行开始分析。这里就是
INIT_BOARD_EXPORT(hc32_hw_uart_init);
然后,找到这个函数,这个函数就是将uart设备注册到serial驱动框架中的,主要是就是将设备的driver层和device层联系起来,当上层调用rt_device_find的时候,就会跟着执行drv_xxx.c里面的硬件配置函数,函数实现看下面代码注释。
int hc32_hw_uart_init(void)
{
    rt_err_t result = RT_EOK;
    /* 当前一共用了多少个串口设备, 在rtconfig.h中的 BSP_USING_UARTx 宏来决定用了多少个串口*/
    rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct hc32_uart_t);
/* 串口默认配置参数  主要是配置波特率 数据位 停止位 奇偶校验位等等 */
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
/* 将这些配置参数放在uart 管理结果体里,同时像device层注册uart设备 */
    for (int i = 0; i < obj_num; i++)
    {
        uart_obj.config = &uart_config;
        uart_obj.serial.ops = &hc32_uart_ops; // 上层操作函数最终会进入到drv层来操作具体的硬件 所以实现drv的ops就是移植的重点
        uart_obj.serial.config = config;
        /* register UART device */
        result = rt_hw_serial_register(&uart_obj.serial, uart_obj.config->name,
                                       RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX, NULL);
        RT_ASSERT(result == RT_EOK);
    }
    return result;
}
然后就要实现各个ops函数了,这个是我们移植的重点和难点。ops操作函数的每个意义如下:
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       // dma传输 这个一开始可以不实现
    };
首先,重点介绍两个比较重要的结构体
struct hc32_uart_config
{
    const char *name;                 // 串口的名字
    M0P_UART_TypeDef *Instance;       // 对应的串口硬件接口
    IRQn_Type irq_type;               // 中断类型
};
struct hc32_uart_t
{
    stc_uart_cfg_t stcCfg;                   // 和具体的MCU有关,配置结构体
    struct hc32_uart_config *config;         // 串口通用配置
    struct rt_serial_device serial;          // 对接uart device 必要
};
接着实现config函数,这个函数的两个入参,一个是对接设备驱动框架的serial,通过serial我们能找到当前操作的是哪个串口,另一个就是配置cfg,这个cfg里的参数就是我们上面注册serial设备时的默认初始化参数 RT_SERIAL_CONFIG_DEFAULT。函数的具体实现逻辑我在函数体中做了注释。
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);   // 通过serial来找到uart 然后操作这个uart
    /* 这里通过interface接口来确定具体操作哪个串口 实现方式和各个MCU有关系 可以参考MCU的SDK里给的simple */
    if (uart->config->Instance == M0P_UART1)
    {
        stc_gpio_cfg_t stcGpioCfg;
        DDL_ZERO_STRUCT(stcGpioCfg);
/* 使能时钟 */
        Sysctrl_SetPeripheralGate(SysctrlPeripheralUart1, TRUE); ///<使能uart1模块时钟
        Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);  //使能GPIO模块时钟
/* 初始化IO */
        ///         stcGpioCfg.enDir = GpioDirOut;
        Gpio_Init(GpioPortA, GpioPin2, &stcGpioCfg);
        Gpio_SetAfMode(GpioPortA, GpioPin2, GpioAf1); //配置PA02 端口为URART1_TX
        ///         stcGpioCfg.enDir = GpioDirIn;
        Gpio_Init(GpioPortA, GpioPin3, &stcGpioCfg);
        Gpio_SetAfMode(GpioPortA, GpioPin3, GpioAf1); //配置PA03 端口为URART1_RX
/* 具体的串口参数配置 各个MCU厂家各不相同 */
        uart->stcCfg.enRunMode = UartMskMode3; ///<模式3
        uart->stcCfg.stcBaud.u32Baud = cfg->baud_rate;
        uart->stcCfg.stcBaud.enClkDiv = UartMsk8Or16Div;      ///<通道采样分频配置
        uart->stcCfg.stcBaud.u32Pclk = Sysctrl_GetPClkFreq(); ///<获得外设时钟(PCLK)频率值
        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)); ///<串口初始化
    ///     Uart_ClrStatus(uart->config->Instance,UartRC);                ///<清接收请求
    Uart_EnableIrq(uart->config->Instance,UartRxIrq);             ///<使能串口接收中断   
    EnableNvic(uart->config->irq_type, IrqLevel3, TRUE);       ///<系统中断使能
    return RT_EOK;
}
接下来实现的putc和getc就好办了,比较简单
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数据接收
    {
        Uart_ClrStatus(uart->config->Instance, UartRC);     // 清中断状态位
        ch = Uart_ReceiveData(uart->config->Instance);      // 接收数据字节
    }
    return ch;
}
最后,我们再分析一下,串口的中断接收。
根据MCU的SDK里的串口中断接收simple,首先找到中断服务函数放在drv_uart.c里,如下:
#if defined(BSP_USING_UART1) || defined(BSP_USING_UART3)
//UART1中断函数
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
然后在中断服务函数里调用uart_isr函数,这个函数也是我们需要实现的函数,如下:
/**
* 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数据接收
    {
        rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND);      // 在getc里清除中断
    }
    if(Uart_GetStatus(uart->config->Instance, UartTC))         //UART1数据发送
    {
        Uart_ClrStatus(uart->config->Instance, UartTC);        //清中断状态位
    }
}
在这个函数里,我们要分析当前发生的是什么中断,并处理对应的中断,常见的中断有接收中断、发送中断以及DMA中断等等。这里,我们只处理接收中断,在接收中断中,调用rt_hw_serial_isr函数来通知serial框架有中断发生,并告知中断类型,然后rt_hw_serial_isr会调用我们驱动中实现的getc函数来接收一个字符,在getc中要注意,接收完成一个字符后,要清除中断标志位。
这些操作都完成后,串口驱动应该就能跑起来了。
  | /
- 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 >
当然,如有要更改串口波特率或者校验位 数据位等串口配置参数,就要实现control函数了。这里就不过多介绍了。
问题&总结
串口驱动实现的重点是要挂接串口驱动函数到serial设备框架,然后再实现每个函数
我们在做串口驱动的时候,应该先熟悉目标开发板的SDK里面的串口相关驱动函数,然后在此基础上再做移植
遇到问题应该单步调试一下,主要是看 uart = rt_container_of(serial, struct hc32_uart_t, serial); 这里有没有获取到正确的uart
对于同系列的MCU要考虑兼容性,但是先实现的时候以实现目标为准,然后再考虑重构和优化。

原作者:静静流淌的柯溪

更多回帖

×
20
完善资料,
赚取积分