1
TIMING SUBROUTINES
Subroutines are used by call programs in what is known as a transparent manner –
that is, the calling program can use the subroutines without being bothered by the details of
what is actually going on in the subroutine. Usually the call program preloads certain
locations with data, calls the subroutine, then gets the results back in the preload locations.
The subroutine must take great care to save the values of all memory locations in the
system that the subroutine used to perform internal functions and restore these values before
returning to the call program. Failure to the save the values results in occasional bugs in the
main program. The main program assumes that everything is the same both before and after a
subroutine is called.
Finally good documentation is essential so that the user of the subroutine knows
precisely how to use it.
Time Delays
Perhaps the most - used subroutine is one that generates a programmable time delay.
Time delays may be done by using software loops that essentially do nothing for some
period, or by using hardware timers that count internal clock pulses.
The hardware timers may be operated in either a software or a hardware mode. In the
software mode, the program inspects the timer Overflow flag and jumps when it is set. The
hardware mode uses the interrupt structure of the 8051 to generate an interrupt to the program
when the timer overflows.
The interrupt method is preferred whenever processor time is scarce. The interrupt
mode allows the processor to continue to execute useful code while the time delay is taking
place. Both the pure software and the timer – software modes tie up the processor while the
delay is taking place.
If the interrupt mode is used then the program must have an interrupt handling routine
at the dedicated interrupt program vector location specified. The program must also have
programmed the various interrupt control registers. The degree of nontransparency generally
means that interrupt driven subroutines are normally written by the user as needed and not
used from a purchased library of subroutines.
Pure Software Time Delay
The subroutine named softime generates delays ranging from 1 to 65,535 milliseconds
by using register R7 to generate the basic 1 milliseconds delay. The call program loads the
desired delay into registers A (LSB) and B (MSB) before calling soft time.
The key to writing this program is to calculate the exact time each instruction will
take at the clock frequency in use. For a crystal of 12 megahertz, each machine cycle (12
clock pulses) is 1 microsecond. Should the crystal frequency be changed, the subroutine
would have to have the internal timing loop number “delay” changed.
Softime
Softime will delay the number of milliseconds expressed by the binary number, from
1 to 65, 535d, found in registers A (LSB) and B (MSB). The call program loads the desired
delay into registers A and B and calls softime. Loading 0s into A and B results in an
immediate return.
Comments in the program show how the basic delay loop takes 1,000 1-microsecond
cycles to complete, or a delay of 1 millisecond. Additional instructions that test for a
2
rollovers at FFh, and A counting down to 0, add an additional 4 microseconds for a total
“normal” delay of 1,004 microseconds each time A is decremented . When A does rollover at
FFh, then B must be decremented, and when A is 0B must be tested for 0 also. There are B
decrements at rollovers (1 cycle) and B +1 tests (2 cycles) for B equal to 0, which add 3B + 2
cycles to the total delay.
Any routine must have additional instructions, or overhead, for entering and leaving
the routine that add to the total time delay of the loop. Over head time includes saving and
restoring registers, testing for A and B set to 00h, and returning to the calling program. The
total delay is then microseconds. The error due to the extra
instructions included in the software timing loop is as follows
Error =
Error =
% Error =
The worst case error occurs when the desired delay is 1 milliseconds (A = 01h, B =
00h), or an error of 1.9%. The error for a desired delay of 256 milliseconds (A = 00h, B =
01h) is .41%, and the error for a desired of 65,535 milliseconds (A = B = FFh) is .40%. Note
that any real crystal is rarely exactly 12 MHz, so additional errors will be introduced by the
crystal tolerance.
The reader may wish to “fix up” softime by deleting some of the NOPs in the basic
loop and changing the definition of delay to bring the total delay closer to that passed to the
routine.
Comment
Note that register A, when used in a defined mnemonic, is
used as “A”. When used as a direct address in a mnemonic
(where any add could be used), the equate name ACC is
used. The equate usage is also seen for R7, where the
name of the register may be used in those mnemonics for
which it is specifically defined. For mnemonics that use
any add, the actual address must be used.
The restriction on A = B = 00 is due to the fact that the
program would initially count A from 00….FFh…00 then
exit. If it were desired to be able to use this initial
condition for A and B, then an all = zero condition could
be handled by the test for 0000used, set a flag for the
condition, decremented B from 00 to FFh the first time B
is decremented , then reset the flag for the remainder of
the program.
Address Mnemonic Comment
;Softime, using a 12 MHz crystal for a loop delay of 1 ms
.equ delay 0a 6h ; 166 6 cycles = 996 loop cycles
3
.equ dlylsb. 0f 4h ; try 500(1f4h) milliseconds; LSB in A
.equ dlymsb, 01h ; MSB in B
.org 0000h
blink:
setb p 1.0 ; demonstrate a 1-second LED blink rate
mova,#dlylsb ; pass the desired delay time to softime
mov b, #dlymsb
acall softime ; call for 500 ms delay @ 12 MHz
clr p1.0 ; turn off LED
mov a#dlyslb ; delay another 500 ms
mov b, # dlymsb
acall softime
sjmp blink ; loop ton start
;
softime: ; time delay routine named “softime”
push 07h ; over head of : 2 cycles
push acc ; : 2 Test for AB = 0000
orl. a, b ; : 1
cjne, a,# 00h, ok ; 2
pop acc ; 2 only if A = B = 00h
sjmp done ; 2
ok: ;
pop acc ; 2
; 9 overhead cycles to enter
timer: ;
mov r7#, delay ; 1 for MOV R7, total loop:1004 cycles
onemil: ;
nop ; 1 for each NOP
nop ;
nop ; this loop takes 6 cycles; total 996 cycles
nop ;
djnz r7, onemil ; 2 for the DJNZ
nop ; 1 for the NOP
nop ; 1 for the NOP
dec a ; 1 for DEC, 1,000 cycles at this point
cjne a, # offh, noroll ; 2 cycles to see if rollover
dec b ; overhead of 1 cycle each B decrement
4
noroll: ; adds B cycles to total delay
cjne a,#00h, timer ; 2 cycles to see if done
cjne a, b, timer ; overhead of 2 cycles each B +1 decrement
done: ; adds 2B + 2 cycles to total delay
pop 07 h ; overhead of 2 cycles to return
ret ; over head of 2 cycles to return
.end ;
; 4 cycles to return
The restriction on A = B = 00 is due to the fact that the program would count A down
from 00 to FFh after a 1 millisecond delay, B would then be decremented to FFh, and the
subroutine would return after a delay of 10000h milliseconds.
Software – Polled Timer
A delay that uses the timers to generate the delay and a continuous software flag test
(the flag is “polled” to see whether it is set) to determine when the timers have finished the
delay is given in this section. The user program signals the total delay desired by passing
delay variables in the A and B registers in a manner similar to the pure software delay
subroutine. A basic interval of 1 millisecond is again chosen so that the delay may range from
1 to 65,535 ms.
The clock frequency for the timer is the crystal frequency divided by 12, or 1 machine
cycle, which makes each count of the timer 1 microsecond for a 12 megahertz crystal.
Twelve megahertz is an excellent choice for generating accurate time delays, such as for use
in systems that maintain a time – of –day clock.
Timer 0 will be used to count 1,000 (03E8h) internal clock pulses to generate the
basic 1 milliseconds delay, registers A and B will be counted down as T0 overflows. The
timer counts up, so it will be necessary to put the 2’s complement of the desired number in
the timer and count up until it overflows.
Timer
The time delay routine named timer uses timer 0 and registers A and B to generate
delays from 1 to 65,535 d milliseconds. The calling program loads, registers A (LSB) and B
(MSB) with the desired delay in milliseconds. Loading a delay of 0000h results in an
immediate return.
Note that the error due to overhead instructions for the timer program is slightly
worse, over the entire range of delays, then a pure software delay program. The total timing
error for the program is as shown next:
Error =
Desired delay 15 3B 10 (microseconds)
Error =
Desired delay (milli seconds)
5
% Error =
The error for a desired 1- milliseconds delay is 2.5%, for a desired delay of 256
milliseconds is 1.5% and for a desired delay of 65,535 milliseconds is also 1.5%. As an
exercise, the reader may decide how to adjust the number loaded into T0 so that the 15
overhead cycles used to decrement A may be eliminated and the error made quit small.
Address Mnemonic Comment
; Timer, a program that uses T0 to generate the basic delay. No check
; for A = B = 00 is done
.equ onemshi. Ofch ; 2’s complement of 03E8h = FC18 h
. equ onemslo, 18 h ; used to set number in T0
.equ dlylsb, 0f4h ; set delay LSB in register A
. equ dlymsb, 01h ; set delay MSB in register B
.org 0000h
blink:
setb p1.0 ; Blink P1.0 LED ; LED on
mov a, # dlylsb ; pass desired 500 ms delay to routine
mov b, # dlymsb
acall timer ;call the Timer subroutine
clr p1.0 ;Blink p1.0 LED; LED off
mov a, #dlylsb ;pass desired 500 ms delay to routine
mov b, # dlymsb
acall timer
sjmp blink
timer:
anl tcon,# 0cfh ; clear T0 overflow &run flags only 2
anl tmod,#0f0h ; T0 enabled; T1 control unchanged 2
orl tmod, # 01h ; set T0 to mode 1(16 bit up counter) 2
;
; overhead cycles to begin timing 6
onems: ; total cycles delay per decrement of A
mov tl0, # onemslo ; set T0 to count up from FC 18h 2
move th0,#onemshi ; 2
orl tcon, # 10h ; start T0 timer 2
wait: ; 1000
jbc tf 0, dwnab ; poll T0 Overflow flag until set
2
6
sjmp wait ; loop until overflow flag set
dwnab:
anl t con , # Oefh ; stop T0; other TCON bits unchanged 2
dec a ; count A down 1
cjne a, #0ffh, noroll ; 2cycles to test for rollover 2
;
dec b ; 1 cycle to count B down
noroll: ; B extra cycles per delay
cjne a, #00h, onems ; 2 overhead cycles every A decrement 2
;
; normal decrement A total = 1015
done: ; 2B + 2 extra cycles per delay
ret ; 2 overhead cycles ; total overhead = 10
;
; decrement B total = 3B + 2
end ; delay = (desired delay) 1015 + (3B) + 10
; microseconds.
Comment
T0 cannot be used accurately for other timing or counting
functions in the user program, thus, there is no need to save
the TCON and TMOD bits for T0. T0 itself could be used to
store data if it is saved.
This program has no inherent advantage over the pure
software delay program, both take up all processor time. The
software polled timer has a slight advantage in flexibility in
that the number loaded into T0 can be easily changed in the
program to shorten or lengthen the basic timing loop. Thus the
call program could also pass the basic timing delay (on other
memory locations) and get delays that could be programmed
in microseconds or hours.
One way for the program to continue to run while the timer
times out is to have the program loop back on itself
periodically, checking the timer overflow flag. This looping is
the normal operating mode for most programs, if the program
execution time is small compared with the desired delay, and
then the error in the total time delay will be small.
Pure Hardware Delay
If delays must be done in a program, but the program cannot wait while the delay is
computed, then the delay will have to be done using a timer enabled to interrupt the main
7
program. A time interrupt allows a timer to run and generate a time delay at the same as the
main program loop is executing.
The program given in this section, hard time, operates in the following manner:
1. Timer T1 is initialized to count a delay of 1,000 microseconds.
Timer T1 will interrupt the main program loop, named wait, whenever the T1
overflow flag is set, or every millisecond. The wait loop continuously blinks port bit P1.7 at a
rate of 7.6 Hertz to demonstrate that it is running during the desired delay.
2. An interrupt routine must be located in the program at the T1 interrupt address 001Bh.
In the example program, the time delay subroutines hard time is called from the
interrupt address subroutine named in T1 located at 001 Bh.
3. Hard time determines if the total desired delay is finished and sets a flag named done
flag, if the desired time delay is finished, before returning to int T1.
4. int T1 jumps to the user routine named blink if the total desired time is up. If the
total desired time is not up, then int T1 returns to the main wait loop. Port bit P1.0 is
blinked at a rate of .5 Hertz by blink. Blink returns to the main wait loop.
The program begins by jumping over the interrupt at 001Bh, and proceeds to initialize
the various registers needed to enable T1 to time and a T1 interrupt to be done. Registers R0
and R1 of bank 3 are used to hold the desired total delay of 1,000 milliseconds.
Note that the time delay is, at most, 1008 microseconds every time T1 overflows. The
delay is due to the latency time before an interrupt may be acknowledgement by the CPU, the
time to call hard time, and the time to re – initialize TL1. Any over head time used by the
subroutine hard time does not change the basic delay after TL1 is set because T1 is running
and timing out while hard time is determining if the total desired time is up. The first delay to
blink will be late by the overhead time needed to find if the desired time is up. After the first
delay, subsequent delays to blink will be the desired delay multiplied by 1.008 milliseconds
plus an extra 5 microseconds needed by hard time to reload the desired time delay and set
done flag if the desired time is up.
; hard time , Users R0 and R1 of bank 3 to hold the desired time delay
.equ onemshi, Ofch ; two’s complement of 3E8h = FC18h
.equ onemslo, 18h
.equ dlylsb, Of4h ; set delay LSB in register R0 of bank 3
.equ dlymsb, 01h ; set delay MSB in register R1 of bank 3
.equ doneflag, 00h ; addressable bit 00 = 1 when delay done
. org 0000h
mov sp # 30h ; set SP above bit addressable area
sjmp overint ; jump over TI interrupt address in program
;
.org 001 bh ; T1 interrupt subroutine at 001Bh
; the interrupt subroutine for T1 must be located here in the program
; the time to interrupt to address 001Bh is a maximum of 3 cycles
8
int T1:
acall hard time ; call “hard time” time delay subroutine
; 2 cycles of overhead for the acall hard time instruction
jbc doneflag, blink ; “hardtime” returns flag set when delay up
reti ;else direct return to user’s main program
;
;begin by initializing the TI and interrupt registers
overint:
orl ie,#88 h ; enable interrupts and T1 interrupt
mov 18h, #dlylsb ; pass desired delay to timer subroutine
mov 19h, #dlymsb ; in bank 3 R0(lsb) and R1(msb)
mov t11,#onemslo ; set T1 to count up from FC18 h
mov th1,# onemshi ;
anl tmod,#0fh ; set T1 to time
orl tmod, #10h ; set T1 to time as a mode 1 timer
clr doneflag ; start with delay not finished
orl tcon, #40 h ; start T1
;
; place main program loop here to run continuously
wait:
setb p1.7 ; main program continuous on, shown here
acall softdelay ; as a software delay loop that blinks an
clr p1.7 ; LED connected to P1.7
acall soft delay
sjmp wait ; loop
soft delay: ; soft delay = .131 seconds
mov r0, 0ffh
outside:
mov r1, 0ffh
inside:
djnz r1, inside
djnz r0, outside
ret
;
; hard time is called approximately every millisecond by T1 interrupt
9
hard time : ; time delay called from T1 interrupt
mov t11,# onemslo ; rest TL1 to 18h, over head = 3 cycles
mov th 1, # one mshi ; interrupt 1,000 cycles from here
; total time between interrupts is actually 1,008 cycles
push psw ; save PSW contents
push acc ; save A register
orl psw, # 18 h ; switch to register bank 3
dec r0 ; countdown LSB in R0, 00 becomes FF
cjne r0,# 0ffh, noroll ; check for R0 = FF
dec r1 ; decrement R1, if so else check for done
noroll:
mov a, r1 ; test for R0 = R1 = 00
orl a, r0 ; if so, then delay is up
jnz notdone ; 2NOT DONE CYCLES
done: ; 8 DONE CYCLES
mov 18h, #dlylsb ; restore desired delay time 3
mov 19h, #dlymsb ; in bank 3 R0(lsb) and R1 (msb) 3
setb doneflag ; show delay is up 2
notdone: ; DIFFERENCE = 6
pop acc ; restore original A
pop psw ; restore original PSW
ret ; return to T1 interrupt call
;
; the user routine that uses the time delay placed here
;
blink: ; jump here whenever delay is up
xrl p1,#01h ; XOR P1.0 alternately 1 or 0
.reti ; return to main program loop
.end
Comment
The minimum usable delay is 1 millisecond because a 1
millisecond delay is done to begin the delay interrupt
cycle
All timing routines can be assembled at interrupt
location 001Bh if stack space is limited
10
The RETI instruction is used when returning to the main
program, after each interrupt.
There is no check for an initial delay of 0000h.
ASSEMBLER
1. USING THE ASSEMLER
The student assembler has most of the capabilities of the professional version with
these limitations:
No macro features
No options
No code beyond address 03FFh
The intent is to supply an assembler that is easy to use, enabling the student to get to
the business of writing programs with a minimum of delay.
The Big Picture
An assembler is a translator machine. Computer programs, written using a defined set
of rules (the syntax), are put into the assembler and hex code pops out (if the syntax has been
followed).
First, prepare a disk with the assembler program. Next, place any input program you
wish to have assembled on this disk.
The input program is in an ASCII text disk file that has been prepared by an editor
program and that must obey these rules:
1. The file name has the extension. ASM (example: myfile. asm).
2. The file must be “pure” ASCII.
Many editor programs save text files using strange and potentially troublesome
control characters. Save your text files in ASCII form.
The assembler produces two outputs files:
1. A file with the same name as the input ASCII text file, which has the extension. LST
is the assembled file complete with line numbers, memory addresses, hex codes,
mnemonics, and comments. Any errors found during assembly will be noted in the
LST file, at the point in the program where they occur.
2. A file with the same name as the input ASCII text file, which has the extension. OBJ
is the hex format file that can be loaded into the simulator and run.
Example
A sample program that blinks LEDs on an 8051 system is edited and saved as an
ASCII file named try. asm.
.org 4000h
loop: mov 90h, #0ffh ;LEDs off
11
acall time ;delay
mov 90h, #7fh ;turn on LED 1
acall time
mov 90h, #0bfh ;turn on LED 2
acall time
mov 90h, #3fh ;both LEDs on
acall time
sjmp loop
time: mov r0, #03h
in1: mov r1, #00h
in2: mov r2, #00h
wait: djnz r2, wait
djnz r1, in2
djnz r0, in1
ret
.end
The .LST file, which is produced by the assembler, has these features:
Line Address Hex Label Mnemonics Comments
000001 4000 .org 4000h
000002 4000 7590FF loop: mov 90h, #0ffh ;LEDs off
000003 4003 1116 acall time ;delay
000004 4005 75907F mov 90h, #7fh ;turn on LED 1
000005 4008 1116 acall time
000006 400A 7590BF mov 90h, #0bfh ;turn on LED 2
000007 400D 1116 acall time
000008 400F 75903F mov 90h, #3fh ;both LEDs on
000009 4012 1116 acall time
0000010 4014 80EA sjmp loop
0000011 4016 7803 time: mov r0, #03h
0000012 4018 7900 in1: mov r1, #00h
0000013 401A 7A00 in2: mov r2, #00h
0000014 401C DAFE wait: djnz r2, wait
0000015 401E D9FA djnz r1, in2
0000016 4020 D8F6 djnz r0, in1
12
0000017 4022 22 ret
0000018 4023 .end
The .OBJ file contains the hex code from the .IST file, together with special leading
(.xxxxxxxx) and trailing characters (the last byte in each line, which is a checksum) that can
be loaded into the simulator or an EPROM burner:
:104000007590FF111675907F11167590BF1116757A
:10401000903F111680EA780379007A00DAFED9FA27
:03402000D8F622AD
:00000001FF
How to Assemble
After you have written your program using the mnemonics from Appendix A and
saved the program in an ASCII text file, type:
A51 –s yourfile (Note: No .ASM)
where yourfile is the name of your ASCII program file. The – s prevents the assembler from
including the symbol table at the end of your program. For the example program, we type:
a51 –s try. The result is [Link] and [Link]
The assembler will assemble your program and inform you of any errors that are
found. You can type the .LST file to the computer screen or print the listing to a printer. All
errors in syntax will be shown by the assembler in the .LST file. Keep in mind that a program
that has been successfully assembled is not guaranteed to work, it is only grammatically
correct. (Sentences in English can be written that are grammatically correct but make no
sense; see any government form for an example). Re-edit your program until assembly is
successful.
2. ASSEMBLER DIRECTIVES
An assembler is a program and has instruction just as does any program. These are
called directives or pseudo operations because they inform the assembler what to do with the
mnemonics that it is to assemble. The pseudo ops are distinctly different from the mnemonics
of the computer code being assembled, so that they stand out in the program listing. For the
Pseudo Corp assembler, they are the following.
.org xxxx ORiGinate the following codes starting at address xxxx.
Example Program Address Hex
.org 0400h becomes: 0400 79
MOV r2,# 00h 0401 00
The .org pseudo op lets you put code and data anywhere in program memory you
wish. Normally the program starts at 0000h using a .org 0000.
.equ label, xxxx EQUate the label name to the number xxxx
Example Program Address Hex
13
.org 0000h becomes: 0000 74
.equ fred,12 h 0001 12
MOV a,# fred
.equ turns numbers into names; it makes the program much more readable because the name
chosen for the label can have some meaning in the program, whereas the number will not.
.db xx Define a Byte: place the 8 – bit number xx next in memory
Example Program Address Hex
.org 0100h becomes: 0100 34
.db 34 h 0101 56
.db 56 h
.db “abc”
Example Program Address Hex
.org 0200h becomes: 0200 31
.db “123” 0201 32
0202 33
.db xx takes the number xx (from 0 to 255d) and converts it to hex in the next
memory location. .db “abc” will convert any character that can be typed into the space
between the quote marks into the equivalent ASCII (no parity) hex code for that character,
and place multiple characters sequentially in memory. .db permits the programmer to place
nay hex byte anywhere in memory.
.dw xxxx Define a Word: Place the 16 – bit number xxx in memory.
Example Program Address Hex
.org 0abcdh becomes: ABCD 12
.dw 1234 h ABCE 34
.dw is a 16 bit version of .db.
.end The End: Tells the assembler to stop assembling
Other directives exist that are rarely used by student programmers Refer to the
assembler documentation contained in the disk file under the name [Link]. The file
[Link] contains some .opdef directives that let .anything become anything (no.) for
those programs written with directives that do not use the period.
3. NUMBERS
Numbers follow one simple rule: They must start with a number from 0 to 9.
For example:
1234
0abcdh
14
0ffh
5aceh
Numbers in the program can be written in decimal or hex form as:
1234 = 1234 decimal
h’ 0dd = DD hexadecimal
0ddh = DD hexadecimal
The first form of the hexadecimal number (h’0dd) is a Unix standard; the second form
(0ddh) is a common assembly language standard.
4. LABELS
Labels are names invented by the programmer that stand for a number in the program,
such as a constant in the .equ directive above, or a number that represents a memory location
in the program. Labels used for memory locations follow two simple rules:
1. All labels must START with an alphabetic character and END with a: (colon)
2. A label can have no more than 8 characters.
The following are examples:
fred:
m1
p1234:
xyz:
The restriction that all numbers begin with a number is now apparent; hexadecimal
numbers beginning with A to F would be mistaken by the assembler as a label and chaos
would result.
Comments
Anything that follows a semicolon (;) in a line of a program is
ignored by the assembler. Comments must start with a ; .For
example,
;this is a comment and will be ignored by the assembler
If you are assembling a program and get a LOT of syntax
errors, you probably forgot to include a semicolon in your comments
Only labels should begin in Column 1.
Typing a Line
To make the program readable, it is recommended that you type all opcodes about 10
spaces or so to the right of the left margin of your text. Start all labels at the left margin of
text, and place any comments to the right of the opcode entry. The finished line should appear
as follow:
label: opcode ;comment
Experiment with the assembler by writing short programs to get a clear understanding
of what each output file contains.