example(sdio): example to use sdmmc host to do SDIO communication with SDIO slave

This commit is contained in:
Michael (XIAO Xufeng) 2018-05-29 16:33:24 +08:00 committed by bot
parent 8ab87a6a87
commit 90345050b2
18 changed files with 1631 additions and 1 deletions

View file

@ -701,6 +701,12 @@ example_test_002_01:
- ESP32
- Example_ShieldBox
example_test_003_01:
<<: *example_test_template
tags:
- ESP32
- Example_SDIO
UT_001_01:
<<: *unit_test_template
tags:

View file

@ -28,6 +28,9 @@
#if __has_include("esp_ping.h")
#include "esp_ping.h"
#endif
#if __has_include("esp_slave.h")
#include "esp_slave.h"
#endif
#if __has_include("esp_spi_flash.h")
#include "esp_spi_flash.h"
#endif
@ -92,6 +95,10 @@ static const esp_err_msg_t esp_err_msg_table[] = {
# endif
# ifdef ESP_ERR_INVALID_MAC
ERR_TBL_IT(ESP_ERR_INVALID_MAC), /* 267 0x10b MAC address was invalid */
# endif
// examples/peripherals/sdio/host/components/esp_slave/include/esp_slave.h
# ifdef ESP_ERR_NOT_FINISHED
ERR_TBL_IT(ESP_ERR_NOT_FINISHED), /* 513 0x201 */
# endif
// components/nvs_flash/include/nvs.h
# ifdef ESP_ERR_NVS_BASE

View file

@ -0,0 +1,115 @@
### SDIO Example
## Introduction
These two projects illustrate the SDIO driver (host and slave). The host
example shows how to initialize a SDIO card, respond to a slave interrupt, as
well as reading and writing registers and buffers. The slave is a dedicated
peripheral, providing 8 interrupts, 52 8-bit R/W registers, an input FIFO and
an output FIFO. The example shows how to configure the driver and use these
feature.
The host first tell the slave to write the registers to a specified value,
then reads and prints the value from the slave. Then tell the slave to send 8
interrupts to the host. Then the host start sending data to the slave FIFO
and then reads from the slave FIFO in loops.
## Wiring
The SDIO protocol requires at least 4 lines (one more line than SD memory
protocol): CMD, CLK, DAT0 and DAT1. DAT1 is mandatory for the interrupt. DAT2
is required if 4-bit mode is used. DAT3 is required in 4-bit mode (connected
to host), or required by the slave as mode detect in 1-bit mode (pull up). It
is okay in 1-bit mode to leave DAT3 of host disconnected.
Please run wires between the slave and master to make the example function
(pins are the same for the host and the slave):
======== =========
Signal GPIO NUM
======== =========
CLK GPIO14
CMD GPIO15
DAT0 GPIO2
DAT1 GPIO4
DAT2 GPIO12
DAT3 GPIO13
Ground GND
======== =========
CMD and DAT0-3 lines require to be pulled up by 50KOhm resistors even in
1-bit mode. See *Board Compability* below for details. In 1-bit mode, the
host can make use of DAT2 and DAT3, however the slave should leave them alone
but pulled up.
Be aware that the example uses lines normally reserved for JTAG. If you're
using a board with JTAG functions, please remember to remove jumpers
connecting to the JTAG adapter. The SD peripheral works at a high frequency
and uses native pins, there's no way to configure it to other pins through
the GPIO matrix.
Please make sure CMD and DATA lines are pulled up by 50KOhm resistors even in
1-bit mode or SPI mode, which is required by the SD specification.
The 4-bit mode can be configured in the menuconfig. If the 4-bit mode is not
used, the host will not control the DAT3 line, the slave hardware is
responsible to pull-up the line (or the slave may run into the SPI mode and
cause a crash).
The host uses HS mode by default. If the example does not work properly,
please try connecting two boards by short wires, grounding between two boards
better or disabling the HS mode in menuconfig.
## Board compatibility
1. If you're using a board (e.g. WroverKit v2 and before, PICO, DevKitC)
which is not able to drive GPIO2 low on downloading, please remember to
disconnect GPIO2 between two boards when downloading the application.
2. It is suggested to use the official Wrover Kit as the slave. This is
because Wrover Kits have pullups on CMD, DAT0 and DAT1. Otherwise you'll have
to connect the pullups manually (or use the Wrover Kit as the host). However,
due to a PCB issue, Wrover Kits v3 and earlier have pullup v.s. pulldown
conflicts on DAT3 line. You'll have to:
1. Pull up GPIO13 by resistor of 5KOhm or smaller (2KOhm suggested)
in 4-bit mode.
2. Pull up, or tie GPIO13 to VDD3.3 in 1-bit mode.
To help you faster evaluate the SDIO example on devkits without pullups,
you can uncomment the pullup enable flags in the initialization code of
the app_main of host or slave. This enables internal weak pullups on CMD,
DAT0 and DAT1 and DAT3 lines. However please don't rely on internal weak
pullups in your own design.
3. Moreover, if your slave devkit is using code flash of 3.3V, it is required
to pull down DAT2 line to set proper flash voltage. This conflicts with SDIO
pullup requirements. Currently devkits using PICO-D4 and Wroom-32 series
modules have this problem. You can either:
- Use Wrover Kit v3 which integrates a Wrover module
- Still use PICO-D4 or Wroom-32 Series modules as the slave, however:
- Don't connect the DAT2 pin and leave it floating. This means
you have to use 1-bit mode in the host. ``SDIO_DAT2_DISABLED``
option should be enabled in the menuconfig to avoid using of
DAT2. Or:
- Burn the EFUSE to force the module using 3.3V as the flash
voltage. In this way the voltage of flash doesn't depend on MTDI
any more, connect DAT2 to the host and make sure it is pulled up
correctly. See document below.
See docs in the programming guide ``api_reference/peripherals/sdio_slave``
and ``api_reference/peripherals/sd_pullup_requirements`` to see more
descriptions about pullups and MTDI requirements and solutions of official
modules and devkits.
## About esp_slave component in this example
The component in this example shows how to communicate with esp32 sdio slave
correctly. However, currently it is for example purpose only.
The example shows how to talk with the slave, but doesn't show how to handle
exceptions. Assertion fails if any of the preconditions (connections,
grounding, slave data preparation, etc.) is not met.
Please do check and handle the return value in your real product.

View file

@ -0,0 +1,19 @@
menu "Example Configuration"
choice EXAMPLE_SLAVE
prompt "Id of Slave used in Espressif master-slave board."
default EXAMPLE_SLAVE_NONE
help
If Espressif master-slave board is used, select which slave is used.
config EXAMPLE_SLAVE_NONE
bool "Not using Espressif master-slave board."
config EXAMPLE_SLAVE_B1
bool "Using slave B1"
config EXAMPLE_SLAVE_B2
bool "Using slave B2"
config EXAMPLE_SLAVE_B3
bool "Using slave B3"
endchoice
endmenu

View file

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := sdio_host
include $(IDF_PATH)/make/project.mk

View file

@ -0,0 +1,6 @@
#
# Component Makefile
#
COMPONENT_ADD_INCLUDEDIRS := include

View file

@ -0,0 +1,345 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_slave.h"
#include "esp_log.h"
#include "freertos/task.h"
#include "soc/host_reg.h"
static const char TAG[] = "esp_slave";
#define ESP_SLAVE_CMD53_END_ADDR 0x1f800
#define TX_BUFFER_MAX 0x1000
#define TX_BUFFER_MASK 0xFFF
#define RX_BYTE_MAX 0x100000
#define RX_BYTE_MASK 0xFFFFF
#define FUNC1_EN_MASK (BIT(1))
esp_err_t esp_slave_init_io(esp_slave_context_t *context)
{
esp_err_t err;
uint8_t ioe;
sdmmc_card_t* card = context->card;
err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_ENABLE, &ioe);
if (err != ESP_OK) return err;
ESP_LOGD(TAG, "IOE: 0x%02x", ioe);
uint8_t ior = 0;
err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior);
if (err != ESP_OK) return err;
ESP_LOGD(TAG, "IOR: 0x%02x", ior);
// enable function 1
ioe |= FUNC1_EN_MASK;
err = sdmmc_io_write_byte(card, 0, SD_IO_CCCR_FN_ENABLE, ioe, &ioe);
if (err != ESP_OK) return err;
ESP_LOGD(TAG, "IOE: 0x%02x", ioe);
// wait for the card to become ready
while ((ior & FUNC1_EN_MASK) == 0) {
err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior);
if (err != ESP_OK) return err;
ESP_LOGD(TAG, "IOR: 0x%02x", ior);
}
// get interrupt status
uint8_t ie;
err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_INT_ENABLE, &ie);
if (err != ESP_OK) return err;
ESP_LOGD(TAG,"IE: 0x%02x", ie);
// enable interrupts for function 1&2 and master enable
ie |= BIT(0) | FUNC1_EN_MASK;
err = sdmmc_io_write_byte(card, 0, SD_IO_CCCR_INT_ENABLE, ie, &ie);
if (err != ESP_OK) return err;
ESP_LOGD(TAG, "IE: 0x%02x", ie);
uint16_t bs = 512;
const uint8_t* bs_u8 = (const uint8_t*) &bs;
uint16_t bs_read = 0;
uint8_t* bs_read_u8 = (uint8_t*) &bs_read;
// Set block sizes for functions 0 to 512 bytes
ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0]));
ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1]));
ESP_LOGI(TAG, "Function 0 BS: %04x", (int) bs_read);
ESP_ERROR_CHECK(sdmmc_io_write_byte(card, 0, SD_IO_CCCR_BLKSIZEL, bs_u8[0], NULL));
ESP_ERROR_CHECK(sdmmc_io_write_byte(card, 0, SD_IO_CCCR_BLKSIZEH, bs_u8[1], NULL));
ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0]));
ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1]));
ESP_LOGI(TAG, "Function 0 BS: %04x", (int) bs_read);
// Set block sizes for functions 1 to given value (default value = 512).
if (context->block_size > 0 || context->block_size <= 2048) {
bs = context->block_size;
} else {
bs = 512;
}
size_t offset = SD_IO_FBR_START * 1;
ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0]));
ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1]));
ESP_LOGI(TAG, "Function 1 BS: %04x", (int) bs_read);
ESP_ERROR_CHECK(sdmmc_io_write_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, bs_u8[0], NULL));
ESP_ERROR_CHECK(sdmmc_io_write_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, bs_u8[1], NULL));
ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0]));
ESP_ERROR_CHECK(sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1]));
ESP_LOGI(TAG, "Function 1 BS: %04x", (int) bs_read);
if (bs_read != context->block_size) {
ESP_LOGW(TAG, "Function1 block size %d different than set value %d", bs_read, context->block_size);
context->block_size = bs_read;
}
return ESP_OK;
}
esp_err_t esp_slave_wait_for_ioready(esp_slave_context_t *context)
{
ESP_LOGV(TAG, "wait_for_ioready");
esp_err_t err;
sdmmc_card_t *card = context->card;
// wait for the card to become ready
uint8_t ior = 0;
while ((ior & FUNC1_EN_MASK) == 0) {
err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior);
if (err != ESP_OK) return err;
ESP_LOGI(TAG, "IOR: 0x%02x", ior);
}
return ESP_OK;
}
static inline esp_err_t esp_slave_write_byte(esp_slave_context_t *context, uint32_t addr, uint8_t val, uint8_t *val_o)
{
return sdmmc_io_write_byte(context->card, 1, addr&0x3FF, val, val_o);
}
static inline esp_err_t esp_slave_write_bytes(esp_slave_context_t *context, uint32_t addr, uint8_t *val, int len)
{
return sdmmc_io_write_bytes(context->card, 1, addr&0x3FF, val, len);
}
static inline esp_err_t esp_slave_read_byte(esp_slave_context_t *context, uint32_t addr, uint8_t *val_o)
{
return sdmmc_io_read_byte(context->card, 1, addr&0x3FF, val_o);
}
static inline esp_err_t esp_slave_read_bytes(esp_slave_context_t *context, uint32_t addr, uint8_t *val_o, int len)
{
return sdmmc_io_read_bytes(context->card, 1, addr&0x3FF, val_o, len);
}
esp_err_t esp_slave_send_packet(esp_slave_context_t *context, const void* start, size_t length, uint32_t wait_ms)
{
sdmmc_card_t *card = context->card;
uint16_t buffer_size = context->buffer_size;
int buffer_used = (length + buffer_size - 1)/buffer_size;
esp_err_t err;
const uint32_t wait_ticks = wait_ms/portTICK_PERIOD_MS;
uint32_t pre = xTaskGetTickCount();
assert(length>0);
for(;;) {
uint32_t num = 0;
err = esp_slave_get_tx_buffer_num(context, &num);
if (err == ESP_OK && num * buffer_size >= length) break;
if (err != ESP_OK && err != ESP_ERR_TIMEOUT) return err;
//not error and buffer not enough, retry ``timeout_cnt`` times
uint32_t now = xTaskGetTickCount();
if (now-pre >= wait_ticks) {
ESP_LOGD(TAG, "buffer is not enough: %d, %d required.", num, buffer_used);
return ESP_ERR_TIMEOUT;
} else {
ESP_LOGV(TAG, "buffer is not enough: %d, %d required. Retry...", num, buffer_used);
}
vTaskDelay(1);
}
ESP_LOGV(TAG, "send_packet: len: %d", length);
uint8_t *start_ptr = (uint8_t*)start;
uint32_t len_remain = length;
do {
const int block_size = 512;
/* Though the driver supports to split packet of unaligned size into
* length of 4x and 1~3, we still send aligned size of data to get
* higher effeciency. The length is determined by the SDIO address, and
* the remainning will be discard by the slave hardware.
*/
int block_n = len_remain/block_size;
int len_to_send;
if (block_n) {
len_to_send = block_n * block_size;
err = sdmmc_io_write_blocks(card, 1, ESP_SLAVE_CMD53_END_ADDR - len_remain, start_ptr, len_to_send);
} else {
len_to_send = len_remain;
err = sdmmc_io_write_bytes(card, 1, ESP_SLAVE_CMD53_END_ADDR - len_remain, start_ptr, (len_to_send + 3) & (~3));
}
if (err != ESP_OK) return err;
start_ptr += len_to_send;
len_remain -= len_to_send;
} while (len_remain);
context->tx_sent_buffers += buffer_used;
return ESP_OK;
}
esp_err_t esp_slave_get_packet(esp_slave_context_t *context, void* out_data, size_t size, size_t *out_length, uint32_t wait_ms)
{
sdmmc_card_t *card = context->card;
esp_err_t err;
esp_err_t ret = ESP_OK;
uint32_t len;
const uint32_t wait_ticks = wait_ms/portTICK_PERIOD_MS;
uint32_t pre = xTaskGetTickCount();
assert (size>0);
for (;;) {
err = esp_slave_get_rx_data_size(context, &len);
if (err == ESP_OK && len > 0) break;
if (err != ESP_OK && err != ESP_ERR_TIMEOUT) return err;
//not error and no data, retry ``timeout_cnt`` times.
uint32_t now = xTaskGetTickCount();
if (now-pre >= wait_ticks) return ESP_ERR_NOT_FOUND;
vTaskDelay(1);
}
ESP_LOGV(TAG, "get_packet: slave len=%d, max read size=%d", len, size);
if (len > size) {
len = size;
ret = ESP_ERR_NOT_FINISHED;
}
uint8_t *start = out_data;
uint32_t len_remain = len;
do {
const int block_size = 512; //currently our driver don't support block size other than 512
int len_to_send;
int block_n = len_remain/block_size;
if (block_n != 0) {
len_to_send = block_n * block_size;
err = sdmmc_io_read_blocks(card, 1, ESP_SLAVE_CMD53_END_ADDR - len_remain, start, len_to_send);
} else {
len_to_send = len_remain;
/* though the driver supports to split packet of unaligned size into length
* of 4x and 1~3, we still get aligned size of data to get higher
* effeciency. The length is determined by the SDIO address, and the
* remainning will be ignored by the slave hardware.
*/
err = sdmmc_io_read_bytes(card, 1, ESP_SLAVE_CMD53_END_ADDR - len_remain, start, (len_to_send + 3) & (~3));
}
if (err != ESP_OK) return err;
start += len_to_send;
len_remain -= len_to_send;
} while(len_remain!=0);
context->rx_got_bytes += len;
*out_length = len;
return ret;
}
esp_err_t esp_slave_get_tx_buffer_num(esp_slave_context_t *context, uint32_t* tx_num)
{
uint32_t len;
esp_err_t err;
ESP_LOGV(TAG, "get_tx_buffer_num");
err = esp_slave_read_bytes(context, HOST_SLC0HOST_TOKEN_RDATA_REG, (uint8_t*)&len, 4);
if (err != ESP_OK) return err;
len = (len>>16)&TX_BUFFER_MASK;
len = (len + TX_BUFFER_MAX - context->tx_sent_buffers)%TX_BUFFER_MAX;
*tx_num = len;
return ESP_OK;
}
esp_err_t esp_slave_get_rx_data_size(esp_slave_context_t *context, uint32_t* rx_size)
{
uint32_t len;
esp_err_t err;
ESP_LOGV(TAG, "get_rx_data_size: got_bytes: %d", context->rx_got_bytes);
err = esp_slave_read_bytes(context, HOST_SLCHOST_PKT_LEN_REG, (uint8_t*)&len, 4);
if (err != ESP_OK) return err;
len &= RX_BYTE_MASK;
len = (len + RX_BYTE_MAX - context->rx_got_bytes)%RX_BYTE_MAX;
*rx_size = len;
return ESP_OK;
}
esp_err_t esp_slave_clear_intr(esp_slave_context_t *context, uint32_t intr_mask)
{
ESP_LOGV(TAG, "clear_intr: %08X", intr_mask);
return esp_slave_write_bytes(context, HOST_SLC0HOST_INT_CLR_REG, (uint8_t*)&intr_mask, 4);
}
esp_err_t esp_slave_get_intr(esp_slave_context_t *context, uint32_t *intr_raw, uint32_t *intr_st)
{
esp_err_t r;
ESP_LOGV(TAG, "get_intr");
if (intr_raw == NULL && intr_st == NULL) return ESP_ERR_INVALID_ARG;
if (intr_raw != NULL) {
r= esp_slave_read_bytes(context, HOST_SLC0HOST_INT_RAW_REG, (uint8_t*)intr_raw, 4);
if (r != ESP_OK) return r;
}
if (intr_st != NULL) {
r = esp_slave_read_bytes(context, HOST_SLC0HOST_INT_ST_REG, (uint8_t*)intr_st, 4);
if (r != ESP_OK) return r;
}
return ESP_OK;
}
esp_err_t esp_slave_set_intr_ena(esp_slave_context_t *context, uint32_t ena_mask)
{
ESP_LOGV(TAG, "set_intr_ena: %08X", ena_mask);
return esp_slave_write_bytes(context, HOST_SLC0HOST_INT_ENA_REG, (uint8_t*)&ena_mask, 4);
}
esp_err_t esp_slave_get_intr_ena(esp_slave_context_t *context, uint32_t *ena_mask_o)
{
ESP_LOGV(TAG, "get_intr_ena");
esp_err_t ret = esp_slave_read_bytes(context, HOST_SLC0HOST_INT_ENA_REG, (uint8_t*)ena_mask_o, 4);
ESP_LOGV(TAG, "ena: %08X", *ena_mask_o);
return ret;
}
esp_err_t esp_slave_write_reg(esp_slave_context_t *context, uint8_t addr, uint8_t value, uint8_t* value_o)
{
ESP_LOGV(TAG, "write_reg: %08X", value);
// addrress over range
if (addr >= 64) return ESP_ERR_INVALID_ARG;
// reserved for interrupts
if (addr >= 28 && addr <= 31) return ESP_ERR_INVALID_ARG;
return esp_slave_write_byte(context, HOST_SLCHOST_CONF_W_REG(addr), value, value_o);
}
esp_err_t esp_slave_read_reg(esp_slave_context_t *context, uint8_t add, uint8_t *value_o)
{
ESP_LOGV(TAG, "read_reg");
// address over range
if (add >= 64) return ESP_ERR_INVALID_ARG;
esp_err_t ret = esp_slave_read_byte(context, HOST_SLCHOST_CONF_W_REG(add), value_o);
ESP_LOGV(TAG, "reg: %08X", *value_o);
return ret;
}
esp_err_t esp_slave_send_slave_intr(esp_slave_context_t *context, uint8_t intr_mask)
{
ESP_LOGV(TAG, "send_slave_intr: %02x", intr_mask);
return esp_slave_write_byte(context, HOST_SLCHOST_CONF_W7_REG+0, intr_mask, NULL);
}

View file

@ -0,0 +1,234 @@
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "driver/sdmmc_defs.h"
#include "soc/host_reg.h"
/*
* NOTE: This component is for example purpose only. Assertion fails if any of
* the preconditions (connections, grounding, slave data preparation, etc.) is
* not met.
* Please do check and handle the return value in your real product.
*/
#define ESP_ERR_NOT_FINISHED 0x201
/** Context used by the ``esp_slave`` component.
*/
typedef struct {
sdmmc_card_t* card; ///< Initialized sdmmc_cmd card
uint16_t buffer_size;
///< 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.
uint16_t block_size;
///< If this is too large, it takes time to send stuff bits; while if too small, intervals between blocks cost much.
///< Should be set according to length of data, and larger than ``TRANS_LEN_MAX/511``.
///< Block size of the SDIO function 1. After the initialization this will hold the value the slave really do. Valid value is 1-2048.
size_t tx_sent_buffers; ///< Counter hold the amount of buffers already sent to ESP32 slave. Should be set to 0 when initialization.
size_t rx_got_bytes; ///< Counter hold the amount of bytes already received from ESP32 slave. Should be set to 0 when initialization.
} esp_slave_context_t;
/** Initialize ``esp_slave_context_t`` by this macro.
*/
#define ESP_SLAVE_DEFAULT_CONTEXT(card) (esp_slave_context_t){\
.card = card, \
.block_size = 0x200, \
.buffer_size = 128, \
.tx_sent_buffers = 0, \
.rx_got_bytes = 0, \
}
/** SDIO Initialize process of a ESP32 slave device.
*
* @param context Context of the ``esp_slave`` component. Send to other functions later.
*
* @return
* - ESP_OK if success
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_init_io(esp_slave_context_t *context);
/** Wait for interrupt of a ESP32 slave device.
*
* @param context Context of the ``esp_slave`` component.
*
* @return
* - ESP_OK if success
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_wait_for_ioready(esp_slave_context_t *context);
/** Get buffer num for the host to send data to the slave. The buffers are size of ``buffer_size``.
*
* @param context Context of the component.
* @param tx_num Output of buffer num that host can send data to ESP32 slave.
*
* @return
* - ESP_OK Success
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_get_tx_buffer_num(esp_slave_context_t *context, uint32_t* tx_num);
/** Get amount of data the ESP32 slave preparing to send to host.
*
* @param context Context of the component.
* @param rx_size Output of data size to read from slave.
*
* @return
* - ESP_OK Success
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_get_rx_data_size(esp_slave_context_t *context, uint32_t* rx_size);
/** Reset the counters of this component. Usually you don't need to do this unless you know the slave is reset.
*
* @param context Context of the component.
*/
inline static void esp_slave_reset_cnt(esp_slave_context_t *context)
{
context->rx_got_bytes = 0;
context->tx_sent_buffers = 0;
}
/** Send a packet to the ESP32 slave. The slave receive the packet into buffers whose size is ``buffer_size`` in the context.
*
* @param context Context of the component.
* @param start Start address of the packet to send
* @param length Length of data to send, if the packet is over-size, the it will be divided into blocks and hold into different buffers automatically.
* @param wait_ms Time to wait before timeout, in ms.
*
* @return
* - ESP_OK Success
* - ESP_ERR_TIMEOUT No buffer to use, or error ftrom SDMMC host controller
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_send_packet(esp_slave_context_t *context, const void* start, size_t length, uint32_t wait_ms);
/** Get a packet from ESP32 slave.
*
* @param context Context of the component.
* @param[out] out_data Data output address
* @param size The size of the output buffer, if the buffer is smaller than the size of data to receive from slave, the driver returns ``ESP_ERR_NOT_FINISHED``
* @param[out] out_length Output of length the data actually received from slave.
* @param wait_ms Time to wait before timeout, in ms.
*
* @return
* - ESP_OK Success, all the data are read from the slave.
* - ESP_ERR_NOT_FINISHED Read success, while there're data remaining.
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_get_packet(esp_slave_context_t *context, void* out_data, size_t size, size_t *out_length, uint32_t wait_ms);
/** wait for an interrupt of the slave
*
* @param context Context of the component.
* @param wait Ticks to wait.
*
* @return
* - ESP_ERR_NOT_SUPPORTED Currently our driver doesnot support SDIO with SPI interface.
* - ESP_OK If interrupt triggered.
* - ESP_ERR_TIMEOUT No interrupts before timeout.
*/
inline static esp_err_t esp_slave_wait_int(esp_slave_context_t *context, TickType_t wait)
{
return sdmmc_io_wait_int(context->card, wait);
}
/** Clear interrupt bits of ESP32 slave. All the bits set in the mask will be cleared, while other bits will stay the same.
*
* @param context Context of the component.
* @param intr_mask Mask of interrupt bits to clear.
*
* @return
* - ESP_OK Success
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_clear_intr(esp_slave_context_t *context, uint32_t intr_mask);
/** Get interrupt bits of ESP32 slave.
*
* @param context Context of the component.
* @param intr_raw Output of the raw interrupt bits. Set to NULL if only masked bits are read.
* @param intr_st Output of the masked interrupt bits. set to NULL if only raw bits are read.
*
* @return
* - ESP_OK Success
* - ESP_INVALID_ARG if both ``intr_raw`` and ``intr_st`` are NULL.
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_get_intr(esp_slave_context_t *context, uint32_t *intr_raw, uint32_t *intr_st);
/** Set interrupt enable bits of ESP32 slave. The slave only sends interrupt on the line when there is a bit both the raw status and the enable are set.
*
* @param context Context of the component.
* @param ena_mask Mask of the interrupt bits to enable.
*
* @return
* - ESP_OK Success
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_set_intr_ena(esp_slave_context_t *context, uint32_t ena_mask);
/** Get interrupt enable bits of ESP32 slave.
*
* @param context Context of the component.
* @param ena_mask_o Output of interrupt bit enable mask.
*
* @return
* - ESP_OK Success
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_get_intr_ena(esp_slave_context_t *context, uint32_t *ena_mask_o);
/** Write general purpose R/W registers (8-bit) of ESP32 slave.
*
* @param context Context of the component.
* @param addr Address of register to write. Valid address: 0-27, 32-63 (28-31 reserved).
* @param value Value to write to the register.
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Address not valid.
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_write_reg(esp_slave_context_t *context, uint8_t addr, uint8_t value, uint8_t* value_o);
/** Read general purpose R/W registers (8-bit) of ESP32 slave.
*
* @param context Context of the component.
* @param add Address of register to read. Valid address: 0-27, 32-63 (28-31 reserved, return interrupt bits on read).
* @param value Output value read from the register.
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Address not valid.
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_read_reg(esp_slave_context_t *context, uint8_t add, uint8_t *value_o);
/** Send interrupts to slave. Each bit of the interrupt will be triggered.
*
* @param context Context of the component.
* @param intr_mask Mask of interrupt bits to send to slave.
*
* @return
* - ESP_OK Success
* - One of the error codes from SDMMC host controller
*/
esp_err_t esp_slave_send_slave_intr(esp_slave_context_t *context, uint8_t intr_mask);

View file

@ -0,0 +1,43 @@
menu "Example Configuration"
config SDIO_EXAMPLE_4BIT
bool "Host tries using 4-bit mode to communicate with slave"
default n
help
If this is set, the host tries using 4-bit mode to communicate with
slave. If failed, the communication falls back to 1-bit mode.
If this is not set, the host uses 1-bit mode. However, CMD1 is still
mandatory for interrupts.
Note that 4-bit mode is not compatible (by default) if the slave is
using 3.3V flash which requires a pull-down on the MTDI pin.
config SDIO_EXAMPLE_HIGHSPEED
bool "Host tries using HS mode to communicate with slave"
default y
help
If this is set, the host tries using high-speed mode to communicate
with slave. If the slave doesn't support high-speed mode, the
communication falls back to default-speed mode. If this is not set,
the host uses DS mode.
If the example does not work, please try disabling the HS mode.
choice EXAMPLE_SLAVE
prompt "Id of Slave used in Espressif master-slave board."
default EXAMPLE_SLAVE_NONE
help
If Espressif master-slave board is used, select which slave is used.
config EXAMPLE_SLAVE_NONE
bool "Not using Espressif master-slave board."
config EXAMPLE_SLAVE_B1
bool "Using slave B1"
config EXAMPLE_SLAVE_B2
bool "Using slave B2"
config EXAMPLE_SLAVE_B3
bool "Using slave B3"
endchoice
endmenu

View file

@ -0,0 +1,391 @@
/* SDIO example, host (uses sdmmc host driver)
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
#include "lwip/igmp.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
#include "soc/rtc_cntl_reg.h"
#include "rom/cache.h"
#include "soc/sdmmc_periph.h"
#include "driver/periph_ctrl.h"
#include "esp_log.h"
#include "soc/gpio_reg.h"
#include "driver/gpio.h"
#include "esp_intr_alloc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_attr.h"
#include "esp_slave.h"
/*
* For SDIO master-slave board, we have 3 pins controlling power of 3 different
* slaves individially. We only enable one at a time.
*/
#define GPIO_B1 5
#define GPIO_B2 18
#define GPIO_B3 19
#if CONFIG_EXAMPLE_SLAVE_B1
#define SLAVE_PWR_GPIO GPIO_B1
#elif CONFIG_EXAMPLE_SLAVE_B2
#define SLAVE_PWR_GPIO GPIO_B2
#elif CONFIG_EXAMPLE_SLAVE_B3
#define SLAVE_PWR_GPIO GPIO_B3
#endif
/*
sdio host example.
This example is supposed to work together with the sdio slave example. It uses the pins as follows:
* Host Slave
* IO14 CLK
* IO15 CMD
* IO2 D0
* IO4 D1
* IO12 D2
* IO13 D3
This is the only pins that can be used in standard ESP modules. The other set of pins (6, 11, 7, 8, 9, 10)
are occupied by the spi bus communicating with the flash.
Protocol Above the ESP slave service:
- Interrupts:
0 is used to notify the slave to read the register 0.
- Registers:
- 0 is the register to hold tasks. Bits:
- 0: the slave should reset.
- 1: the slave should send interrupts.
- 2: the slave should write the shared registers acoording to the value in register 1.
- 1 is the register to hold test value.
- other registers will be written by the slave for testing.
- FIFO:
The receving FIFO is size of 256 bytes.
When the host writes something to slave recv FIFO, the slave should return it as is to the sending FIFO.
The example works as following process:
1. reset the slave.
2. tell the slave to write registers and read them back.
3. tell the slave to send interrupts to the host.
4. send data to slave FIFO and read them back.
5. loop step 4.
*/
#define WRITE_BUFFER_LEN 4096
#define READ_BUFFER_LEN 1024
static const char TAG[] = "example_host";
#define SDIO_INTERRUPT_LINE GPIO_NUM_4 //DATA1
#define SLAVE_INTR_NOTIFY 0
#define SLAVE_REG_JOB 0
#define SLAVE_REG_VALUE 1
typedef enum {
JOB_IDLE = 0,
JOB_RESET = 1,
JOB_SEND_INT = 2,
JOB_WRITE_REG = 4,
} example_job_t;
//host use this to inform the slave it should reset its counters
esp_err_t slave_reset(esp_slave_context_t *context)
{
esp_err_t ret;
ESP_LOGI(TAG, "send reset to slave...");
ret = esp_slave_write_reg(context, 0, JOB_RESET, NULL);
if (ret != ESP_OK) {
return ret;
}
ret = esp_slave_send_slave_intr(context, BIT(SLAVE_INTR_NOTIFY));
if (ret != ESP_OK) {
return ret;
}
vTaskDelay(500 / portTICK_RATE_MS);
ret = esp_slave_wait_for_ioready(context);
ESP_LOGI(TAG, "slave io ready");
return ret;
}
//host use this to initialize the slave card as well as SDIO registers
esp_err_t slave_init(esp_slave_context_t *context)
{
/* Probe */
sdmmc_host_t config = SDMMC_HOST_DEFAULT();
#ifdef CONFIG_SDIO_EXAMPLE_4BIT
config.flags = SDMMC_HOST_FLAG_4BIT;
#else
config.flags = SDMMC_HOST_FLAG_1BIT;
#endif
#ifdef CONFIG_SDIO_EXAMPLE_HIGHSPEED
config.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
#else
config.max_freq_khz = SDMMC_FREQ_PROBING;
#endif
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
/* Note: For small devkits there may be no pullups on the board.
This enables the internal pullups to help evaluate the driver quickly.
However the internal pullups are not sufficient and not reliable,
please make sure external pullups are connected to the bus in your
real design.
*/
//slot_config.flags = SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
sdmmc_host_init();
sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config);
sdmmc_card_t *card = (sdmmc_card_t *)malloc(sizeof(sdmmc_card_t));
if (card == NULL) {
return ESP_ERR_NO_MEM;
}
for (;;) {
if (sdmmc_card_init(&config, card) == ESP_OK) {
break;
}
ESP_LOGW(TAG, "slave init failed, retry...");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
sdmmc_card_print_info(stdout, card);
gpio_pullup_en(14);
gpio_pulldown_dis(14);
gpio_pullup_en(15);
gpio_pulldown_dis(15);
gpio_pullup_en(2);
gpio_pulldown_dis(2);
gpio_pullup_en(4);
gpio_pulldown_dis(4);
gpio_pullup_en(12);
gpio_pulldown_dis(12);
gpio_pullup_en(13);
gpio_pulldown_dis(13);
*context = ESP_SLAVE_DEFAULT_CONTEXT(card);
esp_err_t ret = esp_slave_init_io(context);
return ret;
}
void slave_power_on()
{
#ifdef SLAVE_PWR_GPIO
gpio_config_t cfg = {
.pin_bit_mask = BIT(SLAVE_PWR_GPIO),
.mode = GPIO_MODE_DEF_OUTPUT,
.pull_up_en = false,
.pull_down_en = false,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&cfg);
gpio_set_level(SLAVE_PWR_GPIO, 0); //low active
#endif
}
//try to get an interrupt from the slave and handle it, return if none.
esp_err_t process_event(esp_slave_context_t *context)
{
uint8_t buffer[READ_BUFFER_LEN];
esp_err_t ret = esp_slave_wait_int(context, 0);
if (ret == ESP_ERR_TIMEOUT) {
return ret;
}
ESP_ERROR_CHECK(ret);
uint32_t intr_raw, intr_st;
ret = esp_slave_get_intr(context, &intr_raw, &intr_st);
ESP_ERROR_CHECK(ret);
ret = esp_slave_clear_intr(context, intr_raw);
ESP_ERROR_CHECK(ret);
ESP_LOGD(TAG, "intr: %08X", intr_raw);
for (int i = 0; i < 8; i++) {
if (intr_raw & BIT(i)) {
ESP_LOGI(TAG, "host int: %d", i);
}
}
const int wait_ms = 50;
if (intr_raw & HOST_SLC0_RX_NEW_PACKET_INT_ST) {
ESP_LOGD(TAG, "new packet coming");
while (1) {
size_t size_read = READ_BUFFER_LEN;
ret = esp_slave_get_packet(context, buffer, READ_BUFFER_LEN, &size_read, wait_ms);
if (ret == ESP_ERR_NOT_FOUND) {
ESP_LOGE(TAG, "interrupt but no data can be read");
break;
} else if (ret != ESP_OK && ret != ESP_ERR_NOT_FINISHED) {
ESP_LOGE(TAG, "rx packet error: %08X", ret);
return ret;
}
ESP_LOGI(TAG, "receive data, size: %d", size_read);
ESP_LOG_BUFFER_HEXDUMP(TAG, buffer, size_read, ESP_LOG_INFO);
if (ret == ESP_OK) {
break;
}
}
}
return ESP_OK;
}
//tell the slave to do a job
static inline esp_err_t slave_inform_job(esp_slave_context_t *context, example_job_t job)
{
esp_err_t ret;
ret = esp_slave_write_reg(context, SLAVE_REG_JOB, job, NULL);
ESP_ERROR_CHECK(ret);
ret = esp_slave_send_slave_intr(context, BIT(SLAVE_INTR_NOTIFY));
ESP_ERROR_CHECK(ret);
return ret;
}
//tell the slave to write registers by write one of them, and read them back
void job_write_reg(esp_slave_context_t *context, int value)
{
esp_err_t ret;
uint8_t reg_read[64];
ESP_LOGI(TAG, "========JOB: write slave reg========");
ret = esp_slave_write_reg(context, SLAVE_REG_VALUE, value, NULL);
ESP_ERROR_CHECK(ret);
ret = slave_inform_job(context, JOB_WRITE_REG);
ESP_ERROR_CHECK(ret);
vTaskDelay(10);
for (int i = 0; i < 64; i++) {
ESP_LOGD(TAG, "reading register %d", i);
ret = esp_slave_read_reg(context, i, &reg_read[i]);
ESP_ERROR_CHECK(ret);
}
ESP_LOGI(TAG, "read registers:");
ESP_LOG_BUFFER_HEXDUMP(TAG, reg_read, 64, ESP_LOG_INFO);
}
//use 1+1+1+1+4+4=12 packets, 513 and 517 not sent
int packet_len[] = {3, 6, 12, 128, 511, 512, 513, 517};
//the sending buffer should be word aligned
DMA_ATTR uint8_t buffer[1024];
//send packets to the slave (they will return and be handled by the interrupt handler)
void job_fifo(esp_slave_context_t *context)
{
for (int i = 0; i < 1024; i++) {
buffer[i] = 0x46 + i * 5;
}
esp_err_t ret;
int pointer = 0;
ESP_LOGI(TAG, "========JOB: send fifos========");
/* CAUTION: This example shows that we can send random length of packet to the slave.
* However it takes time of two transactions if the length is not multiples of 4 bytes.
* e.g. sending 6 bytes is done by sending 4 + 2 bytes each transaction.
* Try to avoid unaligned packets if possible to get higher effeciency.
*/
for (int i = 0; i < sizeof(packet_len) / sizeof(int); i++) {
const int wait_ms = 50;
int length = packet_len[i];
ret = esp_slave_send_packet(context, buffer + pointer, length, wait_ms);
if (ret == ESP_ERR_TIMEOUT) {
ESP_LOGD(TAG, "several packets are expected to timeout.");
} else {
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "send packet length: %d", length);
}
pointer += (length + 3) & (~3); //the length can be random, but data should start at the 32-bit boundary.
}
}
//inform the slave to send interrupts to host (the interrupts will be handled in the interrupt handler)
void job_getint(esp_slave_context_t *context)
{
ESP_LOGI(TAG, "========JOB: get interrupts from slave========");
slave_inform_job(context, JOB_SEND_INT);
}
void app_main()
{
esp_slave_context_t context;
esp_err_t err;
//enable the power if on espressif SDIO master-slave board
slave_power_on();
ESP_LOGI(TAG, "host ready, start initializing slave...");
err = slave_init(&context);
ESP_ERROR_CHECK(err);
err = slave_reset(&context);
ESP_ERROR_CHECK(err);
uint32_t start, end;
job_write_reg(&context, 10);
int times = 2;
while (1) {
job_getint(&context);
start = xTaskGetTickCount();
while (1) {
process_event(&context);
vTaskDelay(1);
end = xTaskGetTickCount();
if ((end - start) * 1000 / CONFIG_FREERTOS_HZ > 5000) {
break;
}
}
if (--times == 0) {
break;
}
};
while (1) {
job_fifo(&context);
start = xTaskGetTickCount();
while (1) {
process_event(&context);
vTaskDelay(1);
end = xTaskGetTickCount();
if ((end - start) * 1000 / CONFIG_FREERTOS_HZ > 2000) {
break;
}
}
}
}

View file

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View file

@ -0,0 +1 @@
CONFIG_EXAMPLE_SLAVE_B1=y

View file

@ -0,0 +1,134 @@
# Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" example of writing test with TinyTestFW """
import re
import os
import sys
# if we want to run test case outside `tiny-test-fw` folder,
# we need to insert tiny-test-fw path into sys path
test_fw_path = os.getenv("TEST_FW_PATH")
if test_fw_path and test_fw_path not in sys.path:
sys.path.insert(0, test_fw_path)
import TinyFW
import IDF
@IDF.idf_example_test(env_tag="Example_SDIO")
def test_example_sdio_communication(env, extra_data):
"""
Configurations
dut1 = host -> dut2 = slave
should be in the same group of devices, otherwise may meet download issue
group1: (Wroom-32 Series or PICO-D4 modules: PICO-Kit, DevKitC, WroverKit v2 or earlier)
group2: (Wrover module: WroverKit v3)
GPIO14->GPIO14
GPIO15->GPIO15
GPIO2->GPIO2
GPIO4->GPIO4
GND->GND
VDD3.3 -> GPIO13 if dut2 uses WroverKit v3
or use sdio test board, which has two wrover modules connect to a same FT3232
Assume that first dut is host and second is slave
"""
dut1 = env.get_dut("sdio_host", "examples/peripherals/sdio/host")
dut2 = env.get_dut("sdio_slave", "examples/peripherals/sdio/slave")
dut1.start_app()
#wait until the master is ready to setup the slave
dut1.expect("host ready, start initializing slave...")
dut2.start_app()
dut1.expect("0a 0d 10 13 16 19 1c 1f 22 25 28 2b 2e 31 34 37")
dut1.expect("3a 3d 40 43 46 49 4c 4f 52 55 58 5b 00 00 00 00")
dut1.expect("6a 6d 70 73 76 79 7c 7f 82 85 88 8b 8e 91 94 97")
dut1.expect("9a 9d a0 a3 a6 a9 ac af b2 b5 b8 bb be c1 c4 c7")
dut2.expect("================ JOB_WRITE_REG ================")
dut2.expect("0a 0d 10 13 16 19 1c 1f 22 25 28 2b 2e 31 34 37")
dut2.expect("3a 3d 40 43 46 49 4c 4f 52 55 58 5b 00 00 00 00")
dut2.expect("6a 6d 70 73 76 79 7c 7f 82 85 88 8b 8e 91 94 97")
dut2.expect("9a 9d a0 a3 a6 a9 ac af b2 b5 b8 bb be c1 c4 c7")
dut1.expect("host int: 0")
dut1.expect("host int: 1")
dut1.expect("host int: 2")
dut1.expect("host int: 3")
dut1.expect("host int: 4")
dut1.expect("host int: 5")
dut1.expect("host int: 6")
dut1.expect("host int: 7")
dut1.expect("host int: 0")
dut1.expect("host int: 1")
dut1.expect("host int: 2")
dut1.expect("host int: 3")
dut1.expect("host int: 4")
dut1.expect("host int: 5")
dut1.expect("host int: 6")
dut1.expect("host int: 7")
dut2.expect("================ JOB_SEND_INT ================")
dut2.expect("================ JOB_SEND_INT ================")
dut1.expect("send packet length: 3")
dut1.expect("send packet length: 6")
dut1.expect("send packet length: 12")
dut1.expect("send packet length: 128")
dut1.expect("send packet length: 511")
dut1.expect("send packet length: 512")
dut2.expect("recv len: 3")
dut2.expect("recv len: 6")
dut2.expect("recv len: 12")
dut2.expect("recv len: 128")
#511
dut2.expect("recv len: 128")
dut2.expect("recv len: 128")
dut2.expect("recv len: 128")
dut2.expect("recv len: 127")
#512
dut2.expect("recv len: 128")
dut2.expect("recv len: 128")
dut2.expect("recv len: 128")
dut2.expect("recv len: 128")
dut1.expect("receive data, size: 3")
dut1.expect("receive data, size: 6")
dut1.expect("receive data, size: 12")
dut1.expect("receive data, size: 128")
dut1.expect("receive data, size: 128")
dut1.expect("receive data, size: 128")
dut1.expect("receive data, size: 128")
dut1.expect("receive data, size: 127")
dut1.expect("receive data, size: 128")
dut1.expect("receive data, size: 128")
dut1.expect("receive data, size: 128")
dut1.expect("receive data, size: 128")
#the last valid line of one round
dut1.expect("ce d3 d8 dd e2 e7 ec f1 f6 fb 00 05 0a 0f 14 19")
#the first 2 lines of the second round
dut1.expect("46 4b 50")
dut1.expect("5a 5f 64 69 6e 73")
if __name__ == '__main__':
TinyFW.set_default_config(env_config_file="EnvConfigTemplate.yml", dut=IDF.IDFDUT)
test_example_sdio_communication()

View file

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := sdio_slave
include $(IDF_PATH)/make/project.mk

View file

@ -0,0 +1,16 @@
menu "Example Configuration"
config SDIO_DAT2_DISABLED
bool "Disable the DAT2 in SDIO slave"
default y
help
SDIO slave DAT pin is unfortunately the same pin as MTDI, which
controls the flash power voltage. For 3.3v flash devkits / modules /
kits, it conflicts with the DAT2 pullups required by the
specification.
This disables the peripheral input from the DAT2 so that we can work
in 1-bit mode when DAT2 is floating (pulled down). 4-bit mode is
therefore unavailable.
endmenu

View file

@ -0,0 +1,276 @@
/* SDIO example, slave (uses sdio slave driver)
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "driver/sdio_slave.h"
#include "esp_log.h"
#include "rom/lldesc.h"
#include "rom/queue.h"
#include "soc/soc.h"
#include "soc/sdio_slave_periph.h"
#include "freertos/task.h"
#include "freertos/ringbuf.h"
/*
sdio slave example.
This example is supposed to work together with the sdio host example. It uses the pins as follows:
* Host Slave
* IO14 CLK
* IO15 CMD
* IO2 D0
* IO4 D1
* IO12 D2
* IO13 D3
This is the only pins that can be used in standard ESP modules. The other set of pins (6, 11, 7, 8, 9, 10)
are occupied by the spi bus communicating with the flash.
Protocol Above the ESP slave service:
- Interrupts:
0 is used to notify the slave to read the register 0.
- Registers:
- 0 is the register to hold tasks. Bits:
- 0: the slave should reset.
- 1: the slave should send interrupts.
- 2: the slave should write the shared registers acoording to the value in register 1.
- 1 is the register to hold test value.
- other registers will be written by the slave for testing.
- FIFO:
The receving FIFO is size of 256 bytes.
When the host writes something to slave recv FIFO, the slave should return it as is to the sending FIFO.
The host works as following process:
1. reset the slave.
2. tell the slave to write registers and read them back.
3. tell the slave to send interrupts to the host.
4. send data to slave FIFO and read them back.
5. loop step 4.
*/
#define SDIO_SLAVE_QUEUE_SIZE 11
#define BUFFER_SIZE 128
#define BUFFER_NUM 12
#define EV_STR(s) "================ "s" ================"
typedef enum {
JOB_IDLE = 0,
JOB_RESET = 1,
JOB_SEND_INT = 2,
JOB_WRITE_REG = 4,
} example_job_t;
static const char TAG[] = "example_slave";
static int s_job = JOB_IDLE;
DMA_ATTR uint8_t data_to_send[BUFFER_SIZE] = {0x97, 0x84, 0x43, 0x67, 0xc1, 0xdd, 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x56, 0x55, 0x44, 0x33 ,0x22, 0x11, 0x00 };
DMA_ATTR uint8_t data_to_recv[BUFFER_SIZE] = {0x97, 0x84, 0x43, 0x67, 0xc1, 0xdd, 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x56, 0x55, 0x44, 0x33 ,0x22, 0x11, 0x00 };
static const char job_desc[][32] = {
"JOB_IDLE",
"JOB_RESET",
"JOB_SEND_INT",
"JOB_WRITE_REG",
};
//reset counters of the slave hardware, and clean the receive buffer (normally they should be sent back to the host)
static esp_err_t slave_reset()
{
esp_err_t ret;
sdio_slave_stop();
ret = sdio_slave_reset();
if (ret != ESP_OK) return ret;
ret = sdio_slave_start();
if (ret != ESP_OK) return ret;
//Since the buffer will not be sent any more, we return them back to receving driver
while(1) {
sdio_slave_buf_handle_t handle;
ret = sdio_slave_send_get_finished(&handle, 0);
if (ret != ESP_OK) break;
ret = sdio_slave_recv_load_buf(handle);
ESP_ERROR_CHECK(ret);
}
return ESP_OK;
}
//sent interrupts to the host in turns
static esp_err_t task_hostint()
{
for(int i = 0; i < 8; i++) {
ESP_LOGV(TAG, "send intr: %d", i);
sdio_slave_send_host_int(i);
//check reset for quick response to RESET signal
if (s_job & JOB_RESET) break;
vTaskDelay(500/portTICK_RATE_MS);
}
return ESP_OK;
}
//read the value in a specified register set by the host, and set other register according to this.
//the host will read these registers later
static esp_err_t task_write_reg()
{
//the host write REG1, the slave should write its registers according to value of REG1
uint8_t read = sdio_slave_read_reg(1);
for (int i = 0; i < 64; i++) {
//skip interrupt regs.
if (i >= 28 && i <= 31) continue;
sdio_slave_write_reg(i, read+3*i);
}
uint8_t reg[64];
for (int i = 0; i < 64; i++) {
//skip interrupt regs.
if (i >= 28 && i <= 31) continue;
reg[i] = sdio_slave_read_reg(i);
}
ESP_LOGI(TAG, "write regs:");
ESP_LOG_BUFFER_HEXDUMP(TAG, reg, 64, ESP_LOG_INFO);
return ESP_OK;
}
//we use the event callback (in ISR) in this example to get higer responding speed
//note you can't do delay in the ISR
//``sdio_slave_wait_int`` is another way to handle interrupts
static void event_cb(uint8_t pos)
{
ESP_EARLY_LOGD(TAG, "event: %d", pos);
switch(pos) {
case 0:
s_job = sdio_slave_read_reg(0);
sdio_slave_write_reg(0, JOB_IDLE);
break;
}
}
DMA_ATTR uint8_t buffer[BUFFER_NUM][BUFFER_SIZE] = {};
//Main application
void app_main()
{
esp_err_t ret;
sdio_slave_config_t config = {
.sending_mode = SDIO_SLAVE_SEND_PACKET,
.send_queue_size = SDIO_SLAVE_QUEUE_SIZE,
.recv_buffer_size = BUFFER_SIZE,
.event_cb = event_cb,
/* Note: For small devkits there may be no pullups on the board.
This enables the internal pullups to help evaluate the driver
quickly. However the internal pullups are not sufficient and not
reliable, please make sure external pullups are connected to the
bus in your real design.
*/
//.flags = SDIO_SLAVE_FLAG_INTERNAL_PULLUP,
};
#ifdef CONFIG_SDIO_DAT2_DISABLED
/* For slave chips with 3.3V flash, DAT2 pullup conflicts with the pulldown
required by strapping pin (MTDI). We can either burn the EFUSE for the
strapping or just disable the DAT2 and work in 1-bit mode.
*/
config.flags |= SDIO_SLAVE_FLAG_DAT2_DISABLED;
#endif
ret = sdio_slave_initialize(&config);
ESP_ERROR_CHECK(ret);
sdio_slave_write_reg(0, JOB_IDLE);
sdio_slave_buf_handle_t handle;
for(int i = 0; i < BUFFER_NUM; i++) {
handle = sdio_slave_recv_register_buf(buffer[i]);
assert(handle != NULL);
ret = sdio_slave_recv_load_buf(handle);
ESP_ERROR_CHECK(ret);
}
sdio_slave_set_host_intena(SDIO_SLAVE_HOSTINT_SEND_NEW_PACKET |
SDIO_SLAVE_HOSTINT_BIT0 |
SDIO_SLAVE_HOSTINT_BIT1 |
SDIO_SLAVE_HOSTINT_BIT2 |
SDIO_SLAVE_HOSTINT_BIT3 |
SDIO_SLAVE_HOSTINT_BIT4 |
SDIO_SLAVE_HOSTINT_BIT5 |
SDIO_SLAVE_HOSTINT_BIT6 |
SDIO_SLAVE_HOSTINT_BIT7
);
sdio_slave_start();
ESP_LOGI(TAG, EV_STR("slave ready"));
for(;;) {
//receive data and send back to host.
size_t length;
uint8_t *ptr;
const TickType_t non_blocking = 0;
ret = sdio_slave_recv(&handle, &ptr, &length, non_blocking);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "handle: %p, recv len: %d, data:", handle, length);
ESP_LOG_BUFFER_HEXDUMP(TAG, ptr, length, ESP_LOG_INFO);
/* If buffer is no longer used, call sdio_slave_recv_load_buf to return it here. Since we wants to show how
* to share large buffers between drivers here (we share between sending and receiving), keep the buffer
* until the buffer is sent by sending driver.
*/
//send the received buffer to host, with the handle as the argument
ret = sdio_slave_send_queue(ptr, length, handle, non_blocking);
if (ret == ESP_ERR_TIMEOUT) {
// send failed, direct return the buffer to rx
ESP_LOGE(TAG, "send_queue full, discard received.");
ret = sdio_slave_recv_load_buf(handle);
}
ESP_ERROR_CHECK(ret);
}
// if there's finished sending desc, return the buffer to receiving driver
for(;;){
sdio_slave_buf_handle_t handle;
ret = sdio_slave_send_get_finished(&handle, 0);
if (ret == ESP_ERR_TIMEOUT) break;
ESP_ERROR_CHECK(ret);
ret = sdio_slave_recv_load_buf(handle);
ESP_ERROR_CHECK(ret);
}
if (s_job != 0) {
for(int i = 0; i < 8; i++) {
if (s_job & BIT(i)) {
ESP_LOGI(TAG, EV_STR("%s"), job_desc[i+1]);
s_job &= ~BIT(i);
switch(BIT(i)) {
case JOB_SEND_INT:
ret = task_hostint();
ESP_ERROR_CHECK(ret);
break;
case JOB_RESET:
ret = slave_reset();
ESP_ERROR_CHECK(ret);
break;
case JOB_WRITE_REG:
ret = task_write_reg();
ESP_ERROR_CHECK(ret);
break;
}
}
}
}
vTaskDelay(1);
}
}

View file

@ -0,0 +1,8 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View file

@ -31,7 +31,10 @@ N/C | WP | | optional, not used in the example
This example doesn't utilize card detect (CD) and write protect (WP) signals from SD card slot.
With the given pinout for SPI mode, same connections between the SD card and ESP32 can be used to test both SD and SPI modes, provided that the appropriate pullups are in place. In SPI mode, pins can be customized. See the initialization of ``sdspi_slot_config_t`` structure in the example code.
With the given pinout for SPI mode, same connections between the SD card and ESP32 can be used to test both SD and SPI modes, provided that the appropriate pullups are in place.
See document `sd_pullup_requirements.rst` in `docs/en/api-reference/peripherals/` for more details about pullup support and compatiblities about modules and devkits.
In SPI mode, pins can be customized. See the initialization of ``sdspi_slot_config_t`` structure in the example code.
### Note about GPIO2