在spi驱框架中,一般会自己初始化一个总线设备(rt_spi_bus),当我们通过挂载后,会相应注册一个从设备(rt_spi_device)。
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);
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);
……
}
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
函数。
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_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;
类型的,由于结构体的地址和第一个元素的首地址相同。这里我们可以在两个结构体类型之间转换。
这个函数是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赋值了???然而我找了好多遍都没有。。。。求解答!!)
这里通过我搜索可以看出,基本device->bus->ops->configure(device, &device->config)
的调用都在数据传输的那几个函数里面。
在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);
这个函数。
这节的简单解释就到这儿了。总结一下,drv_spi.c主要是完成了spi_bus结构体中ops的实现,和注册了一个总线设备。spi_dev.c主要是device的操作函数对接。以及对device设备的初始化等。spi_core.c就是提供给用户的上层接口。反正无论怎么调用,都是相近一切办法去调用底层的ops。