SPI DMA problem

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

SPI DMA problem

3,308 Views
trunghieudon
Contributor I

Hello,

I am trying to use DMA LPSPI to write to a SD card. The DMA SPI was supposed to send 512 bytes in one major loop. The major loop has 512 minor loops and each minor loop transfer 1 byte.

Here is my problem. I wanted to maximize my throughput that's why I set my SPI TX watermark to 3. But when I set it to 3, somehow my last byte (byte number 511) was not sent. It is oddly replaced with a random 0xFF byte. However, If I set the TX watermark to 1,2 or 4, it would work perfectly. However, I feel like setting a TX water mark of 4 will cause problems and 2 is not really efficient (the TX fifo has a size of 4). I have attached the picture of the array I get from reading the data back from the SD card. I know for sure that my read function is not the source of problem. 

I'm using the S32K148evb-q176.

Here is my code for SPI config.

/*
* Copyright (c) 2014 - 2016, Freescale Semiconductor, Inc.
* Copyright (c) 2016 - 2018, NXP.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY NXP "AS IS" AND ANY EXPRESSED OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL NXP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "device_registers.h" /* include peripheral declarations */
#define PTB0 0

void LPSPI0_init_master(void)
{
/*!
* LPSPI0 Clocking:
* ===================================================
*/
PCC->PCCn[PCC_LPSPI0_INDEX] = 0; /* Disable clocks to modify PCS ( default) */
PCC->PCCn[PCC_LPSPI0_INDEX] = PCC_PCCn_PR_MASK /* (default) Peripheral is present. */
|PCC_PCCn_CGC_MASK /* Enable PCS=SPLL_DIV2 (40 MHz func'l clock) */
|PCC_PCCn_PCS(6);
/*!
* LPSPI0 Initialization:
* ===================================================
*/
LPSPI0->CR = 0x00000000; /* Disable module for configuration */
LPSPI0->IER = 0x00000000; /* Interrupts not used */
LPSPI0->DER = 0x00000000; /* DMA not used */
LPSPI0->CFGR0 = 0x00000000; /* Defaults: */
/* RDM0=0: rec'd data to FIFO as normal */
/* CIRFIFO=0; Circular FIFO is disabled */
/* HRSEL, HRPOL, HREN=0: Host request disabled */

LPSPI0->CFGR1 = LPSPI_CFGR1_MASTER_MASK | LPSPI_CFGR1_NOSTALL_MASK; /* Configurations: master mode */
/* PCSCFG=0: PCS[3:2] are enabled */
/* OUTCFG=0: Output data retains last value when CS negated */
/* PINCFG=0: SIN is input, SOUT is output */
/* MATCFG=0: Match disabled */
/* PCSPOL=0: PCS is active low */
/* NOSTALL=0: Stall if Tx FIFO empty or Rx FIFO full */
/* AUTOPCS=0: does not apply for master mode */
/* SAMPLE=0: input data sampled on SCK edge */
/* MASTER=1: Master mode */

LPSPI0->TCR = LPSPI_TCR_PRESCALE(2)
|LPSPI_TCR_PCS(0)
|LPSPI_TCR_FRAMESZ(7); /* Transmit cmd: PCS0, 16 bits, prescale func'l clk by 4, etc */
/* CPOL=0: SCK inactive state is low */
/* CPHA=0: capture data on SCK lead'g, changing on trail'g edge */
/* PRESCALE=2: Functional clock divided by 2**2 = 4 */
/* PCS=0: Transfer using PCS0 */
/* LSBF=0: Data is transfered MSB first */
/* BYSW=0: Byte swap disabled */
/* CONT, CONTC=0: Continuous transfer disabled */
/* RXMSK=0: Normal transfer: rx data stored in rx FIFO */
/* TXMSK=0: Normal transfer: data loaded from tx FIFO */
/* WIDTH=0: Single bit transfer */
/* FRAMESZ=47: # bits in frame = 47+1=48 */

LPSPI0->CCR = LPSPI_CCR_SCKPCS(4)
|LPSPI_CCR_PCSSCK(4)
|LPSPI_CCR_DBT(8)
|LPSPI_CCR_SCKDIV(8); /* Clock dividers based on prescaled func'l clk of 100 nsec */
/* SCKPCS=4: SCK to PCS delay = 4+1 = 5 (500 nsec) */
/* PCSSCK=4: PCS to SCK delay = 4+1 = 5 (500 usec) */
/* DBT=8: Delay between Transfers = 8+2 = 10 (1 usec) */
/* SCKDIV=8: SCK divider =8+2 = 10 (1 usec: 1 MHz baud rate) */

LPSPI0->FCR = LPSPI_FCR_TXWATER(3); /* RXWATER=0: Rx flags set when Rx FIFO > 0 */
/* TXWATER=3: Tx flags set when Tx FIFO <= 3 */

LPSPI0->CR = LPSPI_CR_MEN_MASK
|LPSPI_CR_DBGEN_MASK; /* Enable module for operation */
/* DBGEN=1: module enabled in debug mode */
/* DOZEN=0: module enabled in Doze mode */
/* RST=0: Master logic not reset */
/* MEN=1: Module is enabled */
}

Here is the code for DMA config

#include "device_registers.h" /* include peripheral declarations */
#include "S32K148.h"
#include "dma.h"

uint8_t dummy = 0xFF;


void DMA_TCD_init_Tx(void* p_tx_data)
{
DMA->TCD[0].SADDR = (uint32_t)(p_tx_data); /* Source Address. */
DMA->TCD[0].SOFF = DMA_TCD_SOFF_SOFF(1); /* Src. addr add 4 byte after Transfers */
DMA->TCD[0].ATTR = DMA_TCD_ATTR_SMOD(0) | /* Src. modulo feature not used */
DMA_TCD_ATTR_SSIZE(0) | /* Src. read 2**2 = 4 byte per transfer */
DMA_TCD_ATTR_DMOD(0) | /* Dest. modulo feature not used */
DMA_TCD_ATTR_DSIZE(0); /* Dest. write 2**2 =4 byte per trans. */

DMA->TCD[0].NBYTES.MLNO = DMA_TCD_NBYTES_MLNO_NBYTES(1); /* Transfer 16 byte /minor loop */
DMA->TCD[0].SLAST = DMA_TCD_SLAST_SLAST(-512); /* Src addr change after major loop */

DMA->TCD[0].DADDR = DMA_TCD_DADDR_DADDR((uint32_t)&(LPSPI0->TDR)); /* Destination Address. */
DMA->TCD[0].DOFF = DMA_TCD_DOFF_DOFF(0); /* No dest adr offset after transfer */

DMA->TCD[0].CITER.ELINKNO= DMA_TCD_CITER_ELINKNO_CITER(512) | /* 11 minor loop iterations */
DMA_TCD_CITER_ELINKNO_ELINK(0); /* No minor loop chan link */

DMA->TCD[0].DLASTSGA = DMA_TCD_DLASTSGA_DLASTSGA(0); /* No dest chg after major loop */
DMA->TCD[0].CSR = DMA_TCD_CSR_START(0) | /* Clear START status flag */
DMA_TCD_CSR_INTMAJOR(0) | /* No IRQ after major loop */
DMA_TCD_CSR_INTHALF(0) | /* No IRQ after 1/2 major loop */
DMA_TCD_CSR_DREQ(1) | /* en chan after major loop */
DMA_TCD_CSR_ESG(0) | /* Disable Scatter Gather */
DMA_TCD_CSR_MAJORELINK(0) | /* No major loop chan link */
DMA_TCD_CSR_ACTIVE(0) | /* Clear ACTIVE status flag */
DMA_TCD_CSR_DONE(0) | /* Clear DONE status flag */
DMA_TCD_CSR_MAJORLINKCH(0) | /* Chan # if major loop ch link */
DMA_TCD_CSR_BWC(0); /* No eDMA stalls after R/W */

DMA->TCD[0].BITER.ELINKNO= DMA_TCD_BITER_ELINKNO_BITER(512) | /* Initial iteration count */
DMA_TCD_BITER_ELINKNO_ELINK(0); /* No minor loop chan link */
}

Here is the code to initiate the DMA using start bit

void initiate_DMA_Tx(){

while (!((DMA->TCD[0].CSR >> DMA_TCD_CSR_DONE_SHIFT) & 1)) { /* Loop till DONE = 1 */
while((LPSPI0->SR & LPSPI_SR_TDF_MASK)>>LPSPI_SR_TDF_SHIFT==0); /* Wait for tx fifo to have space */

DMA->SSRT = 0; /* Set chan 0 START bit to initiate next minor loop */

while (((DMA->TCD[0].CSR >> DMA_TCD_CSR_START_SHIFT) & 1) | /* Wait for START = 0 */
((DMA->TCD[0].CSR >> DMA_TCD_CSR_ACTIVE_SHIFT) & 1)) {} /* and ACTIVE = 0 */
/* Now minor loop has completed */
}
}

When TX Watermark is 1,2,4

d.PNG

When TX Watermark is 3

ds.PNG

Labels (1)
Tags (1)
0 Kudos
Reply
5 Replies

3,175 Views
danielmartynek
NXP TechSupport
NXP TechSupport

Hello trunghieudon@geotab.com,

I tested your code with the S32K144_project_LPSPI example.

The only thing I changed

LPSPI0 -> LPSPI1

LPSPI_TCR_PCS(0) -> LPSPI_TCR_PCS(3)

The initiate_DMA_Tx() was being called continuously and all the data seemed to be transmitted correctly.

pastedImage_4.png

Can you scope the SPI?

Or could you share a complete test project so that can replicate the issue on my side?

Thanks,

BR, Daniel

0 Kudos
Reply

3,175 Views
trunghieudon
Contributor I

Hi Daniel,

Thank you for your response. I have attached my code. Though my code is expected to get a response from the SD card therefore it might need some modifications to the code on your end to run it. I also scoped the SPI and the error also shows on the scope.
I noticed just now that if I run the whole thing without any breakpoints, it will show the wrong SPI data as mentioned. But if I put breakpoints before and after the initiate_DMA_Tx() (line 254 in SD_Module.c), it will run properly and sends the right data. So I am suspecting that this is a timing issue. I have tried to add the "wait for SPI transfer complete" while((LPSPI0->SR & LPSPI_SR_TCF_MASK)>>LPSPI_SR_TCF_SHIFT==0); after the initiate_DMA_Tx() but this will only solve for the first initiate_DMA_Tx() call (meaning 511th bit will be corrected, though 1023th bit, 1535th bit, 2047th bit will still be 255). Do you know why this is the problem?

0 Kudos
Reply

3,175 Views
danielmartynek
NXP TechSupport
NXP TechSupport

Hi Austin,

It's difficult to debug it without the SD card.

But I think you have found the solution already.

pastedImage_1.png

pastedImage_2.png

You send 0xFF at the end of the 512-frame transfer, and the code that you sent does not wait until the SPI transfer is complete. At the time the DMA transfer ends the SPI is still working.

The fact that it works with the TCF flag only for the first time got me thinking that you probably don't clear the TCF flag.

Also, you could enable interrupts on the LPSPI error flags.

Regards,

Daniel

0 Kudos
Reply

3,175 Views
trunghieudon
Contributor I

Hi danielmartynek,

That makes sense. Though I was wrong before, It actually behaves even stranger. It works only on the 1st time, 3rd time, 5th time and wouldn't work on any even calls. This happens when I check the TCF flag after calling the initiate_DMA_Tx() or after it finished the 512th minor loop (checking TCF within the initiate_DMA_Tx() function). I then tried to check TCF flag at the 511th minor loop and 512th minor loop and it works. 

void initiate_DMA_Tx(){


uint16_t count=0;
while (!((DMA->TCD[0].CSR >> DMA_TCD_CSR_DONE_SHIFT) & 1)) { /* Loop till DONE = 1 */
while((LPSPI0->SR & LPSPI_SR_TDF_MASK)>>LPSPI_SR_TDF_SHIFT==0); /* Wait for tx fifo to have space */

DMA->SSRT = 0; /* Set chan 0 START bit to initiate next minor loop */

while (((DMA->TCD[0].CSR >> DMA_TCD_CSR_START_SHIFT) & 1) | /* Wait for START = 0 */
((DMA->TCD[0].CSR >> DMA_TCD_CSR_ACTIVE_SHIFT) & 1)) {} /* and ACTIVE = 0 */
/* Now minor loop has completed */
count++;


if(count>=511){
while((LPSPI0->SR & LPSPI_SR_TCF_MASK)>>LPSPI_SR_TCF_SHIFT==0); /* Wait for Transfer to be complete*/
LPSPI0->SR |= LPSPI_SR_TCF_MASK; /* Clear TCF flag */
}


}


}

It was probably the delay time of the if statement + checking it that allows it to slow down and finishes the final transfers. It doesn't seem to be the best way to do it but it works for now. Let me know if you know why it only works for odds calls and if there is a better way to check for the complete transfer. Thanks very much for your help!

Best,

Austin

0 Kudos
Reply

3,175 Views
danielmartynek
NXP TechSupport
NXP TechSupport

Hi Austin,

Apparently, the root cause is that you use DMA and the core at the same time to fill the TX FIFO. And the Watermark changes the timing. I guess you should use a mutex and don't allow the core to write to the data register when the DMA is using it. Once the DMA transfer is complete, you could read the Module Busy Flag (MBF) or check the number of words in the TX FIFO (FSR_TXCOUNT).

By the way, your TCF clearing is not good, it clears all the other w1c flags in the status register that are set at the time of the write (read-modify-write operation).

Mask the TCF flag only: LPSPI0->SR = LPSPI_SR_TCF_MASK; /* Clear TCF flag */

Regards,

Daniel

0 Kudos
Reply