This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TMS320F28379D: SD Card Data Logging using DMA

Part Number: TMS320F28379D
Other Parts Discussed in Thread: CONTROLSUITE

Hello All,

So, I have a requirement to log the data to SD Card periodically for some parameters. I started using the SD Card example provided in ControlSuite. I modified wc=0 to wc=256 in xmit_datablock function in mmc-F2837x.c file. I modified the main function and added function calls to f_mount, f_open, f_write, f_sync, f_close and f_mount in the sequence mentioned to create a file and write a string to it. I am able to successfully do that without any problems. No I am trying to implement DMA feature to SPI TX Channel but I am not able to do that successfully. I am using "Example_2837xD_Spi_dma" as an example for DMA use.

I want to write blocks of 512 bytes at a time using DMA. That way I dont need to heavily modify the code. So, here is the configuration and the changes that I made based on my understanding in the sd_card example.

I added the following in mmc-F2837x.c file:

#define BURST         (FIFO_LVL-1)    // burst size should be less than 8
#define TRANSFER      63              // [(MEM_BUFFER_SIZE/FIFO_LVL)-1]
#define FIFO_LVL      8               // FIFO Interrupt Level

//
// Globals
//
volatile Uint16 *DMADest;
volatile Uint16 *DMASource;
volatile Uint16 SpiTxDmaDone = 0;

// Function Prototype
void dma_init(void);

I added the following code in the power_on function again in mmc-F2837x.c file:

// SPI Port Settings
/* Configure the SPI C port */
SpicRegs.SPICCR.bit.SPISWRESET = 0;			//Set reset bit low
SpicRegs.SPICTL.bit.CLK_PHASE = 0;
SpicRegs.SPICCR.bit.CLKPOLARITY = 1;
SpicRegs.SPICTL.bit.MASTER_SLAVE = 1;		//Master mode
SpicRegs.SPIBRR.all = 63;					//update value to proper setting for correct bitrate ( current: ~500kHz)
SpicRegs.SPICCR.bit.SPICHAR = 0x7;			//Set char length to 8 bits
SpicRegs.SPICTL.bit.TALK = 1;
SpicRegs.SPICCR.bit.SPISWRESET = 1;			//Release SPI from reset
SpicRegs.SPIPRI.bit.FREE = 1;
SpicRegs.SPIPRI.bit.SOFT = 1;

EALLOW;  // This is needed to write to EALLOW protected registers
PieVectTable.DMA_CH5_INT= &local_D_INTCH5_ISR;
EDIS;   // This is needed to disable write to EALLOW protected registers

dma_init();            // Set up DMA for SPI configuration

SpicRegs.SPIFFCT.all = 0x0;

for(m=0;m<3;m++);

SpicRegs.SPIFFRX.all=0x2040;             // RX FIFO enabled, clear FIFO int
SpicRegs.SPIFFRX.bit.RXFFIL = 0;  // Set RX FIFO level

SpicRegs.SPIFFTX.all=0xE040;             // FIFOs enabled, TX FIFO released,
SpicRegs.SPIFFTX.bit.TXFFIL = FIFO_LVL;  // Set TX FIFO level

// Ensure DMA is connected to Peripheral Frame 2 bridge (EALLOW protected)
	EALLOW;
	CpuSysRegs.SECMSEL.bit.PF2SEL = 1;
	EDIS;

// Enable interrupts required for SPI DMA
	PieCtrlRegs.PIECTRL.bit.ENPIE = 1;   // Enable the PIE block
	PieCtrlRegs.PIEIER7.bit.INTx5 = 1;   // Enable PIE Group 7, INT 1 (DMA CH1)
	PieCtrlRegs.PIEIER7.bit.INTx6 = 1;   // Enable PIE Group 7, INT 2 (DMA CH2)
	IER= M_INT7;                         // Enable CPU INT6

One thing to note is that I have SpicRegs.SPICCR.bit.SPICHAR = 0x7 as sd_card example uses BYTE (unsigned char) type.

DMA Init and TX ISR looks like this:

//
// dma_init - DMA setup for both TX and RX channels.
//
void dma_init()
{
    //
    // Initialize DMA
    //
    DMAInitialize();

    //
    // configure DMACH5 for TX
    //
    // DMACH5AddrConfig(&SpicRegs.SPITXBUF,DMASource);
    DMACH5BurstConfig(BURST,1,0);         // Burst size, src step, dest step
    DMACH5TransferConfig(TRANSFER,1,0);   // transfer size, src step, dest step
    DMACH5ModeConfig(DMA_SPICTX,PERINT_ENABLE,ONESHOT_DISABLE,CONT_DISABLE,
                     SYNC_DISABLE,SYNC_SRC,OVRFLOW_DISABLE,SIXTEEN_BIT,
                     CHINT_END,CHINT_ENABLE);
}

//
// local_D_INTCH5_ISR - DMA Channel 5 ISR
//
__interrupt void local_D_INTCH5_ISR(void)
{
    EALLOW;  // NEED TO EXECUTE EALLOW INSIDE ISR !!!
//    DmaRegs.CH5.CONTROL.bit.HALT=1;
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP7; // ACK to receive more interrupts from this PIE group
    EDIS;

    SpiTxDmaDone = 1;
    return;
}

I added one function "xmit_datablock_dma" that would replace "xmit_datablock" again in mmc-F2837x.c file. This function is being called from disk_write function. Here is the definition for xmit_datablock_dma.c file. The idea is that this new function shall have the exact same functionality as to "xmit_datablock" with the difference that uses DMA to transfer 512 BYTE block.

/*-----------------------------------------------------------------------*/
/* Send a data packet to MMC using DMA                                   */
/*-----------------------------------------------------------------------*/
static BOOL xmit_datablock_dma ( BYTE token)            /* Data/Stop token */
{
    BYTE resp; //, wc;
    unsigned int Count=0;
    volatile DWORD rcvdat;

    SpiTxDmaDone = 0;

    if (wait_ready() != 0xFF) return FALSE;

    xmit_spi(token);        /* Xmit data token */
    if (token != 0xFD)      /* Is data token */
    {
    	StartDMACH5();                       // Start SPI TX DMA channel

    	while (!SpiTxDmaDone);

    	xmit_spi(0xFF);                    /* CRC (Dummy) */
        xmit_spi(0xFF);

//        resp = rcvr_spi();               /* Reveive data response */
//        if ((resp & 0x1F) != 0x05)
//        		return FLASE;

// FIOEDIT: Changed the above 3 lines to this logic. have to manually implement RX functionality as below because RX FIFO gets enabled even though I dont need it. 
// And from the bunch of RX chars, this code finds for 0xE5. while(SpicRegs.SPIFFTX.bit.TXFFST >= 7); SpicRegs.SPITXBUF = 0xFF00; //Write dummy data while(SpicRegs.SPIFFRX.bit.RXFFST <= 0) { } while(SpicRegs.SPIFFRX.bit.RXFFST > 0) { rcvdat = (SpicRegs.SPIRXBUF & 0xFF); //Read Transferred data if ((rcvdat & 0x1F) == 0x05) return TRUE; } return FALSE; } return TRUE; }

In the above code, one thing that I have noticed is that when I try to debug this code and when I write the dummy data to receive characters, RXFFST is about 8 or 9 everytime and one of the response is 0xE5 while all other responses are 0xFF and that is why I had to implement that while loop to check 0xE5 out of all those received responses. However, if I dont use DMA, I only need these 3 lines of code below:

resp = rcvr_spi();               /* Reveive data response */
if ((resp & 0x1F) != 0x05)
     return FLASE;

The last thing that I modified is the disk_write function which is as follows:

DRESULT disk_write (
    BYTE drv,            /* Physical drive number (0) */
    const BYTE *buff,    /* Pointer to the data to be written */
    DWORD sector,        /* Start sector number (LBA) */
    BYTE count            /* Sector count (1..255) */
)
{
	DMASource = (volatile Uint16 *)buff;
	DMACH5AddrConfig(&SpicRegs.SPITXBUF,DMASource);

    if (drv || !count) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;
    if (Stat & STA_PROTECT) return RES_WRPRT;

    if (!(CardType & 4)) sector *= 512;    /* Convert to byte address if needed */

    SELECT();            /* CS = L */

    if (count == 1)      /* Single block write */
    {
// FIOEDIT
//        if ((send_cmd(CMD24, sector) == 0)    /* WRITE_BLOCK */
//                && xmit_datablock(buff, 0xFE))
    	if ((send_cmd(CMD24, sector) == 0)    /* WRITE_BLOCK */
                && xmit_datablock_dma(0xFE))
            count = 0;
    }
    else                  /* Multiple block write */
    {
        if (CardType & 2)
        {
            send_cmd(CMD55, 0);
            send_cmd(CMD23, count);    /* ACMD23 */
        }
        if (send_cmd(CMD25, sector) == 0)      /* WRITE_MULTIPLE_BLOCK */
        {
            do
            {
// FIOEDIT
//            	if (!xmit_datablock(buff, 0xFC)) break;
//            	DMASource = (volatile Uint16 *)TxDataBuff;
//            	DMACH5AddrConfig(&SpicRegs.SPITXBUF,DMASource);
            	if (!xmit_datablock_dma(0xFC)) break;
                buff += 512;
            }
            while (--count);
// FIOEDIT
          if (!xmit_datablock(0, 0xFD))    /* STOP_TRAN token */
//            if (!xmit_datablock_dma(0xFD))    /* STOP_TRAN token */
                count = 1;
        }
    }

    DESELECT();            /* CS = H */
    rcvr_spi();            /* Idle (Release DO) */

    return count ? RES_ERROR : RES_OK;
}

In the above disk_write function, count is always equal to one as I am not exceeding to write more than 512 bytes at a time. The only modifications are assigning DMA source and destionation address to be *buff and then replacing xmit_datablock with xmit_datablock_dma.


I tried to debug a lot and I have exhausted my knowledge. This is for the first time I am dealing with file system code and I am not a guru. I know C2000 documentation clearly mentions this:

/*******************************************************************************************
The value written to the SIZE registers is one less than the intended size. So, to transfer three 16-bit words, the value 2 should be placed in the SIZE register.
Regardless of the state of the DATASIZE bit, the value specified in the SIZE registers are for 16-bit addresses. So, to transfer three 32-bit words, the value 5 should be placed in the SIZE register.

Regardless of the state of the DATASIZE bit, the value specified in the STEP registers are for 16-bit addresses. So, to increment one 32-bit address, a value of 2 should be placed in these registers.
********************************************************************************************/
I tried to play around with those settings along with the SpicRegs.SPICCR.bit.SPICHAR = 0x7 setting by making it 0xF which is equivalent to 16 bits. But nothing works yet when I try to debug with DMA option enabled. :(

I know DMA gets triggered as I can hit a Breakpoint in the ISR.

I really need your advice to make this work. I have speed and timing constraints and I need DMA feature for SD Card Data Logging to work. I appreciate your time!

Regards,

Archit

  • Hello,
    I am writing to let you know that a C2000 team member has been assigned to this post and should be answering shortly.

    Regards
    Baskaran
  • Hi Archit,

    I see you removed the HALT=1 from the ISR. Don't you need that to prevent transfers from happening outside of the xmit functions? Or are you stopping the channel elsewhere in the code?

    How is the data being transmitted by the DMA formatted? Recall that the when the SPI character size is less than 16, you need to left justify the data. Is your data buffer formatted so that every character you want to transmit is in the upper 8 bits?

    Thanks,

    Whitney

  • I checked my code and It seems that I temporarily removed the HALT=1 from the ISR to test something. However it is in the code. The code does not left justify the data being transmitted out. I completely missed that. Its a very good catch. What is the best way to do that? I really have very tight timing constraints and I do not want to create a local array that will have the shifted version of the input BYTE *buff. Performing that shift operation one by one and copying over 512 words every time will take up additional time and will not fit in the timing constraint I have. What I mean is I do not want to do something like this in the disk_write function in mmc-F2837x.c.

    DRESULT disk_write (
        BYTE drv,            /* Physical drive number (0) */
        const BYTE *buff,    /* Pointer to the data to be written */
        DWORD sector,        /* Start sector number (LBA) */
        BYTE count            /* Sector count (1..255) */
    )
    {
    	unsigned int Idx;
    	unsigned int TxDataBuff[512] = { 0 };
    	
    	for (Idx=0; Idx<512; Idx++)
    		TxDataBuff[512] = ((unsigned long)*buff++) << 8;
    	
    	DMASource = (volatile Uint16 *)TxDataBuff;
    	DMACH5AddrConfig(&SpicRegs.SPITXBUF,DMASource);
    
    // ..............................
    // Rest of the function code as in the previous post.
    // ..............................
    
    }

    Can you please suggest something efficient? I have shared all the code in my previous post.

    I really appreciate your time and help!

    Thanks,

    Archit

  • One quick thing. I did try to modify the code as mentioned in my previous post to left shift the data by 8 bits and tried to execute the code. However, that does not seem to work..... When I debug the code; below is the function call trace where its failing:

    And the specific location where it fails in disk_write is

        	if ((send_cmd(CMD24, sector) == 0)    /* WRITE_BLOCK */
                    && xmit_datablock_dma(0xFE))
                count = 0;
    

    If statement condition passes and so count becomes 0. And the error is thrown.

    Please let me know if you need any more information.

    Thanks,

    Archit

  • May I ask what kind of data is going into the tx buffer and if it's being used for anything other than logging purposes? I'm just wondering if it's a scenario where you can shift or use the __byte() intrinsic to move the data into the upper byte as the data buffer is being populated. Is that a possibility?

    Whitney

  • No, its not being used any where. I actually tried the left shift before triggering DMA and it still does not work. I can share my CCS project if that helps to quickly resolve the issue.
  • Any update Whitney? Please let me know. I can share the CCS project for you to look at if that helps. Please let me know. Thanks!

  • Hi Archit,

    Sorry I didn't get back to this today. You can send me your project. I should have some time to look at it tomorrow afternoon.

    Whitney
  • I sent you a private message with the CCS project and the modified fatfs library files. I appreciate your time and help to resolve this issue. Thanks.
  • Hi Archit,

    I'm still kind of stumped, but here are my observations.

    1. I noticed that if I stepped through the code that you added in xmit_datablock_dma() after the dummy CRC writes, I was able to get it to return TRUE. I also found that if I placed a delay ( like SysCtlDelay(10000)) before the last while loop, it would work even when I wasn't stepping through it. I'm still not sure where the extra 8 words in the RX buffer are coming from. It almost looks like the last DMA burst coming in after the interrupt. Can you confirm that you see the same behavior?

    2. The code you added to shift the data buffer isn't going to work. You've declared a local TxDataBuff with the same name as your global TxDataBuff. The address being configured into the DMA is the local one, so it's basically transmitting your stack instead of the string intended.

    Whitney
  • When you say it works for you, do you mean you are able to successfully log the data to the SD Card using DMA? Secondly I am not willing to introduce "SysCtlDelay(10000))" as the whole idea is to speed up the transfer and not pend the processor or slow its execution down. Initially I want to make the DMA work and then I will remove all those wait states or at least reduce those to fit the timing. It would be ok for me to not receive the response for a few major frames which executes periodically.

    1.) Yes, I do see the same behavior and the actual response which is 229d is always at the last of 8 bytes. I assume the RX FIFO LVL is set to 8 and so we see specifically 8 bytes. However, I am not sure where those extra bytes come from.........

    2.) So, the snippet of the code in disk_write looks like this:

    	unsigned int Idx;
    	unsigned int TxDataBuff[512] = { 0 };
    
    	for (Idx=0; Idx<512; Idx++)
    		TxDataBuff[Idx] = ((unsigned long)*buff++) << 8;
    
    	DMASource = (volatile Uint16 *)TxDataBuff;
    	DMACH5AddrConfig(&SpicRegs.SPITXBUF,DMASource);
    

    Here I am assigning the left shifted "buff" content to TxDataBuff. And then assigning the pointer of TxDataBuff to DMASource. While debugging before, I did make sure that the DMASource is pointing at the correct memory location of TxDataBuff which is a stack. The idea is that disk_write is not only called to just write the string. Because it is a file system, there are additional calls to disk_write which are not for writing the data. So, I simply take the buff* shift the content of the buff* and assign to TxDataBuff. That could be the actual data that I want to store or could be just file system management portion.

  • When I said that it worked, I meant that I was able to get the write function to return "OK." I'm still not seeing the data on the SD card. I either see an empty file, no file, or a totally corrupted SD card that needs reformatted.

    I wasn't suggesting adding the delay as a permanent solution. I meant it as more of a clue as to what might be going wrong.

    You're right, I didn't look at the loop that shifted the data closely enough. The reuse of the variable name threw me off. You will want to change your linker options to make the stack bigger though.

    Do you have access to logic analyzer or oscilloscope that you could use to check out what data/how much data is being transmitted? I don't think the pins connected to the SD card slot are pinned out on the docking station but you could probably use the XBARs to duplicate the signal on a different GPIOs. Something like:

        EALLOW;
        InputXbarRegs.INPUT1SELECT = 122;
        OutputXbarRegs.OUTPUT1MUX0TO15CFG.bit.MUX1 = 1;
        OutputXbarRegs.OUTPUT1MUXENABLE.bit.MUX1 = 1;
        EDIS;
        GPIO_SetupPinMux(2, GPIO_MUX_CPU1, 5);

    Thanks,

    Whitney

  • Whitney, Any update since the last message I sent? I really need this resolved asap.

    Regards,
    Archit
  • If anyone can help with some insights, I would really appreciate it.

    Regards,
    Archit
  • No update since my response to your PM. I'll paste it here in case it didn't go through.

    I spent some time on this today, and still really don't have any answers or suggestions. Like I said in my previous message, even if I get the functions to return an "OK" status, I still don't actually see anything when I check the SD card.

    I'd be interested in seeing some comparison on a logic analyzer of what the regular xmit_datablock() function waveforms look like versus xmit_datablock_dma() waveforms. Did you happen to do that when you had a logic analyzer?

    Whitney