SPI Master driver

Overview

The ESP32 has four SPI peripheral devices, called SPI0, SPI1, HSPI and VSPI. SPI0 is entirely dedicated to the flash cache the ESP32 uses to map the SPI flash device it is connected to into memory. SPI1 is connected to the same hardware lines as SPI0 and is used to write to the flash chip. HSPI and VSPI are free to use. SPI1, HSPI and VSPI all have three chip select lines, allowing them to drive up to three SPI devices each as a master.

The spi_master driver

The spi_master driver allows easy communicating with SPI slave devices, even in a multithreaded environment. It fully transparently handles DMA transfers to read and write data and automatically takes care of multiplexing between different SPI slaves on the same master

Terminology

The spi_master driver uses the following terms:

  • Host: The SPI peripheral inside the ESP32 initiating the SPI transmissions. One of SPI, HSPI or VSPI. (For now, only HSPI or VSPI are actually supported in the driver; it will support all 3 peripherals somewhere in the future.)
  • Bus: The SPI bus, common to all SPI devices connected to one host. In general the bus consists of the miso, mosi, sclk and optionally quadwp and quadhd signals. The SPI slaves are connected to these signals in parallel.
    • miso - Also known as q, this is the input of the serial stream into the ESP32
    • mosi - Also known as d, this is the output of the serial stream from the ESP32
    • sclk - Clock signal. Each data bit is clocked out or in on the positive or negative edge of this signal
    • quadwp - Write Protect signal. Only used for 4-bit (qio/qout) transactions.
    • quadhd - Hold signal. Only used for 4-bit (qio/qout) transactions.
  • Device: A SPI slave. Each SPI slave has its own chip select (CS) line, which is made active when a transmission to/from the SPI slave occurs.
  • Transaction: One instance of CS going active, data transfer from and/or to a device happening, and CS going inactive again. Transactions are atomic, as in they will never be interrupted by another transaction.

SPI transactions

A transaction on the SPI bus consists of five phases, any of which may be skipped:

  • The command phase. In this phase, a command (0-16 bit) is clocked out.
  • The address phase. In this phase, an address (0-64 bit) is clocked out.
  • The write phase. The master sends data to the slave.
  • The dummy phase. The phase is configurable, used to meet the timing requirements.
  • The read phase. The slave sends data to the master.

In full duplex, the read and write phases are combined, causing the SPI host to read and write data simultaneously. The total transaction length is decided by command_bits + address_bits + trans_conf.length, while the trans_conf.rx_length only determins length of data received into the buffer.

In half duplex, the length of write phase and read phase are decided by trans_conf.length and trans_conf.rx_length respectively. ** Note that a half duplex transaction with both a read and write phase is not supported when using DMA. ** If such transaction is needed, you have to use one of the alternative solutions:

  1. use full-duplex mode instead.

  2. disable the DMA by set the last parameter to 0 in bus initialization function just as belows: ret=spi_bus_initialize(VSPI_HOST, &buscfg, 0);

    this may prohibit you from transmitting and receiving data longer than 32 bytes.

  3. try to use command and address field to replace the write phase.

The command and address phase are optional in that not every SPI device will need to be sent a command and/or address. This is reflected in the device configuration: when the command_bits or address_bits fields are set to zero, no command or address phase is done.

Something similar is true for the read and write phase: not every transaction needs both data to be written as well as data to be read. When rx_buffer is NULL (and SPI_USE_RXDATA) is not set) the read phase is skipped. When tx_buffer is NULL (and SPI_USE_TXDATA) is not set) the write phase is skipped.

Using the spi_master driver

  • Initialize a SPI bus by calling spi_bus_initialize. Make sure to set the correct IO pins in the bus_config struct. Take care to set signals that are not needed to -1.
  • Tell the driver about a SPI slave device connected to the bus by calling spi_bus_add_device. Make sure to configure any timing requirements the device has in the dev_config structure. You should now have a handle for the device, to be used when sending it a transaction.
  • To interact with the device, fill one or more spi_transaction_t structure with any transaction parameters you need. Either queue all transactions by calling spi_device_queue_trans, later quering the result using spi_device_get_trans_result, or handle all requests synchroneously by feeding them into spi_device_transmit.
  • Optional: to unload the driver for a device, call spi_bus_remove_device with the device handle as an argument
  • Optional: to remove the driver for a bus, make sure no more drivers are attached and call spi_bus_free.

Command and address phases

During the command and address phases, cmd and addr field in the spi_transaction_t struct are sent to the bus, while nothing is read at the same time. The default length of command and address phase are set in the spi_device_interface_config_t and by spi_bus_add_device. When the the flag SPI_TRANS_VARIABLE_CMD and SPI_TRANS_VARIABLE_ADDR are not set in the spi_transaction_t,the driver automatically set the length of these phases to the default value as set when the device is initialized respectively.

If the length of command and address phases needs to be variable, declare a spi_transaction_ext_t descriptor, set the flag SPI_TRANS_VARIABLE_CMD or/and SPI_TRANS_VARIABLE_ADDR in the flags of base member and configure the rest part of base as usual. Then the length of each phases will be command_bits and address_bits set in the spi_transaction_ext_t.

Write and read phases

Normally, data to be transferred to or from a device will be read from or written to a chunk of memory indicated by the rx_buffer and tx_buffer members of the transaction structure. When DMA is enabled for transfers, these buffers are highly recommended to meet the requirements as belows:

  1. allocated in DMA-capable memory using pvPortMallocCaps(size, MALLOC_CAP_DMA);
  2. 32-bit aligned (start from the boundary and have length of multiples of 4 bytes).

If these requirements are not satisfied, efficiency of the transaction will suffer due to the allocation and memcpy of temporary buffers.

Sometimes, the amount of data is very small making it less than optimal allocating a separate buffer for it. If the data to be transferred is 32 bits or less, it can be stored in the transaction struct itself. For transmitted data, use the tx_data member for this and set the SPI_USE_TXDATA flag on the transmission. For received data, use rx_data and set SPI_USE_RXDATA. In both cases, do not touch the tx_buffer or rx_buffer members, because they use the same memory locations as tx_data and rx_data.

Application Example

Display graphics on the 320x240 LCD of WROVER-Kits: peripherals/spi_master.

API Reference - SPI Common

Functions

bool spicommon_periph_claim(spi_host_device_t host)

Try to claim a SPI peripheral.

Call this if your driver wants to manage a SPI peripheral.

Return
True if peripheral is claimed successfully; false if peripheral already is claimed.
Parameters
  • host: Peripheral to claim

bool spicommon_periph_free(spi_host_device_t host)

Return the SPI peripheral so another driver can claim it.

Return
True if peripheral is returned successfully; false if peripheral was free to claim already.
Parameters
  • host: Peripheral to return

bool spicommon_dma_chan_claim(int dma_chan)

Try to claim a SPI DMA channel.

Call this if your driver wants to use SPI with a DMA channnel.

Return
True if success; false otherwise.
Parameters
  • dma_chan: channel to claim

bool spicommon_dma_chan_free(int dma_chan)

Return the SPI DMA channel so other driver can claim it, or just to power down DMA.

Return
True if success; false otherwise.
Parameters
  • dma_chan: channel to return

esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_config_t *bus_config, int dma_chan, int flags, bool *is_native)

Connect a SPI peripheral to GPIO pins.

This routine is used to connect a SPI peripheral to the IO-pads and DMA channel given in the arguments. Depending on the IO-pads requested, the routing is done either using the IO_mux or using the GPIO matrix.

Return
  • ESP_ERR_INVALID_ARG if parameter is invalid
  • ESP_OK on success
Parameters
  • host: SPI peripheral to be routed
  • bus_config: Pointer to a spi_bus_config struct detailing the GPIO pins
  • dma_chan: DMA-channel (1 or 2) to use, or 0 for no DMA.
  • flags: Combination of SPICOMMON_BUSFLAG_* flags
  • is_native: A value of ‘true’ will be written to this address if the GPIOs can be routed using the IO_mux, ‘false’ if the GPIO matrix is used.

esp_err_t spicommon_bus_free_io(spi_host_device_t host)

Free the IO used by a SPI peripheral.

Return
  • ESP_ERR_INVALID_ARG if parameter is invalid
  • ESP_OK on success
Parameters
  • host: SPI peripheral to be freed

void spicommon_cs_initialize(spi_host_device_t host, int cs_io_num, int cs_num, int force_gpio_matrix)

Initialize a Chip Select pin for a specific SPI peripheral.

Parameters
  • host: SPI peripheral
  • cs_io_num: GPIO pin to route
  • cs_num: CS id to route
  • force_gpio_matrix: If true, CS will always be routed through the GPIO matrix. If false, if the GPIO number allows it, the routing will happen through the IO_mux.

void spicommon_cs_free(spi_host_device_t host, int cs_num)

Free a chip select line.

Parameters
  • host: SPI peripheral
  • cs_num: CS id to free

void spicommon_setup_dma_desc_links(lldesc_t *dmadesc, int len, const uint8_t *data, bool isrx)

Setup a DMA link chain.

This routine will set up a chain of linked DMA descriptors in the array pointed to by dmadesc. Enough DMA descriptors will be used to fit the buffer of len bytes in, and the descriptors will point to the corresponding positions in buffer and linked together. The end result is that feeding dmadesc[0] into DMA hardware results in the entirety len bytes of data being read or written.

Parameters
  • dmadesc: Pointer to array of DMA descriptors big enough to be able to convey len bytes
  • len: Length of buffer
  • data: Data buffer to use for DMA transfer
  • isrx: True if data is to be written into data, false if it’s to be read from data.

spi_dev_t *spicommon_hw_for_host(spi_host_device_t host)

Get the position of the hardware registers for a specific SPI host.

Return
A register descriptor stuct pointer, pointed at the hardware registers
Parameters
  • host: The SPI host

void spicommon_freeze_cs(spi_host_device_t host)

Temporarily connect CS signal input to high to avoid slave detecting unexpected transactions.

Note
Don’t use this in the application.
Parameters
  • host: The spi host.

void spicommon_restore_cs(spi_host_device_t host, int cs_io_num, bool iomux)

Use this function instead of cs_initial to avoid overwrite the output config This is used in test by internal gpio matrix connections

Note
Don’t use this in the application.
Parameters
  • host: The spi host.
  • cs_io_num: GPIO number of the CS pin.
  • iomux: The peripheral is using iomux pins.

int spicommon_irqsource_for_host(spi_host_device_t host)

Get the IRQ source for a specific SPI host.

Return
The hosts IRQ source
Parameters
  • host: The SPI host

bool spicommon_dmaworkaround_req_reset(int dmachan, dmaworkaround_cb_t cb, void *arg)

Request a reset for a certain DMA channel.

Essentially, when a reset is needed, a driver can request this using spicommon_dmaworkaround_req_reset. This is supposed to be called with an user-supplied function as an argument. If both DMA channels are idle, this call will reset the DMA subsystem and return true. If the other DMA channel is still busy, it will return false; as soon as the other DMA channel is done, however, it will reset the DMA subsystem and call the callback. The callback is then supposed to be used to continue the SPI drivers activity.

Note
In some (well-defined) cases in the ESP32 (at least rev v.0 and v.1), a SPI DMA channel will get confused. This can be remedied by resetting the SPI DMA hardware in case this happens. Unfortunately, the reset knob used for thsi will reset both DMA channels, and as such can only done safely when both DMA channels are idle. These functions coordinate this.

Return
True when a DMA reset could be executed immediately. False when it could not; in this case the callback will be called with the specified argument when the logic can execute a reset, after that reset.
Parameters
  • dmachan: DMA channel associated with the SPI host that needs a reset
  • cb: Callback to call in case DMA channel cannot be reset immediately
  • arg: Argument to the callback

bool spicommon_dmaworkaround_reset_in_progress()

Check if a DMA reset is requested but has not completed yet.

Return
True when a DMA reset is requested but hasn’t completed yet. False otherwise.

void spicommon_dmaworkaround_idle(int dmachan)

Mark a DMA channel as idle.

A call to this function tells the workaround logic that this channel will not be affected by a global SPI DMA reset.

void spicommon_dmaworkaround_transfer_active(int dmachan)

Mark a DMA channel as active.

A call to this function tells the workaround logic that this channel will be affected by a global SPI DMA reset, and a reset like that should not be attempted.

Structures

struct spi_bus_config_t

This is a configuration structure for a SPI bus.

You can use this structure to specify the GPIO pins of the bus. Normally, the driver will use the GPIO matrix to route the signals. An exception is made when all signals either can be routed through the IO_MUX or are -1. In that case, the IO_MUX is used, allowing for >40MHz speeds.

Note
Be advised that the slave driver does not use the quadwp/quadhd lines and fields in spi_bus_config_t refering to these lines will be ignored and can thus safely be left uninitialized.

Public Members

int mosi_io_num

GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used.

int miso_io_num

GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used.

int sclk_io_num

GPIO pin for Spi CLocK signal, or -1 if not used.

int quadwp_io_num

GPIO pin for WP (Write Protect) signal which is used as D2 in 4-bit communication modes, or -1 if not used.

int quadhd_io_num

GPIO pin for HD (HolD) signal which is used as D3 in 4-bit communication modes, or -1 if not used.

int max_transfer_sz

Maximum transfer size, in bytes. Defaults to 4094 if 0.

Macros

SPI_MAX_DMA_LEN
SPICOMMON_BUSFLAG_SLAVE

Initialize I/O in slave mode.

SPICOMMON_BUSFLAG_MASTER

Initialize I/O in master mode.

SPICOMMON_BUSFLAG_QUAD

Also initialize WP/HD pins, if specified.

Type Definitions

typedef void (*dmaworkaround_cb_t)(void *arg)

Callback, to be called when a DMA engine reset is completed

Enumerations

enum spi_host_device_t

Enum with the three SPI peripherals that are software-accessible in it.

Values:

SPI_HOST =0

SPI1, SPI.

HSPI_HOST =1

SPI2, HSPI.

VSPI_HOST =2

SPI3, VSPI.

API Reference - SPI Master

Functions

esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, int dma_chan)

Initialize a SPI bus.

Warning
For now, only supports HSPI and VSPI.
Warning
If a DMA channel is selected, any transmit and receive buffer used should be allocated in DMA-capable memory.
Return
  • ESP_ERR_INVALID_ARG if configuration is invalid
  • ESP_ERR_INVALID_STATE if host already is in use
  • ESP_ERR_NO_MEM if out of memory
  • ESP_OK on success
Parameters
  • host: SPI peripheral that controls this bus
  • bus_config: Pointer to a spi_bus_config_t struct specifying how the host should be initialized
  • dma_chan: Either channel 1 or 2, or 0 in the case when no DMA is required. Selecting a DMA channel for a SPI bus allows transfers on the bus to have sizes only limited by the amount of internal memory. Selecting no DMA channel (by passing the value 0) limits the amount of bytes transfered to a maximum of 32.

esp_err_t spi_bus_free(spi_host_device_t host)

Free a SPI bus.

Warning
In order for this to succeed, all devices have to be removed first.
Return
  • ESP_ERR_INVALID_ARG if parameter is invalid
  • ESP_ERR_INVALID_STATE if not all devices on the bus are freed
  • ESP_OK on success
Parameters
  • host: SPI peripheral to free

esp_err_t spi_bus_add_device(spi_host_device_t host, spi_device_interface_config_t *dev_config, spi_device_handle_t *handle)

Allocate a device on a SPI bus.

This initializes the internal structures for a device, plus allocates a CS pin on the indicated SPI master peripheral and routes it to the indicated GPIO. All SPI master devices have three CS pins and can thus control up to three devices.

Note
While in general, speeds up to 80MHz on the dedicated SPI pins and 40MHz on GPIO-matrix-routed pins are supported, full-duplex transfers routed over the GPIO matrix only support speeds up to 26MHz.
Return
  • ESP_ERR_INVALID_ARG if parameter is invalid
  • ESP_ERR_NOT_FOUND if host doesn’t have any free CS slots
  • ESP_ERR_NO_MEM if out of memory
  • ESP_OK on success
Parameters
  • host: SPI peripheral to allocate device on
  • dev_config: SPI interface protocol config for the device
  • handle: Pointer to variable to hold the device handle

esp_err_t spi_bus_remove_device(spi_device_handle_t handle)

Remove a device from the SPI bus.

Return
  • ESP_ERR_INVALID_ARG if parameter is invalid
  • ESP_ERR_INVALID_STATE if device already is freed
  • ESP_OK on success
Parameters
  • handle: Device handle to free

esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait)

Queue a SPI transaction for execution.

Return
  • ESP_ERR_INVALID_ARG if parameter is invalid
  • ESP_ERR_TIMEOUT if there was no room in the queue before ticks_to_wait expired
  • ESP_ERR_NO_MEM if allocating DMA-capable temporary buffer failed
  • ESP_OK on success
Parameters
  • handle: Device handle obtained using spi_host_add_dev
  • trans_desc: Description of transaction to execute
  • ticks_to_wait: Ticks to wait until there’s room in the queue; use portMAX_DELAY to never time out.

esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, spi_transaction_t **trans_desc, TickType_t ticks_to_wait)

Get the result of a SPI transaction queued earlier.

This routine will wait until a transaction to the given device (queued earlier with spi_device_queue_trans) has succesfully completed. It will then return the description of the completed transaction so software can inspect the result and e.g. free the memory or re-use the buffers.

Return
  • ESP_ERR_INVALID_ARG if parameter is invalid
  • ESP_ERR_TIMEOUT if there was no completed transaction before ticks_to_wait expired
  • ESP_OK on success
Parameters
  • handle: Device handle obtained using spi_host_add_dev
  • trans_desc: Pointer to variable able to contain a pointer to the description of the transaction that is executed. The descriptor should not be modified until the descriptor is returned by spi_device_get_trans_result.
  • ticks_to_wait: Ticks to wait until there’s a returned item; use portMAX_DELAY to never time out.

esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc)

Do a SPI transaction.

Essentially does the same as spi_device_queue_trans followed by spi_device_get_trans_result. Do not use this when there is still a transaction queued that hasn’t been finalized using spi_device_get_trans_result.

Return
  • ESP_ERR_INVALID_ARG if parameter is invalid
  • ESP_OK on success
Parameters
  • handle: Device handle obtained using spi_host_add_dev
  • trans_desc: Description of transaction to execute

Structures

struct spi_device_interface_config_t

This is a configuration for a SPI slave device that is connected to one of the SPI buses.

Public Members

uint8_t command_bits

Default amount of bits in command phase (0-16), used when SPI_TRANS_VARIABLE_CMD is not used, otherwise ignored.

uint8_t address_bits

Default amount of bits in address phase (0-64), used when SPI_TRANS_VARIABLE_ADDR is not used, otherwise ignored.

uint8_t dummy_bits

Amount of dummy bits to insert between address and data phase.

uint8_t mode

SPI mode (0-3)

uint8_t duty_cycle_pos

Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128.

uint8_t cs_ena_pretrans

Amount of SPI bit-cycles the cs should be activated before the transmission (0-16). This only works on half-duplex transactions.

uint8_t cs_ena_posttrans

Amount of SPI bit-cycles the cs should stay active after the transmission (0-16)

int clock_speed_hz

Clock speed, in Hz.

int spics_io_num

CS GPIO pin for this device, or -1 if not used.

uint32_t flags

Bitwise OR of SPI_DEVICE_* flags.

int queue_size

Transaction queue size. This sets how many transactions can be ‘in the air’ (queued using spi_device_queue_trans but not yet finished using spi_device_get_trans_result) at the same time.

transaction_cb_t pre_cb

Callback to be called before a transmission is started. This callback is called within interrupt context.

transaction_cb_t post_cb

Callback to be called after a transmission has completed. This callback is called within interrupt context.

struct spi_transaction_t

This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes.

Public Members

uint32_t flags

Bitwise OR of SPI_TRANS_* flags.

uint16_t cmd

Command data, of which the length is set in the command_bits of spi_device_interface_config_t. NOTE: this field, used to be “command” in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF 3.0.

  • Example: write 0x0123 and command_bits=12 to send command 0x12, 0x3_ (in previous version, you may have to write 0x3_12).

uint64_t addr

Address data, of which the length is set in the address_bits of spi_device_interface_config_t. NOTE: this field, used to be “address” in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF3.0.

  • Example: write 0x123400 and address_bits=24 to send address of 0x12, 0x34, 0x00 (in previous version, you may have to write 0x12340000).

size_t length

Total data length, in bits.

size_t rxlength

Total data length received, should be not greater than length in full-duplex mode (0 defaults this to the value of length).

void *user

User-defined variable. Can be used to store eg transaction ID.

const void *tx_buffer

Pointer to transmit buffer, or NULL for no MOSI phase.

uint8_t tx_data[4]

If SPI_USE_TXDATA is set, data set here is sent directly from this variable.

void *rx_buffer

Pointer to receive buffer, or NULL for no MISO phase. Written by 4 bytes-unit if DMA is used.

uint8_t rx_data[4]

If SPI_USE_RXDATA is set, data is received directly to this variable.

struct spi_transaction_ext_t

This struct is for SPI transactions which may change their address and command length. Please do set the flags in base to SPI_TRANS_VARIABLE_CMD_ADR to use the bit length here.

Public Members

struct spi_transaction_t base

Transaction data, so that pointer to spi_transaction_t can be converted into spi_transaction_ext_t.

uint8_t command_bits

The command length in this transaction, in bits.

uint8_t address_bits

The address length in this transaction, in bits.

Macros

SPI_DEVICE_TXBIT_LSBFIRST

Transmit command/address/data LSB first instead of the default MSB first.

SPI_DEVICE_RXBIT_LSBFIRST

Receive data LSB first instead of the default MSB first.

SPI_DEVICE_BIT_LSBFIRST

Transmit and receive LSB first.

SPI_DEVICE_3WIRE

Use MOSI (=spid) for both sending and receiving data.

SPI_DEVICE_POSITIVE_CS

Make CS positive during a transaction instead of negative.

SPI_DEVICE_HALFDUPLEX

Transmit data before receiving it, instead of simultaneously.

SPI_DEVICE_CLK_AS_CS

Output clock on CS line if CS is active.

SPI_TRANS_MODE_DIO

Transmit/receive data in 2-bit mode.

SPI_TRANS_MODE_QIO

Transmit/receive data in 4-bit mode.

SPI_TRANS_MODE_DIOQIO_ADDR

Also transmit address in mode selected by SPI_MODE_DIO/SPI_MODE_QIO.

SPI_TRANS_USE_RXDATA

Receive into rx_data member of spi_transaction_t instead into memory at rx_buffer.

SPI_TRANS_USE_TXDATA

Transmit tx_data member of spi_transaction_t instead of data at tx_buffer. Do not set tx_buffer when using this.

SPI_TRANS_VARIABLE_CMD

Use the command_bits in spi_transaction_ext_t rather than default value in spi_device_interface_config_t.

SPI_TRANS_VARIABLE_ADDR

Use the address_bits in spi_transaction_ext_t rather than default value in spi_device_interface_config_t.

Type Definitions

typedef struct spi_transaction_t spi_transaction_t
typedef void (*transaction_cb_t)(spi_transaction_t *trans)
typedef struct spi_device_t *spi_device_handle_t

Handle for a device on a SPI bus.