《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发

RT-Thread设备驱动开发-第2章 UART设备驱动开发 

UART介绍

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)也常被称为串口。UART作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。UART是在应用程序开发过程中使用频率最高的数据总线。在嵌入式设计中,UART常用于主机与辅助设备通信,如嵌入式设备与外接模块(Wi-Fi、蓝牙模块等)的通信,嵌入式设备与PC监视器的通信,或用于两个嵌入式设备之间的通信。

UART串口属于字符设备的一种,它的硬件连接也比较简单,只要两根传输线就可以实现双向通信:一根线(TX)发送数据,另一根线(RX)接收数据。

UART串口通信有几个重要的参数,分别是波特率、起始位、数据位、停止位和奇偶检验位,对于两个使用UART串口通信的端口,这些参数必须匹配,否则通信将无法正常完成。

《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发_第1张图片

数据格式包含起始位、数据位、奇偶校验位、停止位。

起始位:表示数据传输的开始,电平逻辑为“0”。

数据位:数据位通常为8bit的数据(一个字节),但也可以是其他大小,例如5bit、6bit、7bit,表示传输数据的位数。

奇偶校验位:用于接收方对接收到的数据进行校验,校验一个二进制数中“1”的个数为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性,使用时也可以不需要此位。

停止位:表示一帧数据的结束,电平逻辑为“1”。

波特率:串口通信时的速率,它用单位时间内传输的二进制代码的有效位数来表示,其单位为bit/s。常见的波特率值有4800、9600、14400、38400、115200等,数值越大数据传输越快,波特率为115200表示每秒传输115200位数据。

UART v2.0版本的UART框架和驱动讲解

UART层级结构

《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发_第2张图片

1)I/O设备管理层向应用层提供rt_device_read/write等标准接口,应用层可以通过这些标准接口访问UART设备。

2)UART设备驱动框架源码文件为serial_v2.c,位于RT-Thread源码的components\drivers\serial文件夹中。抽象出的UART设备驱动框架和平台无关,是一层通用的软件层。UART设备驱动框架提供以下功能。

①对接上层的I/O设备管理层,以让应用层调用I/O设备管理层提供的统一接口对UART进行操作。

②UART设备驱动框架向UART设备驱动层提供UART设备操作方法接口struct rt_uart_ops(如configure、control、putc、getc、transmit),驱动开发者需要实现这些接口。

③提供设备注册管理接口rt_hw_serial_register和中断处理接口rt_hw_serial_isr。

《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发_第3张图片

3)UART设备驱动源码文件为drv_usartv2.c,放在具体bsp目录下,v2表示对接在串口v2版本的设备驱动框架上。UART设备驱动的实现与平台相关,它操作具体的MCU UART控制器。UART设备驱动需要实现UART设备的操作方法struct rt_uart_ops,以提供访问和控制UART硬件的能力。这一层也负责调用rt_hw_serial_register函数将UART设备注册到操作系统。最后还需调用中断处理接口rt_hw_serial_isr,通知UART设备驱动框架层处理数据。

《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发_第4张图片

4)最下面一层是MCU外接的UART模块,如UART通信模块、RS-232芯片或者RS-485芯片电路模块等,这样MCU就可以与外接模块进行数据通信了。

UART设备驱动开发的主要任务就是实现串口设备操作方法接口struct rt_uart_ops,然后注册串口设备。

查看代码,串口初始化:

int rt_hw_usart_init(void)
{
    rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct stm32_uart);
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
    rt_err_t result = 0;
    stm32_uart_get_dma_config();
    for (int i = 0; i < obj_num; i++)
    {
        uart_obj[i].config = &uart_config[i];
        uart_obj[i].serial.ops    = &stm32_uart_ops;
        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
                                       | uart_obj[i].uart_dma_flag
                                       , NULL);
        RT_ASSERT(result == RT_EOK);
    }
    return result;
}

创建UART设备。对UART设备来说,在驱动开发时需要先从struct rt_serial_device结构中派生出新的串口设备模型,然后根据自己的设备类型定义私有数据域。特别是在可能有多个类似设备的情况下(例如串口1、串口2),设备接口可以共用同一套接口,不同的只是各自的数据域(例如寄存器基地址)。

例如,STM32的UART设备模型从struct rt_serial_device派生,并增加了STM32UART的特有数据结构,如STM32串口句柄、串口配置信息、DMA结构信息等。

/* stm32 uart dirver class */
struct stm32_uart
{
    UART_HandleTypeDef handle;
    struct stm32_uart_config *config;
#ifdef RT_SERIAL_USING_DMA
    struct
    {
        DMA_HandleTypeDef handle;
        rt_size_t last_index;
    } dma_rx;
    struct
    {
        DMA_HandleTypeDef handle;
    } dma_tx;
#endif
    rt_uint16_t uart_dma_flag;
    **struct rt_serial_device serial;**
};

实现UART设备的操作方法

struct rt_uart_ops
{
    rt_err_t (*configure)(struct rt_serial_device       *serial,
                          struct serial_configure       *cfg);
    rt_err_t (*control)(struct rt_serial_device         *serial,
                                            int          cmd,
                                            void        *arg);
    int (*putc)(struct rt_serial_device *serial, char c);
    int (*getc)(struct rt_serial_device *serial);
    rt_size_t (*transmit)(struct rt_serial_device       *serial,
                                 rt_uint8_t             *buf,
                                 rt_size_t               size,
                                 rt_uint32_t             tx_flag);
};

《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发_第5张图片

《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发_第6张图片

这些操作方法会完成串口的基本操作,例如:configure方法用于配置串口(波特率等);control方法用于控制串口;putc方法用于串口向外发送字符数据;getc方法用于串口获取字符数据;transmit方法用于数据发送,主要是进行多字节数据的发送。下面继续讲解如何实现这些操作方法。

注册UART设备

UART设备的操作方法实现后需要注册设备到操作系统,注册UART设备的接口是rt_err_t rt_hw_serial_register(struct rt_serial_device serial,const char name,rt_uint32_t flag, void *data)。

UART设备中断处理

《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发_第7张图片《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发_第8张图片

增加DMA模式

增加UART设备DMA模式,需要首先对每个UART的DMA进行配置,接着进行DMA初始化和中断处理,最后完成DMA发送。以下是DMA配置代码。

驱动配置

RT-Thread使用SCons构建工程,使用基于Kconfig机制的menuconfig工具配置工程。因此不仅要实现驱动,还要实现驱动相关的配置选项:一是Kconfig配置,配置好的配置文件将会在menuconfig工具中形成对应的配置界面;二是进行SConscript配置,配置好后,相应的驱动文件将会被添加到工程中。后面各章的驱动相关配置选项与此类似,如无特殊配置将不再赘述。

1.Kconfig配置

下面参考bsp/stm32/stm32f407-atk-explorer/board/Kconfig文件配置串口驱动的相关选项,如下所示:

menuconfig BSP_USING_UART
        bool "Enable UART"
        default y
        select RT_USING_SERIAL
        if BSP_USING_UART
            config BSP_USING_UART0
                bool "Enable UART0"
                default y
            config BSP_UART0_RX_USING_DMA
                bool "Enable UART0 RX DMA"
                depends on BSP_USING_UART0
                select RT_SERIAL_USING_DMA
                default n
            config BSP_USING_UART1
                bool "Enable UART1"
                default n
            config BSP_UART1_RX_USING_DMA
                bool "Enable UART1 RX DMA"
                depends on BSP_USING_UART1
                select RT_SERIAL_USING_DMA
                default n
            config BSP_USING_UART2
                bool "Enable UART2"
                default n
            config BSP_UART2_RX_USING_DMA
                bool "Enable UART2 RX DMA"
                depends on BSP_USING_UART2
                select RT_SERIAL_USING_DMA
                default n
            config BSP_USING_UART3
                bool "Enable UART3"
                default n
            config BSP_UART3_RX_USING_DMA
                bool "Enable UART3 RX DMA"
                depends on BSP_USING_UART3
                select RT_SERIAL_USING_DMA
                default n
            config BSP_USING_UART4
                bool "Enable UART4"
                default n
            config BSP_UART4_RX_USING_DMA
                bool "Enable UART4 RX DMA"
                depends on BSP_USING_UART4
                select RT_SERIAL_USING_DMA
                default n
            config BSP_USING_UART5
                bool "Enable UART5"
                default n
            config BSP_UART5_RX_USING_DMA
                bool "Enable UART5 RX DMA"
                depends on BSP_USING_UART5
                select RT_SERIAL_USING_DMA
                default n
            config BSP_USING_UART6
                bool "Enable UART6"
                default n
            config BSP_UART6_RX_USING_DMA
                bool "Enable UART6 RX DMA"
                depends on BSP_USING_UART6
                select RT_SERIAL_USING_DMA
                default n
            config BSP_USING_UART7
                bool "Enable UART7"
                default n
            config BSP_UART7_RX_USING_DMA
                bool "Enable UART7 RX DMA"
                depends on BSP_USING_UART7
                select RT_SERIAL_USING_DMA
                default n
        endif

代码段中相关宏的说明如下所示。

BSP_USING_UART:串口驱动代码对应的宏定义,这个宏控制串口驱动相关代码是否会添加到工程中。

RT_USING_SERIAL:串口驱动框架代码对应的宏定义,这个宏控制串口驱动框架的相关代码是否会添加到工程中。

BSP_USING_UART1:串口设备1对应的宏定义,这个宏控制串口设备1是否会注册到系统中。

BSP_UART1_RX_USING_DMA:串口设备1使用DMA接收数据。

2.SConscript配置

在HAL_Drivers/SConscript文件中为串口驱动添加判断选项,代码如下所示。这是一段Python代码,表示如果定义了宏BSP_USING_UART,则drv_uart.c会被添加到工程的源文件中。

if GetDepend(['RT_USING_SERIAL']):
    src += ['drv_usart.c']

注册设备之后,UART设备将以字符设备的形式在I/O设备管理器中存在。系统启动并开始运行后,可以在终端使用list_device命令看到注册的设备包含了UART设备,之后则可以使用UART设备驱动框架提供的统一API对UART设备进行操作。

小结

在RT-Thread中,将UART外设抽象为UART设备,并结合UART设备的通用操作方法与驱动框架思想设计出UART设备驱动框架,这为开发者提供了更便利的设备控制方式。同时,这使基于UART设备编写出来的应用代码更具兼容性与通用性。开发者还需要注意以下两点。

1)操作方法的名称可以自定义,但不要脱离实际意义,并且需要遵守代码规范。所有的操作方法/函数都属于内部函数,在函数实现时,需要使用static进行修饰。本条注意事项对每种驱动都适用,后面章节将不再赘述。

2)在进入与退出中断时,需要调用中断进入和中断退出函数,如下所示。本条注意事项对每种驱动都适用,后面章节将不再赘述。

《RT-Thread设备驱动开发指南》—— 基础篇之UART设备驱动开发_第9张图片

感谢架构师李肯的赠书!

欢迎关注个人公众号:嵌入式学习与实践

你可能感兴趣的:(驱动开发)