Interrupts and the Operating System

In a typical system, the software can be divided into 3 possible groups. One is the Operating Loop, another is the Interrupt Service Routines, and the last is the BIOS and OS functions and subroutines. The Operating Loop is the main part of the system. It will usually end up being a sequence of calls to BIOS and OS subroutines, arranged in an order that accomplishes what we set out to do, with a little manipulation and data transfer in between. At the same time, at least it LOOKS like it's happening at the same time, the interrupts are being serviced as they happen. In the DS5000, there are seven possible events that can trigger an interrupt. Two of them are from external interrupt inputs, that can be from whatever hardware we've added to the DS5000 that we deem to need servicing as soon as they happen. Two more are from internal timers or counters that cause an interrupt when a certain count has occurred. One is a Power Fail that happens when the power to the DS5000 has dropped below a certain voltage. The last two are from transmitting or receiving data through the serial port.

To digress just a moment, there are two ways to service, or act on, events that happen in the system. One is to scan them and the other is to use interrupts. Scanning is just what is sounds like. Each possible event is scanned in a sequence, one at a time. This is ok for things that don't require immediate action. Interrupts, on the other hand, cause the current process to be suspended temporarily and the event that caused the interrupt is serviced, or handled, immediately. Remember, from a previous lesson, that the routine that is executed as a result of an interrupt is called the interrupt service routine, or the interrupt handler routine.

In most systems, and ours in particular, there is a mixture of both. The choice of which to use depends on how long you can wait to take action on an event. For instance, when a character is received through the serial port, it must be read and stored somewhere for later use, and done quickly enough that the next character received doesn't overwrite the previous one. The serial port is double buffered. This means it can be receiving one character, while still holding the last character it has previously received. Remember, or understand, that the serial port gets it's name from the fact that characters are received as a serial stream of individual bits, one bit at a time. When 8 bits have been received, this byte is transferred to a one byte receive buffer, at which time an interrupt is generated, and the serial receiver can start receiving the next character. If the operating loop is in some conversion routine that takes a while to finish, and a character comes in on the serial port, this conversion needs to interrupted for long enough to get the received character and store it in a buffer area, and then return to finish the conversion routine, without loosing any serial data.

The number of interrupts available for use is limited and part of the system designer's job is to figure out which events need interrupts and which ones can be polled, or scanned. Some, like the serial port and the power fail, are dedicated and can't be used for anything else. But one of the two timer/counter interrupts and the two external interrupts can be used for whatever the designer sees fit. I normally like to take one of the two external interrupts and apply the 60 hz line frequency to it so that the micro is getting interrupted 60 times a second, all the time. This allows for, what appears to be, two separate pieces of software to be running at the same time. The operating loop, doing its scheduled tasks, and an interrupt routine that is doing several jobs that need to be done during the same time period. An example of one of these jobs is to update a real - time system clock/calender that can be used by the operating system, to schedule tasks based on time of day/date. Another job is to poll some parts of the system hardware often enough to appear to be interrupt driven, when there isn't a dedicated interrupt that could be used. What happens is that when the 60 hz interrupt happens, certain events are scanned to see if they have happened. These events are therefore scanned every 1/60 th of a second. In this manner a combination of polled - interrupt driven response to an event can be used to expand the effective number of interrupts available to the designer. It's faster than just polling, but not as fast as a dedicated interrupt.

In the DS5000, as with any micro that has interrupt capability, there is a method by which the interrupt gets serviced in a timely manner. Remember how a call works, as we saw in the previous lesson. When the call is executed, the next instruction's address that would have been executed, is pushed onto the stack, and a jump is made to the start of the called routine. At the end of that routine, a return is executed, which pops the previously pushed address, and causes a jump back to the next instruction after the call. An interrupt does essentially the same thing. When the interrupt occurs, and the current instruction that is being processed is finished, the address of the next instruction to be executed is pushed onto the stack. Then a jump is made to a dedicated location. This particular jump is called a vector.

It's referred to as a vector, to differentiate it from a regular jump, although it's essentially the same thing. Each interrupt has it's own vector, or unique location where it's service routine starts. These are hard coded into the DS5000 and can't be changed. These vector addresses start at 0000h in program memory. Following is a map of these locations and the interrupt assigned to each.

0000h   Reset/Power UP
0003h   External Interrupt Input 0
000Bh   Timer 0 Interrupt
0013h   External Interrupt Input 1
001Bh   Timer 1 Interrupt
0023h   Serial I/O
002Bh   Power Fail Warning

These are the starting addresses of the service routines for each of the interrupt sources in the DS5000. Notice one that I haven't mentioned before called Reset/Power Up. When power is applied to the DS5000, program execution starts at location 0000h. Another way to get to this location, other than jumping to it, is to do a reset of the DS5000. There is a pin on the DS5000 called Reset. Triggering this input causes the DS5000 to vector to 0000h and start executing just like power up. The two differences between this and a regular interrupt is that it doesn't push any address onto the stack, and it is recognized any time it happens and can't be turned off. The other interrupts can be disabled by the program, and while disabled, do not generate any interrupts. Also when power up occurs or a reset occurs, the interrupts are automatically disabled and must be enabled before they can be used. Reset does several other things that I will mention later.

Again let me digress a moment. About the jump instructions. In some of our previous programming examples we had a jump instruction called sjmp (Short Jump). This instruction can only jump to addresses relatively close to it. There is another jump called ajmp (Absolute Jump) that can get to anywhere, as long as anywhere is in the first 2K bytes of program memory. There is a third jump called ljmp (Long Jump). It can get to any place in program memory. The short jump is good for small loops inside a routine. The absolute jump is good when the entire program fits in less than 2K bytes. But to get to any place, regardless of how far away it is, the long jump is used. The sjmp and the ajmp are 2 byte instructions and the long jump is a 3 byte instruction.

Back to interrupts. You will notice that there isn't many locations between vector addresses. What is normally done is that at the start of each vector address, a long jump instruction (3 bytes) is placed, that jumps to the actual start of the service routine. This way the service routines can be anywhere in program memory. The vector address jumps to the service routine. There is more than enough room between each vector address to put a long jump instruction. Looking at the table above, there are 3 locations for the Reset / Power-Up jump instruction and at least 8 locations for each of the other vectors. When actually writing the software, at address 0000h will be a jump instruction that jumps around the other vector locations. I usually jump to 0030h and continue with the rest of the program. This leaves 5 locations for the Power Fail vector, more than enough room for the 3 byte long jump instruction needed to get to it's service routine.

Besides being able to disable all of the interrupts at once, there is a way to enable or disable them individually. There is a special function register (SFR) called the interrupt enable register. Each bit in this register controls one of the interrupts. When a power-up or reset occurs, all of these bits are cleared to 0's, which disables all the interrupts. Setting anyone of them to a 1, enables that interrupt. Also there is a bit, that if 0, disables all of the interrupts at once, regardless of the state of the individual enables. It is also cleared by power-up or reset. So to enable any one interrupt, the global interrupt enable must be set (enabled) and the individual enable bit for each of the interrupts must also be set. I usually set each of the individual enables that I want to be active, and then, just before entering the operating loop, I enable the global enable, enabling all of the interrupts at once.

When the DS5000 is powered up or reset, after jumping around the interrupt vectors, the first things that are done are called housekeeping functions, like setting the stack pointer to the start of the stack, clearing out various locations, setting up the baud rate for the serial port, and any other things that must be done to get ready for the operation of the system. This is called booting up. The same thing happens in your desktop PC. This code only runs once for a power-up / reset. It's also called initialization. It sets up a known condition to start from, so that the system always (hopefully!) starts off the same way.

Following is a program segment that shows the first few locations of program memory with a typical example of the interrupt vectors.

         .org h'0000   ;start assembling at address 0000h
boot:    ljmp cont     ;jump around interrupt vectors
         .org h'0003   ;assemble at address 0003h
         ljmp exint0   ;jump to external interrupt 0 routine
         .org h'000b   ;assemble at address 000bh
         ljmp timer0   ;jump to timer 0 routine
         .org h'0013   ;assemble at address 0013h
         ljmp exint1   ;jump to external interrupt 1 routine
         .org h'001b   ;assemble at address 001bh
         ljmp timer1   ;jump to timer 1 routine
         .org h'0023   ;assemble at address 0023h
         ljmp serio    ;jump to serial I/O routine
         .org h'002b   ;assemble at address 002bh
         ljmp pwrfl    ;jump to power fail routine
         .org h'0030   ;assemble at address 0030h
cont:

This is how our system will look when we're done. At the label cont: we continue with housekeeping and initialization, having jumped around the interrupt vector locations. Remember that the .org is an assembler directive that tells the assembler that the next instuction assembled will be at the address in the .org. By using the .org, we can place the jumps at the appropriate vector addresses that are hard coded in the DS5000.

There are other things about interrupts that we will cover as they come up, but this lesson was to get you used to the idea of interrupts and what they're used for in a typical system. Remember in a previous lesson we were standing at a busy intersection waiting for the traffic light to change, when a person came up and tapped us on the shoulder and asked what time it was. It didn't stop us from going across the street, it just temporarily interrupted us long enough to tell them what time it was. This is the essence of interrupts. They interrupt normal program execution long enough to handle some event that has occurred in the system.

Polling, or scanning, is the other method used to handle events in the system. It is much slower than interrupts because the servicing of any single event has to wait its turn in line while other events are checked to see if they have occurred. There can be any number of polled events but a limited number of interrupt driven events. The choice of which method to use is determined by the speed at which the event must be handled.

Also, we have a third option and that is a combination of polling and interrupts by using a continuously interrupting event and polling certain other events during each iteration, or occurrance, of the interrupt. This method is faster than straight polling but slower than a dedicated interrupt. It also has other limitations that we will discuss later on.

In the next lesson we will start looking at the various parts of our system.

My home page is http://www.hkrmicrop.com/personal/index.html .

On to lesson 10.