mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
feat(sdio): add hw_test example for signal integrity checks
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "app_main.c"
|
||||
PRIV_REQUIRES esp_driver_sdio esp_ringbuf
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,16 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config SDIO_DAT2_DISABLED
|
||||
bool "Disable the DAT2 in SDIO slave"
|
||||
default n
|
||||
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
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
#include "driver/sdio_slave.h"
|
||||
#include "esp_log.h"
|
||||
#include "sys/queue.h"
|
||||
#include "soc/soc.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "sdkconfig.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 according 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 receiving 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 16
|
||||
|
||||
#define EV_STR(s) "================ "s" ================"
|
||||
|
||||
//skip interrupt regs.
|
||||
#define SLAVE_ADDR(i) ((i) >= 28? (i) + 4: (i))
|
||||
|
||||
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(void)
|
||||
{
|
||||
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 receiving 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(void)
|
||||
{
|
||||
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_PERIOD_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(void)
|
||||
{
|
||||
//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 < 60; i++) {
|
||||
sdio_slave_write_reg(SLAVE_ADDR(i), read + 3 * i);
|
||||
}
|
||||
uint8_t reg[60] = {0};
|
||||
for (int i = 0; i < 60; i++) {
|
||||
reg[i] = sdio_slave_read_reg(SLAVE_ADDR(i));
|
||||
}
|
||||
ESP_LOGI(TAG, "write regs:");
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, reg, 60, ESP_LOG_INFO);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
//we use the event callback (in ISR) in this example to get higher 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(void)
|
||||
{
|
||||
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);
|
||||
|
||||
for (int i = 0; i < BUFFER_NUM; i++) {
|
||||
sdio_slave_buf_handle_t 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 (;;) {
|
||||
const TickType_t non_blocking = 0, blocking = portMAX_DELAY;
|
||||
sdio_slave_buf_handle_t recv_queue[BUFFER_NUM];
|
||||
int packet_size = 0;
|
||||
|
||||
sdio_slave_buf_handle_t handle;
|
||||
ret = sdio_slave_recv_packet(&handle, non_blocking);
|
||||
if (ret != ESP_ERR_TIMEOUT) {
|
||||
recv_queue[packet_size++] = handle;
|
||||
|
||||
//Receive following buffers in the same packet in blocking mode.
|
||||
//You can also skip this step and handle the data buffer by buffer, if the data is a stream or you don't care about the packet boundary.
|
||||
while (ret == ESP_ERR_NOT_FINISHED) {
|
||||
//The return value must be ESP_OK or ESP_ERR_NOT_FINISHED.
|
||||
ret = sdio_slave_recv_packet(&handle, blocking);
|
||||
recv_queue[packet_size++] = handle;
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
int packet_len = 0;
|
||||
for (int i = 0; i < packet_size; i++) {
|
||||
size_t buf_len;
|
||||
sdio_slave_recv_get_buf(recv_queue[i], &buf_len);
|
||||
packet_len += buf_len;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Packet received, len: %d", packet_len);
|
||||
|
||||
for (int i = 0; i < packet_size; i++) {
|
||||
handle = recv_queue[i];
|
||||
//handle data in the buffer, here we print them and send the same buffer back to the host
|
||||
//receive data and send back to host.
|
||||
size_t length;
|
||||
uint8_t *ptr = sdio_slave_recv_get_buf(handle, &length);
|
||||
|
||||
ESP_LOGI(TAG, "Buffer %d, len: %d", i, length);
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, ptr, length, ESP_LOG_INFO);
|
||||
|
||||
/* If buffer is no longer used, we can call sdio_slave_recv_load_buf to use it to receive data again.
|
||||
* But here we wants to show how to share large buffers between drivers here (we share the buffer
|
||||
* between sending and receiving), the buffer is kept until the buffer is sent by sending driver.
|
||||
*/
|
||||
|
||||
//the recv_buf_handle is used as the argument, so that we can easily load the same buffer to recv driver,
|
||||
// after it's sent
|
||||
|
||||
void* send_args = handle;
|
||||
ret = sdio_slave_send_queue(ptr, length, send_args, 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 (;;) {
|
||||
void* send_args = NULL;
|
||||
ret = sdio_slave_send_get_finished(&send_args, 0);
|
||||
|
||||
//extract the buffer handle from the sending args
|
||||
sdio_slave_buf_handle_t handle = (sdio_slave_buf_handle_t)send_args;
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user