mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(spi): support data output inversion
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 交换矩阵传输,那么所有的信号都将通由其传输。
|
||||
|
||||
Reference in New Issue
Block a user