Page 1 of 1
Interrupt Handler
Posted: Sun Feb 24, 2008 11:16 pm
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?
Posted: Mon Feb 25, 2008 12:10 am
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).
Posted: Mon Feb 25, 2008 1:37 am
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
Posted: Mon Feb 25, 2008 2:30 am
by DonnaD
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....
Posted: Mon Feb 25, 2008 2:39 am
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
Posted: Mon Feb 25, 2008 3:04 am
by dclxvi
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:
with:
and then there wouldn't need to be a dummy byte after the BRK.
Posted: Mon Feb 25, 2008 4:00 am
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
Posted: Mon Feb 25, 2008 4:01 am
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
Posted: Mon Feb 25, 2008 4:38 am
by 8BIT
Here is my IRQ handler
; 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
Posted: Mon Feb 25, 2008 5:31 am
by dclxvi
That first code works... but if I subtract one instead of two from the PC to avoid the ignored byte, it doesn't work.
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
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.
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.
Posted: Mon Feb 25, 2008 5:49 am
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.
Posted: Wed Feb 27, 2008 7:58 pm
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!
Posted: Wed Feb 27, 2008 8:40 pm
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.
Posted: Thu Feb 28, 2008 4:18 am
by DonnaD
OK.. I get it now... I almost had it but not quite.... that makes sense now.
thanks