背景
既然要使用SPI模块,那就得了解RTT的SPI是如何设计的,这样才能搞懂SPI的使用逻辑,以便更好的适配RTT的SPI驱动,也避免使用过程中踩雷。
SPI驱动框架解析
初级SPI
此SPI的作用为注册不带cs的spi设备或已经带硬件cs脚的spi设备。
注册入口
还是老思路,rt-thread源码中,bsp目录下不少芯片都有适配spi驱动(驱动文件名为drv_spi.c),因此,我们分析SPI驱动入口也从这开始查看。以GD32驱动为例,其注册入口如下:
#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读入口 _spi_bus_device_write, // SPI写入口 RT_NULL }; #endif 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 = RT_NULL; #endif /* register to device manager */ return rt_device_register(device, name, RT_DEVICE_FLAG_RDWR); } 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_PRIO); /* set ops */ bus->ops = ops; /* initialize owner */ bus->owner = RT_NULL; /* set bus mode */ bus->mode = RT_SPI_BUS_MODE_SPI; return RT_EOK; } static struct rt_spi_ops gd32_spi_ops = { .configure = spi_configure, // 配置SPI参数 .xfer = spixfer, // SPI数据传输 }; int rt_hw_spi_init(void) { int result = 0; int i; for (i = 0; i < sizeof(spi_bus_obj) / sizeof(spi_bus_obj[0]); i++) { spi_bus_obj[i].spi_bus->parent.user_data = (void *)&spi_bus_obj[i]; // 注册SPI驱动入口 result = rt_spi_bus_register(spi_bus_obj[i].spi_bus, spi_bus_obj[i].bus_name, &gd32_spi_ops); RT_ASSERT(result == RT_EOK); LOG_D("%s bus init done", spi_bus_obj[i].bus_name); } return result; } INIT_BOARD_EXPORT(rt_hw_spi_init);
从代码上看,spi驱动的注册,实际上也还是走到了rt_device_register,注册的设备类型为 RT_Device_Class_SPIBUS,而驱动层,向系统注册SPI设备时,需相应的实现两个接口,一个是configure,用于配置SPI工作模式,另一个时xfer,用于实现spi数据传递。
SPI读接口
在SPI注册代码中,我们可以看到spi读的入口,其名称为_spi_bus_device_read,由于注册符合标准设备注册流程,那应用层肯定可以通过rt_device_read的方式实现读。在这里我们需向上以及向下查看,看该接口具体是如何使用的。
rt_ssize_t rt_spi_transfer(struct rt_spi_device *device, const void *send_buf, void *recv_buf, rt_size_t length) { rt_ssize_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) { //如果spi未初始化,则初始化spi /* 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 */ LOG_E("SPI device %s configuration failed", device->parent.parent.name); 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) { LOG_E("SPI device %s transfer failed", device->parent.parent.name); goto __exit; } } else { return -RT_EIO; } __exit: rt_mutex_release(&(device->bus->lock)); return result; } static rt_ssize_t _spi_bus_device_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) { struct rt_spi_bus *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); }
SPI写接口
在SPI注册代码中,我们可以看到spi写的入口,其名称为_spi_bus_device_write,由于注册符合标准设备注册流程,那应用层肯定可以通过rt_device_write的方式实现读。在这里我们需向上以及向下查看,看该接口具体是如何使用的。
//rt_spi_transfer函数见读接口 static rt_ssize_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; 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); }
rt_spi_configure 工作模式配置接口
rt_err_t rt_spi_bus_configure(struct rt_spi_device *device) { rt_err_t result = -RT_ERROR; if (device->bus != RT_NULL) { result = rt_mutex_take(&(device->bus->lock), RT_WAITING_FOREVER); if (result == RT_EOK) { if (device->bus->owner == device) { /* current device is using, re-configure SPI bus */ result = device->bus->ops->configure(device, &device->config); if (result != RT_EOK) { /* configure SPI bus failed */ LOG_E("SPI device %s configuration failed", device->parent.parent.name); } } /* release lock */ rt_mutex_release(&(device->bus->lock)); } } else { result = RT_EOK; } return result; } rt_err_t rt_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg) { RT_ASSERT(device != RT_NULL); RT_ASSERT(cfg != RT_NULL); /* reset the CS pin */ if (device->cs_pin != PIN_NONE) { if (cfg->mode & RT_SPI_CS_HIGH) rt_pin_write(device->cs_pin, PIN_LOW); else rt_pin_write(device->cs_pin, PIN_HIGH); } /* If the configurations are the same, we don't need to set again. */ if (device->config.data_width == cfg->data_width && device->config.mode == (cfg->mode & RT_SPI_MODE_MASK) && device->config.max_hz == cfg->max_hz) { return RT_EOK; } /* 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; return rt_spi_bus_configure(device); }
驱动层适配框架
经过上面的分析,已经驱动,我们需要实现configure和xfer两个入口,而由于入口少,因此入口所需要实现的功能也比较复杂,这里给出一个相对全面的模板:
configure
static rt_err_t spi_configure(struct rt_spi_device* device, struct rt_spi_configuration* configuration) { /* 设置带宽 */ /* SPI速率设置 */ /* SPI工作模式*/ /*驱动特有设置*/ /* 初始化SPI */ return RT_EOK; };
xfer
static rt_ssize_t spixfer(struct rt_spi_device *device, struct rt_spi_message *message) { // 校验输入参数 RT_ASSERT(device != RT_NULL); RT_ASSERT(device->bus != RT_NULL); RT_ASSERT(message != RT_NULL); rt_err_t err = RT_EOK; if (message->cs_take && !(device->config.mode & RT_SPI_NO_CS) && (device->cs_pin != PIN_NONE)) { // 使能CS } if (message->length > 0) { if (message->send_buf == RT_NULL && message->recv_buf != RT_NULL) { /**< 读取数据 */ } else if (message->send_buf != RT_NULL && message->recv_buf == RT_NULL) { /**< 发送数据 */ } else if (message->send_buf != RT_NULL && message->recv_buf != RT_NULL) { /**< 读写数据 */ } } if (message->cs_release && !(device->config.mode & RT_SPI_NO_CS) && (device->cs_pin != PIN_NONE)) { // cs 禁用 } return err; }
补充接口
从这里看,实际上框架层已经较好的实现了SPI的写入口,既考虑到了多线程使用的场景,也考虑到了不同设备对spi接口的要求不一样的场景。但是呢,标准的rt_device_read毕竟不是那么的好理解成spi的操作。因此spi实际上还标准化了一系列的接口,以便上层调用:
rt_spi_transfer 数据单次传输接口
这个接口在读接口中有解析,实际上就是SPI传递一次数据的实现
rt_spi_transfer_message 数据多次传输接口
struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device, struct rt_spi_message *message) { rt_err_t result; struct rt_spi_message *index; RT_ASSERT(device != RT_NULL); /* get first message */ index = message; if (index == RT_NULL) return index; result = rt_mutex_take(&(device->bus->lock), RT_WAITING_FOREVER); if (result != RT_EOK) { return index; } /* configure SPI bus */ 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 */ goto __exit; } } /* transmit each SPI message */ while (index != RT_NULL) { /* transmit SPI message */ result = device->bus->ops->xfer(device, index); if (result < 0) { break; } index = index->next; } __exit: /* release bus lock */ rt_mutex_release(&(device->bus->lock)); return index; }
从接口实现上看,其实这就是传递多次版的rt_spi_transfer实现,不过需要注意的是,多次传输,spi的数据不再是在函数内部组包,而是需要组好包后交由函数调用。
其他接口
老实说,后面的接口,个人不太明白为什么要重复写,因为这些接口实际上都可以使用上面的两个transfer函数实现。我只能猜这些接口都是某些特定场景下的实现吧。
rt_spi_send_then_send
发送一次数据后再发送一次数据。
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_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 */ LOG_E("SPI device %s configuration failed", device->parent.parent.name); goto __exit; } } /* send data1 */ message.send_buf = send_buf1; message.recv_buf = RT_NULL; message.length = send_length1; message.cs_take = 1; message.cs_release = 0; message.next = RT_NULL; result = device->bus->ops->xfer(device, &message); if (result < 0) { LOG_E("SPI device %s transfer failed", device->parent.parent.name); goto __exit; } /* send data2 */ message.send_buf = send_buf2; message.recv_buf = RT_NULL; message.length = send_length2; message.cs_take = 0; message.cs_release = 1; message.next = RT_NULL; result = device->bus->ops->xfer(device, &message); if (result < 0) { LOG_E("SPI device %s transfer failed", device->parent.parent.name); goto __exit; } result = RT_EOK; } else { return -RT_EIO; } __exit: rt_mutex_release(&(device->bus->lock)); return result; }
rt_spi_send_then_recv
发送一次数据后读一次数据
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 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 */ LOG_E("SPI device %s configuration failed", device->parent.parent.name); goto __exit; } } /* send data */ message.send_buf = send_buf; message.recv_buf = RT_NULL; message.length = send_length; message.cs_take = 1; message.cs_release = 0; message.next = RT_NULL; result = device->bus->ops->xfer(device, &message); if (result < 0) { LOG_E("SPI device %s transfer failed", device->parent.parent.name); goto __exit; } /* recv data */ message.send_buf = RT_NULL; message.recv_buf = recv_buf; message.length = recv_length; message.cs_take = 0; message.cs_release = 1; message.next = RT_NULL; result = device->bus->ops->xfer(device, &message); if (result < 0) { LOG_E("SPI device %s transfer failed", device->parent.parent.name); goto __exit; } result = RT_EOK; } else { return -RT_EIO; } __exit: rt_mutex_release(&(device->bus->lock)); return result; }
rt_spi_sendrecv8
rt_err_t rt_spi_sendrecv8(struct rt_spi_device *device, rt_uint8_t senddata, rt_uint8_t *recvdata) { rt_ssize_t len = rt_spi_transfer(device, &senddata, recvdata, 1); if (len < 0) { return (rt_err_t)len; } else { return RT_EOK; } }
rt_spi_sendrecv16
rt_err_t rt_spi_sendrecv16(struct rt_spi_device *device, rt_uint16_t senddata, rt_uint16_t *recvdata) { rt_ssize_t len; rt_uint16_t tmp; if (device->config.mode & RT_SPI_MSB) { tmp = ((senddata & 0xff00) >> 8) | ((senddata & 0x00ff) << 8); senddata = tmp; } len = rt_spi_transfer(device, &senddata, recvdata, 2); if(len < 0) { return (rt_err_t)len; } if (device->config.mode & RT_SPI_MSB) { tmp = ((*recvdata & 0xff00) >> 8) | ((*recvdata & 0x00ff) << 8); *recvdata = tmp; } return RT_EOK; }
次级spi
注册入口
次级spi注册入口,顾名思义,在初级spi的基础上,增加一些必要信息,再次注册的spi设备。其代码如下:
#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 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); } rt_err_t rt_spi_bus_attach_device_cspin(struct rt_spi_device *device, const char *name, const char *bus_name, rt_base_t cs_pin, 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; if(cs_pin != PIN_NONE) { rt_pin_mode(cs_pin, PIN_MODE_OUTPUT); } rt_memset(&device->config, 0, sizeof(device->config)); device->parent.user_data = user_data; device->cs_pin = cs_pin; return RT_EOK; } /* not found the host bus */ return -RT_ERROR; }
从实现上看,其实这个接口就是在初级spi无cs脚时,再封装一层,增加一个gpo方式实现的cs脚来保证上层应用不关注SPI的硬件配置信息。
读写入口
实际上这个入口是通过标准设备接口实现读写的入口,但目前来看,这两个接口属于历史遗留,或者单纯为了满足标准设备框架的实现而留些来的入口。
static rt_ssize_t _spidev_device_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) { struct rt_spi_device *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_ssize_t _spidev_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { struct rt_spi_device *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); }
control入口
其实这个入口可以去掉,因为实际无功能。
static rt_err_t _spidev_device_control(rt_device_t dev, int cmd, void *args) { switch (cmd) { case 0: /* set device */ break; case 1: break; } return RT_EOK; }
总结
分析到这,实际上spi设备的框架已经较为清晰了,其框图如下: