On a PIC, and especially a PIC16, I think the basic principles of interrupts are obscured by the complexities of how they work on PIC16s; details about saving state, handling paging, Read-only vectors, and so on. On some other CPUs, things are much simpler.
So, stepping backwards...
"Hardware Interrupts" are a mechanism for providing faster (more immediate) software servicing of particular hardware events. Typically the CPU hardware will check some hardware status after each CPU instruction (for CISC machines for potentially long-running single instructions, sometimes even with a single instruction.) If the hardware needs attention, the CPU will transfer execution to a special piece of software called "the Interrupt Service Routine" (ISR). The ISR can do whatever is necessary to service the hardware, and then it returns to the point where the main software was interrupted, and continues whatever it was doing before.
So what has to happen in order for that to work?
Well, at the hardware level, we want to be able to turn this sort of thing on and off at a global level. Some things should just not be interrupted! Other things shouldn't cause interrupts before all the pieces associated with them are set up properly. On the PIC (16F877), this global enable/disable of the interrupt function is controlled by the GIE bit of the INTCON register.
But before that can work, we have to route the signals that we want to cause interrupts to the appropriate gates, and others that we don't want to cause interrupts need to be set up NOT to. In this case, this is also done via setting or clearing other bits in the INTCON register. See figure 12-9 of the 877 manual for the cascade of gates involved.
And we have to set up the destination address(es) of the ISR code functions. On PICxx877 this is easy; it goes to location 4. Other processors may have multiple locations for multiple interrupt sources, or have external interrupt controllers than you need to poke at...
So if we want RB0 and TM0 to cause interrupt, somewhere in our code initialization we will have code that looks something like:
bsf INTCON,T0IE ;enable TMR0 interrupt bsf INTCON,INTE ;enable B0 interrupt bsf INTCON,GIE ; enable interrupt globally.
Now we're ready to do anything we want, and hope we get interrupted by important events!
But wait; how is this going to work from a SW point of view, if the ISR has to continue what we were doing when the ISR is done? The interrupt as a whole will have to preserve everything about the state of the CPU that was relevant to what the main code was doing at the time the interrupt occurred. PC address. Register contents. Status flags for the multi-precision math or conditional jump/skip I was about to take, and so on. All of that will need to be saved when an interrupt occurs, and restore to the previous state when the ISR finishes. This is not TOO much different than a subroutine call, but it is less voluntary than a subroutine, and so must preserve things more exactly (your subroutine can say "I trash the flags values", and you're supposed to deal with that.)
The Saving of State is divided up between what the hardware does, and what the ISR software you write has to do. On many CPUs, the interrupt hardware will push both the return address and the status flags onto the stack, for example. On some CPUs, it'll do that AND switch you to another set of registers reserved just for used by ISRs. However, on the PIC16, the stack is only for return addresses, so that's all that gets saved for you automatically. You suddenly arrive at location 4, and you're NOT ALLOWED TO MAKE ANY CHANGES THAT YOU CAN'T REVERT on exit. And you don't have a general purpose stack. The other thing that has been done for you is that interrupts are now globally OFF, which prevents things like new interrupts showing up and wanting to save (now different) state in the same variables. Here is where this bit of code comes in:
org 0x4 ContextSave movwf temp_w ;save w in temp. movwf does not change STATUS ;; NOTE However, that temp_w must exist in all RAM banks. swapf STATUS,w ;put unchanged STATUS in w with nibbles reversed. clrf STATUS ;now use RAM page 0 movwf temp_s ;save STATUS in temp_s movf PCLATH,W ; save PCLATH as well movwf temp_pclath clrf PCLATH ; now on code page 0 for sure too. ;;; Now the important and hard-to-do-without registers have been ;;; saved and reset as appropriate for continuing to run the ISR ;;; code here in low memory.
Now, since all interrupts came to location 4, we have to check which one it might have been and jump to appropriate smaller bits of code. this is usually done by examining the INTCON register(s) some more:
Dispathc_int btfsc INTCON, RBIE ;; check for RB0 interrupt goto RB0INTCODE btfsc INTCON, T0IF ;; check for timer interrupt goto TIMERINT ;;; Shouldn't be here; just get out by falling through INTEXIT MOVF temp_pclath, W ;Restore PCLATH MOVWF PCLATH ;Move W into PCLATH SWAPF temp_s,W ;Swap STATUS_TEMP register into W ;(sets bank to original state) MOVWF STATUS ;Move W into STATUS register SWAPF temp_w,F ;Swap temp_w SWAPF temp_w,W ;Swap temp_w into W retfie RB0INTCODE ;; we got a signal on RB0. Do the right things! ; : Blah ; : Blah goto INTEXIT ;get out TIMERINT incfz TIMERTICS_LOW goto INTEXIT inc TIMERTICS_HIGH goto INTEXIT
The ISR code has to have SOME state (memory variable) it can manipulate somewhere in the processor. That's fine as long as it's not state that the main program also manipulates. If there is data that has to be shared between ISR code and main Code, you have to be VERY CAREFUL about how and when it is manipulated. The easiest thing to do is surround the main code that touches this data with instructions that turn off interrupts (globally) while the main code is working with them (and then turns them on again when its done.)
Since our ISR code is done, we now know where the startup code can go:
StartupCode: ;; blah
And hopefully we can set up the reset vector to go there:
org 0x0 movlw HIGH(StartupCode) ; 0 movwf PCLATH ; 1 goto StartupCode ; plenty of room; won't run into our ISR code.
(mind you this probably has errors; I haven't actually tried this, and it's been a while since I coded a PIC in assembler. But I hope the explanation helps some...)
BillW