Interrupt Handler
Interrupt Handler
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?
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?
Here is the section that initiates the BRK
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
monitor BRK ;Enter ML Monitor
JSR cls ;clears screen
JSR menu ;displays menu
JMP mainlp ;jumps to main program loopJSR 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 handlerDonnaD wrote:
Would is the standard practive... to put a NOP there then?
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
Code: Select all
CLC
ADC #2
BCC skip
Code: Select all
INC
BNE skip
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
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.
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:
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
rtiDonnaD 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
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
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
Hope that clears up why my code wasn't working for you.
Daryl
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
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
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
blargg wrote:
Any reason you can't just put a NOP after your BRK? That's a much simpler solution.
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
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
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.
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 loopThe 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.
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!
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!
- GARTHWILSON
- Forum Moderator
- Posts: 8773
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
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.
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.