One of the SITCOM training computer prototypes
 SITCOM 85 Training Computer
 
 

Dynamic subroutines

There's nothing mysterious about dynamic subroutines. Basically they are quite the same as the subroutines we saw on the previous page. The only difference is that we can change their behaviour by sending some parameters to them.

Wouldn't it be nice if we had a delay routine that could be used for several purposes, with varying delay times? That is easy enough, we simply pass a parameter to the subroutine instructing it how long it should delay.

Here's a program that does that:

 
   

;------------------------------------------------------------------------
;
; LESSON3C.ASM
;
; A Kitt scanner, with a dynamic subroutine
;
;------------------------------------------------------------------------

            .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

            MVI   B,25             Delay 25 * 5 ms
            CALL  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

            MVI   B,25             Delay 25 * 5 ms
            CALL  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 B * 5 ms
;  No registers are affected
;------------------------------------------------------------------------

DELAY       PUSH  PSW              Save affected registers to the stack
            PUSH  B                  (AF and BC)

DELAY1      MVI   C,250            Load inner loop counter

DELAY2      NOP                    Begin of inner loop
            NOP
            NOP
            NOP
            NOP
            JMP   $+3
            NOP
            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

            POP   B                Restore affected registers from the
            POP   PSW               stack (in reverse order!)
            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

            RET                    Return to calling program
 
 

What I've done in this program is to load the B register with the desired delay time (measured in 5 ms intervals) prior to calling the DELAY routine. This way you can use the same DELAY routine for a variety of delay times, from 5 ms until more than on second.

This dynamic subroutine only requires one parameter, others may need two, 4, or even more. It is fine for subroutines with a limited number of parameters pass them using the internal registers of the 8085.
But what if you need too many, or bigger parameters, that won't fit in the registers anymore? For instance you want to pass a whole string to a subroutine? Then you'll have to use other techniques. For instance pass parameters by reference, telling the subroutine where to find the actual parameter in memory. Or pass parameters using the stack. This last approach is often used by higher level languages, like BASIC or C. Passing parameters over the stack is a bit beyond the scope of this lesson, but maybe we can do that in one of the future lessons.

I have taken the liberty to make some minor changes to the DELAY routine itself, mainly to demonstrate the strength of the stack.
The first thing the program does when it enters the subroutine is to store the affected registers. There are 3 affected registers in this routine, which are B and C, and the Flag register. The first 2 were obvious, but did you expect changes to the Flag register? At the end of the subroutine the Z flag would be set due to the last DCR instruction!
So if we want our subroutine to be transparent to our main program, which means that it does not alter any internal registers, we have to save the 3 affected registers before we begin. And what better place is there than the stack! Two PUSH instructions are used to save the PSW (Accu + Flag register) and the BC registers to the stack. Remember that the stack also holds the return address of our subroutine, so it holds at least 6 bytes now! Fortunately we don't have to worry about that, because the stack is a completely automated feature of the processor.
At the end of the subroutine the two affected register pairs are restored again by pulling the previous values from the stack. Please note that the pulling order is the reverse of the pushing order, because the last value pushed must be the first value to be pulled from the stack. Finally the return address is pulled from the stack by the RET instruction. In fact the RET instruction is nothing more than a POP PC function, which does not exist by the way.

If you look a bit closer you see that I've changed the inner loop of the delay routine. This has nothing to do with making the subroutine dynamic, I only wanted to double the inner loop to approximately 5 ms. Doubling the time required me to add 30 clock pulses to the inner loop. This could not be done by adding just NOP instructions, because a NOP takes 4 clock pulses to execute. That is why I used 5 extra NOPs, totaling to 20 clock pulses, followed by a JMP $+3 instruction which takes another 10 clock pulses.
JMP $+3 is another way of telling the processor to jump 3 bytes further than the first byte of this instruction. A JMP instruction is always 3 bytes long, which causes JMP $+3 to jump to the next instruction. This results in a JMP instruction that does nothing but consume 10 clock pulses.

 
 

A new INIT program

Our include program INIT01.ASM has lasted a couple of experiments. Do you still remember that it is still there, and absolutely essential! If it weren't there we would have to initialize the assembler, the I/O constants and the 8255 in each and every experiment over and over again. INIT01.ASM does that all for us, without us worrying about it.
I think the INIT01.ASM program has served us well enough, and is entitled for early retirement. That's because I'm going to write a new INIT program, appropriately called INIT02.ASM this time.

 
   

;------------------------------------------------------------------------
;
; INIT02.ASM
;
; Initialise experiments with the basic I/O board and define DELAY
;
;------------------------------------------------------------------------

            .CR   8085            The processor we're using
            .CP   1,9600,N,8,1    SITCOM is connected to COM1
            .TF   COM1:,INT       Send generated code to COM1

;------------------------------------------------------------------------
;
; Constants declarations
;
;------------------------------------------------------------------------

PORTA       .EQ   000H            Port A on the 8255
PORTB       .EQ   001H            Port B on the 8255
PORTC       .EQ   002H            Port C on the 8255
CONTROL     .EQ   003H            Control register of the 8255

;------------------------------------------------------------------------
;
; Initialize the 8255
;
;------------------------------------------------------------------------

RESET      JMP   START            Skip the subroutine definitions

;------------------------------------------------------------------------
;  Delay B * 5 ms
;  No registers are affected
;------------------------------------------------------------------------

DELAY       PUSH  PSW              Save affected registers to the stack
            PUSH  B                  (AF and BC)

DELAY1      MVI   C,250            Load inner loop counter

DELAY2      NOP                    Begin of inner loop
            NOP
            NOP
            NOP
            NOP
            JMP   $+3
            NOP
            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

            POP   B                Restore affected registers from the
            POP   PSW               stack (in reverse order!)
            RET                    Return to calling program

;------------------------------------------------------------------------
;  Here's the new start of the program
;------------------------------------------------------------------------

START       MVI   A,10001001B      Select outputs for Ports A and B
            OUT   CONTROL          of the 8255, Port C are inputs
            LXI   SP,00000H        Initialize the stack
 
 

Basically INIT02.ASM is quite similar to INIT01.ASM. The assembler is initialized, the I/O addresses are defined and the 8255 is initialized.
Wait a minute! The label START has moved further away, and does not start at address 00000H any more. Address 00000H now contains a JMP START instruction, which skips the definition of the subroutine. We have to do that, otherwise the program would start executing the delay subroutine first. And when it finally encounters the RET instruction it will pull some random value from a not initialized stack and continue execution just somewhere in memory. There is a 65536:1 chance that that will be the intended start address.
That is why we have to skip all subroutine definitions this way.

I have also added a line to initialize the stack, as we will be needing that in almost every program that follows.
But remember: If you are writing a program of your own, don't forget the initialize the stack too! Having automated it in our next examples would make you forget easily that initializing the stack is very important!

Our DELAY subroutine is now constantly assembled in all subsequent lessons, ready for us to use it with any delay time we desire from 5 ms, up until 1.275 seconds.

 
 

Binary counting demo

As a bonus, to test our new INIT02.ASM prgoram, I add a small program that counts from 0 up to 65535 in binary.
We are going to explain number conversions in one of our next lessons, so don't worry if you don't fully understand binary counting yet.

 
   

;------------------------------------------------------------------------
;
; LESSON3D.ASM
;
; Binary counting demo
;
;------------------------------------------------------------------------

            .IN   INIT02           Initialize the assembler

;------------------------------------------------------------------------

            LXI   D,00000H         Initialize counting value

BINCOUNT    MOV   A,D              Get Most significant byte
            CMA                    Invert all bits
            OUT   PORTB            Before outputting it to port B
            MOV   A,E              Do the same for the Least significant
            CMA                     byte
            OUT   PORTA

            MVI   B,50             Delay 50 * 5 ms
            CALL  DELAY			

            INX   D                Increment the 16-bit register BC
            JMP   BINCOUNT         Always jump back to BINCOUNT
 
 

First of all, be careful to use INIT02 this time as init file, otherwise you won't have the necessary DELAY routine.

Short program, isn't it? It starts by initializing a 16-bit register DE.
Then we already enter the main program loop. There both bytes are sent to the two output ports of the 8255, but not before their contents are inverted. Remember that a LED will only light if it receives a 0!
After sending the bytes to the output it is time for a break, so we call the DELAY routine.
Finally we have to increment the 16-bit register, effectively adding 1 to the previous value. That is done with the new instruction INX D, which is a 16-bit version of the INR instruction.

Feel free to change the delay time if you don't like the counting speed. Or you can make the program count backwards, by replacing the INX D instruction by a DCX D instruction.

Experiment tips:
Change the delay time to 5 ms, by loading B with 1 before DELAY is called. Then observe how fast it counts now! You don't even see the two least significant LEDs blink!
Try to think of what will happen if you load B with 0 before you start DELAY! Then try it out, see if it happens what you anticipated. If it behaves different from what you expected, try to reason why it works the way it does!
You may also skip the DELAY routine all together, and see how fast it can count at top speed.

 
  Continue To Lesson 4 - Selective output
 
  [Home] [Latest News] [Essentials] [Hardware] [The Build] [Programs] [Projects] [Downloads]
 
  Made in the UK