Файл: Introduction to Microcontrollers. Architecture, Programming, and Interfacing of the Motorola 68HC12 (G.J. Lipovski, 1999).pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 14.06.2025

Просмотров: 1423

Скачиваний: 0

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

6.1 Local Variables

141

Figure 63. Changing a Global Variable before It Has Been Completely Used

illustrates a program segment using TEMP to store a variable to be recalled later. Before that value is recalled, however, TEMP has been changed by subroutine B, which is called by subroutine A, which itself is called by the program segment. This case is difficult to debug because each subroutine will work correctly when tested individually but will not work when one is called, either directly or indirectly through other subroutines, from within the other. This technique also confuses documentation, specifically the meaning of the local variable TEMP, generally making the program less clear.

With the other technique, the local variables will be put in different memory locations, having different symbolic names. See Figure 6.5. This approach is superior to the last approach, because differently named local variables, stored in different locations, will not interfere with the data stored in other locations. The names can be chosen to denote their meaning, reducing the need for comments. However, memory is taken up by these local variables of various program segments, even though they are hardly ever used. In a single-chip 'A4 or 'B32, only IK bytes of SRAM are available. Using all these bytes for rarely used local variables leaves less room for the program's truly global data.

TEMP: D S

6

; Allocate 6 bytes of memory for temporary variables

enter: MOVB#1, TEMP

; Allocate and initialize V( 1)

MOVE

#2,TEMP+1

; Allocate and initialize V(2)

MOVE

#3,TEMP+2

; Allocate and initialize W(l)

MOVE

#4,TEMP+3

; Allocate and initialize W(2)

LDAA

TEMP

; V(l) into A

LDAB

TEMP+2

; W(l) into B

MUL

; The value of first term is now in D

STD

TEMP+4

; Store first term in TERM

LDAA

TEMP+1

; V(2) into A

LDAB

TEMP+3

; W(2) into B

MUL

; Calculate second term

ADDD

TEMP+4

; Add in TERM; dot product is now in D

Figure 6.4. Inner Product Utilizinga Global Variable such as TEMP (a Bad Example)


144

Chapter 6 Assembly Language Subroutines

Figure 6.8. Nested Subroutines Using Local Variables Stored on the Stack

Let's now look at our dot product example in Figure 6.7, where we will initialize the copies of V(l), V(2), W(l), and W(2) to have values 1, 2, 3 and 4, respectively. The first term of the dot product shown in formula (1), which will also be placed on the stack, will be denoted TERM. Notice how the simple rule for balancing the stack is used in this segment. If the stack pointer were changed in the interior of the segment, offsets for local variables would change, making it difficult to keep track of them. As it is now, we have to determine the offsets from the stack pointer for each local variable. The local variable TERM occupies the top two bytes, the local variables V(l) and V(2) occupy the next two bytes, and the local variables W(l) and W(2) occupy the next two bytes.

Figure 6.8 illustrates how the use of the stack avoids the aforementioned problem with global variables. Because the stack pointer is moved to allocate room for local variables, the temporary variables for the outermost program are stored in memory locations different from those that store local variables of an inner subroutine like B.

The advantage of using the stack can be seen when two subroutines are called one after another, as illustrated in Figure 6.9. The first subroutine moves the stack pointer to allocate room for the local variable, and the local variable is stored with an offset of 0 in that room. Upon completion of this subroutine,the stack pointer is restored, deallocating stacked local variables. The second subroutine moves the stack pointer to allocate room for its local variable, and the local variable is stored with an offset of 0 in that room. Upon completion of this subroutine, the stack pointer is restored, deallocating stacked local variables. Note that the same physical memory words are used for local variables in the first subroutine that was called, as are used for local variables in the second subroutine that was called. However, if the second subroutine were called from within the first subroutine, as in Figure 6.8, the stack pointer would have been moved, so that the second subroutine would not erase the data used by the first subroutine. Using the stack for local variables conserves SRAM utilization and prevents accidental erasure of local variables.

Figure 6.9. Local Variables Stored on the Stack, for Successive Subroutines

146

Chapter 6 Assembly Language Subroutines

TERM:

EQU

0

VI:

EQU

TERM+2

V2:

EQU

Vl +1

Wl:

EQU

V2 + 1

W2:

EQU

Wl + 1

MBYTES: EQU

W2 + 1

Figure 6.11. Defining Symbolic Names for Stacked Local Variables by Sizes

Another technique, shown in Figure 6.12, uses the DS directive to play a trick on the assembler. The technique uses the DS directive to bind the stacked local variables partially with the stack pointer SP, using the location counter and the ORG directive to modify the location counter. Recall that ALPHA DS 2 will normally allocate two bytes for the variable ALPHA. The location counter is used to bind addresses to labels like ALPHA as the assembler generates machine code. The location counter and hence the address bound to the label ALPHA correspond to the memory location where the word associated with the label ALPHA is to be put. A DS statement, with a label, binds the current value of the location counter to the label (as the name of the container, not the contents) and adds the number in the DS statement to the location counter. This will bind a higher address to the next label, allocating the desired number of words to the label of the current directive. Note that ALPHA EQU * will bind the current location counter to the label ALPHA but not affect the location counter. Also, recall that the ORG directive can set the location counter to any value. These can be used as shown in Figure 6.12.

You can reset the location counter to zero many times, and you should do this before each group of DS directives that are used to define local storage for each program segment. These DS statements should appear first in your program segment. Each set should be preceded by a directive such as LCSAVE DS 0 to save the location counter using LCSAVE and an ORG 0 directive to set the location counter to 0; and each set

should be followed by a directive

such as ORG LCSAVE to set the origin back to the

saved value to begin generating

machine code for your program segment. The last

directive in Figure 6.12, ORG LCSAVE, can be replaced by DS LCSAVE-*, which

avoids the use of the ORG statement. The DS directive adds its operand LCSAVE-* to

the location counter, so this directive loads LCSAVE into the location counter.

LCSAVE : EQU *

; Save current location counter

ORG

0

; Set the location counter to zero

TERM:

DS

2

; First term of dot product

VI:

DS

1

; Copy of input vector element V(l)

V2 :

DS

1

; Copy of input vector element V(2)

W1:

D S

1

; Copy of input vector element W(1)

W2:

D S

1

; Copy of input vector element W(2)

N B Y T E S : E Q U *

; Number of bytes of local variables

ORG

LCSAVE ; Restore location counter

Figure 6.12. Declaring Symbolic Names for Local Variables Using DS Directives


148

Chapter 6 Assembly Language Subroutines

was used for the local variables of the outer program segment. It is always in a known position on the stack (in this case, on the very top of the stack), so it is easy to find. See Figure 6.14, where the inner program segment can access the local variables of the outer segment by loading the stack marker into any index register and using index addressing to get the variable. Note that the stack marker is deallocated together with the other stacked local variables at the end of the program segment.

MARKA:

EQU

0

; Stack mark for segment A

W:

EQU

2

; Input vector

WW:

EQU

4

; Input vector

SlZEAi

EQU

6

*

STARTA:

TFR

SP,X

; Start for segment A

LEAS

-SIZEA,SP

STX

MARKA,SP

MOVW

#$102,W,SP

; Initialize both bytes of VV

*

MOW

#$304,WW,SP

; Initialize both bytes of WW

MARKB : EQU

0

; Stack mark for segment B

TERM:

EQU

2

SIZEB:

EQU

4

*

STARTS:

TFR

SP,X

LEAS

-SIZEB,SP

STX

MARKB,SP

LDAA

VV,X

;V(l)intoA

LDAB

WW,X

;W(l)intoB

MUL

; First term is now in D

STD

TERM, SP

; Store first term in TERM

LDAA

W+1,X

;V(2)intoA

LDAB

WW+1,X

;W(2)intoB

MUL

; Calculate second term

ADDD

TERM,SP

; Add in TERM; dot product in D

ENDB:

LEAS

SIZEB ,SP

; End of segment B

*

ENDA:

LEAS

SIZEA,SP

; End of segment A

Figure 6.14. Accessing Stacked Local Variables Using a Stack Marker

Either the extended local access or the stack marker access mechanisms can be used in cases where program segments are further nested. Consider program segment C, with SIZEC stacked local variables, which is nested in segment B and needs to load accumulator A with the value of SA, a stacked local variable of segment A. Using extended local access, as in the first example, the following instruction will accomplish the access.

LDAA SIZEC+SIZEB+SA,SP


150

Chapter 6 Assembly Language Subroutines

Before we begin, however, we reiterate that these techniques are quite similar to those used in Section 6.1 to store local variables. However, these techniques are used between subroutines,while the latter were used entirely within asubroutine.

* SUBROUTINE DOT PRODUCT

DOTPRD: MUL

; First term is now in D

EXG

D, Y

; Store first term in Y, get W(2) in B

EXG

A,X

;V(2)intoA

MUL

; Calculate second term

LEAY

D, Y

; Add terms, to get result in Y

RTS

; Return to the calling program

a. A subroutine

LDAA

# 2

; Copy of V( 1) into A

LDX

# 7

; Copy of V(2) into low byte of X

LDAB

# 6

; Copy of W(1) into B

LDY

# 3

; Copy of W(2) into low byte of Y

BSR

DOTPRD

; Call the subroutine

STY

DTPD

; Store dot product in DTPD

b. A calling sequence

LDAA

LV, SP

; Copy of V(l) into A

LDX

LW-1, S P ; Copy of V(2) into low byte of X

LDAB

LV+1,SP

; Copy of W(l) into B

LDY

LW, S P

; Copy of W(2) into low byte of Y

BSR

DOTPRD

; Call the subroutine

STY

DTPD

; Store dot product in DTPD

c. Another calling sequence

Figure 6.15. A Subroutinewith Parameters in Registers

In this section we examine six methods used to pass parameters to a subroutine. We illustrate each method with the dot product from Section 6.1. We first consider the simplest method, which is to pass parameters in registers as we did in our earlier examples. Then the passing of parameters by global variables is discussed and discouraged. We then consider passing parameters on the stack and after the call, which are the most common methods used by high-level languages. We then discuss the technique of passing parameters using a table, which is widely used in operating system subroutines.


152

Chapter 6 Assembly Language Subroutines

Figure 6.16. Change a Global Parameter before Its Subroutine Has Used It

*SUBROUTINE DOTPD - LOCAL VARIABLES

TERM:

EQU

0

; First term of the dot product

MBYTES:

EQU

2

*

DOTPRD:

LEAS

-MBYTES, SP

; Allocate local variables

LDAA

V1

; First component of V into A

LDAB

W1

; First component of W into B

MUL

; First term of dot product into D

STD

TERM, SP

; Save first term

LDAA

V2

; Second component of V into A

LDAB

W2

; Second component of W into B

MUL

; Second term of dot product into D

ADDD

2,SP+

; Dot product into D, Deallocate loc var

STD

DTPD

; Place dot product

RTS

Figure 6.17. A Subroutine with Parameters in Global Variables

In assembly language, global variables are defined through a DS directive that is usually written at the beginning of the program. These variables are often stored on page zero on smaller microcontrollers so that direct page addressing may be used to access them. However in the 6812, page zero is used for I/O ports. Assuming that the directives are written somewhere in the program, the subroutine in Figure 6.17 does the previous calculation, passing the parameters through these locations. Note that we use local variables in this subroutine, as discussed in Section 6.1.

The subroutine in Figure 6.17 uses global variables VI, V2, Wl, W2, and DTPD to pass parameters to the subroutine and from it. If the calling routine wants to compute the dot product of its local variables LV and LW, which each store a pair of 2-element 1-byte vectors, putting the result in LDP, the calling sequence in Figure 6.18 could be used. Notice that the calling routine's local variables are copied into global variables VI, V2, Wl, and W2 before execution and copied out of the global variable DTPD after execution. Any other calling sequence for this version of DOTPRD must also copy the vectors of