Lesson 3
Our last program has grown to almost 100 bytes already.
Which is no problem for us, as we have more than 32000 bytes to our disposal.
But there is some room for improvement to our last programs.
So far the brief description, and I bet that it sounds quite confusing if you have never seen it work before. That's why we are going to explain the process in more detail now. |
The stackThe stack is a dedicated piece of memory which can store temporary values, like return addresses or register contents. You may think of the stack as being a pile of clean, wet plates while doing the dishes together. One person cleans the dishes and puts them on top of a pile, the other person takes them from the top of the pile to wipe them dry. So the last plate that's put up the pile is taken off first, until all plates are gone.
How do we know where the stack is?
The current location of the stack is pointed to by the stack pointer SP, a 16-bit register in the 8085.
Being a 16-bit register means that the stack can be located anywhere in RAM (Read/write) memory.
First the stack pointer is decremented (it was pointing to the top of the stack, to the last byte pushed). Then the most significant byte of the byte pair to be saved is placed in the memory location pointed to by the current value of the SP register. Then SP is decremented once more, pointing to the next free memory location. Finally the least significant byte of the byte pair is stored at that location. Pulling words from the stack is done in the reverse order:
Please note that after one push and one pull operation, the stack is exactly in its previous situation.
Why do we need a stack to perform subroutine calls?
Isn't it enough to simply save the PC somewhere in memory, and restore it from that memory once the subroutine is done?
But before we can use the stack, and thus before we can use subroutines, we have to initialize it. Initializing the stack means no more than setting the SP register to point to a free part of memory. In our examples we will place the stack at the top of memory, so it can grow down from 0FFFFH. This gives plenty of stack space, without having to worry that it gets in our way some time. |
LXI SP,00000H Initialize the stack pointer |
Placing this single program line at the beginning of our program will do the trick. It may sound strange that we have to initialize the stack pointer to 00000H, if we want the stack to grow down from 0FFFFH. Remember that the stack pointer SP is decremented before the first value is stored in the stack. Decrementing a register that contains 00000H, will result in a wrap around to 0FFFFH, which is just what we wanted. |
Subroutines
As we've seen in the last program of lesson 2, there were two identical delay loops.
These are ideal to be replaced by a subroutine.
Subroutines work quite similarly.
Only this time the subroutine code itself is placed outside the main program, either before or after it.
In our example we place the subroutine after the main program.
|
;------------------------------------------------------------------------ ; ; LESSON3A.ASM ; ; A Kitt scanner, with subroutines ; ;------------------------------------------------------------------------ .IN INIT01 Initialize the assembler ;------------------------------------------------------------------------ LXI SP,00000H Initialize stack pointer MVI D,00011111B Initialize pattern MVI E,11111111B MOV H,E MOV L,E WALKR MOV A,E Output the bit pattern to port A OUT PORTA MOV A,D Output the bit pattern to port B OUT PORTB CALL DELAY Slow the processor down a bit MOV A,L See if we have to change direction ANI 01000000B JZ GOLEFT We must if b6 is a 0! GORIGHT STC Set the carry MOV A,H Rotate entire pattern, including RAR the extra carry byte MOV H,A MOV A,D RAR MOV D,A MOV A,E RAR MOV E,A MOV A,L RAR MOV L,A JMP WALKR Always jump back to WALKR ;------------------------------------------------------------------------ WALKL MOV A,E Output the bit pattern to port A OUT PORTA MOV A,D Output the bit pattern to port B OUT PORTB CALL DELAY Slow the processor down a bit MOV A,H See if we have to change direction ANI 000000010B JZ GORIGHT We must if b1 is a 0! GOLEFT STC Set the carry MOV A,L Rotate entire pattern, including RAL the extra carry byte MOV L,A MOV A,E RAL MOV E,A MOV A,D RAL MOV D,A MOV A,H RAL MOV H,A JMP WALKL Always jump back to WALKL ;------------------------------------------------------------------------ DELAY MVI B,50 Load outer loop counter DELAY1 MVI C,250 Load inner loop counter DELAY2 NOP Begin of inner loop NOP NOP NOP DCR C Decrement inner loop counter JNZ DELAY2 Repeat until loop counter = 0 DCR B Decrement outer loop counter JNZ DELAY1 Repeat until loop counter = 0 RET Return to calling program |
We have seen most of this program before.
The first change is immediately in the first line of code, where the stack pointer is initialized.
Easy enough, isn't it?!
This program is 92 bytes long, as opposed to the 98 bytes of the original program.
|
Nesting subroutines
If we look a bit closer at this last program we see another small portion of code appearing twice.
It's the part that copies the bit patterns to the output ports.
These can also be put inside a subroutine.
|
;------------------------------------------------------------------------ ; ; LESSON3B.ASM ; ; A Kitt scanner, with nested subroutines ; ;------------------------------------------------------------------------ .IN INIT01 Initialize the assembler ;------------------------------------------------------------------------ LXI SP,00000H Initialize stack pointer MVI D,00011111B Initialize pattern MVI E,11111111B MOV H,E MOV L,E WALKR CALL OUTPUT Output both bit patterns and delay MOV A,L See if we have to change direction ANI 01000000B JZ GOLEFT We must if b6 is a 0! GORIGHT STC Set the carry MOV A,H Rotate entire pattern, including RAR the extra carry byte MOV H,A MOV A,D RAR MOV D,A MOV A,E RAR MOV E,A MOV A,L RAR MOV L,A JMP WALKR Always jump back to WALKR ;------------------------------------------------------------------------ WALKL CALL OUTPUT Output both bit patterns and delay MOV A,H See if we have to change direction ANI 000000010B JZ GORIGHT We must if b1 is a 0! GOLEFT STC Set the carry MOV A,L Rotate entire pattern, including RAL the extra carry byte MOV L,A MOV A,E RAL MOV E,A MOV A,D RAL MOV D,A MOV A,H RAL MOV H,A JMP WALKL Always jump back to WALKL ;------------------------------------------------------------------------ DELAY MVI B,50 Load outer loop counter DELAY1 MVI C,250 Load inner loop counter DELAY2 NOP Begin of inner loop NOP NOP NOP DCR C Decrement inner loop counter JNZ DELAY2 Repeat until loop counter = 0 DCR B Decrement outer loop counter JNZ DELAY1 Repeat until loop counter = 0 RET Return to calling program ;------------------------------------------------------------------------ OUTPUT MOV A,E Output the bit pattern to port A OUT PORTA MOV A,D Output the bit pattern to port B OUT PORTB CALL DELAY Slow the processor down a bit RET Return to calling program |
Our program is now only 90 bytes long!
This program demonstrates the nesting of subroutines.
Here we can see the power of the stack.
Without any problem the processor remembers its way back, even if we have altered the program counter twice!
Nesting subroutines is a very common practice in assembly programming, and we are likely to see many more examples of it in the upcoming lessons and projects. |
Continue With Lesson 3 - Creating a dynamic delay subroutine |
[Home] [Latest News] [Essentials] [Hardware] [The Build] [Programs] [Projects] [Downloads] | |||