【RT-Thread进阶】设备应用层与底层的映射以及串口DMA中断回调原理机制

写在前面

在入门学习了RT-Thread之后,我撰写了本文剖析了在RTT中设备应用层与底层时如何映射和关联起来的,以及解释了串口DMA回调的原理机制(可触类旁通),有助于深入了解了RTT的逻辑架构,最后是我的相关学习心得。
有关RT-Thread入门学习可以参考我的这篇文章:

【RT-Thread Studio】RT-Thread学习笔记

1 串口设备树映射

1.1 I/O设备树模型

I/O 设备模型框架补充图

首先HAL库中封装了直接对硬件寄存器进行相关操作的方法,然后设备驱动层drv_usart.c中会调用HAL库中的方法实现对寄存器的操作

1.2 映射机制
1.2.1 RT-Thread启动rtthread_startup

RT-Thread启动时在调用rtthread_startup(void)进行了设备初始化

1.2.2 板级设备初始化rt_hw_board_init

1.2.3 设备初始化hw_board_init

1.2.4 串口设备初始化rt_hw_usart_init

实现**设备驱动层(drv_usart.c)设备驱动框架层(serial.c)**的映射

即,stm32串口设备类(stm32_uart)→rtt串口设备类(rt_serial_device)

第1363行中进入uart_config

board.h中有宏定义串口设备的话,以上对应配置文件就会被启用

进入串口1配置UART1_CONFIG

里面包含串口1相关配置,将相关配置注册后,这也是为什么当在main函数中调用dev1 = rt_device_find("uart1");能找到设备的原因

1.2.5 注册串口设备类rt_hw_serial_register

实现**设备驱动框架层(serial.c)I/O设备管理层(device.c)**的映射

即,rtt串口设备类(rt_serial_device)→rtt设备基类(rt_device)

1.2.6 注册串口设备基类rt_device_register

**I/O设备管理层(device.c)**中

实现**rtt设备基类(rt_device)rtt基类(rt_object)**的映射

1.2.7 rtt基类初始化rt_object_init

所有内核对象的信息(对象类型、大小)都存放在对象容器中,每类对象都被连接在一个链表上:

1.3 应用实例分析

分析完映射机制之后,让我们在应用实例中进行分析

1.3.1 查找设备

先找到基类(rt_object)

这里将rt_object返回有强制类型转换成rt_device_t的原因是,在device.c中注册rt_object基类时,是将rt_device_c结构体中的第一个元素也就是rt_object添加到内核对象容器中,因此对应的这个设备(比如串口1),所定义的内存应该是连续的即rt_device_t = rt_object + flag…,因此强转成rt_device_c只是恢复了对rt_object之后的元素的寻址,也就是通过原来的rt_object的地址还原了完整的rt_device_t 类(首地址一样),最终获取到串口设备基类的所有配置信息。

1.3.2 打开设备

上面有提到,这里的初始化device_init函数其实是映射到了serial.c中的rt_serial_init

回到rt_device_open

同样的这里的device_open也是映射到serial.c中的rt_serial_open

打开rt_serial_open

这里面就根据传入的参数进行相应配置

2 串口DMA接收回调原理机制

2.1 前言

当你在通过rtt的接口设置回调函数的时候,其实看到的是非常浅显的:

image-20240814212801656

这里只是将dev->rx_indicate这个函数指针指向了用户自定义的回调函数就没了,因为想要摸清回调的机制,就要找到这里的回调是从哪里映射过来的。

2.2 分析

**< 3 应用实例分析 3.2 打开设备>**说到打开设备,本章我们刚好从这里接着分析。

由于传入的参数是RT_DEVICE_FLAG_DMA_RX(DMA接收模式),在serial.c中我们就只看这一部分:

image-20240814202910186

这里对应DMA的两种接收模式:

至于这里选择的是哪种模式,我们可以在drv.usart.c文件中初始化的时候找到,就看serial->config.bufsz(缓冲区)是否初始化为0就可以判断了,在rt_hw_usart_init中:

image-20240814203457575

在.\rt-thread\components\drivers\include\drivers下的serial.h中:

查到缓冲区大小并不为0,而是64,因此可以判断这里选择FIFO模式。

前面是缓冲区开辟和初始化,那箭头所指serial->ops->control是哪里映射过来的?看到是串口设备serial相关的,让我们回到drv_usart.c最初的配置中找ops

可以发现ops初始配置成了stm32_uart_ops,打开stm32_uart_ops

找到了serial->ops->control映射的函数stm32_control,打开,重点关注参数cmd,在serial.c的rt_serial_open·serial->ops->control的参数传入的是:

stm32_control中:

找到switch中对应参数的case调用的函数stm32_dma_config,打开,找到HAL_UART_Receive_DMA

打开HAL_UART_Receive_DMA,在.\libraries\STM32F4xx_HAL_Driver\Src路径下的stm32f4xx_hal_uart.c中:

打开HAL_DMA_Start_IT,在.\libraries\STM32F4xx_HAL_Driver\Src路径下的stm32f4xx_hal_dma.c中:

可以发现,这里将DMA接收中断使能位置1了,打开DMA_IT_TC,在.\libraries\STM32F4xx_HAL_Driver\Src路径下的stm32f4xx_hal_dma.h中:

产生中断,调用HAL_UART_TxCpltCallback,它在UART_DMAReceiveCplt中:

在.\libraries\STM32F4xx_HAL_Driver\Src路径下的stm32f4xx_hal_uart.c中:

打开,在相同文件中:

可以发现他是一个弱函数,即如果用户如果定义了一个跟它同名的函数,就能将它重写,那我们只要找到哪里重写了该中断回调函数,就能找到回调的过程了。

最终在drv_usart.c中找到:

可以看到这里调用了DMA中断,进入dma_isr

可以发现调用了串口设备中断rt_hw_serial_isr,打开:

功夫不负有心人,终于找到了DMA回调的源头:

至此,串口DMA回调机制全部摸清!至于其他的中断回调机制与这个大同小异。

3 心得

​ 构建了I/O设备树,实现从底层bsp到应用层的逐级映射,类的“继承”实现面向对象的思想,对应接口的调用后封装实现了面向接口的思想,因此,当用户在应用层操作硬件设备时,只用创建对象,调用接口就能实现相应操作,屏蔽了底层复杂的操作和差异性,即用户只用定义相关的对象、方法,通过对应接口就能实现底层到它的映射,同时,由于封装,当底层驱动升级或者更替时不会影响上层应用,降低了代码的耦合性、复杂性,极大地提高了实际开发的便利性和系统的可靠性。

​ 例如**<2 串口DMA接收回调原理机制>**中讲的到的中断回调,虽然DMA接收完毕后会触发中断回调函数,那把上层的操作逐级封装在这个callback中,再通过函数指针映射到应用层,因此在表面上看用户单纯只是实现了这个函数指针指向的函数,起到了屏蔽底层的作用。

​ 此外,也由于封装,当想自定义设备时道理也是一样的,只需要创建设备对象,定义好相关的设备配置,实现设备操作方法的映射(init、open、control…),再将设备注册(将rt_object对象添加到内核对象容器),那当用户在调用rt_device_find时就能通过名字找到设备,找到基类rt_object,返回还原设备基类rt_device_t,就能够调用自定义的设备操作方法。详见**<设备和驱动 1 I/O设备模型 1.2 创建和注册I/O设备 1.2.3 注册I/O设备>**中有实例。

​ 最后再补充一下为什么在上文说到类的“继承”时要加双引号,因为C中没有C++类的继承(class Student:public Person)的概念,所以这里说的C的继承的实现方式时,先创建一个大类,大类中包含一个中类,中类中包含一个小类,在配置好大类后,将中类注册(相关初始化),中类注册后将中类中的小类注册(将小类节点添加到链表中),那么就能通过链表索引找到小类节点,又由于大中小类的首地址相同,因此就能根据小类倒着找到中类、大类了。

image-20240815100859187
在进阶学习了RT-Thread,了解了它的架构之后,彷佛打开了新世界的大门,十分震撼,正如RTT官方文档中描绘的:

比如从驱动层到驱动框架层,由不同厂商的相同硬件模块创建了很多子类对象,然后对接到同一个父类接口上,多对一,体现面向对象的抽象的威力。以串口设备为例,不管下层是 STM32、GD32 还是别的平台的,只要都是串口设备,都对接到 RT-Thread 的串口设备类——如图所绘,多个硬件对象对接同一个父类对象接口。同理,从设备驱动框架层到IO设备管理接口层,又是多对一,又是再一次的屏蔽差异,再一次的抽象。——面向对象的思想贯穿其中。

  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值