rtt的io设备框架面向对象学习-spi总线和设备

目录

        • 1.spi总线
        • 1.1硬件spi总线子类
          • 1.1.1 举例
        • 1.2 软件spi总线基类
          • 1.2.1实例
        • 2.spi设备
        • 3.相关文件
        • 4.总结
        • 5.内部调用流程
        • 6.应用程序使用流程

1.spi总线

spi总线分为硬件spi总线和软件模拟spi总线。
按照面向对象的思想,要抽象出硬件spi总线和软件spi总线的相同点和不同点。相同点就变成了spi总线基类,不同点就是各个子类的私有特性。

rtt就是这么干的,共同点是什么?方法——都得有spi配置方法和数据传输方法等,于是抽象出了spi的方法
struct rt_spi_ops
{
rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration); rt_ssize_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message);
};

然后作为成员组成rt_spi_bus类:
struct rt_spi_bus
{
struct rt_device parent;
rt_uint8_t mode;
const struct rt_spi_ops *ops;
struct rt_mutex lock;
struct rt_spi_device *owner;
};

对象图如下
在这里插入图片描述

1.1硬件spi总线子类

对于硬件spi子类,则负责实现rt_spi_bus的ops操作方法。

缺图对象图,ops展开的一个个方法用虚方法表示或者普通方法即可,意味着重写父类方法。

1.1.1 举例

bsp / stm32 / libraries / HAL_Drivers / driver/drv_spi.h 中定义了可以实例化的硬件spi总线类:

struct stm32_spi
{
SPI_HandleTypeDef handle;
struct stm32_spi_config *config;
struct rt_spi_configuration *cfg;
struct
{
DMA_HandleTypeDef handle_rx; DMA_HandleTypeDef handle_tx;
} dma;
rt_uint8_t spi_dma_flag;
struct rt_spi_bus spi_bus;
struct rt_completion cpt;
};

其实把struct rt_spi_bus spi_bus; 放到倒数第二个位置我是不满的,如果把它放到第一个成员,和rtt的io框架更加具有一致性。如改成这样

struct stm32_spi
{
struct rt_spi_bus spi_bus;
SPI_HandleTypeDef handle;
struct stm32_spi_config *config;
struct rt_spi_configuration *cfg;
struct
{
DMA_HandleTypeDef handle_rx; DMA_HandleTypeDef handle_tx;
} dma;
rt_uint8_t spi_dma_flag;
struct rt_completion cpt;
};
这样看着更舒服。

bsp / stm32 / libraries / HAL_Drivers / driver/drv_spi.c中实现了其构造函数t_hw_spi_bus_init,实例化了类对象并重写了父类的方法。

重写的父类方法:
static const struct rt_spi_ops stm_spi_ops =
{
.configure = spi_configure,
.xfer = spixfer,
};

构造函数rt_hw_spi_bus_init函数中重写父类方法,并调用其父类构造函数rt_spi_bus_register执行接下来的构造流程。而rt_spi_bus_register同样的套路重写父类——设备基类——的方法,然后它调用它的父类设备基类的构造函数rt_device_register执行接下来的构造流程。
详细参见io设备管理层。https://blog.csdn.net/yhb1206/article/details/136440373

最终结果是把stm32硬件spi总线对象放到rtt对象容器的链表里去管理。等待使用者拿出来使用——万事俱备只欠东风。
请添加图片描述

如上类图,stm32_spi类继承spi总线基类必须重写spi总线基类的纯虚方法——configure,xfer方法——这两个方法已是通过对应bsp的sdk厂家驱动来实现,完全硬件强相关了。其他厂家一样——这样就对接到统一的框架里面去,实现跨硬件平台了。

1.2 软件spi总线基类

对于软件spi总线基类,差异点就是要用gpio模拟硬件spi的4线或3线通信,那么软件gpio除了实现上面共同点ops操作方法外,还得抽象软件spi通信。
于是抽象出spi软总线基类——rt_spi_bit_obj。

/ components / drivers / spi / spi-bit-ops.h中定义

struct rt_spi_bit_obj
{
struct rt_spi_bus bus;
struct rt_spi_bit_ops *ops;
struct rt_spi_configuration config;
};

因为有共同点所以直接继承rt_spi_bus基类,它的差异点抽象出了rt_spi_bit_ops操作方法以实现gpio模拟spi通信。

struct rt_spi_bit_ops
{ void data; / private data for lowlevel routines */
void (*const tog_sclk)(void *data);
void (*const set_sclk)(void *data, rt_int32_t state); void (*const set_mosi)(void *data, rt_int32_t state);
void (*const set_miso)(void *data, rt_int32_t state);
rt_int32_t (*const get_sclk)(void *data); rt_int32_t (*const get_mosi)(void *data); rt_int32_t (*const get_miso)(void *data);
void (*const dir_mosi)(void *data, rt_int32_t state);
void (*const dir_miso)(void *data, rt_int32_t state); void (const udelay)(rt_uint32_t us);
rt_uint32_t delay_us; /
sclk, mosi and miso line delay */
};

这个抽象出的方法作为软件spi总线类rt_spi_bit_obj的成员,为何还要抽象出来?因为下面还有个子类——具体硬件厂家的软件spi总线——因为各个硬件操作gpio具体实现是不同的,但是这些方法是都一样,所以抽象出来——跨硬件平台,这个框架才叫框架,这个框架才有意义。

接着就是硬件spi软总线层次了,各个厂家bsp创建各自的软spi总线子类对象,实现软件spi基类的ops方法。

在这里插入图片描述
如上类图,软件spi基类rt_spi_bit_obj继承spi总线基类重写spi总线基类的纯虚方法了——configure和xfer,然后它又抽象了spi软件模拟gpio、延时等操作,这些作为纯虚方法需要它的子类来实现——也就是具体bsp来实现。

1.2.1实例

bsp / stm32 / libraries / HAL_Drivers / drivers 下的drv_soft_spi.h
drv_soft_spi.c

在drv_soft_spi.h 中定义了可以实例化的软件spi总线类:
struct stm32_soft_spi
{
struct rt_spi_bit_obj spi;
struct rt_spi_bit_ops ops;
struct stm32_soft_spi_config *cfg;
};

可以看到struct stm32_soft_spi类继承自spi软总线基类
struct rt_spi_bit_obj spi;
第二个成员是spi软总线基类中定义的方法,struct rt_spi_bit_ops ops; ,又在struct stm32_soft_spi
这里开个成员专门承载它,重复,但是为了给软总线基类的方法赋值。
第三个成员是配置。

在drv_soft_spi.c中重写了spi软总线基类定义的方法
,如下:
static struct rt_spi_bit_ops stm32_soft_spi_ops =
{
.data = RT_NULL,
.tog_sclk = stm32_tog_sclk,
.set_sclk = stm32_set_sclk,
.set_mosi = stm32_set_mosi,
.set_miso = stm32_set_miso,
.get_sclk = stm32_get_sclk,
.get_mosi = stm32_get_mosi,
.get_miso = stm32_get_miso,
.dir_mosi = stm32_dir_mosi,
.dir_miso = stm32_dir_miso,
.udelay = stm32_udelay,
.delay_us = 1,
};

其构造函数是rt_hw_softspi_init,它调用rt_spi_bit_add_bus开启struct stm32_soft_spi类的实例化/构造流程。
请添加图片描述

如上类图,stm32_soft_spi类继承spi软总线基类必须重写spi软总线基类的纯虚方法,这些方法已是通过对应bsp的sdk厂家驱动来实现,完全硬件强相关了。其他厂家一样——这样就对接到统一的框架里面去,实现跨硬件平台了。

2.spi设备

回顾下spi总线基类的定义

struct rt_spi_bus
{
struct rt_device parent;
rt_uint8_t mode;
const struct rt_spi_ops *ops;
struct rt_mutex lock;
struct rt_spi_device *owner;
};

其中“struct rt_spi_device *owner”,这是spi设备对象指针,为何在spi总线基类里加个spi设备的指针成员,且还叫owner(字面意思:spi总线持有者或霸占者或拥有者)?
用面向对象的思想来分析下,spi总线和spi设备是啥关系?可以搜下面向对象思想中对象关系有哪些,有包含(拥有)关系,聚合关系,关联关系等等。而spi总线和spi设备就是关联关系吧。谁关联谁?我觉得是互相关联吧。你看,它们的关系特点:一个spi总线上可以挂载很多个spi设备,同一时刻,一个spi总线只能和一个spi设备通信,这样看来同一个spi总线上的spi设备是分时共享这个spi总线的,spi设备之间是有竞争关系的,spi总线就相当于共享资源,同一时刻只能有一个spi设备霸占。所以上面抽象出的spi总线基类里有一个owner标志,表示当前是哪个spi设备在使用/霸占/持有spi总线。那当然还得有把锁来处理这个竞争关系。
那么同样的,spi设备里肯定也有一个指针指向它挂载到那个spi总线上了——因为它们是关联关系。
于是看下rtt抽象出的spi设备类rt_spi_device定义:
struct rt_spi_device
{
struct rt_device parent;
struct rt_spi_bus *bus;
struct rt_spi_configuration config;
rt_base_t cs_pin;
void *user_data;
};

果然不出所料,有个spi总线基类指针来表明该spi设备挂载到哪个spi总线上了。

spi设备初始化/注册/构造:
spi_core.c 中的rt_spi_bus_attach_device_cspin函数,它会调用spi_dev.c中的rt_spidev_device_init函数。这样它会创建spi设备,然后根据name找到spi总线,把spi总线指针保存到上面bus成员里,其实就是绑定spi设备和spi总线,实现关联关系。

到bsp的驱动层,一般都会分别实现软硬总线的spi设备构造接口,但是其实spi设备的构造实现最终都是调用spi_core.c 中的rt_spi_bus_attach_device_cspin函数来开启的。因为spi设备其实不区分软硬,只是spi设备对象里的bus指向是软还是硬总线是不同的。。

请添加图片描述

实例:
bsp / stm32 / libraries / HAL_Drivers / drivers 下的drv_spi.h
drv_spi.c
drv_soft_spi.h
drv_soft_spi.c

drv_spi.c定义了硬件spi总线类的设备构造函数rt_hw_spi_device_attach来实现挂载到硬件spi总线的spi设备实例化。
drv_soft_spi.c定义了软件spi总线类的设备构造函数rt_hw_softspi_device_attach来实现挂载到软件spi总线的spi设备实例化。
其实它们的实现都是对rt_spi_bus_attach_device_cspin的封装,直接用rt_spi_bus_attach_device_cspin来实例化挂载到软硬件spi总线上的设备没啥区别。
当然前提是对应的spi总线对象已经实例化了。

3.相关文件

/ components / drivers / spi 下
spi-bit-ops.c
spi-bit-ops.h
spi_core.c
spi_dev.c

spi_dev.c是spi总线基类和spi设备基类的注册地,
rt_spi_bus_device_init
rt_spidev_device_init
实现对rt_device基类的注册,核心是重写父类rt_device的init/read等方法。
从spi总线基类和spi设备基类的重写方法实现上看,它们都调用的是rt_spi_transfer函数。

rt_spi_transfer在哪里?在spi_core.c 里,看它的实现最终调用的是:
device->bus->ops->configure
device->bus->ops->xfer
也就是spi总线基类对子类定义(约束)的方法。
就是从这里出现了跳转,分支,岔路:
(1)对于spi软总线子类来说,调用的是spi-bit-ops.c中定义的方法:
static const struct rt_spi_ops spi_bit_bus_ops =
{
.configure = spi_bit_configure,
.xfer = spi_bit_xfer,
};
spi_bit_xfer函数中调用的软总线基类对其子类定义的struct rt_spi_bit_ops方法,这些方法就是操作4线或3线的引脚高低电平操作,还有us延时,这些由bsp驱动层来实现,我想可以参照软件i2c总线,利用pin设备来实现自洽。

(2)对于硬件spi子类(bsp驱动层实现)来说,走的是各个bsp实现的硬件spi传输。

另外在spi_core.c 中开启spi总线基类和spi设备初始化的接口:
rt_spi_bus_register
rt_spi_bus_attach_device_cspin

rt_spi_bus_register函数会调用spi_dev.c中的rt_spi_bus_device_init。

rt_spi_bus_attach_device_cspin函数会调用spi_dev.c中的rt_spidev_device_init函数。

从以上分析看,spi_core.c 是个中转站/中间人/跳板/跳转点/分发站。

4.总结

参见rtt的oopc实现特点

5.内部调用流程

参见内部调用流程

6.应用程序使用流程

参见官方文档等路径

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值