fix(driver_spi): added bit trans length check for master driver

Closes https://github.com/espressif/esp-idf/issues/16049

Closes https://github.com/espressif/esp-idf/issues/17725
This commit is contained in:
wanckl
2026-01-14 20:09:33 +08:00
parent 1a86c86702
commit 53458fc6aa
14 changed files with 165 additions and 15 deletions
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -1061,6 +1061,8 @@ static SPI_MASTER_ISR_ATTR esp_err_t check_trans_valid(spi_device_handle_t handl
const spi_bus_attr_t* bus_attr = host->bus_attr;
bool tx_enabled = (trans_desc->flags & SPI_TRANS_USE_TXDATA) || (trans_desc->tx_buffer);
bool rx_enabled = (trans_desc->flags & SPI_TRANS_USE_RXDATA) || (trans_desc->rx_buffer);
uint8_t txlen_extra = trans_desc->length % 8;
uint8_t rxlen_extra = trans_desc->rxlength % 8;
spi_transaction_ext_t *t_ext = (spi_transaction_ext_t *)trans_desc;
bool dummy_enabled = (((trans_desc->flags & SPI_TRANS_VARIABLE_DUMMY) ? t_ext->dummy_bits : handle->cfg.dummy_bits) != 0);
bool extra_dummy_enabled = handle->hal_dev.timing_conf.timing_dummy;
@@ -1082,6 +1084,7 @@ static SPI_MASTER_ISR_ATTR esp_err_t check_trans_valid(spi_device_handle_t handl
SPI_CHECK(!((trans_desc->flags & (SPI_TRANS_MODE_DIO | SPI_TRANS_MODE_QIO)) && !is_half_duplex), "Incompatible when setting to both multi-line mode and half duplex mode", ESP_ERR_INVALID_ARG);
#ifdef CONFIG_IDF_TARGET_ESP32
SPI_CHECK(!is_half_duplex || !bus_attr->dma_enabled || !rx_enabled || !tx_enabled, "SPI half duplex mode does not support using DMA with both MOSI and MISO phases.", ESP_ERR_INVALID_ARG);
SPI_CHECK(!bus_attr->dma_enabled || !rxlen_extra, "rx unaligned byte with DMA is not supported", ESP_ERR_NOT_SUPPORTED);
#endif
#if !SOC_SPI_HD_BOTH_INOUT_SUPPORTED
//On these chips, HW doesn't support using both TX and RX phases when in halfduplex mode
@@ -1101,6 +1104,8 @@ static SPI_MASTER_ISR_ATTR esp_err_t check_trans_valid(spi_device_handle_t handl
//Dummy phase is not available when both data out and in are enabled, regardless of FD or HD mode.
SPI_CHECK(!tx_enabled || !rx_enabled || !dummy_enabled || !extra_dummy_enabled, "Dummy phase is not available when both data out and in are enabled", ESP_ERR_INVALID_ARG);
SPI_CHECK(!txlen_extra || (txlen_extra >= SPI_LL_TX_MINI_EXTRA_BITS), "tx %d-bit is not supported on this(or this version) chip", ESP_ERR_NOT_SUPPORTED, trans_desc->length);
SPI_CHECK(!rxlen_extra || (rxlen_extra >= SPI_LL_RX_MINI_EXTRA_BITS), "rx %d-bit is not supported on this chip", ESP_ERR_NOT_SUPPORTED, trans_desc->rxlength);
if (bus_attr->dma_enabled) {
SPI_CHECK(trans_desc->length <= SPI_LL_DMA_MAX_BIT_LEN, "txdata transfer > hardware max supported len", ESP_ERR_INVALID_ARG);
SPI_CHECK(trans_desc->rxlength <= SPI_LL_DMA_MAX_BIT_LEN, "rxdata transfer > hardware max supported len", ESP_ERR_INVALID_ARG);
@@ -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
*/
@@ -35,6 +35,19 @@ const static char TAG[] = "test_spi";
// There is no input-only pin except on esp32 and esp32s2
#define TEST_SOC_HAS_INPUT_ONLY_PINS (CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2)
static uint8_t bitswap(uint8_t in)
{
uint8_t out = 0;
for (int i = 0; i < 8; i++) {
out = out >> 1;
if (in & 0x80) {
out |= 0x80;
}
in = in << 1;
}
return out;
}
static void check_spi_pre_n_for(int clk, int pre, int n)
{
spi_device_handle_t handle;
@@ -827,19 +840,6 @@ TEST_CASE("SPI Master DMA test: length, start, not aligned", "[spi]")
#if (TEST_SPI_PERIPH_NUM >= 2)
//These will only be enabled on chips with 2 or more SPI peripherals
static uint8_t bitswap(uint8_t in)
{
uint8_t out = 0;
for (int i = 0; i < 8; i++) {
out = out >> 1;
if (in & 0x80) {
out |= 0x80;
}
in = in << 1;
}
return out;
}
void test_cmd_addr(spi_slave_task_context_t *slave_context, bool lsb_first)
{
spi_device_handle_t spi;
@@ -1792,6 +1792,91 @@ TEST_CASE("test_bus_free_safty_to_remain_devices", "[spi]")
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST));
}
TEST_CASE("Test master 1-9 bits tx/rx", "[spi]")
{
spi_device_handle_t dev0;
spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG();
buscfg.flags |= SPICOMMON_BUSFLAG_GPIO_PINS;
spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG();
spi_transaction_t trans_cfg = {
.tx_data[0] = 0xc9,
.tx_data[1] = 0xad,
.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA,
};
uint8_t exp[2], reversed[2] = {bitswap(trans_cfg.tx_data[0]), bitswap(trans_cfg.tx_data[1])};
for (int dma = 0; dma < 2; dma++) {
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED));
spitest_gpio_output_sel(buscfg.miso_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spid_out); // set loopback
for (int cfg = 0; cfg < 2; cfg++) {
printf("Test %s with DMA: %s\n", cfg ? "LSB" : "MSB", dma ? "AUTO" : "DISABLED");
devcfg.flags = cfg ? SPI_DEVICE_RXBIT_LSBFIRST : 0;
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &dev0));
for (int i = 1; i <= 9; i++) {
trans_cfg.length = i;
trans_cfg.rxlength = i;
if (i <= 8) {
exp[0] = devcfg.flags & SPI_DEVICE_RXBIT_LSBFIRST ? reversed[0] & (0xff >> (8 - i)) : trans_cfg.tx_data[0] & (0xff << (8 - i));
}
exp[1] = devcfg.flags & SPI_DEVICE_RXBIT_LSBFIRST ? reversed[1] & (0xff >> (16 - i)) : trans_cfg.tx_data[1] & (0xff << (16 - i));
#if CONFIG_IDF_TARGET_ESP32
if ((i % 8) && dma) {
#else
if ((i % 8) && ((i % 8 < SPI_LL_TX_MINI_EXTRA_BITS) || (i % 8 < SPI_LL_RX_MINI_EXTRA_BITS))) {
#endif
TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, spi_device_transmit(dev0, &trans_cfg));
continue;
}
TEST_ESP_OK(spi_device_transmit(dev0, &trans_cfg));
printf("len %d bits tx %x %x reversed %x %x exp %2x %2x rx %2x %2x\n", i, trans_cfg.tx_data[0], trans_cfg.tx_data[1], reversed[0], reversed[1], exp[0], exp[1], trans_cfg.rx_data[0], trans_cfg.rx_data[1]);
TEST_ASSERT_EQUAL_HEX8_ARRAY(exp, trans_cfg.rx_data, 2);
memset(trans_cfg.rx_data, 0, sizeof(trans_cfg.rx_data));
}
TEST_ESP_OK(spi_bus_remove_device(dev0));
printf("--------------------------------\n");
}
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST));
}
}
TEST_CASE("Test master 1-9 bits rx only", "[spi]")
{
spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG();
buscfg.flags |= SPICOMMON_BUSFLAG_GPIO_PINS;
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_DISABLED));
spi_device_handle_t dev0;
spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG();
devcfg.flags |= SPI_DEVICE_HALFDUPLEX;
TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &dev0));
spi_transaction_t trans_cfg = {
.flags = SPI_TRANS_USE_RXDATA,
};
gpio_set_level(buscfg.miso_io_num, 1);
spitest_gpio_output_sel(buscfg.miso_io_num, FUNC_GPIO, SIG_GPIO_OUT_IDX);
for (int i = 1; i <= 9; i++) {
trans_cfg.rxlength = i;
if ((i % 8) && (i % 8 < SPI_LL_RX_MINI_EXTRA_BITS)) {
TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, spi_device_transmit(dev0, &trans_cfg));
continue;
}
TEST_ESP_OK(spi_device_transmit(dev0, &trans_cfg));
printf("len %d bits rx %2x %2x\n", i, trans_cfg.rx_data[0], trans_cfg.rx_data[1]);
if (i <= 8) {
TEST_ASSERT_EQUAL_HEX8((0xff << (8 - i)), trans_cfg.rx_data[0]);
}
TEST_ASSERT_EQUAL_HEX8((0xff << (16 - i)), trans_cfg.rx_data[1]);
memset(trans_cfg.rx_data, 0, sizeof(trans_cfg.rx_data));
}
TEST_ESP_OK(spi_bus_remove_device(dev0));
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST));
}
TEST_CASE("test_spi_master_sleep_retention", "[spi]")
{
// Prepare a TOP PD sleep
@@ -42,6 +42,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN (1 << 24) //reg len: 24 bits
#define SPI_LL_CPU_MAX_BIT_LEN (16 * 32) //Fifo len: 16 words
#define SPI_LL_TX_MINI_EXTRA_BITS 1 //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 1 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_MOSI_FREE_LEVEL 0 //Default level after bus initialized
// CS_WORKAROUND: SPI slave with using DMA, the rx dma suffers from unexpected transactions
@@ -41,6 +41,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN (1 << 18) //reg len: 18 bits
#define SPI_LL_CPU_MAX_BIT_LEN (16 * 32) //Fifo len: 16 words
#define SPI_LL_TX_MINI_EXTRA_BITS 2 //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 1 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_MOSI_FREE_LEVEL 1 //Default level after bus initialized
/**
@@ -41,6 +41,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN (1 << 18) //reg len: 18 bits
#define SPI_LL_CPU_MAX_BIT_LEN (16 * 32) //Fifo len: 16 words
#define SPI_LL_TX_MINI_EXTRA_BITS 2 //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 1 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_MOSI_FREE_LEVEL 1 //Default level after bus initialized
/**
@@ -41,6 +41,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN SPI_MS_DATA_BITLEN
#define SPI_LL_CPU_MAX_BIT_LEN (16 * 32) //Fifo len: 16 words
#define SPI_LL_TX_MINI_EXTRA_BITS 1 //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 1 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_MOSI_FREE_LEVEL 1 //Default level after bus initialized
#define SPI_LL_SUPPORT_CLK_SRC_PRE_DIV 1 //clock source have divider before peripheral
#define SPI_LL_PERIPH_CLK_DIV_MAX ((SPI_CLKCNT_N + 1) * (SPI_CLKDIV_PRE + 1)) //peripheral internal maxmum clock divider
@@ -41,6 +41,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN (1 << 18) //reg len: 18 bits
#define SPI_LL_CPU_MAX_BIT_LEN (16 * 32) //Fifo len: 16 words
#define SPI_LL_TX_MINI_EXTRA_BITS 2 //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 1 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_MOSI_FREE_LEVEL 1 //Default level after bus initialized
/**
@@ -41,6 +41,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN SPI_MS_DATA_BITLEN
#define SPI_LL_CPU_MAX_BIT_LEN (16 * 32) //Fifo len: 16 words
#define SPI_LL_TX_MINI_EXTRA_BITS 1 //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 1 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_MOSI_FREE_LEVEL 1 //Default level after bus initialized
#define SPI_LL_SUPPORT_CLK_SRC_PRE_DIV 1 //clock source have divider before peripheral
#define SPI_LL_PERIPH_CLK_DIV_MAX ((SPI_CLKCNT_N + 1) * (SPI_CLKDIV_PRE + 1)) //peripheral internal maxmum clock divider
@@ -43,6 +43,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN (1 << 18) //reg len: 18 bits
#define SPI_LL_CPU_MAX_BIT_LEN (16 * 32) //Fifo len: 16 words
#define SPI_LL_TX_MINI_EXTRA_BITS (ESP_CHIP_REV_ABOVE(efuse_hal_chip_revision(), 102) ? 1 : 2) //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 1 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_MOSI_FREE_LEVEL 1 //Default level after bus initialized
/**
@@ -41,6 +41,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN SPI_MS_DATA_BITLEN
#define SPI_LL_CPU_MAX_BIT_LEN (16 * 32) //Fifo len: 16 words
#define SPI_LL_TX_MINI_EXTRA_BITS 1 //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 1 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_SUPPORT_CLK_SRC_PRE_DIV 1 //clock source have divider before peripheral
#define SPI_LL_PERIPH_CLK_DIV_MAX ((SPI_CLKCNT_N + 1) * (SPI_CLKDIV_PRE + 1)) //peripheral internal maxmum clock divider
#define SPI_LL_MOSI_FREE_LEVEL 1 //Default level after bus initialized
@@ -48,6 +48,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN (1 << 23) //reg len: 23 bits
#define SPI_LL_CPU_MAX_BIT_LEN (18 * 32) //Fifo len: 18 words
#define SPI_LL_TX_MINI_EXTRA_BITS 1 //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 8 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_MOSI_FREE_LEVEL 1 //Default level after bus initialized
#define SPI_LL_DMA_SHARED 1 //spi_dma shared with adc and dac on S2
@@ -43,6 +43,8 @@ extern "C" {
#define SPI_LL_DMA_MAX_BIT_LEN (1 << 18) //reg len: 18 bits
#define SPI_LL_CPU_MAX_BIT_LEN (16 * 32) //Fifo len: 16 words
#define SPI_LL_TX_MINI_EXTRA_BITS 2 //Minimum length of TX non byte aligned data in bits
#define SPI_LL_RX_MINI_EXTRA_BITS 1 //Minimum length of RX non byte aligned data in bits
#define SPI_LL_MOSI_FREE_LEVEL 1 //Default level after bus initialized
/**
@@ -367,6 +367,25 @@ An SPI Host reads and writes data into memory byte by byte. By default, data is
For example, if ``0b00010`` needs to be sent, it should be written into a ``uint8_t`` variable, and the length for reading should be set to 5 bits. The Device will still receive 8 bits with 3 additional "random" bits, so the reading must be performed correctly.
Not all chips support data transmission with any bit lengths. Sending or receiving unsupported bit lengths will return :c:macro:`ESP_ERR_NOT_SUPPORTED` error. The supported lengths are shown in the table below (**YES** means support any bits length, **NO** means bytes (8 bits) only):
+------+--------+-------+----------+--------------------+---------------------+
| | ESP32 | ESP32-S2 | ESP32-S3/C2/C3/C6 | ESP32-H2/P4/C5/C61 |
+======+========+=======+==========+====================+=====================+
| TX | DMA | YES | YES | (bit_len % 8) != 1 | YES |
+ +--------+-------+----------+--------------------+---------------------+
| | NO DMA | YES | YES | (bit_len % 8) != 1 | YES |
+------+--------+-------+----------+--------------------+---------------------+
| RX | DMA | NO | NO | YES | YES |
+ +--------+-------+----------+--------------------+---------------------+
| | NO DMA | YES | NO | YES | YES |
+------+--------+-------+----------+--------------------+---------------------+
If you still need to use unsupported bit lengths, you can use the following alternatives:
1. Use :cpp:member:`spi_transaction_t::cmd` and :cpp:member:`spi_transaction_t::addr` and data phase combination. The drawback is that the command and address phases do not receive data from the slave device.
2. Use two supported length transmissions combination, like ``2+7`` to implement ``9 bit`` transmission, while keeping the CS line valid. The drawback is that there is a minimum time interval between two transmissions (see :ref:`transaction_time_cost`), and the overall transfer rate is lower.
On top of that, {IDF_TARGET_NAME} is a little-endian chip, which means that the least significant byte of ``uint16_t`` and ``uint32_t`` variables is stored at the smallest address. Hence, if ``uint16_t`` is stored in memory, bits [7:0] are sent first, followed by bits [15:8].
For cases when the data to be transmitted has a size differing from ``uint8_t`` arrays, the following macros can be used to transform data to the format that can be sent by the SPI driver directly:
@@ -528,6 +547,7 @@ There are three factors limiting the transfer speed:
The main parameter that determines the transfer speed for large transactions is clock frequency. For multiple small transactions, the transfer speed is mostly determined by the length of transaction intervals.
.. _transaction_time_cost:
Transaction Duration
^^^^^^^^^^^^^^^^^^^^
@@ -367,6 +367,25 @@ SPI 主机逐字节地将数据读入和写入内存。默认情况下,数据
例如,如果需要发送 ``0b00010``,则应将其写成 ``uint8_t`` 变量,读取长度设置为 5 位。此时,设备仍然会收到 8 位数据,并另有 3 个“随机”位,所以读取过程必须准确。
不是所有芯片都支持任意位长度的数据传输,发送或接收不支持的位长度时会返回 :c:macro:`ESP_ERR_NOT_SUPPORTED` 错误。支持的长度如下表所示(**YES** 表示支持任意长度,**NO** 表示只支持整字节 8 bits 长度):
+------+--------+-------+----------+--------------------+---------------------+
| | ESP32 | ESP32-S2 | ESP32-S3/C2/C3/C6 | ESP32-H2/P4/C5/C61 |
+======+========+=======+==========+====================+=====================+
| TX | DMA | YES | YES | (bit_len % 8) != 1 | YES |
+ +--------+-------+----------+--------------------+---------------------+
| | NO DMA | YES | YES | (bit_len % 8) != 1 | YES |
+------+--------+-------+----------+--------------------+---------------------+
| RX | DMA | NO | NO | YES | YES |
+ +--------+-------+----------+--------------------+---------------------+
| | NO DMA | YES | NO | YES | YES |
+------+--------+-------+----------+--------------------+---------------------+
如仍需使用不支持的长度传输,根据表格可以有以下替代方法:
1. 使用 :cpp:member:`spi_transaction_t::cmd`:cpp:member:`spi_transaction_t::addr` 和数据阶段组合。缺点:命令和地址阶段不接收从机数据。
2. 使用两次支持长度的传输组合,比如 ``2+7`` 实现 ``9 bit`` 传输,期间保持 CS 线有效。缺点:两次传输之间有最小时间间隔(见 :ref:`transaction_time_cost`),整体速率较低。
此外,{IDF_TARGET_NAME} 属于小端芯片,即 ``uint16_t````uint32_t`` 变量的最低有效位存储在最小的地址。因此,如果 ``uint16_t`` 存储在内存中,则首先发送位 [7:0],其次是位 [15:8]。
在某些情况下,要传输的数据大小与 ``uint8_t`` 数组不同,可使用以下宏将数据转换为可由 SPI 驱动直接发送的格式:
@@ -528,6 +547,7 @@ GPIO 矩阵与 IO_MUX 管脚
影响大传输事务传输速度的主要参数是时钟频率。而多个小传输事务的传输速度主要由传输事务间隔时长决定。
.. _transaction_time_cost:
传输事务持续时间
^^^^^^^^^^^^^^^^^^^^