Title: An Embedded Software Primer
1An Embedded Software Primer
2Chapter 4 Interrupts
- Microprocessor Architecture
- Interrupt Basics
- The Shared-Data Problem
- Interrupt Latency
3Microprocessor Architecture
- Most microprocessors and their assembly languages
are generally similar to one another. - An assembly language is a more easily readable
form of the instructions that a microprocessor
actually works with. - The assembly language is translated into binary
numbers by a program called an assembler before
execution. - When translating C, most of the statements are
converted into multiple assembly instructions by
the compiler. - Each microprocessor family will have a different
assembly language (e.g. the Intel 8085, 8051
etc.) however the individual microprocessors in
the same family generally have the same assembly
language. - Microprocessors typically have a set of
(general-purpose) registers that can store data
values.
4Microprocessor Architecture (contd.)
- The registers are used before performing any data
operations like arithmetic. For the following
sections assume the microprocessor has the
registers R1, R2, R3 .etc. - Apart from the above registers, microprocessors
normally contain special registers like - Program Counter Contains the address of the next
instruction to be executed. - Stack Pointer Contains address of the top of the
processor stack. - Accumulator Used by the microprocessor to do
arithmetic operations.
5Microprocessor Architecture (contd.)
- Some assembly language conventions
- The variables in the instruction are normally
read from right to left. - e.g. MOVE R1, R2
- will move the contents of register R2 to register
R1 - A variable-name in an instruction refers to its
address. - MOVE R5, Temperature
- is read move the address of Temperature to
register R5 - A variable in parentheses refers to the value of
the variable. - MOVE R5, (Temperature)
- will place the value of Temperature in register
R5. - Anything following a semicolon is a comment.
6Microprocessor Architecture (contd.)
- Some important instructions include
- Arithmetic ADD R2, R3
- add contents of R3 to R2
- Bit-oriented NOT R5
- invert all the bits of R5
- Jump (unconditional) Will unconditionally jump
to an instruction specified by a label. Labels
are followed by colons. - ADD R2, R3
- NOT R2
- JUMP NO_ADD
- ADD
- ADD R2, R5 Comments come here
- NO_ADD
- MOVE R7, R2 R7 contains the final result
- The instruction adds contents of R3 to R2,
inverts the result in R2, jumps unconditionally
to the NO_ADD label (the instruction ADD R2, R5
never gets executed) and stores the result in R7.
7Microprocessor Architecture (contd.)
- Conditional Jump Executes a jump if a certain
condition is true. - SUBTRACT R2, R3
- JCOND ZERO,NEXT
- .
- .
- NEXT
- .
- .
- Here jump is executed if result of the
subtraction is zero. - PUSH and POP These are stack instructions. PUSH
adjusts the stack pointer and adds data to the
stack while POP retrieves the data and adjusts
the pointer. - CALL Used to execute functions and subroutines.
Followed by a RETURN instruction for getting
back. - CALL SUBTRACT_THESE
- MOVE R7,R2
- .
- .
- SUBTRACT_THESE
- SUB R2,R3
- RETURN
8Interrupt Basics
- Interrupts are triggered when certain events
occur in the hardware. - e.g. when a serial chip has sent data to a
microprocessor and wants it to read it from its
pin, it sends an interrupt to the processor,
usually by sending a signal to one of the
processors IRQ (interrupt request) pins. - On receiving an interrupt, the microprocessor
stops its current execution, saves the address of
the next instruction on the stack and jumps to an
interrupt service routine (ISR) or interrupt
handler. - The ISR is basically a subroutine written by the
user to perform certain operations to handle the
interrupt with a RETURN instruction at the end.
It is a good practice to save register state and
reset the interrupt in ISRs. - ISRs are similar to a CALL except that the call
to the ISR is automatically made.
9Interrupt Basics (contd.)
- The following shows an e.g. of an ISR
- Task Code ISR
- ...
- MOVE R1, R7
- MUL R1, 5 PUSH R1
- ADD R1, R2 PUSH R2
- DIV R1, 2 ...
- JCOND ZERO, END ISR code comes here
- SUBTRACT R1, R3 ...
- ... POP R2
- ... POP R1
- END MOVE R7, R1 RETURN
- ...
- ...
10Interrupt Basics (contd.)
- Saving and Restoring the Context
- As the above code demonstrated, the task code has
no idea of the changes taking place in registers
like R1 or R2 in the ISR. - Hence if R1 is modified by the ISR, we might get
an incorrect final result. - Due to limited number of registers, the ISRs and
task codes usually have to work with same
registers. - To solve this problem it is common practice to
save the register contents onto the stack (saving
the context) and restoring them at the end of the
ISR (restoring the context). - This is mandatory to prevent any bugs from
appearing due to interrupts
11Interrupt Basics (contd.)
- Disabling Interrupts
- Most microprocessors allow programs to disable
interrupts. - In most cases the program can select which
interrupts to disable during critical operations
and which to keep enabled by writing
corresponding values into a special register. - Nonmaskable interrupts however cannot be disabled
and are normally used to indicate power failures
or other serious event. - Certain processors assign priorities to
interrupts, allowing programs to specify a
threshold priority so that only interrupts having
higher priorities than the threshold are enabled
and the ones below it are disabled.
12The Shared-Data Problem
- In many cases the ISRs need to communicate with
the task codes through shared variables. - Fig. 4.4 from Simon shows the classic shared-data
problem. - The code continuously monitors two temperatures
and sets off an alarm if they are different. - An ISR reads the temperatures from the hardware.
- The interrupt might be invoked through a timer or
through the temperature sensing hardware itself. - Now, consider that the temperatures are 70
degrees and an interrupt occurs after iTemp0
iTemperatures0 is executed. - The temperatures now become 75 degrees. Hence on
returning from ISR, iTemp1 will be assigned 75
and an alarm will be set off even though the
temperatures were the same. - Fig. 4.5 compares the values iTemperatures0 and
iTemperatures1 directly without making a local
copy. However as the assembly code for this code
in Fig. 4.6 shows if an interrupt occurs between
the two MOVES, this translates into the same
problem as Fig. 4.4 and the alarm goes off when
it shouldnt have.
13The Shared-Data Problem (contd.)
14The Shared-Data Problem (contd.)
- Characteristics of the Shared-Data Bug
- The problem in both the above figures (4.4 and
4.5) is due to the shared array iTemperatures. - These bugs are very difficult to find as they
occur only when the interrupt occurs in between
the first 2 MOVE instructions, other than which
code works perfectly. - Solving the Shared-Data problem
- The problem can be solved by disabling the
interrupts during the instructions that use the
shared variable and re-enabling them later. - The following modification to Fig. 4.4 (or 4.5)
solves the problem. - while (TRUE)
-
- disable() Disable interrupts
- iTemp0 iTemperatures0
- iTemp1 iTemperatures1
- enable() Re-enable interrupts
- ...
- Remaining code same as in Fig. 4.4
- ...
-
- In assembly DI and EI are used to disable and
enable interrupts respectively.
15- Atomic and Critical Section
- A part of a program is atomic if it cannot be
interrupted. Hence the code between disable() and
enable() above is atomic. - Atomic might also be used for codes that only
disable interrupts working with the same data.
Hence if interrupts not working with the
temperature variable (like pressure, time etc.)
are left enabled code is still considered atomic
and free of bugs. - Few Examples
- Consider an ISR that updates iHours, iMinutes and
iSeconds every second through a hardware timer
interrupt. The function to calculate the time can
be written as follows - long iSecondsSinceMidnight (void)
-
- long lReturnVal
- disable()
- lReturnVal
- (((iHours60) iMinutes) 60) iSeconds
- enable()
- return (lReturnVal)
-
- A problem in the above code arises when the
function is called from a critical section that
has disabled interrupts. The function will
incorrectly enable interrupts on return. Fig.
4.10 solves this problem.
16The Shared-Data Problem (contd.)
- The volatile Keyword
- Fig. 4.12 from Simon fixes the shared-data
problem without disabling interrupts by reading
the seconds twice.
17The Shared-Data Problem (contd.)
- However certain compilers will optimize the code.
It will read lSecondsToday in one or more
registers and instead of updating the value
before saving it to lReturn it will read the
value from the register every time instead of
from memory. - Some compilers might also remove the while-loop
during optimization causing the same bug as in
Figs 4.4 and 4.5. - This is prevented by declaring lSecondsToday as
volatile. This warns the C compiler that the
variable might change due to interrupts or other
routines and not to optimize code pertaining to
it. - static volatile long int lSecondsToday
18Interrupt Latency
- Interrupt latency is the amount of time taken to
respond to an interrupt. - This depends on several factors-
- Longest period during which the interrupt is
disabled - Time taken to execute ISRs of higher priority
interrupts - Time taken for the microprocessor to stop the
current execution, do the necessary bookkeeping
and start executing the ISR - Time taken for the ISR to save context and start
executing instructions that count as a response - The third factor is measured by knowing the
instruction execution times from the processor
manual, when instructions are not cached. - Make ISRs short
- Factors 4 and 2 are controlled by writing
efficient code that are not too long. - Factor 3 depends on hardware and is not under
software control.
19Interrupt Latency (contd.)
- Disabling Interrupts
- Disabling interrupts increases interrupt latency
so this period should be kept as short as
possible. - Consider the scenario in which two different task
codes disable interrupts for 125 and 250 µs resp. - In this case the max. time taken to start
executing an ISR will be 250 µs and not 125250
µs because ISR is started as soon as the
interrupts are re-enabled in either one of the
atomic sections. - Alternatives to Disabling Interrupts
- As disabling interrupts increases latency, some
alternatives might be required in certain
situations. - Fig. 4.15 (Simon) uses two array sets
iTemperaturesA and iTemperaturesB and a variable
(fTaskCodeUsingTempB) to ensure that the ISR
always works with the array not being used by the
task code. - However the while-loop might have to run twice if
the task checks the wrong array the first time.
20Interrupt Latency (contd.)
- Fig. 4.16 uses a queue. The ISR writes data at
the head of the queue and increments iHead by 2
while the task code starts reading from the tail
of the queue. - These alternatives are not robust and even minor
changes might introduce bugs and hence should
only be used as the last option.