SPI Slave Half Duplex¶
Introduction¶
The half duplex (HD) mode is a special mode provided by ESP SPI Slave peripheral. Under this mode, the hardware provides more services than the full duplex (FD) mode (the mode for general purpose SPI transactions, see SPI Slave Driver). These services reduce the CPU load and the response time of SPI Slave, but the communication format is determined by the hardware. The communication format is always half duplex, so comes the name of Half Duplex Mode.
There are several different types of transactions, determined by the command phase of the transaction. Each transaction may consist of the following phases: command, address, dummy, data. The command phase is mandatory, while the other fields may be determined by the command field. During the command, address, dummy phases, the bus is always controlled by the master, while the direction of the data phase depends on the command. The data phase can be either an in phase, for the master to write data to the slave; or an out phase, for the master to read data from the slave.
About the details of how master should communicate with the SPI Slave, see ESP SPI Slave HD (Half Duplex) Mode Protocol.
By these different transactions, the slave provide these services to the master:
A DMA channel for the master to write a great amount of data to the slave.
A DMA channel for the master to read a great amount of data from the slave.
Several general purpose registers, shard between the master and the slave.
Several general purpose interrupts, for the master to interrupt the SW of slave.
Terminology¶
Transaction
Channel
Sending
Receiving
Data Descriptor
Driver Feature¶
Transaction read/write by master in segments
Queues for data to send and received
Driver usage¶
Slave initialization¶
Call spi_slave_hd_init()
to initialize the SPI bus as well as the peripheral and the driver. The SPI slave will exclusively use the SPI peripheral, pins of the bus before it’s deinitialized. Most configurations of the slave should be done as soon as the slave is being initialized.
The spi_bus_config_t
specifies how the bus should be initialized, while spi_slave_hd_slot_config_t
specifies how the SPI Slave driver should work.
Deinitialization (optional)¶
Call spi_slave_hd_deinit()
to uninstall the driver. The resources, including the pins, SPI peripheral, internal memory used by the driver, interrupt sources, will be released by the deinit function.
Send/Receive Data by DMA Channels¶
To send data to the master through the sending DMA channel, the application should properly wrap the data to send by a spi_slave_hd_data_t
descriptor structure before calling spi_slave_hd_queue_trans()
with the data descriptor, and the channel argument of SPI_SLAVE_CHAN_TX
. The pointers to descriptors are stored in the queue, and the data will be send to the master upon master’s RDDMA command in the same order they are put into the queue by spi_slave_hd_queue_trans()
.
The application should check the result of data sending by calling spi_slave_hd_get_trans_res()
with the channel set as SPI_SLAVE_CHAN_TX
. This function will block until the transaction with command RDDMA from master successfully completes (or timeout). The out_trans
argument of the function will output the pointer of the data descriptor which is just finished.
Receiving data from the master through the receiving DMA channel is quite similar. The application calls spi_slave_hd_queue_trans()
with proper data descriptor and the channel argument of SPI_SLAVE_CHAN_RX
. And the application calls the spi_slave_hd_get_trans_res()
later to get the descriptor to the receiving buffer, before it handles the data in the receiving buffer.
Note
This driver itself doesn’t have internal buffer for the data to send, or just received. The application should provide data descriptors for the data buffer to send to master, or to receive data from the master.
The application will have to properly keep the data descriptor as well as the buffer it points to, after the descriptor is successfully sent into the driver internal queue by spi_slave_hd_queue_trans()
, and before returned by spi_slave_hd_get_trans_res()
. During this period, the hardware as well as the driver may read or write to the buffer and the descriptor when required at any time.
Please note that the buffer doesn’t have to be fully sent or filled before it’s terminated. For example, in the segment transaction mode, the master has to send CMD7 to terminate a WRDMA transaction, or send CMD8 to terminate a RDDMA transaction (in segments), no matter the send (receive) buffer is used up (full) or not.
Using Data Arguments¶
Sometimes you may have initiator (sending data descriptor) and closure (handling returned descriptors) functions in different places. When you get the returned data descriptor in the closure, you may need some extra information when handle the finished data descriptor. For example, you may want to know which round it is for the returned descriptor, when you send the same piece of data for several times.
Set the arg
member in the data descriptor to an variable indicating the transaction (by force casting), or point it to a a structure which wraps all the information you may need when handling the sending/receiving data. Then you can get what you need in your closure.
Using callbacks¶
Note
These callbacks are called in the ISR, so that they are fast enough. However, you may need to be very careful to write the code in the ISR. The callback should return as soon as possible. No delay or blocking operations are allowed.
The spi_slave_hd_intr_config_t
member in the spi_slave_hd_slot_config_t
configuration structure passed when initialize the SPI Slave HD driver, allows you having callbacks for each events you may concern.
The corresponding interrupt for each callbacks that is not NULL will enabled, so that the callbacks can be called immediately when the events happen. You don’t need to provide callbacks for the unconcerned events.
The arg
member in the configuration structure can help you pass some context to the callback, or indicate which SPI Slave instance when you are using the same callbacks for several SPI Slave peripherals. Set the arg
member to an variable indicating the SPI Slave instance (by force casting), or point it to a context structure. All the callbacks will be called with this arg
argument you set when the callbacks are initialized.
There are two other arguments: the event
and the awoken
. The event
passes the information of the current event to the callback. The spi_slave_hd_event_t
type contains the information of the event, for example, event type, the data descriptor just finished (The data argument will be very useful in this case!). The awoken
argument is an output one, telling the ISR there are tasks are awoken after this callback, and the ISR should call portYIELD_FROM_ISR() to do task scheduling. Just pass the awoken
argument to all FreeRTOS APIs which may unblock tasks, and the awoken will be returned to the ISR.
Receiving General Purpose Interrupts From the Master¶
When the master sends CMD 0x08, 0x09 or 0x0A, the slave corresponding will be triggered. Currently the CMD8 is permanently used to indicate the termination of RDDMA segments. To receiving general purpose interrupts, register callbacks for CMD 0x09 and 0x0A when the slave is initialized, see Using callbacks.
Application Example¶
The code example for Device/Host communication can be found in the peripherals/spi_slave_hd directory of ESP-IDF examples.
API reference¶
Header File¶
Functions¶
-
esp_err_t
spi_slave_hd_init
(spi_host_device_t host_id, const spi_bus_config_t *bus_config, const spi_slave_hd_slot_config_t *config)¶ Initialize the SPI Slave HD driver.
- Return
ESP_OK: on success
ESP_ERR_INVALID_ARG: invalid argument given
ESP_ERR_INVALID_STATE: function called in invalid state, may be some resources are already in use
ESP_ERR_NOT_FOUND if there is no available DMA channel
ESP_ERR_NO_MEM: memory allocation failed
or other return value from
esp_intr_alloc
- Parameters
host_id
: The host to usebus_config
: Bus configuration for the bus usedconfig
: Configuration for the SPI Slave HD driver
-
esp_err_t
spi_slave_hd_deinit
(spi_host_device_t host_id)¶ Deinitialize the SPI Slave HD driver.
- Return
ESP_OK: on success
ESP_ERR_INVALID_ARG: if the host_id is not correct
- Parameters
host_id
: The host to deinitialize the driver
-
esp_err_t
spi_slave_hd_queue_trans
(spi_host_device_t host_id, spi_slave_chan_t chan, spi_slave_hd_data_t *trans, TickType_t timeout)¶ Queue transactions (segment mode)
- Return
ESP_OK: on success
ESP_ERR_INVALID_ARG: The input argument is invalid. Can be the following reason:
The buffer given is not DMA capable
The length of data is invalid (not larger than 0, or exceed the max transfer length)
The transaction direction is invalid
ESP_ERR_TIMEOUT: Cannot queue the data before timeout. Master is still processing previous transaction.
ESP_ERR_INVALID_STATE: Function called in invalid state. This API should be called under segment mode.
- Parameters
host_id
: Host to queue the transactionchan
: SPI_SLAVE_CHAN_TX or SPI_SLAVE_CHAN_RXtrans
: Transaction descriptorstimeout
: Timeout before the data is queued
-
esp_err_t
spi_slave_hd_get_trans_res
(spi_host_device_t host_id, spi_slave_chan_t chan, spi_slave_hd_data_t **out_trans, TickType_t timeout)¶ Get the result of a data transaction (segment mode)
- Note
This API should be called successfully the same times as the
spi_slave_hd_queue_trans
.- Return
ESP_OK: on success
ESP_ERR_INVALID_ARG: Function is not valid
ESP_ERR_TIMEOUT: There’s no transaction done before timeout
ESP_ERR_INVALID_STATE: Function called in invalid state. This API should be called under segment mode.
- Parameters
host_id
: Host to queue the transactionchan
: Channel to get the result, SPI_SLAVE_CHAN_TX or SPI_SLAVE_CHAN_RX[out] out_trans
: Pointer to the transaction descriptor (spi_slave_hd_data_t
) passed to the driver before. Hardware has finished this transaction. Membertrans_len
indicates the actual number of bytes of received data, it’s meaningless for TX.timeout
: Timeout before the result is got
-
void
spi_slave_hd_read_buffer
(spi_host_device_t host_id, int addr, uint8_t *out_data, size_t len)¶ Read the shared registers.
- Parameters
host_id
: Host to read the shared registersaddr
: Address of register to read, 0 toSOC_SPI_MAXIMUM_BUFFER_SIZE-1
[out] out_data
: Output buffer to store the read datalen
: Length to read, not larger thanSOC_SPI_MAXIMUM_BUFFER_SIZE-addr
-
void
spi_slave_hd_write_buffer
(spi_host_device_t host_id, int addr, uint8_t *data, size_t len)¶ Write the shared registers.
- Parameters
host_id
: Host to write the shared registersaddr
: Address of register to write, 0 toSOC_SPI_MAXIMUM_BUFFER_SIZE-1
data
: Buffer holding the data to writelen
: Length to write,SOC_SPI_MAXIMUM_BUFFER_SIZE-addr
-
esp_err_t
spi_slave_hd_append_trans
(spi_host_device_t host_id, spi_slave_chan_t chan, spi_slave_hd_data_t *trans, TickType_t timeout)¶ Load transactions (append mode)
- Note
In this mode, user transaction descriptors will be appended to the DMA and the DMA will keep processing the data without stopping
- Return
ESP_OK: on success
ESP_ERR_INVALID_ARG: The input argument is invalid. Can be the following reason:
The buffer given is not DMA capable
The length of data is invalid (not larger than 0, or exceed the max transfer length)
The transaction direction is invalid
ESP_ERR_TIMEOUT: Master is still processing previous transaction. There is no available transaction for slave to load
ESP_ERR_INVALID_STATE: Function called in invalid state. This API should be called under append mode.
- Parameters
host_id
: Host to load transactionschan
: SPI_SLAVE_CHAN_TX or SPI_SLAVE_CHAN_RXtrans
: Transaction descriptortimeout
: Timeout before the transaction is loaded
-
esp_err_t
spi_slave_hd_get_append_trans_res
(spi_host_device_t host_id, spi_slave_chan_t chan, spi_slave_hd_data_t **out_trans, TickType_t timeout)¶ Get the result of a data transaction (append mode)
- Note
This API should be called the same times as the
spi_slave_hd_append_trans
- Return
ESP_OK: on success
ESP_ERR_INVALID_ARG: Function is not valid
ESP_ERR_TIMEOUT: There’s no transaction done before timeout
ESP_ERR_INVALID_STATE: Function called in invalid state. This API should be called under append mode.
- Parameters
host_id
: Host to load the transactionchan
: SPI_SLAVE_CHAN_TX or SPI_SLAVE_CHAN_RX[out] out_trans
: Pointer to the transaction descriptor (spi_slave_hd_data_t
) passed to the driver before. Hardware has finished this transaction. Membertrans_len
indicates the actual number of bytes of received data, it’s meaningless for TX.timeout
: Timeout before the result is got
Structures¶
-
struct
spi_slave_hd_data_t
¶ Descriptor of data to send/receive.
Public Members
-
uint8_t *
data
¶ Buffer to send, must be DMA capable.
-
size_t
len
¶ Len of data to send/receive. For receiving the buffer length should be multiples of 4 bytes, otherwise the extra part will be truncated.
-
size_t
trans_len
¶ For RX direction, it indicates the data actually received. For TX direction, it is meaningless.
-
void *
arg
¶ Extra argument indiciating this data.
-
uint8_t *
-
struct
spi_slave_hd_event_t
¶ Information of SPI Slave HD event.
Public Members
-
spi_event_t
event
¶ Event type.
-
spi_slave_hd_data_t *
trans
¶ Corresponding transaction for SPI_EV_SEND and SPI_EV_RECV events.
-
spi_event_t
-
struct
spi_slave_hd_callback_config_t
¶ Callback configuration structure for SPI Slave HD.
Public Members
-
slave_cb_t
cb_buffer_tx
¶ Callback when master reads from shared buffer.
-
slave_cb_t
cb_buffer_rx
¶ Callback when master writes to shared buffer.
-
slave_cb_t
cb_send_dma_ready
¶ Callback when TX data buffer is loaded to the hardware (DMA)
-
slave_cb_t
cb_sent
¶ Callback when data are sent.
-
slave_cb_t
cb_recv_dma_ready
¶ Callback when RX data buffer is loaded to the hardware (DMA)
-
slave_cb_t
cb_recv
¶ Callback when data are received.
-
slave_cb_t
cb_cmd9
¶ Callback when CMD9 received.
-
slave_cb_t
cb_cmdA
¶ Callback when CMDA received.
-
void *
arg
¶ Argument indicating this SPI Slave HD peripheral instance.
-
slave_cb_t
-
struct
spi_slave_hd_slot_config_t
¶ Configuration structure for the SPI Slave HD driver.
Public Members
-
uint8_t
mode
¶ SPI mode, representing a pair of (CPOL, CPHA) configuration:
0: (0, 0)
1: (0, 1)
2: (1, 0)
3: (1, 1)
-
uint32_t
spics_io_num
¶ CS GPIO pin for this device.
-
uint32_t
flags
¶ Bitwise OR of SPI_SLAVE_HD_* flags.
-
uint32_t
command_bits
¶ command field bits, multiples of 8 and at least 8.
-
uint32_t
address_bits
¶ address field bits, multiples of 8 and at least 8.
-
uint32_t
dummy_bits
¶ dummy field bits, multiples of 8 and at least 8.
-
uint32_t
queue_size
¶ Transaction queue size. This sets how many transactions can be ‘in the air’ (queued using spi_slave_hd_queue_trans but not yet finished using spi_slave_hd_get_trans_result) at the same time.
-
spi_dma_chan_t
dma_chan
¶ DMA channel to used.
-
spi_slave_hd_callback_config_t
cb_config
¶ Callback configuration.
-
uint8_t
Macros¶
-
SPI_SLAVE_HD_TXBIT_LSBFIRST
¶ Transmit command/address/data LSB first instead of the default MSB first.
-
SPI_SLAVE_HD_RXBIT_LSBFIRST
¶ Receive data LSB first instead of the default MSB first.
-
SPI_SLAVE_HD_BIT_LSBFIRST
¶ Transmit and receive LSB first.
-
SPI_SLAVE_HD_APPEND_MODE
¶ Adopt DMA append mode for transactions. In this mode, users can load(append) DMA descriptors without stopping the DMA.
Type Definitions¶
-
typedef bool (*
slave_cb_t
)(void *arg, spi_slave_hd_event_t *event, BaseType_t *awoken)¶ Callback for SPI Slave HD.