Docstoc

Interrupts

Document Sample
Interrupts Powered By Docstoc
					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.
• 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 address of 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 handler or an interrupt service
  routine. It is 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.
a microprocessor responding to interrupt
       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 micro-processors, 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 micro-processor 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
  micro-processors 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.
          Disabling Interrupts
• Suppose that the requirements for your system are as
  follows:
• disable interrupts for 125 microseconds (μsec) for your
  task code to use a pair of temperature variables it shares
  with the interrupt routine that reads the temperatures
• disable interrupts for 250 μsec for your task code to get
  the time accurately
• respond within 625μsec when you get a special signal
  from another processor in your system; the
  interprocessor interrupt routine takes 300 μsec to
  execute.
• Can this be made to work?
• The interrupt routine needs 300 μsec, for a total,
  worst-case time of 550 μsec, within the 625-
  μsec limit.
• Suppose to cut costs, the hardware group
  proposes to replace the microprocessor with one
  that runs only half as fast.
• Now, all the processing times are doubled,
  interrupts are disabled for twice as long, the
  interrupt service routine takes twice as long, but
  the 625-μsec deadline remains the same. Now
  will the system meet its deadline?
• The answer is no.
• Interrupts will be disabled for up to 500 μsee at a
  time, and the interrupt service routine needs 600
  μsec to do its work. The total of these two is
  1100 μsec, much longer than the 625-μsec
  deadline.
• It depends. If you can assign the network
  interrupt a lower priority than the
  interprocessor interrupt
      Alternatives to Disabling
             Interrupts
• Since disabling interrupts increases interrupt
  latency, a few alternative methods for dealing
  with shared data.
• in most cases simply disabling interrupts is more
  robust than the techniques discussed below
• The interrupt routine always writes to whichever
  set the task code is not using. This simple
  mechanism solves the shared-data problem,
  because the interrupt routine will never write into
  the set of temperatures that the task code is
  reading.
int iTemperaturesA[2];
static int iTemperaturesB[2];
static BOOL fTaskCodeUsingTempsB = FALSE;
void interrupt vReadTemperatures (void) {
  if (fTaskCodeUsingTempsB) {
    iTemperaturesA[0] = !! read in value from hardware;
    iTemperaturesA[l] = !! read in value from hardware; )
  }else {
    iTemperaturesB[0] = !! read in value from hardware;
    iTemperaturesB[l] = !! read in value from hardware;
}
void main (void) {
  while (TRUE) {
    if (fTaskCodeUsingTempsB)
      if (iTemperaturesB[0] != iTemperaturesB[l]) !! Set off howling alarm;
    else
     if (iTemperaturesA[0] != iTemperaturesA[l]) !! Set off howling alarm;
   fTaskCodeUsingTempsB = ! fTaskCodeUsingTempsB;
}
}
   Figure 4.16 A Circular Queue
    Without Disabling Interrupts
• Because the iHead pointer and the iTail pointer
  ensure that the interrupt routine will be writing to
  different locations in the queue than the ones
  from which the task code is reading, the shared-
  data problem with the temperatures themselves
  is eliminated.
• Because of the fragility of this code, it would
  make sense to write it this way only if disabling
  interrupts is really not an option.
#define QUEUE_SIZE 100
int iTemperatureQueue[QUEUE_SIZE];
int iHead = 0; /* Place to add next item */
int iTail = 0;     /* Place to read next item */
void interrupt vReadTemperatures (void) {
/* If the queue is not full . . . */
  if (!(( iHead+2 == iTail) || (iHead==QUEUE_SIZE-2 && iTail==0)))
  {
     iTemperatureQueue[iHead] = !!read one temperature;
     iTemperatureQueue[iHead + 1] = !!read other temperature;
     iHead += 2;
     if (iHead == QUEUE_SIZE)
     iHead = 0;
  } else
     !!throw away next value
}
void main (void) {
int iTemperaturel, iTemperatureZ;
   while (TRUE) {
/* If there is any data. . . */
   if (iTail != iHead)
   {
     iTemperaturel= iTemperatureQueue[iTai1];
     iTemperature2= iTemperatureQueue[iTail + 1];
     iTail += 2;
     if (iTail = QUEUE_SIZE)
     iTail = 0; .'.' Do something with iValue;
   }
}

				
DOCUMENT INFO
Shared By:
Categories:
Tags:
Stats:
views:7
posted:12/8/2011
language:English
pages:59