Reference the C8051F330 datasheet, Table 15.1.
Each interrupt source (Reset, Timer 0 overflow, SMB0, etc) has a hardcoded interrupt vector address. When the interrupt is asserted, the micro generates an LCALL
instruction to that vector address. SMB0 is 0x003B, Timer 0 overflow is 0x000B for example. When you code an interrupt handler (interrupt
keyword in Keil, see this question for other examples), your linker usually puts some kind of JMP
instruction at the vector address to get to your handler.
If you neglect to write a handler for an interrupt that is enabled, the micro will still vector to that address. What happens then depends on what the linker put where in code space. I find that small functions tend to get stuffed in between the vector addresses, especially if you're not using many interrupts and there are several contiguous vector addresses not in use. You can find out by looking at Code space at 0x003B (the SMB0 vector address) in a disassembler.
If you accidentally vector into the middle of some such function located at the wrong place at right time, you'll pick up any side effects of the portion of that function that is run and then the RET
at the end of the function will put you right back where you were before the interrupt. Except that since you didn't go through RETI
, the interrupt is not cleared. Any future interrupts of the same or lower priority will be blocked, because the micro thinks you're still servicing the SMB0 interrupt.
You suggested that a NOP
might replace unimplemented handlers, but this cannot be done since your toolchain doesn't know what interrupts are enabled at compile time. If you don't write a handler and locate it at a particular vector address, it presumes that that portion of code space is fair game and will put something else there.