文章目錄
一、SPI設備對象管理與示例
前篇博客介紹了I/O設備模型框架,並以PIN設備驅動框架爲例說明了RT-thread I/O設備模型框架的實現原理,下面以SPI設備驅動框架爲例再做進一步介紹。SPI設備與QSPI設備CubeMX配置及HAL API庫函數的使用可參考博客:SPI + QSPI + HAL。
最上層的I/O設備管理層在前篇博客已經介紹過了,下面從中間的SPI設備驅動框架層開始介紹。
1.1 SPI設備驅動框架層
由於SPI設備是主從通信,所以需要對主設備和從設備分別進行描述,SPI主設備稱爲SPI總線,SPI從設備稱爲SPI設備,一個SPI總線上可以綁定多個SPI設備。
- SPI總線控制塊
先看SPI總線在驅動框架層是如何描述的:
// rt-thread-4.0.1\components\drivers\include\drivers\spi.h
struct rt_spi_bus
{
struct rt_device parent;
rt_uint8_t mode;
const struct rt_spi_ops *ops;
struct rt_mutex lock;
struct rt_spi_device *owner;
};
/**
* SPI operators
*/
struct rt_spi_ops
{
rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration);
rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message);
};
- SPI設備控制塊
SPI總線雖然可綁定多個SPI設備,但每次只能與一個從設備通信,所以SPI總線在某時刻擁有一個SPI設備,SPI設備在驅動框架層的描述如下:
// rt-thread-4.0.1\components\drivers\include\drivers\spi.h
/**
* SPI Virtual BUS, one device must connected to a virtual BUS
*/
struct rt_spi_device
{
struct rt_device parent;
struct rt_spi_bus *bus;
struct rt_spi_configuration config;
void *user_data;
};
/**
* SPI configuration structure
*/
struct rt_spi_configuration
{
rt_uint8_t mode;
rt_uint8_t data_width;
rt_uint16_t reserved;
rt_uint32_t max_hz;
};
/**
* SPI message structure
*/
struct rt_spi_message
{
const void *send_buf;
void *recv_buf;
rt_size_t length;
struct rt_spi_message *next;
unsigned cs_take : 1;
unsigned cs_release : 1;
};
SPI總線對SPI設備的訪問只有兩種方法:一個是對SPI設備進行參數配置,配置函數是rt_spi_ops.configure,配置參數結構體爲rt_spi_configuration;另一個是在SPI主從設備間進行數據傳輸,傳輸數據的函數是rt_spi_ops.xfer,傳輸的消息結構體爲rt_spi_message。
- QSPI設備控制塊
// rt-thread-4.0.1\components\drivers\include\drivers\spi.h
struct rt_qspi_device
{
struct rt_spi_device parent;
struct rt_qspi_configuration config;
void (*enter_qspi_mode)(struct rt_qspi_device *device);
void (*exit_qspi_mode)(struct rt_qspi_device *device);
};
struct rt_qspi_configuration
{
struct rt_spi_configuration parent;
/* The size of medium */
rt_uint32_t medium_size;
/* double data rate mode */
rt_uint8_t ddr_mode;
/* the data lines max width which QSPI bus supported, such as 1, 2, 4 */
rt_uint8_t qspi_dl_width ;
};
struct rt_qspi_message
{
struct rt_spi_message parent;
/* instruction stage */
struct
{
rt_uint8_t content;
rt_uint8_t qspi_lines;
} instruction;
/* address and alternate_bytes stage */
struct
{
rt_uint32_t content;
rt_uint8_t size;
rt_uint8_t qspi_lines;
} address, alternate_bytes;
/* dummy_cycles stage */
rt_uint32_t dummy_cycles;
/* number of lines in qspi data stage, the other configuration items are in parent */
rt_uint8_t qspi_data_lines;
};
QSPI總線對QSPI設備的訪問操作跟SPI一致,所以QSPI總線就不再單獨用結構體描述了,直接使用SPI總線結構體,但作爲QSPI總線使用時,rt_spi_ops兩個操作函數指針指向的函數不同,傳入的參數指針也不同,QSPI總線擁有的設備也會指向rt_qspi_device,由於rt_qspi_device繼承自rt_spi_device,二者首地址一致,rt_spi_bus.owner指向QSPI設備也是可以的。
QSPI實際上是對SPI的擴展增強,從SPI設備與QSPI設備的描述結構體也可以看出,QSPI設備結構體都繼承自SPI結構體,並對其進行成員擴展。爲了支持QSPI的命令序列,rt_qspi_message擴展出了QSPI命令序列的指令、地址、複用交替字節、空時鐘週期、數據等五個階段的參數描述。
- SPI總線接口函數
I/O設備管理層要想訪問某設備,需要在下面的設備驅動層創建設備實例,並將該設備註冊到I/O設備管理層,下面先看看SPI總線的創建與註冊過程:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
rt_err_t rt_spi_bus_register(struct rt_spi_bus *bus,
const char *name,
const struct rt_spi_ops *ops)
{
rt_err_t result;
result = rt_spi_bus_device_init(bus, name);
if (result != RT_EOK)
return result;
/* initialize mutex lock */
rt_mutex_init(&(bus->lock), name, RT_IPC_FLAG_FIFO);
/* set ops */
bus->ops = ops;
/* initialize owner */
bus->owner = RT_NULL;
/* set bus mode */
bus->mode = RT_SPI_BUS_MODE_SPI;
return RT_EOK;
}
// rt-thread-4.0.1\components\drivers\spi\spi_dev.c
rt_err_t rt_spi_bus_device_init(struct rt_spi_bus *bus, const char *name)
{
struct rt_device *device;
RT_ASSERT(bus != RT_NULL);
device = &bus->parent;
/* set device type */
device->type = RT_Device_Class_SPIBUS;
/* initialize device interface */
#ifdef RT_USING_DEVICE_OPS
device->ops = &spi_bus_ops;
#else
device->init = RT_NULL;
device->open = RT_NULL;
device->close = RT_NULL;
device->read = _spi_bus_device_read;
device->write = _spi_bus_device_write;
device->control = _spi_bus_device_control;
#endif
/* register to device manager */
return rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
}
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops spi_bus_ops =
{
RT_NULL,
RT_NULL,
RT_NULL,
_spi_bus_device_read,
_spi_bus_device_write,
_spi_bus_device_control
};
#endif
static rt_size_t _spi_bus_device_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
{
struct rt_spi_bus *bus = (struct rt_spi_bus *)dev;
RT_ASSERT(bus != RT_NULL);
RT_ASSERT(bus->owner != RT_NULL);
return rt_spi_transfer(bus->owner, RT_NULL, buffer, size);
}
static rt_size_t _spi_bus_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
struct rt_spi_bus *bus = (struct rt_spi_bus *)dev;
RT_ASSERT(bus != RT_NULL);
RT_ASSERT(bus->owner != RT_NULL);
return rt_spi_transfer(bus->owner, buffer, RT_NULL, size);
}
SPI驅動框架層向上層註冊的操作函數集合spi_bus_ops最終通過調用rt_spi_transfer與rt_spi_configure實現,這兩個函數實際最終調用的是rt_spi_bus.ops(也即rt_spi_ops),這兩個函數的實現由下層的SPI設備驅動層實現。
- SPI設備接口函數
SPI總線初始化並註冊完成後,需要把SPI設備綁定到相應的總線上才能進行SPI通信,SPI設備綁定到SPI總線的過程如下:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
const char *name,
const char *bus_name,
void *user_data)
{
rt_err_t result;
rt_device_t bus;
/* get physical spi bus */
bus = rt_device_find(bus_name);
if (bus != RT_NULL && bus->type == RT_Device_Class_SPIBUS)
{
device->bus = (struct rt_spi_bus *)bus;
/* initialize spidev device */
result = rt_spidev_device_init(device, name);
if (result != RT_EOK)
return result;
rt_memset(&device->config, 0, sizeof(device->config));
device->parent.user_data = user_data;
return RT_EOK;
}
/* not found the host bus */
return -RT_ERROR;
}
rt_err_t rt_spidev_device_init(struct rt_spi_device *dev, const char *name)
{
struct rt_device *device;
RT_ASSERT(dev != RT_NULL);
device = &(dev->parent);
/* set device type */
device->type = RT_Device_Class_SPIDevice;
#ifdef RT_USING_DEVICE_OPS
device->ops = &spi_device_ops;
#else
device->init = RT_NULL;
device->open = RT_NULL;
device->close = RT_NULL;
device->read = _spidev_device_read;
device->write = _spidev_device_write;
device->control = _spidev_device_control;
#endif
/* register to device manager */
return rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
}
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops spi_device_ops =
{
RT_NULL,
RT_NULL,
RT_NULL,
_spidev_device_read,
_spidev_device_write,
_spidev_device_control
};
#endif
/* SPI Dev device interface, compatible with RT-Thread 0.3.x/1.0.x */
static rt_size_t _spidev_device_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
{
struct rt_spi_device *device = (struct rt_spi_device *)dev;
RT_ASSERT(device != RT_NULL);
RT_ASSERT(device->bus != RT_NULL);
return rt_spi_transfer(device, RT_NULL, buffer, size);
}
static rt_size_t _spidev_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
{
struct rt_spi_device *device= = (struct rt_spi_device *)dev;
RT_ASSERT(device != RT_NULL);
RT_ASSERT(device->bus != RT_NULL);
return rt_spi_transfer(device, buffer, RT_NULL, size);
}
SPI設備向上層註冊的操作函數集合與SPI總線類似,用戶可以通過上層提供的統一接口rt_device_ops來訪問SPI設備。
SPI設備也向用戶提供了SPI設備驅動框架層的一些API接口,最主要的是rt_spi_ops的兩個接口:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
/**
* This function set configuration on SPI device.
*
* @param device the SPI device attached to SPI bus
* @param cfg the config parameter to be set to SPI device
*
* @return RT_EOK if set config successfully.
*/
rt_err_t rt_spi_configure(struct rt_spi_device *device,
struct rt_spi_configuration *cfg);
/**
* This function transfers a message list to the SPI device.
*
* @param device the SPI device attached to SPI bus
* @param message the message list to be transmitted to SPI device
*
* @return RT_NULL if transmits message list successfully,
* SPI message which be transmitted failed.
*/
struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device,
struct rt_spi_message *message);
SPI設備接口除了上面兩個外,還有爲方便用戶,基於這兩個函數擴展出來的接口函數:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
/**
* This function transmits data to SPI device.
*
* @param device the SPI device attached to SPI bus
* @param send_buf the buffer to be transmitted to SPI device.
* @param recv_buf the buffer to save received data from SPI device.
* @param length the length of transmitted data.
*
* @return the actual length of transmitted.
*/
rt_size_t rt_spi_transfer(struct rt_spi_device *device,
const void *send_buf,
void *recv_buf,
rt_size_t length);
/* send data then receive data from SPI device */
rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device,
const void *send_buf,
rt_size_t send_length,
void *recv_buf,
rt_size_t recv_length);
rt_err_t rt_spi_send_then_send(struct rt_spi_device *device,
const void *send_buf1,
rt_size_t send_length1,
const void *send_buf2,
rt_size_t send_length2);
rt_size_t rt_spi_recv(struct rt_spi_device *device,
void *recv_buf,
rt_size_t length);
rt_size_t rt_spi_send(struct rt_spi_device *device,
const void *send_buf,
rt_size_t length);
一個SPI總線有時需要連接多個SPI設備,但每次只能與其中一個SPI設備通信,爲解決SPI總線在多個SPI設備中的訪問互斥問題,SPI設備驅動框架層提供了SPI總線與SPI設備的獲取/釋放函數接口:
// rt-thread-4.0.1\components\drivers\spi\spi_core.c
/**
* This function takes SPI bus.
*
* @param device the SPI device attached to SPI bus
*
* @return RT_EOK on taken SPI bus successfully. others on taken SPI bus failed.
*/
rt_err_t rt_spi_take_bus(struct rt_spi_device *device);
/**
* This function releases SPI bus.
*
* @param device the SPI device attached to SPI bus
*
* @return RT_EOK on release SPI bus successfully.
*/
rt_err_t rt_spi_release_bus(struct rt_spi_device *device);
/**
* This function take SPI device (takes CS of SPI device).
*
* @param device the SPI device attached to SPI bus
*
* @return RT_EOK on release SPI bus successfully. others on taken SPI bus failed.
*/
rt_err_t rt_spi_take(struct rt_spi_device *device);
/**
* This function releases SPI device (releases CS of SPI device).
* * @param device the SPI device attached to SPI bus
* * @return RT_EOK on release SPI device successfully.
*/
rt_err_t rt_spi_release(struct rt_spi_device *device);
- QSPI總線接口函數
QSPI總線與SPI總線雖然共有描述結構體,但其中的成員參數配置有些許差異,爲此QSPI總線創建與註冊函數在SPI總線註冊函數的基礎上進行了封裝,QSPI總線註冊函數如下:
// rt-thread-4.0.1\components\drivers\spi\qspi_core.c
rt_err_t rt_qspi_bus_register(struct rt_spi_bus *bus, const char *name, const struct rt_spi_ops *ops)
{
rt_err_t result = RT_EOK;
result = rt_spi_bus_register(bus, name, ops);
if(result == RT_EOK)
{
/* set SPI bus to qspi modes */
bus->mode = RT_SPI_BUS_MODE_QSPI;
}
return result;
}
- QSPI設備接口函數
QSPI設備綁定到QSPI總線的函數並沒有在QSPI驅動框架層提供,這裏就不介紹了。
QSPI設備的配置與傳輸函數聲明如下:
// rt-thread-4.0.1\components\drivers\spi\qspi_core.c
/**
* This function can set configuration on QSPI device.
*
* @param device the QSPI device attached to QSPI bus.
* @param cfg the configuration pointer.
*
* @return the actual length of transmitted.
*/
rt_err_t rt_qspi_configure(struct rt_qspi_device *device, struct rt_qspi_configuration *cfg);
/**
* This function transmits data to QSPI device.
*
* @param device the QSPI device attached to QSPI bus.
* @param message the message pointer.
*
* @return the actual length of transmitted.
*/
rt_size_t rt_qspi_transfer_message(struct rt_qspi_device *device, struct rt_qspi_message *message);
/**
* This function can send data then receive data from QSPI device
*
* @param device the QSPI device attached to QSPI bus.
* @param send_buf the buffer to be transmitted to QSPI device.
* @param send_length the number of data to be transmitted.
* @param recv_buf the buffer to be recivied from QSPI device.
* @param recv_length the data to be recivied.
*
* @return the status of transmit.
*/
rt_err_t rt_qspi_send_then_recv(struct rt_qspi_device *device, const void *send_buf, rt_size_t send_length,void *recv_buf, rt_size_t recv_length);
/**
* This function can send data to QSPI device
*
* @param device the QSPI device attached to QSPI bus.
* @param send_buf the buffer to be transmitted to QSPI device.
* @param send_length the number of data to be transmitted.
*
* @return the status of transmit.
*/
rt_err_t rt_qspi_send(struct rt_qspi_device *device, const void *send_buf, rt_size_t length);
QSPI的接口函數最終由註冊到QSPI總線的操作函數集合rt_spi_ops實現,rt_spi_ops中的兩個函數則有QSPI設備驅動層提供。
1.2 SPI設備驅動層
在SPI設備驅動層,並沒有刻意將SPI主從設備分開描述,SPI總線與SPI設備更多的是方便上層協議管理而抽象出來的。
- SPI設備驅動描述
在STM32 HAL庫中有結構體SPI_HandleTypeDef描述SPI外設,RT-Thread爲方便上面SPI設備驅動框架層的管理又增添了一些成員變量,比如配置參數(包括SPI配置參數與DMA配置參數)等,SPI設備驅動描述如下:
// libraries\HAL_Drivers\drv_spi.h
/* stm32 spi dirver class */
struct stm32_spi
{
SPI_HandleTypeDef handle;
struct stm32_spi_config *config;
struct rt_spi_configuration *cfg;
struct
{
DMA_HandleTypeDef handle_rx;
DMA_HandleTypeDef handle_tx;
} dma;
rt_uint8_t spi_dma_flag;
struct rt_spi_bus spi_bus;
};
struct stm32_spi_config
{
SPI_TypeDef *Instance;
char *bus_name;
struct dma_config *dma_rx, *dma_tx;
};
struct stm32_spi_device
{
rt_uint32_t pin;
char *bus_name;
char *device_name;
};
struct stm32_hw_spi_cs
{
GPIO_TypeDef* GPIOx;
uint16_t GPIO_Pin;
};
SPI設備有一個片選引腳NCS,一般SPI設備只有一個片選引腳,當出現一主多從SPI設備時,SPI主設備的片選引腳不夠用,可以使用GPIO引腳作爲NCS片選引腳,每個GPIO控制一個SPI從設備的片選使能,這種方式成爲NCS的軟件控制模式,也是較常用的方式。
SPI設備驅動可由結構體stm32_spi描述,爲方便向上面的SPI驅動框架層註冊綁定設備,還提供了stm32_spi_device結構體,包含SPI總線名、SPI設備名、SPI設備的片選引腳等。
- SPI設備驅動接口
首先看SPI驅動初始化過程:
// libraries\HAL_Drivers\drv_spi.c
int rt_hw_spi_init(void)
{
stm32_get_dma_info();
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的連接原理圖如下:
- CubeMX配置QUADSPI
打開CubeMX文件projects\stm32l475_device_sample\board \CubeMX_Config\CubeMX_Config.ioc新增QSPI配置如下圖所示:
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配置界面如下圖所示:
使能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潘多拉開發板中,運行結果如下:
本示例工程源碼下載地址: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配置界面如下:
啓用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的兩套API接口函數都運行正常,爲了區分,數據讀寫的地址不同,可以從輸出結果看出來。
SFUD除了提供通用訪問serial flash的驅動框架及API接口外,還提供了finsh命令sf,從上圖可以看出sf命令支持probe / read / write / erase / bench幾種命令,要運行後面的命令,需要先執行sf probe命令,下面給出sf命令的運行示例:
本示例工程源碼下載地址:https://github.com/StreamAI/RT-Thread_Projects/tree/master/projects/stm32l475_device_sample