DMA to PWM

Ask anything your want about the 32X Mushroom programming.

Moderator: BigEvilCorporation

Graz
Very interested
Posts: 81
Joined: Thu Aug 23, 2007 12:36 am
Location: Orlando, FL

DMA to PWM

Post by Graz » Fri Jul 31, 2009 12:25 am

I'm trying to get DMA to the PWM controller working and am having no success. Here is what I am doing:

Code: Select all

SAR1 = source address (in ROM, SH2 address space)
DAR1 = mono pulse width register (0x20004038)
TCR1 = 1024 (some number long enough that I'd notice the output)
CHCHR1 = 0x14E1 (fits Sega's 00XX 0100 1110 0XXX recommended pattern for 16-bit transfers)
DRCR1 = 0
DMACR = 1

Wait... repeat.
Nothing happens. Obviously I've done something wrong. Does anyone know what I've missed, or have a working example they'd like to post?[/code]

TascoDLX
Very interested
Posts: 256
Joined: Tue Feb 06, 2007 8:18 pm

Post by TascoDLX » Fri Jul 31, 2009 4:00 am

Are you setting the PWM Control Register (at 0x20004030) correctly? Bit 4 is used to set mono mode but it's not listed in one of the manuals. Also, be sure to set the Cycle Register (at 0x20004032) but that should go without saying.

Graz
Very interested
Posts: 81
Joined: Thu Aug 23, 2007 12:36 am
Location: Orlando, FL

Post by Graz » Fri Jul 31, 2009 11:56 am

Yes, I've set the cycle and control registers. Writing directly to the PWM FIFO works. I haven't set the mono bit you mentioned, but I have tried DMAing to just the left or right channel with similar results.

Chilly Willy
Very interested
Posts: 2791
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Fri Jul 31, 2009 3:29 pm

I was originally trying to use DMA with Wolf32X for the sound. This was the code:

Code: Select all

    // init the sound hardware
    MARS_PWM_CTRL = 0x0185; // TM = 1, RTP, RMD = right, LMD = left
    if (MARS_VDP_DISPMODE & MARS_PAL_FORMAT)
        MARS_PWM_CYCLE = 521; //44.1kHz
    else
        MARS_PWM_CYCLE = 516; //44.1kHz

    while (1)
    {
        // only do sound when sound subsytem initialized
        while (MARS_SYS_COMM4 == 1)
        {
            // start DMA on first buffer and fill second
            SH2_DMA_SAR1 = (unsigned long)sndbuf & 0x1FFFFFFF;
            SH2_DMA_DAR1 = (unsigned long)&MARS_PWM_LEFT & 0x1FFFFFFF; // storing a long here will set left and right
            SH2_DMA_TCR1 = (unsigned long)NUM_SAMPS; // number longs
            SH2_DMA_CHCR1 = (unsigned long)0x18E1; // dest fixed, src incr, size long, ext req, dack mem to dev, dack hi, dack edge, dreq rising edge, cycle-steal, dual addr, intr disabled, clear TE, dma enabled
            SH2_DMA_DMAOR = (unsigned long)1; // enable DMA - starts dma

            FillSoundBuff(NUM_SAMPS * 2 * 2);

            // wait on DMA
            while (!(SH2_DMA_CHCR1 & 2)) ; // wait on TE
            SH2_DMA_DMAOR = (unsigned long)0; // disable DMA
            SH2_DMA_CHCR1 = (unsigned long)0x18E0; // clear TE, dma disabled

            // start DMA on second buffer and fill first
            SH2_DMA_SAR1 = ((unsigned long)sndbuf & 0x1FFFFFFF) + NUM_SAMPS * 2 * 2;
            SH2_DMA_DAR1 = (unsigned long)&MARS_PWM_LEFT & 0x1FFFFFFF; // storing a long here will set left and right
            SH2_DMA_TCR1 = (unsigned long)NUM_SAMPS; // number longs
            SH2_DMA_CHCR1 = (unsigned long)0x18E1; // dest fixed, src incr, size long, ext req, dack mem to dev, dack hi, dack edge, dreq rising edge, cycle-steal, dual addr, intr disabled, clear TE, dma enabled
            SH2_DMA_DMAOR = (unsigned long)1; // enable DMA - starts dma

            FillSoundBuff(0);

            // wait on DMA
            while (!(SH2_DMA_CHCR1 & 2)) ; // wait on TE
            SH2_DMA_DMAOR = (unsigned long)0; // disable DMA
            SH2_DMA_CHCR1 = (unsigned long)0x18E0; // clear TE, dma disabled
        }
    }
It plays stereo samples via the slave SH2 DMA... well, it was supposed to. It didn't work right, but it did at least make noise on real hardware. I didn't try to debug it because none of the emulators at the time supported DMA PWM audio, so I just switched to SH2 driven audio out using the slave.

I've thought about going back and trying to get the DMA PWM to work as that would be a lot less overhead for the audio. By the way, how are you testing your code? If you're using an emulator, it may not support DMA PWM as I've never seen a 32X game that used it. Gens/GS only just added DMA PWM a few days ago. I'm not sure if KEGA supports it or not.

Graz
Very interested
Posts: 81
Joined: Thu Aug 23, 2007 12:36 am
Location: Orlando, FL

Post by Graz » Sat Aug 01, 2009 3:09 pm

I've been testing on emulators so far. I had been using GensKMod until I realized that it didn't support DMA. I've also tried Gens/GS and Kega. I guess I'll stick to real hardware from now on. PWM sound seems to work in every game I've tried on Gens, so I guess not too many use DMA. Does anyone know any titles that definitely use DMA for PWM audio?

Chilly Willy
Very interested
Posts: 2791
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Sat Aug 01, 2009 3:15 pm

Like I said, I'm not aware of anything more than the 32X test cart that uses DMA PWM. Gen/GS only just added it - you either need to use the test arc posted at Sonic Retro or build it yourself from the latest repo source. The latest code has two fixes related to the PWM (both submitted by yours truly): DMA PWM, and better conversion of PWM values to PC samples.

TascoDLX
Very interested
Posts: 256
Joined: Tue Feb 06, 2007 8:18 pm

Post by TascoDLX » Sun Aug 02, 2009 1:04 pm

Graz wrote:I haven't set the mono bit you mentioned
Nevermind then. I'm not sure it actually exists, and I think the other manual is better anyway.

I tried the test ROM (Mars Check Program) -- it took a little convincing (read: hacking) -- but, in Fusion, PWM DMA works... on the slave, not the master. I haven't a clue what that means. It's the same code.

For the record, the test code uses samples in RAM as the source. It shouldn't make a difference, though. It uses a sine wave so it only needs to keep a small portion in ROM. And it writes to L+R instead of MONO even though the samples are identical on both channels.

Here's what it does after it puts the samples in RAM:

Code: Select all

- write zero (3 words) to MONO (clear both FIFO)
- set PWM cycle register = 0x0209 (~ 44100 KHz)
- set PWM control register = 0x0185 (TM = 1; RTP = 1; Normal Stereo out)
- set DMA.SAR1 = 0x06020000 (RAM source)
- set DMA.DAR1 = 0x20004034 (FIFO dest : L+R)
- set DMA.TCR1 = 0x00002C00 (length is 44 KB)
- set DMA.CHCR1 = 0x00000000 (this clears the DE bit, just in case)
- set DMA.CHCR1 = 0x000018E1 (as expected)
- set DMA.DMAOR = 0x00000001 (go go gadget DMAC)
Then it's good until the transfer ends.

Graz
Very interested
Posts: 81
Joined: Thu Aug 23, 2007 12:36 am
Location: Orlando, FL

Post by Graz » Sun Aug 02, 2009 6:22 pm

TascoDLX wrote:PWM DMA works... on the slave, not the master.
Argh! I've only been using the master! Still haven't had any success, but I don't have time to check anything else right now. I'll move to the slave when I get a chance in a week or so.

BTW, does anyone have the utility that converts AIFF to PWM samples that's mentioned in the hardware manual?

Graz
Very interested
Posts: 81
Joined: Thu Aug 23, 2007 12:36 am
Location: Orlando, FL

Post by Graz » Sun Aug 09, 2009 8:34 pm

Ok, so I tried my original code in Kega using the slave and it works. I still need to find a way to generate PWM data. If anyone has any code to generate PWM data or a better explanation of how to create it than that in the docs, it'd be great if you could post it.

Chilly Willy
Very interested
Posts: 2791
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Sun Aug 09, 2009 10:48 pm

Err - what's so hard about generating PWM samples?

The cycle count sets both the sample rate and the largest value PWM samples can be. The rate is (NTSC) 23.01 MHz / (count - 1). That is how many cycles wide the pulse period will be. Samples simply tell how many cycles to hold the output high before going low for the rest of the cycles specified by count - 1. So 1 will be the minimum PWM sample value, and count - 1 will be the maximum. So the idea is to add all your PCM voices together, then scale the result to (count - 2), then add count/2 to that (makes your signed PCM data unsigned PWM data).

It can be very simple in some cases. For example, take 22050 Hz. The count for that will be 1044. Let's say we have four 8-bit channels of signed PCM. Add the four together. We know that at worst, the data will range from -512 to +511. Add 520 to the result of adding our four channels together and we know that it will range from 8 to 1031, with the "zero" point almost exactly half of (1044 - 2). See how easy it was to generate PWM samples? Just add your four channels of signed PCM, then add 520 to the result. Done.

Graz
Very interested
Posts: 81
Joined: Thu Aug 23, 2007 12:36 am
Location: Orlando, FL

Post by Graz » Mon Aug 10, 2009 12:46 am

Yes, that does sound extremely simple - perhaps it's the quality of the explanation (um, thanks). It all makes sense new. Wow, do I feel dim. I'll give this a spin in a day or so and see what I get.

Chilly Willy
Very interested
Posts: 2791
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Mon Aug 10, 2009 1:37 am

Graz wrote:Yes, that does sound extremely simple - perhaps it's the quality of the explanation (um, thanks). It all makes sense new. Wow, do I feel dim. I'll give this a spin in a day or so and see what I get.
It's a matter of what you've been exposed to in the past. I've dealt with nearly every kind of audio there is, so it seems simple to me. Once you learn about certain types of audio, they may seem simple to you as well. :D

In this case, PWM is linear, just like PCM, so converting the two back and forth is pretty simple once you understand them. It would be a LOT more "fun" is one of the two were non-linear. :wink:

Graz
Very interested
Posts: 81
Joined: Thu Aug 23, 2007 12:36 am
Location: Orlando, FL

Post by Graz » Tue Aug 11, 2009 12:18 am

I applied my new found knowledge to the problem and now have perfect playback - sounds great. Just to recap, in case anyone like me stumbles across this years from now...
  • Set Cycle Register (0x20004034 on SH2) to base clock / sample rate
    Send unsigned PCM data scaled to 1 to (Cycle Register - 1) to PWM fifos
That's it (assuming you've configured everything else correctly). Base clock is 23.01 MHz for NTSC and 22.8 MHz for PAL. For example, if I want to play at 22050 Hz (as in Chilly's example) on an NTSC system, Cycle Register becomes 23.01 x 10^6 / 22050 = 1044. Now, scale PCM data from 1 to 1043 and send it to the FIFOs. This is all fairly approximate because you'll have to round the values as nothing really divides evenly anyway.
Essentially, there is a trade-off between sample rate and bit depth. Higher sample rate -> lower bit depth and vice-versa.

mic_
Very interested
Posts: 265
Joined: Tue Aug 12, 2008 12:26 pm
Location: Sweden
Contact:

Post by mic_ » Wed Dec 09, 2009 8:01 pm

So, did anyone of you ever get DMA-driven PWM output working (on a real 32X as well, not just in emulators)? If so, do you have any source code for a playback example? I'd like to do some audio experiments, but I don't want to waste one of the SH2s by doing the PWM writes in a timed loop.

Graz
Very interested
Posts: 81
Joined: Thu Aug 23, 2007 12:36 am
Location: Orlando, FL

Post by Graz » Thu Dec 10, 2009 12:15 am

I got it working but only tested in the emulator. My ability to run on real hardware is a little hampered right now, so I never got around to testing. I do have all the relevant kit, though, so I can get it all back together. I'll probably be able to give it a shot in a couple of weeks. I'll post again once I get it working.

Post Reply