RT-Thread之UART设备驱动开发教程

接口/总线/驱动

1138人已加入

描述

UART介绍

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

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

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

控制器

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

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

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

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

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

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

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

UART层级结构

控制器

1)I/O设备管理层向应用层提供rt_device_read/write等标准接口,应用层可以通过这些标准接口访问UART设备。
2)UART设备驱动框架源码文件为serial_v2.c,位于RT-Thread源码的componentsdriversserial文件夹中。抽象出的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。4929.html

控制器

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设备驱动框架层处理数据。

控制器

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);
};

控制器

这些操作方法会完成串口的基本操作,例如: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设备中断处理

控制器

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

控制器

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分