Jump to content
  • 0

How do I handle interrupts?


lamb-duh
 Share

Question

I'd like to run a subroutine when vsync starts, how can I do this? The list of vera registers suggests that this exists, but I can't find any documentation on how to configure interrupt handlers. The 6502 interrupt vector is mapped into ROM, so presumably it can't be completely hijacked from the kernel.

Link to comment
Share on other sites

24 answers to this question

Recommended Posts

  • 0
5 hours ago, StephenHorn said:

Glad to hear! Just a note, though, r37 broke line IRQ handling for the VERA, so that technique will be impossible to test unless you grab and compile for yourself the corrected code currently in Github.

This is good to know - I was about to start moving my 6502 code (which uses line interrupts heavily) from R36 to R37.   Any chance you can share an updated and recompiled R37 binary for use on Windows or a link to some notes on how to compile on a windows host? Thanks.

Link to comment
Share on other sites

  • 0
On 7/13/2020 at 6:02 AM, Geehaf said:

Any chance you can share an updated and recompiled R37 binary for use on Windows or a link to some notes on how to compile on a windows host? Thanks.

Sure. I've gone ahead and updated my builds at https://github.com/indigodarkwolf/x16-bin/. x16emu_Release.exe was built with compiler and linker optimizations enabled, x16emu_Debug.exe was not. The original r37 exe is included as x16emu.exe.

Keep in mind that this is extremely unofficial, not all of the fixes/changes in my branch are necessarily going to make it into r38. And also, it should go without saying that I'm not the author of all the changes, this build rolls up everything that's been accepted into the main branch of the emulator since r37, plus a few extras I've submitted. That all said, here's a rundown of the differences from r37:

  • Fixed VERA line IRQ configuration and triggering.
  • Added VERA sprite collision IRQ.
  • VERA emulation optimizations
  • Built-in emulator debugger changes (`-debug` command-line option, then toggled with F12)
    • Debugger now displays the 16-bit zeropage registers r0-r15 when showing system RAM (Corresponding to $0002-$0021). See also: Commander X16 Programmer's Reference Guide. This also displays an additional 4 registers past the X16 API's registers ($0022-$0029), labeled x16-x19.
    • Debugger can now show a dump of VRAM in place of the system memory dump. `v <addr>` where <addr> is the start address of VRAM you want to inspect. (Accepted, but a merge error introduced a bug and the fix isn't accepted yet)
    • Debugger can now fill memory, either system RAM or VRAM depending on what's presently being dumped. `f <addr> <value> [count] [addr_incr]`, where <addr> is the address to modify, <value> is hexadecimal value to fill at that address, [count] is an optional number of times to write the value, and [addr_incr] is an optional number to increment the target address by between writes ('1' to fill a contiguous region, '2' to fill every other byte, '3' for every third byte, etc).
    • Debugger VRAM dump is colorized to indicate whether the portion of memory has special meaning to the VERA: (Not accepted yet)
      • Cyan is tilemap/textmap data
      • Green is potential tile/glyph data (determined by the start of the layer's tile data, ending after 256 or 1024 tiles' worth of bytes, depending on tilemap settings)
      • Red is the portion of VRAM mapped to internal features (palette, sprite data, PSG).
      • Grey is none of the above/potentially unused
    • (Edit) Debugger can now take a "snapshot" of system RAM, with `snap`. It can then perform a diff on that snapshot, with `diff`. (Not accepted, not planning to submit for r38 at this time.)
  • Added a "warp" mode.
    • Launch the emulator with `-warp` command line option to reduce VERA framerate (sprites will still be considered, but only 1/64 frames will actually be drawn to the screen) and uncap the emulator's speed to 64X.
    • Warp factor can be scaled by powers of 2, using `Ctrl -` and `Ctrl +` key combinations. (Not accepted yet)
  • I/O registers $9FB8-$9FBB expose a 32-bit clock tick count for the CPU. It appears this is emulator-specific behavior, so do not depend on this for anything running on final hardware. The emulator environment can be detected by testing $9FBE for the value $31 and $9FBF for the value $36 (these are the ASCII values for '1' and '6', respectively).
  • Added a variety of instructions from the 65C02 that were missing: (Not accepted yet)
    • BBR0-BBR7 (branch if bit reset)
    • BBS0-BBS7 (branch if bit set)
    • RMB0-RMB7 (reset memory bit)
    • SMB0-SMB7 (set memory bit)
    • WAI (wait for interrupt)
    • Moved the "debug" opcode from $FF to $DB. The emulator doesn't implement STP (stop), which would be at $DB, and the debug opcode's previous location conflicts with BBS7.
Edited by StephenHorn
  • Like 2
Link to comment
Share on other sites

  • 0

What's the best way to handle multiple interrupts coming in at the same time?

Another guide to 6502 interrupt handling (http://6502.org/tutorials/interrupts.html ) says that the interrupt handler can be written to only deal with one interrupt at the time, and the processor architecture makes sure nothing gets lost. However, if I continue to use the kernel interrupt handler, I'm relying on the kernel to clear the interrupt flags, does the kernel handle all interrupts at the same time? if it doesn't, then I need my interrupt handler to coordinate with the kernel handler so that we both deal with the *same* interrupt. What if a new interrupt comes in at just the right time that my code misses it but the kernel processes it?

If I just clobber the kernel interrupt handler, then I have to do all the hardware processing myself. I'm thinking that my vsync handler should just write something to memory that would trigger something outside of the interrupt handler to process the frame, rather than writing a huge interrupt handler that has most of the program logic. If I write short interrupt handlers, these problems probably become unlikely enough that I can wait until I have a better handle on 6502 programming to actually figure them out.

Link to comment
Share on other sites

  • 0
16 minutes ago, lamb-duh said:

What's the best way to handle multiple interrupts coming in at the same time?

Another guide to 6502 interrupt handling (http://6502.org/tutorials/interrupts.html ) says that the interrupt handler can be written to only deal with one interrupt at the time, and the processor architecture makes sure nothing gets lost. However, if I continue to use the kernel interrupt handler, I'm relying on the kernel to clear the interrupt flags, does the kernel handle all interrupts at the same time? if it doesn't, then I need my interrupt handler to coordinate with the kernel handler so that we both deal with the *same* interrupt. What if a new interrupt comes in at just the right time that my code misses it but the kernel processes it?

If I just clobber the kernel interrupt handler, then I have to do all the hardware processing myself. I'm thinking that my vsync handler should just write something to memory that would trigger something outside of the interrupt handler to process the frame, rather than writing a huge interrupt handler that has most of the program logic. If I write short interrupt handlers, these problems probably become unlikely enough that I can wait until I have a better handle on 6502 programming to actually figure them out.

What I think that guide means is that the processor can only activate one interrupt handler at a time. You can deal with as many interrupt signals within that handler as you like. There's nothing at all stopping you from doing something like this:

Quote

    lda $9F27 ; Let's check the VERA's interrupts.
    and #$02 ; Did the line interrupt fire?
    beq no_line_interrupt
    ;
    ; Handle the line interrupt here!
    ;
    lda #02
    sta $9F27 ; Clear the line interrupt
no_line_interrupt:
    lda $9F27 ; Let's check the VERA's interrupts... again.
    and #$01 ; Did the vsync interrupt fire?
    beq no_vsync_interrupt
    ;
    ; Handle the vsync interrupt here!
    ;
    lda #01
    sta $9F27 ; Clear the vsync interrupt
no_vsync_interrupt:
    lda $9F27 ; Let's che-- okay, you get it.
    and #$04 ; Sprite collision!
    beq no_sprite_collision
    ;
    ; Handle the sprite collision here!
    ;
    lda #04
    sta $9F27 ; Clear the sprite collision interrupt
no_sprite_collision:
    lda $9F27 ; Criminey, how many interrupts does VERA have? (A lot, in turns out.)
   and #$08 ; Low audio buffer interrupt
    beq enough_audio
    ;
    ; Handle the low audio buffer interrupt!
    ;
    lda #08
    sta $9F27 ; Clear the low audio buffer interrupt
enough_audio:
    ply
    plx
    pla
    rti ; That's right, I'm a bad boy and I don't play by the kernal's rules, or return control to it. I make games, need the cycles, and the kernal is overrated, anyhow. 😉

Now, whether or not it's a good idea to implement all of your interrupt handling into a single routine is another discussion altogether, but is less "is this technically possible?" and more "is it a well-suited approach for my purposes?".

And that example focuses on the VERA as a source of interrupts, but there are other potential sources of interrupts as well. I just happen to not know of any off the top of my head, because it hasn't been relevant to my stuff.

Edited by StephenHorn
Link to comment
Share on other sites

  • 0
25 minutes ago, lamb-duh said:

However, if I continue to use the kernel interrupt handler, I'm relying on the kernel to clear the interrupt flags, does the kernel handle all interrupts at the same time? if it doesn't, then I need my interrupt handler to coordinate with the kernel handler so that we both deal with the *same* interrupt. What if a new interrupt comes in at just the right time that my code misses it but the kernel processes it?

If you continue to use the kernal interrupt handler, it will attempt to handle any interrupts it would normally handle, in the way it normally handles them, as long as the appropriate IRQ flags are still set.

This means that yes, if you miss an IRQ that the kernal would have handled, then it's possible the kernal will handle it when you return control to it. That said, depending on what you're handling, it may not be likely. VSYNC, for instance, only comes around every 133333-ish cycles, that's room for a lot of work. Obviously, don't try to rotate a 4x32-bit float vector with a 4x3x32-bit transform matrix, you simply don't have the horses to do that within a frame, but as long as you don't go nuts in interrupt handler, you should be fine.

The only reason I ever returned control to the kernal, personally, rather than simply clobbering its IRQ handler, was because there was a time, once upon a time, when the kernal needed to regain control in order to poll the controllers. That's no longer necessary, there is a separate function to call to poll the controllers, separate from the function to retrieve the most recently polled state data from the controllers. Edit: Of course, as the system evolves, maybe there'll be something else that'll appear, some I/O or something.

Edited by StephenHorn
Link to comment
Share on other sites

  • 0

If I clobber the kernel interrupt handler, then my interrupt handler has to handle every interrupt, right? Like if an interrupt comes in and my routine doesn't handle it, as soon as it `rti`s, the interrupt process is going to start all over again and get stuck in an infinite loop, right? Are most interrupt sources like vera, where you have to explicitly turn them on? is there a list of what interrupts my code has to respond to if it doesn't pass off control to the kernel?

Link to comment
Share on other sites

  • 0
35 minutes ago, lamb-duh said:

If I clobber the kernel interrupt handler, then my interrupt handler has to handle every interrupt, right? Like if an interrupt comes in and my routine doesn't handle it, as soon as it `rti`s, the interrupt process is going to start all over again and get stuck in an infinite loop, right? Are most interrupt sources like vera, where you have to explicitly turn them on? is there a list of what interrupts my code has to respond to if it doesn't pass off control to the kernel?

I don't know if it's documented anywhere, but as far as I know the only interrupt that's presently enabled by default is the VERA's vsync. Again, that might change in the future, at which point I'll be updating some demos...

Link to comment
Share on other sites

  • 0

How about the keyboard? GETIN seems to break when I clobber the kernal irq handler. Does the keyboard generate interrupts, or do you have to poll it?

edit: I found this discussion https://www.lemon64.com/forum/viewtopic.php?t=32619 about commodore 64 keyboard handling. It says that I have to read the keyboard matrix and debounce the keys myself. does the x16 have an api call like joystick_scan, but for the keyboard that would manually trigger the keyboard processor so that I can still use GETIN?

edit: turns out SCNKEY from the commodore 64 api does what I want, except that it also writes data into (what seem to be) two random memory locations, but I guess I can just save those on the stack and then I don't have to worry about whether I was using them for something else.

Edited by lamb-duh
  • Like 1
Link to comment
Share on other sites

  • 0
On 7/13/2020 at 5:42 AM, StephenHorn said:

There is no example in this article how to modify the $0314-$3015, so I tried i myself. As i write to the $0314 and $0315 memory locations, my program returns to the basic immediatelly. In case the modification part is commented out, it runs further.
And obviously the $0314 and $0315 are unchanged either way.
 

Quote

sei

lda $0314

sta Default_irq_handler

lda $0315

sta Default_irq_handler+1

lda custom_irq_handler

sta $0314

lda custom_irq_handler+1

sta $0315

cli

rts

 

custom_irq_handler:

; Whatever code your program

; wanted to execute...

I am using the emulator with Turbo Rascal, and the generated assembly code looks good.
Is there any special tricks to modify these?

Harmony and happyness!

Link to comment
Share on other sites

  • 0
51 minutes ago, Miklós Pathy said:

Is there any special tricks to modify these?

Ok, my mistake, but i write it down here for others, who also don't know.
If you want to tell the asm compiler to take a label's low and high byte, and compile it into your code you have to do this:
 

Quote

lda #<custom_irq_handler

sta $0314

lda #>custom_irq_handler

sta $0315

cli

rts

 

custom_irq_handler:

 

  • Like 1
Link to comment
Share on other sites

  • 0
1 hour ago, Miklós Pathy said:

Ok, my mistake, but i write it down here for others, who also don't know.
If you want to tell the asm compiler to take a label's low and high byte, and compile it into your code you have to do this:

Yes, that's correct.

2 hours ago, Miklós Pathy said:

There is no example in this article how to modify the $0314-$3015, so I tried i myself.

Whoops! My bad, it seems that I completely forgot about that.

Edit: I've gone ahead and fixed the wiki entry.

Edited by StephenHorn
  • Like 1
Link to comment
Share on other sites

  • 0
On 7/15/2020 at 7:49 AM, lamb-duh said:

How about the keyboard? GETIN seems to break when I clobber the kernal irq handler. Does the keyboard generate interrupts, or do you have to poll it?

edit: I found this discussion https://www.lemon64.com/forum/viewtopic.php?t=32619 about commodore 64 keyboard handling. It says that I have to read the keyboard matrix and debounce the keys myself. does the x16 have an api call like joystick_scan, but for the keyboard that would manually trigger the keyboard processor so that I can still use GETIN?

edit: turns out SCNKEY from the commodore 64 api does what I want, except that it also writes data into (what seem to be) two random memory locations, but I guess I can just save those on the stack and then I don't have to worry about whether I was using them for something else.

This problem came out for me too. Even if I jump back to the kernal interrupt handler. It would be nice if you could share your SCNKEY solution.
BTW there I started a discussion about the keyboard handling in the facebook group. The CX16 has a PS2 keyboard, so maybe later the PS2 keyboard transactions will be implemented for acceptable keyboard handling. And as I remember, PS2  is not implemented in the emulator, so it is still a problem. Maybe i should start the thread here too.

Edited by Miklós Pathy
Link to comment
Share on other sites

  • 0
1 hour ago, Miklós Pathy said:

This problem came out for me too. Even if I jump back to the kernal interrupt handler. It would be nice if you could share your SCNKEY solution.
BTW there I started a discussion about the keyboard handling in the facebook group. The CX16 has a PS2 keyboard, so maybe later the PS2 keyboard transactions will be implemented for acceptable keyboard handling. And as I remember, PS2  is not implemented in the emulator, so it is still a problem. Maybe i should start the thread here too.

Sorry, I didn't actually get that far, after I found out about SCNKEY I got distracted by something else and never got a chance to test it out. One thing that comes to mind though-- when you pass off control to the kernal, you must *not* clear the interrupt lines yourself. That was a bit confusing for me at first, but if you clear the interrupt line before going into the kernal, the kernal will miss the signal.

As for SCNKEY, this is the commodore reference I've been using: http://sta.c64.org/cbm64krnfunc.html

As far as I can tell, all you have to do is `jsr SCNKEY` ($ff9f) every once in a while to poll the keyboard, and then GETIN will work again.

 

edit: As far as I can tell, the computer has PS/2 port on it for keyboard input, but it's connected through a controller that makes it (presumably) more like a commodore keyboard. I'm pretty sure PS/2 generates an interrupt when keys are pressed, but that interrupt does not go into the 6502. (I could be completely off base here though)

Edited by lamb-duh
Link to comment
Share on other sites

  • 0
22 hours ago, lamb-duh said:

when you pass off control to the kernal, you must *not* clear the interrupt lines yourself. That was a bit confusing for me at first, but if you clear the interrupt line before going into the kernal, the kernal will miss the signal.

I do not understand. What it means "clear the interrupt lines"?

 

Quote

As far as I can tell, all you have to do is `jsr SCNKEY` ($ff9f) every once in a while to poll the keyboard, and then GETIN will work again.

Nope. It is worst. As i call SCNKEY, my program exits immediatelly. If i call GETIN, it exits only if i press a key.

 

Quote

edit: As far as I can tell, the computer has PS/2 port on it for keyboard input, but it's connected through a controller that makes it (presumably) more like a commodore keyboard. I'm pretty sure PS/2 generates an interrupt when keys are pressed, but that interrupt does not go into the 6502. (I could be completely off base here though)

I suppose i will start a keyboard handling thread here in the forum.

Link to comment
Share on other sites

  • 0
16 minutes ago, Miklós Pathy said:

I do not understand. What it means "clear the interrupt lines"?
 

The kernal checks what components may have caused the interrupt to fire, for instance it checks $9F27 for the bit $01 so that it can know whether VSYNC has occurred. If your custom IRQ handler clears that bit, then the kernal will check for it, find 0, and not do its normal VSYNC routines. This could be a problem if you're expecting the kernal to perform its normal VSYNC processing after returning control to it.

Link to comment
Share on other sites

  • 0
30 minutes ago, Miklós Pathy said:

I do not understand. What it means "clear the interrupt lines"?

I'm probably not using the right word. When an interrupt comes in, the program has to write to a certain memory location (depending on what caused the interrupt) to say that the interrupt has been handled, otherwise you'll get stuck in a loop where the interrupt handler is triggered again as soon as it exits. (for vsync you would poke 1 into $9f27) this is done by the kernal interrupt routine, so if your interrupt handler is calling back into it, you don't need to do anything.

 

If SCNKEY is just causing a crash though, that's not what the problem is. I don't think I can be of any more help, sorry

 

Edited by lamb-duh
Link to comment
Share on other sites

  • 0
2 minutes ago, StephenHorn said:

The kernal checks what components may have caused the interrupt to fire, for instance it checks $9F27 for the bit $01 so that it can know whether VSYNC has occurred. If your custom IRQ handler clears that bit, then the kernal will check for it, find 0, and not do its normal VSYNC routines. This could be a problem if you're expecting the kernal to perform its normal VSYNC processing after returning control to it.

Ah, that. By my experience, it has no effect, i can do anything i want, it qiuts.

Link to comment
Share on other sites

  • 0
On 7/12/2020 at 11:42 PM, StephenHorn said:

Hey this is great, thanks. I've been playing with some raster style IRQ's on the X16 emulator, got some neat stretching going... I was gonna make a post and ask if there is a way to read the current scanline a la $D012 on the C64?

 

However, I do see that there is both a VSYNC IRQ as well as a LINE IRQ so this might just solve my problem.... do you or anyone know if there is a way to read the current scanline for comparing? I'll have a look at the LINE irq though

Link to comment
Share on other sites

  • 0
1 hour ago, Sidchip said:

Hey this is great, thanks. I've been playing with some raster style IRQ's on the X16 emulator, got some neat stretching going... I was gonna make a post and ask if there is a way to read the current scanline a la $D012 on the C64?

 

However, I do see that there is both a VSYNC IRQ as well as a LINE IRQ so this might just solve my problem.... do you or anyone know if there is a way to read the current scanline for comparing? I'll have a look at the LINE irq though

You cannot query the current scanline anywhere. The best you can do is use the line IRQ settings from the VERA and, if you want to execute something every line, make absolutely damn sure you keep your execution down to less than the full scanline.

It will help to make sure your program sets the ROM bank to 0, so that BASIC is not catching interrupts before the kernal can get to it, otherwise BASIC will spend a surprisingly large quantity of time in what effectively amounts to trampoline code.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

Please review our Terms of Use