590 likes | 960 Views
4 Interrupts the response problem is the difficult one of making sure that the embedded system reacts rapidly to external events, even if it is in the middle of doing something else.
E N D
the response problem is the difficult one of making sure that the embedded system reacts rapidly to external events, even if it is in the middle of doing something else. • Interrupts cause the microprocessor in the embedded system to suspend doing whatever it is doing and to execute some different code instead, code that will respond to whatever event caused the interrupt.
4.1 Microprocessor Architecture • Assembly language is the human-readable form of the instructions that the microprocessor really knows how to do. • A program called an assembler translates the assembly language into binary numbers • each assembly-language instruction turns into just one instruction for the microprocessor. • When the compiler translates C, most statements become multiple instructions for the microprocessor to execute. • Every family of microprocessors has a different assembly language • Within each family, the assembly languages are almost identical to one another.
The typical microprocessor has a set of registers, sometimes called general-purpose registers • Before doing any operation on data, microprocessors must move the data into registers. • assume our microprocessor has registers called R1, R2, R3, and so on. • a program counter: keeps track of the address of the next instruction that the microprocessor is to execute. • a stack pointer: stores the memory address of the top of the stack. • address of variable: the name of a variable appears in an instruction • value of a variable: the name of the variable in parentheses • a comment: follows by a semicolon
moves data from one place to another: copies value in register R2 into register R3 MOVE R3,R2 • copy the value of iTemperature from the memory into register R5 MOVE R5,(iTemperature) • places the addressof iTemperature into register R5 MOVE R5,iTemperature
accumulator • a special register called the accumulator, many can do standard arithmetic or bit-oriented operations in any register. • adds the contents of register R3 into register R7 ADD R7,R3 • inverts all of the bits in register R4 NOT R4
jump instruction ADD R1,R2 JUMP NO_ADD MORE_ADDITION: ADD R1,R3 ; These are skipped ADD R1,R4 NO_ADD: MOVE (xyz),R1
conditional jump instructions • instructions that jump if a certain condition is true. • Most microprocessors can test conditions such as whether the results of a previous arithmetic operation was 0 or greater than 0 and other similar, simple things. SUBTRACT R1, R5 JCOND ZERO, NO_MORE MOVE R3,(xyz) NO_MORE:
stack • The PUSH instruction adjusts the stack pointer and adds a data item to the stack. • The POP instruction retrieves the data and adjusts the stack pointer back.
subroutines • CALL instruction for getting to subroutines or functions • RETURN instruction for getting back. CALL ADD_EM_UP MOVE (xyz), R1 … ADD_EM_UP: ADD R1, R3 ADD R1, R4 ADD R1, R5 RETURN
x = y + 133; • MOVE R1, (y) ;Get the value of y into R1 • ADD R1, 133 ;Add 133 • MOVE (X),R1 ;Save the result in x • If (x >= z) • MOVE R2,(z) ;Get the value of z • SUBTRACT R1,R2 ;Subtract z from x • JCOND NEG,L101 ;Skip if the result is negative • Z += y; • MOVE R1,(y) ;Get the value of y into R1 • ADD R2,R1 ;Add it to z. • MOVE (z),R2 ;Save the result in z • w= sqrt (z); • L101: • MOVE R1,(z) ;Get the value of Z into R1 • PUSH R1 ;Put the parameter on the stack • CALL SQRT ;Call the sqrt function • MOVE (w), R1 ;The result comes back in R1 • POP R1 ;Throw away the parameter
4.2 Interrupt Basics • what interrupts are, • what microprocessors typically do when an interrupt happens, • what interrupt routines typically do, • how they are usually written. • To begin with, interrupts start with a signal from the hardware. • Most I/O chips, such as ones that drive serial ports or network interfaces, need attention when certain events occur.
Each of these chips has a pin that it asserts when it requires service. The hardware engineer attaches this pin to an input pin on the microprocessor called an interrupt request, or IRQ
When the microprocessor detects its interrupt request pins is asserted, it stops executing the sequence of instructions it was executing, saves on the stack the address of the instruction that would have been next, and jumps to an interrupt routine. • Interrupt routines do whatever needs to be done when the interrupt signal occurs. • A serial port interrupt routine must read the character from the serial port chip and put it into memory • interrupt routines also must do some miscellaneous housekeeping chores, such as resetting the interrupt-detecting hardware within the microprocessor to be ready for the next interrupt.
An interrupt routine is sometimes called an interrupt handleror an interrupt service routine. Itis also sometimes called by the abbreviation ISR. • The last instruction to be executed in an interrupt routine is an assembly language RETURN instruction. When it gets there, the microprocessor retrieves from the stack the address of the next instruction • There is no CALL instruction; the microprocessor does the call automatically in response to the hardware signal.
Saving and Restoring the Context • It is difficult or impossible for a microprocessor to get much done without using at least some of the registers. • most microprocessors must move data values into the registers before they can operate on them • it is unreasonable to expect anyone to write an interrupt routine that doesn't touch any of the registers. • this problem is for the interrupt routine to save the contents of the registers it uses at the start of the routine and to restore those contents at the end. • the contents of the registers are saved on the stack
Pushing all of the registers at the beginning of an interrupt routine is known as saving the context; popping them at the end, as restoring the context. • you must write your interrupt service routines to push and pop all of the registers they use, since you have no way of knowing what registers will have important values in them when the interrupt occurs.
Disabling Interrupts • Almost every system allows you to disable interrupts • This stops the interrupt signal at the source • Most microprocessors have a nonmaskable interrupt, an input pin that causes an interrupt that cannot be disabled. • the nonmaskable interrupt is most commonly used for events that are completely beyond the normal range of the ordinary processing
a different mechanism for disabling and enabling interrupts. • microprocessors assign a priority to each interrupt request signal and allow your program to specify the priority of the lowest-priority interrupt that it is willing to handle at any given time. • disable all interrupts by setting the acceptable priority higher than that of any interrupt, • enable all interrupts by setting the acceptable priority very low
Some Common Questions • How does the microprocessor know where to find the interrupt routine when the interrupt occurs? • Some microprocessors assume that the interrupt service routine is at a fixed location. • The most typical is that a table somewhere in memory contains interrupt vectors, the addresses of the interrupt routines. • When an interrupt occurs, the microprocessor will look up the address of the interrupt routine in this interrupt vector table.
How do microprocessors that use an interrupt vector table know where the table is? • the table is always at the same location in memory, at 0x00000 for the Intel 80186 • Can a microprocessor be interrupted in the middle of an instruction? • Usually not. In almost every case, the microprocessor will finish the instruction that it is working on before jumping to the interrupt routine.
If two interrupts happen at the same time, which interrupt routine does the microprocessor do first? • Almost every microprocessor assigns a priority to each interrupt signal, and the microprocessor will do the interrupt routine associated with the higher-priority signal first. • What happens if an interrupt is signaled while the interrupts are disabled? • In most cases the microprocessor will remember the interrupt until interrupts are reenabled, at which point it will jump to the interrupt routine. • If more than one interrupt is signaled while interrupts are disabled, the microprocessor will do them in priority order when interrupts are reenabled.
Can an interrupt request signal interrupt another interrupt routine? • On most microprocessors, yes. interrupt nesting happens automatically • The Intel x86 microprocessors disable all interrupts automatically whenever they enter any interrupt routine; therefore, the interrupt routines must reenable interrupts to allow interrupt nesting. • a higher-priority interrupt can interrupt a lower-priority interrupt routine
What happens if I disable interrupts and then forget to reenable them? • The microprocessor will execute no more interrupt routines, • What happens if I disable interrupts when they are already disabled or enable interrupts when they are already enabled? • Nothing. • Are interrupts enabled or disabled when the microprocessor first starts up? • Disabled.
Can I write my interrupt routines in C? • Yes, usually. Most compilers used for embedded-systems code allows you to tell the compiler that a particular function is an interrupt routine. For example: void interrupt vHandieTimerlRQ (void) { … } • The most common reason for writing interrupt routines in assembly language is that on many microprocessors you can write faster code in assembly language than you can in C. • If speed is not an issue, writing your interrupt routines in C is a good idea.
4.3 The Shared-Data Problem • interrupt routines need to communicate with the rest of your code. • Microprocessor do not complete all its work in interrupt routines. • the interrupt routines and the task code must share one or more variables that they can use to communicate with one another.
data-sharing problem • Figure 4.4 Classic Shared-Data Problem static int iTemperatures[2]; void interrupt vReadTemperatures (void) { //中斷時讀入2個溫度值 iTemperatures[0] = !! read in value from hardware iTemperatures[1] = !! read in value from hardware } void main (void) { int iTemp0, iTemp1; while (TRUE) { iTemp0 = iTemperatures[0]; //溫度來自於中斷服務程式 iTemp1 = iTemperatures[1]; if (iTemp0 != iTemp1) //比較2個溫度是否相同 !! Set off howling alarm; } }
What is the problem with the program in Figure 4.4? • suppose that both temperatures have been 73 degrees • iTemp0 = iTemperatures[0]; • interrupt occurs • both temperatures have changed to 74 degrees • iTemp1 = iTemperatures[1]; • they will differ 73≠74 and the system will set off the alarm
Figure 4.5 Harder Shared-Data • Problem static int iTemperatures[2]; void interrupt vReadTemperatures (void) { iTemperatures[0] = !! read in value from hardware iTemperatures[1] = !! read in value from hardware } void main (void) { while (TRUE) { if (iTemperatures[0] != iTernperatures[1]) !! Set off howling alarm; } }
What is the problem with the program in Figure 4.5? • the same bug that was in Figure 4.4 is also in Figure 4.5 • the statement iTemperatures[0] = iTernperatures[1] can be interrupted, since the compiler translates the statement into multiple assembly-language instructions.
Figure 4.6 Assembly Language Equivalent of Figure 4.5 MOVE R1, iTemperatures[0]) MOVE R2, (iTemperatures[1]) SUBTRACT R1,R2 JCOND ZERO, TEMPERATURES_0K ; Code goes here to set off the alarm TEMPERATURES OK:
Characteristics of the Shared-Data Bug • The problem with the code in Figure 4.4 and in Figure 4.5 is that the iTemperatures array is shared between the interrupt routine and the task code. • Bugs are difficult to find, because they do not happen every time the code runs • The assembly-language code in Figure 4.6 shows that the bug appears only if the interrupt occurs between the two critical instructions. • Whenever an interrupt routine and your task code share data, be suspicious and analyze the situation to ensure that you do not have a shared-data bug.
Solving the Shared-Data Problem • The first method of solving the shared-data problem is to disable interrupts whenever your task code uses the shared data. • The hardware can assert the interrupt signal requesting service, but the microprocessor will not jump to the interrupt routine while the interrupts are disabled. • the code in Figure 4.7 always compares two temperatures that were read at the same time.
Figure 4.7 Disabling Interrupts Solves the Shared Data Problem static int iTemperatures[2]; void interrupt vReadTemperatures (void){ iTemperatures[0] = !! read in value from hardware iTemperatures[1] = !! read in value from hardware } void main (void) { int iTemp0, iTempi; while (TRUE) { disable (); /* Disable interrupts while we use the array */ iTemp0 = iTemperatures[0]; iTemp1 = iTemperatures[1]; enable (); if (iTemp0 != iTemp1)!! Set off howling alarm; } }
Figure 4.8 Disabling Interrupts in Assembly Language DI ; disable interrupts while we use the array MOVE R1,(iTemperature[0]) MOVE R2,(iTemperature[l]) EI ; enable interrupts again SUBTRACT R1, R2 JCOND ZERO, TEMPERATURES_OK ; Code goes here to set off the alarm TEMPERATURES_OK: • no C compilers or assemblers are smart enough to figure out when it is necessary to disable interrupts.
"Atomic" and "Critical Section" • A part of a program is said to be atomic if it cannot be interrupted. • the shared-data problem arises when an interrupt routine and the task code share data, and the task code uses the shared data in a way that is not atomic. • disable interrupts around the lines of the task code that use the shared data, made that collection of lines atomic • A set of instructions that must be atomic for the system to work properly is often called a critical section.
A Few More Examples • the function ISecondsSinceMidnight returns the number of seconds since midnight. • A hardware timer asserts an interrupt signal every second, which causes the microprocessor to run the interrupt routine vUpdateTime to update the static variables that keep track of the time. • If the hardware timer interrupts while the microprocessor is doing the arithmetic in ISecondsSinceMidnight, then the result might be wrong.
Figure 4.9 Interrupts with a Timer static int iSeconds, iMinutes, iHours; void interrupt vUpdateTime (void) { ++iSeconds; if (iSeconds >= 60) { iSeconds = 0; ++iMinutes; if (iMinutes >= 60) { iMinutes = 0; ++iHours; if (iHours >= 24) iHours = 0; } } !! Do whatever needs to be done to the hardware } long ISecondsSinceMidnight (void) { return ( (((iHours * 60) + iMinutes) * 60) + iSeconds); }
is the program okay? • Suppose that the time is 3:59:59. • The function ISecondsSinceMidnight might read iHours as 3, • but then if the interrupt occurs and changes the time to 4:00:00, • ISecondsSinceMidnight will read iMinutes, and iSeconds as 0 • and return a value that makes it look as though the time is 3:00:00, • almost an hour off.
called from within a critical section long ISecondsSinceMidnight (void) { disable (); return ( (((iHours * 60) + iMinutes) * 60) + iSeconds); enable (); /* WRONG: This never gets executed! */ } • Better, do it like this: long ISecondsSinceMidnight (void) { long IReturnVal; disable (); IReturnVal =(((iHours * 60) + iMinutes) * 60) + iSeconds; enable ();/* original interrupt is enable?*/ return (IReturnVal); }
Disabling and Restoring Interrupts long lSecondsSinceMidnight (void) { long lReturnVal; BOOL fInterruptStateOld; /* Interrupts already disabled? */ fInterruptStateOld = disable (); IReturnVal =(((iHours * 60) + iMinutes) * 60) + iSeconds; /* Restore interrupts to previous state */ if (flnterruptStateOld) enable (); return (lReturnVal); }
Another Potential Solution • Figure 4.11 Another Shared-Data Problem Solution static long int lSecondsToday; void interrupt vllpdateTime (void) { ++lSecondsToday; if (lSecondsToday == 60 * 60 * 24) lSecondsToday = 0L; } long lSecondsSinceMidnight (void) { return (lSecondsToday); }
the shared-data problem • the problem arises if the task code uses the shared variable in a nonatomic way. • If the microprocessor's registers are large enough to hold a long integer, then the assembly language is likely to be MOVE R1, (lSecondsToday) RETURN • which is atomic. • If the microprocessor's registers are too small to hold a long integer, then the assembly language will be something like: MOVE R1, (lSecondsToday) ; Get first byte or word MOVE R2, (lSecondsToday+1) ; Get second byte or word • RETURN • This is not atomic, and it can cause a bug, because if the interrupt occurs while the registers are being loaded, you can get a wildly incorrect result. • The interrupt routine in Figure 4.11 is more efficient than the one in Figure 4.9
The volati1e Keyword • Most compilers assume that a value stays in memory unless the program changes it, and they use that assumption for optimization. This can cause problems. • the code in Figure 4.12 is an attempt to fix the shared-data problem in lSecondsSinceMidnight without disabling interrupts. • The idea is that if lSecondsSinceMidnight reads the same value from ISecondsToday twice, then no interrupt can have occurred in the middle of the read, and the value must be valid.
A Program That Needs the volatile Keyword static long int lSecondsToday; void interrupt vUpdateTime (void) { … ++lSecondsToday; if (lSecondsToday == 60L * 60L * 24L) lSecondsToday = 0L; … } long lSecondsSinceMidnight (void) { long 1Return; /* When we read the same value twice, it must be good. */ 1Return = lSecondsToday; while (lReturn != lSecondsToday) 1Return = lSecondsToday; return (IReturn); }
Some compilers will cause a new problem. For this line of code lReturn = lSecondsToday; • the compiler will produce code to read the value of lSecondsToday into registers and save that value in lReturn. • whi1e statement, the optimizer in the compiler will notice that it read the value of lSecondsToday once already and that that value is still in the registers. • Instead of re-reading the value from memory, the compiler produces code to use the value in the registers, • the two must be equal and that the condition in the while statement will therefore always be false
To avoid this, you need to declare 1SecondsToday to be volatile. • The volatile keyword allows you to warn your compiler that certain variables may change because of interrupt routines or other things the compiler doesn't know about. static volatile long int lSecondsToday; • With the volatile keyword in the declaration the compiler knows that the microprocessor must read the value of 1SecondsToday from memory every time it is referenced. • The compiler is not allowed to optimize reads or writes of lSecondsToday out of existence. • If your compiler doesn't support the volati1e keyword, you should be able to obtain the same result by turning off the compiler optimizations.
4.4 Interrupt Latency • How fast does my system respond to each interrupt? 1. The longest period of time during which that interrupt is (or all interrupts are) disabled 2. The period of time it takes to execute any interrupt routines for interrupts that are of higher priority than the one in question 3. How long it takes the microprocessor to stop what it is doing, do the necessary bookkeeping, and start executing instructions within the interrupt routine 4. How long it takes the interrupt routine to save the context and then do enough work that what it has accomplished counts as a "response"
How do I get the times associated with the four factors listed above? • microprocessor documentation • write the code and measure how long it takes to execute • count the instructions of various types, how long each type of instruction takes • the shorter the period during which interrupts are disabled, the better your response will be.