Often there is a need to call subroutines from another subroutine. This is called nesting
and is illustrated in Figure 6.11. Nested subroutines provide increased efficiency and provide for better code reuse. Theoretically, nesting can go on for many levels, that is, a subroutine can call another, which can call another, which can call another, and so on. However, system constraints limit this practice. First, each time a subroutine is called, the two-byte return address is pushed onto the stack. If another subroutine is called before the first one is finished executing, then two additional bytes will be pushed onto the stack as the return address for this next routine. Each additional nested layer requires two more bytes for each subsequent return address. Eventually the system will run out of stack space by filling all available memory with return addresses.
1. What is the purpose of the compare to $0F in HEX2ASC?
2. Why is $07 added to the code if the hex value is not lower than $0A?
* Subroutine to convert the lower hex digit ($0-$F) * to ASCII equivalent. Return ASCII of digit in
0120 81 0f HEX2ASC CMPA #$0F ;Is digit > $F?
0122 22 08 BHI END ;Value not single hex digit 0124 81 0a CMPA #$0A ;Is digit alpha?
0126 25 02 BLO SKIP ;If NOT skip extra $07 0128 8b 07 ADDA #$07 ;Alpha digit needs $07 more 012A 8b 30 SKIP ADDA #$30 ;Finish ASCII conversion 012C 39 END RTS
of a byte in AccA AccA.
Figure 6.10 Source Code Listing of Hex-to-ASCII Conversion Subroutine
Instructions in main program Instructions in delay subroutine DELAY 1 3 5 6 4 2 JSR DELAY JSR DELAY JSR SLOWER RTS Instructions in slower subroutine RTS JSR DELAY B A
Figure 6.11 Example of a Nested Subroutine
150
Excessive nesting also leads to excessive overhead costs. The processor requires extra machine cycles to execute the JSR/BSR and RTS required each time a subroutine is called. In some cases, these extra machine cycles are more than the routine requires for execution. Therefore, moderation is the best practice. There needs to be a balance between the modular benefits of subroutines and the execution efficiencies of in-line code. The ideal balance is dependent on the particular system and its priorities on speed versus memory efficiency.
Time calculation lends itself well to this concept of subroutine nesting. For example, a one-minute delay subroutine could be implemented by calling a one-second delay subroutine 60 times. Furthermore, the one-second delay subroutine could call a 10 ms delay subroutine 100 times. This idea is illustrated in Example 6.5.
Example 6.5
Problem: Write a subroutine that will delay the processor for 1 second. Use the 10 ms delay subroutine from Example 6.2, but enhance the routine so that the registers are not changed when this delay is complete. Estimate as closely as possible with a single program loop the actual time of the 1 second delay.
Solution: The flowchart shown in Figure 6.12 illustrates the specific operations necessary to accomplish this task. The source code listing for this task is shown in Figure 6.13. This particular listing contains an additional column of information.
Delay 1 s
Save A register on stack
Load loop counter for 1 s Delay 10 ms (DLY10MS) Decrement Loop Counter Count = 0? Restore A register from Stack Return No Yes
Figure 6.12 Flowchart of 1-Second Delay Subroutine 151
Each instruction has the number of machine cycles required to execute shown in brackets. This information facilitates the calculation of the actual delay.
The DLY10MS subroutine utilizes delays 10 ms; it needs to be executed 100 times to accomplish the 1 second delay. The overhead associated with calling this routine, returning from the routine and initializing the loop are minor compared to the overall length. The code for the DLY10MS subroutine is shown again in the same listing for continuity.
Each time the DLY2 loop is executed in the DLY1S routine, the DLY10MS routine is executed. Thus, the DLY10MS routine is nested within the DLY1S routine.
The two subroutines DLY1S and DLY10MS are written so that the original contents of the registers are not affected. These routines use AccA and the X register as counters to accomplish the delay. At the start of each subroutine, the counter register is pushed onto the stack. This saves the original contents while the register is being used for another purpose. Before the subroutine is complete, the original register value is restored to the counter register via a pull instruction. The combination of the push and pull instructions inside each subroutine maintains a balanced use of the stack. One more example is presented to show the value of nested subroutines. In section 6.4, the process of converting a hex digit to the equivalent ASCII character code was presented. Since each byte stored in memory can contain two hex digits, the HEX2ASC subroutine could be called twice to convert each digit stored within a byte. Example 6.6 shows how a routine could be written to convert two hex digits from a single byte into two ASCII characters. It utilizes the HEX2ASC subroutine from section 6.4.
Example 6.6
Problem: Write a subroutine to convert a two-digit hex number to the two equivalent ASCII character codes. Utilize the HEX2ASC subroutine from section 6.4 to convert each digit separately and then store the results in two sequential bytes in memory. The address of the first byte is passed to this subroutine in the X register.
* Subroutine to delay 1 s by calling a 10 ms delay 100 times
0140 36 [ 3 ] DLY1S PSHA ;Save A temporarily on stack 0141 86 64 [ 2 ] LDAA #100 ;set loop counter
0143 bd 00 0b [ 6 ] DLY2 JSR DLY10MS ;Delay 10 ms
0146 4a [ 2 ] DECA ;decrement loop counter 0147 26 fa [ 3 ] BNE DLY2 ;If not zero do again 0149 32 [ 4 ] PULA ;restore value in A 014A 39 [ 5 ] RTS ;return
* Subroutine to delay 10 ms
014B 3c [ 4 ] DLY10MS PSHX ;Save X temporarily on stack 014C ce 0d 01 [ 3 ] LDX #$0D01 ;set loop counter
014F 09 [ 3 ] DLYLP DEX ;decrement loop counter 0150 26 fd [ 3 ] BNE DLYLP ;If not zero do again 0152 38 [ 5 ] PULX ;restore value in X 0153 39 [ 5 ] RTS ;return
Figure 6.13 Source Code Listing of Nested Subroutine Time Delay
152
Solution: Each byte of data contains two hex digits. The left digit will be converted first and stored at the memory location contained in the X register. The right digit will then be converted, and the ASCII equivalent will be stored at X + 1. The conversion subroutine (HEX2ASC) requires that the hex digit to be converted is located in the right position of the byte and the upper nibble is zero. Therefore, the upper hex digit must be moved to the lower digit before it can be converted. Figures 6.14 and 6.15 illustrate how this is done.
The original data is stored on the stack so that the upper digit can be shifted and converted. The upper digit is shifted to the lower digit position, and the HEX2ASC converter is called. This subroutine returns the ASCII equivalent that can be directly
Two-digit HEX to ASCII Temporarily save hex to stack Isolate upper digit Convert digit (HEX2ASC) Store ASCII char code at X Get original value from stack
Isolate lower digit Convert digit (HEX2ASC) Store ASCII char code at X+1 Return
Figure 6.14 Flowchart of Two-Digit Hex to ASCII Conversion
* Subroutine to convert two hex digits in A register
* to ASCII equivalent. Return the ASCII in memory pointed to by X.
0160 36 HH2AA PSHA ;Store A on stack
0161 44 LSRA ;Shift upper digit to lower 0162 44 LSRA
0163 44 LSRA 0164 44 LSRA
0165 bd 00 00 JSR HEX2ASC ;Convert digit to ASCII 0168 a7 00 STAA $00,X ;Store ASCII at X 016A 32 PULA ;Get original hex value 016B 84 0f ANDA #$0F ;Isolate lower digit 016D bd 00 13 JSR HEX2ASC ;Convert digit to ASCII 0170 a7 01 STAA $01,X
0172 39 RTS
Figure 6.15 Source Code Listing of Two-Digit Hex to ASCII Conversion 153
written to memory. The indexed mode is used to address memory since the effective address is located in the X register. This completes the conversion and storage of the upper digit.
Following the conversion of the first digit, the original data is retrieved from the stack so that the lower digit can be converted. The AND instruction is used to apply a logical mask to isolate the lower digit. Again, the HEX2ASC converter is called, which returns the ASCII equivalent character code of the hex digit. This ASCII code is then written to the memory location following the location of the first digit.