SPI Transaction Fails When DMA Enabled

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
tthornung
Level 1
Level 1
First reply posted First question asked Welcome!

Hello,

I am attempting to access an EEPROM over SPI and then use DMA to transfer the result of the SPI transaction into an in-memory buffer. When the DMA finishes a burst (in this case, one burst = data from one SPI transaction), a DMA interrupt should trigger and kick off another SPI transaction.

Currently, I am seeing a strange result where the DMA interrupt occurs once, but then the call to kick off another SPI transaction fails. After checking on an oscilloscope, I see that the first SPI transaction occurs (this first transaction is manually triggered in main(), as opposed to being triggered by the DMA interrupt), but that the CS# never goes high to indicate that the transaction is complete.

In a similar previous experiment, I was able to use DMA to transfer a UART's RX data into an in-memory buffer. I have also confirmed that SPI works correctly in experiments that do not enable DMA.

After some more experimenting, I found that SPI transactions only fail when the DMA instance uses the RX_FIFO_RD register as the source address and when DMA is triggered by a cyhal_source_t signal generated from the SPI SCB. I find this strange, though, because the PDL example here (https://github.com/Infineon/mtb-example-psoc6-spi-master-dma) seems to (effectively) do the same thing.

SPI is configured as follows:

 

cy_rslt_t status;
status = cyhal_spi_init(&eepromSPI, PIN_MCU_SPI_MOSI, PIN_MCU_SPI_MISO,PIN_MCU_SPI_CLK,PIN_MCU_SPI_CS, NULL, BITS_PER_FRAME,CYHAL_SPI_MODE_00_MSB, false);
if(status != CY_RSLT_SUCCESS)
    CY_ASSERT(0);
status = cyhal_spi_set_frequency(&eepromSPI, SPI_FREQ);
if(status != CY_RSLT_SUCCESS)
    CY_ASSERT(0);
cyhal_source_t eeprom_source;
cyhal_spi_set_fifo_level(&eepromSPI, CYHAL_SPI_FIFO_RX, 0);
status = cyhal_spi_enable_output(&eepromSPI, CYHAL_SPI_OUTPUT_TRIGGER_RX_FIFO_LEVEL_REACHED, &eeprom_source);
if (CY_RSLT_SUCCESS != status)
    CY_ASSERT(0);

 

 

DMA is configured as follows:

 

cyhal_dma_t *create_dma(
    cyhal_dma_direction_t dir,
    uint32_t src,
    uint32_t dst,
    uint32_t word_size,
    uint32_t words_per_burst,
    uint32_t total_words,
    cyhal_source_t *trigger_signal,
    cyhal_dma_event_callback_t callback
    ) {

    cyhal_dma_t *dma;
    cy_rslt_t rslt;
    dma = (cyhal_dma_t *) malloc(sizeof(cyhal_dma_t));
    if (NULL == dma) {
        return NULL;
    }
    int32_t src_increment;
    int32_t dst_increment;
    switch (dir) {
    case CYHAL_DMA_DIRECTION_MEM2MEM:
        src_increment = 1;
        dst_increment = 1;
        break;
    case CYHAL_DMA_DIRECTION_PERIPH2MEM:
        src_increment = 0;
        dst_increment = 1;
        break;
    case CYHAL_DMA_DIRECTION_MEM2PERIPH:
        src_increment = 1;
        dst_increment = 0;
        break;
    case CYHAL_DMA_DIRECTION_PERIPH2PERIPH:
        src_increment = 0;
        dst_increment = 0;
        break;
    default:
        handle_error("Invalid DMA direction provided.");
    }

    cyhal_dma_cfg_t cfg = {
        .src_addr = src,                   // Start from address
        .src_increment = src_increment,    // # words to increment by
        .dst_addr = dst,                   // Destination from address
        .dst_increment = dst_increment,    // # words to increment by
        .transfer_width = word_size,       // # bits per transfer
        .length = total_words,             // number of transfers
        .burst_size = words_per_burst,     // number of elements in each burst
        .action = CYHAL_DMA_TRANSFER_BURST // CYHAL_DMA_TRANSFER_COMPLETE event triggered after each burst is transferred
    };

    // For M2P and P2M transfers, we want to transfer one burst at a time so that
    // we can perform a 1->N or N->1 data transfer.
    cyhal_dma_input_t dma_input = CYHAL_DMA_INPUT_TRIGGER_SINGLE_BURST;
    if (CYHAL_DMA_DIRECTION_MEM2MEM == dir) {
        // For M2M data transfers, we want to transfer all memory at once.
        dma_input = CYHAL_DMA_INPUT_TRIGGER_ALL_ELEMENTS;
    }
    cyhal_dma_src_t *dma_src_ptr = NULL;
    cyhal_dma_src_t dma_src;
    if (NULL != trigger_signal) {
        dma_src.source = *trigger_signal;
        dma_src.input = dma_input;
        dma_src_ptr = &dma_src;
    }

    rslt = cyhal_dma_init_adv(dma, dma_src_ptr, NULL, NULL, CYHAL_DMA_PRIORITY_DEFAULT, dir);
    if (CY_RSLT_SUCCESS != rslt) {
        handle_error("Failed to init dma.");
    }
    rslt = cyhal_dma_configure(dma, &cfg);
    if (CY_RSLT_SUCCESS != rslt) {
        handle_error("Failed to configure dma.");
    }
    if (NULL != callback) {
        cyhal_dma_register_callback(dma, callback, dma);
        // Callback is triggered after the TRANSFER_COMPLETE event occurs
        cyhal_dma_enable_event(dma, CYHAL_DMA_TRANSFER_COMPLETE, CYHAL_ISR_PRIORITY_DEFAULT, true);
    }

    rslt = cyhal_dma_enable(dma);
    if (CY_RSLT_SUCCESS != rslt) {
        handle_error("Failed to enable dma.");
    }
    return dma;
}

cyhal_dma_t *eeprom_dma = create_dma(
    CYHAL_DMA_DIRECTION_PERIPH2MEM,
    &SCB_RX_FIFO_RD(eepromSPI.base),
    (uint32_t) buffer, 
    8, // 1 "word" = 1 byte
    1, // 1 "word" per SPI transfer that we're reading
    5, // number of "words" to transfer for one descriptor
    &eeprom_source,
    eeprom_dma_callback);

 

 

I'm curious if something about using a source signal to trigger DMA and DMA reading from the RX_FIFO_RD buffer is effectively pausing the SPI transaction and preventing it from finishing, but I'm not sure. I also find it strange that  the source signal is triggering DMA before the CS goes high.

Finally, when I run all of this in the debugger, I see that in the rx buf that I provide to the async SPI transaction that it is one byte off when the DMA interrupt occurs. data[3] should have 'E' but it seems that the data has not been properly shifted over in time.

tthornung_0-1679414058144.png

 

Any help or suggestions would be greatly appreciated! Thanks!

0 Likes
1 Reply
tthornung
Level 1
Level 1
First reply posted First question asked Welcome!

Update:

After significant frustration with the HAL implementation, I decided to abandon the HAL and base my work off of a PDL example instead. Now that I'm using the PDL, there is no strange behavior when DMA, source signals, and reading from the RX_FIFO_RD register mix.

I am still genuinely curious as to why the HAL did not work correctly. It is entirely possible that I missed something configuration-related, but after extensive debugging I am left unsure as to what exactly was different between the HAL implementation from before and the PDL functions I am using now.

0 Likes