mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +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 {
|
||||
|
||||
@@ -640,7 +640,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) {
|
||||
@@ -650,7 +650,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);
|
||||
}
|
||||
@@ -724,8 +724,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 {
|
||||
@@ -735,6 +738,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
|
||||
@@ -789,23 +793,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) {
|
||||
@@ -818,7 +824,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.
|
||||
|
||||
@@ -20,6 +20,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"
|
||||
@@ -698,6 +699,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
|
||||
|
||||
Reference in New Issue
Block a user