mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'sdio-example-4bit-bringup' into 'master'
example(sdio_slave): add docs and examples for SI testing See merge request espressif/esp-idf!49371
This commit is contained in:
@@ -153,7 +153,7 @@ The host should initialize the {IDF_TARGET_NAME} SDIO slave according to the sta
|
||||
|
||||
Furthermore, there is an {IDF_TARGET_NAME}-specific upper-level communication protocol built upon the foundation of CMD52/CMD53 to Function 1. Within this particular communication protocol, the master and slave engage in data exchange and communication through the utilization of CMD52/CMD53 commands. For more detailed information, please consult the `ESP SDIO Slave Protocol <https://espressif.github.io/idf-extra-components/latest/esp_serial_slave_link/sdio_slave_protocol.html#esp-sdio-slave-protocol>`_ section.
|
||||
|
||||
There is also a component `ESSL <https://components.espressif.com/components/espressif/esp_serial_slave_link>`_ designed for {IDF_TARGET_NAME} master to communicate with {IDF_TARGET_NAME} SDIO slave. See example :example:`peripherals/sdio` when programming your host.
|
||||
There is also a component `ESSL <https://components.espressif.com/components/espressif/esp_serial_slave_link>`_ designed for {IDF_TARGET_NAME} master to communicate with {IDF_TARGET_NAME} SDIO slave. See example :example:`peripherals/sdio/basic` when programming your host.
|
||||
|
||||
|
||||
.. _interrupts:
|
||||
@@ -274,7 +274,7 @@ There are several ways to use the ``arg`` in the queue parameter:
|
||||
sdio_slave_send_get_finished((void**)&handle, portMAX_DELAY);
|
||||
sdio_slave_recv_load_buf(handle);
|
||||
|
||||
For more about this, see :example:`peripherals/sdio`.
|
||||
For more about this, see :example:`peripherals/sdio/basic`.
|
||||
|
||||
Reset SDIO
|
||||
^^^^^^^^^^^^
|
||||
@@ -287,10 +287,45 @@ If there is a usage scenario where the ESP chip remains powered on but the HOST
|
||||
|
||||
Reset the SDIO hardware. The interrupt enable status and shared register values will be lost. You may need to call ``sdio_slave_set_host_intena`` and ``sdio_slave_write_reg`` to set them.
|
||||
|
||||
Signal Quality Checks
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For SDIO slave links, command-only initialization can succeed even when one or more data lines are disconnected or have poor signal quality. In the ESP-IDF host stack, SDIO card discovery and configuration use ``CMD0``, ``CMD5``, and ``CMD52`` transactions on the command line. For example, the :example:`peripherals/sdio/hw_test` host can complete ``sdmmc_card_init()`` and its ``enable_slave_function()`` step before any payload transfer starts on ``DAT0-DAT3``.
|
||||
|
||||
The first payload transfer on ``DAT0`` appears when the host starts the first ``CMD53`` data transaction in 1-bit mode, so this example does contain a real 1-bit-only payload path when the user selects 1-line mode before initialization. ``DAT1-DAT3`` do not carry payload traffic until 4-bit mode has already been enabled and the first 4-bit ``CMD53`` transfer begins. Enabling 4-bit mode itself still uses command-line ``CMD52`` transactions, so 1-bit mode and 4-bit mode should be tested separately by choosing the bus width before initialization; the 4-bit flow does not first send payload in 1-bit mode.
|
||||
|
||||
When checking wiring, use the following checkpoints:
|
||||
|
||||
- If the link fails before command-only initialization completes, inspect ``CMD`` and ``CLK`` first.
|
||||
- If command-only initialization succeeds but 1-bit mode fails, inspect ``DAT0`` first.
|
||||
- If 1-bit mode works but 4-bit reads fail, inspect ``DAT1-DAT3``, pull-ups, and the return path between host and slave.
|
||||
|
||||
If the problem is caused by simultaneous switching noise (SSN), the symptoms usually look like this:
|
||||
|
||||
- 1-line mode works, while 4-line mode fails consistently or intermittently.
|
||||
- Communication fails when the boards are connected with jumper wires or other high-impedance ground paths, but improves after slightly changing the grounding or adding more ground connections, and disappears on a well-grounded setup.
|
||||
- Adjusting the data-line drive strength or adding small series resistors on the data lines reduces or removes the failure.
|
||||
- The host reports timeout or CRC errors.
|
||||
- Lowering the clock frequency has limited effect, and the issue can still appear even at 400 kHz.
|
||||
- The failure is easiest to reproduce when four data lines switch together, for example with the repeated ``FF 00`` payload used by :example:`peripherals/sdio/hw_test`.
|
||||
|
||||
To confirm this type of problem, use a logic analyzer or oscilloscope and probe as close to the slave pins as possible. The :example:`peripherals/sdio/hw_test` example is one convenient way to generate a repeatable pattern, but the same checks can also be done with any setup that can issue repeated 4-bit SDIO reads with payload that makes all four data lines switch at the same time.
|
||||
|
||||
- With a logic analyzer, capture ``CMD``, ``CLK``, and ``DAT0-DAT3``. A common symptom is data compression: some bits disappear because the slave interprets a clock glitch as an extra clock edge, so later data appears earlier than expected. With the ``hw_test`` example, the normal 4-bit read pattern is one low start bit on all four data lines, then a repeating sequence of two clocks high and two clocks low for eight cycles, followed by CRC. Missing data in this pattern indicates that the slave observed a false clock edge.
|
||||
- With an oscilloscope, observe the clock near the slave pins, especially after the data phase begins. If this issue is present, the later part of the clock waveform often becomes visibly worse. The effect can become smaller after improving grounding, adjusting drive strength, or adding small series resistors.
|
||||
- Correlate both captures. When data compression is visible on the logic analyzer, there should be a severe clock glitch shortly before the missing data point.
|
||||
|
||||
The typical fix is to improve the grounding between host and slave.
|
||||
|
||||
To reproduce this type of issue, let the host issue repeated 4-bit SDIO reads with payload that makes all four data lines switch together. The receive mode of :example:`peripherals/sdio/hw_test` is one such repeatable traffic source: its fixed ``FF 00`` frame makes all four data lines switch during every transfer.
|
||||
|
||||
One representative case happens during slave-to-host reads in 4-bit mode. When the host outputs a rising clock edge and the slave drives all four data lines from ``1`` to ``0`` at the same time, the slave ground can bounce upward. This can distort the observed clock at the slave, create a false extra edge, shorten the expected low-level width of the start bit or data bits, and finally cause a CRC error in the host driver. During practical debugging, changing the GPIO drive strength on the host or slave side may also change how severe the interference appears; :example:`peripherals/sdio/hw_test` lets the host adjust drive strength from the menu and the slave from macros.
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
|
||||
- :example:`peripherals/sdio/host` and :example:`peripherals/sdio/slave` demonstrate how to use a host to communicate with an ESP SDIO slave device.
|
||||
- :example:`peripherals/sdio/basic/host` and :example:`peripherals/sdio/basic/slave` demonstrate how to use a host to communicate with an ESP SDIO slave device.
|
||||
- :example:`peripherals/sdio/hw_test/host` and :example:`peripherals/sdio/hw_test/slave` show how to turn the SDIO host/slave pair into a signal-integrity test setup.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
@@ -124,7 +124,7 @@ Alternatively, a pre-allocated DMA-capable buffer can be provided via the :cpp:m
|
||||
|
||||
.. only:: SOC_SDMMC_HOST_SUPPORTED and SOC_SDIO_SLAVE_SUPPORTED
|
||||
|
||||
There is a component `ESSL <https://components.espressif.com/components/espressif/esp_serial_slave_link>`_ (ESP Serial Slave Link) to use if you are communicating with an ESP32 SDIO slave. See example :example:`peripherals/sdio/host`.
|
||||
There is a component `ESSL <https://components.espressif.com/components/espressif/esp_serial_slave_link>`_ (ESP Serial Slave Link) to use if you are communicating with an ESP32 SDIO slave. See example :example:`peripherals/sdio/basic/host`.
|
||||
|
||||
Combo (Memory + IO) Cards
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -153,7 +153,7 @@ SDIO 从机驱动程序的相关术语如下:
|
||||
|
||||
此外,在通过 CMD52/CMD53 访问到 Function 1 这一机制的基础上,还存在一个仅适用于 {IDF_TARGET_NAME} 的上层通信协议。该特定通信协议中,主机和从机通过 CMD52/CMD53 命令进行数据交换和通信。更多详情,请参阅 `ESP SDIO 从机协议 <https://espressif.github.io/idf-extra-components/latest/esp_serial_slave_link/sdio_slave_protocol.html#esp-sdio-slave-protocol>`_ 。
|
||||
|
||||
组件 `ESSL <https://components.espressif.com/components/espressif/esp_serial_slave_link>`_ 也支持 {IDF_TARGET_NAME} 主机与 {IDF_TARGET_NAME} SDIO 从机通信。在开发主机应用程序时,请参阅 :example:`peripherals/sdio` 中的示例。
|
||||
组件 `ESSL <https://components.espressif.com/components/espressif/esp_serial_slave_link>`_ 也支持 {IDF_TARGET_NAME} 主机与 {IDF_TARGET_NAME} SDIO 从机通信。在开发主机应用程序时,请参阅 :example:`peripherals/sdio/basic` 中的示例。
|
||||
|
||||
|
||||
.. _interrupts:
|
||||
@@ -274,7 +274,7 @@ SDIO 从机驱动程序的相关术语如下:
|
||||
sdio_slave_send_get_finished((void**)&handle, portMAX_DELAY);
|
||||
sdio_slave_recv_load_buf(handle);
|
||||
|
||||
更多详情,请参阅 :example:`peripherals/sdio`。
|
||||
更多详情,请参阅 :example:`peripherals/sdio/basic`。
|
||||
|
||||
重置 SDIO
|
||||
^^^^^^^^^^^^
|
||||
@@ -287,10 +287,45 @@ SDIO 从机驱动程序的相关术语如下:
|
||||
|
||||
重置 SDIO 硬件,中断使能状态和共享寄存器的值会丢失,可能需要调用 ``sdio_slave_set_host_intena``、 ``sdio_slave_write_reg`` 设置。
|
||||
|
||||
信号质量检查
|
||||
^^^^^^^^^^^^
|
||||
|
||||
对于 SDIO 从机链路,即使一条或多条数据线接错、未连接或者信号质量较差,仅依赖命令线的初始化过程仍然可能成功。在 ESP-IDF 的主机协议栈中,SDIO 卡识别和配置阶段主要使用 ``CMD0``、``CMD5`` 和 ``CMD52``,这些事务都走命令线。例如,:example:`peripherals/sdio/hw_test` 中的 host 端可以先完成 ``sdmmc_card_init()`` 和它自己的 ``enable_slave_function()`` 步骤,此时还没有在 ``DAT0-DAT3`` 上开始 payload 传输。
|
||||
|
||||
``DAT0`` 上第一次真正的 payload 传输,出现在 host 以 1 线模式发起第一笔 ``CMD53`` 数据事务时,因此这个 example 确实存在只用 ``DAT0`` 传 payload 的情况,但前提是用户在初始化前明确选择 1 线模式。``DAT1-DAT3`` 只有在已经切换到 4 线模式之后,第一笔 4 线 ``CMD53`` 事务开始时,才会真正承载 payload 数据。切换到 4 线模式本身仍然通过命令线上的 ``CMD52`` 完成,因此应分别测试 1 线模式和 4 线模式,在初始化前选择总线宽度,而不是先跑 1 线再在同一流程中切到 4 线;4 线流程本身不会先用 1 线传 payload。
|
||||
|
||||
排查接线时,可以先看以下几个检查点:
|
||||
|
||||
- 如果在仅依赖命令线的初始化完成之前就失败,优先检查 ``CMD`` 和 ``CLK``。
|
||||
- 如果仅依赖命令线的初始化成功,但 1 线模式失败,优先检查 ``DAT0``。
|
||||
- 如果 1 线模式正常而 4 线读失败,优先检查 ``DAT1-DAT3``、上拉以及 host 与 slave 之间的回流路径。
|
||||
|
||||
如果问题由 simultaneous switching noise (SSN) 引起,现象通常如下:
|
||||
|
||||
- 1 线模式正常,而 4 线模式稳定失败或者概率性失败。
|
||||
- 使用杜邦线等接地阻抗较高的连接方式时通信失败,微调接地或增加地线后问题缓解或消失;接地良好的系统中问题消失。
|
||||
- 调整数据线驱动强度,或者在数据线上串联小电阻后,问题缓解或消失。
|
||||
- host 侧报错常见为 timeout 或 CRC error。
|
||||
- 降低时钟频率影响有限,即使降低到 400 kHz 仍可能出现问题。
|
||||
- 当四条数据线同时切换时问题最容易复现,例如 :example:`peripherals/sdio/hw_test` 中重复的 ``FF 00`` payload。
|
||||
|
||||
要确认是否存在这类问题,建议使用逻辑分析仪或示波器,并尽量在靠近 slave 管脚的位置取样。:example:`peripherals/sdio/hw_test` 是一种方便的可复现流量来源,但并不必须依赖该示例;任何能够重复执行 4 线 SDIO 读操作、并且数据会在四条线上同时切换的系统,都可以用来完成相同检查。
|
||||
|
||||
- 使用逻辑分析仪时,抓取 ``CMD``、``CLK`` 和 ``DAT0-DAT3``。一个典型现象是数据长度被压缩:由于 slave 把时钟毛刺误认为额外时钟沿,某些位会丢失,后续数据提前出现。若使用 ``hw_test`` 示例,正常的 4 线读数据应当表现为四条数据线同时出现 1 位低电平起始位,之后是 2 个时钟高电平、2 个时钟低电平的循环,共 8 轮,最后跟 CRC。如果这段模式里出现数据缺失,就说明 slave 观察到了错误时钟边沿。
|
||||
- 使用示波器时,在靠近 slave 管脚的位置观察时钟,尤其是数据开始出现之后的后半段。如果问题存在,时钟波形在后段通常会明显变差。改善接地、调整驱动强度或者串联小电阻后,这种现象往往会减轻。
|
||||
- 把两种抓图结果交叉比对:如果逻辑分析仪看到数据被压缩,那么在数据缺失点之前,示波器上通常可以看到明显的时钟毛刺。
|
||||
|
||||
解决这类问题的典型方法,是改善 host 与 slave 之间的接地。
|
||||
|
||||
若要复现这类问题,应让 host 反复执行 4 线读,并使用会在四条数据线上同时切换的 payload。:example:`peripherals/sdio/hw_test` 的 receive 模式就是这样一种可重复流量来源,其固定的 ``FF 00`` 帧会在每个传输周期让四条数据线同时切换。
|
||||
|
||||
一个典型案例发生在 4 线模式的 slave-to-host 读操作中。当 host 输出时钟上升沿,同时 slave 让四条数据线从 ``1`` 同时切换到 ``0`` 时,slave 侧地电平可能被抬高。这会让 slave 看到的时钟波形发生下陷并产生伪额外边沿,导致起始位或者数据位的预期低电平宽度变短,最终让 host 驱动报告 CRC 错误。实际排查时,调整 host 或 slave 侧的 GPIO 驱动强度也可能改变干扰表现的严重程度;:example:`peripherals/sdio/hw_test` 允许 host 通过菜单、slave 通过宏来尝试不同驱动强度。
|
||||
|
||||
应用示例
|
||||
--------
|
||||
|
||||
- :example:`peripherals/sdio/host` 和 :example:`peripherals/sdio/slave` 演示了如何使用主机与 ESP SDIO 从机进行通信。
|
||||
- :example:`peripherals/sdio/basic/host` 和 :example:`peripherals/sdio/basic/slave` 演示了如何使用主机与 ESP SDIO 从机进行通信。
|
||||
- :example:`peripherals/sdio/hw_test/host` 和 :example:`peripherals/sdio/hw_test/slave` 展示了如何将 SDIO 主从示例改造成信号完整性测试组合。
|
||||
|
||||
API 参考
|
||||
-------------
|
||||
|
||||
@@ -124,7 +124,7 @@ SD/SDIO/MMC 驱动支持 SD 存储器、SDIO 卡和 eMMC 芯片。这是一个
|
||||
|
||||
.. only:: SOC_SDMMC_HOST_SUPPORTED and SOC_SDIO_SLAVE_SUPPORTED
|
||||
|
||||
如果需要与 ESP32 的 SDIO 从设备通信,请使用 `ESSL <https://components.espressif.com/components/espressif/esp_serial_slave_link>`_ 组件(ESP 串行从设备链接)。请参阅示例 :example:`peripherals/sdio/host`。
|
||||
如果需要与 ESP32 的 SDIO 从设备通信,请使用 `ESSL <https://components.espressif.com/components/espressif/esp_serial_slave_link>`_ 组件(ESP 串行从设备链接)。请参阅示例 :example:`peripherals/sdio/basic/host`。
|
||||
|
||||
复合卡(存储 + IO)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -585,7 +585,7 @@ examples/peripherals/rmt/stepper_motor:
|
||||
- esp_driver_rmt
|
||||
- soc
|
||||
|
||||
examples/peripherals/sdio/host:
|
||||
examples/peripherals/sdio/basic/host:
|
||||
disable:
|
||||
- if: IDF_TARGET == "esp32p4"
|
||||
temporary: true
|
||||
@@ -604,7 +604,7 @@ examples/peripherals/sdio/host:
|
||||
- esp_driver_sdio
|
||||
- soc
|
||||
|
||||
examples/peripherals/sdio/slave:
|
||||
examples/peripherals/sdio/basic/slave:
|
||||
disable:
|
||||
- if: SOC_SDIO_SLAVE_SUPPORTED != 1
|
||||
disable_test:
|
||||
@@ -616,6 +616,30 @@ examples/peripherals/sdio/slave:
|
||||
- esp_driver_sdio
|
||||
- soc
|
||||
|
||||
examples/peripherals/sdio/hw_test/host:
|
||||
disable:
|
||||
- if: SOC_SDMMC_HOST_SUPPORTED != 1
|
||||
disable_test:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32p4"]
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
depends_components:
|
||||
- esp_hal_sd
|
||||
- sdmmc
|
||||
- esp_driver_sdmmc
|
||||
- soc
|
||||
|
||||
examples/peripherals/sdio/hw_test/slave:
|
||||
disable:
|
||||
- if: SOC_SDIO_SLAVE_SUPPORTED != 1
|
||||
disable_test:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32c5", "esp32c6", "esp32c61"]
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
depends_components:
|
||||
- esp_driver_sdio
|
||||
- soc
|
||||
|
||||
examples/peripherals/sigma_delta:
|
||||
disable:
|
||||
- if: SOC_SDM_SUPPORTED != 1
|
||||
|
||||
@@ -59,7 +59,7 @@ disconnected, the config options are still valid, and the host example will stil
|
||||
slave meet the "all pins should be pulled up" requirement.
|
||||
|
||||
For the SDIO Slave, CMD and DAT0-3 lines require to be pulled up (suggested resistor value: 10 KOhm) even in 1-bit mode
|
||||
or SPI mode, which is required by the SD specification. See *Board Compability* below for details.
|
||||
or SPI mode, which is required by the SD specification. See *Board Compatibility* 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.
|
||||
@@ -128,12 +128,12 @@ modules and devkits.
|
||||
|
||||
## About `esp_serial_slave_link` component used in the host example
|
||||
|
||||
The host example is based on [esp_serial_slave_link component](https://components.espressif.com/components/espressif/esp_serial_slave_link), which is used to communicate to a ESP slave device.
|
||||
The host example is based on the [esp_serial_slave_link component](https://components.espressif.com/components/espressif/esp_serial_slave_link), which is used to communicate with an ESP slave device.
|
||||
|
||||
The component can be installed by esp component manager. Since this example already installed it, no need to re-installed it again, but if you want to install this component in your own project, you can input the following command:
|
||||
The component can be installed by the ESP component manager. Since this example already installs it, there is no need to install it again. If you want to use this component in your own project, run the following command:
|
||||
|
||||
```
|
||||
idf.py add-dependency espressif/esp_serial_slave_link
|
||||
```
|
||||
|
||||
If the dependency is added, you can check `idf_component.yml` for more detail. When building this example or other projects with managed components, the component manager will search for the required components online and download them into the `managed_componets` folder.
|
||||
If the dependency is added, you can check `idf_component.yml` for more details. When building this example or other projects with managed components, the component manager will search for the required components online and download them into the `managed_components` folder.
|
||||
@@ -1,11 +1,8 @@
|
||||
/* SDIO example, host (uses sdmmc_host/sdspi_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.
|
||||
*/
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
@@ -1,11 +1,8 @@
|
||||
/* 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.
|
||||
*/
|
||||
/*
|
||||
* 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"
|
||||
176
examples/peripherals/sdio/hw_test/README.md
Normal file
176
examples/peripherals/sdio/hw_test/README.md
Normal file
@@ -0,0 +1,176 @@
|
||||
### SDIO Channel Test Example
|
||||
|
||||
## Introduction
|
||||
|
||||
This example pair turns the SDIO host/slave examples into a channel test app
|
||||
for lab work.
|
||||
|
||||
Use it when you want to:
|
||||
|
||||
- check whether a board pair can communicate over SDIO,
|
||||
- compare 1-line and 4-line transfer modes,
|
||||
- compare default-speed and high-speed clock settings,
|
||||
- generate repeated traffic that is easy to capture with a scope or logic
|
||||
analyzer,
|
||||
- validate that the received data still matches a known fixed pattern.
|
||||
|
||||
The `host` app provides an interactive serial menu:
|
||||
|
||||
1. Select 1-line or 4-line mode and select default-speed or high-speed clock.
|
||||
2. Start link initialization.
|
||||
3. Enter transmit mode or receive mode.
|
||||
4. Run one transfer per second until `z` is pressed to return to the menu.
|
||||
|
||||
The `slave` app is a passive endpoint. Once initialized, it keeps enough
|
||||
buffers ready so the host can repeatedly send or receive a fixed 16-byte test
|
||||
frame without an extra handshake before each round.
|
||||
|
||||
The fixed test frame is:
|
||||
|
||||
`FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00`
|
||||
|
||||
Both sides validate received data against this exact sequence and print a
|
||||
success or failure log for each completed transfer.
|
||||
|
||||
## Wiring
|
||||
|
||||
Connect the host and slave boards exactly as described in the SDIO programming
|
||||
guide and the basic SDIO example for the selected targets.
|
||||
|
||||
For this signal-integrity test, connect all SDIO signal lines between the two
|
||||
boards and always provide a solid common ground. Keep the wires short and avoid
|
||||
loose return paths.
|
||||
|
||||
If link initialization fails, the host deinitializes the bus and returns to the
|
||||
setup menu so the user can choose a different width/speed combination.
|
||||
|
||||
## Signal Integrity Notes
|
||||
|
||||
This example can exercise either a single active data lane or four active data
|
||||
lanes. The waveforms below are conceptual. They show the fixed `FF 00`
|
||||
payload pattern used by this example and intentionally omit the exact CRC bits.
|
||||
|
||||
### 1-line mode
|
||||
|
||||
```text
|
||||
CLK : _|-|_|-|_|-|_|-|_|-|_|-|_|-|_
|
||||
CMD : -----------------------------
|
||||
DAT0 : _|--------________--------________...
|
||||
DAT1 : -----------------------------
|
||||
DAT2 : -----------------------------
|
||||
DAT3 : -----------------------------
|
||||
^start 0xFF 0x00 0xFF 0x00
|
||||
```
|
||||
|
||||
### 4-line mode
|
||||
|
||||
```text
|
||||
CLK : _|-|_|-|_|-|_|-|_|-|_|-|_|-|_
|
||||
CMD : -----------------------------
|
||||
DAT0 : _|--__--__--__--__--__--__...
|
||||
DAT1 : _|--__--__--__--__--__--__...
|
||||
DAT2 : _|--__--__--__--__--__--__...
|
||||
DAT3 : _|--__--__--__--__--__--__...
|
||||
^start FF 00 FF 00 ...
|
||||
```
|
||||
|
||||
In 4-line mode, the repeated `FF 00` frame makes all four data lines switch
|
||||
at the same time during the `0xF -> 0x0` and `0x0 -> 0xF` nibble
|
||||
transitions. This is useful when checking simultaneous switching noise and
|
||||
grounding quality.
|
||||
|
||||
To confirm the wiring before analyzing signal quality:
|
||||
|
||||
1. If CMD and CLK are correct, the host should complete the command-only
|
||||
initialization path and print `SDIO link initialized successfully`. In this
|
||||
example, both `sdmmc_card_init()` and `enable_slave_function()` still use
|
||||
only CMD-based transactions before the first data transfer starts.
|
||||
2. If DAT0 is correct, 1-line mode should work. This example does have a real
|
||||
1-line-only data phase when you choose `1-line`: the first data-line
|
||||
transfer is the first CMD53 transaction after initialization succeeds.
|
||||
3. If DAT1-DAT3 are connected but 4-line mode still fails, use a logic analyzer
|
||||
or oscilloscope to check whether the slave is responding on DAT1-DAT3 during
|
||||
4-line reads. The 4-line flow does not first send payload in 1-line mode, so
|
||||
a 4-line failure after a passing 1-line test points to DAT1-DAT3. No
|
||||
response usually indicates wiring or pull-up issues;
|
||||
response with corrupted data usually indicates signal-quality issues.
|
||||
|
||||
Use a logic analyzer or oscilloscope when checking these nodes, and make sure
|
||||
the pull-ups and board-specific SDIO requirements are correct. For more
|
||||
signal-quality analysis and mitigation guidance, see the SDIO programming guide.
|
||||
|
||||
## How To Use
|
||||
|
||||
## 1. Build And Flash
|
||||
|
||||
Flash the `host` app to the host-capable board and the `slave` app to the
|
||||
slave-capable board.
|
||||
|
||||
For example, in a manual setup with ESP32 host and ESP32-C6 slave:
|
||||
|
||||
- flash `examples/peripherals/sdio/hw_test/host` to the ESP32 host board
|
||||
- flash `examples/peripherals/sdio/hw_test/slave` to the ESP32-C6 slave board
|
||||
|
||||
## 2. Open The Host Console
|
||||
|
||||
After reset, the host prints a setup menu. Use it to select:
|
||||
|
||||
- line width: 1-line or 4-line
|
||||
- speed: default-speed or high-speed
|
||||
|
||||
Then start SDIO initialization.
|
||||
|
||||
If initialization fails, the host prints the error and returns to the setup
|
||||
menu.
|
||||
|
||||
## 3. Choose The Traffic Direction
|
||||
|
||||
Once the link is up, use the second menu:
|
||||
|
||||
- transmit mode: host sends the fixed 16-byte pattern once per second
|
||||
- receive mode: host reads the fixed 16-byte pattern once per second
|
||||
- `z`: stop the current loop and return to the second menu
|
||||
|
||||
No extra synchronization is performed before each round. The slave is expected
|
||||
to be ready already.
|
||||
|
||||
## 4. Adjust Drive Strength (Optional)
|
||||
|
||||
If you want to compare how drive strength affects the observed waveforms or
|
||||
error rate, change it before starting link initialization:
|
||||
|
||||
- on the host, use the setup menu keys `k`, `m`, and `g` to configure CLK,
|
||||
CMD, and DATA drive strength
|
||||
- on the slave, edit `SLAVE_CLK_DRIVE_CAP`, `SLAVE_CMD_DRIVE_CAP`, and
|
||||
`SLAVE_DATA_DRIVE_CAP` in `slave/main/app_main.c`, then rebuild and flash
|
||||
|
||||
Both sides print the active drive-strength settings during startup.
|
||||
|
||||
## 5. Check The Logs
|
||||
|
||||
Watch for:
|
||||
|
||||
- initialization success or failure,
|
||||
- transfer success or driver error codes,
|
||||
- pattern validation success or failure on both boards.
|
||||
|
||||
The transfer interval is intentionally slow so repeated waveforms are easy to
|
||||
capture.
|
||||
|
||||
## Pytest Coverage
|
||||
|
||||
`pytest_sdio_hw_test.py` drives the same host serial menu that a user sees.
|
||||
|
||||
The automated pytest cases exercise these width/speed combinations on the
|
||||
available SDIO multi-device runners:
|
||||
|
||||
- 1-line + default-speed
|
||||
- 1-line + high-speed
|
||||
- 4-line + default-speed
|
||||
- 4-line + high-speed
|
||||
|
||||
For each combination, the test enters both host transmit mode and host receive
|
||||
mode, waits for 5 successful transfers, and fails immediately if any transfer
|
||||
or pattern-validation error log appears. Other SDIO-capable board pairs can
|
||||
still use this example manually if their wiring and pin assignments follow the
|
||||
SDIO programming guide.
|
||||
8
examples/peripherals/sdio/hw_test/host/CMakeLists.txt
Normal file
8
examples/peripherals/sdio/hw_test/host/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(sdio_si_test)
|
||||
5
examples/peripherals/sdio/hw_test/host/README.md
Normal file
5
examples/peripherals/sdio/hw_test/host/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
| Supported Targets | ESP32 | ESP32-P4 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | -------- | --------- |
|
||||
|
||||
For wiring, setup flow, and signal-integrity guidance, see `README.md` in the
|
||||
parent folder.
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "app_main.c"
|
||||
PRIV_REQUIRES esp_driver_gpio esp_driver_sdmmc esp_driver_uart
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,51 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
|
||||
|
||||
config EXAMPLE_ADJUSTABLE_PIN
|
||||
bool
|
||||
default SOC_SDMMC_USE_GPIO_MATRIX
|
||||
|
||||
config EXAMPLE_PIN_CMD
|
||||
int "CMD (MOSI) GPIO number"
|
||||
depends on EXAMPLE_ADJUSTABLE_PIN
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 4 if IDF_TARGET_ESP32P4
|
||||
default 15
|
||||
|
||||
config EXAMPLE_PIN_CLK
|
||||
int "CLK GPIO number"
|
||||
depends on EXAMPLE_ADJUSTABLE_PIN
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 33 if IDF_TARGET_ESP32P4
|
||||
default 14
|
||||
|
||||
config EXAMPLE_PIN_D0
|
||||
int "D0 (MISO) GPIO number"
|
||||
depends on EXAMPLE_ADJUSTABLE_PIN
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 32 if IDF_TARGET_ESP32P4
|
||||
default 2
|
||||
|
||||
config EXAMPLE_PIN_D1
|
||||
int "D1 (INTR) GPIO number"
|
||||
depends on EXAMPLE_ADJUSTABLE_PIN
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 23 if IDF_TARGET_ESP32P4
|
||||
default 4
|
||||
|
||||
config EXAMPLE_PIN_D2
|
||||
int "D2 GPIO number"
|
||||
depends on EXAMPLE_ADJUSTABLE_PIN
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 53 if IDF_TARGET_ESP32P4
|
||||
default 12
|
||||
|
||||
config EXAMPLE_PIN_D3
|
||||
int "D3 (CS) GPIO number"
|
||||
depends on EXAMPLE_ADJUSTABLE_PIN
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 5 if IDF_TARGET_ESP32P4
|
||||
default 13
|
||||
|
||||
endmenu
|
||||
674
examples/peripherals/sdio/hw_test/host/main/app_main.c
Normal file
674
examples/peripherals/sdio/hw_test/host/main/app_main.c
Normal file
@@ -0,0 +1,674 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_log_buffer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "soc/sdmmc_pins.h"
|
||||
|
||||
static const char *TAG = "example_host";
|
||||
|
||||
#define TEST_FRAME_LEN 16
|
||||
#define SDIO_BUFFER_SIZE 128
|
||||
#define LOOP_INTERVAL_MS 1000
|
||||
#define INIT_RETRY_COUNT 5
|
||||
#define INIT_RETRY_DELAY_MS 1000
|
||||
#define IO_TIMEOUT_MS 1000
|
||||
#define POLL_SLICE_MS 50
|
||||
#define DEBUG_FRAME_LOG_LIMIT 8
|
||||
#define SDIO_FUNC_NUM 1
|
||||
#define SDIO_FUNC1_EN_MASK BIT(1)
|
||||
#define SDIO_DATA_END_ADDR 0x1f800
|
||||
#define DRIVE_CAP_KEEP_DEFAULT (-1)
|
||||
|
||||
#if CONFIG_EXAMPLE_ADJUSTABLE_PIN
|
||||
#define PIN_CLK CONFIG_EXAMPLE_PIN_CLK
|
||||
#define PIN_CMD CONFIG_EXAMPLE_PIN_CMD
|
||||
#define PIN_D0 CONFIG_EXAMPLE_PIN_D0
|
||||
#define PIN_D1 CONFIG_EXAMPLE_PIN_D1
|
||||
#define PIN_D2 CONFIG_EXAMPLE_PIN_D2
|
||||
#define PIN_D3 CONFIG_EXAMPLE_PIN_D3
|
||||
#else
|
||||
#define PIN_CLK SDMMC_SLOT1_IOMUX_PIN_NUM_CLK
|
||||
#define PIN_CMD SDMMC_SLOT1_IOMUX_PIN_NUM_CMD
|
||||
#define PIN_D0 SDMMC_SLOT1_IOMUX_PIN_NUM_D0
|
||||
#define PIN_D1 SDMMC_SLOT1_IOMUX_PIN_NUM_D1
|
||||
#define PIN_D2 SDMMC_SLOT1_IOMUX_PIN_NUM_D2
|
||||
#define PIN_D3 SDMMC_SLOT1_IOMUX_PIN_NUM_D3
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LINK_WIDTH_1BIT = 1,
|
||||
LINK_WIDTH_4BIT = 4,
|
||||
} link_width_t;
|
||||
|
||||
typedef enum {
|
||||
LINK_SPEED_PROBING = 0,
|
||||
LINK_SPEED_DEFAULT,
|
||||
LINK_SPEED_HIGH,
|
||||
} link_speed_t;
|
||||
|
||||
typedef struct {
|
||||
int clk_drive;
|
||||
int cmd_drive;
|
||||
int data_drive;
|
||||
} drive_strength_config_t;
|
||||
|
||||
typedef struct {
|
||||
link_width_t width;
|
||||
link_speed_t speed;
|
||||
drive_strength_config_t drive_strength;
|
||||
} link_config_t;
|
||||
|
||||
typedef struct {
|
||||
sdmmc_card_t *card;
|
||||
bool host_initialized;
|
||||
bool link_ready;
|
||||
} host_link_t;
|
||||
|
||||
static const uint8_t s_pattern[TEST_FRAME_LEN] = {
|
||||
0xFF, 0x00, 0xFF, 0x00,
|
||||
0xFF, 0x00, 0xFF, 0x00,
|
||||
0xFF, 0x00, 0xFF, 0x00,
|
||||
0xFF, 0x00, 0xFF, 0x00,
|
||||
};
|
||||
|
||||
static DRAM_DMA_ALIGNED_ATTR uint8_t s_tx_frame[TEST_FRAME_LEN];
|
||||
static DRAM_DMA_ALIGNED_ATTR uint8_t s_rx_frame[TEST_FRAME_LEN];
|
||||
|
||||
static int read_console_key_blocking(void);
|
||||
|
||||
static const char *link_width_name(link_width_t width)
|
||||
{
|
||||
return (width == LINK_WIDTH_4BIT) ? "4-line" : "1-line";
|
||||
}
|
||||
|
||||
static const char *link_speed_name(link_speed_t speed)
|
||||
{
|
||||
switch (speed) {
|
||||
case LINK_SPEED_PROBING:
|
||||
return "400K";
|
||||
case LINK_SPEED_HIGH:
|
||||
return "high-speed";
|
||||
case LINK_SPEED_DEFAULT:
|
||||
default:
|
||||
return "default-speed";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *drive_strength_name(int value)
|
||||
{
|
||||
switch (value) {
|
||||
case DRIVE_CAP_KEEP_DEFAULT:
|
||||
return "default";
|
||||
case GPIO_DRIVE_CAP_0:
|
||||
return "cap0";
|
||||
case GPIO_DRIVE_CAP_1:
|
||||
return "cap1";
|
||||
case GPIO_DRIVE_CAP_2:
|
||||
return "cap2";
|
||||
case GPIO_DRIVE_CAP_3:
|
||||
return "cap3";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static int prompt_drive_strength_selection(const char *label, int current_value)
|
||||
{
|
||||
printf("\nSelect %s drive strength:\n", label);
|
||||
printf(" n - keep default\n");
|
||||
printf(" 0 - GPIO_DRIVE_CAP_0\n");
|
||||
printf(" 1 - GPIO_DRIVE_CAP_1\n");
|
||||
printf(" 2 - GPIO_DRIVE_CAP_2\n");
|
||||
printf(" 3 - GPIO_DRIVE_CAP_3\n");
|
||||
printf("Current: %s\n", drive_strength_name(current_value));
|
||||
fflush(stdout);
|
||||
|
||||
switch (read_console_key_blocking()) {
|
||||
case 'n':
|
||||
return DRIVE_CAP_KEEP_DEFAULT;
|
||||
case '0':
|
||||
return GPIO_DRIVE_CAP_0;
|
||||
case '1':
|
||||
return GPIO_DRIVE_CAP_1;
|
||||
case '2':
|
||||
return GPIO_DRIVE_CAP_2;
|
||||
case '3':
|
||||
return GPIO_DRIVE_CAP_3;
|
||||
default:
|
||||
printf("Unknown selection, keeping previous value\n");
|
||||
fflush(stdout);
|
||||
return current_value;
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_test_pattern(uint8_t *buffer, size_t len)
|
||||
{
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
buffer[i] = s_pattern[i % TEST_FRAME_LEN];
|
||||
}
|
||||
}
|
||||
|
||||
static bool validate_test_pattern(const uint8_t *buffer, size_t len)
|
||||
{
|
||||
if (len != TEST_FRAME_LEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return memcmp(buffer, s_pattern, TEST_FRAME_LEN) == 0;
|
||||
}
|
||||
|
||||
static int find_pattern_mismatch(const uint8_t *buffer, size_t len)
|
||||
{
|
||||
size_t compare_len = (len < TEST_FRAME_LEN) ? len : TEST_FRAME_LEN;
|
||||
for (size_t i = 0; i < compare_len; ++i) {
|
||||
if (buffer[i] != s_pattern[i]) {
|
||||
return (int)i;
|
||||
}
|
||||
}
|
||||
if (len != TEST_FRAME_LEN) {
|
||||
return (int)compare_len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void log_frame_debug(const char *label, uint32_t frame_index, const uint8_t *buffer, size_t len)
|
||||
{
|
||||
ESP_LOGI(TAG, "%s frame=%" PRIu32 " len=%u", label, frame_index, (unsigned)len);
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, len, ESP_LOG_INFO);
|
||||
}
|
||||
|
||||
static void log_gpio_drive_capability(const char *label, gpio_num_t pin)
|
||||
{
|
||||
gpio_drive_cap_t capability = GPIO_DRIVE_CAP_DEFAULT;
|
||||
esp_err_t err = gpio_get_drive_capability(pin, &capability);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "%s GPIO%d drive capability: %s (%d)", label, pin, drive_strength_name(capability), capability);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to read drive capability for GPIO%d: %s", pin, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
static void log_host_drive_strengths(link_width_t width, const char *label)
|
||||
{
|
||||
log_gpio_drive_capability(label, PIN_CLK);
|
||||
log_gpio_drive_capability(label, PIN_CMD);
|
||||
log_gpio_drive_capability(label, PIN_D0);
|
||||
if (width == LINK_WIDTH_4BIT) {
|
||||
log_gpio_drive_capability(label, PIN_D1);
|
||||
log_gpio_drive_capability(label, PIN_D2);
|
||||
log_gpio_drive_capability(label, PIN_D3);
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t apply_gpio_drive_strength(gpio_num_t pin, int drive_strength)
|
||||
{
|
||||
if (drive_strength == DRIVE_CAP_KEEP_DEFAULT) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return gpio_set_drive_capability(pin, (gpio_drive_cap_t)drive_strength);
|
||||
}
|
||||
|
||||
static esp_err_t apply_host_drive_strengths(const link_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_CLK, config->drive_strength.clk_drive), TAG, "set CLK drive failed");
|
||||
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_CMD, config->drive_strength.cmd_drive), TAG, "set CMD drive failed");
|
||||
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_D0, config->drive_strength.data_drive), TAG, "set D0 drive failed");
|
||||
if (config->width == LINK_WIDTH_4BIT) {
|
||||
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_D1, config->drive_strength.data_drive), TAG, "set D1 drive failed");
|
||||
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_D2, config->drive_strength.data_drive), TAG, "set D2 drive failed");
|
||||
ESP_RETURN_ON_ERROR(apply_gpio_drive_strength(PIN_D3, config->drive_strength.data_drive), TAG, "set D3 drive failed");
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t enable_slave_function(sdmmc_card_t *card)
|
||||
{
|
||||
uint8_t fn_enable = 0;
|
||||
ESP_RETURN_ON_ERROR(sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_ENABLE, &fn_enable), TAG, "read FN_ENABLE failed");
|
||||
|
||||
fn_enable |= SDIO_FUNC1_EN_MASK;
|
||||
ESP_RETURN_ON_ERROR(sdmmc_io_write_byte(card, 0, SD_IO_CCCR_FN_ENABLE, fn_enable, NULL), TAG, "write FN_ENABLE failed");
|
||||
|
||||
uint8_t fn_ready = 0;
|
||||
TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(IO_TIMEOUT_MS);
|
||||
do {
|
||||
esp_err_t err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &fn_ready);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
if ((fn_ready & SDIO_FUNC1_EN_MASK) != 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(POLL_SLICE_MS));
|
||||
} while (xTaskGetTickCount() < deadline);
|
||||
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
static esp_err_t send_one_frame(host_link_t *link, const uint8_t *buffer, size_t len)
|
||||
{
|
||||
uint32_t addr = SDIO_DATA_END_ADDR - len;
|
||||
return sdmmc_io_write_bytes(link->card, SDIO_FUNC_NUM, addr, buffer, len);
|
||||
}
|
||||
|
||||
static esp_err_t receive_one_frame(host_link_t *link, uint8_t *buffer, size_t *out_len)
|
||||
{
|
||||
esp_err_t err = sdmmc_io_read_bytes(link->card, SDIO_FUNC_NUM, SDIO_DATA_END_ADDR - TEST_FRAME_LEN, buffer, TEST_FRAME_LEN);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
*out_len = TEST_FRAME_LEN;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void slave_power_on(void)
|
||||
{
|
||||
gpio_config_t gpio_cfg = {
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pin_bit_mask = 1ULL << GPIO_NUM_18,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&gpio_cfg));
|
||||
ESP_LOGI(TAG, "power on slave");
|
||||
gpio_set_level(GPIO_NUM_18, 1);
|
||||
}
|
||||
|
||||
static void configure_console(void)
|
||||
{
|
||||
if (uart_is_driver_installed((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
ESP_ERROR_CHECK(uart_driver_install((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0));
|
||||
}
|
||||
|
||||
static int read_console_key_with_timeout(int timeout_ms)
|
||||
{
|
||||
uint8_t byte = 0;
|
||||
int elapsed_ms = 0;
|
||||
|
||||
while (elapsed_ms <= timeout_ms) {
|
||||
int wait_ms = (timeout_ms == 0) ? 0 : POLL_SLICE_MS;
|
||||
if (timeout_ms > 0 && (timeout_ms - elapsed_ms) < wait_ms) {
|
||||
wait_ms = timeout_ms - elapsed_ms;
|
||||
}
|
||||
|
||||
int read_len = uart_read_bytes((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM,
|
||||
&byte,
|
||||
1,
|
||||
pdMS_TO_TICKS(wait_ms));
|
||||
if (read_len > 0) {
|
||||
if (byte == '\r' || byte == '\n') {
|
||||
continue;
|
||||
}
|
||||
return tolower(byte);
|
||||
}
|
||||
|
||||
if (timeout_ms == 0) {
|
||||
break;
|
||||
}
|
||||
elapsed_ms += wait_ms;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int read_console_key_blocking(void)
|
||||
{
|
||||
while (true) {
|
||||
uint8_t byte = 0;
|
||||
int read_len = uart_read_bytes((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM,
|
||||
&byte,
|
||||
1,
|
||||
portMAX_DELAY);
|
||||
if (read_len > 0) {
|
||||
if (byte == '\r' || byte == '\n') {
|
||||
continue;
|
||||
}
|
||||
return tolower(byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool wait_for_stop_key(void)
|
||||
{
|
||||
for (int elapsed = 0; elapsed < LOOP_INTERVAL_MS; elapsed += POLL_SLICE_MS) {
|
||||
int key = read_console_key_with_timeout(0);
|
||||
if (key == 'z') {
|
||||
return true;
|
||||
}
|
||||
if (key >= 0) {
|
||||
ESP_LOGW(TAG, "Ignoring key '%c' while transfer loop is running. Press z to stop.", key);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(POLL_SLICE_MS));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void print_setup_menu(const link_config_t *config)
|
||||
{
|
||||
printf("\n=== SDIO Link Setup ===\n");
|
||||
printf("Current width: %s\n", link_width_name(config->width));
|
||||
printf("Current speed: %s\n", link_speed_name(config->speed));
|
||||
printf("CLK drive: %s\n", drive_strength_name(config->drive_strength.clk_drive));
|
||||
printf("CMD drive: %s\n", drive_strength_name(config->drive_strength.cmd_drive));
|
||||
printf("DATA drive: %s\n", drive_strength_name(config->drive_strength.data_drive));
|
||||
printf("Select bus width:\n");
|
||||
printf(" 1 - 1-line\n");
|
||||
printf(" 4 - 4-line\n");
|
||||
printf("Select clock mode:\n");
|
||||
printf(" p - 400K\n");
|
||||
printf(" d - default-speed\n");
|
||||
printf(" h - high-speed\n");
|
||||
printf("Drive strength:\n");
|
||||
printf(" k - configure CLK drive\n");
|
||||
printf(" m - configure CMD drive\n");
|
||||
printf(" g - configure DATA drive\n");
|
||||
printf("Press s to start link initialization\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void print_traffic_menu(const link_config_t *config)
|
||||
{
|
||||
printf("\n=== SDIO Traffic Menu ===\n");
|
||||
printf("Current link: %s, %s\n", link_width_name(config->width), link_speed_name(config->speed));
|
||||
printf("Press t for transmit mode\n");
|
||||
printf("Press r for receive mode\n");
|
||||
printf("Press x to close the link\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void enable_bus_pullups(link_width_t width)
|
||||
{
|
||||
const gpio_num_t pins_1bit[] = {PIN_CMD, PIN_CLK, PIN_D0};
|
||||
const gpio_num_t pins_4bit[] = {PIN_CMD, PIN_CLK, PIN_D0, PIN_D1, PIN_D2, PIN_D3};
|
||||
const gpio_num_t *pins = (width == LINK_WIDTH_4BIT) ? pins_4bit : pins_1bit;
|
||||
size_t pin_count = (width == LINK_WIDTH_4BIT)
|
||||
? sizeof(pins_4bit) / sizeof(pins_4bit[0])
|
||||
: sizeof(pins_1bit) / sizeof(pins_1bit[0]);
|
||||
|
||||
for (size_t i = 0; i < pin_count; ++i) {
|
||||
gpio_pullup_en(pins[i]);
|
||||
gpio_pulldown_dis(pins[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void host_link_deinit(host_link_t *link)
|
||||
{
|
||||
if (link == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (link->card != NULL && link->card->host.dma_aligned_buffer != NULL) {
|
||||
free(link->card->host.dma_aligned_buffer);
|
||||
link->card->host.dma_aligned_buffer = NULL;
|
||||
}
|
||||
|
||||
if (link->host_initialized) {
|
||||
esp_err_t err = sdmmc_host_deinit();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "sdmmc_host_deinit failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
free(link->card);
|
||||
memset(link, 0, sizeof(*link));
|
||||
}
|
||||
|
||||
static esp_err_t host_link_init(const link_config_t *config, host_link_t *link)
|
||||
{
|
||||
esp_err_t err;
|
||||
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
|
||||
host.flags = ((config->width == LINK_WIDTH_4BIT) ? SDMMC_HOST_FLAG_4BIT : SDMMC_HOST_FLAG_1BIT)
|
||||
| SDMMC_HOST_FLAG_ALLOC_ALIGNED_BUF;
|
||||
switch (config->speed) {
|
||||
case LINK_SPEED_PROBING:
|
||||
host.max_freq_khz = SDMMC_FREQ_PROBING;
|
||||
break;
|
||||
case LINK_SPEED_HIGH:
|
||||
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
|
||||
break;
|
||||
case LINK_SPEED_DEFAULT:
|
||||
default:
|
||||
host.max_freq_khz = SDMMC_FREQ_DEFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
slot_config.width = config->width;
|
||||
#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
|
||||
slot_config.clk = PIN_CLK;
|
||||
slot_config.cmd = PIN_CMD;
|
||||
slot_config.d0 = PIN_D0;
|
||||
slot_config.d1 = PIN_D1;
|
||||
slot_config.d2 = PIN_D2;
|
||||
slot_config.d3 = PIN_D3;
|
||||
#endif
|
||||
|
||||
link->card = calloc(1, sizeof(sdmmc_card_t));
|
||||
if (link->card == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
err = sdmmc_host_init();
|
||||
if (err != ESP_OK) {
|
||||
host_link_deinit(link);
|
||||
return err;
|
||||
}
|
||||
link->host_initialized = true;
|
||||
|
||||
err = sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config);
|
||||
if (err != ESP_OK) {
|
||||
host_link_deinit(link);
|
||||
return err;
|
||||
}
|
||||
|
||||
for (int attempt = 0; attempt < INIT_RETRY_COUNT; ++attempt) {
|
||||
err = sdmmc_card_init(&host, link->card);
|
||||
if (err == ESP_OK) {
|
||||
break;
|
||||
}
|
||||
ESP_LOGW(TAG, "sdmmc_card_init failed (%s), retrying...", esp_err_to_name(err));
|
||||
vTaskDelay(pdMS_TO_TICKS(INIT_RETRY_DELAY_MS));
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
host_link_deinit(link);
|
||||
return err;
|
||||
}
|
||||
|
||||
enable_bus_pullups(config->width);
|
||||
log_host_drive_strengths(config->width, "Host default");
|
||||
err = apply_host_drive_strengths(config);
|
||||
if (err != ESP_OK) {
|
||||
host_link_deinit(link);
|
||||
return err;
|
||||
}
|
||||
log_host_drive_strengths(config->width, "Host active");
|
||||
|
||||
err = enable_slave_function(link->card);
|
||||
if (err != ESP_OK) {
|
||||
host_link_deinit(link);
|
||||
return err;
|
||||
}
|
||||
|
||||
link->link_ready = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void run_transmit_mode(host_link_t *link)
|
||||
{
|
||||
fill_test_pattern(s_tx_frame, sizeof(s_tx_frame));
|
||||
printf("Entering transmit mode\n");
|
||||
fflush(stdout);
|
||||
|
||||
while (true) {
|
||||
esp_err_t err = send_one_frame(link, s_tx_frame, sizeof(s_tx_frame));
|
||||
if (err == ESP_OK) {
|
||||
printf("TX transfer success\n");
|
||||
} else {
|
||||
printf("TX transfer error: %s\n", esp_err_to_name(err));
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
if (wait_for_stop_key()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void run_receive_mode(host_link_t *link)
|
||||
{
|
||||
uint32_t frame_index = 0;
|
||||
|
||||
printf("Entering receive mode\n");
|
||||
fflush(stdout);
|
||||
|
||||
while (true) {
|
||||
size_t received_len = 0;
|
||||
esp_err_t err = receive_one_frame(link, s_rx_frame, &received_len);
|
||||
++frame_index;
|
||||
if (err == ESP_OK) {
|
||||
bool valid = validate_test_pattern(s_rx_frame, received_len);
|
||||
if (frame_index <= DEBUG_FRAME_LOG_LIMIT || !valid) {
|
||||
log_frame_debug("RX", frame_index, s_rx_frame, received_len);
|
||||
}
|
||||
if (valid) {
|
||||
printf("RX pattern validation: success\n");
|
||||
} else {
|
||||
int mismatch_index = find_pattern_mismatch(s_rx_frame, received_len);
|
||||
if (mismatch_index >= 0 && mismatch_index < TEST_FRAME_LEN && received_len > (size_t)mismatch_index) {
|
||||
ESP_LOGE(TAG,
|
||||
"RX mismatch at byte %d: expected 0x%02X got 0x%02X",
|
||||
mismatch_index,
|
||||
s_pattern[mismatch_index],
|
||||
s_rx_frame[mismatch_index]);
|
||||
} else {
|
||||
ESP_LOGE(TAG,
|
||||
"RX length mismatch: expected %u got %u",
|
||||
TEST_FRAME_LEN,
|
||||
(unsigned)received_len);
|
||||
}
|
||||
printf("RX pattern validation: failure\n");
|
||||
}
|
||||
} else {
|
||||
printf("RX transfer error: %s\n", esp_err_to_name(err));
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
if (wait_for_stop_key()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
link_config_t config = {
|
||||
.width = LINK_WIDTH_1BIT,
|
||||
.speed = LINK_SPEED_DEFAULT,
|
||||
.drive_strength = {
|
||||
.clk_drive = DRIVE_CAP_KEEP_DEFAULT,
|
||||
.cmd_drive = DRIVE_CAP_KEEP_DEFAULT,
|
||||
.data_drive = DRIVE_CAP_KEEP_DEFAULT,
|
||||
},
|
||||
};
|
||||
|
||||
slave_power_on();
|
||||
configure_console();
|
||||
|
||||
while (true) {
|
||||
host_link_t link = {0};
|
||||
|
||||
while (!link.link_ready) {
|
||||
print_setup_menu(&config);
|
||||
|
||||
switch (read_console_key_blocking()) {
|
||||
case '1':
|
||||
config.width = LINK_WIDTH_1BIT;
|
||||
break;
|
||||
case '4':
|
||||
config.width = LINK_WIDTH_4BIT;
|
||||
break;
|
||||
case 'd':
|
||||
config.speed = LINK_SPEED_DEFAULT;
|
||||
break;
|
||||
case 'p':
|
||||
config.speed = LINK_SPEED_PROBING;
|
||||
break;
|
||||
case 'h':
|
||||
config.speed = LINK_SPEED_HIGH;
|
||||
break;
|
||||
case 'k':
|
||||
config.drive_strength.clk_drive = prompt_drive_strength_selection("CLK", config.drive_strength.clk_drive);
|
||||
break;
|
||||
case 'm':
|
||||
config.drive_strength.cmd_drive = prompt_drive_strength_selection("CMD", config.drive_strength.cmd_drive);
|
||||
break;
|
||||
case 'g':
|
||||
config.drive_strength.data_drive = prompt_drive_strength_selection("DATA", config.drive_strength.data_drive);
|
||||
break;
|
||||
case 's': {
|
||||
esp_err_t err = host_link_init(&config, &link);
|
||||
if (err == ESP_OK) {
|
||||
printf("SDIO link initialized successfully\n");
|
||||
} else {
|
||||
printf("SDIO link initialization failed: %s\n", esp_err_to_name(err));
|
||||
host_link_deinit(&link);
|
||||
}
|
||||
fflush(stdout);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
printf("Unknown selection\n");
|
||||
fflush(stdout);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (link.link_ready) {
|
||||
print_traffic_menu(&config);
|
||||
|
||||
switch (read_console_key_blocking()) {
|
||||
case 't':
|
||||
run_transmit_mode(&link);
|
||||
break;
|
||||
case 'r':
|
||||
run_receive_mode(&link);
|
||||
break;
|
||||
case 'x':
|
||||
host_link_deinit(&link);
|
||||
printf("SDIO link closed\n");
|
||||
fflush(stdout);
|
||||
break;
|
||||
default:
|
||||
printf("Unknown selection\n");
|
||||
fflush(stdout);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
162
examples/peripherals/sdio/hw_test/pytest_sdio_hw_test.py
Normal file
162
examples/peripherals/sdio/hw_test/pytest_sdio_hw_test.py
Normal file
@@ -0,0 +1,162 @@
|
||||
# SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_idf import IdfDut
|
||||
|
||||
HOST_PATH = os.path.join(os.path.dirname(__file__), 'host')
|
||||
SLAVE_PATH = os.path.join(os.path.dirname(__file__), 'slave')
|
||||
TRANSFER_REPEAT_COUNT = 5
|
||||
HOST_FAILURE_PATTERNS = [
|
||||
'TX transfer error:',
|
||||
'RX transfer error:',
|
||||
'RX pattern validation: failure',
|
||||
]
|
||||
SLAVE_FAILURE_PATTERNS = [
|
||||
'RX pattern validation: failure',
|
||||
]
|
||||
|
||||
_WIDTH_SPEED_PARAMS = [
|
||||
pytest.param('1', 'd', id='1line_ds'),
|
||||
pytest.param('1', 'h', id='1line_hs'),
|
||||
pytest.param('4', 'd', id='4line_ds'),
|
||||
pytest.param('4', 'h', id='4line_hs'),
|
||||
]
|
||||
|
||||
|
||||
def parameter_expand(existing_parameters: list[list[str]], value_list: list[str]) -> list[list[str]]:
|
||||
ret = []
|
||||
for param in existing_parameters:
|
||||
ret.extend([param + [value] for value in value_list])
|
||||
return ret
|
||||
|
||||
|
||||
esp32_32_param = [[f'{HOST_PATH}|{SLAVE_PATH}', 'esp32|esp32']]
|
||||
esp32_c6_param = [[f'{HOST_PATH}|{SLAVE_PATH}', 'esp32|esp32c6']]
|
||||
esp32p4_c5_param = [[f'{HOST_PATH}|{SLAVE_PATH}', 'esp32p4|esp32c5']]
|
||||
esp32_c61_param = [[f'{HOST_PATH}|{SLAVE_PATH}', 'esp32|esp32c61']]
|
||||
|
||||
esp32_param_default = [pytest.param(*param) for param in parameter_expand(esp32_32_param, ['default|default'])]
|
||||
c6_param_default = [pytest.param(*param) for param in parameter_expand(esp32_c6_param, ['default|default'])]
|
||||
c5_param_default = [pytest.param(*param) for param in parameter_expand(esp32p4_c5_param, ['default|default'])]
|
||||
c61_param_default = [pytest.param(*param) for param in parameter_expand(esp32_c61_param, ['default|default'])]
|
||||
|
||||
|
||||
def _send_choice(dut: IdfDut, choice: str) -> None:
|
||||
dut.write(f'\n{choice}\n')
|
||||
|
||||
|
||||
def _expect_success_or_fail(dut: IdfDut, success_pattern: str, failure_patterns: list[str], timeout: int = 10) -> None:
|
||||
matched = dut.expect_exact([success_pattern, *failure_patterns], timeout=timeout)
|
||||
matched_text = matched.decode() if isinstance(matched, bytes) else str(matched)
|
||||
assert matched_text == success_pattern, f'Expected "{success_pattern}", got "{matched_text}"'
|
||||
|
||||
|
||||
def _expect_setup_menu(host: IdfDut) -> None:
|
||||
host.expect_exact('=== SDIO Link Setup ===', timeout=10)
|
||||
host.expect_exact('Select bus width:', timeout=5)
|
||||
host.expect_exact('Select clock mode:', timeout=5)
|
||||
host.expect_exact('Press s to start link initialization', timeout=5)
|
||||
|
||||
|
||||
def _expect_transfer_menu(host: IdfDut) -> None:
|
||||
host.expect_exact('=== SDIO Traffic Menu ===', timeout=10)
|
||||
host.expect_exact('Press t for transmit mode', timeout=5)
|
||||
host.expect_exact('Press r for receive mode', timeout=5)
|
||||
host.expect_exact('Press x to close the link', timeout=5)
|
||||
|
||||
|
||||
def _select_link_mode(host: IdfDut, width: str, speed: str) -> None:
|
||||
_expect_setup_menu(host)
|
||||
_send_choice(host, width)
|
||||
_send_choice(host, speed)
|
||||
_send_choice(host, 's')
|
||||
host.expect_exact('SDIO link initialized successfully', timeout=15)
|
||||
_expect_transfer_menu(host)
|
||||
|
||||
|
||||
def _run_transmit_check(host: IdfDut, slave: IdfDut) -> None:
|
||||
_send_choice(host, 't')
|
||||
host.expect_exact('Entering transmit mode', timeout=5)
|
||||
for _ in range(TRANSFER_REPEAT_COUNT):
|
||||
_expect_success_or_fail(host, 'TX transfer success', HOST_FAILURE_PATTERNS)
|
||||
_expect_success_or_fail(slave, 'RX pattern validation: success', SLAVE_FAILURE_PATTERNS)
|
||||
_send_choice(host, 'z')
|
||||
_expect_transfer_menu(host)
|
||||
|
||||
|
||||
def _run_receive_check(host: IdfDut, slave: IdfDut) -> None:
|
||||
_send_choice(host, 'r')
|
||||
host.expect_exact('Entering receive mode', timeout=5)
|
||||
for _ in range(TRANSFER_REPEAT_COUNT):
|
||||
_expect_success_or_fail(host, 'RX pattern validation: success', HOST_FAILURE_PATTERNS)
|
||||
_expect_success_or_fail(slave, 'TX buffer recycled', SLAVE_FAILURE_PATTERNS)
|
||||
_send_choice(host, 'z')
|
||||
_expect_transfer_menu(host)
|
||||
|
||||
|
||||
def _run_channel_quality_test(
|
||||
dut: tuple[IdfDut, IdfDut],
|
||||
width_choice: str,
|
||||
speed_choice: str,
|
||||
) -> None:
|
||||
host = dut[0]
|
||||
slave = dut[1]
|
||||
|
||||
host.pexpect_proc.timeout = 10
|
||||
slave.pexpect_proc.timeout = 10
|
||||
|
||||
slave.expect_exact('SDIO slave ready for channel test', timeout=15)
|
||||
|
||||
_select_link_mode(host, width_choice, speed_choice)
|
||||
_run_transmit_check(host, slave)
|
||||
_run_receive_check(host, slave)
|
||||
|
||||
|
||||
@pytest.mark.sdio_multidev_32_c6
|
||||
@pytest.mark.parametrize('count', [2], indirect=True)
|
||||
@pytest.mark.parametrize('app_path, target, config', c6_param_default, indirect=True)
|
||||
@pytest.mark.parametrize('width_choice, speed_choice', _WIDTH_SPEED_PARAMS)
|
||||
def test_sdio_hw_esp32_esp32c6(
|
||||
dut: tuple[IdfDut, IdfDut],
|
||||
width_choice: str,
|
||||
speed_choice: str,
|
||||
) -> None:
|
||||
_run_channel_quality_test(dut, width_choice, speed_choice)
|
||||
|
||||
|
||||
@pytest.mark.sdio_master_slave
|
||||
@pytest.mark.parametrize('count', [2], indirect=True)
|
||||
@pytest.mark.parametrize('app_path, target, config', esp32_param_default, indirect=True)
|
||||
@pytest.mark.parametrize('width_choice, speed_choice', _WIDTH_SPEED_PARAMS)
|
||||
def test_sdio_hw_esp32_esp32(
|
||||
dut: tuple[IdfDut, IdfDut],
|
||||
width_choice: str,
|
||||
speed_choice: str,
|
||||
) -> None:
|
||||
_run_channel_quality_test(dut, width_choice, speed_choice)
|
||||
|
||||
|
||||
@pytest.mark.sdio_multidev_p4_c5
|
||||
@pytest.mark.parametrize('count', [2], indirect=True)
|
||||
@pytest.mark.parametrize('app_path, target, config', c5_param_default, indirect=True)
|
||||
@pytest.mark.parametrize('width_choice, speed_choice', _WIDTH_SPEED_PARAMS)
|
||||
def test_sdio_hw_esp32p4_esp32c5(
|
||||
dut: tuple[IdfDut, IdfDut],
|
||||
width_choice: str,
|
||||
speed_choice: str,
|
||||
) -> None:
|
||||
_run_channel_quality_test(dut, width_choice, speed_choice)
|
||||
|
||||
|
||||
@pytest.mark.sdio_multidev_32_c61
|
||||
@pytest.mark.parametrize('count', [2], indirect=True)
|
||||
@pytest.mark.parametrize('app_path, target, config', c61_param_default, indirect=True)
|
||||
@pytest.mark.parametrize('width_choice, speed_choice', _WIDTH_SPEED_PARAMS)
|
||||
def test_sdio_hw_esp32_esp32c61(
|
||||
dut: tuple[IdfDut, IdfDut],
|
||||
width_choice: str,
|
||||
speed_choice: str,
|
||||
) -> None:
|
||||
_run_channel_quality_test(dut, width_choice, speed_choice)
|
||||
8
examples/peripherals/sdio/hw_test/slave/CMakeLists.txt
Normal file
8
examples/peripherals/sdio/hw_test/slave/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(sdio_slave)
|
||||
4
examples/peripherals/sdio/hw_test/slave/README.md
Normal file
4
examples/peripherals/sdio/hw_test/slave/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
| Supported Targets | ESP32 | ESP32-C5 | ESP32-C6 | ESP32-C61 |
|
||||
| ----------------- | ----- | -------- | -------- | --------- |
|
||||
|
||||
See README.md in the parent folder
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "app_main.c"
|
||||
PRIV_REQUIRES esp_driver_gpio esp_driver_sdio esp_ringbuf
|
||||
INCLUDE_DIRS ".")
|
||||
272
examples/peripherals/sdio/hw_test/slave/main/app_main.c
Normal file
272
examples/peripherals/sdio/hw_test/slave/main/app_main.c
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/sdio_slave.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_log_buffer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/sdio_slave_pins.h"
|
||||
|
||||
#define TAG "example_slave"
|
||||
#define TEST_FRAME_LEN 16
|
||||
#define SDIO_BUFFER_SIZE 128
|
||||
#define RX_BUFFER_COUNT 16
|
||||
#define TX_BUFFER_COUNT 8
|
||||
#define LOOP_IDLE_DELAY_MS 20
|
||||
#define DEBUG_FRAME_LOG_LIMIT 8
|
||||
#define DRIVE_CAP_KEEP_DEFAULT (-1)
|
||||
#define SLAVE_CLK_DRIVE_CAP DRIVE_CAP_KEEP_DEFAULT
|
||||
#define SLAVE_CMD_DRIVE_CAP DRIVE_CAP_KEEP_DEFAULT
|
||||
#define SLAVE_DATA_DRIVE_CAP DRIVE_CAP_KEEP_DEFAULT
|
||||
|
||||
static const uint8_t s_pattern[TEST_FRAME_LEN] = {
|
||||
0xFF, 0x00, 0xFF, 0x00,
|
||||
0xFF, 0x00, 0xFF, 0x00,
|
||||
0xFF, 0x00, 0xFF, 0x00,
|
||||
0xFF, 0x00, 0xFF, 0x00,
|
||||
};
|
||||
|
||||
static DMA_ATTR uint8_t s_rx_buffers[RX_BUFFER_COUNT][SDIO_BUFFER_SIZE];
|
||||
static DMA_ATTR uint8_t s_tx_buffers[TX_BUFFER_COUNT][TEST_FRAME_LEN];
|
||||
static uint32_t s_tx_frame_counter;
|
||||
|
||||
static const gpio_num_t s_clk_pin = (gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CLK;
|
||||
static const gpio_num_t s_cmd_pin = (gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CMD;
|
||||
static const gpio_num_t s_data_pins[] = {
|
||||
(gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D0,
|
||||
(gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D1,
|
||||
(gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D2,
|
||||
(gpio_num_t)SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D3,
|
||||
};
|
||||
|
||||
static void fill_test_pattern(uint8_t *buffer, size_t len)
|
||||
{
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
buffer[i] = s_pattern[i % TEST_FRAME_LEN];
|
||||
}
|
||||
}
|
||||
|
||||
static bool validate_test_pattern(const uint8_t *buffer, size_t len)
|
||||
{
|
||||
if (len != TEST_FRAME_LEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return memcmp(buffer, s_pattern, TEST_FRAME_LEN) == 0;
|
||||
}
|
||||
|
||||
static int find_pattern_mismatch(const uint8_t *buffer, size_t len)
|
||||
{
|
||||
size_t compare_len = (len < TEST_FRAME_LEN) ? len : TEST_FRAME_LEN;
|
||||
for (size_t i = 0; i < compare_len; ++i) {
|
||||
if (buffer[i] != s_pattern[i]) {
|
||||
return (int)i;
|
||||
}
|
||||
}
|
||||
if (len != TEST_FRAME_LEN) {
|
||||
return (int)compare_len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void log_frame_debug(const char *label, uint32_t frame_index, const uint8_t *buffer, size_t len)
|
||||
{
|
||||
ESP_LOGI(TAG, "%s frame=%" PRIu32 " len=%u", label, frame_index, (unsigned)len);
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, len, ESP_LOG_INFO);
|
||||
}
|
||||
|
||||
static const char *drive_strength_name(int value)
|
||||
{
|
||||
switch (value) {
|
||||
case DRIVE_CAP_KEEP_DEFAULT:
|
||||
return "default";
|
||||
case GPIO_DRIVE_CAP_0:
|
||||
return "cap0";
|
||||
case GPIO_DRIVE_CAP_1:
|
||||
return "cap1";
|
||||
case GPIO_DRIVE_CAP_2:
|
||||
return "cap2";
|
||||
case GPIO_DRIVE_CAP_3:
|
||||
return "cap3";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void log_gpio_drive_capability(const char *label, gpio_num_t pin)
|
||||
{
|
||||
gpio_drive_cap_t capability = GPIO_DRIVE_CAP_DEFAULT;
|
||||
esp_err_t err = gpio_get_drive_capability(pin, &capability);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "%s GPIO%d drive capability: %s (%d)", label, pin, drive_strength_name(capability), capability);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to read drive capability for GPIO%d: %s", pin, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t apply_gpio_drive_strength(gpio_num_t pin, int drive_strength)
|
||||
{
|
||||
if (drive_strength == DRIVE_CAP_KEEP_DEFAULT) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return gpio_set_drive_capability(pin, (gpio_drive_cap_t)drive_strength);
|
||||
}
|
||||
|
||||
static esp_err_t apply_slave_drive_strengths(void)
|
||||
{
|
||||
esp_err_t err = apply_gpio_drive_strength(s_clk_pin, SLAVE_CLK_DRIVE_CAP);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = apply_gpio_drive_strength(s_cmd_pin, SLAVE_CMD_DRIVE_CAP);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(s_data_pins) / sizeof(s_data_pins[0]); ++i) {
|
||||
err = apply_gpio_drive_strength(s_data_pins[i], SLAVE_DATA_DRIVE_CAP);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void log_slave_drive_strengths(const char *label)
|
||||
{
|
||||
log_gpio_drive_capability(label, s_clk_pin);
|
||||
log_gpio_drive_capability(label, s_cmd_pin);
|
||||
for (size_t i = 0; i < sizeof(s_data_pins) / sizeof(s_data_pins[0]); ++i) {
|
||||
log_gpio_drive_capability(label, s_data_pins[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void init_rx_buffers(void)
|
||||
{
|
||||
for (int i = 0; i < RX_BUFFER_COUNT; ++i) {
|
||||
sdio_slave_buf_handle_t handle = sdio_slave_recv_register_buf(s_rx_buffers[i]);
|
||||
assert(handle != NULL);
|
||||
ESP_ERROR_CHECK(sdio_slave_recv_load_buf(handle));
|
||||
}
|
||||
}
|
||||
|
||||
static void queue_tx_buffer(uint8_t *buffer)
|
||||
{
|
||||
fill_test_pattern(buffer, TEST_FRAME_LEN);
|
||||
++s_tx_frame_counter;
|
||||
if (s_tx_frame_counter <= DEBUG_FRAME_LOG_LIMIT) {
|
||||
log_frame_debug("TX queue", s_tx_frame_counter, buffer, TEST_FRAME_LEN);
|
||||
}
|
||||
ESP_ERROR_CHECK(sdio_slave_send_queue(buffer, TEST_FRAME_LEN, buffer, portMAX_DELAY));
|
||||
}
|
||||
|
||||
static void init_tx_buffers(void)
|
||||
{
|
||||
for (int i = 0; i < TX_BUFFER_COUNT; ++i) {
|
||||
queue_tx_buffer(s_tx_buffers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void recycle_finished_tx_buffers(void)
|
||||
{
|
||||
while (true) {
|
||||
void *arg = NULL;
|
||||
esp_err_t err = sdio_slave_send_get_finished(&arg, 0);
|
||||
if (err == ESP_ERR_TIMEOUT) {
|
||||
return;
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
uint8_t *buffer = (uint8_t *)arg;
|
||||
queue_tx_buffer(buffer);
|
||||
printf("TX buffer recycled\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
static bool process_rx_packet(void)
|
||||
{
|
||||
sdio_slave_buf_handle_t handle = NULL;
|
||||
uint8_t *buffer = NULL;
|
||||
size_t length = 0;
|
||||
esp_err_t err = sdio_slave_recv(&handle, &buffer, &length, 0);
|
||||
|
||||
if (err == ESP_ERR_TIMEOUT) {
|
||||
return false;
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
if (validate_test_pattern(buffer, length)) {
|
||||
static uint32_t rx_frame_counter;
|
||||
++rx_frame_counter;
|
||||
if (rx_frame_counter <= DEBUG_FRAME_LOG_LIMIT) {
|
||||
log_frame_debug("RX", rx_frame_counter, buffer, length);
|
||||
}
|
||||
printf("RX pattern validation: success\n");
|
||||
} else {
|
||||
int mismatch_index = find_pattern_mismatch(buffer, length);
|
||||
log_frame_debug("RX invalid", 0, buffer, length);
|
||||
if (mismatch_index >= 0 && mismatch_index < TEST_FRAME_LEN && length > (size_t)mismatch_index) {
|
||||
ESP_LOGE(TAG,
|
||||
"RX mismatch at byte %d: expected 0x%02X got 0x%02X",
|
||||
mismatch_index,
|
||||
s_pattern[mismatch_index],
|
||||
buffer[mismatch_index]);
|
||||
} else {
|
||||
ESP_LOGE(TAG,
|
||||
"RX length mismatch: expected %u got %u",
|
||||
TEST_FRAME_LEN,
|
||||
(unsigned)length);
|
||||
}
|
||||
printf("RX pattern validation: failure\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
ESP_ERROR_CHECK(sdio_slave_recv_load_buf(handle));
|
||||
return true;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
sdio_slave_config_t config = {
|
||||
.sending_mode = SDIO_SLAVE_SEND_PACKET,
|
||||
.send_queue_size = TX_BUFFER_COUNT,
|
||||
.recv_buffer_size = SDIO_BUFFER_SIZE,
|
||||
.event_cb = NULL,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(sdio_slave_initialize(&config));
|
||||
log_slave_drive_strengths("Slave default");
|
||||
ESP_ERROR_CHECK(apply_slave_drive_strengths());
|
||||
log_slave_drive_strengths("Slave active");
|
||||
init_rx_buffers();
|
||||
init_tx_buffers();
|
||||
|
||||
ESP_ERROR_CHECK(sdio_slave_start());
|
||||
|
||||
printf("SDIO slave ready for channel test\n");
|
||||
fflush(stdout);
|
||||
|
||||
while (true) {
|
||||
bool did_work = false;
|
||||
|
||||
recycle_finished_tx_buffers();
|
||||
did_work |= process_rx_packet();
|
||||
|
||||
if (!did_work) {
|
||||
vTaskDelay(pdMS_TO_TICKS(LOOP_IDLE_DELAY_MS));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
# SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import os
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_idf import IdfDut
|
||||
@@ -12,12 +11,16 @@ from pytest_embedded_idf.utils import idf_parametrize
|
||||
@pytest.mark.parametrize(
|
||||
'count, app_path',
|
||||
[
|
||||
(2, f'{os.path.join(os.path.dirname(__file__), "host")}|{os.path.join(os.path.dirname(__file__), "slave")}'),
|
||||
(
|
||||
2,
|
||||
f'{os.path.join(os.path.dirname(__file__), "basic", "host")}|'
|
||||
f'{os.path.join(os.path.dirname(__file__), "basic", "slave")}',
|
||||
),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_example_sdio_communication(dut: Tuple[IdfDut, IdfDut]) -> None:
|
||||
def test_example_sdio_communication(dut: tuple[IdfDut, IdfDut]) -> None:
|
||||
"""
|
||||
Configurations
|
||||
host = host -> slave = slave
|
||||
|
||||
@@ -764,8 +764,6 @@ examples/network/simple_sniffer/main/cmd_sniffer.h
|
||||
examples/network/simple_sniffer/main/simple_sniffer_example_main.c
|
||||
examples/peripherals/ledc/ledc_basic/main/ledc_basic_example_main.c
|
||||
examples/peripherals/ledc/ledc_fade/main/ledc_fade_example_main.c
|
||||
examples/peripherals/sdio/host/main/app_main.c
|
||||
examples/peripherals/sdio/slave/main/app_main.c
|
||||
examples/peripherals/spi_master/hd_eeprom/components/eeprom/spi_eeprom.c
|
||||
examples/peripherals/spi_master/hd_eeprom/components/eeprom/spi_eeprom.h
|
||||
examples/peripherals/spi_master/hd_eeprom/main/spi_eeprom_main.c
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
|
||||
-
|
||||
re: "sdmmc_io: sdmmc_io_read_byte: sdmmc_io_rw_direct"
|
||||
hint: "Please verify that card supports IO capabilities. Refer 'IDF_PATH/examples/peripherals/sdio/host/README.md' for more details"
|
||||
hint: "Please verify that card supports IO capabilities. Refer 'IDF_PATH/examples/peripherals/sdio/basic/host/README.md' for more details"
|
||||
|
||||
-
|
||||
re: "example: Failed to initialize the card \\({}\\). Make sure SD card lines have pull-up resistors in place."
|
||||
|
||||
Reference in New Issue
Block a user