SPI/DMA mystery; OK to have both SrcIncrement and DstIncrement at 0?

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

cross mob
CaKu_4284131
Level 5
Level 5
50 replies posted 25 replies posted 10 likes received

I'm running SPI with DMA like this:

pastedImage_0.png

pastedImage_28.png

pastedImage_29.png

pastedImage_30.png

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?

0 Likes
1 Solution

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.

View solution in original post

6 Replies

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

Regards,
Bragadeesh
0 Likes
lock attach
Attachments are accessible only for community members.

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.

0 Likes

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."

0 Likes

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.

Hi CaKu_4284131​,

We are glad that your issue is resolved. Your explanation makes perfect sense.

Regards,

Bragadeesh

Regards,
Bragadeesh
0 Likes