厚积薄发,我们一起学RTT UART设备驱动框架
UART简介
STM32 芯片具有多个 USART
外设用于串口通讯,它是 Universal Synchronous Asynchronous Receiver and Transmitter
的缩写,即通用同步异步收发器
可以灵活地与外部设备进行全双工数据交换。
有别于 USART,它还有具有 UART
外设(Universal Asynchronous Receiver and Transmitter)
,它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。
简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。
UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。
其工作原理是将传输数据的每个字符一位接一位地传输。其传输数据格式如下:
UART设备框架学习笔记
RT-Thread 提供了一套简单的 I/O 设备模型框架,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层 :
应用程序访问串口设备的接口:
下面我们直接来看个实例:
同时使用两个串口(uart1
和uart3
),uart1
作为系统打印调试串口,用来打印一些日志信息,uart3
作为我们本次实验的测试串口,实现与串口调试助手的收发测试。
uart3
设备启动后,往串口调试助手发送字符串I am uart3
。同时,uart3
设备使用中断的方式接收数据,然后再错位输出数据,比如收到ASCII码字符A
,则会回复B
。
#define SAMPLE_UART_NAME "uart3" /* 串口设备名称 */
/* uart3应用函数 */
static int uart3_app(void)
{
rt_err_t ret = RT_EOK; /* 函数返回值 */
rt_thread_t tid; /* 动态线程句柄 */
char uart3_name[RT_NAME_MAX]; /* 保存查找的设备名 */
char usart3_tx_str[] = "I am uart3.\r\n"; /* uart3发送的字符串 */
rt_strncpy(uart3_name, SAMPLE_UART_NAME, RT_NAME_MAX);
/* 查找串口设备 */
uart3_dev = rt_device_find(uart3_name);
if (!uart3_dev)
{
rt_kprintf("find %s failed!\n", uart3_name);
return RT_ERROR;
}
/* 初始化信号量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 以读写及中断接收方式打开串口设备 */
rt_device_open(uart3_dev, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
/* 设置接收回调函数 */
rt_device_set_rx_indicate(uart3_dev, uart3_rx_callback);
/* 发送字符串 */
rt_device_write(uart3_dev, 0, usart3_tx_str, (sizeof(usart3_tx_str) - 1));
/* 创建动态线程 :优先级 25 ,时间片 5个系统滴答,线程栈512字节 */
tid = rt_thread_create("uart3_rx_thread",
static_uart3_rx_entry,
RT_NULL,
STACK_SIZE,
THREAD_PRIORITY,
TIMESLICE);
/* 创建成功则启动动态线程 */
if (tid != RT_NULL)
{
rt_thread_startup(tid);
}
return ret;
}
我们的应用程序首先根据串口设备名字uart3
来查找设备,查找到设备之后则返回串口设备句柄uart3_dev
。
为什么应用程序可以查找得到名字为uart3
的串口设备呢?那是因为我们的硬件驱动层已经把名字为uart3
的串口设备注册到系统中:
而串口设备注册函数
里会调用通用的设备注册函数
:
在这个流程中涉及到了如下函数:
uart3_app函数:在main.c文件中定义。 rt_hw_usart_init函数:在drv_usart.c文件中定义。 rt_hw_serial_register函数:在serial.c文件中定义。 rt_device_register函数:在device.c文件中定义。 rt_device_find函数:在device.c文件中定义。
在上面的RT-Thread
驱动框架框图中,分为好几层,在这里的对应关系如下:
此处,main.c
文件属于应用层,我们的应用程序为:
drv_usart.c
文件属于硬件设备驱动层,是RT-Thread
为我们提供的,其属于板级支持包
中的一部分:
这一层与硬件相关,其调用底层芯片固件库,如:
serial.c
文件属于驱动框架(驱动抽象层),是RT-Thread
系统的组件:
其在RT-Thread
源码中的位置如下:
device.c
文件给应用程序提供操作设备的接口,这个文件属于RT-Thread
内核文件。
RT-Thread
内核采用面向对象的设计思想进行设计,其中设备属于它的一类对象。其继承关系如下:
在这个应用程序中,我们用到了信号量(用其它同步机制也可以,比如事件),信号量属于IPC机制
中的一种:
信号量用于线程与线程
、中断与线程间
的同步中,在我们这个实验中是中断与线程间
的同步。
在裸机开发中,有这样一种场景(中断接收数据,主函数中处理数据):
在串口的接收中断函数中接收数据,然后使用一个全局变量作为中断接收的标志,有触发中断,则这个标志变量被置位;另一方面,在我们的主函数的while循环中,判断这个标志位是否被置位,若置位则进行相应的操作,并把该标志变量清零。
在RT-Thread系统中,其IPC机制做的事情与上面这个裸机开发中的标志变量做的事情类似(中断与线程同步)。比如在我们这个实验中,若uart3的接收中断被触发,则会触发回调函数,如:
回调函数中进行释放信号量操作,在线程中阻塞等待接收信号量:
接收到数据之后,再错位发送数据。最终,我们的实验结果如下:
以上就是本次的笔记分享,如有错误,欢迎指出,谢谢!
最后
如果觉得文章不错,转发、在看,也是我们继续更新得动力。