IOT-OS之RT-Thread(九)--- SPI设备对象管理与SFUD管理框架

一、SPI设备对象管理与示例

前篇博客介绍了I/O设备模型框架,并以PIN设备驱动框架为例说明了RT-thread I/O设备模型框架的实现原理,下面以SPI设备驱动框架为例再做进一步介绍。SPI设备与QSPI设备CubeMX配置及HAL API库函数的使用可参考博客:SPI + QSPI + HAL

RT-Thread I/O设备模型框架
最上层的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();
    return rt_hw_spi_bus_ini
  • 9
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: RT-Thread是一个开源的嵌入式操作系统,它支持多种网络协议,如NB-IoT. 如果在使用RT-Thread上传数据到云平台时遇到没有数据流的问题,可能是由于网络连接问题或者云平台配置问题导致的。建议检查网络连接,确认云平台配置是否正确,并检查是否有防火墙或安全组限制了数据流量。 ### 回答2: RT-Thread是一个开源的实时操作系统,可以用于物联网设备的开发。NB-IoT(Narrowband Internet of Things)是一种低功耗广域网通信技术,适用于物联网设备的连接。而云平台是一个用于存储、管理和分析物联网设备数据的平台。 在RT-Thread中使用NB-IoT上传数据到云平台时,如果没有数据流,可能存在以下几种可能的原因: 1. 设备连接问题:首先要确保物联网设备已经成功连接到NB-IoT网络。检查设备的信号强度和联网状态,确认是否正常连接。 2. 云平台配置问题:确认设备的云平台配置是否正确。检查设备的云平台账号和密钥是否正确,确保设备可以正确地与云平台进行通信。 3. 数据传输问题:检查设备上传数据的程序是否正确。确认设备采集的数据是否正确,以及上传数据的协议和格式是否与云平台要求一致。 4. 云平台接收问题:云平台可能存在接收数据的问题。检查云平台的数据流配置是否正确,确保设备上传的数据可以被正确地接收和处理。 如果以上的检查都没有问题,仍然无法上传数据到云平台,可以尝试联系RT-Thread开发者社区或云平台的技术支持团队,寻求他们的帮助和建议。他们可能会提供更详细的解决方案,或者帮助排查可能存在的其他问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值