Page 1 of 4
DMA to PWM
Posted: Fri Jul 31, 2009 12:25 am
by Graz
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]
Posted: Fri Jul 31, 2009 4:00 am
by TascoDLX
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.
Posted: Fri Jul 31, 2009 11:56 am
by Graz
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.
Posted: Fri Jul 31, 2009 3:29 pm
by Chilly Willy
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.
Posted: Sat Aug 01, 2009 3:09 pm
by Graz
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?
Posted: Sat Aug 01, 2009 3:15 pm
by Chilly Willy
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.
Posted: Sun Aug 02, 2009 1:04 pm
by TascoDLX
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.
Posted: Sun Aug 02, 2009 6:22 pm
by Graz
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?
Posted: Sun Aug 09, 2009 8:34 pm
by Graz
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.
Posted: Sun Aug 09, 2009 10:48 pm
by Chilly Willy
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.
Posted: Mon Aug 10, 2009 12:46 am
by Graz
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.
Posted: Mon Aug 10, 2009 1:37 am
by Chilly Willy
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.
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.

Posted: Tue Aug 11, 2009 12:18 am
by Graz
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.
Posted: Wed Dec 09, 2009 8:01 pm
by mic_
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.
Posted: Thu Dec 10, 2009 12:15 am
by Graz
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.