文章目录
一、SPI设备对象管理与示例
前篇博客介绍了I/O设备模型框架,并以PIN设备驱动框架为例说明了RT-thread I/O设备模型框架的实现原理,下面以SPI设备驱动框架为例再做进一步介绍。SPI设备与QSPI设备CubeMX配置及HAL API库函数的使用可参考博客:SPI + QSPI + HAL。
最上层的I/O设备管理层在前篇博客已经介绍过了,下面从中间的SPI设备驱动框架层开始介绍。
1.1 SPI设备驱动框架层
由于SPI设备是主从通信,所以需要对主设备和从设备分别进行描述,SPI主设备称为SPI总线,SPI从设备称为SPI设备,一个SPI总线上可以绑定多个SPI设备。
- SPI总线控制块
先看SPI总线在驱动框架层是如何描述的:
// rt-thread-4.0.1\components\drivers\include\drivers\spi.h
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;
};
/**
* SPI operators
*/
struct rt_spi_ops
{
rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration);
rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message);
};
- SPI设备控制块
SPI总线虽然可绑定多个SPI设备,但每次只能与一个从设备通信,所以SPI总线在某时刻拥有一个SPI设备,SPI设备在驱动框架层的描述如下:
// rt-thread-4.0.1\components\drivers\include\drivers\spi.h
/**
* SPI Virtual BUS, one device must connected to a virtual BUS
*/
struct rt_spi_device
{
struct rt_device parent;
struct rt_spi_bus *bus;
struct rt_spi_configuration config;
void *user_data;
};
/**
* SPI configuration structure
*/
struct rt_spi_configuration
{
rt_uint8_t mode;
rt_uint8_t data_width;
rt_uint16_t reserved;
rt_uint32_t max_hz;
};
/**
* SPI message structure
*/
struct rt_spi_message
{
const void *send_buf;
void *recv_buf;
rt_size_t length;
struct rt_spi_message *next;
unsigned cs_take : 1;
unsigned cs_release : 1;
};
SPI总线对SPI设备的访问只有两种方法:一个是对SPI设备进行参数配置,配置函数是rt_spi_ops.configure,配置参数结构体为rt_spi_configuration;另一个是在SPI主从设备间进行数据传输,传输数据的函数是rt_spi_ops.xfer,传输的消息结构体为rt_spi_message。
- QSPI设备控制块
// rt-thread-4.0.1\components\drivers\include\drivers\spi.h
struct rt_qspi_device
{
struct rt_spi_device parent;
struct rt_qspi_configuration config;
void (*enter_qspi_mode)(struct rt_qspi_device *device);
void (*exit_qspi_mode)(struct rt_qspi_device *device);
};
struct rt_qspi_configuration
{
struct rt_spi_configuration parent;
/* The size of medium */
rt_uint32_t medium_size;
/* double data rate mode */
rt_uint8_t ddr_mode;
/* the data lines max width which QSPI bus supported, such as 1, 2, 4 */
rt_uint8_t qspi_dl_width ;
};
struct rt_qspi_message
{
struct rt_spi_message parent;
/* instruction stage */
struct
{
rt_uint8_t content;
rt_uint8_t qspi_lines;
} instruction;
/* address and alternate_bytes stage */
struct
{
rt_uint32_t content;
rt_uint8_t size;
rt_uint8_t qspi_lines;
} address, alternate_bytes;
/* dummy_cycles stage */
rt_uint32_t dummy_cycles;
/* number of lines in qspi data stage, the other configuration items are in parent */
rt_uint8_t qspi_data_lines;
};
QSPI总线对QSPI设备的访问操作跟SPI一致,所以QSPI总线就不再单独用结构体描述了,直接使用SPI总线结构体,但作为QSPI总线使用时,rt_spi_ops两个操作函数指针指向的函数不同,传入的参数指针也不同,QSPI总线拥有的设备也会指向rt_qspi_device,由于rt_qspi_device继承自rt_spi_device,二者首地址一致,rt_spi_bus.owner指向QSPI设备也是可以的。
QSPI实际上是对SPI的扩展增强,从SPI设备与QSPI设备的描述结构体也可以看出,QSPI设备结构体都继承自SPI结构体,并对其进行成员扩展。为了支持QSPI的命令序列,rt_qspi_message扩展出了QSPI命令序列的指令、地址、复用交替字节、空时钟周期、数据等五个阶段的参数描述。
- SPI总线接口函数
I/O设备管理层要想访问某设备,需要在下面的设备驱动层创建设备实例,并将该设备注册到I/O设备管理层,下面先看看SPI总线的创建与注册过程:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
rt_err_t rt_spi_bus_register(struct rt_spi_bus *bus,
const char *name,
const struct rt_spi_ops *ops)
{
rt_err_t result;
result = rt_spi_bus_device_init(bus, name);
if (result != RT_EOK)
return result;
/* initialize mutex lock */
rt_mutex_init(&(bus->lock), name, RT_IPC_FLAG_FIFO);
/* set ops */
bus->ops = ops;
/* initialize owner */
bus->owner = RT_NULL;
/* set bus mode */
bus->mode = RT_SPI_BUS_MODE_SPI;
return RT_EOK;
}
// rt-thread-4.0.1\components\drivers\spi\spi_dev.c
rt_err_t rt_spi_bus_device_init(struct rt_spi_bus *bus, const char *name)
{
struct rt_device *device;
RT_ASSERT(bus != RT_NULL);
device = &bus->parent;
/* set device type */
device->type = RT_Device_Class_SPIBUS;
/* initialize device interface */
#ifdef RT_USING_DEVICE_OPS
device->ops = &spi_bus_ops;
#else
device->init = RT_NULL;
device->open = RT_NULL;
device->close = RT_NULL;
device->read = _spi_bus_device_read;
device->write = _spi_bus_device_write;
device->control = _spi_bus_device_control;
#endif
/* register to device manager */
return rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
}
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops spi_bus_ops =
{
RT_NULL,
RT_NULL,
RT_NULL,
_spi_bus_device_read,
_spi_bus_device_write,
_spi_bus_device_control
};
#endif
static rt_size_t _spi_bus_device_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
{
struct rt_spi_bus *bus = (struct rt_spi_bus *)dev;
RT_ASSERT(bus != RT_NULL);
RT_ASSERT(bus->owner != RT_NULL);
return rt_spi_transfer(bus->owner, RT_NULL, buffer, size);
}
static rt_size_t _spi_bus_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
struct rt_spi_bus *bus = (struct rt_spi_bus *)dev;
RT_ASSERT(bus != RT_NULL);
RT_ASSERT(bus->owner != RT_NULL);
return rt_spi_transfer(bus->owner, buffer, RT_NULL, size);
}
SPI驱动框架层向上层注册的操作函数集合spi_bus_ops最终通过调用rt_spi_transfer与rt_spi_configure实现,这两个函数实际最终调用的是rt_spi_bus.ops(也即rt_spi_ops),这两个函数的实现由下层的SPI设备驱动层实现。
- SPI设备接口函数
SPI总线初始化并注册完成后,需要把SPI设备绑定到相应的总线上才能进行SPI通信,SPI设备绑定到SPI总线的过程如下:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
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_err_t rt_spidev_device_init(struct rt_spi_device *dev, const char *name)
{
struct rt_device *device;
RT_ASSERT(dev != RT_NULL);
device = &(dev->parent);
/* set device type */
device->type = RT_Device_Class_SPIDevice;
#ifdef RT_USING_DEVICE_OPS
device->ops = &spi_device_ops;
#else
device->init = RT_NULL;
device->open = RT_NULL;
device->close = RT_NULL;
device->read = _spidev_device_read;
device->write = _spidev_device_write;
device->control = _spidev_device_control;
#endif
/* register to device manager */
return rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
}
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops spi_device_ops =
{
RT_NULL,
RT_NULL,
RT_NULL,
_spidev_device_read,
_spidev_device_write,
_spidev_device_control
};
#endif
/* SPI Dev device interface, compatible with RT-Thread 0.3.x/1.0.x */
static rt_size_t _spidev_device_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
{
struct rt_spi_device *device = (struct rt_spi_device *)dev;
RT_ASSERT(device != RT_NULL);
RT_ASSERT(device->bus != RT_NULL);
return rt_spi_transfer(device, RT_NULL, buffer, size);
}
static rt_size_t _spidev_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
struct rt_spi_device *device= = (struct rt_spi_device *)dev;
RT_ASSERT(device != RT_NULL);
RT_ASSERT(device->bus != RT_NULL);
return rt_spi_transfer(device, buffer, RT_NULL, size);
}
SPI设备向上层注册的操作函数集合与SPI总线类似,用户可以通过上层提供的统一接口rt_device_ops来访问SPI设备。
SPI设备也向用户提供了SPI设备驱动框架层的一些API接口,最主要的是rt_spi_ops的两个接口:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
/**
* This function set configuration on SPI device.
*
* @param device the SPI device attached to SPI bus
* @param cfg the config parameter to be set to SPI device
*
* @return RT_EOK if set config successfully.
*/
rt_err_t rt_spi_configure(struct rt_spi_device *device,
struct rt_spi_configuration *cfg);
/**
* This function transfers a message list to the SPI device.
*
* @param device the SPI device attached to SPI bus
* @param message the message list to be transmitted to SPI device
*
* @return RT_NULL if transmits message list successfully,
* SPI message which be transmitted failed.
*/
struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device,
struct rt_spi_message *message);
SPI设备接口除了上面两个外,还有为方便用户,基于这两个函数扩展出来的接口函数:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
/**
* This function transmits data to SPI device.
*
* @param device the SPI device attached to SPI bus
* @param send_buf the buffer to be transmitted to SPI device.
* @param recv_buf the buffer to save received data from SPI device.
* @param length the length of transmitted data.
*
* @return the actual length of transmitted.
*/
rt_size_t rt_spi_transfer(struct rt_spi_device *device,
const void *send_buf,
void *recv_buf,
rt_size_t length);
/* send data then receive data from SPI device */
rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device,
const void *send_buf,
rt_size_t send_length,
void *recv_buf,
rt_size_t recv_length);
rt_err_t rt_spi_send_then_send(struct rt_spi_device *device,
const void *send_buf1,
rt_size_t send_length1,
const void *send_buf2,
rt_size_t send_length2);
rt_size_t rt_spi_recv(struct rt_spi_device *device,
void *recv_buf,
rt_size_t length);
rt_size_t rt_spi_send(struct rt_spi_device *device,
const void *send_buf,
rt_size_t length);
一个SPI总线有时需要连接多个SPI设备,但每次只能与其中一个SPI设备通信,为解决SPI总线在多个SPI设备中的访问互斥问题,SPI设备驱动框架层提供了SPI总线与SPI设备的获取/释放函数接口:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
/**
* This function takes SPI bus.
*
* @param device the SPI device attached to SPI bus
*
* @return RT_EOK on taken SPI bus successfully. others on taken SPI bus failed.
*/
rt_err_t rt_spi_take_bus(struct rt_spi_device *device);
/**
* This function releases SPI bus.
*
* @param device the SPI device attached to SPI bus
*
* @return RT_EOK on release SPI bus successfully.
*/
rt_err_t rt_spi_release_bus(struct rt_spi_device *device);
/**
* This function take SPI device (takes CS of SPI device).
*
* @param device the SPI device attached to SPI bus
*
* @return RT_EOK on release SPI bus successfully. others on taken SPI bus failed.
*/
rt_err_t rt_spi_take(struct rt_spi_device *device);
/**
* This function releases SPI device (releases CS of SPI device).
* * @param device the SPI device attached to SPI bus
* * @return RT_EOK on release SPI device successfully.
*/
rt_err_t rt_spi_release(struct rt_spi_device *device);
- QSPI总线接口函数
QSPI总线与SPI总线虽然共有描述结构体,但其中的成员参数配置有些许差异,为此QSPI总线创建与注册函数在SPI总线注册函数的基础上进行了封装,QSPI总线注册函数如下:
// rt-thread-4.0.1\components\drivers\spi\qspi_core.c
rt_err_t rt_qspi_bus_register(struct rt_spi_bus *bus, const char *name, const struct rt_spi_ops *ops)
{
rt_err_t result = RT_EOK;
result = rt_spi_bus_register(bus, name, ops);
if(result == RT_EOK)
{
/* set SPI bus to qspi modes */
bus->mode = RT_SPI_BUS_MODE_QSPI;
}
return result;
}
- QSPI设备接口函数
QSPI设备绑定到QSPI总线的函数并没有在QSPI驱动框架层提供,这里就不介绍了。
QSPI设备的配置与传输函数声明如下:
// rt-thread-4.0.1\components\drivers\spi\qspi_core.c
/**
* This function can set configuration on QSPI device.
*
* @param device the QSPI device attached to QSPI bus.
* @param cfg the configuration pointer.
*
* @return the actual length of transmitted.
*/
rt_err_t rt_qspi_configure(struct rt_qspi_device *device, struct rt_qspi_configuration *cfg);
/**
* This function transmits data to QSPI device.
*
* @param device the QSPI device attached to QSPI bus.
* @param message the message pointer.
*
* @return the actual length of transmitted.
*/
rt_size_t rt_qspi_transfer_message(struct rt_qspi_device *device, struct rt_qspi_message *message);
/**
* This function can send data then receive data from QSPI device
*
* @param device the QSPI device attached to QSPI bus.
* @param send_buf the buffer to be transmitted to QSPI device.
* @param send_length the number of data to be transmitted.
* @param recv_buf the buffer to be recivied from QSPI device.
* @param recv_length the data to be recivied.
*
* @return the status of transmit.
*/
rt_err_t rt_qspi_send_then_recv(struct rt_qspi_device *device, const void *send_buf, rt_size_t send_length,void *recv_buf, rt_size_t recv_length);
/**
* This function can send data to QSPI device
*
* @param device the QSPI device attached to QSPI bus.
* @param send_buf the buffer to be transmitted to QSPI device.
* @param send_length the number of data to be transmitted.
*
* @return the status of transmit.
*/
rt_err_t rt_qspi_send(struct rt_qspi_device *device, const void *send_buf, rt_size_t length);
QSPI的接口函数最终由注册到QSPI总线的操作函数集合rt_spi_ops实现,rt_spi_ops中的两个函数则有QSPI设备驱动层提供。
1.2 SPI设备驱动层
在SPI设备驱动层,并没有刻意将SPI主从设备分开描述,SPI总线与SPI设备更多的是方便上层协议管理而抽象出来的。
- SPI设备驱动描述
在STM32 HAL库中有结构体SPI_HandleTypeDef描述SPI外设,RT-Thread为方便上面SPI设备驱动框架层的管理又增添了一些成员变量,比如配置参数(包括SPI配置参数与DMA配置参数)等,SPI设备驱动描述如下:
// libraries\HAL_Drivers\drv_spi.h
/* stm32 spi dirver class */
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 stm32_spi_config
{
SPI_TypeDef *Instance;
char *bus_name;
struct dma_config *dma_rx, *dma_tx;
};
struct stm32_spi_device
{
rt_uint32_t pin;
char *bus_name;
char *device_name;
};
struct stm32_hw_spi_cs
{
GPIO_TypeDef* GPIOx;
uint16_t GPIO_Pin;
};
SPI设备有一个片选引脚NCS,一般SPI设备只有一个片选引脚,当出现一主多从SPI设备时,SPI主设备的片选引脚不够用,可以使用GPIO引脚作为NCS片选引脚,每个GPIO控制一个SPI从设备的片选使能,这种方式成为NCS的软件控制模式,也是较常用的方式。
SPI设备驱动可由结构体stm32_spi描述,为方便向上面的SPI驱动框架层注册绑定设备,还提供了stm32_spi_device结构体,包含SPI总线名、SPI设备名、SPI设备的片选引脚等。
- SPI设备驱动接口
首先看SPI驱动初始化过程:
// libraries\HAL_Drivers\drv_spi.c
int rt_hw_spi_init(void)
{
stm32_get_dma_info();