Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
设备驱动
由串口驱动分析RTT设备驱动框架
发布于 2024-05-28 14:27:54 浏览:574
订阅该版
[tocm] ### 内核对象管理框架简析 rt-thread中,线程、信号量、互斥锁、设备等等都是“**内核对象**”,`_object_container`是一个数组(容器),包含所有的内核对象信息。 对象信息的定义 ```c struct rt_object_information { enum rt_object_class_type type; /**< object class type */ rt_list_t object_list; /**< object list */ rt_size_t object_size; /**< object size */ }; ``` 定义了类型,链表,对象的大小 对象有哪些呢? ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/7febf808534d1121924321310def3dfe.png) `_object_container`定义了内核的所有对象: ```c static struct rt_object_information _object_container[RT_Object_Info_Unknown] = { /* initialize object container - thread */ {RT_Object_Class_Thread, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread), sizeof(struct rt_thread)}, #ifdef RT_USING_SEMAPHORE /* initialize object container - semaphore */ {RT_Object_Class_Semaphore, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore), sizeof(struct rt_semaphore)}, #endif #ifdef RT_USING_MUTEX /* initialize object container - mutex */ {RT_Object_Class_Mutex, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Mutex), sizeof(struct rt_mutex)}, #endif #ifdef RT_USING_EVENT /* initialize object container - event */ {RT_Object_Class_Event, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Event), sizeof(struct rt_event)}, #endif #ifdef RT_USING_MAILBOX /* initialize object container - mailbox */ {RT_Object_Class_MailBox, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MailBox), sizeof(struct rt_mailbox)}, #endif #ifdef RT_USING_MESSAGEQUEUE /* initialize object container - message queue */ {RT_Object_Class_MessageQueue, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MessageQueue), sizeof(struct rt_messagequeue)}, #endif #ifdef RT_USING_MEMHEAP /* initialize object container - memory heap */ {RT_Object_Class_MemHeap, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemHeap), sizeof(struct rt_memheap)}, #endif #ifdef RT_USING_MEMPOOL /* initialize object container - memory pool */ {RT_Object_Class_MemPool, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemPool), sizeof(struct rt_mempool)}, #endif #ifdef RT_USING_DEVICE /* initialize object container - device */ {RT_Object_Class_Device, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Device), sizeof(struct rt_device)}, #endif /* initialize object container - timer */ {RT_Object_Class_Timer, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Timer), sizeof(struct rt_timer)}, #ifdef RT_USING_MODULE /* initialize object container - module */ {RT_Object_Class_Module, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Module), sizeof(struct rt_dlmodule)}, #endif }; ``` 这个宏初始化[链表](https://blog.csdn.net/victor_zy/article/details/122170734),将`prev`和`next`都指向自己,形成环形链表。 ```c #define _OBJ_CONTAINER_LIST_INIT(c) \ {&(_object_container[c].object_list), &(_object_container[c].object_list)} ``` 总结一下,一个数组`_object_container`描述所有的内核对象,数组的每个元素是一个结构体类型`rt_object_information`, 成员是内核对象的类型,内核对象链表,内核对象大小。 类型是唯一的 ,知道对象类型,遍历这个数组,就能定位到这个对象类型信息。下面这个函数就是干这活的 ```c struct rt_object_information * rt_object_get_information(enum rt_object_class_type type) { int index; for (index = 0; index < RT_Object_Info_Unknown; index ++) if (_object_container[index].type == type) return &_object_container[index]; return RT_NULL; } ``` 遍历`_object_container`数组,如果类型匹配,就得到了索引。 串口是个片内设备,我们重点关注`rt_device`。设备的内核对象类型是`RT_Object_Class_Device` , ### 看看stm32的串口是怎么注册到内核的 #### 相关结构体定义 - 配置结构体 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/03fa2ae9fba3906532bbaff879062131.png) - stm32串口结构体 ```c struct stm32_uart { UART_HandleTypeDef handle; struct stm32_uart_config *config; // 串口配置数据 #ifdef RT_SERIAL_USING_DMA struct { DMA_HandleTypeDef handle; rt_size_t remaining_cnt; } dma_rx; struct { DMA_HandleTypeDef handle; } dma_tx; #endif rt_uint16_t uart_dma_flag; struct rt_serial_device serial; // 串口设备,继承自rt_device }; ``` - 框架串口结构体 `parent`是`rt_device` ```c struct rt_serial_device { struct rt_device parent; const struct rt_uart_ops *ops; struct serial_configure config; void *serial_rx; void *serial_tx; }; ``` - 顶层`rt_device`的定义: ```c struct rt_device { struct rt_object parent; /**< inherit from rt_object */ enum rt_device_class_type type; /**< device type */ rt_uint16_t flag; /**< device flag */ rt_uint16_t open_flag; /**< device open flag */ rt_uint8_t ref_count; /**< reference count */ rt_uint8_t device_id; /**< 0 - 255 */ /* device call back */ rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); #ifdef RT_USING_DEVICE_OPS const struct rt_device_ops *ops; #else /* common device interface */ rt_err_t (*init) (rt_device_t dev); rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); rt_err_t (*close) (rt_device_t dev); rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); rt_err_t (*control)(rt_device_t dev, int cmd, void *args); #endif #if defined(RT_USING_POSIX) const struct dfs_file_ops *fops; struct rt_wqueue wait_queue; #endif void *user_data; /**< device private data */ }; ``` 一张图概括 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/85791e7aaf9bbe77f58df22119265fbc.png) 如果把结构体类型比作C++中的类,那么上图就描述了派生关系。 - 串口配置结构体数组 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/aba0260697101885ae499e4fe3588cc4.png) menuconfig中如果选了串口1。`BSP_USING_UART1`这个宏就会定义。这个数组元素个数等于已使能的串口数。 `UART1_CONFIG`也是个宏定义 ```c #define UART1_CONFIG \ { \ .name = "uart1", \ .Instance = USART1, \ .irq_type = USART1_IRQn, \ } ``` 用这个宏初始化`stm32_uart_config`这个结构体。 - stm32串口对象数组的定义 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/ee594cc831a8da315db667fb5d6ed759.png) 很明显,使能了几个串口,配置数组就有多大,对应的,就会定义多大的`stm32_uart`类型的数组 ### 初始化 **初始化的过程就是把所有menuconfig里使能的串口注册到内核,通俗讲,就是把对应的串口对象插入上文所说`RT_Object_Class_Device`类型的链表中。记住,注册并没有从HAL层初始化串口设备。注册是为了使api能够找到设备,并能通过调用框架接口操作设备** 串口对象的方法(当然对于C而言面向对象的思想,对象就是结构体,结构体中声明的函数指针就是其方法) ```c static const struct rt_uart_ops stm32_uart_ops = { .configure = stm32_configure, .control = stm32_control, .putc = stm32_putc, .getc = stm32_getc, .transmit = stm32_transmit }; ``` `rt_serial_device`结构体中包含`rt_uart_ops`。上图派生(包含)关系中。那么`stm32_uart`就也会有这些方法 `config` , `control` , `putc` , `getc` 这些成员都是函数指针。 `stm32_config` `stm32_putc` `stm32_getc`等这些函数都实现了底层的串口配置和收发等。这段定义不再贴出了 重点关注`rt_hw_usart_init`和`rt_hw_serial_register`这两个函数,重点注意下图中三个结构体类型 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/f236715f69412c8ab0f6e56694cbcc30.png.webp) 注意绿色框中的成员变量,初始化的过程就是给这成员变量赋个初值 在`rt_hw_usart_init`中,这个for循环里: ```c uart_obj[i].config = &uart_config[i]; // uart_config这个数组的元素是UART1_CONFIG, UART2_CONFIG等这样的宏 uart_obj[i].serial.ops = &stm32_uart_ops; //stm32_uart_ops是静态常量 ``` 然后调用`rt_hw_serial_register`,这个函数中先把device成员初始化,再把串口设备对象注册到内核 ```c device->type = RT_Device_Class_Char; device->rx_indicate = RT_NULL; device->tx_complete = RT_NULL; #ifdef RT_USING_DEVICE_OPS device->ops = &serial_ops; #else device->init = rt_serial_init; // 中间层接口 device->open = rt_serial_open; // 中间层接口 device->close = rt_serial_close; // 中间层接口 device->read = RT_NULL; device->write = RT_NULL; device->control = rt_serial_control; // 中间层接口 #endif device->user_data = data; /* register a character device */ ret = rt_device_register(device, name, flag); ``` 可以看到,串口设备类型是字符设备,调用完`rt_device_register`后,这个设备就加入了设备管理器,说白了,以后就可以调用系统api(`rt_device_find`),通过名字(比如"uart2")找到这个串口设备,找到了就可以对其操作了。 `rt_device_register`中调用了`rt_object_init`(终于用到了上节分析的内容): ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/29495fc295da5e1455d111df97ed4238.png.webp) 在`rt_hw_serial_register`这个函数中看到`read`和`write`初始化为了`RT_NULL`。`v2`版是这样的,在调用了`open`后,`read`和`write`才会根据`flag`赋具体的值,在`rt_serial_open`中有这段 ```c /* initialize the Rx structure according to open flag */ if (serial->serial_rx == RT_NULL) rt_serial_rx_enable(dev, dev->open_flag & (RT_SERIAL_RX_BLOCKING | RT_SERIAL_RX_NON_BLOCKING)); /* initialize the Tx structure according to open flag */ if (serial->serial_tx == RT_NULL) rt_serial_tx_enable(dev, dev->open_flag & (RT_SERIAL_TX_BLOCKING | RT_SERIAL_TX_NON_BLOCKING)); ``` 比如,再跳到`rt_serial_rx_enable`中,有这段: ```c dev->read = _serial_poll_rx; ``` ### 初始化过程分析 先来个总览: `rt_device_open` -> `dev->init(dev)` -> `rt_serial_init` -> `serial->ops->configure` -> `stm32_configure` -> `HAL_UART_Init` 一级一级的看,按驱动框架来,第一级是device,第二级是serial ,第三级是stm32uart, 第四级是HAL,第五级...... 1. device.c文件中,`rt_device_open`接口中: ```c result = device_init(dev); // device_init是个宏,宏定义替换后,dev->init(dev), 调用了自己的成员函数指针init,传入参数是自己 ``` 串口设备注册后 device的init成员 初始化为rt_serial_init。所以下一步找`rt_serial_init` 2. serial_v2.c文件 `rt_serial_init`函数中 ```c result = serial->ops->configure(serial, &serial->config); ``` 上文分析过,`serial->ops->configure`初始化为stm32_configure 3. drv_usart_v2.c文件中 在`stm32_configure`这个函数的最后可以看到,调用了HAL库函数去初始化串口 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/5eac6f507f7fc5d73322ba588859bdb2.png) 4. 在stm32l4xx_hal_uart.c这是库文件,在libraries路径 `HAL_UART_Init`函数中调用这个 ```c HAL_UART_MspInit(huart); ``` 5. stm32l4xx_hal_msp.c文件中 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/82e994fe837fbd80537d4391d33bc995.png) 这个文件是cubemx生成的,文件内容肯定会根据cubemx中的配置的变化而变化。 实际上`scons` 是包括了的,可以打开board路径下的`SConscript` ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20240528/2f72f8030be0ef3667575366352704e4.png) 像这样一层层的看,也可以分析出`rt_device_read` `rt_device_write`的调用过程
2
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
Z_Y
这家伙很懒,什么也没写!
文章
11
回答
42
被采纳
1
关注TA
发私信
相关文章
1
让成员函数能作为rt_device中的回调函数
2
关于设备配置时延时的处理
3
rt_device_set_rx_indicate设置的回调如何传递参数?
4
关于 输入捕获 驱动的
5
关于设备驱动的迷茫与疑惑
6
关于rtthread中各种驱动的问题
7
drv_hwtimer 和hwtimer关系是啥?
8
RTT有没有接口文档,可以用于写一些自己创建的设备的这些文档?
9
i2c设备驱动为什么没有速率设置
10
UART设备中断接收及轮询发送
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
UART
WIZnet_W5500
ota在线升级
freemodbus
PWM
flash
cubemx
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
中断
编译报错
Debug
SFUD
rt_mq_消息队列_msg_queue
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
a1012112796
10
个答案
1
次被采纳
踩姑娘的小蘑菇
4
个答案
1
次被采纳
红枫
4
个答案
1
次被采纳
张世争
4
个答案
1
次被采纳
Ryan_CW
4
个答案
1
次被采纳
本月文章贡献
catcatbing
3
篇文章
5
次点赞
YZRD
2
篇文章
5
次点赞
qq1078249029
2
篇文章
2
次点赞
xnosky
2
篇文章
1
次点赞
Woshizhapuren
1
篇文章
5
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部