RT-Thread学习笔记(5):SPI驱动框架分析

1、两个设备模型

在spi驱框架中,一般会自己初始化一个总线设备(rt_spi_bus),当我们通过挂载后,会相应注册一个从设备(rt_spi_device)。

1、总线设备模型

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;
};

这个是会被RT-Thread的自动初始化机制自动注册进设备框架的。

int rt_hw_spi_init(void)
{
	……
    rt_hw_spi_bus_init();
    	->rt_spi_bus_register(……,);
    		->rt_spi_bus_device_init(bus, name);
    			->rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
}
INIT_BOARD_EXPORT(rt_hw_spi_init);

2、从设备模型

struct rt_spi_device
{
    struct rt_device parent;
    struct rt_spi_bus *bus;

    struct rt_spi_configuration config;
    void   *user_data;
};

这个设备一般是在挂载的时候被注册进设备框架。

rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
                                  const char           *name,
                                  const char           *bus_name,
                                  void                 *user_data)
{
	……
    rt_spidev_device_init(device, name);
    	->rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
    ……
}

2、与spi相关的三个文件

1、drv_spi.c

RT-Thread学习笔记(5):SPI驱动框架分析_第1张图片
以上是这个文件的几个函数:

  • stm32_spi_init:主要完成了spi硬件的初始化,主要完成这个函数:HAL_SPI_Init。
  • spixfer:spi总线数据传输,HAL_SPI_Transmit、HAL_SPI_Receive。
  • spi_configure:spi总线的配置,最后通过调用 stm32_spi_init函数完成spi的初始化。
  • rt_hw_spi_bus_init:主要是给结构体struct stm32_spi赋值,然后调用rt_spi_bus_register去把这个注册进spi设备框架中。
  • rt_hw_spi_device_attach:spi挂载的函数。(还没完)
  • rt_hw_spi_init:自动初始化接口。

这里说一下rt_hw_spi_device_attach函数,这个在官方文档有解释,大家想详细了解,建议查看一下官方文档。RT-Thread SPI

若使用 rt-thread/bsp/stm32 目录下的 BSP 则可以使用下面的函数挂载 SPI 设备到总线:
rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, GPIO_TypeDef* cs_gpiox, uint16_t cs_gpio_pin);

也就是说当我们是使用的stm32这款BSP的时候,我们可以直接调用这个函数进行spi从设备挂载。通过这个函数,它可以直接帮我们找到某一个gpio对应的引脚编号。这样子我们就不需要去查drv_gpio.c里面的引脚编号了。其实内部最终还是调用了rt_spi_bus_attach_device函数。

rt_hw_spi_bus_init()函数
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;
};

这几个函数中,最重要的函数也就是这个函数了。根据我以前的风格,我还是上张图,让你们看看,这个函数运行完,到底干了什么。

spi_config[0] = {                                               \
        .Instance = SPI2,                           \
        .bus_name = "spi2",                         \
        .irq_type = SPI2_IRQn,                      \
    }

RT-Thread学习笔记(5):SPI驱动框架分析_第2张图片

2、spi_core.c

RT-Thread学习笔记(5):SPI驱动框架分析_第3张图片
这上面的函数中,大部分官方文档都有解释,我这里对其中几个做出解释就行。

rt_spi_bus_attach_device()函数

这个函数主要是从设备挂载的函数,这个函数会注册一个从设备进设备框架中,现在说一下大概是怎么实现的。

rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
                                  const char           *name,
                                  const char           *bus_name,
                                  void                 *user_data)
{
    rt_err_t result;
    rt_device_t bus;

    /* get physical spi bus */
    bus = rt_device_find(bus_name);
    if (bus != RT_NULL && bus->type == RT_Device_Class_SPIBUS)
    {
        device->bus = (struct rt_spi_bus *)bus;

        /* initialize spidev device */
        result = rt_spidev_device_init(device, name);
        if (result != RT_EOK)
            return result;

        rt_memset(&device->config, 0, sizeof(device->config));
        device->parent.user_data = user_data;

        return RT_EOK;
    }

    /* not found the host bus */
    return -RT_ERROR;
}

首先是通过rt_device_find函数去找到spi总线设备。

device->bus = (struct rt_spi_bus *)bus;

通过这条语句,这个rt_spi_device结构体类型的device指向挂载spi的总线设备。

这里(struct rt_spi_bus *)bus的写法,因为在struct rt_spi_bus结构体的第一个元素就是一个struct rt_device parent;类型的,由于结构体的地址和第一个元素的首地址相同。这里我们可以在两个结构体类型之间转换。
RT-Thread学习笔记(5):SPI驱动框架分析_第4张图片

rt_spi_configure()函数

这个函数是spi参数配置函数,官方文档说,挂载完事需要配置参数也就是调用的这个函数。

rt_err_t rt_spi_configure(struct rt_spi_device        *device,
                          struct rt_spi_configuration *cfg)
{
    rt_err_t result;

    RT_ASSERT(device != RT_NULL);

    /* set configuration */
    device->config.data_width = cfg->data_width;
    device->config.mode       = cfg->mode & RT_SPI_MODE_MASK ;
    device->config.max_hz     = cfg->max_hz ;

    if (device->bus != RT_NULL)
    {
        result = rt_mutex_take(&(device->bus->lock), RT_WAITING_FOREVER);
        if (result == RT_EOK)
        {
            if (device->bus->owner == device)
            {
                device->bus->ops->configure(device, &device->config);
            }

            /* release lock */
            rt_mutex_release(&(device->bus->lock));
        }
    }

    return RT_EOK;
}

这个函数先是配置了device结构体的config值,然后就是if (device->bus->owner == device)这个判断,大家可以看我截图第二张,可以看出,如果在挂载后执行这个函数,这句话是不成立的,因为owner是RT_NULL。所以这个函数仅仅只是对config赋值。(这里我真的很疑惑,为什么在这个函数这个if不成立,我们就应该在这个函数进行初始化。还是说我这个代码读的有点问题,难道在这儿之前owner赋值了???然而我找了好多遍都没有。。。。求解答!!)
RT-Thread学习笔记(5):SPI驱动框架分析_第5张图片
这里通过我搜索可以看出,基本device->bus->ops->configure(device, &device->config)的调用都在数据传输的那几个函数里面。

rt_spi_transfer()函数

在spi_dev.c里面的函数中,基本都调用了这个函数。

rt_size_t rt_spi_transfer(struct rt_spi_device *device,
                          const void           *send_buf,
                          void                 *recv_buf,
                          rt_size_t             length)
{
    rt_err_t result;
    struct rt_spi_message message;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(device->bus != RT_NULL);

    result = rt_mutex_take(&(device->bus->lock), RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        if (device->bus->owner != device)
        {
            /* not the same owner as current, re-configure SPI bus */
            result = device->bus->ops->configure(device, &device->config);
            if (result == RT_EOK)
            {
                /* set SPI bus owner */
                device->bus->owner = device;
            }
            else
            {
                /* configure SPI bus failed */
                rt_set_errno(-RT_EIO);
                result = 0;
                goto __exit;
            }
        }

        /* initial message */
        message.send_buf   = send_buf;
        message.recv_buf   = recv_buf;
        message.length     = length;
        message.cs_take    = 1;
        message.cs_release = 1;
        message.next       = RT_NULL;

        /* transfer message */
        result = device->bus->ops->xfer(device, &message);
        if (result == 0)
        {
            rt_set_errno(-RT_EIO);
            goto __exit;
        }
    }
    else
    {
        rt_set_errno(-RT_EIO);

        return 0;
    }

__exit:
    rt_mutex_release(&(device->bus->lock));

    return result;
}

这个函数的if (device->bus->owner != device)这个判断就是成立的了,然后相应的下一句result = device->bus->ops->configure(device, &device->config);初始化也就在这儿。然后这个函数的主要功能就是判断设备初始化没有,如果没有就初始化,然后发数据,如果初始化了,就直接发数据。包括其他那几个传输函数,都是这样的。然后基本传输函数都是调用了device->bus->ops->xfer(device, &message);这个函数。

3、spi_dev.c

RT-Thread学习笔记(5):SPI驱动框架分析_第6张图片

最后

这节的简单解释就到这儿了。总结一下,drv_spi.c主要是完成了spi_bus结构体中ops的实现,和注册了一个总线设备。spi_dev.c主要是device的操作函数对接。以及对device设备的初始化等。spi_core.c就是提供给用户的上层接口。反正无论怎么调用,都是相近一切办法去调用底层的ops。

你可能感兴趣的:(RTThread笔记,学习,单片机,stm32)