nRF5 SDK v13.0.0
Experimental: QSPI
This information applies to the nRF52840 SoC only.

The Quad Serial Peripheral Interface (QSPI) driver includes two layers: the hardware access layer (HAL) and the driver layer (DRV).

The hardware access layer provides basic APIs for accessing the registers of the QSPI peripheral. For details, see the API documentation for QSPI HAL.

The driver layer provides APIs on a higher level than the HAL. For details, see the API documentation for QSPI driver.

Key features include:

The Experimental: QSPI Example provides sample code that you can use to quickly get started. Note that before the transmission starts, both the peripheral and memory must be configured, unlike for other drivers, where only the peripheral must be configured.

Driver configuration

The configurable parameters include:

Using the QSPI driver with the memory device for asynchronous transfers

Writing an application that uses QSPI involves two steps:

Defining a data handling function

Define a helper Boolean and the data handling function for QSPI. Example:

volatile bool m_finished = false;
static void data_handler(nrf_drv_qspi_evt_t event, void * p_context)
{
{
// Handle incoming event from the peripheral.
m_finished = true;
}
}

Initializing and configuring the driver

Call nrf_drv_qspi_init to initialize and configure the driver. For possible configuration options, refer to the nrf_drv_qspi_config_t structure documentation. Values in fields configured by NRF_DRV_QSPI_DEFAULT_CONFIG can be found in the sdk_config.h file (see the QSPI_ prefix). Remember that the default configuration of pins is fetched from the pca10056.h file which can be found in the components/boards directory.

Example:

uint32_t err_code;
err_code = nrf_drv_qspi_init(&config, data_handler, NULL);
if (err_code != NRF_SUCCESS)
{
// Initialization failed. Take recovery action.
}

Configuring memory

Configure the memory using proper custom instructions. This task depends on the chosen memory. Refer to the memory documentation before writing code. The example code below works with MX25L6435F memory which can be find on the PCA10056 Development Kit.

Remember that nrf_drv_qspi_cinstr_xfer is a synchronous function and it does not require checking the status flags in peripheral registers.

#define QSPI_MEM_CMD_RST_EN 0x66
#define QSPI_MEM_CMD_RST_MEM 0x99
#define QSPI_MEM_CMD_WRSR 0x01
#define QSPI_MEM_QUAD_MODE 0x40
...
uint8_t transfer_byte = 0x40;
nrf_qspi_cinstr_conf_t cinstr_cfg = {
.opcode = QSPI_MEM_CMD_RST_EN,
.io2_level = true,
.io3_level = true,
.wipwait = true,
.wren = true
};
// Enable memory reset function.
nrf_drv_qspi_cinstr_xfer(&cinstr_cfg, &transfer_byte, NULL, false);
// Reset the memory.
cinstr_cfg.opcode = QSPI_MEM_CMD_RST_MEM;
nrf_drv_qspi_cinstr_xfer(&cinstr_cfg, &transfer_byte, NULL, false);
// Enable quad mode.
cinstr_cfg.opcode = QSPI_STD_CMD_WRSR;
nrf_drv_qspi_cinstr_xfer(&cinstr_cfg, &transfer_byte, NULL, false);
Note
Before peripheral initialization, if the memory used in the system supports the 32-bit addressing mode, set the addrmode field in the nrf_drv_qspi_config_t instance. Then, after peripheral initialization, send a proper custom instruction to the memory device.

Transferring data

You can now use nrf_drv_qspi_write and nrf_drv_qspi_read to transfer data. Memory status is checked automatically before the start of read, write, and erase operations. The event comes after the last operation is finished.

QSPI_write_example.svg
Example of a write operation. Checking status byte and event generation.

Remember to align the adress to four bytes. You do not need to check how much data will be transfered into memory during one transfer. The peripheral splits data into 256-byte transfers.

Note
In many cases, memories have a 256-byte page buffer and it is important to wait for the transfer to finish before filling the buffer with new data to avoid data corruption.
uint8_t m_buffer_tx[QSPI_TEST_DATA_SIZE];
uint8_t m_buffer_rx[QSPI_TEST_DATA_SIZE];
nrf_drv_qspi_write(&m_buffer_tx, QSPI_TEST_DATA_SIZE, 0);
// Wait until sending of opcode and data is finished.
while(!m_finished);
m_finished = false;
nrf_drv_qspi_read(&m_buffer_rx, QSPI_TEST_DATA_SIZE, 0);
// Wait until reading of data from the memory device is finished.
while(!m_finished);
m_finished = false;

Erasing data

  1. If an erase step is required, nrf_drv_qspi_erase can be used to erase one block or nrf_drv_qspi_chip_erase to erase the whole chip. Remember that the erase operation takes a lot of time. If an erase operation is executed before a read or write operation, a long delay is observed.
// Wait until sending of opcode is finished.
while(!m_finished);
m_finished = false;

Another option is to prepare a function that waits for Erase Finished state. Additionally, a timeout feature can be implemented in such case:

uint32_t ms_time = 0;
while (nrf_drv_qspi_mem_busy_check() == NRF_ERROR_BUSY)
{
if (ms_time++ > timeout_ms) {
return NRF_ERROR_TIMEOUT;
}
nrf_delay_ms(1);
};

When the QSPI driver is no longer needed or its configuration must be changed, call nrf_drv_qspi_uninit.


Documentation feedback | Developer Zone | Subscribe | Updated