RT-Thread源码解读-------UART设备

本文只要是讲解UART设备在RT-Thread的应用层怎么使用,以及探究底层的实现方式。会从应用层和半导体厂商角度进行讲解。
一、UART简介
对于UART简介,RT-Thread官网的文档中心已经介绍,我把链接附上:UART简介。其实,网上也会有很多方面的资料,大家可以从网上搜索一些资料阅读一下。
我在这里进行一些简单的几点说明:

  • UART是一种全双工的通信方式,所谓的全双工就是在同一时刻两个UART设备能够同时进行收发数据。
  • 在工业上用的RS485、RS232总线一般都是经过UART协议转换过来的,具体如下图所示,图中MCU通过UART产生数据,经过MAX3485转换成RS485协议,或者经过MAX3232转换成RS232协议,进行相应的通信即可。
  • UART在很多地方会用到,一般的低速外设模块与MCU进行通信时都会选择UART,笔者在项目开发过程中遇到过很多;比如:GPS与MCU通信、GPRS与MCU通信等。
    好了,先啰嗦到这里,网上有很多资料写的很棒,大家自行查阅。
    二、UART设备源码剖析
    本文介绍UART设备框架的顺序为:首先从应用层角度出发,介绍应用API接口的使用方法;接着探究应用API的内部实现过程;最后从半导体厂商角度出发介绍怎样编写RT-Thread的UART设备接口代码。
    首先,作为一个应用开发人员,怎样使用UART接口函数。应用程序通过 RT-Thread提供的 I/O 设备管理接口来访问串口硬件,相关接口如下所示:
    在这里插入图片描述1、rt_device_find
    a、应用层API接口
    这里是根据UART设备名称寻找一个设备,该函数会返回一个设备句柄。
    在这里插入图片描述b、框架实现
    应用层通过调用如下代码,实现发现UART设备。
rt_device_find("uart0");//发现串口设备

rt_device_find代码如下:

/**
 * This function finds a device driver by specified name.
 *
 * @param name the device driver's name
 *
 * @return the registered device driver on successful, or RT_NULL on failure.
 */
rt_device_t rt_device_find(const char *name)//
{
   
    struct rt_object *object;
    struct rt_list_node *node;
    struct rt_object_information *information;

    /* enter critical */
    if (rt_thread_self() != RT_NULL)
        rt_enter_critical();//锁定调度器,不允许任务调度

    /* try to find device object */
    information = rt_object_get_information(RT_Object_Class_Device);//寻找设备内核对象
    RT_ASSERT(information != RT_NULL);
    for (node  = information->object_list.next;
         node != &(information->object_list);
         node  = node->next)
    {
   
        object = rt_list_entry(node, struct rt_object, list);//根据成员变量node得到内核对象struct rt_object
        if (rt_strncmp(object->name, name, RT_NAME_MAX) == 0)//根据设备名称寻找设备内核对象
        {
   //寻找到设备
            /* leave critical */
            if (rt_thread_self() != RT_NULL)
                rt_exit_critical();

            return (rt_device_t)object;//强制转换成设备类型结构体,返回
        }
    }

    /* leave critical */
    if (rt_thread_self() != RT_NULL)
        rt_exit_critical();

    /* not found */
    return RT_NULL;
}
RTM_EXPORT(rt_device_find);

通过上面函数的内部实现可以知道,发现串口设备是通过设备名称实现的。因此,这就要求我们的产品代码中,必须为每一个外设起一个独一无二的设备名。对于串口设备,我们习惯上使用uart0、uart1、uart2等。在RT-Thread内核中,设备类型是内核对象struct rt_object *object;派生过来的。在RT-Thread内核中,通过将设备作为RT_Object_Class_Device类型的内核对象进行管理的。在RT-Thread内核中, static struct rt_object_information rt_object_container[RT_Object_Info_Unknown] 存储着所有类型的内核对象,每一种内核对象都会通过双向链表的形式组织在一起。挖坑关于这部分内容我会专门写一篇文章。
通过分析上面代码发现,函数rt_device_find返回的就是一个指针,指向的是struct rt_device类型的结构体变量。
c、底层实现(半导体或者硬件驱动需要做的事)
下面解决一个疑问:为什么应用层只需要根据串口设备的名称就能够找到串口设备,这些底层是怎么实现的呢?换句话说,在应用层调用rt_device_find发现设备之前,系统执行了哪些代码来支持该操作?我们从rt_device_find函数具体执行过程作为突破口,就能够发现。rt_device_find是通过寻找内核中是否含有name成员变量的struct rt_device类型结构体来判断系统中是否含有我们想要寻找的设备。因此,在rt_device_find函数之前一定会有struct rt_device类型结构体定义和初始化的代码。对,我们就是依靠这个思路进行底层实现的。这部分代码一般会在一些底层驱动代码的文件中,我们以文件usart.c (bsp\stm32f20x\drivers)进行分析。

#ifdef RT_USING_UART1
struct stm32_serial_int_rx uart1_int_rx;
struct stm32_serial_device uart1 =
{
   
	USART1,
	&uart1_int_rx,
	RT_NULL
};
struct rt_device uart1_device;//串口设备
#endif

上面代码是定义一个名为uart1_device的struct rt_device类型的结构体,表示一个串口设备。注意这里是一个全局变量,也就是说会一直存在,直到整个代码运行结束。
下面还是在文件usart.c (bsp\stm32f20x\drivers)中的一段代码,具体如下:

/*
 * Init all related hardware in here
 * rt_hw_serial_init() will register all supported USART device
 */
void rt_hw_usart_init()
{
   
	USART_InitTypeDef USART_InitStructure;

	RCC_Configuration();//时钟配置

	GPIO_Configuration();//GPIO工作模式配置

	NVIC_Configuration();//中断配置

	/* uart init */
#ifdef RT_USING_UART1
	USART_DeInit(USART1);
	USART_InitStructure.USART_BaudRate            = 115200;//设置波特率
	USART_InitStructure.USART_WordLength          = USART_WordLength_8b;//设置串口传输为8bit
	USART_InitStructure.USART_StopBits            = USART_StopBits_1;//一位停止位
	USART_InitStructure.USART_Parity              = USART_Parity_No ;//
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//没有硬件流控
	USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;//开启串口接收和发送模式

	USART_Init(USART1, &USART_InitStructure);//设置底层硬件串口工作方式

	/* register uart1 */
	rt_hw_serial_register(&uart1_device, "uart1",
		RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM,
		&uart1);//向RT-Thread内核注册uart1_device设备,该函数是半导体产商为适应RT-Thread的UART框架而编写的函数

	/* enable interrupt */
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//打开串口接收中断
	/* Enable USART1 */
	USART_Cmd(USART1, ENABLE);
	USART_ClearFlag(USART1,USART_FLAG_TXE);
#endif

	/* uart init */
#ifdef RT_USING_UART6
	USART_DeInit(USART6);
	USART_InitStructure.USART_BaudRate            = 115200;
	USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits            = USART_StopBits_1;
	USART_InitStructure.USART_Parity              = USART_Parity_No ;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;

	USART_Init(USART6, &USART_InitStructure);

	/* register uart1 */
	rt_hw_serial_register(&uart6_device, "uart6",
		RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM,
		&uart6);

	/* enable interrupt */
	USART_ITConfig(USART6, USART_IT_RXNE, ENABLE);
	/* Enable USART6 */
	USART_Cmd(USART6, ENABLE);
	USART_ClearFlag(USART6,USART_FLAG_TXE);
#endif
}

该函数可以分为两大部分:第一部分是调用HAL库,对ST32的片内UART进行初始化设置,这是设置的底层硬件部分。第二部分调用了函数rt_hw_serial_register,继续追踪。这里多说一句,rt_hw_serial_register是半导体产商为适配RT-Thread的UART框架(其实本质就是I/O设备模型)而编写的函数,因此这个函数对于不同半导体产商的MCU会不同。我们得到rt_hw_serial_register函数的代码如下:

/*
 * serial register for STM32
 * support STM32F103VB and STM32F103ZE
 */
rt_err_t rt_hw_serial_register(rt_device_t device, const char* name, rt_uint32_t flag, struct stm32_serial_device *serial)
{
   
	RT_ASSERT(device != RT_NULL);

	if ((flag & RT_DEVICE_FLAG_DMA_RX) ||
		(flag & RT_DEVICE_FLAG_INT_TX))
	{
   
		RT_ASSERT(0);
	}

	device-&g
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值