SDIO Card Slave Driver¶
Overview¶
The ESP32 SDIO Card peripherals (Host, Slave) shares two sets of pins as below table. The first set is usually occupied by SPI0 bus which is responsible for the SPI flash holding the code to run. This means SDIO slave driver can only runs on the second set of pins while SDIO host is not using it.
The SDIO slave can run under 3 modes: SPI, 1-bit SD and 4-bit SD modes, which is detected automatically by the hardware. According to the SDIO specification, CMD and DAT0-3 lines should be pulled up no matter in 1-bit, 4-bit or SPI mode.
Connections¶
Pin Name |
Corresponding pins in SPI mode |
Slot1 |
Slot2 |
---|---|---|---|
GPIO Number |
|||
CLK |
SCLK |
6 |
14 |
CMD |
MOSI |
11 |
15 |
DAT0 |
MISO |
7 |
2 |
DAT1 |
Interrupt |
8 |
4 |
DAT2 |
N.C. (pullup) |
9 |
12 |
DAT3 |
#CS |
10 |
13 |
1-bit SD mode: Connect CLK, CMD, DAT0, DAT1 pins and the ground.
4-bit SD mode: Connect all pins and the ground.
SPI mode: Connect SCLK, MOSI, MISO, Interrupt, #CS pins and the ground.
Note
Please check if CMD and DATA lines D0-D3 of the card are properly pulled up by 10 KOhm resistors. This should be ensured even in 1-bit mode or SPI mode. Most official modules don’t offer these pullups internally. If you are using official development boards, check Overview of Compatibility to see whether your development boards have such pullups.
Note
Most official modules have conflicts on strapping pins with the SDIO slave function. If you are using a ESP32 module with 3.3 V flash inside, you have to burn the EFUSE when you are developing on the module for the first time. See Overview of Compatibility to see how to make your modules compatible with the SDIO.
Here is a list for modules/kits with 3.3 V flash:
Modules: ESP32-PICO-D4, ESP32-WROOM-32 series (including ESP32-SOLO-1), ESP32-WROVER-B and ESP32-WROVER-IB
Kits: ESP32-PICO-KIT, ESP32-DevKitC (till v4), ESP32-WROVER-KIT (v4.1 (also known as ESP32-WROVER-KIT-VB), v2, v1 (also known as DevKitJ v1))
You can tell the version of your ESP23-WROVER-KIT version from the module on it: v4.1 are with ESP32-WROVER-B modules, v3 are with ESP32-WROVER modules, while v2 and v1 are with ESP32-WROOM-32 modules.
Refer to SD Pull-up Requirements for more technical details of the pullups.
The host initialize the slave into SD mode by first sending CMD0 with DAT3 pin high, or in SPI mode by sending CMD0 with CS pin (the same pin as DAT3) low.
After the initialization, the host can enable the 4-bit SD mode by writing CCCR register 0x07 by CMD52. All the bus detection process are handled by the slave peripheral.
The host has to communicate with the slave by an ESP-slave-specific protocol. The slave driver offers 3 services over Function 1 access by CMD52 and CMD53: (1) a sending FIFO and a receiving FIFO, (2) 52 8-bit R/W registers shared by host and slave, (3) 16 interrupt sources (8 from host to slave, and 8 from slave to host).
Terminology¶
The SDIO slave driver uses the following terms:
Transfer: a transfer is always started by a command token from the host, and may contain a reply and several data blocks. ESP32 slave software is based on transfers.
Sending: slave to host transfers.
Receiving: host to slave transfers.
Note
Register names in ESP32 Technical Reference Manual > SDIO Slave Controller [PDF] are oriented from the point of view of the host, i.e. ‘rx’ registers refer to sending, while ‘tx’ registers refer to receiving. We’re not using tx or rx in the driver to avoid ambiguities.
FIFO: specific address in Function 1 that can be access by CMD53 to read/write large amount of data. The address is related to the length requested to read from/write to the slave in a single transfer: requested length = 0x1F800-address.
Ownership: When the driver takes ownership of a buffer, it means the driver can randomly read/write the buffer (usually via DMA). The application should not read/write the buffer until the ownership is returned to the application. If the application reads from a buffer owned by a receiving driver, the data read can be random; if the application writes to a buffer owned by a sending driver, the data sent may be corrupted.
Requested length: The length requested in one transfer determined by the FIFO address.
Transfer length: The length requested in one transfer determined by the CMD53 byte/block count field.
Note
Requested length is different from the transfer length. ESP32 slave DMA base on the requested length rather than the transfer length. The transfer length should be no shorter than the requested length, and the rest part will be filled with 0 (sending) or discard (receiving).
Receiving buffer size: The buffer size is pre-defined between the host and the slave before communication starts. Slave application has to set the buffer size during initialization by the
recv_buffer_size
member ofsdio_slave_config_t
.Interrupts: the esp32 slave support interrupts in two directions: from host to slave (called slave interrupts below) and from slave to host (called host interrupts below). See more in Interrupts.
Registers: specific address in Function 1 access by CMD52 or CMD53.
Communication with ESP SDIO Slave¶
The host should initialize the ESP32 SDIO slave according to the standard SDIO initialization process (Sector 3.1.2 of SDIO Simplified Specification), which is described briefly in ESP SDIO Slave Initialization.
Furthermore, there’s an ESP32-specific upper-level communication protocol upon the CMD52/CMD53 to Func 1. Please refer to ESP SDIO Slave Protocol. There is also a component ESP Serial Slave Link for ESP32 master to communicate with ESP32 SDIO slave, see example peripherals/sdio when programming your host.
Interrupts¶
There are interrupts from host to slave, and from slave to host to help communicating conveniently.
Slave Interrupts¶
The host can interrupt the slave by writing any one bit in the register 0x08D. Once any bit of the register is
set, an interrupt is raised and the SDIO slave driver calls the callback function defined in the slave_intr_cb
member
in the sdio_slave_config_t
structure.
Note
The callback function is called in the ISR, do not use any delay, loop or spinlock in the callback.
There’s another set of functions can be used. You can call sdio_slave_wait_int
to wait for an interrupt within a
certain time, or call sdio_slave_clear_int
to clear interrupts from host. The callback function can work with the
wait functions perfectly.
Host Interrupts¶
The slave can interrupt the host by an interrupt line (at certain time) which is level sensitive. When the host see the interrupt line pulled down, it may read the slave interrupt status register, to see the interrupt source. Host can clear interrupt bits, or choose to disable a interrupt source. The interrupt line will hold active until all the sources are cleared or disabled.
There are several dedicated interrupt sources as well as general purpose sources. see sdio_slave_hostint_t
for
more information.
Receiving FIFO¶
When the host is going to send the slave some packets, it has to check whether the slave is ready to receive by reading the buffer number of slave.
To allow the host sending data to the slave, the application has to load buffers to the slave driver by the following steps:
Register the buffer by calling
sdio_slave_recv_register_buf
, and get the handle of the registered buffer. The driver will allocate memory for the linked-list descriptor needed to link the buffer onto the hardware.Load buffers onto the driver by passing the buffer handle to
sdio_slave_recv_load_buf
.Call
sdio_slave_recv
to get the received data. If non-blocking call is needed, setwait=0
.Pass the handle of processed buffer back to the driver by
sdio_recv_load_buf
again.
Note
To avoid overhead from copying data, the driver itself doesn’t have any buffer inside, the application is responsible to offer new buffers in time. The DMA will automatically store received data to the buffer.
Sending FIFO¶
Each time the slave has data to send, it raises an interrupt and the host will request for the packet length. There are two sending modes:
Stream Mode: when a buffer is loaded to the driver, the buffer length will be counted into the packet length requested by host in the incoming communications. Regardless previous packets are sent or not. This means the host can get data of several buffers in one transfer.
Packet Mode: the packet length is updated packet by packet, and only when previous packet is sent. This means that the host can only get data of one buffer in one transfer.
Note
To avoid overhead from copying data, the driver itself doesn’t have any buffer inside. Namely, the DMA takes data directly from the buffer provided by the application. The application should not touch the buffer until the sending is finished.
The sending mode can be set in the sending_mode
member of sdio_slave_config_t
, and the buffer numbers can be
set in the send_queue_size
. All the buffers are restricted to be no larger than 4092 bytes. Though in the stream
mode several buffers can be sent in one transfer, each buffer is still counted as one in the queue.
The application can call sdio_slave_transmit
to send packets. In this case the function returns when the transfer
is successfully done, so the queue is not fully used. When higher effeciency is required, the application can use the
following functions instead:
Pass buffer information (address, length, as well as an
arg
indicating the buffer) tosdio_slave_send_queue
. If non-blocking call is needed, setwait=0
. If thewait
is notportMAX_DELAY
(wait until success), application has to check the result to know whether the data is put in to the queue or discard.Call
sdio_slave_send_get_finished
to get and deal with a finished transfer. A buffer should be keep unmodified until returned fromsdio_slave_send_get_finished
. This means the buffer is actually sent to the host, rather than just staying in the queue.
There are several ways to use the arg
in the queue parameter:
Directly point
arg
to a dynamic-allocated buffer, and use thearg
to free it when transfer finished.Wrap transfer informations in a transfer structure, and point
arg
to the structure. You can use the structure to do more things like:typedef struct { uint8_t* buffer; size_t size; int id; }sdio_transfer_t; //and send as: sdio_transfer_t trans = { .buffer = ADDRESS_TO_SEND, .size = 8, .id = 3, //the 3rd transfer so far }; sdio_slave_send_queue(trans.buffer, trans.size, &trans, portMAX_DELAY); //... maybe more transfers are sent here //and deal with finished transfer as: sdio_transfer_t* arg = NULL; sdio_slave_send_get_finished((void**)&arg, portMAX_DELAY); ESP_LOGI("tag", "(%d) successfully send %d bytes of %p", arg->id, arg->size, arg->buffer); some_post_callback(arg); //do more thingsWorking with the receiving part of this driver, point
arg
to the receive buffer handle of this buffer. So that we can directly use the buffer to receive data when it’s sent:uint8_t buffer[256]={1,2,3,4,5,6,7,8}; sdio_slave_buf_handle_t handle = sdio_slave_recv_register_buf(buffer); sdio_slave_send_queue(buffer, 8, handle, portMAX_DELAY); //... maybe more transfers are sent here //and load finished buffer to receive as sdio_slave_buf_handle_t handle = NULL; sdio_slave_send_get_finished((void**)&handle, portMAX_DELAY); sdio_slave_recv_load_buf(handle);More about this, see peripherals/sdio.
Application Example¶
Slave/master communication: peripherals/sdio.
API Reference¶
Header File¶
Enumerations¶
-
enum
sdio_slave_hostint_t
¶ Mask of interrupts sending to the host.
Values:
-
SDIO_SLAVE_HOSTINT_BIT0
= BIT(0)¶ General purpose interrupt bit 0.
-
SDIO_SLAVE_HOSTINT_BIT1
= BIT(1)¶
-
SDIO_SLAVE_HOSTINT_BIT2
= BIT(2)¶
-
SDIO_SLAVE_HOSTINT_BIT3
= BIT(3)¶
-
SDIO_SLAVE_HOSTINT_BIT4
= BIT(4)¶
-
SDIO_SLAVE_HOSTINT_BIT5
= BIT(5)¶
-
SDIO_SLAVE_HOSTINT_BIT6
= BIT(6)¶
-
SDIO_SLAVE_HOSTINT_BIT7
= BIT(7)¶
-
SDIO_SLAVE_HOSTINT_SEND_NEW_PACKET
= BIT(23)¶ New packet available.
-
-
enum
sdio_slave_timing_t
¶ Timing of SDIO slave.
Values:
-
SDIO_SLAVE_TIMING_PSEND_PSAMPLE
= 0¶ Send at posedge, and sample at posedge. Default value for HS mode. Normally there’s no problem using this to work in DS mode.
-
SDIO_SLAVE_TIMING_NSEND_PSAMPLE
¶ Send at negedge, and sample at posedge. Default value for DS mode and below.
-
SDIO_SLAVE_TIMING_PSEND_NSAMPLE
¶ Send at posedge, and sample at negedge.
-
SDIO_SLAVE_TIMING_NSEND_NSAMPLE
¶ Send at negedge, and sample at negedge.
-
-
enum
sdio_slave_sending_mode_t
¶ Configuration of SDIO slave mode.
Values:
-
SDIO_SLAVE_SEND_STREAM
= 0¶ Stream mode, all packets to send will be combined as one if possible.
-
SDIO_SLAVE_SEND_PACKET
= 1¶ Packet mode, one packets will be sent one after another (only increase packet_len if last packet sent).
-
Header File¶
Functions¶
-
esp_err_t
sdio_slave_initialize
(sdio_slave_config_t *config)¶ Initialize the sdio slave driver
- Return
ESP_ERR_NOT_FOUND if no free interrupt found.
ESP_ERR_INVALID_STATE if already initialized.
ESP_ERR_NO_MEM if fail due to memory allocation failed.
ESP_OK if success
- Parameters
config
: Configuration of the sdio slave driver.
-
void
sdio_slave_deinit
(void)¶ De-initialize the sdio slave driver to release the resources.
-
esp_err_t
sdio_slave_start
(void)¶ Start hardware for sending and receiving, as well as set the IOREADY1 to 1.
- Note
The driver will continue sending from previous data and PKT_LEN counting, keep data received as well as start receiving from current TOKEN1 counting. See
sdio_slave_reset
.- Return
ESP_ERR_INVALID_STATE if already started.
ESP_OK otherwise.
-
void
sdio_slave_stop
(void)¶ Stop hardware from sending and receiving, also set IOREADY1 to 0.
- Note
this will not clear the data already in the driver, and also not reset the PKT_LEN and TOKEN1 counting. Call
sdio_slave_reset
to do that.
-
esp_err_t
sdio_slave_reset
(void)¶ Clear the data still in the driver, as well as reset the PKT_LEN and TOKEN1 counting.
- Return
always return ESP_OK.
-
sdio_slave_buf_handle_t
sdio_slave_recv_register_buf
(uint8_t *start)¶ Register buffer used for receiving. All buffers should be registered before used, and then can be used (again) in the driver by the handle returned.
- Note
The driver will use and only use the amount of space specified in the
recv_buffer_size
member set in thesdio_slave_config_t
. All buffers should be larger than that. The buffer is used by the DMA, so it should be DMA capable and 32-bit aligned.- Return
The buffer handle if success, otherwise NULL.
- Parameters
start
: The start address of the buffer.
-
esp_err_t
sdio_slave_recv_unregister_buf
(sdio_slave_buf_handle_t handle)¶ Unregister buffer from driver, and free the space used by the descriptor pointing to the buffer.
- Return
ESP_OK if success, ESP_ERR_INVALID_ARG if the handle is NULL or the buffer is being used.
- Parameters
handle
: Handle to the buffer to release.
-
esp_err_t
sdio_slave_recv_load_buf
(sdio_slave_buf_handle_t handle)¶ Load buffer to the queue waiting to receive data. The driver takes ownership of the buffer until the buffer is returned by
sdio_slave_send_get_finished
after the transaction is finished.- Return
ESP_ERR_INVALID_ARG if invalid handle or the buffer is already in the queue. Only after the buffer is returened by
sdio_slave_recv
can you load it again.ESP_OK if success
- Parameters
handle
: Handle to the buffer ready to receive data.
-
esp_err_t
sdio_slave_recv
(sdio_slave_buf_handle_t *handle_ret, uint8_t **out_addr, size_t *out_len, TickType_t wait)¶ Get received data if exist. The driver returns the ownership of the buffer to the app.
- Note
Call
sdio_slave_load_buf
with the handle to re-load the buffer onto the link list, and receive with the same buffer again. The address and length of the buffer got here is the same as got fromsdio_slave_get_buffer
.- Return
ESP_ERR_INVALID_ARG if handle_ret is NULL
ESP_ERR_TIMEOUT if timeout before receiving new data
ESP_OK if success
- Parameters
handle_ret
: Handle to the buffer holding received data. Use this handle insdio_slave_recv_load_buf
to receive in the same buffer again.[out] out_addr
: Output of the start address, set to NULL if not needed.[out] out_len
: Actual length of the data in the buffer, set to NULL if not needed.wait
: Time to wait before data received.
-
uint8_t *
sdio_slave_recv_get_buf
(sdio_slave_buf_handle_t handle, size_t *len_o)¶ Retrieve the buffer corresponding to a handle.
- Return
buffer address if success, otherwise NULL.
- Parameters
handle
: Handle to get the buffer.len_o
: Output of buffer length
-
esp_err_t
sdio_slave_send_queue
(uint8_t *addr, size_t len, void *arg, TickType_t wait)¶ Put a new sending transfer into the send queue. The driver takes ownership of the buffer until the buffer is returned by
sdio_slave_send_get_finished
after the transaction is finished.- Return
ESP_ERR_INVALID_ARG if the length is not greater than 0.
ESP_ERR_TIMEOUT if the queue is still full until timeout.
ESP_OK if success.
- Parameters
addr
: Address for data to be sent. The buffer should be DMA capable and 32-bit aligned.len
: Length of the data, should not be longer than 4092 bytes (may support longer in the future).arg
: Argument to returned insdio_slave_send_get_finished
. The argument can be used to indicate which transaction is done, or as a parameter for a callback. Set to NULL if not needed.wait
: Time to wait if the buffer is full.
-
esp_err_t
sdio_slave_send_get_finished
(void **out_arg, TickType_t wait)¶ Return the ownership of a finished transaction.
- Return
ESP_ERR_TIMEOUT if no transaction finished, or ESP_OK if succeed.
- Parameters
out_arg
: Argument of the finished transaction. Set to NULL if unused.wait
: Time to wait if there’s no finished sending transaction.
-
esp_err_t
sdio_slave_transmit
(uint8_t *addr, size_t len)¶ Start a new sending transfer, and wait for it (blocked) to be finished.
- Return
ESP_ERR_INVALID_ARG if the length of descriptor is not greater than 0.
ESP_ERR_TIMEOUT if the queue is full or host do not start a transfer before timeout.
ESP_OK if success.
- Parameters
addr
: Start address of the buffer to sendlen
: Length of buffer to send.
-
uint8_t
sdio_slave_read_reg
(int pos)¶ Read the spi slave register shared with host.
- Note
register 28 to 31 are reserved for interrupt vector.
- Return
value of the register.
- Parameters
pos
: register address, 0-27 or 32-63.
-
esp_err_t
sdio_slave_write_reg
(int pos, uint8_t reg)¶ Write the spi slave register shared with host.
- Note
register 29 and 31 are used for interrupt vector.
- Return
ESP_ERR_INVALID_ARG if address wrong, otherwise ESP_OK.
- Parameters
pos
: register address, 0-11, 14-15, 18-19, 24-27 and 32-63, other address are reserved.reg
: the value to write.
Get the interrupt enable for host.
- Return
the interrupt mask.
-
void
sdio_slave_set_host_intena
(sdio_slave_hostint_t mask)¶ Set the interrupt enable for host.
- Parameters
mask
: Enable mask for host interrupt.
-
esp_err_t
sdio_slave_send_host_int
(uint8_t pos)¶ Interrupt the host by general purpose interrupt.
- Return
ESP_ERR_INVALID_ARG if interrupt num error
ESP_OK otherwise
- Parameters
pos
: Interrupt num, 0-7.
-
void
sdio_slave_clear_host_int
(sdio_slave_hostint_t mask)¶ Clear general purpose interrupt to host.
- Parameters
mask
: Interrupt bits to clear, by bit mask.
-
esp_err_t
sdio_slave_wait_int
(int pos, TickType_t wait)¶ Wait for general purpose interrupt from host.
- Note
this clears the interrupt at the same time.
- Return
ESP_OK if success, ESP_ERR_TIMEOUT if timeout.
- Parameters
pos
: Interrupt source number to wait for. is set.wait
: Time to wait before interrupt triggered.
Structures¶
-
struct
sdio_slave_config_t
¶ Configuration of SDIO slave.
Public Members
-
sdio_slave_timing_t
timing
¶ timing of sdio_slave. see
sdio_slave_timing_t
.
-
sdio_slave_sending_mode_t
sending_mode
¶ mode of sdio_slave.
SDIO_SLAVE_MODE_STREAM
if the data needs to be sent as much as possible;SDIO_SLAVE_MODE_PACKET
if the data should be sent in packets.
-
int
send_queue_size
¶ max buffers that can be queued before sending.
-
size_t
recv_buffer_size
¶ If buffer_size is too small, it costs more CPU time to handle larger number of buffers. If buffer_size is too large, the space larger than the transaction length is left blank but still counts a buffer, and the buffers are easily run out. Should be set according to length of data really transferred. All data that do not fully fill a buffer is still counted as one buffer. E.g. 10 bytes data costs 2 buffers if the size is 8 bytes per buffer. Buffer size of the slave pre-defined between host and slave before communication. All receive buffer given to the driver should be larger than this.
-
sdio_event_cb_t
event_cb
¶ when the host interrupts slave, this callback will be called with interrupt number (0-7).
-
uint32_t
flags
¶ Features to be enabled for the slave, combinations of
SDIO_SLAVE_FLAG_*
.
-
sdio_slave_timing_t
Macros¶
-
SDIO_SLAVE_RECV_MAX_BUFFER
¶
-
SDIO_SLAVE_FLAG_DAT2_DISABLED
¶ It is required by the SD specification that all 4 data lines should be used and pulled up even in 1-bit mode or SPI mode. However, as a feature, the user can specify this flag to make use of DAT2 pin in 1-bit mode. Note that the host cannot read CCCR registers to know we don’t support 4-bit mode anymore, please do this at your own risk.
-
SDIO_SLAVE_FLAG_HOST_INTR_DISABLED
¶ The DAT1 line is used as the interrupt line in SDIO protocol. However, as a feature, the user can specify this flag to make use of DAT1 pin of the slave in 1-bit mode. Note that the host has to do polling to the interrupt registers to know whether there are interrupts from the slave. And it cannot read CCCR registers to know we don’t support 4-bit mode anymore, please do this at your own risk.
-
SDIO_SLAVE_FLAG_INTERNAL_PULLUP
¶ Enable internal pullups for enabled pins. It is required by the SD specification that all the 4 data lines should be pulled up even in 1-bit mode or SPI mode. Note that the internal pull-ups are not sufficient for stable communication, please do connect external pull-ups on the bus. This is only for example and debug use.
Type Definitions¶
-
typedef void (*
sdio_event_cb_t
)(uint8_t event)¶
-
typedef void *
sdio_slave_buf_handle_t
¶ Handle of a receive buffer, register a handle by calling
sdio_slave_recv_register_buf
. Use the handle to load the buffer to the driver, or callsdio_slave_recv_unregister_buf
if it is no longer used.