Merge branch 'feat/spi_add_data_output_inversion_v6.1' into 'release/v6.1'

feat(spi): support data output inversion (backport v6.1)

See merge request espressif/esp-idf!52745
This commit is contained in:
Jiang Jiang Jian
2026-09-15 20:37:14 +08:00
6 changed files with 86 additions and 10 deletions

View File

@@ -65,6 +65,7 @@ extern "C"
#define SPICOMMON_BUSFLAG_OCTAL (SPICOMMON_BUSFLAG_QUAD|SPICOMMON_BUSFLAG_IO4_IO7) ///< Check existing of MOSI/MISO/WP/HD/SPIIO4/SPIIO5/SPIIO6/SPIIO7 pins as output. Or indicates bus able to work under octal mode.
#define SPICOMMON_BUSFLAG_NATIVE_PINS SPICOMMON_BUSFLAG_IOMUX_PINS
#define SPICOMMON_BUSFLAG_SLP_ALLOW_PD (1<<9) ///< Allow to power down the peripheral during light sleep, and auto recover then.
#define SPICOMMON_BUSFLAG_DATA_OUT_INV (1<<10) ///< Invert output data signals through the GPIO matrix.
/**
* @brief SPI DMA channels
@@ -94,6 +95,9 @@ typedef spi_common_dma_t spi_dma_chan_t;
* delay, which may cause incorrect read for >40MHz speeds.
*
* @note Be advised that the slave driver does not use the quadwp/quadhd lines and fields in spi_bus_config_t referring to these lines will be ignored and can thus safely be left uninitialized.
* @note When `SPICOMMON_BUSFLAG_DATA_OUT_INV` is set in `flags`, all configured bus signals are routed through the GPIO matrix.
* @note On ESP32, `data_io_default_level` only supports 0. When
`SPICOMMON_BUSFLAG_DATA_OUT_INV` is set, the idle data level is also inverted.
*/
typedef struct {
union {

View File

@@ -622,7 +622,7 @@ static void s_spi_common_gpio_check_reserve(gpio_num_t gpio_num)
}
}
static void s_spi_common_bus_via_gpio(gpio_num_t gpio_num, int in_sig, int out_sig, uint64_t *io_mask)
static void s_spi_common_bus_via_gpio(gpio_num_t gpio_num, int in_sig, int out_sig, bool out_inv, uint64_t *io_mask)
{
assert(GPIO_IS_VALID_GPIO(gpio_num)); //coverity check
if (in_sig != -1) {
@@ -632,7 +632,7 @@ static void s_spi_common_bus_via_gpio(gpio_num_t gpio_num, int in_sig, int out_s
// For gpio_matrix, reserve output pins, see 'esp_gpio_reserve.h'
*io_mask |= BIT64(gpio_num);
s_spi_common_gpio_check_reserve(gpio_num);
gpio_matrix_output(gpio_num, out_sig, false, false);
gpio_matrix_output(gpio_num, out_sig, out_inv, false);
}
gpio_func_sel(gpio_num, PIN_FUNC_GPIO);
}
@@ -706,8 +706,11 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
temp_flag |= SPICOMMON_BUSFLAG_DUAL;
}
bool data_out_inv = bus_config->flags & SPICOMMON_BUSFLAG_DATA_OUT_INV;
//check if the selected pins correspond to the iomux pins of the peripheral
bool use_iomux = !(flags & SPICOMMON_BUSFLAG_GPIO_PINS) && bus_uses_iomux_pins(host, bus_config);
bool use_iomux = !data_out_inv &&
!(flags & SPICOMMON_BUSFLAG_GPIO_PINS) &&
bus_uses_iomux_pins(host, bus_config);
if (use_iomux) {
temp_flag |= SPICOMMON_BUSFLAG_IOMUX_PINS;
} else {
@@ -717,6 +720,7 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
uint32_t missing_flag = flags & ~temp_flag;
missing_flag &= ~SPICOMMON_BUSFLAG_MASTER; //don't check this flag
missing_flag &= ~SPICOMMON_BUSFLAG_SLP_ALLOW_PD;
missing_flag &= ~SPICOMMON_BUSFLAG_DATA_OUT_INV;
if (missing_flag != 0) {
//check pins existence
@@ -771,23 +775,25 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
if (bus_config->mosi_io_num >= 0) {
int in_sig = spi_periph_signal[host].spid_in; // always connect input in case sio master is used
int out_sig = spi_periph_signal[host].spid_out;// always connect output in case sio slave is used, output capability is checked in slave hd driver
s_spi_common_bus_via_gpio(bus_config->mosi_io_num, in_sig, out_sig, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->mosi_io_num, in_sig, out_sig, data_out_inv, &gpio_reserv);
}
if (bus_config->miso_io_num >= 0) {
int in_sig = ((flags & SPICOMMON_BUSFLAG_MASTER) || (temp_flag & SPICOMMON_BUSFLAG_DUAL)) ? spi_periph_signal[host].spiq_in : -1;
int out_sig = (!(flags & SPICOMMON_BUSFLAG_MASTER) || (temp_flag & SPICOMMON_BUSFLAG_DUAL)) ? spi_periph_signal[host].spiq_out : -1;
s_spi_common_bus_via_gpio(bus_config->miso_io_num, in_sig, out_sig, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->miso_io_num, in_sig, out_sig, data_out_inv, &gpio_reserv);
}
if (bus_config->sclk_io_num >= 0) {
int in_sig = (flags & SPICOMMON_BUSFLAG_MASTER) ? -1 : spi_periph_signal[host].spiclk_in;
int out_sig = (flags & SPICOMMON_BUSFLAG_MASTER) ? spi_periph_signal[host].spiclk_out : -1;
s_spi_common_bus_via_gpio(bus_config->sclk_io_num, in_sig, out_sig, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->sclk_io_num, in_sig, out_sig, false, &gpio_reserv);
}
if (bus_config->quadwp_io_num >= 0) {
s_spi_common_bus_via_gpio(bus_config->quadwp_io_num, spi_periph_signal[host].spiwp_in, spi_periph_signal[host].spiwp_out, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->quadwp_io_num, spi_periph_signal[host].spiwp_in,
spi_periph_signal[host].spiwp_out, data_out_inv, &gpio_reserv);
}
if (bus_config->quadhd_io_num >= 0) {
s_spi_common_bus_via_gpio(bus_config->quadhd_io_num, spi_periph_signal[host].spihd_in, spi_periph_signal[host].spihd_out, &gpio_reserv);
s_spi_common_bus_via_gpio(bus_config->quadhd_io_num, spi_periph_signal[host].spihd_in,
spi_periph_signal[host].spihd_out, data_out_inv, &gpio_reserv);
}
#if SOC_SPI_SUPPORT_OCT
if (flags & SPICOMMON_BUSFLAG_OCTAL) {
@@ -800,7 +806,7 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
};
for (size_t i = 0; i < sizeof(io_nums) / sizeof(io_nums[0]); i++) {
if (io_nums[i] >= 0) {
s_spi_common_bus_via_gpio(io_nums[i], io_signals[i][1], io_signals[i][0], &gpio_reserv);
s_spi_common_bus_via_gpio(io_nums[i], io_signals[i][1], io_signals[i][0], data_out_inv, &gpio_reserv);
}
}
}

View File

@@ -331,7 +331,11 @@ static esp_err_t spi_master_init_driver(spi_host_device_t host_id)
}
spi_hal_init(&host->hal, host_id);
spi_hal_set_data_pin_idle_level(&host->hal, bus_attr->bus_cfg.data_io_default_level);
bool hal_idle_level = bus_attr->bus_cfg.data_io_default_level;
if (bus_attr->bus_cfg.flags & SPICOMMON_BUSFLAG_DATA_OUT_INV) {
hal_idle_level = !hal_idle_level; // Compensate for inversion set in GPIO Matrix
}
spi_hal_set_data_pin_idle_level(&host->hal, hal_idle_level);
if (host_id != SPI1_HOST) {
//SPI1 attributes are already initialized at start up.

View File

@@ -19,6 +19,7 @@
#include "esp_memory_utils.h"
#include "esp_private/spi_common_internal.h"
#include "esp_private/esp_clk.h"
#include "esp_private/gpio.h"
#include "esp_private/sleep_cpu.h"
#include "esp_private/esp_sleep_internal.h"
#include "esp_private/esp_pmu.h"
@@ -697,6 +698,59 @@ TEST_CASE("spi bus setting with different pin configs", "[spi]")
#endif
}
TEST_CASE("spi data output inversion", "[spi]")
{
for (int invert = 0; invert < 2; invert++) {
ESP_LOGI(TAG, "Testing data output inversion: %s", invert ? "enabled" : "disabled");
spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG();
buscfg.data_io_default_level = false;
buscfg.flags |= invert ? SPICOMMON_BUSFLAG_DATA_OUT_INV : 0;
spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG();
spi_device_handle_t handle = NULL;
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_DISABLED));
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &handle));
if (invert) {
const spi_bus_attr_t *bus_attr = spi_bus_get_attr(TEST_SPI_HOST);
TEST_ASSERT_TRUE(bus_attr->flags & SPICOMMON_BUSFLAG_GPIO_PINS);
}
/* Check inversion during a transaction. Add MOSI-to-MISO loopback after bus initialization to preserve the selected MOSI output route. */
TEST_ESP_OK(gpio_matrix_input(buscfg.mosi_io_num, spi_periph_signal[TEST_SPI_HOST].spiq_in, false));
uint8_t tx_data = 0xA5;
uint8_t rx_data = 0;
spi_transaction_t trans = {
.length = sizeof(tx_data) * 8,
.tx_buffer = &tx_data,
.rx_buffer = &rx_data,
};
TEST_ESP_OK(spi_device_polling_transmit(handle, &trans));
uint8_t expected_rx_data = invert ? 0x5A : 0xA5;
ESP_LOGI(TAG, "Loopback: TX=0x%02X, RX=0x%02X (expected=0x%02X)",
(unsigned)tx_data, (unsigned)rx_data, (unsigned)expected_rx_data);
TEST_ASSERT_EQUAL_HEX8(expected_rx_data, rx_data);
/* Check the MOSI idle level after the transaction. */
bool expected_idle_level = buscfg.data_io_default_level;
#if !SPI_LL_MOSI_FREE_LEVEL
if (invert) {
/* GPIO Matrix inversion also affects the idle level when the target cannot configure it. */
expected_idle_level = !buscfg.data_io_default_level;
}
#endif
int actual_idle_level = gpio_get_level(PIN_NUM_MOSI);
ESP_LOGI(TAG, "Idle: MOSI=%d (expected=%d)", actual_idle_level, expected_idle_level);
TEST_ASSERT_EQUAL_INT(expected_idle_level, actual_idle_level);
TEST_ESP_OK(spi_bus_remove_device(handle));
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST));
}
}
TEST_CASE("SPI Master no response when switch from host1 (SPI2) to host2 (SPI3)", "[spi]")
{
//spi config

View File

@@ -465,6 +465,10 @@ To have better control of the calling sequence of functions, send mixed transact
GPIO Matrix and IO_MUX
^^^^^^^^^^^^^^^^^^^^^^
.. note::
Enabling :c:macro:`SPICOMMON_BUSFLAG_DATA_OUT_INV` forces all configured SPI bus signals through the GPIO Matrix, even when dedicated IO_MUX pins are selected. Since the GPIO Matrix and IO_MUX have target-specific timing and frequency differences, consider the limitations described below when selecting the SPI clock frequency.
.. only:: esp32
Most of ESP32's peripheral signals have a direct connection to their dedicated IO_MUX pins. However, the signals can also be routed to any other available pins using the less direct GPIO matrix. If at least one signal is routed through the GPIO matrix, then all signals will be routed through it.

View File

@@ -465,6 +465,10 @@ ISR 会干扰飞行中的轮询传输事务,以适应中断传输事务。在
GPIO 矩阵与 IO_MUX 管脚
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. note::
启用 :c:macro:`SPICOMMON_BUSFLAG_DATA_OUT_INV` 后,即使选择了专用 IO_MUX 管脚,驱动也会强制所有已配置的 SPI 总线信号通过 GPIO 矩阵。由于不同芯片的 GPIO 矩阵和 IO_MUX 存在不同的时序和频率限制,选择 SPI 时钟频率时应考虑下文所述限制。
.. only:: esp32
ESP32 的多数外设信号都直接与之专用的 IO_MUX 管脚连接,但这些信号也可以通过 GPIO 交换矩阵传输到任何其他可用的管脚。只要有一个信号通过 GPIO 交换矩阵传输,那么所有的信号都将通由其传输。