ESP32 SPI Slave: Sporadic frame loss when master sends 20-byte frames in every ~20us
Posted: Thu Sep 18, 2025 9:31 am
Hello,
I’m running into a sporadic frame loss issue on ESP32 when using the SPI slave driver with DMA.
The SPI master continuously sends 20-byte data frames using its dma channel. (STM32 750KHz)
Occasionally, some frames are lost (not delivered to my receive task), even though the master clock and timing are stable.
If i add small delay (1ms) between each master transmit frames, then my esp32 receiver is working fine.
I tried with out a my_post_trans_callback() aswell- (using spi_slave_get_trans_result() inside Receiver task). But the results were same.
Did I miss some basic slave configurations here ? Any problem with my receiver logic.
Any advice or insights are greatly appreciated!
Relevant Code:
#define NUM_BUFFERS 24
#define PROTOCOL_PACKET_LEN 20
DMA_ATTR static uint8_t rx_buffers[NUM_BUFFERS][PROTOCOL_PACKET_LEN];
DMA_ATTR static uint8_t tx_buffers[NUM_BUFFERS][PROTOCOL_PACKET_LEN];
static spi_slave_transaction_t transactions[NUM_BUFFERS];
static QueueHandle_t completed_trans_queue;
void IRAM_ATTR my_post_trans_callback(spi_slave_transaction_t *trans) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(completed_trans_queue, &trans, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
void IRAM_ATTR spi_receive_task(void *pvParameters) {
spi_slave_transaction_t *completed_trans;
uint8_t processing_buffer[PROTOCOL_PACKET_LEN];
while (1) {
if (xQueueReceive(completed_trans_queue, &completed_trans, portMAX_DELAY) == pdTRUE) {
memcpy(processing_buffer, completed_trans->rx_buffer, PROTOCOL_PACKET_LEN);
spi_slave_queue_trans(RCV_HOST, completed_trans, portMAX_DELAY);
// Process frame... //copying to a ring buffer// print incoming frames on another task
}
}
}
esp_err_t SPI_Init(void) {
spi_bus_config_t buscfg = {
.mosi_io_num = GPIO_MOSI,
.miso_io_num = GPIO_MISO,
.sclk_io_num = GPIO_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_slave_interface_config_t slvcfg = {
.mode = 1,
.spics_io_num = GPIO_CS,
.queue_size = NUM_BUFFERS + 4,
.flags = 0,
.post_trans_cb = my_post_trans_callback,
};
ESP_ERROR_CHECK(spi_slave_initialize(RCV_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO));
for (int i = 0; i < NUM_BUFFERS; i++) {
memset(&transactions, 0, sizeof(spi_slave_transaction_t));
transactions.length = PROTOCOL_PACKET_LEN * 8;
transactions.rx_buffer = rx_buffers;
transactions.tx_buffer = tx_buffers;
spi_slave_queue_trans(RCV_HOST, &transactions, portMAX_DELAY);
}
xTaskCreatePinnedToCore(spi_receive_task, "spi_slavehandler", 4096, NULL,
configMAX_PRIORITIES - 1, NULL, 1);
return ESP_OK;
}
I’m running into a sporadic frame loss issue on ESP32 when using the SPI slave driver with DMA.
The SPI master continuously sends 20-byte data frames using its dma channel. (STM32 750KHz)
Occasionally, some frames are lost (not delivered to my receive task), even though the master clock and timing are stable.
If i add small delay (1ms) between each master transmit frames, then my esp32 receiver is working fine.
I tried with out a my_post_trans_callback() aswell- (using spi_slave_get_trans_result() inside Receiver task). But the results were same.
Did I miss some basic slave configurations here ? Any problem with my receiver logic.
Any advice or insights are greatly appreciated!
Relevant Code:
#define NUM_BUFFERS 24
#define PROTOCOL_PACKET_LEN 20
DMA_ATTR static uint8_t rx_buffers[NUM_BUFFERS][PROTOCOL_PACKET_LEN];
DMA_ATTR static uint8_t tx_buffers[NUM_BUFFERS][PROTOCOL_PACKET_LEN];
static spi_slave_transaction_t transactions[NUM_BUFFERS];
static QueueHandle_t completed_trans_queue;
void IRAM_ATTR my_post_trans_callback(spi_slave_transaction_t *trans) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(completed_trans_queue, &trans, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
void IRAM_ATTR spi_receive_task(void *pvParameters) {
spi_slave_transaction_t *completed_trans;
uint8_t processing_buffer[PROTOCOL_PACKET_LEN];
while (1) {
if (xQueueReceive(completed_trans_queue, &completed_trans, portMAX_DELAY) == pdTRUE) {
memcpy(processing_buffer, completed_trans->rx_buffer, PROTOCOL_PACKET_LEN);
spi_slave_queue_trans(RCV_HOST, completed_trans, portMAX_DELAY);
// Process frame... //copying to a ring buffer// print incoming frames on another task
}
}
}
esp_err_t SPI_Init(void) {
spi_bus_config_t buscfg = {
.mosi_io_num = GPIO_MOSI,
.miso_io_num = GPIO_MISO,
.sclk_io_num = GPIO_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_slave_interface_config_t slvcfg = {
.mode = 1,
.spics_io_num = GPIO_CS,
.queue_size = NUM_BUFFERS + 4,
.flags = 0,
.post_trans_cb = my_post_trans_callback,
};
ESP_ERROR_CHECK(spi_slave_initialize(RCV_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO));
for (int i = 0; i < NUM_BUFFERS; i++) {
memset(&transactions, 0, sizeof(spi_slave_transaction_t));
transactions.length = PROTOCOL_PACKET_LEN * 8;
transactions.rx_buffer = rx_buffers;
transactions.tx_buffer = tx_buffers;
spi_slave_queue_trans(RCV_HOST, &transactions, portMAX_DELAY);
}
xTaskCreatePinnedToCore(spi_receive_task, "spi_slavehandler", 4096, NULL,
configMAX_PRIORITIES - 1, NULL, 1);
return ESP_OK;
}