mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'backport/spi_slave_add_independent_tx_rx_length-v6.1' into 'release/v6.1'
feat(driver_spi): slave driver support config different tx/rx length (v6.1) See merge request espressif/esp-idf!49960
This commit is contained in:
@@ -67,7 +67,9 @@ typedef struct {
|
||||
*/
|
||||
struct spi_slave_transaction_t {
|
||||
uint32_t flags; ///< Bitwise OR of SPI_SLAVE_TRANS_* flags
|
||||
size_t length; ///< Total data length, in bits
|
||||
size_t length; ///< Total data length, in bits. It both for TX and RX, do NOT use together with independent length
|
||||
size_t tx_length; ///< Independent Tx data length, in bits, using with tx_buffer. do NOT use together with `length`
|
||||
size_t rx_length; ///< Independent Rx data length, in bits, using with rx_buffer. do NOT use together with `length`
|
||||
size_t trans_len; ///< Transaction data length, in bits
|
||||
const void *tx_buffer; ///< Pointer to transmit buffer, or NULL for no MOSI phase
|
||||
void *rx_buffer; /**< Pointer to receive buffer, or NULL for no MISO phase.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/param.h>
|
||||
#include "esp_types.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_check.h"
|
||||
@@ -413,7 +414,8 @@ static void SPI_SLAVE_ISR_ATTR spi_slave_uninstall_priv_trans(spi_host_device_t
|
||||
free(priv_trans->tx_buffer);
|
||||
}
|
||||
if (trans->rx_buffer && (trans->rx_buffer != priv_trans->rx_buffer)) {
|
||||
memcpy(trans->rx_buffer, priv_trans->rx_buffer, (trans->length + 7) / 8);
|
||||
size_t compatible_len = trans->rx_length ? trans->rx_length : trans->length;
|
||||
memcpy(trans->rx_buffer, priv_trans->rx_buffer, (MIN(compatible_len, trans->trans_len) + 7) / 8);
|
||||
free(priv_trans->rx_buffer);
|
||||
}
|
||||
}
|
||||
@@ -429,12 +431,12 @@ static esp_err_t SPI_SLAVE_ATTR spi_slave_setup_priv_trans(spi_host_device_t hos
|
||||
}
|
||||
|
||||
bool auto_malloc = (trans->flags & SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO);
|
||||
esp_err_t ret = spicommon_dma_setup_priv_buffer(spihost[host]->id, (uint32_t *)trans->tx_buffer, (trans->length + 7) / 8, true, true, auto_malloc, &priv_trans->tx_buffer);
|
||||
esp_err_t ret = spicommon_dma_setup_priv_buffer(spihost[host]->id, (uint32_t *)trans->tx_buffer, ((trans->length ? trans->length : trans->tx_length) + 7) / 8, true, true, auto_malloc, &priv_trans->tx_buffer);
|
||||
if (ret != ESP_OK) {
|
||||
spi_slave_uninstall_priv_trans(host, priv_trans);
|
||||
return ret;
|
||||
}
|
||||
ret = spicommon_dma_setup_priv_buffer(spihost[host]->id, (uint32_t *)trans->rx_buffer, (trans->length + 7) / 8, false, true, auto_malloc, &priv_trans->rx_buffer);
|
||||
ret = spicommon_dma_setup_priv_buffer(spihost[host]->id, (uint32_t *)trans->rx_buffer, ((trans->length ? trans->length : trans->rx_length) + 7) / 8, false, true, auto_malloc, &priv_trans->rx_buffer);
|
||||
if (ret != ESP_OK) {
|
||||
spi_slave_uninstall_priv_trans(host, priv_trans);
|
||||
}
|
||||
@@ -446,7 +448,12 @@ esp_err_t SPI_SLAVE_ATTR spi_slave_queue_trans(spi_host_device_t host, const spi
|
||||
BaseType_t r;
|
||||
SPI_CHECK(is_valid_host(host), "invalid host", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(trans_desc->length <= spihost[host]->bus_attr->max_transfer_sz * 8, "data transfer > host maximum", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(!(trans_desc->length && (trans_desc->tx_length || trans_desc->rx_length)), "length and tx_length/rx_length are mutually exclusive", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(trans_desc->length <= spihost[host]->bus_attr->max_transfer_sz * 8, "tx data transfer > host maximum", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(trans_desc->tx_length <= spihost[host]->bus_attr->max_transfer_sz * 8, "tx data transfer > host maximum", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(trans_desc->rx_length <= spihost[host]->bus_attr->max_transfer_sz * 8, "rx data transfer > host maximum", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(!trans_desc->tx_buffer || (trans_desc->tx_length || trans_desc->length), "set tx_buffer but no length or tx_length", ESP_ERR_INVALID_ARG);
|
||||
SPI_CHECK(!trans_desc->rx_buffer || (trans_desc->rx_length || trans_desc->length), "set rx_buffer but no length or rx_length", ESP_ERR_INVALID_ARG);
|
||||
|
||||
spi_slave_trans_priv_t priv_trans = {.trans = (spi_slave_transaction_t *)trans_desc};
|
||||
SPI_CHECK(ESP_OK == spi_slave_setup_priv_trans(host, &priv_trans), "slave setup priv_trans failed", ESP_ERR_NO_MEM);
|
||||
@@ -499,7 +506,12 @@ esp_err_t SPI_SLAVE_ISR_ATTR spi_slave_queue_trans_isr(spi_host_device_t host, c
|
||||
BaseType_t do_yield = pdFALSE;
|
||||
ESP_RETURN_ON_FALSE_ISR(is_valid_host(host), ESP_ERR_INVALID_ARG, SPI_TAG, "invalid host");
|
||||
ESP_RETURN_ON_FALSE_ISR(spihost[host], ESP_ERR_INVALID_ARG, SPI_TAG, "host not slave");
|
||||
ESP_RETURN_ON_FALSE_ISR(trans_desc->length <= spihost[host]->bus_attr->max_transfer_sz * 8, ESP_ERR_INVALID_ARG, SPI_TAG, "data transfer > host maximum");
|
||||
ESP_RETURN_ON_FALSE_ISR(!(trans_desc->length && (trans_desc->tx_length || trans_desc->rx_length)), ESP_ERR_INVALID_ARG, SPI_TAG, "length and tx_length/rx_length are mutually exclusive");
|
||||
ESP_RETURN_ON_FALSE_ISR(trans_desc->length <= spihost[host]->bus_attr->max_transfer_sz * 8, ESP_ERR_INVALID_ARG, SPI_TAG, "tx data transfer > host maximum");
|
||||
ESP_RETURN_ON_FALSE_ISR(trans_desc->tx_length <= spihost[host]->bus_attr->max_transfer_sz * 8, ESP_ERR_INVALID_ARG, SPI_TAG, "tx data transfer > host maximum");
|
||||
ESP_RETURN_ON_FALSE_ISR(trans_desc->rx_length <= spihost[host]->bus_attr->max_transfer_sz * 8, ESP_ERR_INVALID_ARG, SPI_TAG, "rx data transfer > host maximum");
|
||||
ESP_RETURN_ON_FALSE_ISR(!trans_desc->tx_buffer || (trans_desc->tx_length || trans_desc->length), ESP_ERR_INVALID_ARG, SPI_TAG, "set tx_buffer but no length or tx_length");
|
||||
ESP_RETURN_ON_FALSE_ISR(!trans_desc->rx_buffer || (trans_desc->rx_length || trans_desc->length), ESP_ERR_INVALID_ARG, SPI_TAG, "set rx_buffer but no length or rx_length");
|
||||
|
||||
spi_slave_trans_priv_t priv_trans = {
|
||||
.trans = (spi_slave_transaction_t *)trans_desc,
|
||||
@@ -508,8 +520,8 @@ esp_err_t SPI_SLAVE_ISR_ATTR spi_slave_queue_trans_isr(spi_host_device_t host, c
|
||||
};
|
||||
if (spihost[host]->bus_attr->dma_enabled) {
|
||||
// isr api is not allowed to auto_malloc, so don't need to 'uninstall' anything here, return directly
|
||||
ESP_RETURN_ON_ERROR_ISR(spicommon_dma_setup_priv_buffer(host, (uint32_t *)trans_desc->tx_buffer, (trans_desc->length + 7) / 8, true, true, false, &priv_trans.tx_buffer), SPI_TAG, "");
|
||||
ESP_RETURN_ON_ERROR_ISR(spicommon_dma_setup_priv_buffer(host, (uint32_t *)trans_desc->rx_buffer, (trans_desc->length + 7) / 8, false, true, false, &priv_trans.rx_buffer), SPI_TAG, "");
|
||||
ESP_RETURN_ON_ERROR_ISR(spicommon_dma_setup_priv_buffer(host, (uint32_t *)trans_desc->tx_buffer, ((trans_desc->length ? trans_desc->length : trans_desc->tx_length) + 7) / 8, true, true, false, &priv_trans.tx_buffer), SPI_TAG, "");
|
||||
ESP_RETURN_ON_ERROR_ISR(spicommon_dma_setup_priv_buffer(host, (uint32_t *)trans_desc->rx_buffer, ((trans_desc->length ? trans_desc->length : trans_desc->rx_length) + 7) / 8, false, true, false, &priv_trans.rx_buffer), SPI_TAG, "");
|
||||
}
|
||||
r = xQueueSendFromISR(spihost[host]->trans_queue, (void *)&priv_trans, &do_yield);
|
||||
if (!r) {
|
||||
@@ -590,14 +602,14 @@ esp_err_t SPI_SLAVE_ATTR spi_slave_transmit(spi_host_device_t host, spi_slave_tr
|
||||
static void SPI_SLAVE_ISR_ATTR s_spi_slave_dma_prepare_data(spi_dma_ctx_t *dma_ctx, spi_slave_hal_context_t *hal)
|
||||
{
|
||||
if (hal->rx_buffer) {
|
||||
spicommon_dma_desc_setup_link(dma_ctx->dmadesc_rx, hal->rx_buffer, ((hal->bitlen + 7) / 8), true);
|
||||
spicommon_dma_desc_setup_link(dma_ctx->dmadesc_rx, hal->rx_buffer, (hal->rx_bitlen + 7) / 8, true);
|
||||
|
||||
spi_dma_reset(dma_ctx->rx_dma_chan);
|
||||
spi_slave_hal_hw_prepare_rx(hal->hw);
|
||||
spi_dma_start(dma_ctx->rx_dma_chan, dma_ctx->dmadesc_rx);
|
||||
}
|
||||
if (hal->tx_buffer) {
|
||||
spicommon_dma_desc_setup_link(dma_ctx->dmadesc_tx, hal->tx_buffer, (hal->bitlen + 7) / 8, false);
|
||||
spicommon_dma_desc_setup_link(dma_ctx->dmadesc_tx, hal->tx_buffer, (hal->tx_bitlen + 7) / 8, false);
|
||||
|
||||
spi_dma_reset(dma_ctx->tx_dma_chan);
|
||||
spi_slave_hal_hw_prepare_tx(hal->hw);
|
||||
@@ -719,7 +731,8 @@ static void SPI_SLAVE_ISR_ATTR spi_intr(void *arg)
|
||||
//We have a transaction. Send it.
|
||||
host->cur_trans = priv_trans;
|
||||
|
||||
hal->bitlen = priv_trans.trans->length;
|
||||
hal->tx_bitlen = priv_trans.trans->tx_length ? priv_trans.trans->tx_length : priv_trans.trans->length;
|
||||
hal->rx_bitlen = priv_trans.trans->rx_length ? priv_trans.trans->rx_length : priv_trans.trans->length;
|
||||
hal->rx_buffer = priv_trans.rx_buffer;
|
||||
hal->tx_buffer = priv_trans.tx_buffer;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -152,7 +152,7 @@ TEST_CASE("test fullduplex slave with only TX direction", "[spi]")
|
||||
spi_slave_transaction_t slave_t;
|
||||
spi_slave_transaction_t *out;
|
||||
memset(&slave_t, 0, sizeof(spi_slave_transaction_t));
|
||||
slave_t.length = 8 * 32;
|
||||
slave_t.tx_length = 8 * 32;
|
||||
slave_t.tx_buffer = slave_txbuf;
|
||||
slave_t.rx_buffer = NULL;
|
||||
slave_t.flags |= SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO;
|
||||
@@ -189,7 +189,7 @@ TEST_CASE("test fullduplex slave with only TX direction", "[spi]")
|
||||
}
|
||||
|
||||
#define TEST_SLV_RX_BUF_LEN 15
|
||||
TEST_CASE("Test slave rx no_dma overwrite when length below/over config", "[spi]")
|
||||
TEST_CASE("Test slave rx_buffer overwrite if trans_len below/over config_len", "[spi]")
|
||||
{
|
||||
spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG();
|
||||
buscfg.flags |= SPICOMMON_BUSFLAG_GPIO_PINS;
|
||||
@@ -198,65 +198,79 @@ TEST_CASE("Test slave rx no_dma overwrite when length below/over config", "[spi]
|
||||
spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG();
|
||||
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &spidev0));
|
||||
|
||||
spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG();
|
||||
TEST_ESP_OK(spi_slave_initialize(TEST_SLAVE_HOST, &buscfg, &slvcfg, SPI_DMA_DISABLED));
|
||||
for (int use_dma = 0; use_dma < 2; use_dma++) {
|
||||
printf("\n------------- DMA: %s -------------\n", use_dma ? "Enable" : "Disable");
|
||||
spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG();
|
||||
TEST_ESP_OK(spi_slave_initialize(TEST_SLAVE_HOST, &buscfg, &slvcfg, use_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED));
|
||||
same_pin_func_sel(TEST_SPI_HOST, TEST_SLAVE_HOST, buscfg, devcfg.spics_io_num);
|
||||
|
||||
//initialize master and slave on the same pins break some of the output configs, fix them
|
||||
same_pin_func_sel(TEST_SPI_HOST, TEST_SLAVE_HOST, buscfg, devcfg.spics_io_num);
|
||||
uint8_t master_tx[TEST_SLV_RX_BUF_LEN], master_rx[TEST_SLV_RX_BUF_LEN];
|
||||
uint8_t slave_tx[TEST_SLV_RX_BUF_LEN], slave_rx[TEST_SLV_RX_BUF_LEN];
|
||||
for (uint8_t i = 0; i < TEST_SLV_RX_BUF_LEN; i++) {
|
||||
master_tx[i] = TEST_SLV_RX_BUF_LEN - i;
|
||||
slave_tx[i] = i + 1;
|
||||
slave_rx[i] = 100;
|
||||
}
|
||||
|
||||
uint8_t master_tx[TEST_SLV_RX_BUF_LEN], slave_rx[TEST_SLV_RX_BUF_LEN];
|
||||
for (uint8_t i = 0; i < TEST_SLV_RX_BUF_LEN; i++) {
|
||||
master_tx[i] = TEST_SLV_RX_BUF_LEN - i;
|
||||
slave_rx[i] = 100;
|
||||
}
|
||||
//------------------------------ trans_len < config_len ------------------------------
|
||||
printf("Testing trans_len < config_len:\n");
|
||||
spi_slave_transaction_t *slave_out, slave_tans = {
|
||||
.tx_buffer = slave_tx,
|
||||
.rx_buffer = slave_rx,
|
||||
.tx_length = 8 * 10, // let tx shorter than rx but larger than trans_len
|
||||
.rx_length = 8 * (TEST_SLV_RX_BUF_LEN),
|
||||
.flags = SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO,
|
||||
};
|
||||
TEST_ESP_OK(spi_slave_queue_trans(TEST_SLAVE_HOST, &slave_tans, portMAX_DELAY));
|
||||
|
||||
//------------------------------ trans_len < config_len ------------------------------
|
||||
printf("Testing trans_len < config_len:\n");
|
||||
spi_slave_transaction_t *slave_out, slave_tans = {
|
||||
.length = 8 * TEST_SLV_RX_BUF_LEN,
|
||||
.rx_buffer = slave_rx,
|
||||
};
|
||||
TEST_ESP_OK(spi_slave_queue_trans(TEST_SLAVE_HOST, &slave_tans, portMAX_DELAY));
|
||||
spi_transaction_t master_tans = {
|
||||
.length = 8 * 7,
|
||||
.rx_buffer = master_rx,
|
||||
.tx_buffer = master_tx,
|
||||
};
|
||||
memset(master_rx, 0x55, TEST_SLV_RX_BUF_LEN);
|
||||
spi_device_polling_transmit(spidev0, &master_tans);
|
||||
|
||||
spi_transaction_t master_tans = {
|
||||
.length = 8 * 7,
|
||||
.tx_buffer = master_tx,
|
||||
};
|
||||
spi_device_polling_transmit(spidev0, &master_tans);
|
||||
TEST_ESP_OK(spi_slave_get_trans_result(TEST_SLAVE_HOST, &slave_out, portMAX_DELAY));
|
||||
|
||||
TEST_ESP_OK(spi_slave_get_trans_result(TEST_SLAVE_HOST, &slave_out, portMAX_DELAY));
|
||||
ESP_LOGI(SLAVE_TAG, "trans_len: %d, config_len rx %d tx %d", master_tans.length / 8, slave_tans.rx_length / 8, slave_tans.tx_length / 8);
|
||||
ESP_LOG_BUFFER_HEX("master tx", master_tans.tx_buffer, master_tans.length / 8);
|
||||
ESP_LOG_BUFFER_HEX("slave rx", slave_tans.rx_buffer, TEST_SLV_RX_BUF_LEN);
|
||||
ESP_LOG_BUFFER_HEX("master rx", master_rx, TEST_SLV_RX_BUF_LEN);
|
||||
|
||||
ESP_LOGI(SLAVE_TAG, "trans_len: %d, config_len %d", slave_tans.trans_len / 8, slave_tans.length / 8);
|
||||
ESP_LOG_BUFFER_HEX("master tx", master_tans.tx_buffer, master_tans.length / 8);
|
||||
ESP_LOG_BUFFER_HEX("slave rx", slave_tans.rx_buffer, TEST_SLV_RX_BUF_LEN);
|
||||
TEST_ASSERT_EQUAL(master_tans.length, slave_tans.trans_len);
|
||||
for (uint8_t i = slave_tans.trans_len; i < slave_tans.rx_length; i += 8) {
|
||||
TEST_ASSERT_EQUAL(slave_rx[i / 8], 100);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_HEX8_ARRAY(slave_tx, master_rx, master_tans.length / 8);
|
||||
|
||||
TEST_ASSERT_EQUAL(master_tans.length, slave_tans.trans_len);
|
||||
for (uint8_t i = slave_tans.trans_len; i < slave_tans.length; i += 8) {
|
||||
TEST_ASSERT_EQUAL(slave_rx[i / 8], 100);
|
||||
}
|
||||
//------------------------------ trans_len > config_len ------------------------------
|
||||
printf("Testing trans_len > config_len:\n");
|
||||
slave_tans.rx_length = 8 * 8;
|
||||
slave_tans.tx_length = 8 * 10;
|
||||
TEST_ESP_OK(spi_slave_queue_trans(TEST_SLAVE_HOST, &slave_tans, portMAX_DELAY));
|
||||
|
||||
//------------------------------ trans_len > config_len ------------------------------
|
||||
printf("Testing trans_len > config_len:\n");
|
||||
slave_tans.length = 8 * 9;
|
||||
TEST_ESP_OK(spi_slave_queue_trans(TEST_SLAVE_HOST, &slave_tans, portMAX_DELAY));
|
||||
master_tans.length = 8 * 13,
|
||||
master_tans.rxlength = 8 * 13;
|
||||
spi_device_polling_transmit(spidev0, &master_tans);
|
||||
|
||||
master_tans.length = 8 * 11,
|
||||
spi_device_polling_transmit(spidev0, &master_tans);
|
||||
TEST_ESP_OK(spi_slave_get_trans_result(TEST_SLAVE_HOST, &slave_out, portMAX_DELAY));
|
||||
|
||||
TEST_ESP_OK(spi_slave_get_trans_result(TEST_SLAVE_HOST, &slave_out, portMAX_DELAY));
|
||||
|
||||
ESP_LOGI(SLAVE_TAG, "trans_len: %d, config_len %d", master_tans.length / 8, slave_tans.trans_len / 8);
|
||||
ESP_LOG_BUFFER_HEX("master tx", master_tans.tx_buffer, master_tans.length / 8);
|
||||
ESP_LOG_BUFFER_HEX("slave rx", slave_tans.rx_buffer, TEST_SLV_RX_BUF_LEN);
|
||||
ESP_LOGI(SLAVE_TAG, "trans_len: %d, config_len rx %d tx %d", master_tans.length / 8, slave_tans.rx_length / 8, slave_tans.tx_length / 8);
|
||||
ESP_LOG_BUFFER_HEX("master tx", master_tans.tx_buffer, master_tans.length / 8);
|
||||
ESP_LOG_BUFFER_HEX("slave rx", slave_tans.rx_buffer, TEST_SLV_RX_BUF_LEN);
|
||||
ESP_LOG_BUFFER_HEX("master rx", master_rx, TEST_SLV_RX_BUF_LEN);
|
||||
|
||||
#if !CONFIG_IDF_TARGET_ESP32 // esp32 already hardware limited trans_len <= config_len
|
||||
TEST_ASSERT_EQUAL(master_tans.length, slave_tans.trans_len);
|
||||
TEST_ASSERT_EQUAL(master_tans.length, slave_tans.trans_len);
|
||||
#endif
|
||||
for (uint8_t i = slave_tans.length; i < TEST_SLV_RX_BUF_LEN * 8; i += 8) {
|
||||
TEST_ASSERT_EQUAL(slave_rx[i / 8], 100);
|
||||
}
|
||||
for (uint8_t i = slave_tans.rx_length; i < TEST_SLV_RX_BUF_LEN * 8; i += 8) {
|
||||
TEST_ASSERT_EQUAL(slave_rx[i / 8], 100);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_HEX8_ARRAY(slave_tx, master_rx, slave_tans.tx_length / 8);
|
||||
|
||||
TEST_ESP_OK(spi_slave_free(TEST_SLAVE_HOST));
|
||||
TEST_ESP_OK(spi_slave_free(TEST_SLAVE_HOST));
|
||||
}
|
||||
TEST_ESP_OK(spi_bus_remove_device(spidev0));
|
||||
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST));
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ typedef struct {
|
||||
* Transaction specific (data), all these parameters will be updated to the
|
||||
* peripheral every transaction.
|
||||
*/
|
||||
uint32_t bitlen; ///< Expected maximum length of the transaction, in bits.
|
||||
uint32_t tx_bitlen; ///< Expected maximum length of the tx transaction, in bits.
|
||||
uint32_t rx_bitlen; ///< Expected maximum length of the rx transaction, in bits.
|
||||
const void *tx_buffer; ///< Data to be sent
|
||||
void *rx_buffer; ///< Buffer to hold the received data.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -46,14 +46,16 @@ void spi_slave_hal_hw_fifo_reset(spi_slave_hal_context_t *hal, bool tx_rst, bool
|
||||
void spi_slave_hal_push_tx_buffer(spi_slave_hal_context_t *hal)
|
||||
{
|
||||
if (hal->tx_buffer) {
|
||||
spi_ll_write_buffer(hal->hw, hal->tx_buffer, hal->bitlen);
|
||||
spi_ll_write_buffer(hal->hw, hal->tx_buffer, hal->tx_bitlen);
|
||||
}
|
||||
}
|
||||
|
||||
void spi_slave_hal_set_trans_bitlen(spi_slave_hal_context_t *hal)
|
||||
{
|
||||
spi_ll_slave_set_rx_bitlen(hal->hw, hal->bitlen);
|
||||
spi_ll_slave_set_tx_bitlen(hal->hw, hal->bitlen);
|
||||
// As full-duplex transaction, register need config to max length to ensure both TX and RX can be transferred
|
||||
uint32_t max_bitlen = (hal->rx_bitlen > hal->tx_bitlen) ? hal->rx_bitlen : hal->tx_bitlen;
|
||||
spi_ll_slave_set_rx_bitlen(hal->hw, max_bitlen);
|
||||
spi_ll_slave_set_tx_bitlen(hal->hw, max_bitlen);
|
||||
}
|
||||
|
||||
void spi_slave_hal_enable_data_line(spi_slave_hal_context_t *hal)
|
||||
@@ -68,12 +70,13 @@ void spi_slave_hal_store_result(spi_slave_hal_context_t *hal)
|
||||
//will be the length sent-1 (i.e. cur_trans->length-1 ), otherwise
|
||||
//the length sent.
|
||||
hal->rcv_bitlen = spi_ll_slave_get_rcv_bitlen(hal->hw);
|
||||
if (hal->rcv_bitlen == hal->bitlen - 1) {
|
||||
uint32_t len_max = (hal->rx_bitlen > hal->tx_bitlen) ? hal->rx_bitlen : hal->tx_bitlen;
|
||||
if (hal->rcv_bitlen == len_max - 1) {
|
||||
hal->rcv_bitlen++;
|
||||
}
|
||||
if (!hal->use_dma && hal->rx_buffer) {
|
||||
//Copy result out
|
||||
spi_ll_read_buffer(hal->hw, hal->rx_buffer, (hal->rcv_bitlen > hal->bitlen) ? hal->bitlen : hal->rcv_bitlen);
|
||||
spi_ll_read_buffer(hal->hw, hal->rx_buffer, (hal->rcv_bitlen > hal->rx_bitlen) ? hal->rx_bitlen : hal->rcv_bitlen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ esp_err_t esp_openthread_platform_workflow_register(esp_openthread_update_func u
|
||||
esp_openthread_platform_workflow_t *current_workflow = s_workflow_list;
|
||||
esp_openthread_platform_workflow_t *before_workflow = NULL;
|
||||
esp_openthread_platform_workflow_t *add_workflow =
|
||||
static_cast<esp_openthread_platform_workflow_t *>(malloc(sizeof(esp_openthread_platform_workflow_t)));
|
||||
static_cast<esp_openthread_platform_workflow_t *>(calloc(1, sizeof(esp_openthread_platform_workflow_t)));
|
||||
ESP_RETURN_ON_FALSE(add_workflow != NULL, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG,
|
||||
"Failed to alloc memory for esp_openthread_workflow");
|
||||
strncpy(add_workflow->name, name, name_len);
|
||||
|
||||
@@ -33,11 +33,6 @@ typedef struct {
|
||||
uint16_t input_buf_len;
|
||||
} pending_transaction_t;
|
||||
|
||||
// DMA bounce buffer for RX — always sized to max(input, output) so MISO is
|
||||
// driven for the full output even when NcpSpi passes a small input buffer.
|
||||
#define SPI_SLAVE_RX_DMA_BUF_SIZE OPENTHREAD_CONFIG_NCP_SPI_BUFFER_SIZE
|
||||
static DRAM_ATTR uint8_t *s_rx_dma_buf = NULL;
|
||||
|
||||
// Guards the BUSY path: only return OT_ERROR_BUSY when a transaction is truly
|
||||
// queued in the driver, so post_trans_cb is guaranteed to fire and re-queue.
|
||||
static volatile DRAM_ATTR bool s_transaction_in_flight = false;
|
||||
@@ -70,11 +65,6 @@ static void IRAM_ATTR handle_spi_transaction_done(spi_slave_transaction_t *trans
|
||||
trans->trans_len = max_buf_len;
|
||||
}
|
||||
|
||||
// Copy RX bounce buffer back to the actual NcpSpi input buffer.
|
||||
if (s_input_buf && s_rx_dma_buf && s_rx_dma_buf != s_input_buf) {
|
||||
memcpy(s_input_buf, s_rx_dma_buf, pending_transaction->input_buf_len);
|
||||
}
|
||||
|
||||
if (s_complete_callback &&
|
||||
s_complete_callback(s_context, (void*)trans->tx_buffer, pending_transaction->output_buf_len,
|
||||
s_input_buf, pending_transaction->input_buf_len, trans->trans_len)) {
|
||||
@@ -86,7 +76,7 @@ esp_err_t esp_openthread_host_rcp_spi_init(const esp_openthread_platform_config_
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
s_spi_config = heap_caps_malloc(sizeof(esp_openthread_spi_slave_config_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
s_spi_config = heap_caps_calloc(1, sizeof(esp_openthread_spi_slave_config_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
ESP_GOTO_ON_FALSE(s_spi_config != NULL, ESP_ERR_NO_MEM, err, OT_PLAT_LOG_TAG,
|
||||
"failed to allocate memory for SPI transaction on internal heap");
|
||||
memcpy(s_spi_config, &(config->host_config.spi_slave_config), sizeof(esp_openthread_spi_slave_config_t));
|
||||
@@ -104,12 +94,10 @@ esp_err_t esp_openthread_host_rcp_spi_init(const esp_openthread_platform_config_
|
||||
gpio_set_pull_mode(s_spi_config->bus_config.sclk_io_num, GPIO_PULLUP_ONLY);
|
||||
gpio_set_pull_mode(s_spi_config->slave_config.spics_io_num, GPIO_PULLUP_ONLY);
|
||||
|
||||
s_spi_transaction = heap_caps_malloc(sizeof(spi_slave_transaction_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
s_spi_transaction = heap_caps_calloc(1, sizeof(spi_slave_transaction_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
ESP_GOTO_ON_FALSE(s_spi_transaction != NULL, ESP_ERR_NO_MEM, err, OT_PLAT_LOG_TAG, "failed to allocate memory for SPI transaction on internal heap");
|
||||
s_pending_transaction = heap_caps_malloc(sizeof(pending_transaction_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
s_pending_transaction = heap_caps_calloc(1, sizeof(pending_transaction_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
ESP_GOTO_ON_FALSE(s_pending_transaction != NULL, ESP_ERR_NO_MEM, err, OT_PLAT_LOG_TAG, "failed to allocate memory for pending transaction on internal heap");
|
||||
s_rx_dma_buf = heap_caps_malloc(SPI_SLAVE_RX_DMA_BUF_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||
ESP_GOTO_ON_FALSE(s_rx_dma_buf != NULL, ESP_ERR_NO_MEM, err, OT_PLAT_LOG_TAG, "failed to allocate memory for RX DMA buffer on internal heap");
|
||||
|
||||
s_spi_transaction->user = (void *)s_pending_transaction;
|
||||
|
||||
@@ -128,8 +116,6 @@ err:
|
||||
s_spi_transaction = NULL;
|
||||
heap_caps_free(s_pending_transaction);
|
||||
s_pending_transaction = NULL;
|
||||
heap_caps_free(s_rx_dma_buf);
|
||||
s_rx_dma_buf = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -141,11 +127,9 @@ void esp_openthread_spi_slave_deinit(void)
|
||||
heap_caps_free(s_spi_config);
|
||||
heap_caps_free(s_spi_transaction);
|
||||
heap_caps_free(s_pending_transaction);
|
||||
heap_caps_free(s_rx_dma_buf);
|
||||
s_spi_config = NULL;
|
||||
s_spi_transaction = NULL;
|
||||
s_pending_transaction = NULL;
|
||||
s_rx_dma_buf = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -162,7 +146,6 @@ otError IRAM_ATTR otPlatSpiSlavePrepareTransaction(uint8_t *aOutputBuf, uint16_t
|
||||
uint16_t aInputBufLen, bool aRequestTransactionFlag)
|
||||
{
|
||||
esp_err_t trans_state = ESP_OK;
|
||||
uint16_t trans_length = 0;
|
||||
|
||||
if (aOutputBuf != NULL) {
|
||||
s_output_buf = aOutputBuf;
|
||||
@@ -173,11 +156,6 @@ otError IRAM_ATTR otPlatSpiSlavePrepareTransaction(uint8_t *aOutputBuf, uint16_t
|
||||
s_input_len = aInputBufLen;
|
||||
}
|
||||
|
||||
// Use max(input, output) so MISO is driven for the full output frame;
|
||||
// s_rx_dma_buf absorbs extra RX bytes to avoid overflowing the NcpSpi buffer.
|
||||
uint16_t trans_data_len = (s_input_len > s_output_len) ? s_input_len : s_output_len;
|
||||
trans_length = trans_data_len * CHAR_BIT;
|
||||
|
||||
// In task context, return BUSY only when a transaction is already in flight
|
||||
// AND CS is asserted — ensures post_trans_cb will fire to re-queue.
|
||||
// In ISR context (post_trans_cb) we always queue unconditionally.
|
||||
@@ -186,8 +164,10 @@ otError IRAM_ATTR otPlatSpiSlavePrepareTransaction(uint8_t *aOutputBuf, uint16_t
|
||||
ESP_EARLY_LOGE(SPI_SLAVE_TAG, "SPI busy");
|
||||
return OT_ERROR_BUSY;
|
||||
}
|
||||
s_spi_transaction->length = trans_length;
|
||||
s_spi_transaction->rx_buffer = s_rx_dma_buf;
|
||||
s_spi_transaction->length = 0;
|
||||
s_spi_transaction->tx_length = s_output_len * CHAR_BIT;
|
||||
s_spi_transaction->rx_length = s_input_len * CHAR_BIT;
|
||||
s_spi_transaction->rx_buffer = s_input_buf;
|
||||
s_spi_transaction->tx_buffer = s_output_buf;
|
||||
|
||||
pending_transaction_t *pending_transaction = (pending_transaction_t *)s_spi_transaction->user;
|
||||
|
||||
@@ -91,7 +91,7 @@ static void trel_browse_notifier(mdns_result_t *result)
|
||||
result = result->next;
|
||||
continue;
|
||||
}
|
||||
trel_txt = malloc(trel_txt_len);
|
||||
trel_txt = calloc(1, trel_txt_len);
|
||||
ESP_RETURN_ON_FALSE(trel_txt != NULL, , OT_PLAT_LOG_TAG, "Failed to malloc buffer for TREL TXT");
|
||||
|
||||
size_t offset = 0;
|
||||
@@ -128,10 +128,9 @@ static void handle_trel_udp_recv(void *ctx, struct udp_pcb *pcb, struct pbuf *p,
|
||||
uint64_t event_trel_rx = 1;
|
||||
ESP_LOGD(OT_PLAT_LOG_TAG, "Receive from %s:%d", ip6addr_ntoa(&(addr->u_addr.ip6)), port);
|
||||
ESP_GOTO_ON_FALSE(atomic_load(&s_recv_queue.used) < CONFIG_OPENTHREAD_TREL_BUFFER_SIZE, ESP_ERR_NO_MEM, exit, OT_PLAT_LOG_TAG, "trel receive buffer full!");
|
||||
source_addr = (otSockAddr *)malloc(sizeof(otSockAddr));
|
||||
source_addr = (otSockAddr *)calloc(1, sizeof(otSockAddr));
|
||||
ESP_GOTO_ON_FALSE(source_addr, ESP_ERR_NO_MEM, exit, OT_PLAT_LOG_TAG, "Failed to allocate buf for Thread TREL");
|
||||
|
||||
memset(source_addr, 0, sizeof(otSockAddr));
|
||||
source_addr->mPort = port;
|
||||
memcpy(&source_addr->mAddress.mFields.m32, addr->u_addr.ip6.addr, sizeof(addr->u_addr.ip6.addr));
|
||||
s_trel_receive_buffer[s_recv_queue.tail].source_addr = source_addr;
|
||||
@@ -185,7 +184,7 @@ esp_err_t esp_openthread_trel_process(otInstance *aInstance, const esp_openthrea
|
||||
source_addr = s_trel_receive_buffer[s_recv_queue.head].source_addr;
|
||||
|
||||
if (recv_buf->next != NULL) {
|
||||
data_buf = (uint8_t *)malloc(recv_buf->tot_len);
|
||||
data_buf = (uint8_t *)calloc(1, recv_buf->tot_len);
|
||||
if (data_buf) {
|
||||
pbuf_copy_partial(recv_buf, data_buf, recv_buf->tot_len, 0);
|
||||
} else {
|
||||
|
||||
@@ -120,7 +120,7 @@ static void udp_recv_task(void *ctx)
|
||||
memcpy(&message_info.mPeerAddr, ip_2_ip6(&task->addr)->addr, sizeof(message_info.mPeerAddr));
|
||||
|
||||
if (recv_buf->next != NULL) {
|
||||
data_buf = (uint8_t *)malloc(recv_buf->tot_len);
|
||||
data_buf = (uint8_t *)calloc(1, recv_buf->tot_len);
|
||||
if (data_buf != NULL) {
|
||||
data_buf_to_free = data_buf;
|
||||
pbuf_copy_partial(recv_buf, data_buf, recv_buf->tot_len, 0);
|
||||
@@ -151,7 +151,7 @@ exit:
|
||||
|
||||
static void handle_udp_recv(void *ctx, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, uint16_t port)
|
||||
{
|
||||
udp_recv_task_t *task = (udp_recv_task_t *)malloc(sizeof(udp_recv_task_t));
|
||||
udp_recv_task_t *task = (udp_recv_task_t *)calloc(1, sizeof(udp_recv_task_t));
|
||||
const struct ip6_hdr *ip6_hdr = ip6_current_header();
|
||||
#if CONFIG_LWIP_IPV4
|
||||
const struct ip_hdr *ip4_hdr = ip4_current_header();
|
||||
@@ -389,7 +389,7 @@ static inline bool is_addr_ip6_any(const ip_addr_t *addr)
|
||||
|
||||
otError otPlatUdpSend(otUdpSocket *udp_socket, otMessage *message, const otMessageInfo *message_info)
|
||||
{
|
||||
udp_send_task_t *task = (udp_send_task_t *)malloc(sizeof(udp_send_task_t));
|
||||
udp_send_task_t *task = (udp_send_task_t *)calloc(1, sizeof(udp_send_task_t));
|
||||
otError error = OT_ERROR_NONE;
|
||||
VerifyOrExit(task != NULL, error = OT_ERROR_NO_BUFS);
|
||||
task->pcb = (struct udp_pcb *)udp_socket->mHandle;
|
||||
@@ -448,7 +448,7 @@ static void udp_multicast_join_leave_task(void *ctx)
|
||||
otError otPlatUdpJoinMulticastGroup(otUdpSocket *socket, otNetifIdentifier netif_id, const otIp6Address *addr)
|
||||
{
|
||||
udp_multicast_join_leave_task_t *task =
|
||||
(udp_multicast_join_leave_task_t *)malloc(sizeof(udp_multicast_join_leave_task_t));
|
||||
(udp_multicast_join_leave_task_t *)calloc(1, sizeof(udp_multicast_join_leave_task_t));
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
VerifyOrExit(task != NULL, error = OT_ERROR_NO_BUFS);
|
||||
@@ -467,7 +467,7 @@ exit:
|
||||
otError otPlatUdpLeaveMulticastGroup(otUdpSocket *socket, otNetifIdentifier netif_id, const otIp6Address *addr)
|
||||
{
|
||||
udp_multicast_join_leave_task_t *task =
|
||||
(udp_multicast_join_leave_task_t *)malloc(sizeof(udp_multicast_join_leave_task_t));
|
||||
(udp_multicast_join_leave_task_t *)calloc(1, sizeof(udp_multicast_join_leave_task_t));
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
VerifyOrExit(task != NULL, error = OT_ERROR_NO_BUFS);
|
||||
|
||||
@@ -85,7 +85,7 @@ esp_err_t SpiSpinelInterface::Enable(const esp_openthread_spi_host_config_t &spi
|
||||
|
||||
ESP_RETURN_ON_FALSE(m_event_fd >= 0, ESP_FAIL, OT_PLAT_LOG_TAG, "fail to get event fd");
|
||||
|
||||
m_rx_dma_buf = (uint8_t *)heap_caps_malloc(kSPIFrameSize, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||
m_rx_dma_buf = (uint8_t *)heap_caps_calloc(1, kSPIFrameSize, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||
ESP_RETURN_ON_FALSE(m_rx_dma_buf != nullptr, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, "fail to alloc SPI RX DMA buffer");
|
||||
|
||||
ESP_LOGI(OT_PLAT_LOG_TAG, "spinel SPI interface initialization completed");
|
||||
|
||||
@@ -103,7 +103,7 @@ void ReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError)
|
||||
{
|
||||
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
|
||||
assert(s_esp_radio_spinel_callbacks[idx].receive_done);
|
||||
uint8_t *frame = (uint8_t *)malloc(aFrame->mLength + 1);
|
||||
uint8_t *frame = (uint8_t *)calloc(1, aFrame->mLength + 1);
|
||||
esp_ieee802154_frame_info_t frame_info;
|
||||
if (frame) {
|
||||
frame[0] = aFrame->mLength;
|
||||
@@ -123,14 +123,14 @@ void TransmitDone(otInstance *aInstance, otRadioFrame *aFrame, otRadioFrame *aAc
|
||||
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
|
||||
assert(s_esp_radio_spinel_callbacks[idx].transmit_done && s_esp_radio_spinel_callbacks[idx].transmit_failed);
|
||||
if (aError == OT_ERROR_NONE) {
|
||||
uint8_t *frame = (uint8_t *)malloc(aFrame->mLength + 1);
|
||||
uint8_t *frame = (uint8_t *)calloc(1, aFrame->mLength + 1);
|
||||
uint8_t *ack = nullptr;
|
||||
if (frame) {
|
||||
esp_ieee802154_frame_info_t ack_info;
|
||||
frame[0] = aFrame->mLength;
|
||||
memcpy((void *)(frame + 1), aFrame->mPsdu, frame[0]);
|
||||
if (aAckFrame) {
|
||||
ack = (uint8_t *)malloc(aAckFrame->mLength + 1);
|
||||
ack = (uint8_t *)calloc(1, aAckFrame->mLength + 1);
|
||||
if (ack) {
|
||||
ack[0] = aAckFrame->mLength;
|
||||
memcpy((void *)(ack + 1), aAckFrame->mPsdu, ack[0]);
|
||||
@@ -170,7 +170,7 @@ void TxStarted(otInstance *aInstance, otRadioFrame *aFrame)
|
||||
{
|
||||
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
|
||||
assert(s_esp_radio_spinel_callbacks[idx].transmit_started);
|
||||
uint8_t *frame = (uint8_t *)malloc(aFrame->mLength + 1);
|
||||
uint8_t *frame = (uint8_t *)calloc(1, aFrame->mLength + 1);
|
||||
if (frame) {
|
||||
frame[0] = aFrame->mLength;
|
||||
memcpy((void *)(frame + 1), aFrame->mPsdu, frame[0]);
|
||||
|
||||
@@ -90,7 +90,7 @@ esp_err_t UartSpinelInterface::Enable(const esp_radio_spinel_uart_config_t &radi
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
m_uart_rx_buffer = static_cast<uint8_t *>(heap_caps_malloc(kMaxFrameSize, MALLOC_CAP_8BIT));
|
||||
m_uart_rx_buffer = static_cast<uint8_t *>(heap_caps_calloc(1, kMaxFrameSize, MALLOC_CAP_8BIT));
|
||||
if (m_uart_rx_buffer == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
@@ -117,6 +117,8 @@ The amount of data that the driver can read or write to the buffers is limited b
|
||||
|
||||
If the length of the transmission is greater than the buffer length, only the initial number of bits specified in the :cpp:member:`spi_slave_transaction_t::length` member will be sent and received. In this case, :cpp:member:`spi_slave_transaction_t::trans_len` is set to :cpp:member:`spi_slave_transaction_t::length` instead of the actual transaction length. To meet the actual transaction length requirements, set :cpp:member:`spi_slave_transaction_t::length` to a value greater than the maximum :cpp:member:`spi_slave_transaction_t::trans_len` expected. If the transmission length is shorter than the buffer length, only the data equal to the length of the buffer will be transmitted.
|
||||
|
||||
When you need to specify different TX/RX lengths in a single transaction, you can use the :cpp:member:`spi_slave_transaction_t::tx_length` and :cpp:member:`spi_slave_transaction_t::rx_length` members to specify the lengths of TX and RX respectively. This configuration is mutually exclusive with :cpp:member:`spi_slave_transaction_t::length`, and cannot be used simultaneously.
|
||||
|
||||
GPIO Matrix and IO_MUX
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -117,6 +117,8 @@ SPI 传输事务
|
||||
|
||||
如果传输长度超过缓存区长度,则只有在 :cpp:member:`spi_slave_transaction_t::length` 中指定的初始比特数会被发送和接收。此时, :cpp:member:`spi_slave_transaction_t::trans_len` 被设置为 :cpp:member:`spi_slave_transaction_t::length` 而非实际传输事务长度。若需满足实际传输事务长度的要求,请将 :cpp:member:`spi_slave_transaction_t::length` 设置为大于 :cpp:member:`spi_slave_transaction_t::trans_len` 预期最大值的值。如果传输长度短于缓存区长度,则只传输与缓存区长度相等的数据。
|
||||
|
||||
当需要在一次传输中单独指定不同的 TX/RX 长度时,可以使用 :cpp:member:`spi_slave_transaction_t::tx_length` 和 :cpp:member:`spi_slave_transaction_t::rx_length` 来分别指定 TX 和 RX 的长度。该配置和 :cpp:member:`spi_slave_transaction_t::length` 互斥,不可同时使用。
|
||||
|
||||
GPIO 交换矩阵和 IO_MUX
|
||||
----------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user