Interrupt Handler

Programming the 6502 microprocessor and its relatives in assembly and other languages.
Post Reply
DonnaD
Posts: 33
Joined: 05 Jul 2007

Interrupt Handler

Post by DonnaD »

Hi,

I have my interrupt handler working and enter it using a BRK instruction, but when I exit the handler, it just returns right back to the handler again, altough I have made certain that the interrupt flag is being cleared with CLI. I clear it at the beggining of my handler.

Any suggestions?
User avatar
dclxvi
Posts: 362
Joined: 11 Mar 2004

Post by dclxvi »

It would be helpful if you'd post (or provide a link to) the code containing the BRK and the BRK/IRQ handler you're using. Even if we can't see the issue just by looking at it, we could at least try the code out (assuming it's relatively short).
blargg
Posts: 42
Joined: 30 Dec 2003
Contact:

Post by blargg »

You are aware that BRK skips the byte after the BRK opcode, right? So this prints 1, not 2 as you might expect:

LDX #0
BRK
INX ; This is skipped
INX
JSR PRINT_X ; Prints 1
...

IRQ:
RTI
DonnaD
Posts: 33
Joined: 05 Jul 2007

Post by DonnaD »

Quote:
You are aware that BRK skips the byte after the BRK opcode, right? So this prints 1, not 2 as you might expect:
No, I wasn't aware of that. That is most likely my problem then. Would is the standard practive... to put a NOP there then?

Nope... that wasn't it....
DonnaD
Posts: 33
Joined: 05 Jul 2007

Post by DonnaD »

Here is the section that initiates the BRK

Code: Select all

monitor	BRK          ;Enter ML Monitor	
	       JSR cls		;clears screen
	       JSR menu     ;displays menu
	       JMP mainlp	;jumps to main program loop
JSR cls
JSR menu
and JMP mainlp never gets executed... because it retruns me to BRK again.


Here is my IRQ handler

Code: Select all

; BRK Handler----------------------------------------------------------------------------------
break   CLI                     ; clear interrupt
        STA   acc               ; save A    
        STX   xreg              ; save X 
        STY   yreg              ; save Y 
        PLA                     ; 
        STA   status            ; save P 
        PLA                     ; pull PCL off stack 
        PLX                     ; pull PCH off stack 
        SEC                     ; 
        SBC   #$02              ; 
        STA   pclo              ; backup to BRK cmd 
        BCS   brk2              ; 
        DEX                     ; 
brk2    STX   pchi              ; save PC 
        TSX                     ; get stack pointer 
        STX   stack             ; save stack pointer
        ;jsr   Bell             ; Beep speaker 
        LDX   #$FF              ; 
        TXS                     ; clear stack 
        JSR   mlmon             ; starts ML Monitor
        RTI                     ; exit break handler
User avatar
dclxvi
Posts: 362
Joined: 11 Mar 2004

Post by dclxvi »

DonnaD wrote:
Would is the standard practive... to put a NOP there then?
Some BRK handlers look at the byte after BRK (by getting PC from the stack), but BRK and RTI ignore it, so it doesn't matter what its value is. NOP works as well as anything else.

RTI pulls P and PC (lo byte, then hi byte) from the stack, so when you pull them off with your BRK handler, you've got to put them back on before the RTI. Something like:

Code: Select all

     LDX pchi
     LDA pclo
     CLC
     ADC #2
     BCC skip
     INX
skip PHX
     PHA
     LDA status
     PHA
     RTI
In fact, in the above, you could replace:

Code: Select all

     CLC
     ADC #2
     BCC skip
with:

Code: Select all

     INC
     BNE skip
and then there wouldn't need to be a dummy byte after the BRK.
DonnaD
Posts: 33
Joined: 05 Jul 2007

Post by DonnaD »

That first code works... but if I subtract one instead of two from the PC to avoid the ignored byte, it doesn't work.

Also, is this a bug? and if so is it in all 6502's... I have a W65C02S
Last edited by DonnaD on Mon Feb 25, 2008 4:04 am, edited 2 times in total.
blargg
Posts: 42
Joined: 30 Dec 2003
Contact:

Post by blargg »

DonnaD, your fix would back the PC back up to point to the BRK again, since it subtracts 2 from it. BRK only skips one extra byte, so if you wanted to continue execution just after the BRK opcode, you'd only subtract 1. Even if it were properly subtracting 1, it's still changing the stack pointer and not putting the return address back. RTI pops a byte, puts that into the status flags, then pops two more and jumps to the address formed by them.

Any reason you can't just put a NOP after your BRK? That's a much simpler solution. Also, the best way to get this kind of thing working is to start with something known to work, then incrementally add things until it breaks. So in this case, you'd start with a minimal BRK handler of just RTI. Once you understand the behavior of it, then add more to the handler. Also, when testing things where instructions seem to be skipped, put a bunch of one-byte instructions in the area you think skipping is occurring, so you ensure that if it is skipping, it's not falling in the middle of an instruction. In the example I used INX, since it also tells us how much is being skipped.

The following (verified to work) BRK handler avoids skipping the byte after BRK:

Code: Select all

irq:
    ; Save A and P
    sta saved_a
    pla
    sta saved_p
    
    ; Decrement return address
    pla
    bne :+
    sta pclo
    pla
    dea
    pha
    lda pclo
:   dea
    pha
    
    ; Put P back on stack
    lda saved_p
    pha
    
    ; Do whatever, but don't change stack pointer
    ; ...
    
    ; Restore A and P, then return to caller
    lda saved_a
    rti
User avatar
8BIT
Posts: 1787
Joined: 30 Aug 2002
Location: Sacramento, CA
Contact:

Post by 8BIT »

DonnaD wrote:
Here is my IRQ handler
Quote:
; BRK Handler----------------------------------------------------------------------------------
break CLI ;clear interrupt
STA acc ; save A
STX xreg ; save X
STY yreg ; save Y
PLA ;
STA status ; save P
PLA ; pull PCL off stack
PLX ; pull PCH off stack
SEC ;
SBC #$02 ;
STA pclo ; backup to BRK cmd
BCS brk2 ;
DEX ;
brk2 STX pchi ; save PC
TSX ; get stack pointer
STX stack ; save stack pointer
;jsr Bell ; Beep speaker
LDX #$FF ;
TXS ; clear stack
JSR mlmon ;starts ML Monitor
RTI ;exit break handler
DonnaD,

My Break routine was not designed to return you to the location following the break. It was meant to be used in troubleshooting to halt a program and save the register contents for examination by the System Monitor.

The LDX #$FF and TXS at the end essentially wipe out the Stack and reset it before entry into the Monitor.

There are ways to get the return address from the stack without the PLA and PHA.

Code: Select all

...
BRK:
     STA  Save_Acc
     STX  Save_X
     STY  Save_Y
     TSX
     LDA  $101, x
     STA  Save_P
     LDA  $102, x
     STA  Save_PCL
     LDA  $103, x
     STA  Save_PCH
     LDX  Save_X
     LDA  Save_A
     RTI
This will retrieve the data without changing the stack pointer and return you to the 2nd byte after the BRK instruction. You can build on this to meet your needs.

Hope that clears up why my code wasn't working for you.

Daryl
User avatar
dclxvi
Posts: 362
Joined: 11 Mar 2004

Post by dclxvi »

DonnaD wrote:
That first code works... but if I subtract one instead of two from the PC to avoid the ignored byte, it doesn't work.

Code: Select all

monitor BRK
pushes the high byte of monitor+2, then pushes the low byte of monitor+2, then pushes P, then jumps to the BRK handler. Using your original BRK handler, you pull P and the address from the stack, subtract 2 from that address and store the result in pclo and pchi. So pclo contains the low byte of monitor, and pchi contains the high byte of monitor.

RTI pulls P from the stack, the pulls the low byte of the return address, then pulls the high byte of the return address, then jumps to the return address.

So using my first routine, 2 is added 2 the 16-bit value whose low byte is in pclo and whose hi byte is in pchi, then the high byte of the result is pushed onto the stack, then the low byte of the result is pushed onto the stack, then P is pushed onto the stack, then comes RTI. Since pclo and pchi contained monitor, the return address pushed onto the stack is monitor+2, so RTI skips the byte after the BRK. My proposed change adds 1 instead of 2, so the return address would be monitor+1 instead of monitor+2

If the mlmon routine doesn't care about the value of pclo and pchi, then you can subtract 1 (instead of 2) from the address you pull from the stack and store that in pclo and pchi. Then push pchi and pclo onto the stack (without adding 1 or 2) before the RTI, since pclo and pchi contain the low and high bytes of monitor+2-1 = monitor+1 which is the address you want to return to.

Also, make sure that you're subtracting 1 correctly. Remember that:

Code: Select all

     DEC lo_byte
     BNE skip
     DEC hi_byte
skip
doesn't decrement properly. Instead, you need something like:

Code: Select all

     LDA lo_byte
     BNE skip
     DEC hi_byte
skip DEC lo_byte
DonnaD wrote:
Also, is this a bug? and if so is it in all 6502's... I have a W65C02S
No, it's not a bug. BRK was intentionally designed to skip the byte after the BRK on all members of the 6502 family, from the original NMOS 6502 to the 65C816.
blargg wrote:
Any reason you can't just put a NOP after your BRK? That's a much simpler solution.
It makes the BRK handler much simpler. However, on rare occasions it's more convenient for BRK to act like a one byte instruction than a two byte instruction, even though it's slower and it makes the BRK handler less simple. Even so, I'd also recommend using BRK and a dummy byte as a starting point, and modifying the BRK handler not to skip the byte after BRK only when necessary.
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

Post by GARTHWILSON »

DonnaD, it is not clear from the little you wrote if you are really understanding the I flag and the CLI instruction. CLI does not clear an interrupt condition. Non-BRK interrupts are generated by hardware, and the interrupt condition needs to be cleared by the interrupt-service routine doing whatever the particular hardware requires in order to clear the condition. CLI clears the interrupt-disable flag, making the processor ready to accept hardware interrupts again through its IRQ\ input. This is addressed in my interrupts article at http://www.6502.org/tutorials/interrupts.html . At least enjoy my cartoons if nothing else. ;)

In your code

Code: Select all

monitor   BRK          ;Enter ML Monitor   
          JSR cls      ;clears screen
          JSR menu     ;displays menu
          JMP mainlp   ;jumps to main program loop
the first JSR will get skipped; so if the low byte of the cls address is 00, the processor would see that as another BRK instruction.

The second byte after the BRK is intended to be a signature byte, so the BRK service routine can optionally read it to help decide what it's supposed to do with this particular BRK. It is not a bug. The 65c02 has all the bugs fixed, unlike the NMOS 6502. The only time I've used BRK was when we used the AIM-65 computers in school 25 years ago, and then we always put two in a row.
DonnaD
Posts: 33
Joined: 05 Jul 2007

Post by DonnaD »

Thanks Garth,

I think I am understanding the I flag properly. Do I not need to use it with BRK, even though I am not using hardware interrrupts?

I would understand that when my software issues a BRK it will set the I flag and enter the BRK handler. Once in the handler, if I don't reset the I flag, it will immediatly execute BRK again since it things it was issued again since I never cleared the I flag.

If I use hardware interrupts, I would have to clear the I flag, as well as the flag on the external hardware... i.e. the 6522 or whatever.

Tell me if I'm right!
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

Post by GARTHWILSON »

Remember-- the I flag is the interrupt-disable flag, so clearing it enables interrupts. BRK, NMI, and IRQ set I. BRK only occurs with the BRK instruction, so regardless of the flag's status, your BRK routine will not be re-entered until the next BRK instruction, even if you leave I alone.

Interrupts set I, and RTI normally clears it (although you can change that by changing the status byte on the stack that RTI pulls off and puts back in P). I've had up to about 100,000 interrupts per second (on purpose-- not from a bug) while not using SEI or CLI for several minutes at a time.

Most interrupt-service routines should not have CLI or SEI in them. The place to use CLI is after you set up your interrupt sources and you're ready to start servicing them. SEI is mainly used when you're entering a piece of code that must not be interrupted, whether for timing reasons or whatever.
DonnaD
Posts: 33
Joined: 05 Jul 2007

Post by DonnaD »

OK.. I get it now... I almost had it but not quite.... that makes sense now.

thanks
Post Reply