Friday, April 29, 2011

To DMA or not to DMA

This has all started with the music player. After making some UI for the music player I decided to test some of the music files I had. I decided to see how far it goes with the performance so I searched for the highest bit rate FLAC file I had... and I found it. It had @10 mins of audio at 1600 kbps for a total of @120 MB. And here the problems started.


XMEGA (clocked at 32MHz) it's not powerful enough to read so much data from SD card (+file system overhead), send it to the audio decoder and refresh the UI fast enough. So I decided to go for DMA.

After documenting over the Internet and the datasheet of the xmega (this was my first DMA try), I decided to have a little test: fill the buffer with data from the SD card, send the data to the audio decoder with the help of DMA, wait until the data transfer is complete and repeat all over. The first problem is that the XMEGA DMA is not working with SPI modules in master SPI. For this to work, I had to use USART in master SPI mode. Fortunately this involved only 2 pins to be swapped.
After that was fixed, I started working on the code for the DMA transfer and this was my first try

playStart(&found);
//DMACH is DMA.CH0
DMA.CTRL |= DMA_ENABLE_bm;   
DMACH.ADDRCTRL =
   DMA_CH_SRCRELOAD_NONE_gc | DMA_CH_SRCDIR_INC_gc |
   DMA_CH_DESTRELOAD_NONE_gc | DMA_CH_DESTDIR_FIXED_gc;
      
DMACH.TRIGSRC = DMA_CH_TRIGSRC_USARTE1_DRE_gc;
DMACH.TRFCNT = 32; //32 byte block size
   
DMACH.DESTADDR0 = (((uint16_t)&USARTE1.DATA) >> 0) & 0xFF;
DMACH.DESTADDR1 = (((uint16_t)&USARTE1.DATA) >> 8) & 0xFF;
DMACH.DESTADDR2 = 0x00;
   
while (readNextData()) //load next sector
{
   count++;

   //reload source address (buffer is 512 bytes)
   DMACH.SRCADDR0 = (((uint16_t)playDataBuf) >> 0) & 0xFF;
   DMACH.SRCADDR1 = (((uint16_t)playDataBuf) >> 8) & 0xFF;
   DMACH.SRCADDR2 = 0x00; //internal SRAM
                  
   for (uint8_t i=0; i<16; i++)
   {
      while ((PORTVS.IN & PIN_VS_DREQ_bm) == 0); //wait for vs1053
      
      PORTVS.OUTCLR = PIN_VS_XDCS_bm;
      DMACH.CTRLA = DMA_CH_ENABLE_bm | DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_1BYTE_gc;
      //vsStream32Bytes(playDataBuf, i * 32); //traditional streaming works
      uint8_t flags;
      do
      {
         flags = (DMACH.CTRLB & (DMA_CH_ERRIF_bm | DMA_CH_TRNIF_bm));
      } while (flags == 0);
      PORTVS.OUTSET = PIN_VS_XDCS_bm;
      
      DMACH.CTRLB |= ( flags );
   }      
}                        
playStop(); 

Well, the problem was that it wouldn't work. It appeared like it was transferring data very fast (too fast actually) to the audio decoder, but no sound... If I commented the DMA part from the code and use the classic transfer function vsStream32Bytes, it all worked well but with DMA no luck. After many hours of debugging the code, the problem was revealed... and it had nothing to do with the DMA controller which actually worked.
 So, here is the working code: 

playStart(&found);
                  
PORTVS.OUTCLR = PIN_VS_XDCS_bm;
                  
DMACH.ADDRCTRL =
   DMA_CH_SRCRELOAD_NONE_gc | DMA_CH_SRCDIR_INC_gc |
   DMA_CH_DESTRELOAD_NONE_gc | DMA_CH_DESTDIR_FIXED_gc;
      
DMACH.TRIGSRC = DMA_CH_TRIGSRC_USARTE1_DRE_gc;
DMACH.TRFCNT = 32;
                        
DMACH.DESTADDR0 = (((uint16_t)&USARTE1.DATA) >> 0 * 8) & 0xFF;
DMACH.DESTADDR1 = (((uint16_t)&USARTE1.DATA) >> 1 * 8) & 0xFF;
DMACH.DESTADDR2 = 0x00;
                        
while (readNextData(dataBuf))
{
   DMACH.SRCADDR0 = (((uint16_t)dataBuf) >> 0 * 8) & 0xFF;
   DMACH.SRCADDR1 = (((uint16_t)dataBuf) >> 1 * 8) & 0xFF;
   DMACH.SRCADDR2 = 0x00;   
                     
   count++;
   for (uint8_t i=0; i<16; i++)
   {
      while ((PORTVS.IN & PIN_VS_DREQ_bm) == 0); //wait for vs1053
                        
      DMACH.CTRLA = DMA_CH_ENABLE_bm | DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_1BYTE_gc;
      uint8_t flags;
      do
      {
         flags = (DMACH.CTRLB & (DMA_CH_ERRIF_bm | DMA_CH_TRNIF_bm));
      } while (flags == 0);
                        
      DMACH.CTRLB |= ( flags );
   }
}                        
playStop();
 

Of course, the above implementation isn't to useful and it's pretty ugly coded, but this was just a test... and it works. Still, I have no idea why it didn't worked in the first place. If anybody has a clue, please leave me a comment below!

1 comment:

  1. On your problem, what is the DMA transfer trigger? Maybe the ADC really finishes too soon. Try setting a bigger prescale factor or using another trigger (timer?).

    Sorry, but the code has changed a lot since then. In the current implementation, XDCS is asserted by default and XCS is asserted only when I read/write registers by classic way.

    I fixed my issues with the DMA. For this issue I don't really know what was the cause. I don't think it's the compiler, since I've also tested it with no optimizations on the code. I believe it's some sort of a problem with DMAC itself.
    Another issue I found was that after using DMA, I couldn't read/write to the VS1053 registers via classic way. This was because I was sending data via SPI but I was ignoring the received data so the buffer got overflowed. So I fixed it by flushing the RX buffer before classic read/write operations.

    ReplyDelete