DMC IRQs

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
User avatar
never-obsolete
Posts: 411
Joined: Wed Sep 07, 2005 9:55 am
Location: Phoenix, AZ
Contact:

DMC IRQs

Post by never-obsolete »

So I'm playing around with splitting the screen twice without a mapper. The first split will use sprite0 and be around scanline 32. The second split will be around scanline 208. I'm trying to use the dmc irq to approximate it since it doesn't have to be exact (I'm extending vblank for chr-ram writes).


The main loop (without all the init code):

Code: Select all

	; ...

	ldx #$40			  ; 
	stx PORT2			 ;
	ldx #0			    ;
	stx SND_CLOCK		; 	
	; ...

	cli
_loop:
	jmp _loop

This is the code called during the nmi handler. The sample is located at $C000 and is $401 bytes of $00.

Code: Select all

	lda sndChannelEnable		; disable dmc channel
	and #$0F					   ;
	sta SND_CLOCK			    ;

	lda #%10001111			   ; set sample ctrl
	sta SND_DMC_CTRL			 ;
	lda #$00				      ; set dac
	sta SND_DMC_DA			   ;
	lda #$00				      ; set sample addr
	sta SND_DMC_ADDR			 ;
	lda #$40				      ; set sample length
	sta SND_DMC_DL			   ;

	lda sndChannelEnable		; enable dmc channel
	ora #$10				      ;
	sta sndChannelEnable		;
	sta SND_CLOCK			    ;

The IRQ handler is empty for now:

Code: Select all

IRQBRK:
	sei
	rti

I've been able to split the screen with mapper IRQs, but I'm not sure if I'm initializing the APU correctly. Any thoughts?
tepples
Posts: 22819
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

A while back, I made a demo that does two splits, and does it to within a granularity less than one scanline. I plan to post it tonight to see what someone else can do with it.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Re: DMC IRQs

Post by Disch »

never-obsolete wrote:

Code: Select all

IRQBRK:
	sei
	rti
That will deadlock your program.

1) I is already set by the IRQ. The SEI does nothing.
2) RTI will pull status from the stack, resulting in I being cleared again
3) Because RTI will clear I, another IRQ will occur immediately, causing a deadlock in the IRQ handler.

You need to acknowledge the IRQ. I forget how this is done on the DMC. I think you just read $4015, but you'll have to double-check.
User avatar
Bregalad
Posts: 8087
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Post by Bregalad »

For the first part of your code, I'm not familiar with those weird labels so I can't help you.

Like Dish says, you MUST acknownledge an IRQ before doing cli or rti or any instruction that will return else it will be an endless loop of IRQs (the only exeption is an IRQ triggered by a brk instruction but here it's clearly not the case).

In the case of DMC IRQs, you should read $4015 before returning. If DMC is your only source of IRQ, you can just discard the result and assume the source of the IRQ was the DMC, else you'd have to acknownledge all possible source of IRQs, and check if their bit was set, and act accordingly,

I recommend to do something like this in your IRQ handler :

Code: Select all

IRQ
    bit $4015
    bvs _noDMC    ;This line can be deleted if DMC is the only possible IRQ source
    lda GrayScale_toggle
    eor #$01
    sta GrayScale_toggle
    ora #$1e
    sta $2001
_noDMC
   rti
This will toggle grayscale mode each IRQ. If you have correctly 2 splitpoint, the middle of your screen between both splitpoints should be gray.
Useless, lumbering half-wits don't scare us.
User avatar
never-obsolete
Posts: 411
Joined: Wed Sep 07, 2005 9:55 am
Location: Phoenix, AZ
Contact:

Post by never-obsolete »

D'oh, I forgot about acknowledging the irq. Thanks for the heads up.

@Tepples: I'll wait to see your example. I have a feeling I'm not setting up the DMC registers correctly.
tepples
Posts: 22819
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

It turned out that I had lost half the source code for this effect when my laptop died a few months ago. But I still had the working ROM on my PowerPak, and I managed to teach myself enough about da65 to recreate working source code.

DPCM Split
Mapper 0, runs on PowerPak. README file inside archive explains how it works. It still has glitches that someone like blargg might be able to help find and fix.
User avatar
Bregalad
Posts: 8087
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Post by Bregalad »

Your technique is interesting. If I understand well, you play a 1-byte sample, and count the cycles before the first IRQ, to compensate for the jittering.

The problem is that you have to wait a dozen of IRQs for a dozen of scanline, so if this technique were to be used for a longer time between both split, this constant stream of useless IRQs would waste very significant CPU time for nothing (and half-killing the purpose of interrupts, although it's still better than waste ALL the CPU time).
Would this technique work as well if you play a 1-byte sample, count the # of cycles before the interrupt, then play a 17 or 33 byte sample to have only 2 IRQs in total ?

Another thing is that you use sprite-0 hit (with 7-cycle jitter I guess) for the first split, and start your IRQ technique from there. If you'd instead reverse the thing, and use IRQ for the first split and sprite 0 for the second, you could do that technique with 3 cycle jitter instead, and it could perform better. Altough of course this will waste 100% of CPU time between the 1st and 2nd split instead of between the top and the 1st split, so it all depends of what you'd want to achieve.

EDIT : Yet another thing is that you could, in the first loop, wait for $4015.7 to rise, while having the I flag set. This would prevent the IRQ, and possibly (possybly not ?) increase accuracy of the code and simplify the code inside the IRQ (that would only execute once per frame).
tepples
Posts: 22819
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Bregalad wrote:The problem is that you have to wait a dozen of IRQs for a dozen of scanline, so if this technique were to be used for a longer time between both split, this constant stream of useless IRQs would waste very significant CPU time for nothing
It's significant, but the 37 cycles out of 432 still fall within my 10% of CPU time budget.

Code: Select all

irq_handler:
  bit $4015  ; acknowledge dpcm irq
  inc irqs

  ; schedule another IRQ
  pha
  lda enable_timer
  sta $4015
  pla  
  rti
And it's not entirely useless to know how much time I have left before the IRQ fires.
Would this technique work as well if you play a 1-byte sample, count the # of cycles before the interrupt, then play a 17 or 33 byte sample to have only 2 IRQs in total ?
Playing a 17-byte sample would allow skipping the first IRQs if the splits are far enough apart ((17+1)*8*54 = 7776 CPU cycles or 69 scanlines). But the demo's splits are only about 10 bytes apart, and it would be two more instructions in the IRQ to handle switching to 1-byte samples after the initial 17- or 33-byte sample. I'll consider it in the second.
Altough of course this will waste 100% of CPU time between the 1st and 2nd split instead of between the top and the 1st split, so it all depends of what you'd want to achieve.
What motivated my development of this technique was trying to display a background picture that needs more than 4 KiB of CHR while decompressing a page of text to put into the next picture. Here, sprite 0 would be in a fixed place (doesn't matter where as long as it's present), and the code would wait for the end of vblank by waiting for sprite 0 flag to turn off.
EDIT : Yet another thing is that you could, in the first loop, wait for $4015.7 to rise, while having the I flag set.
The number of cycles in the loop that measures the IRQ jitter has to match the number of cycles in the loop that compensates, and it takes more time to read $4015 than the zero-page variable that the IRQ handler updates.
User avatar
Bregalad
Posts: 8087
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Post by Bregalad »

What motivated my development of this technique was trying to display a background picture that needs more than 4 KiB of CHR while decompressing a page of text to put into the next picture. Here, sprite 0 would be in a fixed place (doesn't matter where as long as it's present), and the code would wait for the end of vblank by waiting for sprite 0 flag to turn off.
That's right, and no offense tepples, but the way you're using IRQ with this "inc variable" method you're polling completley kills the purpose of an IRQ, which is to free the main CPU to do another task.
Doing it is completely dumb - you'd as well have the main code doing a loop timed between the split and the result would be the same.

Yet I understand that this is a proof of concept, but you should have the main code doing something else than polling available in order to have this any useful.
So the IRQ handler will need not only to acknownledge, but also to handle the split and the loop that comensate the jitering. That's why I suggest you wait a fake IRQ just by polling $4015 after the first split (in our case) - this should simplify code and avoid an if/else statement in your IRQ routine.
Useless, lumbering half-wits don't scare us.
tepples
Posts: 22819
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Bregalad wrote:
What motivated my development of this technique was trying to display a background picture that needs more than 4 KiB of CHR while decompressing a page of text to put into the next picture.
That's right, and no offense tepples, but the way you're using IRQ with this "inc variable" method you're polling completley kills the purpose of an IRQ, which is to free the main CPU to do another task.
I understand that, but finalizing the code for this other task may take weeks. I have a day job, my workouts, and sometimes younger cousins who visit over summer break.
So the IRQ handler will need not only to acknownledge, but also to handle the split and the loop that comensate the jitering.
It doesn't wait for IRQ ten times; it waits for the number of elapsed IRQs to reach 10. So even if I do something that takes the time of eight IRQs to complete before I jump into that loop, I can still get into the loop in time. But now I bet tokumaru is itching to remind me that "something" may occasionally exceed this time, causing an occasional glitch. So my ultimate plan is to move more of the scroll split processing into the IRQ handler, which should add two cycles to the "just count" case.
tepples
Posts: 22819
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

As the Tourette's Guy channeling the late Robert Stack would put it, "UPDATE!"

I got the whole effect to run in the NMI and IRQ handlers. This version is a rock-solid letterbox generator for NTSC NES.


EDIT: Attached DPCM Letterbox demo here as a backup
Attachments
dpcmletterbox.zip
(14.36 KiB) Downloaded 618 times
User avatar
tokumaru
Posts: 12493
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Are you saying that with your method it is possible to use DMC IRQ's for effects that were only possible with special mappers? That's awesome!

Is it precise enough to allow you to change the scroll mid-frame without artifacts? If yes, I'd say you just did a great thing for the NESDEV community. I always wanted a method to do raster effects without having to use mappers or 100% of the CPU time.

And if you keep sample playback disabled during VBlank and a while after that I guess you can read the controller then, without having to worry about corruption, right?

EDIT: I imagine it would be hard to make this effect dynamic, right? I mean, if you need the split to move up and down as opposed to keeping it steady.
User avatar
Bregalad
Posts: 8087
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Post by Bregalad »

Congratluation tepples, this is really great.

Are you saying that with your method it is possible to use DMC IRQ's for effects that were only possible with special mappers? That's awesome!
Yeah he's saying this. I think it only works if there is a sprite zero hit first tough. Also I'm not sure if it's possible to get 3 or more splitpoints that way, but anyways 2 is still much better than just 1 without any mapper.
And if you keep sample playback disabled during VBlank and a while after that I guess you can read the controller then, without having to worry about corruption, right?
You can read the controller multiple times anyway so I don't think this is a problem unless you're like REALLY tight on timing.
Useless, lumbering half-wits don't scare us.
User avatar
tokumaru
Posts: 12493
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Bregalad wrote:I think it only works if there is a sprite zero hit first tough.
He said that the purpose of the sprite hit was to time the first IRQ, and that you don't need it if your NMI handler is constant-timed (like mine is).
You can read the controller multiple times anyway
Well, you can, but why would you? I'd rather have my NMI handler read the controllers before doing any DMC IRQ stuff and use that one read for the rest of the frame.
so I don't think this is a problem unless you're like REALLY tight on timing.
I don't like the idea of indefinitely reading the controllers until 2 consecutive read match, so I'd rather just read it when it's safe to do so.
User avatar
Dwedit
Posts: 5066
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Post by Dwedit »

Could the DMC technique be used for screen masking for vertical mirroring?
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Post Reply