• AVR Freaks

PIC32 SD card reader using DMA?

Author
david.harrison
Starting Member
  • Total Posts : 87
  • Reward points : 0
  • Joined: 2011/03/14 11:13:07
  • Location: Ottawa, Ontario, Canada
  • Status: offline
2012/02/06 15:07:18 (permalink)
0

PIC32 SD card reader using DMA?

I am using a PIC32MX695F512L and reading an SD card using SPI2 clocked at 40MHz - yes it works at that speed on a very small application system board with short traces. Everything works using a modified FATfs file system and my own SD card reader code using standard CPU intensive SPI reads and writes.
 
When reading a buffer of 4096 bytes I am getting 1.7MBytes/second read speed. I want to at least double that using DMA - the SPI bus by itself would give 40MHz/8 = 5MBytes/sec if there were no FAT in the way to slow things down. I see various hints in this forum about using DMA to write SD cards but next to nothing on how to use DMA to read SD cards. It is more complicated than writing SD cards since the PIC32 is in SPI master mode which means you have to send bytes out in order to receive bytes back from the SD card.
 
In another part of my system I have SPI DMA writes to an external I2S DAC working, but the DAC is in I2S master mode and the PIC32 is in I2S  (SPI) slave mode.
Does anyone have a working MPLAB project they could send me that uses DMA SPI reads from an SD Card? Thanks for any help you can give me.
 
 

David Harrison
CEO
Model Sounds Inc.
www.modelsoundsinc.com
The World's Most Advanced R/C Sound Systems
#1

9 Replies Related Threads

    erupter
    Senior Member
    • Total Posts : 179
    • Reward points : 0
    • Joined: 2011/05/31 15:15:52
    • Location: Rome, Italy
    • Status: offline
    Re:PIC32 SD card reader using DMA? 2012/02/07 01:36:37 (permalink)
    0
    Hi David.
    Look for Aiden.Morrison's posts, he did the mos research on the topic.

    Here is one with code, last time it didn't run on me...
    http://www.microchip.com/forums/m572924.aspx

    If you succeed I'd me most obliged if you would share ;)
    #2
    david.harrison
    Starting Member
    • Total Posts : 87
    • Reward points : 0
    • Joined: 2011/03/14 11:13:07
    • Location: Ottawa, Ontario, Canada
    • Status: offline
    Re:PIC32 SD card reader using DMA? 2012/02/07 12:54:19 (permalink)
    0
    erupter

    Hi David.
    Look for Aiden.Morrison's posts, he did the mos research on the topic.

    Here is one with code, last time it didn't run on me...
    http://www.microchip.com/forums/m572924.aspx

    If you succeed I'd me most obliged if you would share ;)


    Yes, I am aware of Aiden.Morrison's postings and I tried them but couldn't get it to work. I have put my code below. I am one of those people who do not like to use Microchip's helper functions and macros as I think they only obscure the code and make it unclear as to what is going on so I prefer to access the peripheral registers directly as below. I use DMA channel 2 for transfers from the SPI2BUF to my RAM buffer and DMA Channel 1 to pump dummy 0xFF bytes out to the SPI2 Bus. I do not need to use interrupts since I have to wait for the SPI2BUF - RAM transfers to complete before I can transfer the next block, so I might just as well wait in a polling loop.
     
    I can't get my code to work either. Has anyone got any ideas why not?
     
    The InitSPI2DMATransfers() function is called once only in the SD Card Init code and the SPI2ToRamUsingDMA2() function is called for every data block I want to receive.
     
    static U8 DummyBuffer[4096];
    void InitSPI2DMATransfers(void)
    {
       DMACONbits.ON = 1;                                  // DMA module is enabled
     
       // Use DMA Channel 2 for SPI2BUF - RAM transfers.
       DCH2CONbits.CHPRI = 2;                           // channel priority 2
       DCH2SSIZ = 1;                                           // Source size
       DCH2CSIZ = 1;                                           // Cell size transferred on an event
       DCH2INTbits.CHDDIE  = 1;                         // Enable DMA Channel 2 Destination Done interrupt.
       DCH2ECONbits.CHSIRQ = _SPI2_RX_IRQ; // Trigger cell transfer event on SPI2 Receive IRQ
       DCH2ECONbits.SIRQEN = 1;                      // Enable cell transfer event on IRQ
     
       DCH2INT  = 0;                                           // Clear all DMA Channel 2 interrupt enables and flags
       IFS1bits.DMA2IF   = 0;                              // Clear  DMA Channel 2 interrupt flag
       DCH2CONbits.CHEN  = 1;                         // Enable DMA Channel 2
       DCH2CONbits.CHAEN = 1;                        // Enable Auto enable
     
       // Initialize DummyBuffer to all 0xFF
       U16 i;
       for (i = 0; i < sizeof(DummyBuffer); i++)
       {
          DummyBuffer = 0xFF;
       }
     
       // Use DMA Channel 1 for DummyBuffer - SPI2BUF copies.
       DCH1CONbits.CHPRI = 1;                           // channel priority 1
       DCH1CSIZ = 1;                                           // cell size transferred on an event
       DCH1DSIZ = 1;                                           // SPI2 destination size
       DCH1INTbits.CHSDIE  = 1;                         // Enable DMA Channel 1 Source Done interrupt.
       DCH1ECONbits.CHSIRQ = _SPI2_TX_IRQ; // Trigger cell transfer event on SPI2 Transmit IRQ
       DCH1ECONbits.SIRQEN = 1;                      // Enable cell transfer event on IRQ
       DCH1INT               = 0;                              // Clear all DMA Channel  1 interrupt enables and flags
       IFS1bits.DMA1IF   = 0;                              // Clear  DMA Channel 1 interrupt flag
       DCH1CONbits.CHEN  = 1;                         // Enable DMA Channel 1
       DCH1CONbits.CHAEN = 1;                        // Enable Auto enable
    }
     
    void SPI2ToRamUsingDMA2(U8* destination, U16 numBytes)
    {
       // Setup transfer from SPI2BUF to our RAM buffer
       DCH2SSA = VirtToPhys((void*)&SPI2BUF);       // Source address
       DCH2DSA = VirtToPhys(destination);               // Destination address
       DCH2DSIZ = numBytes;                                   // Size for destination
       DCH2CONbits.CHEN  = 1;                                // Enable DMA Channel 2
       DCH2CONbits.CHAEN = 1;                               // Enable Auto enable
       DCH2INT = 0;                                                  // Clear all DMA Channel 2 interrupt enables and flags
       IFS1bits.DMA2IF = 0;                                      // Clear DMA Channel 2 interrupt flag
      
       // Setup transfer from DummyBuffer (0xFF) to SPI2BUF
       DCH1SSA = VirtToPhys(DummyBuffer);           // Source address
       DCH1DSA = VirtToPhys((void*)&SPI2BUF);     // Destination address
       DCH1SSIZ = numBytes;                                  // Size for this transfer source
       DCH1CONbits.CHEN  = 1;                               // Enable DMA Channel 1
       DCH1CONbits.CHAEN = 1;                              // Enable Auto enable
       DCH1INT = 0;                                                 // Clear all DMA Channel 1 interrupt enables and flags
       IFS1bits.DMA1IF = 0;                                     // Clear DMA Channel 1 interrupt flag
     
       DCH1ECONbits.CFORCE = 1;                         // Force the transfer of numBytes from DummyBuffer to SPI2BUF
       DCH2ECONbits.CFORCE = 1;                         // Force the transfer of numBytes from SPI2BUF to destination
      
       // Wait for transfer from SPI2BUF to our RAM buffer to complete
       while (!DCH2INTbits.CHDDIF) {}
     
       DCH2INT = 0;                                               // Clear all DMA Channel 2 interrupt enables and flags
       IFS1bits.DMA2IF = 0;                                   // Clear DMA Channel 2 interrupt flag
       DCH1INT = 0;                                               // Clear all DMA Channel 1 interrupt enables and flags
       IFS1bits.DMA1IF = 0;                                   // Clear DMA Channel 1 interrupt flag
    }
     
    post edited by david.harrison - 2012/02/07 12:55:31

    David Harrison
    CEO
    Model Sounds Inc.
    www.modelsoundsinc.com
    The World's Most Advanced R/C Sound Systems
    #3
    simong123
    Lab Member No. 003
    • Total Posts : 1310
    • Reward points : 0
    • Joined: 2012/02/07 18:21:03
    • Location: Future Gadget Lab (UK Branch)
    • Status: offline
    Re:PIC32 SD card reader using DMA? 2012/02/07 19:17:25 (permalink)
    0
    Below is an extract of known working code to read from SD Card using SPI1 and DMA 0/1 from one of my projects. Note it uses the trick of disabling the SDO pin and setting it to high to generate the 0xFF's sent to the SD card. This removes the requirement to have a large buffer filled with 0xFF's. Also note it uses an end of transfer interrupt to set a flag, instead of polling the DMA registers.
    #define SDIFSCLR IFS0CLR
    #define SDIECCLR IEC0CLR
    #define SDIECIFSBITS 0x03000000u
    #define SDRXINT 24
    #define SDTXINT 25
    #define SPIBUF SPI1BUF     
    volatile unsigned int gRXDone=0;
    unsigned errcnt=0;  
    #pragma interrupt DMAInt01 ipl6 vector 37
    void DMAInt01(void)
    {
     mDMA0ClearIntFlag();
     mDMA1ClearIntFlag();
     gRXDone=1;
    }  
    int ReadSPIDMA(char *buffer,unsigned len)
    {
     unsigned tmp;      
    //disable SDO pin and set high         
    TRISGbits.TRISG8=0; 
        LATGbits.LATG8=1; 
        SPICON1bits.DISSDO=1;     
     //clear out spi buffer       
     data_token=SPIBUF; 
        SPI1STATCLR=0x00000040; 
     //clear interrupt flags     
    IFS1CLR=0x00030000; 
        IEC1CLR=0x00030000; 
        SDIFSCLR=SDIECIFSBITS; 
        SDIECCLR=SDIECIFSBITS;  
    //set up dma0 
        DCH0CON=0x02; 
        DCH0ECON=0x0; 
        DCH0ECONbits.CHSIRQ=SDTXINT;//SPI1ATX 
        DCH0ECONbits.SIRQEN = 1; 
        DCH0SSA=VirtToPhys(buffer); 
        DCH0DSA=VirtToPhys(&SPIBUF); 
        DCH0SSIZ=len; 
        DCH0DSIZ=1; 
        DCH0CSIZ=1; 
        DCH0INTCLR=0x00ff00ff; 
        DCH0INTSET=0x00090000; 
         //set up dma1
        DCH1CON=0x03; 
        DCH1ECON=0x0; 
        DCH1ECONbits.CHSIRQ=SDRXINT;//SPI1ARX 
        DCH1ECONbits.SIRQEN = 1; 
        DCH1SSA=VirtToPhys(&SPIBUF); 
        DCH1DSA=VirtToPhys(buffer); 
        DCH1SSIZ=1; 
        DCH1DSIZ=len; 
        DCH1CSIZ=1; 
        DCH1INTCLR=0x00ff00ff; 
        DCH1INTSET=0x00090000; 
        DCH1CONSET=0x00000080;  
     
    //clear interrupt flags 
        gRXDone=0; 
        mDMA0ClearIntFlag(); 
        mDMA1ClearIntFlag();  
    //enable dma1 interrupt 
        INTEnable(INT_DMA1,1); 
        IEC1SET=0x00020000;  
    //start transfer 
        DCH0CONSET=0x00000080; 
        DCH0ECON |=0x00000080;  
    tmp=8192;  
     //wait for end of transfer
    //    while((!(DCH1INT&0x0b))&&(tmp))
    //    {
    //    tmp--;//PollDelay();
    //    }     
    while((!gRXDone)&&(tmp)) 
        { 
            Nop(); 
        }   
    //re-enable SDO
        SPI2CONbits.DISSDO=0;     
       if (!(tmp))  //we timed out - increment error count 

            errcnt++;
    }
     
    --edited for format and comments
    post edited by simong123 - 2012/02/07 19:36:20
    #4
    david.harrison
    Starting Member
    • Total Posts : 87
    • Reward points : 0
    • Joined: 2011/03/14 11:13:07
    • Location: Ottawa, Ontario, Canada
    • Status: offline
    Re:PIC32 SD card reader using DMA? 2012/02/08 10:08:18 (permalink)
    0
    I've integrated your code into my system. I've managed to get it to work, with your help, thanks very much. I've modified it a bit to comply with my coding style which hates "magic numbers" which MCHP seem very fond of in their reference manuals! Those "magic numbers" are almost impossible to understand!
     
    My code differs in that it does not use an interrupt handler. If you use an interrupt handler to set a done flag and then go and wait for that done flag in a polling loop, you might just as well wait for the interrupt condition in the polling loop and dispense with the interrupt handler! Also I have separated out the non-recurring code into a separate initialization routine which need only be called once. This minmizes the amount of code that has to be called for every data block received.
     
    My DMA channels used for this are 1 and 2 since DMA0 is used elsewhere. My SPI channel is SPI2. I like the neat trick of disabling the SDO output and setting the pin to high to generate the stream of 0XFF's needed to clock out the data from the SD card.
    So my read data throughput has now increased from 1.7MBytes/second to close to 3MBytes/second - a big improvement.
     
    void InitSPI2DMATransfers(void)
    {
       DMACONbits.ON = 1;          // DMA module is enabled
       // Setup transfer from SPI2BUF to our RAM buffer
       DCH2SSA = VirtToPhys((void*)&SPI2BUF);   // Source address
       DCH2SSIZ = 1;                            // Source size
       DCH2CSIZ = 1;                            // Cell size
       DCH2ECONbits.CHSIRQ = _SPI2_RX_IRQ;      // Trigger cell transfer event on SPI2 Receive IRQ
       DCH2ECONbits.SIRQEN = 1;                 // Enable cell transfer event on IRQ
       DCH2CONbits.CHEN  = 1;                   // Enable DMA Channel 2
       DCH2INT = 0;                             // Clear all DMA Channel 2 interrupt enables and flags
       IFS1bits.DMA2IF = 0;                     // Clear DMA Channel 2 interrupt flag
       // Setup transfer from buffer (0xFF) to SPI2BUF
       // Note: SDO is disabled and set to 1 so we always clock out 0xFF,
       // so the actual source for the DMA transfer doesn't matter,
       // as long as it points to a valid address throughout the transfer
       DCH1DSA = VirtToPhys((void*)&SPI2BUF);   // Destination address
       DCH1DSIZ = 1;                            // Destination size
       DCH1CSIZ = 1;                            // Cell size
       DCH1ECONbits.CHSIRQ = _SPI2_TX_IRQ;      // Trigger cell transfer event on SPI2 Transmit IRQ
       DCH1ECONbits.SIRQEN = 1;                 // Enable cell transfer event on IRQ
       DCH1CONbits.CHEN  = 1;                   // Enable DMA Channel 1
       DCH2INT = 0;                   // Clear all DMA Channel 2 interrupt enables and flags
       IFS1bits.DMA2IF = 0;                // Clear DMA Channel 2 interrupt flag
       DCH1INT = 0;                   // Clear all DMA Channel 1 interrupt enables and flags
       IFS1bits.DMA1IF = 0;               // Clear DMA Channel 1 interrupt flag
    }
     
    void SPI2ToRamUsingDMA2(U8* buffer, U16 numBytes)
    {
       // Disable SDO pin and set high (to transmit 0xFFs)
       SPI2CONbits.DISSDO = 1;
       TRISGbits.TRISG8 = 0;
       SDCardSDOOutPinValue = 1;
       // Clear out spi buffer
       U8 dummy = SPI2BUF;
       SPI2STATbits.SPIROV = 0;
       // Setup transfer from SPI2BUF to our RAM buffer
        DCH2DSA = VirtToPhys(buffer);            // Destination address
       DCH2DSIZ = numBytes;                     // Destination size
       DCH2CONbits.CHEN = 1;                   // Enable DMA Channel 2
       DCH2INT = 0;                             // Clear all DMA Channel 2 interrupt enables and flags
       IFS1bits.DMA2IF = 0;                     // Clear DMA Channel 2 interrupt flag
       // Setup transfer from buffer (0xFF) to SPI2BUF
       // Note: SDO is disabled and set to 1 so we always clock out 0xFF,
       // so the actual source for the DMA transfer doesn't matter,
       // as long as it points to a valid address throughout the transfer
       DCH1SSA = VirtToPhys(buffer);            // Source address
       DCH1SSIZ = numBytes;                     // Source size
       DCH1CONbits.CHEN = 1;                   // Enable DMA Channel 1
       DCH1INT = 0;                             // Clear all DMA Channel 1 interrupt enables and flags
       IFS1bits.DMA1IF = 0;                     // Clear DMA Channel 1 interrupt flag
       DCH1ECONbits.CFORCE = 1;                 // Force the transfer of numBytes from buffer (0xFF) to SPI2BUF
       // Wait for transfer from SPI2BUF to our RAM buffer to complete
       while (!DCH2INTbits.CHDDIF) {}
       DCH2INT = 0;                   // Clear all DMA Channel 2 interrupt enables and flags
       IFS1bits.DMA2IF = 0;                // Clear DMA Channel 2 interrupt flag
       DCH1INT = 0;                   // Clear all DMA Channel 1 interrupt enables and flags
       IFS1bits.DMA1IF = 0;               // Clear DMA Channel 1 interrupt flag
       // Re-enable SDO for normal SPI master operation
       SPI2CONbits.DISSDO = 0;
    }
     
    post edited by david.harrison - 2012/02/08 17:08:53

    David Harrison
    CEO
    Model Sounds Inc.
    www.modelsoundsinc.com
    The World's Most Advanced R/C Sound Systems
    #5
    erupter
    Senior Member
    • Total Posts : 179
    • Reward points : 0
    • Joined: 2011/05/31 15:15:52
    • Location: Rome, Italy
    • Status: offline
    Re:PIC32 SD card reader using DMA? 2012/02/08 10:46:42 (permalink)
    0
    Simong: do you also write to it with DMA?
    I'm most interested in writing.
    I already use FatFS and can get decent t-rates, but I'd like the DMA to free some processor time.

    Free-lance Embedded Engineer
    http://it.linkedin.com/in/embeddedesign/en
    #6
    simong123
    Lab Member No. 003
    • Total Posts : 1310
    • Reward points : 0
    • Joined: 2012/02/07 18:21:03
    • Location: Future Gadget Lab (UK Branch)
    • Status: offline
    Re:PIC32 SD card reader using DMA? 2012/02/09 10:03:52 (permalink)
    0
    @David,
        I use an interrupt because according to MCHP continuously polling the DMA registers will slow down the transfer due to bus contention. Don't know if that is true or not.
    @erupter
    Yes. I will try to post the code this evening. Posting anything to this forum and making it anything like readable is not easy;)
     
     
    edit - formatting for readability
    post edited by simong123 - 2012/02/09 10:05:38
    #7
    david.harrison
    Starting Member
    • Total Posts : 87
    • Reward points : 0
    • Joined: 2011/03/14 11:13:07
    • Location: Ottawa, Ontario, Canada
    • Status: offline
    Re:PIC32 SD card reader using DMA? 2012/02/10 09:39:32 (permalink)
    0
    simong123

    @David,
        I use an interrupt because according to MCHP continuously polling the DMA registers will slow down the transfer due to bus contention. Don't know if that is true or not.


    Thanks, I didn't know that. I tried using an interrupt handler, as you suggest, but it didn't make any difference to my read speed, which is the bottom line.

    David Harrison
    CEO
    Model Sounds Inc.
    www.modelsoundsinc.com
    The World's Most Advanced R/C Sound Systems
    #8
    simong123
    Lab Member No. 003
    • Total Posts : 1310
    • Reward points : 0
    • Joined: 2012/02/07 18:21:03
    • Location: Future Gadget Lab (UK Branch)
    • Status: offline
    Re:PIC32 SD card reader using DMA? 2012/02/15 02:52:50 (permalink)
    0
    As promised, SD DMA write. Appologies for the delay.

    Same defines etc as for read.

    int WriteSPIDMA(char *buffer,unsigned len)
    {
        unsigned tmp;     
        data_token=SPIBUF;
        SPI1STATCLR=0x00000040;
        IFS1CLR=0x00030000;
        IEC1CLR=0x00030000;
        SDIFSCLR=SDIECIFSBITS;
        SDIECCLR=SDIECIFSBITS;
        DCH1CON=0x02;
        DCH1ECON=0x0;
        DCH1ECONbits.CHSIRQ=SDTXINT;//SPI1ATX
        DCH1ECONbits.SIRQEN = 1;
        DCH1SSA=VirtToPhys(buffer);
        DCH1DSA=VirtToPhys(&SPIBUF);
        DCH1SSIZ=len;
        DCH1DSIZ=1;
        DCH1CSIZ=1;
        DCH1INTCLR=0x00ff00ff;
        DCH1INTSET=0x00090000;
       
        gRXDone=0;
        mDMA1ClearIntFlag();
        INTEnable(INT_DMA1,1);
        IEC1SET=0x00020000;
        DCH1CONSET=0x00000080;
        DCH1ECON |=0x00000080;
        while(!gRXDone)
        {
            Nop();
        }
        while(SPI1STATbits.SPIBUSY)
        {
            tmp++;
        }
        (SPIBUF);
        SPI1STATCLR=0x0000ffff;
        return len;
    }

    post edited by simong123 - 2012/02/15 02:54:23
    #9
    erupter
    Senior Member
    • Total Posts : 179
    • Reward points : 0
    • Joined: 2011/05/31 15:15:52
    • Location: Rome, Italy
    • Status: offline
    Re:PIC32 SD card reader using DMA? 2012/02/15 06:12:06 (permalink)
    0
    Quoting is borken again... Simong you said in a previous post that you used interrupt instead of polling. Why in your latest piece of code I see you polling both gRXDone and SPIxSTATbits.SPIBUSY ? Also I would like to know what should I do to adapt your code to FatFS. Thank you very much for sharing!

    Free-lance Embedded Engineer
    http://it.linkedin.com/in/embeddedesign/en
    #10
    Jump to:
    © 2019 APG vNext Commercial Version 4.5