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

文章目录

  • 一、SPI设备对象管理与示例
    • 1.1 SPI设备驱动框架层
    • 1.2 SPI设备驱动层
    • 1.3 QSPI访问W25Q128示例
  • 二、SFUD管理与示例
    • 2.1 SFUD Flash描述
    • 2.2 SFUD Flash接口
    • 2.3 SFUD访问W25Q128示例
  • 更多文章:

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

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

IOT-OS之RT-Thread(九)--- SPI设备对象管理与SFUD管理框架_第1张图片
最上层的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_init();
}
INIT_BOARD_EXPORT(rt_hw_spi_init);

static int rt_hw_spi_bus_init(void)
{
    rt_err_t result;
    for (int i = 0; i < sizeof(spi_config) / sizeof(spi_config[0]); i++)
    {
        spi_bus_obj[i].config = &spi_config[i];
        spi_bus_obj[i].spi_bus.parent.user_data = &spi_config[i];
        spi_bus_obj[i].handle.Instance = spi_config[i].Instance;

        if (spi_bus_obj[i].spi_dma_flag & SPI_USING_RX_DMA_FLAG)
        {
            /* Configure the DMA handler for Transmission process */
			......
        }

        if (spi_bus_obj[i].spi_dma_flag & SPI_USING_TX_DMA_FLAG)
        {
            /* Configure the DMA handler for Transmission process */
           ......
        }

        result = rt_spi_bus_register(&spi_bus_obj[i].spi_bus, spi_config[i].bus_name, &stm_spi_ops);
        RT_ASSERT(result == RT_EOK);

        LOG_D("%s bus init done", spi_config[i].bus_name);
    }

    return result;
}

static struct stm32_spi spi_bus_obj[sizeof(spi_config) / sizeof(spi_config[0])] = {0};

static struct stm32_spi_config spi_config[] =
{
#ifdef BSP_USING_SPI1
    SPI1_BUS_CONFIG,
#endif
......
#ifdef BSP_USING_SPI6
    SPI6_BUS_CONFIG,
#endif
};

// libraries\HAL_Drivers\config\l4\spi_config.h

#ifdef BSP_USING_SPI1
#ifndef SPI1_BUS_CONFIG
#define SPI1_BUS_CONFIG                                     \
    {                                                       \
        .Instance = SPI1,                                   \
        .bus_name = "spi1",                                 \
    }
#endif /* SPI1_BUS_CONFIG */
#endif /* BSP_USING_SPI1 */

#ifdef BSP_SPI1_TX_USING_DMA
#ifndef SPI1_TX_DMA_CONFIG
#define SPI1_TX_DMA_CONFIG                                  \
    {                                                       \
        .dma_rcc = SPI1_TX_DMA_RCC,                         \
        .Instance = SPI1_TX_DMA_INSTANCE,                   \
        .request = SPI1_TX_DMA_REQUEST,                     \
        .dma_irq = SPI1_TX_DMA_IRQ,                         \
    }
#endif /* SPI1_TX_DMA_CONFIG */
#endif /* BSP_SPI1_TX_USING_DMA */

#ifdef BSP_SPI1_RX_USING_DMA
#ifndef SPI1_RX_DMA_CONFIG
#define SPI1_RX_DMA_CONFIG                                  \
    {                                                       \
        .dma_rcc = SPI1_RX_DMA_RCC,                         \
        .Instance = SPI1_RX_DMA_INSTANCE,                   \
        .request = SPI1_RX_DMA_REQUEST,                     \
        .dma_irq = SPI1_RX_DMA_IRQ,                         \
    }
#endif /* SPI1_RX_DMA_CONFIG */
#endif /* BSP_SPI1_RX_USING_DMA */
......

从上面的代码可以看出,SPI驱动初始化函数通过RT-Thread自动初始化组件完成,不需要我们再去调用。SPI的驱动初始化函数主要完成了SPI所有总线的参数配置和SPI总线向上层的注册,SPI总线注册函数rt_spi_bus_register前面介绍过。SPI总线的配置参数stm32_spi_config在一个专门的配置文件\HAL_Drivers\config\l4\spi_config.h中以宏定义形式给出。

接下来看SPI总线注册的操作函数集合:

// libraries\HAL_Drivers\drv_spi.c

static const struct rt_spi_ops stm_spi_ops =
{
    .configure = spi_configure,
    .xfer = spixfer,
};

static rt_err_t spi_configure(struct rt_spi_device *device,
                              struct rt_spi_configuration *configuration)
{
    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(configuration != RT_NULL);

    struct stm32_spi *spi_drv =  rt_container_of(device->bus, struct stm32_spi, spi_bus);
    spi_drv->cfg = configuration;

    return stm32_spi_init(spi_drv, configuration);
}

static rt_uint32_t spixfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    HAL_StatusTypeDef state;
    rt_size_t message_length, already_send_length;
    rt_uint16_t send_length;
    rt_uint8_t *recv_buf;
    const rt_uint8_t *send_buf;

    struct stm32_spi *spi_drv =  rt_container_of(device->bus, struct stm32_spi, spi_bus);
    SPI_HandleTypeDef *spi_handle = &spi_drv->handle;
    struct stm32_hw_spi_cs *cs = device->parent.user_data;

    if (message->cs_take)
    {
        HAL_GPIO_WritePin(cs->GPIOx, cs->GPIO_Pin, GPIO_PIN_RESET);
    }

    message_length = message->length;
    recv_buf = message->recv_buf;
    send_buf = message->send_buf;
    while (message_length)
    {
        /* the HAL library use uint16 to save the data length */
        if (message_length > 65535)
        {
            send_length = 65535;
            message_length = message_length - 65535;
        }
        else
        {
            send_length = message_length;
            message_length = 0;
        }

        /* calculate the start address */
        already_send_length = message->length - send_length - message_length;
        send_buf = (rt_uint8_t *)message->send_buf + already_send_length;
        recv_buf = (rt_uint8_t *)message->recv_buf + already_send_length;
        
        /* start once data exchange in DMA mode */
        if (message->send_buf && message->recv_buf)
        {
            if ((spi_drv->spi_dma_flag & SPI_USING_TX_DMA_FLAG) && (spi_drv->spi_dma_flag & SPI_USING_RX_DMA_FLAG))
            {
                state = HAL_SPI_TransmitReceive_DMA(spi_handle, (uint8_t *)send_buf, (uint8_t *)recv_buf, send_length);
            }
            else
            {
                state = HAL_SPI_TransmitReceive(spi_handle, (uint8_t *)send_buf, (uint8_t *)recv_buf, send_length, 1000);
            }
        }
        else if (message->send_buf)
        {
            if (spi_drv->spi_dma_flag & SPI_USING_TX_DMA_FLAG)
            {
                state = HAL_SPI_Transmit_DMA(spi_handle, (uint8_t *)send_buf, send_length);
            }
            else
            {
                state = HAL_SPI_Transmit(spi_handle, (uint8_t *)send_buf, send_length, 1000);
            }
        }
        else
        {
            memset((uint8_t *)recv_buf, 0xff, send_length);
            if (spi_drv->spi_dma_flag & SPI_USING_RX_DMA_FLAG)
            {
                state = HAL_SPI_Receive_DMA(spi_handle, (uint8_t *)recv_buf, send_length);
            }
            else
            {
                state = HAL_SPI_Receive(spi_handle, (uint8_t *)recv_buf, send_length, 1000);
            }
        }

        if (state != HAL_OK)
        {
            LOG_I("spi transfer error : %d", state);
            message->length = 0;
            spi_handle->State = HAL_SPI_STATE_READY;
        }
        else
        {
            LOG_D("%s transfer done", spi_drv->config->bus_name);
        }

        /* For simplicity reasons, this example is just waiting till the end of the
           transfer, but application may perform other tasks while transfer operation
           is ongoing. */
        while (HAL_SPI_GetState(spi_handle) != HAL_SPI_STATE_READY);
    }

    if (message->cs_release)
    {
        HAL_GPIO_WritePin(cs->GPIOx, cs->GPIO_Pin, GPIO_PIN_SET);
    }

    return message->length;
}

SPI这两个操作函数实际最终调用的是STM32 HAL SPI库函数,也是上层接口函数在底层的最终实现函数。

最后看SPI设备的绑定过程:

// libraries\HAL_Drivers\drv_spi.c

/**
 * Attach the spi device to SPI bus, this function must be used after initialization.
  */
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)
{
    RT_ASSERT(bus_name != RT_NULL);
    RT_ASSERT(device_name != RT_NULL);

    rt_err_t result;
    struct rt_spi_device *spi_device;
    struct stm32_hw_spi_cs *cs_pin;

    /* initialize the cs pin && select the slave*/
    GPIO_InitTypeDef GPIO_Initure;
    GPIO_Initure.Pin = cs_gpio_pin;
    GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Initure.Pull = GPIO_PULLUP;
    GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(cs_gpiox, &GPIO_Initure);
    HAL_GPIO_WritePin(cs_gpiox, cs_gpio_pin, GPIO_PIN_SET);

    /* attach the device to spi bus*/
    spi_device = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device));
    RT_ASSERT(spi_device != RT_NULL);
    cs_pin = (struct stm32_hw_spi_cs *)rt_malloc(sizeof(struct stm32_hw_spi_cs));
    RT_ASSERT(cs_pin != RT_NULL);
    cs_pin->GPIOx = cs_gpiox;
    cs_pin->GPIO_Pin = cs_gpio_pin;
    result = rt_spi_bus_attach_device(spi_device, device_name, bus_name, (void *)cs_pin);

    if (result != RT_EOK)
    {
        LOG_E("%s attach to %s faild, %d\n", device_name, bus_name, result);
    }

    RT_ASSERT(result == RT_EOK);

    LOG_D("%s attach to %s done", device_name, bus_name);

    return result;
}

SPI设备绑定函数传入的参数正好是结构体stm32_spi_device的成员,从该函数实现代码看,使用CubeMX配置SPI外设时不需要配置NCS片选引脚和DMA通道,这部分配置有RT-Thread提供的SPI驱动完成,SPI设备绑定最终要调用上层的SPI设备绑定函数rt_spi_bus_attach_device,该函数在前面也介绍过了。

  • QSPI设备驱动接口

首先看QSPI驱动初始化过程:

// libraries\HAL_Drivers\drv_qspi.c

static int rt_hw_qspi_bus_init(void)
{
    return stm32_qspi_register_bus(&_stm32_qspi_bus, "qspi1");
}
INIT_BOARD_EXPORT(rt_hw_qspi_bus_init);

struct stm32_qspi_bus
{
    QSPI_HandleTypeDef QSPI_Handler;
    char *bus_name;
#ifdef BSP_QSPI_USING_DMA
    DMA_HandleTypeDef hdma_quadspi;
#endif
};

struct rt_spi_bus _qspi_bus1;
struct stm32_qspi_bus _stm32_qspi_bus;

static int stm32_qspi_register_bus(struct stm32_qspi_bus *qspi_bus, const char *name)
{
    RT_ASSERT(qspi_bus != RT_NULL);
    RT_ASSERT(name != RT_NULL);

    _qspi_bus1.parent.user_data = qspi_bus;
    return rt_qspi_bus_register(&_qspi_bus1, name, &stm32_qspi_ops);
}

QSPI驱动与SPI驱动初始化类似,依然使用RT-Thread自动初始化组件完成初始化,不需用户再调用初始化函数。与SPI不同的是,QSPI一般只有一组外设,所以QSPI驱动描述相比SPI更简单,比如上面的QSPI总线注册函数stm32_qspi_register_bus传递QSPI总线名时直接传入字符串"qspi1",而不需要像SPI驱动那样使用数组容器spi_bus_obj[i]来表示SPI总线。QSPI总线注册函数最终调用的是上层的函数rt_qspi_bus_register,这个函数的实现过程在前面介绍过。

接下来看QSPI总线注册的操作函数集合:

// libraries\HAL_Drivers\drv_qspi.c

static const struct rt_spi_ops stm32_qspi_ops =
{
    .configure = qspi_configure,
    .xfer = qspixfer,
};

static rt_err_t qspi_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration)
{
    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(configuration != RT_NULL);

    struct rt_qspi_device *qspi_device = (struct rt_qspi_device *)device;
    return stm32_qspi_init(qspi_device, &qspi_device->config);
}

static int stm32_qspi_init(struct rt_qspi_device *device, struct rt_qspi_configuration *qspi_cfg)
{
	......
    QSPI_HandleTypeDef QSPI_Handler_config = QSPI_BUS_CONFIG;
    qspi_bus->QSPI_Handler = QSPI_Handler_config;
    ......
    result = HAL_QSPI_Init(&qspi_bus->QSPI_Handler);
    if (result  == HAL_OK)
    {
        LOG_D("qspi init success!");
    }
    else
    {
        LOG_E("qspi init failed (%d)!", result);
    }

#ifdef BSP_QSPI_USING_DMA
    /* QSPI interrupts must be enabled when using the HAL_QSPI_Receive_DMA */
    ......
    DMA_HandleTypeDef hdma_quadspi_config = QSPI_DMA_CONFIG;
    qspi_bus->hdma_quadspi = hdma_quadspi_config;
    
    if (HAL_DMA_Init(&qspi_bus->hdma_quadspi) != HAL_OK)
    {
        LOG_E("qspi dma init failed (%d)!", result);
    }
    __HAL_LINKDMA(&qspi_bus->QSPI_Handler, hdma, qspi_bus->hdma_quadspi);
#endif /* BSP_QSPI_USING_DMA */

    return result;
}

static rt_uint32_t qspixfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    rt_size_t len = 0;

    struct rt_qspi_message *qspi_message = (struct rt_qspi_message *)message;
    struct stm32_qspi_bus *qspi_bus = device->bus->parent.user_data;
#ifdef BSP_QSPI_USING_SOFTCS
    struct stm32_hw_spi_cs *cs = device->parent.user_data;
#endif

    const rt_uint8_t *sndb = message->send_buf;
    rt_uint8_t *rcvb = message->recv_buf;
    rt_int32_t length = message->length;

#ifdef BSP_QSPI_USING_SOFTCS
    if (message->cs_take)
    {
        rt_pin_write(cs->pin, 0);
    }
#endif

    /* send data */
    if (sndb)
    {
        qspi_send_cmd(qspi_bus, qspi_message);
        if (qspi_message->parent.length != 0)
        {
            if (HAL_QSPI_Transmit(&qspi_bus->QSPI_Handler, (rt_uint8_t *)sndb, 5000) == HAL_OK)
            {
                len = length;
            }
            else
            {
                LOG_E("QSPI send data failed(%d)!", qspi_bus->QSPI_Handler.ErrorCode);
                qspi_bus->QSPI_Handler.State = HAL_QSPI_STATE_READY;
                goto __exit;
            }
        }
        else
        {
            len = 1;
        }
    }
    else if (rcvb)/* recv data */
    {
        qspi_send_cmd(qspi_bus, qspi_message);
#ifdef BSP_QSPI_USING_DMA
        if (HAL_QSPI_Receive_DMA(&qspi_bus->QSPI_Handler, rcvb) == HAL_OK)
#else
        if (HAL_QSPI_Receive(&qspi_bus->QSPI_Handler, rcvb, 5000) == HAL_OK)
#endif
        {
            len = length;
#ifdef BSP_QSPI_USING_DMA
            while (qspi_bus->QSPI_Handler.RxXferCount != 0);
#endif
        }
        else
        {
            LOG_E("QSPI recv data failed(%d)!", qspi_bus->QSPI_Handler.ErrorCode);
            qspi_bus->QSPI_Handler.State = HAL_QSPI_STATE_READY;
            goto __exit;
        }
    }

__exit:
#ifdef BSP_QSPI_USING_SOFTCS
    if (message->cs_release)
    {
        rt_pin_write(cs->pin, 1);
    }
#endif
    return len;
}


// libraries\HAL_Drivers\config\l4\qspi_config.h

#ifdef BSP_USING_QSPI
#ifndef QSPI_BUS_CONFIG
#define QSPI_BUS_CONFIG                                        \
    {                                                          \
        .Instance = QUADSPI,                                   \
        .Init.FifoThreshold = 4,                               \
        .Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE, \
        .Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_4_CYCLE,  \
    }
#endif /* QSPI_BUS_CONFIG */
#endif /* BSP_USING_QSPI */
    
#ifdef BSP_QSPI_USING_DMA
#ifndef QSPI_DMA_CONFIG
#define QSPI_DMA_CONFIG                                        \
    {                                                          \
        .Instance = QSPI_DMA_INSTANCE,                         \
        .Init.Request = QSPI_DMA_REQUEST,                      \
        .Init.Direction = DMA_PERIPH_TO_MEMORY,                \
        .Init.PeriphInc = DMA_PINC_DISABLE,                    \
        .Init.MemInc = DMA_MINC_ENABLE,                        \
        .Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE,       \
        .Init.MemDataAlignment = DMA_MDATAALIGN_BYTE,          \
        .Init.Mode = DMA_NORMAL,                               \
        .Init.Priority = DMA_PRIORITY_LOW                      \
    }
#endif /* QSPI_DMA_CONFIG */
#endif /* BSP_QSPI_USING_DMA */

QSPI这两个操作函数通过调用STM32 HAL QSPI库函数实现,QSPI上层的接口函数则最终是通过调用这两个操作函数实现。

最后看QSPI设备的绑定过程:

// libraries\HAL_Drivers\drv_qspi.c

/**
  * @brief  This function attach device to QSPI bus.
  * @param  device_name      QSPI device name
  * @param  pin              QSPI cs pin number
  * @param  data_line_width  QSPI data lines width, such as 1, 2, 4
  * @param  enter_qspi_mode  Callback function that lets FLASH enter QSPI mode
  * @param  exit_qspi_mode   Callback function that lets FLASH exit QSPI mode
  * @retval 0 : success
  *        -1 : failed
  */
rt_err_t stm32_qspi_bus_attach_device(const char *bus_name, const char *device_name, rt_uint32_t pin, rt_uint8_t data_line_width, void (*enter_qspi_mode)(), void (*exit_qspi_mode)())
{
    struct rt_qspi_device *qspi_device = RT_NULL;
    struct stm32_hw_spi_cs *cs_pin = RT_NULL;
    rt_err_t result = RT_EOK;

    RT_ASSERT(bus_name != RT_NULL);
    RT_ASSERT(device_name != RT_NULL);
    RT_ASSERT(data_line_width == 1 || data_line_width == 2 || data_line_width == 4);

    qspi_device = (struct rt_qspi_device *)rt_malloc(sizeof(struct rt_qspi_device));
    if (qspi_device == RT_NULL)
    {
        LOG_E("no memory, qspi bus attach device failed!");
        result = RT_ENOMEM;
        goto __exit;
    }
    cs_pin = (struct stm32_hw_spi_cs *)rt_malloc(sizeof(struct stm32_hw_spi_cs));
    if (qspi_device == RT_NULL)
    {
        LOG_E("no memory, qspi bus attach device failed!");
        result = RT_ENOMEM;
        goto __exit;
    }

    qspi_device->enter_qspi_mode = enter_qspi_mode;
    qspi_device->exit_qspi_mode = exit_qspi_mode;
    qspi_device->config.qspi_dl_width = data_line_width;

    cs_pin->Pin = pin;
#ifdef BSP_QSPI_USING_SOFTCS
    rt_pin_mode(pin, PIN_MODE_OUTPUT);
    rt_pin_write(pin, 1);
#endif

    result = rt_spi_bus_attach_device(&qspi_device->parent, device_name, bus_name, (void *)cs_pin);

__exit:
    if (result != RT_EOK)
    {
        if (qspi_device)
        {
            rt_free(qspi_device);
        }

        if (cs_pin)
        {
            rt_free(cs_pin);
        }
    }

    return  result;
}

前面介绍QSPI设备驱动框架层接口函数时并没有介绍QSPI设备绑定函数,从QSPI设备驱动层的绑定函数stm32_qspi_bus_attach_device的实现代码可以看出,其最终调用的依然是上层的SPI设备绑定函数rt_spi_bus_attach_device,QSPI与SPI设备绑定过程差异没那么大,也就没有在QSPI设备驱动框架层单独实现QSPI设备绑定函数。

1.3 QSPI访问W25Q128示例

在前篇博客的工程stm32l475_device_sample基础上新增QSPI外设访问W25Q128 Flash,STM32L475与W25Q128的连接原理图如下:
IOT-OS之RT-Thread(九)--- SPI设备对象管理与SFUD管理框架_第2张图片

  • CubeMX配置QUADSPI

打开CubeMX文件projects\stm32l475_device_sample\board \CubeMX_Config\CubeMX_Config.ioc新增QSPI配置如下图所示:
IOT-OS之RT-Thread(九)--- SPI设备对象管理与SFUD管理框架_第3张图片
QUADSPI配置参数的含义在博客:SPI + QSPI + HAL中有详细介绍。QUADSPI配置完后直接点击GENERATE CODE生成工程代码即可。

  • Kconfig新增QSPI设备条件宏

STM32 CubeMX主要生成了HAL_QSPI_MspInit() / MspDeInit()函数,RT-Thread要想使用QSPI设备驱动,需要在图形化配置工具menuconfig中使能QSPI设备,menuconfig配置项是从文件Kconfig中读取的,配置完成后将相应的宏定义写入到配置文件rtconfig.h中,RT-Thread最终是从rtconfig.h读取相应的宏定义。

虽然直接在rtconfig.h中配置QSPI设备的宏定义也可以,但为了方便统一由menuconfig工具管理宏定义,我们在Kconfig中配置QSPI设备的宏依赖选项,在Kconfig中新增QSPI设备配置信息如下:

// projects\stm32l475_device_sample\board\Kconfig
......
menu "On-chip Peripheral Drivers"
	......
	menuconfig BSP_USING_QSPI
        bool "Enable QSPI BUS"
        default n
        select RT_USING_QSPI
        select RT_USING_SPI        
        if BSP_USING_QSPI
            config BSP_QSPI_USING_DMA
                bool "Enable QSPI DMA support"
                depends on BSP_USING_QSPI
                default n
        endif
    ......

Kconfig中新增BSP_USING_QSPI配置后,在工程目录打开env工具输入menuconfig,使能上面配置的BSP_USING_QSPI,menuconfig配置界面如下图所示:
IOT-OS之RT-Thread(九)--- SPI设备对象管理与SFUD管理框架_第4张图片
使能BSP_USING_QSPI后同时也把RT-Thread Components --> Device Drivers下的Using SPI Bus / Device device drivers与Enable QSPI mode也使能了。BSP_USING_QSPI属于QSPI设备驱动层的条件宏,RT_USING_QSPI与RT_USING_SPI属于QSPI设备驱动框架层的条件宏。

menuconfig使能BSP_USING_QSPI后保存配置,新增的宏定义就会被写入到工程目录下的rtconfig.h文件中,我们打开该文件查看新增宏定义如下:

// projects\stm32l475_device_sample\rtconfig.h

/* Device Drivers */
......
#define RT_USING_SPI
#define RT_USING_QSPI

/* On-chip Peripheral Drivers */
......
#define BSP_USING_QSPI
#define BSP_QSPI_USING_DMA
  • 编写QSPI访问W25Q128的示例代码

本示例工程完成W25Q128最常用的几种访问操作:读取设备ID、擦除指定分区、向指定分区写入数据、从指定分区中读取数据。使用前面介绍的QSPI设备驱动框架层提供的访问接口函数实现。

在工程目录projects\stm32l475_device_sample\applications下新建spi_sample.c源文件,打开该文件并编写QSPI访问W25Q128的示例代码如下:

// projects\stm32l475_device_sample\applications\spi_sample.c

#include "rtdevice.h"
#include "rtthread.h"
#include "board.h"
#include "drv_qspi.h"

#define QSPI_BUD_NAME       "qspi1"
#define QSPI_DEVICE_NAME    "qspi10"
#define W25Q_FLASH_NAME     "W25Q128"

#define QSPI_CS_PIN         GET_PIN(E, 11)

rt_uint8_t wData[4096] = {"QSPI bus write data to W25Q flash."};
rt_uint8_t rData[4096];

static int rt_hw_spi_flash_init(void)
{
    if(stm32_qspi_bus_attach_device(QSPI_BUD_NAME, QSPI_DEVICE_NAME, (rt_uint32_t)QSPI_CS_PIN, 1, RT_NULL, RT_NULL) != RT_EOK)
        return -RT_ERROR;

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init);

static void qspi_w25q_sample(void)
{
    struct rt_qspi_device *qspi_dev_w25q = RT_NULL;
    struct rt_qspi_message message;
    struct rt_qspi_configuration config;

    rt_uint8_t w25q_read_id[4] = {0x90, 0x00, 0x00, 0x00};
    rt_uint8_t w25q_read_data[4] = {0x03, 0x00, 0x10, 0x00};
    rt_uint8_t w25q_erase_sector[4] = {0x20, 0x00, 0x10, 0x00};
    rt_uint8_t w25q_write_enable = 0x06;
    rt_uint8_t W25X_ReadStatusReg1 = 0x05;
    rt_uint8_t id[2] = {0};
    rt_uint8_t status = 1;

    qspi_dev_w25q = (struct rt_qspi_device *)rt_device_find(QSPI_DEVICE_NAME);
    if(qspi_dev_w25q == RT_NULL){
        rt_kprintf("qspi sample run failed! can't find %s device!\n", QSPI_DEVICE_NAME);
    }else{
        // config w25q qspi
        config.parent.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
        config.parent.data_width = 8;
        config.parent.max_hz = 50 * 1000 * 1000;
        config.medium_size = 16 * 1024 * 1024;
        config.ddr_mode = 0;
        config.qspi_dl_width = 4;
        rt_qspi_configure(qspi_dev_w25q, &config);
        rt_kprintf("qspi10 config finish.\n");

        // read w25q id
        rt_qspi_send_then_recv(qspi_dev_w25q, w25q_read_id, 4, id, 2);
        rt_kprintf("qspi10 read w25q ID is:%2x%2x\n", id[0], id[1]);

        // erase sector address 4096
        rt_qspi_send(qspi_dev_w25q, &w25q_write_enable, 1);
        rt_qspi_send(qspi_dev_w25q, w25q_erase_sector, 4);
        // wait transfer finish
        while((status & 0x01) == 0x01)
            rt_qspi_send_then_recv(qspi_dev_w25q, &W25X_ReadStatusReg1, 1, &status, 1);
        rt_kprintf("qspi10 erase w25q sector(address:0x1000) success.\n");

        // write data to w25q address 4096
        rt_qspi_send(qspi_dev_w25q, &w25q_write_enable, 1);
        message.parent.send_buf = wData;
        message.parent.recv_buf = RT_NULL;
        message.parent.length = 64;
        message.parent.next = RT_NULL;
        message.parent.cs_take = 1;
        message.parent.cs_release = 1;
        message.instruction.content = 0X32;
        message.instruction.qspi_lines = 1;
        message.address.content = 0X00001000;
        message.address.size = 24;
        message.address.qspi_lines = 1;
        message.dummy_cycles = 0;
        message.qspi_data_lines = 4;
        rt_qspi_transfer_message(qspi_dev_w25q, &message);
        // wait transfer finish
        status = 1;
        while((status & 0x01) == 0x01)
            rt_qspi_send_then_recv(qspi_dev_w25q, &W25X_ReadStatusReg1, 1, &status, 1);
        rt_kprintf("qspi10 write data to w25q(address:0x1000) success.\n");

        // read data from w25q address 4096
        rt_qspi_send_then_recv(qspi_dev_w25q, w25q_read_data, 4, rData, 64);
        rt_kprintf("qspi10 read data from w25q(address:0x1000) is:%s\n", rData);
    }
}
MSH_CMD_EXPORT(qspi_w25q_sample, qspi w25q128 sample);

QSPI设备绑定函数使用了RT-Thread自动初始化组件,QSPI设备绑定注册后,在示例程序中只需要通过rt_device_find找到已注册或绑定的设备就可以获取该设备指针,访问该设备了。

博客SPI + QSPI + HAL介绍了QSPI访问W25Q128的过程,前面又介绍了QSPI设备驱动框架层提供的用户函数接口(I/O设备管理层提供的接口功能较弱,这里直接没有使用),这里的示例工程实际上就是使用QSPI新的接口函数,按照W25QXX驱动程序的实现原理再次实现一遍。

QSPI访问W25Q128的示例代码逻辑相对简单,要使用QSPI设备需要先将该设备初始化并绑定到相应总线上,然后通过rt_device_find获得该QSPI设备的句柄。在使用前一般先对该设备参数进行配置(通过函数rt_qspi_configure实现),在传输消息时如果消息比较简单可以直接调用相应的API函数实现,如果消息比较复杂可以自己构造消息结构体rt_qspi_message(包含命令序列、发送数据缓冲区、接收数据缓冲区等信息),然后通过函数rt_qspi_transfer_message传输该消息。

示例代码编写完成后,在工程目录中打开env输入scons --target=mdk5生成MDK5工程代码,打开生成的project.uvprojx,编译无报错,烧录到我们的STM32L475潘多拉开发板中,运行结果如下:
IOT-OS之RT-Thread(九)--- SPI设备对象管理与SFUD管理框架_第5张图片
本示例工程源码下载地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_device_sample

二、SFUD管理与示例

像W25Q128这种串行Flash芯片种类有很多,如果每种芯片都提供一套驱动函数不便于管理,RT-Thread为串行Flash提供了一套通用驱动函数SFUD(Serial Flash Universal Driver),能驱动多数常用SPI Flash,SFUD属于SPI设备驱动框架层。

2.1 SFUD Flash描述

在介绍SFUD Flash设备描述结构体前,先介绍下SPI Flash结构体。SFUD Flash为了通用性,描述信息比较多;SPI Flash相对简单,而且是SFUD框架层与I / O设备管理层之间的桥梁。

  • SPI Flash控制块
// rt-thread-4.0.1\components\drivers\spi\spi_flash.h

struct spi_flash_device
{
    struct rt_device                flash_device;
    struct rt_device_blk_geometry   geometry;
    struct rt_spi_device *          rt_spi_device;
    struct rt_mutex                 lock;
    void *                          user_data;
};

typedef struct spi_flash_device *rt_spi_flash_device_t;


// rt-thread-4.0.1\include\rtdef.h

/**
 * block device geometry structure
 */
struct rt_device_blk_geometry
{
    rt_uint32_t sector_count;                           /**< count of sectors */
    rt_uint32_t bytes_per_sector;                       /**< number of bytes per sector */
    rt_uint32_t block_size;                             /**< number of bytes to erase one block */
};

spi_flash_device结构体继承自rt_device基对象,包含了块设备的几何结构信息geometry、SPI设备句柄rt_spi_device、互斥锁lock、用户数据指针user_data等,其中user_data可指向SFUD Flash句柄。

  • SFUD Flash控制块
// rt-thread-4.0.1\components\drivers\spi\sfud\inc\sfud_def.h

/**
 * serial flash device
 */
typedef struct {
    char *name;                                  /**< serial flash name */
    size_t index;                                /**< index of flash device information table  @see flash_table */
    sfud_flash_chip chip;                        /**< flash chip information */
    sfud_spi spi;                                /**< SPI device */
    bool init_ok;                                /**< initialize OK flag */
    bool addr_in_4_byte;                         /**< flash is in 4-Byte addressing */
    struct {
        void (*delay)(void);                     /**< every retry's delay */
        size_t times;                            /**< default times for error retry */
    } retry;
    void *user_data;                             /**< some user data */

#ifdef SFUD_USING_QSPI
    sfud_qspi_read_cmd_format read_cmd_format;   /**< fast read cmd format */
#endif

#ifdef SFUD_USING_SFDP
    sfud_sfdp sfdp;                              /**< serial flash discoverable parameters by JEDEC standard */
#endif

} sfud_flash, *sfud_flash_t;

/**
 * SPI device
 */
typedef struct __sfud_spi {
    /* SPI device name */
    char *name;
    /* SPI bus write read data function */
    sfud_err (*wr)(const struct __sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
                   size_t read_size);
#ifdef SFUD_USING_QSPI
    /* QSPI fast read function */
    sfud_err (*qspi_read)(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format,
                          uint8_t *read_buf, size_t read_size);
#endif
    /* lock SPI bus */
    void (*lock)(const struct __sfud_spi *spi);
    /* unlock SPI bus */
    void (*unlock)(const struct __sfud_spi *spi);
    /* some user data */
    void *user_data;
} sfud_spi, *sfud_spi_t;

/**
 * QSPI flash read cmd format
 */
typedef struct {
    uint8_t instruction;
    uint8_t instruction_lines;
    uint8_t address_size;
    uint8_t address_lines;
    uint8_t alternate_bytes_lines;
    uint8_t dummy_cycles;
    uint8_t data_lines;
} sfud_qspi_read_cmd_format;

/**
 * the SFDP (Serial Flash Discoverable Parameters) parameter info which used on this library
 */
typedef struct {
    bool available;                              /**< available when read SFDP OK */
    uint8_t major_rev;                           /**< SFDP Major Revision */
    uint8_t minor_rev;                           /**< SFDP Minor Revision */
    uint16_t write_gran;                         /**< write granularity (bytes) */
    uint8_t erase_4k;                            /**< 4 kilobyte erase is supported throughout the device */
    uint8_t erase_4k_cmd;                        /**< 4 Kilobyte erase command */
    bool sr_is_non_vola;                         /**< status register is supports non-volatile */
    uint8_t vola_sr_we_cmd;                      /**< volatile status register write enable command */
    bool addr_3_byte;                            /**< supports 3-Byte addressing */
    bool addr_4_byte;                            /**< supports 4-Byte addressing */
    uint32_t capacity;                           /**< flash capacity (bytes) */
    struct {
        uint32_t size;                           /**< erase sector size (bytes). 0x00: not available */
        uint8_t cmd;                             /**< erase command */
    } eraser[SFUD_SFDP_ERASE_TYPE_MAX_NUM];      /**< supported eraser types table */
    //TODO lots of fast read-related stuff (like modes supported and number of wait states/dummy cycles needed in each)
} sfud_sfdp, *sfud_sfdp_t;

sfud_flash结构体包含的信息比较多,除了flash设备名、flash设备信息表索引、flash芯片信息、SPI设备结构体(包括操作函数指针)外,还包括QSPI命令格式、flash发现参数等,每个成员后都有注释,就不详细解释了。

前面介绍spi_flash_device.user_data可以指向SFUD Flash控制块,这里的sfud_flash.user_data也可以指向SPI Flash控制块,这里借助user_data成员互相访问的支持,正是SFUD框架层与I / O设备管理层实现通信的基础。

  • SFUD Flash芯片信息描述

SFUD的实现是为了支持尽可能多的芯片型号,但市场上芯片型号太多,RT-Thread官方不可能提供对所有芯片型号的支持,如果用户使用的芯片型号并没有出现在SFUD的默认支持列表中,用户可以根据芯片手册,将该型号芯片的信息添加到SFUD芯片信息描述表中,就可以借助SFUD框架操作该型号芯片了。

SFUD Flash描述芯片信息的列表有三个,这三个列表的定义如下:

// rt-thread-4.0.1\components\drivers\spi\sfud\inc\sfud_flash_def.h

/* manufacturer information */
typedef struct {
    char *name;
    uint8_t id;
} sfud_mf;

/* SFUD supported manufacturer information table */
#define SFUD_MF_TABLE                                     \
{                                                         \
    {"Cypress",    SFUD_MF_ID_CYPRESS},                   \
	......
    {"Winbond",    SFUD_MF_ID_WINBOND},                   \
    {"Micronix",   SFUD_MF_ID_MICRONIX},                  \
}

/* flash chip information */
typedef struct {
    char *name;                                  /**< flash chip name */
    uint8_t mf_id;                               /**< manufacturer ID */
    uint8_t type_id;                             /**< memory type ID */
    uint8_t capacity_id;                         /**< capacity ID */
    uint32_t capacity;                           /**< flash capacity (bytes) */
    uint16_t write_mode;                         /**< write mode @see sfud_write_mode */
    uint32_t erase_gran;                         /**< erase granularity (bytes) */
    uint8_t erase_gran_cmd;                      /**< erase granularity size block command */
} sfud_flash_chip;

#define SFUD_FLASH_CHIP_TABLE                                                                                       \
{                                                                                                                   \
	......
    {"W25Q128BV", SFUD_MF_ID_WINBOND, 0x40, 0x18, 16L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                  \
    {"W25Q256FV", SFUD_MF_ID_WINBOND, 0x40, 0x19, 32L*1024L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20},                  \
	......
    {"PCT25VF016B", SFUD_MF_ID_SST, 0x25, 0x41, 2L*1024L*1024L, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20},              \
}

#ifdef SFUD_USING_QSPI
/* QSPI flash chip's extended information compared with SPI flash */
typedef struct {
    uint8_t mf_id;                               /**< manufacturer ID */
    uint8_t type_id;                             /**< memory type ID */
    uint8_t capacity_id;                         /**< capacity ID */
    uint8_t read_mode;                           /**< supported read mode on this qspi flash chip */
} sfud_qspi_flash_ext_info;
#endif

#define SFUD_FLASH_EXT_INFO_TABLE                                                                  \
{                                                                                                  \
    ......
    /* W25Q128JV */                                                                                \
    {SFUD_MF_ID_WINBOND, 0x40, 0x18, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_OUTPUT|QUAD_IO},     \
    /* W25Q256FV */                                                                                \
    {SFUD_MF_ID_WINBOND, 0x40, 0x19, NORMAL_SPI_READ|DUAL_OUTPUT|DUAL_IO|QUAD_OUTPUT|QUAD_IO},     \
    ......
    /* GD25Q64B */                                                                                 \
    {SFUD_MF_ID_GIGADEVICE, 0x40, 0x17, NORMAL_SPI_READ|DUAL_OUTPUT},                              \
}

如果SFUD默认Flash芯片信息列表中找不到你需要的芯片型号,可以根据芯片手册手动添加相应的item,方便的可扩展性也是通用性的体现。

2.2 SFUD Flash接口

由于SFUD位于设备驱动框架层,自然需要设备驱动层的支持。以前面介绍的QSPI访问W25Q128 Flash为例,要使用SFUD,需要QSPI总线已完成初始化并注册、QSPI设备已完成初始化并绑定到相应总线,也即前面QSPI访问W25Q128示例工程执行完stm32_qspi_bus_attach_device后的状态。

SFUD初始化过程由rt_sfud_flash_probe调用,在rt_sfud_flash_probe函数中完成查找设备驱动层注册的设备,并完成目标设备sfud_flash与rt_spi_flash_device两个结构体成员的赋值,最后向系统注册rt_spi_flash_device。rt_sfud_flash_probe函数的实现代码如下:

// rt-thread-4.0.1\components\drivers\spi\spi_flash_sfud.c

/**
 * Probe SPI flash by SFUD(Serial Flash Universal Driver) driver library and though SPI device.
 *
 * @param spi_flash_dev_name the name which will create SPI flash device
 * @param spi_dev_name using SPI device name
 *
 * @return probed SPI flash device, probe failed will return RT_NULL
 */
rt_spi_flash_device_t rt_sfud_flash_probe(const char *spi_flash_dev_name, const char *spi_dev_name) {
    rt_spi_flash_device_t rtt_dev = RT_NULL;
    sfud_flash *sfud_dev = RT_NULL;
    char *spi_flash_dev_name_bak = RT_NULL, *spi_dev_name_bak = RT_NULL;
    /* using default flash SPI configuration for initialize SPI Flash
     * @note you also can change the SPI to other configuration after initialized finish */
    struct rt_spi_configuration cfg = RT_SFUD_DEFAULT_SPI_CFG;
    extern sfud_err sfud_device_init(sfud_flash *flash);
#ifdef SFUD_USING_QSPI
    struct rt_qspi_configuration qspi_cfg = RT_SFUD_DEFAULT_QSPI_CFG;
    struct rt_qspi_device *qspi_dev = RT_NULL;
#endif

    RT_ASSERT(spi_flash_dev_name);
    RT_ASSERT(spi_dev_name);

    rtt_dev = (rt_spi_flash_device_t) rt_malloc(sizeof(struct spi_flash_device));
    sfud_dev = (sfud_flash_t) rt_malloc(sizeof(sfud_flash));
    spi_flash_dev_name_bak = (char *) rt_malloc(rt_strlen(spi_flash_dev_name) + 1);
    spi_dev_name_bak = (char *) rt_malloc(rt_strlen(spi_dev_name) + 1);

    if (rtt_dev) {
        rt_memset(rtt_dev, 0, sizeof(struct spi_flash_device));
        /* initialize lock */
        rt_mutex_init(&(rtt_dev->lock), spi_flash_dev_name, RT_IPC_FLAG_FIFO);
    }

    if (rtt_dev && sfud_dev && spi_flash_dev_name_bak && spi_dev_name_bak) {
        rt_memset(sfud_dev, 0, sizeof(sfud_flash));
        rt_strncpy(spi_flash_dev_name_bak, spi_flash_dev_name, rt_strlen(spi_flash_dev_name));
        rt_strncpy(spi_dev_name_bak, spi_dev_name, rt_strlen(spi_dev_name));
        /* make string end sign */
        spi_flash_dev_name_bak[rt_strlen(spi_flash_dev_name)] = '\0';
        spi_dev_name_bak[rt_strlen(spi_dev_name)] = '\0';
        /* SPI configure */
        {
            /* RT-Thread SPI device initialize */
            rtt_dev->rt_spi_device = (struct rt_spi_device *) rt_device_find(spi_dev_name);
            if (rtt_dev->rt_spi_device == RT_NULL || rtt_dev->rt_spi_device->parent.type != RT_Device_Class_SPIDevice) {
                rt_kprintf("ERROR: SPI device %s not found!\n", spi_dev_name);
                goto error;
            }
            sfud_dev->spi.name = spi_dev_name_bak;

#ifdef SFUD_USING_QSPI
            /* set the qspi line number and configure the QSPI bus */
            if(rtt_dev->rt_spi_device->bus->mode &RT_SPI_BUS_MODE_QSPI) {
                qspi_dev = (struct rt_qspi_device *)rtt_dev->rt_spi_device;
                qspi_cfg.qspi_dl_width = qspi_dev->config.qspi_dl_width;
                rt_qspi_configure(qspi_dev, &qspi_cfg);
            }
            else
#endif
                rt_spi_configure(rtt_dev->rt_spi_device, &cfg);
        }
        /* SFUD flash device initialize */
        {
            sfud_dev->name = spi_flash_dev_name_bak;
            /* accessed each other */
            rtt_dev->user_data = sfud_dev;
            rtt_dev->rt_spi_device->user_data = rtt_dev;
            rtt_dev->flash_device.user_data = rtt_dev;
            sfud_dev->user_data = rtt_dev;
            /* initialize SFUD device */
            if (sfud_device_init(sfud_dev) != SFUD_SUCCESS) {
                rt_kprintf("ERROR: SPI flash probe failed by SPI device %s.\n", spi_dev_name);
                goto error;
            }
            /* when initialize success, then copy SFUD flash device's geometry to RT-Thread SPI flash device */
            rtt_dev->geometry.sector_count = sfud_dev->chip.capacity / sfud_dev->chip.erase_gran;
            rtt_dev->geometry.bytes_per_sector = sfud_dev->chip.erase_gran;
            rtt_dev->geometry.block_size = sfud_dev->chip.erase_gran;
#ifdef SFUD_USING_QSPI
            /* reconfigure the QSPI bus for medium size */
            if(rtt_dev->rt_spi_device->bus->mode &RT_SPI_BUS_MODE_QSPI) {
                qspi_cfg.medium_size = sfud_dev->chip.capacity;
                rt_qspi_configure(qspi_dev, &qspi_cfg);
                if(qspi_dev->enter_qspi_mode != RT_NULL)
                    qspi_dev->enter_qspi_mode(qspi_dev);

                /* set data lines width */
                sfud_qspi_fast_read_enable(sfud_dev, qspi_dev->config.qspi_dl_width);
            }
#endif /* SFUD_USING_QSPI */
        }

        /* register device */
        rtt_dev->flash_device.type = RT_Device_Class_Block;
#ifdef RT_USING_DEVICE_OPS
        rtt_dev->flash_device.ops  = &flash_device_ops;
#else
        rtt_dev->flash_device.init = RT_NULL;
        rtt_dev->flash_device.open = RT_NULL;
        rtt_dev->flash_device.close = RT_NULL;
        rtt_dev->flash_device.read = rt_sfud_read;
        rtt_dev->flash_device.write = rt_sfud_write;
        rtt_dev->flash_device.control = rt_sfud_control;
#endif

        rt_device_register(&(rtt_dev->flash_device), spi_flash_dev_name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);

        DEBUG_TRACE("Probe SPI flash %s by SPI device %s success.\n",spi_flash_dev_name, spi_dev_name);
        return rtt_dev;
    } else {
        rt_kprintf("ERROR: Low memory.\n");
        goto error;
    }

error:

    if (rtt_dev) {
        rt_mutex_detach(&(rtt_dev->lock));
    }
    /* may be one of objects memory was malloc success, so need free all */
    rt_free(rtt_dev);
    rt_free(sfud_dev);
    rt_free(spi_flash_dev_name_bak);
    rt_free(spi_dev_name_bak);

    return RT_NULL;
}


// rt-thread-4.0.1\components\drivers\spi\sfud\src\sfud.c

/**
 * SFUD initialize by flash device
 *
 * @param flash flash device
 *
 * @return result
 */
sfud_err sfud_device_init(sfud_flash *flash) {
    sfud_err result = SFUD_SUCCESS;

    /* hardware initialize */
    result = hardware_init(flash);
    if (result == SFUD_SUCCESS) {
        result = software_init(flash);
    }
    ......
}

/**
 * hardware initialize
 */
static sfud_err hardware_init(sfud_flash *flash) {
    extern sfud_err sfud_spi_port_init(sfud_flash * flash);

    sfud_err result = SFUD_SUCCESS;
    size_t i;

    SFUD_ASSERT(flash);

    result = sfud_spi_port_init(flash);
    if (result != SFUD_SUCCESS) {
        return result;
    }
    ......
}


// rt-thread-4.0.1\components\drivers\spi\spi_flash_sfud.c

sfud_err sfud_spi_port_init(sfud_flash *flash) {
    sfud_err result = SFUD_SUCCESS;

    RT_ASSERT(flash);

    /* port SPI device interface */
    flash->spi.wr = spi_write_read;
#ifdef SFUD_USING_QSPI
    flash->spi.qspi_read = qspi_read;
#endif
    flash->spi.lock = spi_lock;
    flash->spi.unlock = spi_unlock;
    flash->spi.user_data = flash;
    if (RT_TICK_PER_SECOND < 1000) {
        rt_kprintf("[SFUD] Warning: The OS tick(%d) is less than 1000. So the flash write will take more time.\n", RT_TICK_PER_SECOND);
    }
    /* 100 microsecond delay */
    flash->retry.delay = retry_delay_100us;
    /* 60 seconds timeout */
    flash->retry.times = 60 * 10000;

    return result;
}

/**
 * SPI write data then read data
 */
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
        size_t read_size) {
    sfud_err result = SFUD_SUCCESS;
    sfud_flash *sfud_dev = (sfud_flash *) (spi->user_data);
    struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (sfud_dev->user_data);
	......
#ifdef SFUD_USING_QSPI
    if(rtt_dev->rt_spi_device->bus->mode & RT_SPI_BUS_MODE_QSPI) {
        qspi_dev = (struct rt_qspi_device *) (rtt_dev->rt_spi_device);
        if (write_size && read_size) {
            if (rt_qspi_send_then_recv(qspi_dev, write_buf, write_size, read_buf, read_size) == 0) {
                result = SFUD_ERR_TIMEOUT;
            }
        } else if (write_size) {
            if (rt_qspi_send(qspi_dev, write_buf, write_size) == 0) {
                result = SFUD_ERR_TIMEOUT;
            }
        }
    }
    else
#endif
    {
        if (write_size && read_size) {
            if (rt_spi_send_then_recv(rtt_dev->rt_spi_device, write_buf, write_size, read_buf, read_size) != RT_EOK) {
                result = SFUD_ERR_TIMEOUT;
            }
        } else if (write_size) {
            if (rt_spi_send(rtt_dev->rt_spi_device, write_buf, write_size) == 0) {
                result = SFUD_ERR_TIMEOUT;
            }
        } else {
            if (rt_spi_recv(rtt_dev->rt_spi_device, read_buf, read_size) == 0) {
                result = SFUD_ERR_TIMEOUT;
            }
        }
    }

    return result;
}

#ifdef SFUD_USING_QSPI
/**
 * QSPI fast read data
 */
static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format, uint8_t *read_buf, size_t read_size) {
    struct rt_qspi_message message;
    sfud_err result = SFUD_SUCCESS;

    sfud_flash *sfud_dev = (sfud_flash *) (spi->user_data);
    struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (sfud_dev->user_data);
    struct rt_qspi_device *qspi_dev = (struct rt_qspi_device *) (rtt_dev->rt_spi_device);
	......
	
    if (rt_qspi_transfer_message(qspi_dev, &message) != read_size) {
        result = SFUD_ERR_TIMEOUT;
    }

    return result;
}
#endif

从上面的代码可以看出,函数rt_sfud_flash_probe调用后完成SFUD框架的初始化,同时为查找到的SPI设备创建sfud_flash对象和rt_spi_flash_device对象,并完成所创建目标设备对象的初始化。

sfud_flash设备的操作函数sfud_flash.spi.wr和sfud_flash.spi.qspi_read最终通过调用前面介绍的SPI / QSPI设备驱动框架层的接口函数实现的,所以SFUD框架是基于SPI / QSPI驱动框架实现的。

函数rt_sfud_flash_probe最后向系统注册的操作函数集合flash_device_ops及操作函数实现代码如下:

// rt-thread-4.0.1\components\drivers\spi\spi_flash_sfud.c

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops flash_device_ops = 
{
    RT_NULL,
    RT_NULL,
    RT_NULL,
    rt_sfud_read,
    rt_sfud_write,
    rt_sfud_control
};
#endif

static rt_size_t rt_sfud_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size) {
    struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (dev->user_data);
    sfud_flash *sfud_dev = (sfud_flash *) (rtt_dev->user_data);
	......
    /* change the block device's logic address to physical address */
    rt_off_t phy_pos = pos * rtt_dev->geometry.bytes_per_sector;
    rt_size_t phy_size = size * rtt_dev->geometry.bytes_per_sector;

    if (sfud_read(sfud_dev, phy_pos, phy_size, buffer) != SFUD_SUCCESS) {
        return 0;
    } else {
        return size;
    }
}

static rt_size_t rt_sfud_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size) {
    struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (dev->user_data);
    sfud_flash *sfud_dev = (sfud_flash *) (rtt_dev->user_data);
	......
    /* change the block device's logic address to physical address */
    rt_off_t phy_pos = pos * rtt_dev->geometry.bytes_per_sector;
    rt_size_t phy_size = size * rtt_dev->geometry.bytes_per_sector;

    if (sfud_erase_write(sfud_dev, phy_pos, phy_size, buffer) != SFUD_SUCCESS) {
        return 0;
    } else {
        return size;
    }
}

static rt_err_t rt_sfud_control(rt_device_t dev, int cmd, void *args) {
    RT_ASSERT(dev);

    switch (cmd) {
    case RT_DEVICE_CTRL_BLK_GETGEOME: {
        struct rt_device_blk_geometry *geometry = (struct rt_device_blk_geometry *) args;
        struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (dev->user_data);

        if (rtt_dev == RT_NULL || geometry == RT_NULL) {
            return -RT_ERROR;
        }

        geometry->bytes_per_sector = rtt_dev->geometry.bytes_per_sector;
        geometry->sector_count = rtt_dev->geometry.sector_count;
        geometry->block_size = rtt_dev->geometry.block_size;
        break;
    }
    case RT_DEVICE_CTRL_BLK_ERASE: {
        rt_uint32_t *addrs = (rt_uint32_t *) args, start_addr = addrs[0], end_addr = addrs[1], phy_start_addr;
        struct spi_flash_device *rtt_dev = (struct spi_flash_device *) (dev->user_data);
        sfud_flash *sfud_dev = (sfud_flash *) (rtt_dev->user_data);
        rt_size_t phy_size;

        if (addrs == RT_NULL || start_addr > end_addr || rtt_dev == RT_NULL || sfud_dev == RT_NULL) {
            return -RT_ERROR;
        }

        if (end_addr == start_addr) {
            end_addr ++;
        }

        phy_start_addr = start_addr * rtt_dev->geometry.bytes_per_sector;
        phy_size = (end_addr - start_addr) * rtt_dev->geometry.bytes_per_sector;

        if (sfud_erase(sfud_dev, phy_start_addr, phy_size) != SFUD_SUCCESS) {
            return -RT_ERROR;
        }
        break;
    }
    }

    return RT_EOK;
}

调用函数rt_sfud_flash_probe后,即完成了SFUD框架初始化,并将查找到的串行Flash设备初始化、将操作函数集合flash_device_ops注册到上面的I / O设备管理层,用户便可以通过I / O设备管理接口访问SFUD Flash了。

flash_device_ops中的函数实现代码并没有直接调用sfud_flash.spi.wr和sfud_flash.spi.qspi_read,说明在I / O设备管理接口下面还有一套SFUD框架接口函数,SFUD框架接口函数最终应该是通过调用sfud_flash.spi.wr和sfud_flash.spi.qspi_read实现的,这套接口函数声明如下:

// rt-thread-4.0.1\components\drivers\spi\sfud\src\sfud.c

/**
 * read flash data
 *
 * @param flash flash device
 * @param addr start address
 * @param size read size
 * @param data read data pointer
 *
 * @return result
 */
sfud_err sfud_read(const sfud_flash *flash, uint32_t addr, size_t size, uint8_t *data);

/**
 * erase flash data
 *
 * @note It will erase align by erase granularity.
 *
 * @param flash flash device
 * @param addr start address
 * @param size erase size
 *
 * @return result
 */
sfud_err sfud_erase(const sfud_flash *flash, uint32_t addr, size_t size);

/**
 * write flash data (no erase operate)
 *
 * @param flash flash device
 * @param addr start address
 * @param data write data
 * @param size write size
 *
 * @return result
 */
sfud_err sfud_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data);

/**
 * erase and write flash data
 *
 * @param flash flash device
 * @param addr start address
 * @param size write size
 * @param data write data
 *
 * @return result
 */
sfud_err sfud_erase_write(const sfud_flash *flash, uint32_t addr, size_t size, const uint8_t *data);

/**
 * erase all flash data
 *
 * @param flash flash device
 *
 * @return result
 */
sfud_err sfud_chip_erase(const sfud_flash *flash);

/**
 * read flash register status
 *
 * @param flash flash device
 * @param status register status
 *
 * @return result
 */
sfud_err sfud_read_status(const sfud_flash *flash, uint8_t *status);

/**
 * write status register
 *
 * @param flash flash device
 * @param is_volatile true: volatile mode, false: non-volatile mode
 * @param status register status
 *
 * @return result
 */
sfud_err sfud_write_status(const sfud_flash *flash, bool is_volatile, uint8_t status);

SFUD框架层接口函数都需要传入参数sfud_flash句柄,通过rt_device_find并不能直接获得sfud_flash句柄(可以获得spi_flash_device句柄),为此SFUD还提供了几个接口函数用于获取sfud_flash句柄,这几个函数声明如下:

// rt-thread-4.0.1\components\drivers\spi\spi_flash_sfud.c

/**
 * Find sfud flash device by SPI device name
 *
 * @param spi_dev_name using SPI device name
 *
 * @return sfud flash device if success, otherwise return RT_NULL
 */
sfud_flash_t rt_sfud_flash_find(const char *spi_dev_name);

/**
 * Find sfud flash device by flash device name
 *
 * @param flash_dev_name using flash device name
 *
 * @return sfud flash device if success, otherwise return RT_NULL
 */
sfud_flash_t rt_sfud_flash_find_by_dev_name(const char *flash_dev_name);

/**
 * Delete SPI flash device
 *
 * @param spi_flash_dev SPI flash device
 *
 * @return the operation status, RT_EOK on successful
 */
rt_err_t rt_sfud_flash_delete(rt_spi_flash_device_t spi_flash_dev);

2.3 SFUD访问W25Q128示例

由于SFUD框架是基于SPI / QSPI设备驱动框架层的,所以这里在前面QSPI访问W25Q128示例工程的基础上进行。QSPI设备驱动层的配置在前面已经完成了,所以不需要再进行CubeMX配置。

  • Kconfig新增SFUD配置条件宏

启用SFUD需要定义相应的宏,为了便于使用menuconfig工具,我们依然在Kconfig中增加配置,启用SFUD需要在Kconfig中增加的配置如下:

// projects\stm32l475_device_sample\board\Kconfig

menu "Onboard Peripheral Drivers"
	......
    config BSP_USING_QSPI_FLASH
        bool "Enable QSPI FLASH (W25Q128 qspi1)"
        select BSP_USING_QSPI
        select RT_USING_SFUD
        select RT_SFUD_USING_QSPI
        default n

endmenu

如果使能了BSP_USING_QSPI_FLASH,会同时定义宏BSP_USING_QSPI / RT_USING_SFUD / RT_SFUD_USING_QSPI,其中BSP_USING_QSPI在前面QSPI访问W25Q128示例工程中已经启用了,这里启用SFUD框架主要通过定义宏RT_USING_SFUD和RT_SFUD_USING_QSPI实现。

在Kconfig中新增SFUD配置后,在工程目录打开env工具,输入menuconfig,启用BSP_USING_QSPI_FLASH配置界面如下:
IOT-OS之RT-Thread(九)--- SPI设备对象管理与SFUD管理框架_第6张图片
启用BSP_USING_QSPI_FLASH并保存配置更改后,同时也把RT-Thread Components --> Device Drivers下的Using SFUD和Using QSPI mode support也使能了。

menuconfig启用BSP_USING_QSPI_FLASH后,自动在rtconfig.h中新增的宏定义如下:

// projects\stm32l475_device_sample\rtconfig.h
......
/* Device Drivers */
......
#define RT_USING_SPI
#define RT_USING_QSPI
#define RT_USING_SFUD
#define RT_SFUD_USING_SFDP
#define RT_SFUD_USING_FLASH_INFO_TABLE
#define RT_SFUD_USING_QSPI
......
/* Onboard Peripheral Drivers */
......
#define BSP_USING_QSPI_FLASH
......
  • 编写SFUD访问W25Q128示例代码

继续在之前的spi_sample.c文件中新增本示例代码如下:

// projects\stm32l475_device_sample\applications\spi_sample.c
......
static int rt_hw_spi_flash_init(void)
{
//    if(rt_hw_spi_device_attach(QSPI_BUD_NAME, QSPI_DEVICE_NAME, GPIOE, GPIO_PIN_11) != RT_EOK)
//        return -RT_ERROR;

    if(stm32_qspi_bus_attach_device(QSPI_BUD_NAME, QSPI_DEVICE_NAME, (rt_uint32_t)QSPI_CS_PIN, 1, RT_NULL, RT_NULL) != RT_EOK)
        return -RT_ERROR;

#ifdef RT_USING_SFUD
    if(rt_sfud_flash_probe(W25Q_FLASH_NAME, QSPI_DEVICE_NAME) == RT_NULL)
        return -RT_ERROR;
#endif

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init);


static void sfud_w25q_sample(void)
{
    rt_spi_flash_device_t flash_dev;
    sfud_flash_t sfud_dev;
    struct rt_device_blk_geometry geometry;

    // 1- use sfud api
    rt_kprintf("\n 1 - Use SFUD API \n");

    sfud_dev = rt_sfud_flash_find_by_dev_name(W25Q_FLASH_NAME);
    if(sfud_dev == RT_NULL){
        rt_kprintf("sfud can't find %s device.\n", W25Q_FLASH_NAME);
    }else{
        rt_kprintf("sfud device name: %s, sector_count: %d, bytes_per_sector: %d, block_size: %d.\n", 
                    sfud_dev->name, sfud_dev->chip.capacity / sfud_dev->chip.erase_gran, 
                    sfud_dev->chip.erase_gran, sfud_dev->chip.erase_gran);

        if(sfud_erase_write(sfud_dev, 0x002000, sizeof(wData), wData) == SFUD_SUCCESS)
            rt_kprintf("sfud api write data to w25q128(address:0x2000) success.\n");

        if(sfud_read(sfud_dev, 0x002000, sizeof(rData), rData) == SFUD_SUCCESS)
            rt_kprintf("sfud api read data from w25q128(address:0x2000) is:%s\n", rData);
    }

    // 2- use rt_device api
    rt_kprintf("\n 2 - Use rt_device API \n");

    flash_dev = (rt_spi_flash_device_t)rt_device_find(W25Q_FLASH_NAME);
    if(flash_dev == RT_NULL){
        rt_kprintf("rt_device api can't find %s device.\n", W25Q_FLASH_NAME);
    }else{
        rt_device_open(&flash_dev->flash_device, RT_DEVICE_OFLAG_OPEN);

        if(rt_device_control(&flash_dev->flash_device, RT_DEVICE_CTRL_BLK_GETGEOME, &geometry) == RT_EOK)
            rt_kprintf("spi flash device name: %s, sector_count: %d, bytes_per_sector: %d, block_size: %d.\n", 
                    flash_dev->flash_device.parent.name, geometry.sector_count, geometry.bytes_per_sector, geometry.block_size);

        if(rt_device_write(&flash_dev->flash_device, 0x03, wData, 1) > 0)
            rt_kprintf("rt_device api write data to w25q128(address:0x3000) success.\n");

        if(rt_device_read(&flash_dev->flash_device, 0x03, rData, 1) > 0)
            rt_kprintf("rt_device api read data from w25q128(address:0x3000) is:%s\n", rData);

        rt_device_close(&flash_dev->flash_device);
    }
}
MSH_CMD_EXPORT(sfud_w25q_sample, sfud w25q128 sample);

上面的组件自动初始化代码新增了rt_sfud_flash_probe函数,并通过条件宏判断其是否会被调用。

SFUD访问W25Q128示例函数中使用两种方式分别实现:一种是使用SFUD框架层接口函数实现;另一种是使用注册到I / O设备管理层的统一管理接口实现。

在工程目录打开env,执行scons --target=mdk5生成工程代码,打开project.uvprojx工程编译报错如下:

ArmClang.exe: error: unsupported option '--c99'

说明SFUD框架还不支持ARMClang编译器,不能使用MDK ARM Compiler V6进行编译,我们将MDK使用的编译器改为ARM Compiler V5,再次编译无报错。

将工程代码烧录到STM32L475潘多拉开发板中,通过finsh看到执行结果如下:
IOT-OS之RT-Thread(九)--- SPI设备对象管理与SFUD管理框架_第7张图片
SFUD访问W25Q128的两套API接口函数都运行正常,为了区分,数据读写的地址不同,可以从输出结果看出来。

SFUD除了提供通用访问serial flash的驱动框架及API接口外,还提供了finsh命令sf,从上图可以看出sf命令支持probe / read / write / erase / bench几种命令,要运行后面的命令,需要先执行sf probe命令,下面给出sf命令的运行示例:
IOT-OS之RT-Thread(九)--- SPI设备对象管理与SFUD管理框架_第8张图片
本示例工程源码下载地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_device_sample

更多文章:

  • 《RT-Thread Sample Project Source Code Based on STM32L475》
  • 《STM32之CubeL4(三)— SPI + QSPI + HAL》
  • 《IOT-OS之RT-Thread(八)— IIC设备对象管理与Sensor管理框架》
  • 《IOT-OS之RT-Thread(十)— DFS文件系统管理与devfs/elmfat示例》

你可能感兴趣的:(STM32,操作系统,流云的博客)