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_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的連接原理圖如下:
STM32L475與W25Q128連接原理圖

  • CubeMX配置QUADSPI

打開CubeMX文件projects\stm32l475_device_sample\board \CubeMX_Config\CubeMX_Config.ioc新增QSPI配置如下圖所示:
STM32L475 QUADSPI CubeMX配置
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配置界面如下圖所示:
menuconfig配置QSPI BUS
使能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潘多拉開發板中,運行結果如下:
QSPI訪問W25Q128示例運行結果
本示例工程源碼下載地址: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配置界面如下:
啓用SFUD配置界面
啓用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看到執行結果如下:
SFUD訪問W25Q128示例執行結果
SFUD訪問W25Q128的兩套API接口函數都運行正常,爲了區分,數據讀寫的地址不同,可以從輸出結果看出來。

SFUD除了提供通用訪問serial flash的驅動框架及API接口外,還提供了finsh命令sf,從上圖可以看出sf命令支持probe / read / write / erase / bench幾種命令,要運行後面的命令,需要先執行sf probe命令,下面給出sf命令的運行示例:
SFUD sf命令執行示例
本示例工程源碼下載地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_device_sample

更多文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章