Keyboard

Get technical support from the community & developers with specific X16 programs if you can't find the solution elsewhere
(for general non-support related chat about programs please comment directly under the program in the software library)
ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Keyboard

Post by ZeroByte »


Yeah, it should do something more like this:

 

__interrupt void myInterrupt(void)
{
 
if (*VERA_ISR & VERA_LINE) {
    vbcounter
++;
   
*VERA_IRQLINE_L = 100;

   
*VERA_ISR = VERA_LINE;
 
}

 
asm {
    jmp
(default_irq_vector)
 
}
}


(I can't figure out how to get my own "code" block on this thing, so just imagine these changes:

After *VERA_ISR = VERA_LINE;

asm {

  ply

  plx

  pla

  rti

}

and put the asm{jmp(default_irq_vector)} into an else{} clause

p.s. it kind of sucks that they didn't design the 6502 with a dedicated vector for BRK in addition to reset, IRQ, and NMI.

 

Greg King
Posts: 162
Joined: Wed Jul 08, 2020 1:14 pm

Keyboard

Post by Greg King »



18 hours ago, Elektron72 said:




It doesn't make sense to jump through the original vector when a line interrupt is handled, as that would cause the kernal interrupt handler to run multiple times in one frame. I may be wrong, but it looks like the program above jumps to the kernal IRQ handler every time an interrupt occurs.



He's right.  I had assumed that Kernal checks for the vertical sync interrupt -- but, it doesn't!!  Therefore, our programs are forced to do it.  Your handler must have some additional code.

__interrupt void myInterrupt(void)
{
 
if (*VERA_ISR & VERA_LINE) {
    vbcounter
++;
   
*VERA_IRQLINE_L = 100;

   
*VERA_ISR = VERA_LINE;
 
} else if (*VERA_ISR & VERA_VSYNC) {
   
asm {
      jmp
(default_irq_vector)
   
}
 
}
 
// Other interrupts are ignored.
}

Note that the __interrupt qualifier makes this function return by jumping to R38 Kernal's $E049, which pulls the registers and does RTI.

User avatar
StephenHorn
Posts: 565
Joined: Tue Apr 28, 2020 12:00 am
Contact:

Keyboard

Post by StephenHorn »



6 hours ago, Greg King said:




Note that the __interrupt qualifier makes this function return by jumping to R38 Kernal's $E049, which pulls the registers and does RTI.



Ugh... if that's what cc65's interrupt handler does, then in my opinion it ought to be changed. First, there's no guarantee that the kernal's interrupt exit routine will always exist at that address, until the kernal is finished and the final X16 is released. Even then, creating that dependency makes it possible to break programs later on if the kernal needs to be patched. Also, if there's logic that was interrupted and depends on a particular ROM bank being loaded, that bank gets clobbered if you let the kernal trampoline into ROM bank 0 and then fail to let it trampoline back to the appropriate ROM bank that had been loaded before the IRQ fired.

The kernal loads a default interrupt handler address at $0314, and a default brk handler address at $0316. It's fine if someone wants to clobber those to run their own handlers, but to return control to the kernal they should be jumping to whatever the kernal had originally loaded at those addresses, not to the location of ROM that happens to perform the PLY, PLX, PLA, and RTI. If you really intend to immediately terminate the interrupt, expert-style, you should just go ahead and do it yourself. (Unless or until the kernal gets an official API function to do it on your behalf, saving your program the... 1 byte difference between a jsr and "PLY, PLX, PLA, RTI".)

Developer for Box16, the other X16 emulator. (Box16 on GitHub)
I also accept pull requests for x16emu, the official X16 emulator. (x16-emulator on GitHub)
CursorKeys
Posts: 82
Joined: Tue Jan 12, 2021 9:52 pm

Keyboard

Post by CursorKeys »


Thanks for all the feedback,



So today I cam back to this, since I forgot about it for a while, as everything that made sense just ended up not working.  And I needed to get some other things done.



Now I noticed that when I disabled the interrupt it was not working either ?, I noticed the combination of kbhit and vpoke, as defined in KickC, at least the version I have, crashed.



So when I found this out, then I replaced the vpoke by my own implementation, and suddenly the version without interupt was all ok, now one more try WITH interupt.



What I did get working on the end, is more or less described by the following minimal code snippet.

 


Quote




..



volatile char vbcounter=0;

volatile char linecounter=0;





__interrupt( rom_sys_cx16 ) void myInterupt() {



  if( *VERA_ISR & VERA_LINE ) {



    //DO Line interupt stuff



    linecounter++;



    //end interupt

    *VERA_ISR &= VERA_LINE;

    asm {

      ply

      plx

      pla

      rti

    }



  }

  else if( VERA_ISR & VERA_VSYNC ) {

    //Do sync interupt stuff



    vbcounter++;

  }

}



..



RTFM helped me a bit as well, seeing that KickC has some support build in for X16 interrupts, with or without keyboard (system or minimal). 

All worked fine for none line interupts, but for line interrupts, I still needed a bit of assembly, as you see above, to exit correctly. (ply, plx, pla, rti )



This is probably the best version I have right now, with the least amount of assembly code in it, and it works nicely together with keyboard.



 

Greg King
Posts: 162
Joined: Wed Jul 08, 2020 1:14 pm

Keyboard

Post by Greg King »



52 minutes ago, CursorKeys said:




    //end interupt

    *VERA_ISR &= VERA_LINE;



You cancel a VERA interrupt by writing the appropriate one-bit to the register:

// end interrupt

*VERA_ISR = VERA_LINE;

CursorKeys
Posts: 82
Joined: Tue Jan 12, 2021 9:52 pm

Keyboard

Post by CursorKeys »


Thanks Greg, that is correct.  So then the final code snippet for KickC compiler, vsync AND line interupts AND keyboard handling:



 


Quote





..



volatile char vbcounter=0;

volatile char linecounter=0;





__interrupt( rom_sys_cx16 ) void myInterupt() {



  if( *VERA_ISR & VERA_LINE ) {



    //DO Line interupt stuff



    linecounter++;



    //end interupt

    *VERA_ISR = VERA_LINE;

    asm {

      ply

      plx

      pla

      rti

    }



  }

  else if( VERA_ISR & VERA_VSYNC ) {

    //Do sync interupt stuff



    vbcounter++;

  }

}



..








 

CursorKeys
Posts: 82
Joined: Tue Jan 12, 2021 9:52 pm

Keyboard

Post by CursorKeys »



On 5/27/2021 at 11:07 AM, Greg King said:




....



Note that the __interrupt qualifier makes this function return by jumping to R38 Kernal's $E049, which pulls the registers and does RTI.



I am using KickC, not cc65  is it the same there?

Greg King
Posts: 162
Joined: Wed Jul 08, 2020 1:14 pm

Keyboard

Post by Greg King »



10 hours ago, CursorKeys said:




I am using KickC, not cc65.  Is it the same there?



No.  cc65's interrupt handling is much less efficient.  It goes solely through the interrupt vector "daisy chain".  Several independent handlers can be connected at the same time (if a handler is linked into a program, then it's connected automatically).  Therefore, there's an extra layer that runs each handler, in turn.

If you want to run C code in a handler, then another layer must be used to save and restore all of the housekeeping data that the compiler's runtime needs.

Normal interrupt handling is slow -- not suitable for some video effects!  If you want to stuff a lot of data into video RAM during the vertical blanking period, then your code must start as soon as possible, and run as fast as possible.  Your program would need to intercept the interrupts manually.  The handler should be written in Assembly code.

Greg King
Posts: 162
Joined: Wed Jul 08, 2020 1:14 pm

Keyboard

Post by Greg King »



On 5/27/2021 at 5:07 AM, Greg King said:




He's right.  I had assumed that Kernal checks for the vertical sync interrupt -- but, it doesn't!!  Therefore, our programs are forced to do it.  Your handler must have some additional code.




__interrupt void myInterrupt(void)
{
 
if (*VERA_ISR & VERA_LINE) {
    vbcounter
++;
   
*VERA_IRQLINE_L = 100;

   
*VERA_ISR = VERA_LINE;
 
} else if (*VERA_ISR & VERA_VSYNC) {
   
asm {
      jmp
(default_irq_vector)
   
}
 
}
 
// Other interrupts are ignored.
}



Note that the __interrupt qualifier makes this function return by jumping to R38 Kernal's $E049, which pulls the registers and does RTI.



That comment is about KickC.  As I wrote earlier, cc65 works very differently.

ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Keyboard

Post by ZeroByte »


Here's how I did IRQ handling in Flappy Bird:

(written in cc65)

The "magic" item here is that I just use the fact that C-space identifiers are available in "ASM-space" as _identifiers.

The void irq(void) function is the game's IRQ handler. The game first copies the default vector from 0x314 into SystemIRQ, then writes the address of irq() into 0x314.

irq() ends by using asm() to jmp into the kernal's IRQ routines after the game's functions are done. note that the command is jmp (_SystemIRQ) - the parens are an indirect jump, which wasn't available on MOS 6502 but works on 65c02, so it's just a one-liner.


Quote




 



#define IRQVECTOR    0x0314

#define IRQvector    (*(uint16_t*)IRQVECTOR)



// global variable:

static uint16_t SystemIRQ;



void irq(void)

{

    //

    // do stuff here

    //

    

    // call system IRQ handler when done with your stuff

    SETROMBANK = 0;

    asm("jmp (_SystemIRQ)");

}



// somewhere in your program's initialization:



// install the IRQ handler

SystemIRQ = IRQvector;

asm("sei");

IRQvector = (uint16_t)&irq;

asm("cli");



 



P.s. : sorry, I am not sure how to get the syntax highlighting to work on the forum....

Post Reply