Page 1 of 3
IRQ
Posted: Wed May 25, 2022 4:59 am
by svenvandevelde
This is quite an important sequence for the CX16 to handle interrupts ?.
At the start:
asm {
lda $9f21
pha
lda $9f20
pha
lda $9f24
pha
lda $9f23
pha
lda $9f22
pha
lda $9f25
pha
}
At the end:
pla
sta $9f25
pla
sta $9f22
pla
sta $9f23
pla
sta $9f24
pla
sta $9f20
pla
sta $9f21
Learning by doing ...
IRQ
Posted: Wed May 25, 2022 4:49 pm
by SlithyMatt
Um... that doesn't seem like a good idea. You could mess up the display or the expected stride by writing to the data ports. Better idea is to only write to VRAM inside the interrupt.
IRQ
Posted: Wed May 25, 2022 10:59 pm
by ZeroByte
On 5/25/2022 at 11:49 AM, SlithyMatt said:
Um... that doesn't seem like a good idea. You could mess up the display or the expected stride by writing to the data ports. Better idea is to only write to VRAM inside the interrupt.
I think that's why he recommended snapshotting it to the stack first. I made a special wrapped version of the music playback routine in Zsound because it stomps all over the VERA ports too. I recommend against advancing the music during IRQ but if you must, here's a safe function wrapper that takes a few dozen extra cycles to accomplish.... have a nice day.
?
IRQ
Posted: Thu May 26, 2022 6:19 am
by svenvandevelde
Actually it is very tricky ...
So indeed, during interrupt i first push these vera registers to the stack, and later before interrupt exit, i pull the registers from the stack.
However, I made a mistake ... The DATA registers should NOT be pulled from the stack as the ADDRSEL in CTRL configures if ADDR0 or ADDR1 is addressed.
So when you pull DATA0
and DATA1 from the stack, it messes up the data originally in the vera.
So I have just removed the push and pull of DATA0 and DATA1.
At the start of the interrupt routine:
lda $9f20
pha
lda $9f21
pha
lda $9f22
pha
lda $9f25
pha
At the end of the interrupt routine:
pla
sta $9f25
pla
sta $9f22
pla
sta $9f21
pla
sta $9f20
IRQ
Posted: Thu May 26, 2022 4:37 pm
by ZeroByte
One thing to note: The above code backs up the currently-selected data port.
If your code then proceeds to use the one that was not active, you'd be screwed.
If you just want to back up and use a specific data port, then return to current state:
; save the current port selection
lda $9f25
pha
; switch to the desired data port
lda #desired_data_port_number
sta $9f25
; back up the state of that data port
lda $9f20
pha
lda $9f21
pha
lda $9f22
pha
; Your code here
; Use ONLY the desired data port - be sure to leave $9f25 alone during this execution
; restore state of the used data port
pla
sta $9f22
pla
sta $9f21
pla
sta $9f20
; return active port selection to previous state
pla
sta $9f25
If you want to back up and use both ports:
; backup currently-active data port
lda $9f20
pha
lda $9f21
pha
lda $9f22
pha
lda $9f25
pha
; switch to the other port....
eor #1
sta $9f25
; ... and back it up as well
lda $9f20
pha
lda $9f21
pha
lda $9f22
sta
lda $9f25
pha
; your code goes here. Feel free to use both ports as required.
; restore both data port addresses and previous selected-port state
pla
sta $9f25
pla
sta $9f22
pla
sta $9f21
pla
sta $9f20
pla
sta $9f25
pla
sta $9f22
pla
sta $9f21
pla
sta $9f20
Note that the order of the CTRL register in these two examples is different!
The first example pushes CTRL first because this guarantees the final write of the restore will be to select the correct active port.
It then switches to the desired data port (which may be the same one that was already active, but it takes more code to test than to just blindly swap to the same port) and backs up the address of the desired data port.
When the restore begins, you'll still have the port you borrowed as the active one, which will be pointed back to wherever it was by the first 3 pops, and lastly, the selected port state is restored.
The second example puts the CTRL register on the stack after the three address selection registers because that way, when it restores, it will first select the previously-not-selected port, restore it, then swap back to the previously-selected port and restore that, leaving it in place as the active port just as it was when your routine was called.
Obviously, if you intend to use the DCSEL registers, you'd want to tuck those into your backup/restore logic.
EDIT: This is incorrect. If you write to those registers, it's because you WANT those changed, so why would you back them up and restore them?
It's completely fine to manipulate DCSEL during these two examples so long as in example 1, the ADDRSEL bit is correct before doing the restore.
IRQ
Posted: Thu May 26, 2022 5:06 pm
by svenvandevelde
Great
@ZeroByte. Thank you for this. I'll try it. You just solved my issue how to decently stack the vera. Thank you also for the detailed explanation!
IRQ
Posted: Thu May 26, 2022 7:32 pm
by Ed Minchau
I'd avoid doing anything with VERA or the RAM/ROM bank pointers during IRQ. Instead I have IRQ set a flag bit, and have the main program do regular checks of such flag bits. Whenever MAIN detects the flag bit set it resets it and handles whatever I need to do in VERA or the RAM banks. It might take a millisecond longer to get around to doing whatever subroutine, but it makes the custom IRQ much faster and there's no danger of messing up VERA addresses or RAM bank pointers this way.
IRQ
Posted: Thu May 26, 2022 8:46 pm
by svenvandevelde
On 5/26/2022 at 9:32 PM, Ed Minchau said:
Instead I have IRQ set a flag bit, and have the main program do regular checks of such flag bits. Whenever MAIN detects the flag bit set it resets it and handles whatever I need to do in VERA or the RAM banks.
So ... You would put all the video painting in the main loop while in the IRQ routine, you would put the coordinates calculations and game AI logic?
Concerning the banking it really depends what memory layout you have... I mean if you have moved half of your game engine memory into a bank, then during IRQ banking is required in bram.
IRQ
Posted: Thu May 26, 2022 10:07 pm
by Ed Minchau
On 5/26/2022 at 2:46 PM, svenvandevelde said:
So ... You would put all the video painting in the main loop while in the IRQ routine, you would put the coordinates calculations and game AI logic?
Concerning the banking it really depends what memory layout you have... I mean if you have moved half of your game engine memory into a bank, then during IRQ banking is required in bram.
I'd have the IRQ do as little as possible. For Asteroid Commander it just updates a couple of bytes set aside for flag bits. The main program then checks those bits a couple hundred times a second, each time a major subroutine is finished and at least once for every trip through the main loop.
IRQ
Posted: Thu May 26, 2022 10:46 pm
by ZeroByte
On 5/26/2022 at 2:32 PM, Ed Minchau said:
I'd avoid doing anything with VERA or the RAM/ROM bank pointers during IRQ. Instead I have IRQ set a flag bit, and have the main program do regular checks of such flag bits. Whenever MAIN detects the flag bit set it resets it and handles whatever I need to do in VERA or the RAM banks. It might take a millisecond longer to get around to doing whatever subroutine, but it makes the custom IRQ much faster and there's no danger of messing up VERA addresses or RAM bank pointers this way.
I have to disagree.
VBLANK is exactly what this kind of thing is for, and waiting for the Kernal to finish polling the keyboard before you get another crack at doing anything is very risky of bleeding out into the visible area and getting tearing artifacts and such.
The VBLANK IRQ can look for a signal value from the main loop that a frame is done, and only do no-op if that flag is not set. If the flag is set, then you know the program is sitting in an idle loop and you won't break anything by manipulating VERA.
Main loop code should always assume dirty state for the data ports anyway, (i.e. always start by configuring the port address) and since you know IRQ won't manipulate the port unless you signaled "frame done", it's safe to have a long loop for loading into VRAM, etc. because you haven't written the "update frame" flag.
I've seen the VBLANK IRQ take up to several characters' (not pixels, but characters) worth of visible raster time to complete and return to the main loop, depending on what the keyboard is doing.
Yes, it takes precious cycles away to do all this pushing VERA onto the stack, but that's why you try to avoid things like using both ports, etc. Flappy Bird is written in C, and it doesn't even push VERA onto the stack during IRQ, and it writes the sprite shadows into VERA during VBLANK and it never crashes.
I am not a super-coder or anything, and don't claim to know it all, and I'm sure there's all kinds of ways to get things done, but my understanding has been what I've just stated here.