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;
};
缺图对象图,ops展开方法用纯虚方法表示或者指针,但ops展开后成为一个个纯虚方法更为贴切。
1.1硬件spi总线子类
对于硬件spi子类,则负责实现rt_spi_bus的ops操作方法。
缺图对象图,ops展开的一个个方法用虚方法表示或者普通方法即可,意味着重写父类方法。
1.1.1实例
bsp / stm32 / libraries / HAL_Drivers / drivers 下的
drv_spi.h
drv_spi.c
在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;
};
这样看着更舒服。
drv_spi.c实现了spi总线基类的方法如下
static const struct rt_spi_ops stm_spi_ops =
{
.configure = spi_configure,
.xfer = spixfer,
};
drv_spi.c的rt_hw_spi_bus_init函数中通过调用rt_spi_bus_register函数从而开启了spi硬件总线子类对象的实例化或构造流程。最终结果是初始化了下spi硬件总线的实例化的结构体——包含spi总线基类的结构体,最终放到对象容器的链表里去管理。等待使用者拿出来使用。万事俱备只欠东风。
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方法。
缺图对象图,
(1)bus—>ops展开的一个个方法用虚方法表示或者普通方法即可,意味着重写父类spi总线基类的方法。
(2)软spi总线子类的ops展开的一个个方法用纯虚方法表示或者指针,但ops展开后成为一个个纯虚方法更为贴切。
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软总线基类定义的方法
rt_spi_bit_ops ops,如下:
stm32_tog_sclk
stm32_set_sclk
stm32_set_mosi
stm32_get_sclk
stm32_get_mosi
stm32_dir_mosi
stm32_dir_miso
stm32_udelay
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类的实例化/构造流程。
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.使用
官方文档说的就是如何使用的文档,而上面学的是分析注册/初始化/构造流程。