6 Replies Latest reply on May 7, 2020 10:20 PM by BragadeeshV_41

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

    CaKu_4284131

      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?

        • 2. Re: SPI/DMA mystery; OK to have both SrcIncrement and DstIncrement at 0?
          BragadeeshV_41

          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

          • 3. Re: SPI/DMA mystery; OK to have both SrcIncrement and DstIncrement at 0?
            CaKu_4284131

            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.

            • 4. Re: SPI/DMA mystery; OK to have both SrcIncrement and DstIncrement at 0?
              CaKu_4284131

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

              • 5. Re: SPI/DMA mystery; OK to have both SrcIncrement and DstIncrement at 0?
                CaKu_4284131

                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.

                • 6. Re: SPI/DMA mystery; OK to have both SrcIncrement and DstIncrement at 0?
                  BragadeeshV_41

                  Hi CaKu_4284131,

                   

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

                   

                  Regards,

                  Bragadeesh