- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I'm running SPI with DMA like this:
I'm transferring blocks of 512 bytes (reading and writing to SD card), and sometimes I don't care about the transmitted data and sometimes I don't care about the received data. (Similar to how the High-Level SCB SPI function Cy_SCB_SPI_Transfer() is used.)
This solution works:
bool sd_spi_transfer(sd_card_t *this, const uint8_t *tx, uint8_t *rx, size_t length) {
uint8_t dummy[512];
if (!tx) {
memset(dummy, 0xFF, length);
tx = &dummy[0];
}
if (!rx) {
rx = &dummy[0];
}
//...
/*Set up SRC addr for TX DMA*/
Cy_DMA_Descriptor_SetSrcAddress(this->txDma_Descriptor_1, tx);
/*Set up DEST addr for RX DMA*/
Cy_DMA_Descriptor_SetDstAddress(this->rxDma_Descriptor_1, rx);
/*Enable DMA_channels*/
Cy_DMA_Channel_Enable(this->rxDma_base, this->rxDma_channel);
Cy_DMA_Channel_Enable(this->txDma_base, this->txDma_channel);
I thought I'd try to optimize away the useless 512 byte buffer, like so:
spi_dma_transfer(spi_t *this, uint16 len, const uint8_t *txBuffer, uint8_t *rxBuffer) {
uint8_t dummy = 0xFF;
if (!txBuffer) {
txBuffer = &dummy;
}
if (!rxBuffer) {
rxBuffer = &dummy;
}
/*Set up SRC addr for TX DMA*/
Cy_DMA_Descriptor_SetSrcAddress(this->txDma_Descriptor_1, txBuffer);
/*Set up DEST addr for RX DMA*/
Cy_DMA_Descriptor_SetDstAddress(this->rxDma_Descriptor_1, rxBuffer);
/* Use 256 X loops and 2 Y loops */
/*Set up RX Descriptor 1*/
Cy_DMA_Descriptor_SetXloopSrcIncrement(this->rxDma_Descriptor_1, 0);
if (&dummy == rxBuffer)
Cy_DMA_Descriptor_SetXloopDstIncrement(this->rxDma_Descriptor_1, 0);
else
Cy_DMA_Descriptor_SetXloopDstIncrement(this->rxDma_Descriptor_1, 1);
Cy_DMA_Descriptor_SetXloopDataCount(this->rxDma_Descriptor_1, 256);
Cy_DMA_Descriptor_SetYloopSrcIncrement(this->rxDma_Descriptor_1, 0);
if (&dummy == rxBuffer)
Cy_DMA_Descriptor_SetYloopDstIncrement(this->rxDma_Descriptor_1, 0);
else
Cy_DMA_Descriptor_SetYloopDstIncrement(this->rxDma_Descriptor_1, 256);
Cy_DMA_Descriptor_SetYloopDataCount(this->rxDma_Descriptor_1, 2);
/*Set up TX Descriptor 1*/
if (&dummy == txBuffer)
Cy_DMA_Descriptor_SetXloopSrcIncrement(this->txDma_Descriptor_1, 0);
else
Cy_DMA_Descriptor_SetXloopSrcIncrement(this->txDma_Descriptor_1, 1);
Cy_DMA_Descriptor_SetXloopDstIncrement(this->txDma_Descriptor_1, 0);
Cy_DMA_Descriptor_SetXloopDataCount(this->txDma_Descriptor_1, 256);
if (&dummy == txBuffer)
Cy_DMA_Descriptor_SetYloopSrcIncrement(this->txDma_Descriptor_1, 0);
else
Cy_DMA_Descriptor_SetYloopSrcIncrement(this->txDma_Descriptor_1, 256);
Cy_DMA_Descriptor_SetYloopDstIncrement(this->txDma_Descriptor_1, 0);
Cy_DMA_Descriptor_SetYloopDataCount(this->txDma_Descriptor_1, 2);
/*Enable DMA_channels*/
Cy_DMA_Channel_Enable(this->rxDma_base, this->rxDma_channel);
Cy_DMA_Channel_Enable(this->txDma_base, this->txDma_channel);
}
The idea is to send from, or receive to, the same byte over and over instead of filling an array that no one will ever look at, and I do that by setting the increment to 0. Of course, the transmit dest. is
Cy_DMA_Descriptor_SetDstAddress(this->txDma_Descriptor_1, (void *) &this->base->TX_FIFO_WR);
and the receive src. is
Cy_DMA_Descriptor_SetSrcAddress(this->rxDma_Descriptor_1, (void *) &this->base->RX_FIFO_RD);
so those don't increment.
It almost, kinda, sorta, works, but not quite. If I'm reading, say, 4 blocks, I'll get 3 OK, and then time out waiting (as much as 4 seconds) for the completion interrupt for the 4th. Logically, it seems like it should be the same. Maybe there is a timing issue? Or, have I messed up some mundane detail, as usual?
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
And, again, in the process of explaining it, I came to understand it myself. The "dummy" variable can't be a stack variable, because this function can return before the DMA descriptor actually runs. This function simply enables the channel, and when it runs, if the DstAddress is pointing to something that once was on the stack, it probably clobbers who-knows-what on the stack now, leading to unpredictable behavior.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Full source for the working version is at FreeRTOS-FAT-CLI-for-PSoC-63/portable/MCU_PSOC6_M4 at DMA · carlk3/FreeRTOS-FAT-CLI-for-PSoC-63 · Gi....
Specifically, see:
https://github.com/carlk3/FreeRTOS-FAT-CLI-for-PSoC-63/blob/DMA/portable/MCU_PSOC6_M4/sd_spi.c
https://github.com/carlk3/FreeRTOS-FAT-CLI-for-PSoC-63/blob/DMA/portable/MCU_PSOC6_M4/spi.c
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi CaKu_4284131,
The answer to your question is yes, you can write to / read from the same address location over and over again by keeping the source / destination increment to 0 and it works.
To debug your issue, we need to analyse the SPI waveform and the Trigger output signal of the DMA.
For debugging purpose, is it possible for you to set the src/ dst increment in the configurator and try them individually?
Also, the project you shared has some missing files and couldn't be built.
Regards.
Bragadeesh
Bragadeesh
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi Bragadeesh-
For debugging purpose, is it possible for you to set the src/ dst increment in the configurator and try them individually?
I started experimenting with this, and made an interesting discovery. The problem isn't in the zero increments, in fact I have that working now. The problem appears to be with having the dummy array local to the function that sets up the addresses, if that makes any sense.
This does not work:
void spi_dma_transfer(spi_t *this, uint16 len, const uint8_t *tx, uint8_t *rx) {
uint8_t dummy = SPI_FILL_CHAR;
if (tx) {
txSrcXincrement = 1;
txSrcYincrement = 256;
} else {
tx = &dummy;
txSrcXincrement = 0;
txSrcYincrement = 0;
}
if (rx) {
// ...
/*Set up SRC addr for TX DMA*/
Cy_DMA_Descriptor_SetSrcAddress(this->txDma_Descriptor_1, tx);
/*Set up DEST addr for RX DMA*/
Cy_DMA_Descriptor_SetDstAddress(this->rxDma_Descriptor_1, rx);
However, if I move the dummy out to the calling function (which is in another file), it works fine, whether dummy is a char or an array.
bool sd_spi_transfer(sd_card_t *this, const uint8_t *tx, uint8_t *rx, size_t length) {
BaseType_t rc;
uint8_t dummy = SPI_FILL_CHAR;
bool txIsArray, rxIsArray;
if (tx) {
txIsArray = true;
} else {
tx = &dummy;
txIsArray = false;
}
if (rx) {
rxIsArray = true;
} else {
rx = &dummy;
rxIsArray = false;
}
// ...
spi_dma_transfer(this->spi, length, tx, rx, txIsArray, rxIsArray);
and
void spi_dma_transfer(spi_t *this, uint16 len,
const uint8_t *tx, uint8_t *rx,
bool txIsArray, bool rxIsArray) {
int32_t txSrcXincrement, txSrcYincrement;
if (txIsArray) {
txSrcXincrement = 1;
txSrcYincrement = 256;
} else {
txSrcXincrement = 0;
txSrcYincrement = 0;
}
Cy_DMA_Descriptor_SetXloopSrcIncrement(this->txDma_Descriptor_1, txSrcXincrement);
Cy_DMA_Descriptor_SetYloopSrcIncrement(this->txDma_Descriptor_1, txSrcYincrement);
// ...
/*Set up SRC addr for TX DMA*/
Cy_DMA_Descriptor_SetSrcAddress(this->txDma_Descriptor_1, tx);
I'm speculating that this has something to do with alignment or optimization.
Also, the project you shared has some missing files and couldn't be built.
Are they the FreeRTOS+FAT files? You can get those at
FreeRTOS/Lab-Project-FreeRTOS-FAT
There are some build instructions for my project in the README.md at
carlk3/FreeRTOS-FAT-CLI-for-PSoC-63
In any case, I've attached a project bundle.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Isn't it funny how explaining a problem to someone else makes the solution clear to oneself? The answer is to make the dummy variable static, like so:
void spi_dma_transfer(spi_t *this, uint16 len, const uint8_t *tx, uint8_t *rx) {
static uint8_t dummy = SPI_FILL_CHAR;
configASSERT(512 == len);
configASSERT(tx || rx);
int32_t txSrcXincrement, txSrcYincrement;
if (!tx) {
tx = &dummy;
txSrcXincrement = 0;
txSrcYincrement = 0;
} else {
txSrcXincrement = 1;
txSrcYincrement = 256;
}
Cy_DMA_Descriptor_SetXloopSrcIncrement(this->txDma_Descriptor_1, txSrcXincrement);
Cy_DMA_Descriptor_SetYloopSrcIncrement(this->txDma_Descriptor_1, txSrcYincrement);
int32_t rxDstXincrement, rxDstYincrement;
if (!rx) {
rx = &dummy;
rxDstXincrement = 0;
rxDstYincrement = 0;
} else {
rxDstXincrement = 1;
rxDstYincrement = 256;
}
Cy_DMA_Descriptor_SetXloopDstIncrement(this->rxDma_Descriptor_1, rxDstXincrement);
Cy_DMA_Descriptor_SetYloopDstIncrement(this->rxDma_Descriptor_1, rxDstYincrement);
configASSERT(CY_DMA_CHANNEL_DISABLED == Cy_DMA_Descriptor_GetChannelState(this->txDma_Descriptor_1));
/*Set up SRC addr for TX DMA*/
Cy_DMA_Descriptor_SetSrcAddress(this->txDma_Descriptor_1, tx);
/*Set up DEST addr for RX DMA*/
Cy_DMA_Descriptor_SetDstAddress(this->rxDma_Descriptor_1, rx);
/*Enable DMA_channels*/
Cy_DMA_Channel_Enable(this->rxDma_base, this->rxDma_channel);
Cy_DMA_Channel_Enable(this->txDma_base, this->txDma_channel);
}
In particular, the static keyword in line 2. I can't tell you exactly why that works, but, as someone famous once said, "Tide goes in, tide goes out. You can’t explain that."
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
And, again, in the process of explaining it, I came to understand it myself. The "dummy" variable can't be a stack variable, because this function can return before the DMA descriptor actually runs. This function simply enables the channel, and when it runs, if the DstAddress is pointing to something that once was on the stack, it probably clobbers who-knows-what on the stack now, leading to unpredictable behavior.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi CaKu_4284131,
We are glad that your issue is resolved. Your explanation makes perfect sense.
Regards,
Bragadeesh
Bragadeesh