前篇博客介绍了I/O设备模型框架,并以PIN设备驱动框架为例说明了RT-thread I/O设备模型框架的实现原理,下面以SPI设备驱动框架为例再做进一步介绍。SPI设备与QSPI设备CubeMX配置及HAL API库函数的使用可参考博客:SPI + QSPI + HAL。
最上层的I/O设备管理层在前篇博客已经介绍过了,下面从中间的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设备在驱动框架层的描述如下:
// 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。
// 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命令序列的指令、地址、复用交替字节、空时钟周期、数据等五个阶段的参数描述。
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总线的过程如下:
// 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总线与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设备的配置与传输函数声明如下:
// 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设备驱动层提供。
在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驱动初始化过程:
// 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驱动初始化过程:
// 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设备绑定函数。
在前篇博客的工程stm32l475_device_sample基础上新增QSPI外设访问W25Q128 Flash,STM32L475与W25Q128的连接原理图如下:
打开CubeMX文件projects\stm32l475_device_sample\board \CubeMX_Config\CubeMX_Config.ioc新增QSPI配置如下图所示:
QUADSPI配置参数的含义在博客:SPI + QSPI + HAL中有详细介绍。QUADSPI配置完后直接点击GENERATE CODE生成工程代码即可。
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
本示例工程完成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
像W25Q128这种串行Flash芯片种类有很多,如果每种芯片都提供一套驱动函数不便于管理,RT-Thread为串行Flash提供了一套通用驱动函数SFUD(Serial Flash Universal Driver),能驱动多数常用SPI Flash,SFUD属于SPI设备驱动框架层。
在介绍SFUD Flash设备描述结构体前,先介绍下SPI Flash结构体。SFUD Flash为了通用性,描述信息比较多;SPI Flash相对简单,而且是SFUD框架层与I / O设备管理层之间的桥梁。
// 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句柄。
// 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的实现是为了支持尽可能多的芯片型号,但市场上芯片型号太多,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,方便的可扩展性也是通用性的体现。
由于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);
由于SFUD框架是基于SPI / QSPI设备驱动框架层的,所以这里在前面QSPI访问W25Q128示例工程的基础上进行。QSPI设备驱动层的配置在前面已经完成了,所以不需要再进行CubeMX配置。
启用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
......
继续在之前的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