gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Audio > Curse you, DMA

#13137 - poslundc - Sat Dec 06, 2003 2:46 am

At DekuTree64's suggestion, I tried converting my sound player so that instead of using an interrupt every on a 16 KHz cycle I would use DMA1 instead.

The trouble is that DMA1 sits there glaring at me and does absolutely nothing. I have checked a dozen other demos and as far as I can tell I am doing exactly the same thing they are doing. I have checked the memory map and the DMA1 source, destination and control registers all have the correct values, and my EWRAM buffer has data in it. My timers and interrupts are working (this program works when I switch it back to a timer-based system).

This has me absolutely stumped. I don't know what I could possibly be doing wrong; the DMA just won't seem to activate.

Here's the code, with some pseudocode for brevity's sake:

Code:
char         *sndBuffA = (char *)EWRAM;
char         *sndBuffB = (char *)(EWRAM + 256);
char         *buffActive;

void IntTIMER1(void)
{
   unsigned short   i;
   char         *buff;
   
   i = 256;
   
   buff = buffActive;
   buffActive = (buffActive == sndBuffA) ? sndBuffB : sndBuffA;
   REG_DMA1SAD = (unsigned int)buffActive;

   if (channels[0].sampleClock)      // just dump in channel 0 for now
      while (i--)
         // dump values into buffer

   REG_IF = INT_TIMER1;
}

void AgbMain(void)
{
   REG_SOUNDCNT_H = 0x0B0F;
   REG_SOUNDCNT_X = 0x0080;

   InitInterrupts();
   PlaySample(0, (ModSample *)(&(testMod[4])), n);

   buffActive = sndBuffA;   
   REG_DMA1SAD = (unsigned int)buffActive;      // Source: sound buffer
   REG_DMA1DAD = 0x040000A0;      // Dest: sound FIFO
   REG_DMA1CNT_H = 0xB600;
   
   REG_TM0CNT_L = 0xFBFF;         // Activate sound chip 16384 times/second
   REG_TM1CNT_L = 0xFEFF;         // Refill buffer every 256 bytes that go through

   REG_TM1CNT_H = 0x00C4;         // Enable with IRQ and cascade from Timer 0
   REG_TM0CNT_H = 0x0080;         // Interrupt at 16384 Hz

   while(1);
}


It's so bloody straightforward I can't figure out what could possibly be wrong with it. Like I said, it works fine with the timers, the memory map displays the correct values for the DMA registers (although the source never increments), and there is data in the buffer.

Anyone feel up to fielding this one?

Thanks,

Dan.

#13140 - DekuTree64 - Sat Dec 06, 2003 3:41 am

You need to set the dest fixed bit in DM1CNT, otherwise it will increment each time and be writing sound data to all sorts of registers.
So instead of 0xb600, set it to 0xb640.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#13141 - poslundc - Sat Dec 06, 2003 4:03 am

Already tried that in my myriad of experiments... no such luck.

Any other ideas?

Dan.

#13144 - tepples - Sat Dec 06, 2003 4:26 am

Most obvious problem that I saw is that you have to turn off a DMA channel before you can write a new source address.

An excerpt of tested code from my music engine follows:
Code:
struct DMACHN
{
  const void *src;
  void *dst;
  u16 count;
  u16 control;
};

#define DMA ((volatile struct DMACHN *)0x040000b0)
#define DMA_DSTUNCH     0x0040
#define DMA_SRCINC      0x0000
#define DMA_REPEAT      0x0200
#define DMA_U32         0x0400
#define DMA_SPECIAL     0xB000

static void dsound_switch_buffers(const void *src)
{
  DMA[1].control = 0;

  /* no-op to let DMA registers catch up */
  asm volatile ("eor r0, r0; eor r0, r0" ::: "r0");

  DMA[1].src = src;
  DMA[1].dst = (void *)0x040000a0; /* write to FIFO A address */
  DMA[1].count = 1;
  DMA[1].control = DMA_DSTUNCH | DMA_SRCINC | DMA_REPEAT | DMA_U32 | DMA_SPECIAL;
}

The dsound_switch_buffers() function starts DMA at a given address.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#13145 - poslundc - Sat Dec 06, 2003 4:57 am

Tepples, you are the freaking patron saint of the GBA or something.

I was already turning the DMA control off and then on again in my tests, but I wasn't allowing the extra cycles for the "off" command to get written to memory.

Bah! No one else's sample code seems to need even to shut DMA off before writing a new start address, let alone allow for latency. How annoying.

Dan.

#13157 - tepples - Sat Dec 06, 2003 4:13 pm

poslundc wrote:
No one else's sample code seems to need even to shut DMA off before writing a new start address, let alone allow for latency.

Perhaps others' sample code is designed to work only in older emulators, written before VisualBoyAdvance became as accurate as it is now by people who don't own an MBV2 cable. This was a big problem on the NES-scene when NESticle was the best thing out there; the increases in accuracy that came with LoopyNES and then FCE Ultra have improved the quality of more recent NES homebrew software with the hardware.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#13210 - SmileyDude - Mon Dec 08, 2003 6:19 pm

tepples wrote:
Perhaps others' sample code is designed to work only in older emulators, written before VisualBoyAdvance became as accurate as it is now by people who don't own an MBV2 cable.


This one definetly caught me when I was working on my mixer earlier this year -- VBA under Linux would work fine, but when I tried it on the GBA it wouldn't work at all.

General rule of thumb for me is that when I work with hardware specific code (like sound mixing or graphics code), I test on hardware more often. Otherwise, if I'm working on something like game logic, or tweaking some graphics, I can test on hardware when I reach certain milestones.
_________________
dennis