Page 1 of 1

Setting cc65 IRQ/NMI for homebrew board?

Posted: Sun Jul 05, 2020 9:05 pm
by arrow201
Hi,

Back in 1988/89 I made my own 65C02 board for my own learning/experiments. Fast forward 30 years, i just recently came across cc65. i dusted off my micro board and low and behold, it still works :) It has 32K of ram, so it's fine for writing C code. I first made a C# PC app to talk to the board. For cc65 to work, i modded a cfg file to match my board, and changed crt0.s to jump to the monitor after exiting main(). It's working fine so far ...cool.

For this board, on boot, the monitor(eprom) sets location $FFFA(NMI) to $00FA, and for $FFFE(IRQ) to $00FD. During boot, the monitor inits these locations($00FA and $00FD) with $40(RTI). Sooooo, back in the day when i'd write my asm apps (i used TASM back then), for IRQs, i'd put a $4C (abs JMP) at $00FD followed by the address of my IRQ routine at $00FE/$00FF. My question after all this(whew!), is how do i tell cc65 where to put the IRQ address of my C method? My guess is right now if i write:

#define STACK_SIZE 256
unsigned char TempStack[STACK_SIZE];

int main(void)
{
SEI(); // disable IRQ
set_irq(&IRQ_Routine, TempStack, STACK_SIZE);
CLI(); // enable IRQ

...
return EXIT_SUCCESS;
}

// IRQ handler
unsigned char IRQ_Routine(void)
{
...
return (IRQ_NOT_HANDLED);
}

...then cc65 is going to try to put the address of "IRQ_Routine" at $FFFE? ...which i don't want. I've Googled and come up with bits and pieces, but still unsure what to do.

For starter is guess i have to add the following to my cfg?
CONDES: type = interruptor,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__,
segment = RODATA,
import = __CALLIRQ__;

..then add some kind of stub code in crt0? I'd like this to work for IRQ routines written in C and asm.

Any help, with examples, appreciated. Thanks!

Re: Setting cc65 IRQ/NMI for homebrew board?

Posted: Mon Jul 13, 2020 9:44 pm
by arrow201
Ok, so looking through the cc65 folders and Googling, I have it(mostly) figured out to write an IRQ routine for a home brewed board in 'C'. I don't know if i have it 100% correct, but i have it working for my board. As I've seen mentioned here, and elsewhere, it's always best to write your IRQ routine in assembler as "set_irq()" has quite a bit of overhead (ie. saves zero page), but if you want to use 'C' then...

Using my board's locations as an example, on boot, the bios $FFFE(IRQ) redirects the IRQ to $00FD and inits $00FD with an RTI.

1) In your .cfg, "FEATURES" section, make sure you have:

Code: Select all

CONDES:  segment = STARTUP,
                type    = interruptor,
                label   = __INTERRUPTOR_TABLE__,
                count   = __INTERRUPTOR_COUNT__,
                import  = __CALLIRQ__;
Placing it to the desired(code type) segment. I just pointed it to my STARTUP segment

2) The "set_irq()" method requires defining "initirq", "doneirq" methods. So i made an IRQinit.s:

Code: Select all

; IRQinit.s
;
; IRQ init
        .export         initirq, doneirq	; needed for set_irq call
        .import         callirq
		
; IRQ location
IRQVec	:= $00FD

; ------------------------------------------------------------------------
.segment        "ONCE"	; ("ONCE" is after my "STARTUP" segment and before my "CODE" segment)

initirq:
       sei				; disable IRQ (take care of SEI/CLI in the 'C' code, but keep this here to be safe)
       lda     #$4C		; JMP
       sta     IRQVec
       lda     #<IRQStub	; get the stub addr
       ldx     #>IRQStub
       sta     IRQVec+1	; save the stub addr in the redirected IRQ adr
       stx     IRQVec+2
;        cli				; enable IRQ
       rts
; ------------------------------------------------------------------------
.segment        "CODE"

doneirq:
        sei					; disable IRQ
        lda		#$40		; RTI
        sta		IRQVec		
;        cli					; enable IRQ	<-- may need to do this if the original IRQ is to get triggered
        rts
; ------------------------------------------------------------------------

IRQStub:
        cld				; Just to be sure
        jsr     callirq		; Call the functions
        rts
IRQStub will be called by "set_irq()", so in "initirq", I set a JMP at $00FD, then the adr to jump to(IRQStub) on an IRQ. Make sure you end IRQStub with RTS, the caller of IRQStub will end it with RTI. My board doesn't use any other IRQs, so on "doneirq", I just put an RTI back at $00FD. Depending on the complexity of your board, you may need to save/restore the adr at your redirected IRQ location, ie. looking at the C64 irq.s(cc65\libsrc\c64), it saves it's current IRQ adr to $0000, on "doneirq", it will restore it.

3) Now the 'C' code:

Code: Select all

unsigned char IRQ(void);

#define STACK_SIZE 64	// vary this depending how busy your IRQ method is
unsigned char TempStack[STACK_SIZE];

int main ()
{
	SEI();	// disable interrupts
	set_irq(&IRQ, TempStack, STACK_SIZE);	// note: needs "initirq", "doneirq"
...other inits
	CLI();	// enable interrupts
...
}

unsigned char IRQ()
{
...your IRQ code
   return IRQ_NOT_HANDLED;
}
I tested it enabling the counter interrupt on a 6522, a scope on the IRQ line confirmed it was working. :)

Re: Setting cc65 IRQ/NMI for homebrew board?

Posted: Thu Jul 30, 2020 3:30 pm
by BigEd
(Oh, must have missed this... can't help, but welcome anyway!)