Q2Microcontroller and Embedded Systems
Question
Q.2. (a) Why are the PUSH and POP important instructions when servicing the number of interrupt sources by their interrupt service routines? [8]
(b) Differentiate between the following: (i) LCALL & ACALL (ii) SJMP & LJMP. [4]
(c) Find the time delay for the following subroutine, assuming a crystal frequency of 11.0592 MHz - DELAY: MOV R3,#250 ; HERE: NOP ; NOP ; NOP ; NOP ; DJNZ R3,HERE ; RET. [4]
Answer
When the 8051 CPU responds to an interrupt request, it automatically saves only the program counter (by pushing it onto the stack) before transferring control to the interrupt service routine (ISR), but it does not automatically save the contents of the accumulator, the PSW, or any other working registers that the interrupted main program may have been actively using at the moment the interrupt occurred. Since the ISR itself typically needs to use the accumulator, working registers, and possibly the PSW to perform its own processing, and since the main program's execution must resume correctly exactly where it left off once the ISR completes, it is essential that the ISR explicitly save (PUSH) the contents of any registers it intends to modify onto the stack immediately upon entry, and explicitly restore (POP) those same registers from the stack, in exactly the reverse order, immediately before returning control to the interrupted main program via the RETI instruction. Without this careful save-and-restore discipline using PUSH and POP, the ISR would silently corrupt whatever data the main program had stored in the accumulator, PSW, or working registers at the time of interruption, causing the main program to resume execution with incorrect register contents and almost certainly leading to erroneous program behavior; this concern becomes especially critical in a system servicing multiple interrupt sources, where several different ISRs, potentially even nested within one another if higher-priority interrupts are enabled to interrupt lower-priority ISRs already in progress, must each independently and correctly preserve and restore the CPU context, since any single ISR that fails to save and restore its used registers correctly can corrupt not only the original main program's state but potentially the state of another, lower-priority ISR that was itself interrupted, making disciplined use of PUSH and POP (and correspondingly careful ordering of pushes and pops to properly nest within the stack's inherent last-in-first-out structure) an essential correctness requirement rather than merely a stylistic best practice in any interrupt-driven embedded application.
LCALL versus ACALL
LCALL (long call) is a 3-byte instruction that can transfer program execution to any subroutine address within the full 64-kilobyte program memory address space, since the instruction encodes the complete 16-bit destination address directly within its two address bytes; because the full absolute address is always specified, LCALL can call a subroutine located anywhere in program memory, but at the cost of using an extra program memory byte and, generally, one additional machine cycle compared to ACALL. ACALL (absolute call) is a 2-byte instruction that can only call a subroutine located within the same 2-kilobyte page of program memory as the instruction immediately following the ACALL instruction itself, since it encodes only an 11-bit page-relative address rather than the full 16-bit absolute address; this restriction makes ACALL more compact (using one less program memory byte) but requires the programmer (or, more commonly, the assembler) to ensure that the target subroutine actually falls within the same 2-kilobyte page, making ACALL less flexible but more memory-efficient than LCALL for calls to nearby subroutines.
SJMP versus LJMP
SJMP (short jump) is a 2-byte instruction that specifies its destination as a signed 8-bit relative offset added to the program counter value following the SJMP instruction, allowing a jump to any address within the range of 128 bytes backward to 127 bytes forward relative to the instruction's own location; this makes SJMP compact and, importantly, inherently position-independent (relocatable), since the actual target address is computed relative to the current program counter rather than as a fixed absolute address. LJMP (long jump) is a 3-byte instruction that specifies the complete 16-bit absolute destination address directly, allowing an unconditional jump to any location within the full 64-kilobyte program memory address space, but using one additional program memory byte and offering no relocatability benefit, since the destination is a fixed absolute address rather than a relative offset.
Time Delay Calculation
In the standard 8051 architecture, one machine cycle consists of 12 oscillator (crystal) clock periods, so with a crystal frequency of 11.0592 MHz, the duration of one machine cycle is 12/11.0592e6 = 1.0851 microseconds approximately.
The given subroutine consists of an initial MOV R3,#250 instruction (1 machine cycle), followed by a loop labeled HERE consisting of four NOP instructions (1 machine cycle each) and a DJNZ R3,HERE instruction (2 machine cycles), with this loop body repeated 250 times as R3 is decremented from 250 down to 0, followed finally by a RET instruction (2 machine cycles) once the loop exits.
The total number of machine cycles consumed by the entire subroutine is therefore the initial MOV (1 cycle) plus 250 repetitions of the 6-cycle loop body, plus the final RET (2 cycles): total cycles = 1 + 250*6 + 2 = 1 + 1500 + 2 = 1503 machine cycles.
Multiplying the total cycle count by the machine cycle duration gives the total time delay: total time = 1503 * 1.0851 microseconds = 1630.86 microseconds approximately, or about 1.631 milliseconds.
This calculation illustrates the standard cycle-counting methodology used to determine software-generated time delays in 8051 assembly programs, in which every instruction in the delay loop is individually assigned its documented machine-cycle count (1 cycle for MOV and NOP, 2 cycles for DJNZ and RET in the standard 8051 instruction timing table), the per-iteration loop cost is multiplied by the number of loop iterations, and any cycles executed only once (outside the repeated loop body) are added separately, giving the total elapsed execution time when multiplied by the fixed machine cycle duration set by the crystal frequency.
It is also worth noting that this careful register-preservation discipline extends naturally to the broader concept of reentrancy in interrupt-driven firmware design: an ISR that saves and restores every register it modifies, and that avoids relying on any global state that could be left in an inconsistent condition if the ISR is itself interrupted partway through by a higher-priority interrupt, is said to be reentrant, meaning it can be safely entered again (nested) before a previous invocation has completed, a property that becomes essential in systems using nested interrupt priorities of the kind supported by the 8051's two-level (high/low) priority scheme, since without disciplined PUSH and POP usage together with careful avoidance of unprotected shared global variables, a nested interrupt scenario could otherwise silently corrupt data shared between the outer and inner ISR invocations.