- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.
Any help or suggestions would be greatly appreciated! Thanks!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.