You are on page 1of 137

Lesson 1: The Language of

Microprocessors
Most people think that computers are some kind of complicated device that is impossible
to learn and infinitely intelligent, able to think better than a person. The truth is much less
glamorous. A computer can only do what the programmer has told it to do, in the form of
a program. A program is just a sequence of very simple commands that lead the
computer to solve some problem. Once the program is written and debugged (you hardly
ever get it right the first time), the computer can execute the instructions very fast, and
always do it the same, every time, without a mistake.
And herein lies the power of a computer. Even though the program consists of very
simple instructions, the overall result can be very impressive, due mostly to the speed at
which the computer can process the instructions. Even though each step in the program is
very simple, the sequence of instructions, executing at millions of steps per second, can
appear to be very complicated, when taken as a whole. The trick is not to think of it as a
whole, but as a series of very simple steps, or commands.
Most microprocessors, or very small computers, (here after referred to simply as micro's)
have much the same commands or instructions that they can perform. They vary mostly
in the names used to describe each command. In a typical micro, there are commands to
move data around, do simple math (add, subtract, multiply, and divide), bring data into
the micro from the outside world, and send data out of the micro to the outside world.
Sounds too simple....it is.
A typical micro has three basic parts inside. They are the Program Counter, Memory, and
Input / Output. The Program Counter keeps track of which command is to be executed.
The Memory contains the commands to be executed. The Input / Output handles the
transfer of data to and from the outside world (outside the micro's physical package). The
micro we'll be using is housed inside a 40 pin package, or chip. There are many other
actual parts inside our micro, but too much, too soon, would be too confusing right now.
We will, however, learn about each and every single one.
A Simple Program
As stated before, a program is a sequence or series of very simple commands or
instructions. A real world example program might be the problem of crossing a busy
street.
Step 1: Walk up to the corner of the street and stop.
Step 2: Look at the traffic light.
Step 3: Is the light green for your direction of travel?

Step 4: If the light is red, goto step 2. (otherwise continue to step 5)


Step 5: Look to the left.
Step 6: Are there cars still passing by?
Step 7: If yes, goto step 5. (otherwise continue to step 8).
Step 8: Look to the right.
Step 9: Are there cars still passing by? (there shouldn't be any by now, but, you never
know!)
Step 10: If yes, goto step 8. (otherwise continue to step 11)
Step 11: Proceed across the street, carefully!!
Now this may seem childish at first glance, but this is exactly what you do every time you
cross a busy street, that has a traffic light (at least, I hope you do). This is also exactly
how you would tell a micro to cross the street, if one could. This is what I mean by a
sequence or series of very simple steps. Taken as a whole, the steps lead you across a
busy intersection, which, if a computer did it, would seem very intelligent. It is
intelligence, people are intelligent. A programmer that programmed these steps into a
micro, would impart that intelligence to the micro.
The micro would not, however, in this case, know what to do when it got to the other
side, since we didn't tell it. A person, on the other hand, could decide what to do next, at a
moments notice, without any apparent programming. In the case of a person, though,
there has been some programming, it's called past experiences.
Another program might be to fill a glass with water from a faucet.
Step 1: Turn on the water.
Step 2: Put the glass under the faucet.
Step 3: Look at the glass.
Step 4: Is it full?
Step 5: If no, goto step 3.(otherwise, continue to step 6)
Step 6: Remove the glass from under the faucet.
Step 7: Turn off the water.

This is a simpler program, with fewer steps, but it solves a problem, to fill a glass with
water. In a micro, the problems are different (they could be the same if you've made some
kind of servant robot), but the logical steps to solve the problem are similar, that is, a
series of very simple steps, leading to the solution of a larger problem.
Also notice that since the steps are numbered, 1 through 7, that is the order in which
they're executed. The Program Counter, in this case, is you, reading each line, starting
with 1 and ending with 7, doing what each one says. In a micro, the Program Counter
automatically advances to the next step, after doing what the current step says, unless a
branch, or jump, is encountered. A branch is an instruction that directs the Program
Counter to go to a specific step, other than the next in the sequence. The branch in this
example is step 5. Not only is this a branch, but it is a conditional branch. In other words,
based on whether the glass is full or not, the branch is taken, or not. A micro has both
branch and conditional branch instructions. Without this ability to reuse instructions, in a
sort of looping action, a solution would take many more steps, if it would be possible at
all.
The point of this lesson is to show how a simple set of instructions can solve a bigger
problem. Taken as a whole, the solution could appear to be more complicated than any of
the separate steps it took to solve it. Well that wasn't so tough, was it?
The most difficult problem to be solved in programming a micro is to define the problem
you are trying to solve. Sounds like some kind of play on words, but I assure you, it's not.
This is the Logical Thought Process I mentioned earlier. The mark of a good programmer
is one who has this ability. It is, in my humble opinion, a developed skill, coupled with
some talent, and a good understanding of the problem you're trying to solve. In most
cases, God has endowed you with the talent and the ability to reason, all you need do is
develop the skill of Problem Solving.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

Decimal Vs. Binary (and Hex)


Most people have learned to use the Decimal numbering system for counting and
calculations. But micros use a different system. It's called Binary. All numbering systems
follow the same rules. Decimal is Base 10 and Binary is Base 2. The base of a system
refers to how many possible numbers can be in each digit position. In decimal, a single
digit number is 0 through 9. In binary a single digit number is 0 or 1. In decimal, as you

count up from 0, when you reach 9 and add 1 more, you have to add another digit
position to the left and carry a 1 into it to get 10 (ten). Ten is a two digit decimal number.
In binary, as you count up from 0, when you reach 1 and add 1 more, you have to add
another digit position to the left and carry a 1 into it to get 10 (two decimal). While this is
exactly what you do in decimal, the result looks like ten. What you have to do is get past
seeing all numbers as decimal. While decimal 10 (ten) looks like binary 10 (two decimal)
they represent different decimal values. It is still useful to think in decimal, since that's
what we're used to, but we have to get used to seeing numbers represented in binary.
Another small difference between decimal terminology and binary is that in binary a digit
is called a bit. It gets even more confusing by the fact that 4 bits make a nibble. Two
nibbles make a byte. Two bytes make a word. Most numbers used in a micro don't go
beyond this, although there are others. Using what I've just said, if two nibbles make a
byte, you could also say that a byte is eight bits.
To represent a binary number larger than 4 bits, or a nibble, a different numbering system
is normally used. It is called hexadecimal, or Base 16. A shorter name for hexadecimal is
simply hex, and that's what we'll use here after. In this system there are 16 possible
numbers for each digit. For the first 10, 0 through 9, it looks like decimal. Unlike
decimal, when you add 1 more to 9, you get A. I know that having a letter to represent a
number is really confusing, but they had to call it something, and A is what they chose.
So a hex A is a decimal 10 (ten). The numbers count up from A through F. Just to clarify
this here is the sequence of counting in hex from 0 to where you have to add another digit
position to the left... 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F (no G). This represents a
decimal count of 0 through 15. At a count of F (15 decimal), if you add 1 more you get
10 (oh no! .. not another 10 that means something else!!). Sad but true.
Let's regroup here. A binary 10 (one zero) is decimal 2, a decimal 10 is ten, and a hex 10
is decimal 16. If you can get this concept, you will have conquered the most difficult part
of learning micros.
I need to get past one more obstacle, the idea of significance. In a decimal number like
123, 3 is the least significant digit position (the right most digit) and 1 is the most
significant digit position (the left most digit). Significance means the relative value of one
digit to the next. In the number 123 (one hundred twenty three) , each number in the right
hand most digit position (3) is worth 1. The value of each number in the next most
significant digit position (2) is worth ten and in the most significant digit position (1)
each is worth a hundred. I'm not trying to insult your intelligence here, but rather to point
out the rule behind this. The rule is that no matter what base you're working in, as you
start adding digits to the left, each one is worth the base times (multiplied) the digit to the
right. In the decimal number 123 (base 10), the 2 digit position is worth 10 times the 3
digit position and the 1 digit position is worth 10 times the 2 digit position. Hence the
familiar units, tens, hundreds, and so on. For some reason, for most people, this makes
sense for decimal (base 10) but not for any other base numbering system.

The very same is true for binary. The only difference is the base. Binary is base 2. So in
binary the least significant bit (remember bits?) is worth 1 ( this happens to be the same
for all bases). The next most significant bit is worth 2, the next worth 4, the next worth 8,
and so on. Each is 2 times (base 2) the previous one. So in an 8 bit binary number (or
byte, remember bytes?), starting from the right and moving left, the values for each of the
8 bit positions are 1, 2, 4, 8, 16, 32, 64 , and 128. If you've got this, you have passed a
major milestone, you should go celebrate your passage. If you haven't, I would re-read
the above until you do, and then go celebrate!! ( By the way, if you didn't get this the first
time through, join the crowd. I didn't either!!)
In hex (base 16) the same rule applies. In a 4 digit hex number, starting at the right and
working left, the first digit is worth 1 ( hey that's just like decimal and binary!!), the next
is worth 16 (base times the previous digit), the next is worth 256 (16 X 16), and the most
significant is worth 4096 (256 X 16). One last note, hex is just binary described another
way. A hex digit is a binary nibble ( remember nibbles?). Both are 4 bit binary values.
Trying to wrap all this confusion up in review, 4 bits is a nibble or a hex digit. Two hex
digits is a byte or 8 bit binary. A 4 digit hex number is a word, or 16 bit binary or 4
nibbles, or 2 bytes. You may have to review the previous paragraphs a few times (I did!!)
to get all the relationships down pat, but it is crucial that you are comfortable with all I've
said, so that what follows will make more sense.
Let's take a few example numbers and find the decimal equivalent of each. Let's start
with the binary number 1011, a nibble. Starting at the right and moving left, the first digit
is worth one, because there is a 1 there. The next is worth two, because there is a 1 in it.
The next would be worth 4, but since there is a 0, it's worth zero. The last or most
significant is worth eight, since there is a 1 in it. Add all these up and you get eleven. So
a binary 1011 is decimal 11. Also, since this could be a hex digit, the hex value would be
B.
Let's take a longer binary number 10100101. Starting at the right moving left, the first is
worth 1, the next is worth 0, the next is worth 4, the next is 0, the next is 0, the next is
worth 32, the next is 0, and the last is 128. Adding all these up you get decimal 165.
Dividing this number up into two hex digits, you get A5. So binary 10100101, decimal
165, and hex A5 are all the same value. Using hex, the rightmost digit is worth 5, and the
most significant digit is worth 160 (10 X 16), resulting in decimal 165. If you understand
this, your ready to move on, leaving the different systems behind. If you don't, keep
reviewing and studying until you do. If you don't understand and still continue, you will
be confused by what follows. I promise that once you get this, the rest is easier.
This ends the second lesson. I hope this wasn't too daunting or difficult, but there was a
lot to get through. The rest of the course should be a little easier. Learning a new
numbering system is like learning a new language. It's a little cumbersome at first, but it
gets easier.

My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

A Micro's Commands (or Instructions).


I would like to take a moment and address a few things that will make it easier for me to
describe things to you later. In a micro we refer to a binary number like 1111 as 1111b, a
decimal number like 123 as 123, and a hex number like A5 as A5h. So don't be confused
by the letters following numbers, it is easier than having to say the whole words binary,
decimal, or hexadecimal, but still indicate it. By doing this, there is no doubt what base a
multidigit, or multibit, number is in.
Also there is another kind of memory, called flags. Flags are single bit numbers used to
indicate different conditions. They are called flags because they flag the program of
events or conditions. If a flag is raised, or has a 1 in it, it is said to be SET. If it is a 0, it is
said to be CLEARED.
One other thing, in an 8 bit byte the 8 bits are referred to as bits 0 through 7, with bit 0
being the right most, or least significant, and bit 7 as the left most or most significant. In
micros, a 0 is just as real a number as 1 or 8, and it should not be thought of as "nothing".
Lastly, there are various Registers inside a micro. These vary from micro to micro, but all
contain a register called the Accumulator. It is also referred to in some as the a register. I
will be using the accumulator in the following discussion. It is a type of memory for
storing temporary results and is 8 bits wide, or a byte, as are most places that data can be
put inside the micro.
In the micro we will be using, there are 5 different types of instructions and several
variations of each, resulting in 110 different instructions. These 5 types are
ARITHMETIC, LOGICAL, BOOLEAN, BRANCHING, and DATA TRANSFER.
ARITHMETIC
The arithmetic instructions include addition, subtraction, division, multiplication,
incrementing, and decrementing. There are two flags used with arithmetic that tell the
program what the outcome of the instruction was. One is the Carry (C) flag. The other is
the Zero (Z) flag. The C flag will be explained in the following example of addition. The
Z flag, if set, says that the result of the instruction left a value of 0 in the accumulator. We
will see the Z flag used in a later lesson.
Addition

This is straight forward and is simply to add two numbers together and get the result.
However there is one more thing. If, in the addition, the result was too big to fit into the
accumulator, part of it might be lost. There is a safeguard against this. Take the case of
11111111b (255) and 11111111b (255). These are the largest numbers that can fit into an
8 bit register or memory location.You can add these as decimal numbers, since I gave
you their values in decimal also, and you would get 510. The binary value for 510 is
111111110b (9 bits). The accumulator is only 8 bits wide, it is a byte. How do you fit a 9
bit number into 8 bits of space? The answer is, you can't, and its called an OVERFLOW
condition. So how do we get around this dilemma? We do it with the CARRY (C) flag. If
the result of the addition is greater than 8 bits, the CARRY (C) flag will hold the 9 th bit.
In this case the accumulator would have a 11111110b (254) and the C flag would be a 1,
or set. This 1 has the value of 256 because this is the 9th bit. We haven't covered a 9 bit
number, but they come up all the time as overflows in addition. Since we are using base
2, and we found out in lesson 2 that the 8th bit (bit 7) in a byte is worth 128, then the 9th
bit is worth 2 times that, or 256. Adding 254 and 256, we get 510, the answer, and we
didn't loose anything, because of the C flag. Had the result of the addition not caused an
overflow, the C flag would be 0, or cleared.
Subtraction
In the case of subtraction, the process is more difficult to explain, and as such, I'm not
going to cover it here. It involves 1's compliment and 2's compliment representation. But
I will tell you this, you can subtract two numbers and if there is an under flow, the C flag
will be a 1, otherwise it will be a 0. An under flow is where you subtract a larger number
from a smaller number.
Multiplication and Division
In the micro we will be using, are multiply and divide instructions, but I will wait till later
to talk about them. They do, however, just what the names suggest.
Two other instructions are included in the arithmetic group. They are increment and
decrement. These instructions are used to count events or loops in a program. Each time
an increment is executed, the value is incremented by 1. A decrement, decrements the
value by 1. These can be used with conditional jumps to loop a section of program, a
certain number of times. We will see these used later.
LOGICAL
In micros there are other mathematical instructions called logical instructions. These are
OR , AND, XOR, ROTATE, COMPLEMENT and CLEAR. These commands are
usually not concerned with the value of the data they work with, but, instead, the value,
or state, of each bit in the data.
OR

The OR function can be demonstrate by taking two binary numbers, 1010b and 0110b.
When OR'ing two numbers, it doesn't matter at which end you start, right or left. Let's
start from the left. In the first bit position there is a 1 in the first number and a 0 in the
second number. This would result in a 1. The next bit has a 0 in the first number and a 1
in the second number. The result would be 1. The next bit has a 1 in the first number and
a 1 in the second number. The result would be a 1. The last bit has a 0 in the first number
and a 0 in the second number, resulting in a 0. So the answer would be 1110b. The rule
that gives this answer says that with an OR, a 1 in either number results in a 1, or said
another way, any 1 in, gives a 1 out.
AND
AND'ing uses a different rule. The rule here is a 0 in either number will result in a 0 , for
each corresponding bit position. Using the same two numbers 1010b and 0110b the result
would be 0010b. You can see that every bit position except the third has a zero in one or
the other number. Another way of defining an AND is to say that a 1 AND a 1 results in a
1.
XOR (eXclusive OR)
XOR'ing is similar to OR'ing with one exception. An OR can also be called an inclusive
OR. This means that a 1 in either number or both will result in a 1. An eXclusive OR says
that if either number has a 1 in it, but not both, a 1 will result. A seemingly small
difference, but crucial. Using the same two numbers, the result would be 1100b. The first
two bits have a 1 in either the first or the second number but not both. The third bit has a
1 in both numbers which results in a 0. The fourth has no 1's at all, so the result is 0. The
difference may seem small, even though the OR and XOR result in different answers.
The main use of an XOR is to test two numbers against each other. If they are the same,
the result will be all 0's, otherwise the answer will have 1's where there are differences.
Compliment
Complimenting a number results in the opposite state of all the 1's and 0's. Take the
number 1111b. Complimenting results in 0000b. This is the simplest operator of all and
the easiest to understand. It's uses are varied, but necessary, as you'll see later.
Rotate
These instructions rotate bits in a byte. The rotation can be left or right, and is done one
bit each instruction. An example might be where the accumulator has a 11000011b in it.
If we rotate left, the result will be 10000111b. You can see that bit 7 has now been moved
into bit 0 and all the other bits have move 1 bit position in, the left direction.
Clear

This instruction clears, or zero's out the accumulator. This is the same as moving a 0 into
the accumulator. This also clears the C flag and sets the Z flag.
BOOLEAN
There are other commands that deal with single bit values. In a program, an 8 bit byte
location may be used as 8 separate flags or indicators, defined by the programmer, and
used to signal events or conditions between separate parts of a program. This might be a
bit that indicates whether a door is open or closed, or whether a key has been pressed or a
switch is closed or open. These bits or flags are usually the mechanism that keeps law
and order in the program, and insures that the program does not get "lost" or misdirected.
We'll see how this works later.
There are instructions to do the same things with bits that are done with bytes by other
instructions. You can AND, OR, SET, CLEAR, or MOVE bits. You will see in a later
lesson how these are used.
BRANCHING
There are also program flow commands. These are branches or jumps. They have several
different names reflecting the way they do the jump or on what condition causes the
jump, like an overflow or under flow, or the results being zero or not zero. But all stop
the normal sequential execution of the program, and jump to another location, other than
the next instruction in sequence.
Jump on Bit
These instructions let you make a jump based on whether a certain bit is set (a 1) or
cleared (a 0). This bit can be the CY (carry) flag, the Z (zero) flag, or any other bit.
Compare and jump
These instructions compare two values and jump based on the result. In lesson 1 we
looked at a glass filling with water and compared it to a full one in our mind, and if it
wasn't full we looked at it some more. This caused that program to loop, or wait, until the
glass was full before continuing on.
Call
There is also a variation on a jump that is referred to as a CALL. A CALL does a jump,
but then eventually comes back to the place where the CALL instruction was executed
and continues with the next instruction after the CALL. This allows the programmer to
create little sub-programs, or subroutines, that do repetitive tasks needed by the main
program. This saves programming time because once the subroutine is written, it can be
used by the main program when ever it needs it, a kind of way to create your own
instructions.

DATA TRANSFER
Moving
These instructions do exactly what you would think. They move data around between the
various registers and memory.
Exchanging
Exchanging is a variation on the move instruction. Here data is exchanged between two
places.
Exchange Digit
This is a variation on the exchange instruction. Here the lower nibble, or digit, is
exchanged with the lower nibble of the other location. The upper nibble is the left 4 bits
(bits 4 through 7) and the lower nibble is the right 4 bits (bits 0 through 3).
Swapping
This is another variation on the exchange instruction, but here the data exchange occurs
between the upper nibble and the lower nibble in the accumulator.
This is the end of lesson 3. I've tried to show all the possible instructions without actually
showing each. I will, in a later lesson, go into each of the 110 different instructions and
explain each one. In the next lesson we will learn more about memory and all the
possible ways to get to a memory location.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

The DS5000 Micro controller.


Well, finally, this is what all the previous lessons have been getting you ready for. To
start looking at the micro we will be using in this course. All the previous lessons have
been laying the ground work and basic concepts common to most micros. The one we
will be using is the Dallas Semiconductor DS5000 micro controller. This chip, in my
mind, is one of the neatest ones ever made. Dallas Semiconductor knew what to put in a
chip. There are a few external parts needed along with the DS5000, but they are few, and

cheap to buy. I will give you a schematic and a parts list, along with the places to get all
the parts later.
I would recommend that you get the Secure Micro controller Data Book (in PDF format)
from Dallas Semiconductor, as soon as you can. The paperback version is out of print
now. The telephone number is 972 - 371 - 4448. They are located in Dallas Texas at 4401
South Beltwood Parkway, zip 752444-3292. It's loaded with information and is
absolutely crucial for a good understanding of the DS5000. The book actually covers
several versions of the 5000 series of microcontroller chips.
My hope is that you will buy one of the DS5000 parts and really start building things
with it. I think it costs about $75. Cheap, considering what you can do with it. But don't
buy it yet, I'll tell you when. There is still lots to learn before you will be able to start
building something with it.
The DS5000 is a microcontroller based on the Intel 8051 microprocessor. It looks like an
8051 as far as programming and the general pinout of the package. The pinout refers to
the physical package and what each pin on the package is used for. But the DS5000 is
much more. There are 40 pins on the package. Of these pins, 32 are connections to the
outside world that information can be brought into or out of the DS5000. There are a few
other pins, but more on them later.
These 32 pins show up inside the package as 4 ports. Each port is 8 bits wide and have
the names p0, p1, p2, and p3. Each port can be inputs or outputs to or from the outside.
Besides being inputs and outputs, p0, p2, and p3 have special functions assigned to them.
P0 and p2 are used for external memory interface, which we will not use since there is
plenty of memory inside the DS5000. These 16 pins will be used for input / output. But if
we were talking about an 8051, this is where memory would connect. Half of the pins of
port 3 will be used for their special functionality. The other four pins of p3 will be used
as regular inputs and outputs, not using their special functions. These functions will be
discussed later, however.
Two pins of p3 are for serial communications with the DS5000. Through these pins,
serial data can be sent or received with another computer. This is how we will load a
program into the DS5000 from a PC.
Two more pins are for a different kind of input called interrupts. In our example in lesson
1 of the program where we are standing at the street corner, watching the light and the
traffic, if a person walked up and tapped us on the shoulder and asked what time it is, this
would be an example of an interrupt. It doesn't alter the program we are doing, it just
temporarily stops us while we tell the time to the person. As soon as we tell the time, we
go back to watching the lights and traffic as before. This describes the action of an
interrupt.
The interrupt has a program associated with it to guide the micro through a problem. In
the case of the above example, this program would be to look at our watch, read the time,

and then tell it to the person. This is called an interrupt service routine. Each time an
interrupt occurs, the current program is temporarily stopped and the service routine is
executed and when complete, returns to the current program. We will spend a lot more
time later describing interrupts and how we'll use them.
Inside the DS5000 is a 128 byte memory area called internal ram (random access
memory). The first 32 bytes are used for 4 banks of registers. They are called bank 0,
bank 1, bank 2, and bank 3. When the micro is first powered up, bank 0 is the selected
bank. Afterwards there is a command which allows switching to another bank, but we
won't do that right now. Inside each bank there are 8 seperate registers. They are called
r0, r1, r2, r3, r4, r5, r6, and r7. These registers are used for temporary storage of whatever
is needed by the program. There is also a register that is the accumulator called a. This
register is different from the other 8 registers. It is used to accumulate the results of
various instructions like add or sub (subtract).
The next 16 bytes of internal ram are for bit storage and retrieval. This allows for 128
different bits that can be used by the program. Most early micros didn't have this very
useful feature.
The last 80 bytes of internal ram are for general storage and is used for many different
things.
There is another 128 byte area of memory called the special function registers. This is
where p0, p1, p2, and p3 are located. The accumulator and program counter are also in
this area along with other registers that control various aspects of the operation of the
DS5000. More on these as they come up later.
Lastly there is one more area of memory that is divided into two parts. The program
memory and the data memory. This is in reality a block of 32K (32768) bytes of ram that,
through the programming of one of the special function registers, can be divided into the
two sections. In most of our work we will divide this up into 16K (16384) bytes of
program and 16K (16384) bytes of data. The program memory is write protected so that
an errant program can't change itself accidently and really make a mess out of things. The
data memory can be written to or read from as needed by the program.
There is a lithium battery inside the DS5000 that keeps all the memory alive when power
is turned off. This is one of the neatest features of the DS5000, and I don't know of
another micro that has this. In most micros there is PROM ( programmable read only
memory) or EPROM (erasable programmable read only memory), as the program
memory. PROM can be programmed once and that's it. EPROM can be programmed and
then erased with ultra violet light through a glass window in the micro's package. But this
requires a special programmer and an ultra violet light source for erasure (over $125
worth of stuff!!).
But in the DS5000, 2 of the pins can be placed in a special state, and the program
downloaded to the chip through the serial port from a PC. There is a special program

inside the DS5000 that allows all this to happen, that goes away when these 2 pins are
returned to their normal state. This special program can be accessed anytime to change
the user program by, in our case, flipping a switch, downloading the program, and then
returning the switch to its normal position. Neat, huh? It takes just a few seconds to do
this, where it used to take a half hour to go through the erase / program cycle of an
EPROM based micro. And no special hardware is needed, other than the serial port.
This speeds up program development and allows a change to be made to the program,
downloaded to the micro, and executed, in under a minute. Slick!!! It will still take plenty
of time to develope and debug a program, but very little time or effort to get the program
into the DS5000, ready to run.
I have also located a free 8051 simulator for your use that will allow you to test small
programs out on your PC without downloading them to the DS5000. You can see all the
registers change as the program is stepped through each instruction. This greatly speeds
up the debugging process, and is an educational tool that shows how each instruction
affects the various parts of the DS5000. We will use this in later lessons to illustrate the
different instructions and programs that we will write.
There are other features to be covered later, as they come up. In the next lesson we will
start looking at assembly language, the method we will use to write a program.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.net/personal/index.html .

What is Assembly Language?


Inside the DS5000, instructions are really stored as hex numbers, not a very good way to
look at them and extremely difficult to decipher. An assembler is a program that allows
you to write instructions in, more or less, english form, much more easily read and
understood, and then converted or assembled into hex numbers.
The program is written with a text editor ( I'll furnish you with one), saved, and then
assembled by the assembler (I'll furnish you with one). The result is the file you
download to the DS5000. Here is an example of the problem of adding 2 plus 2: [This file
will be called prob01.asm]
mov r0,#2
mov a,#2
add a,r0

The first line moves a 2 into register r0. The second moves a 2 into the accumulator. This
is all the data we need for the program. The third line adds the accumulator with r0 and

stores the result back into the accumulator, destroying the 2 that was originally in it. The
accumulator has a 4 in it now and r0 still has a 2 in it.
Assembly language follows some rules that I will describe as they come up. With most
instructions, especially those involving data transfer, the instruction is first, followed by
at least 1 space, then the destination followed by a comma, and then the source. The
destination is where the result of the instruction will end up and the source is where the
data is coming from.
Next we will read a switch, and light an led if the switch is pressed. Bit 0 of p1 will be
the switch. When the switch is closed or pressed, bit 0 will be a 1, and if the switch is
open or not pressed, bit 0 will be a 0.
Bit 0 of p0 will be the led. If bit 0 is a 0 the led is off and if bit 0 is a 1, the led will be on.
All the other bits of both p0 and p1 will be ignored and assumed to be all 0's, for the sake
of discussion. [This is prob02.asm]
start: mov a,p1
mov p0,a
sjmp,start

;read the switch


;write to the led
;go to start

The first line has something new. It's called a label. In this case it is start:. A label is a
way of telling the assembler that this line has a name that can be referred to later to get
back to it. All labels are followed by the symbol : , which tells the assembler that this is a
label. Also a comment can be added to the line to remind you of what that line does. A
comment is always started with a ; which tells the assembler to ignore all that follows on
that line because it is a comment.
In the first line we also read the switch by reading p1 (the source) and putting it into the
accumulator (the destination).
The next line writes the accumulator, which has the switch in it (the source) to p0, which
has the led attached (the destination).
The last line jumps back to start. This completes the loop of reading the switch and
writing to the led.
I hope you see that if the switch is closed, the first line will result in a 1 in the
accumulator. In the second line, writing a 1 to p0 will light the led. If the switch is open
in line 1, there will be a 0 in the accumulator which, when written to p0, will turn off the
led. This loop would continue, endlessly, reading the switch and writing the led, until the
micro is turned off, or a new program loaded. If the switch is pressed, the led will be lit,
otherwise it won't..
This particular problem could have been solved with just a switch connected to an led,
like a light is connected to a wall switch in your house. But with a micro in the loop,
much more could be done. We could have a clock that also turns on and off the led based

on time. Or we could monitor the temperature and turn the led on and off based on what
temperature it is. Or we could monitor several switches and turn the led on and off based
on a combination of switches.
In the above example we assumed that the other bits of p0 and p1 were all zeros. But in
reality, each of these bits could have a function assigned to them. Then we would need to
look only at bit 0 in p0 and bit 0 in p1. This further complicates the problem.
In assembly we can assign a name to a bit and refer to it by that name, instead of a bit in a
port. This is done with an equate directive. Directives are assembler commands that don't
result in program but instead direct the assembler to some action. All directives start with
a period.
In the DS5000 there are many bit locations. 128 of them were discussed in the previous
lesson. There are others like all the bits in the accumulator, all the bits in the p0, p1, p2,
and p3 ports. Each one is assigned an address by the DS5000 and can't be changed. The
first 128 are the 16 bytes of internal ram following the first 32 bytes. Bit 0 of p0 is
address 128 (80h) and bit 0 of p1 is address 144 (90h). Now let's look at what this looks
like in assembly. [This is prob03.asm]
.equ
.equ
start: mov
carry
mov
sjmp

switch,144
led,128
c,switch

;p1 bit 0 is now called switch


;p0 bit 0 is now called led
;get the state of the switch and put in

led,c
start

;mov the carry flag to the led


;jump to start

This has the same result as the previous program, but doesn't assume anything about the
other bits in p0 and p1. Also the equate only has to be made once at the start of the
program, and thereafter the name or label is used instead of the bit number. This makes
things much simpler for the programmer. Also the carry flag is used with bit instructions
like the accumulator is used for byte instructions. All bit moves must be through the carry
flag. All equates must be defined before they are used in a program. This holds true for
labels also. Another advantage of naming bits with an equate is that if, later in the design
process, you decide to use a different bit for the led or the switch, only the equate has to
be changed, not the program itself.
Another way to do the 2 plus 2 problem is to use a similiar technique. This directive is
the reserve storage directive. Here is how this would look in assembly: [This is
prob04.asm]
first: .rs
secnd: .rs
mov
mov
mov
add

1
1
first,#2
secnd,#2
a,first
a,secnd

;reserve a byte called first


;reserve a byte called secnd
;put a 2 into first
;put a 2 into secnd
;mov first into accumulator
;add secnd to accumulator

In the first line we see the .rs directive. The first part is the name or label, the second is
the directive, and the third is the amount of storage to be assigned to the name. In this
case we only needed 1 byte for each variable so only 1 was reserved for each. Also you
will notice that everything lines up vertically. This is only for readability for the
programmers sake of clarity. The rule that the assembler uses is that at least 1 space must
be between each part of the line. Also the names can be much longer, but I prefer short
names so I don't have to type so much. Plus by using short names, more space is left for
the comment field. Comments are very important. When you initially write a program,
the tendancy is not to write much in the comment field because you're in a hurry. But if
you have to come back to it a few weeks later, it's much easier to understand what you've
written if you've taken the time to write good comments. Also good comments help in
debugging.
In this example, we reserved two bytes and stored a 2 in each one. Then we got the first
one (first) and put it into the accumulator. Then we added the second (secnd) to the
accumulator. Now the accumulator has a 4 in it, and first and secnd still have a 2 in them.
This method also has the advantage of being able to not worry which byte is first and
which byte is secnd, because the assembler takes care of it for you. The difference
between a .rs and a .equ is that with the .equ you tell the assembler exactly which bit you
want to name, where with the .rs, the order of the reserve storage determins which byte is
which. It really doesn't matter which is which, because you refer to them as names
instead of actual locations.
In the DS5000, there are several ways of addressing variables. One is the register
addresses, r0, r1, r2, and so on. Another is by direct addressing. This uses the actual
address in internal ram where the variable is. Using a name with the .rs directive is a form
of direct addressing. Here the assembler assigns the actual address of the named variable
by which order the .rs's are in. If the variable first was actual location 0 then the variable
secnd would be actual location 1. Location and address mean the same thing. When the
assembler assembles the program, the names are dropped (to protect the innocent) and the
actual addresses are used.
Another form of addressing is called immediate addressing. This is the form used in the
original 2 plus 2 problem. It is indicated by the # symbol in front of the number. This tells
the assembler that the number is in the instruction, not somewhere else. Also this method
was used in the above example where the variables first and secnd were loaded with a 2.
The 2 was in the instruction not somewhere else. But when we started getting the
numbers to add we used direct addressing, using the names. Here the 2's were in locations
and not in the instruction. Here are those instructions and what type of addressing was
used in each:
first: .rs
secnd: .rs
mov
mov
mov
add

1
1
first,#2
secnd,#2
a,first
a,secnd

;this is a directive not an instruction


;so is this one
;direct,immediate
;direct,immediate
;implied,direct
;implied,direct

Here's the original 2 plus 2 problem and the addressing modes of each instruction:
mov r0,#2
mov a,#2
add a,r0

;register,immediate
;implied,immediate
;implied,register

Notice that each instruction can have two different addressing modes, one for the
destination and one for the source. Also notice that the a is an implied address. Register
refers stictly to the registers r0, r1, r2, and so on, even though I've referred previously to
the accumulator as a register. This comes partly from my experience with the Zilog Z-80
microprocessor, where the accumulator is a register. In the DS5000 the accumulator is in
the special function registers but the instruction implies that the accumulator will be used
as either the source or the destination, depending on the instruction. Implied is a method
of addressing that shortens the number of bytes any particular instruction assembles into.
To digress just a little here, an instruction like add a,r0 is a one byte instruction. In
other words this instruction would end up inside the DS5000 as one byte. Part of the byte
is the opcode and the other part is which register is affected or used. The reason for this is
that a prime concern in programming a micro is how may bytes the program will actually
take up inside the micro, after it's been assembled. The idea is to cram as much as
possible into as few bytes as possible. This is why implied addressing is used. It limits
choices in the use of the instruction, you always have to use the accumulator as either the
source or the destination, but it shrinks the size of the instruction, so that more
instructions can fit inside the micro. This is a choice made by the maker of the micro, and
is not up for discussion. It's a trade off of flexibility vs. size. That's why you'll see lots of
instructions that use the accumulator. This is the best way to describe implied addressing.
In the case of an instruction like add a,secnd ,two bytes are assembled. The first byte
says that this is an add instruction and that the accumulator is implied as the destination.
The second byte is the direct address of the source variable, secnd. This is transparent to
the programmer because we are using an assembler, but the underlying results are
noteworthy when trying to cram the most into the DS5000. Well enough of that. We will
probably get into this again, later.
Another form of addressing variables is called register indirect or just plain indirect
addressing. This is a little more complicated. Here the address is held in a register, either
r0 or r1. The following is another example of the 2 plus 2 problem using register indirect
addressing. [This is prob05.asm]
buffer: .rs
mov
mov
buffer
inc
mov
buffer
mov
mov
inc
add

2
r0,#buffer
@r0,#2

;reserve two locations for the data


;set r0 to the start of the buffer
;put a 2 into the first location of the

r0
@r0,#2

;increment r0 to point to the second byte


;put a 2 into the second location of the

r0,#buffer
a,@r0
r0
a,@r0

;set r0 to the start of the buffer


;get the first 2
;step to the second 2
;add the second 2 to the first 2

Line 1 is again a reserve storage directive, but this time we are reserving two locations,
one for each 2 in the problem. Line 2 sets r0 to point to the first location of the buffer
we've created with the .rs directive. As stated before, the # means immediate, but in this
case the assembler sees that the first character after the # is not a number, and, instead
finds the label buffer and gets the direct address of where it is in the internal ram and
puts that address in r0, immediately. That's a mouthfull but that's what happens on that
one line!
Line 3 stores a 2 in the first location of buffer. The @ symbol tells the assembler that
the following register holds the address (indirect) of where to put the 2 into. So after this
instruction, the first byte of buffer has a 2 in it. Line 4 increments or steps r0 to the
second location in buffer. If r0 had the address of the first byte, then incrementing it by
1 now results in the address of the second byte being in r0.
Line 5 does the same thing that line 3 did, except that the 2 is stored in the second byte of
buffer. Line 6 does the same thing that line 2 did, getting r0 to point to the first location
in buffer. Line 7 moves the first byte of buffer (the first 2)into the accumulator. Line
8 steps r0 to the address of the second 2 in buffer. Line 9 adds the second 2 to the first 2
and stores the result in the accumulator.
Repeating what we did for the other example programs, here are the addressing modes of
each line:
buffer: .rs
mov
mov
inc
mov
mov
mov
inc
add

2
r0,#buffer
@r0,#2
r0
@r0,#2
r0,#buffer
a,@r0
r0
a,@r0

;this is a directive, not an instruction


;register,immediate
;indirect,immediate
;this is arithmetic, adding 1 to r0
;indirect,immediate
;register,immediate
;implied,indirect
;arithmetic, adding 1 to r0
;implied,indirect

Here is another example of the 2 plus 2 problem done still a different way: [prob06.asm]
buffer: .rs
mov
mov
mov
add

2
#buffer,#2
#buffer+1,#2
a,buffer
a,buffer+1

;reserve 2 bytes for the data


;put a 2 in the first location in buffer
;put a 2 in the second location in buffer
;move the first 2 into the accumulator
;add the first 2 to the second 2

Line 1 reserves 2 bytes and names it buffer. Line 2 puts a 2 into the first location of
buffer. Line 3 puts a 2 in the second location of buffer. Line 4 moves the first 2 into
the accumulator. Line 5 adds the first 2 to the second 2 and stores the result into the
accumulator. There are still other ways to solve this problem. In fact, if a problem was
given to 100 programmers, there would probably be 100 different programs, all with the
same results.

Lastly, I want to explain something else about the assembler. Since there are two distinct
memory areas in the DS5000, there must be a way to describe to the assembler which one
is being referred to at any particular place in the source file. The source file is what the
above program, or any program that has been written, is referred to. It is the source for
the assembler, or the file that is going to be read by the assembler to generate the object
file (the object of the assembler) from. The object file is the file that will be download to
the DS5000. They are two different files. One you've written with a text editor (the
source file) and the other is created by the assembler (the object file) when you assemble
the source file. You use an assembler with the object in mind of generating a file to
download to the micro, hence the name, object file.
I've left out some directives from the previous programs, for simplicities sake, that I need
to mention now. One is the .org directive. It is the originate or origin directive. This tells
the assembler at what address the first byte of assembled code is to be placed inside the
DS5000. It is the origin of the program or the beginning. Here's how this would look for
our last example program:
.org h'0000
mov r0,#buffer
mov @r0,#2

;set r0 to the start of the buffer


;put a 2 into the first location of the

inc r0
mov @r0,#2

;increment r0 to point to the second byte


;put a 2 into the second location of the

mov
mov
inc
add

;set r0 to the start of the buffer


;get the first 2
;step to the second 2
;add the second 2 to the first 2

buffer

buffer
r0,#buffer
a,@r0
r0
a,@r0

.segment .memory
.memory
.org h'00
buffer: .rs 2

The first part is the same with the exception of the .org. This tells the assembler that the
first byte of code will be assembled at address 0000h, in this case. Later in the program is
another directive called .segment. This tells the assembler that a different area of
memory named .memory will be used now. This represents the internal ram. The next line
has a .memory directive. This switches to the internal ram memory. The .org following
this is telling the assmbler that the first .rs is located at address 00h. So the address of
buffer is 00h or just 0. This is the value or address that is loaded into r0 when the mov
r0,#buffer is executed.
Well we've covered quite a lot in this lesson, and I hope you've gotten most of it. If not, I
would suggest re-reading it until you do. I would also suggest that you print out all of
these lessons so you can refer to them later. In the next lesson we will actually be
assembling these programs and running them in the simulator for a closer look at what
happens inside the DS5000. This is where it really gets good.

My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

Using an Assembler and Simulator


I would suggest that you create a directory somewhere on your hard drive called
COURSE and put the following files in this directory. They are all zipped and you will
need PKUNZIP from PKWARE or a similar unzipper to extract the files. All these
programs are DOS based and should be ran under DOS. If you are using WIN3.1 or
WIN95 you'll need to get to DOS. If you're a power user you could setup icons to run
these programs, but my explanations and examples will assume that you are at the DOS
command line C:\COURSE prompt.
I've spent several hours searching the net to find all these files. All are either shareware or
freeware and there should be no ethical problems using them for this course. You should,
however, purchase all of them in the event you continue using them after the course. I
can't give you license to use them, but for educational purposes, I don't think that there
will be any problems. Most of these programs have manuals that explain how they work.
Print these out for future reference. I've lost the docs for QEDIT and BR but if you've
ever used a text editor or browser, you shouldn't have any problem using them.
The assembler we are going to use is made by Pseudo Corp and can be downloaded here.
The simulator is made by HiTech Corp and is here. The examples from lesson 6 are here.
The text editor QEDIT , by SemWare, can be downloaded here. A text browser can be
downloaded here. Any text editor can be used to create the assembler source files. You
could even use notepad in Windows. I am giving you the ones in lesson 6 already typed
up. Also, a handy 8051 reference can be downloaded here. It is a TSR that can be loaded
once before starting the assembler or simulator, and then invoked by pressing a couple of
keys together. It has some good info and might be useful in the process.
For those of you who just can't wait, here is a rough draft of the system we will use later
on in this course for training purposes. It's isn't finished (all the parts are not marked) but
all the pin numbers and the general connections are complete, and could be built, but
don't. I'll give you the real thing later on. There is a text file describing how to print it out,
read it before trying to print it!
The first step in writing a program is to define the problem as completely as possible.
You will always think of things as you go along that you left out. After thinking out the
problem, you start by typing up a source file. I have already typed up all the sample files
in lesson 6 and assembled them. The source files all end with the .asm extension. The
extensions created by the assembler are .obj which is the object file and .lst which is

the listing file. The listing file shows all the addresses resolved by the assembler, all the
code generated and all the comments. This file is very important in using the simulator.
In the simulator, there are no labels shown because the simulator really dis-assembles the
object file as it is loaded into the simulator. No names are listed in the simulator so the
listing file is used to keep up with where the simulator really is. You'll see all this as we
go along. The listing file also shows any errors in the assembly process so that you can
correct them. It is crucial that you look at the listing file to be sure that there are no errors
listed.
One note, the assembler creates object files with the extension .obj but the simulator
looks for object files with the extension .hex. So you have to rename them after
assembly before loading them into the simulator. Sorry about that, but that's the way it
works. I have already renamed the problem object files for you, but any you create will
have to be renamed each time you assemble a file before loading them into the simulator.
The simulator is a way to test your programs without downloading them to the DS5000.
This means, for our purposes, you don't have to have a DS5000 to learn to use a DS5000.
The way you would use it in real life is to write each program, assemble it, and then load
it into the simulator. After all the different pieces of the whole program have been tested,
you would combine them into one file, possibly test it again, and then download it to the
DS5000. This particular simulator isn't fancy, and there are better ones, especially the one
from Pseudo Corp, the one I use when I'm making something ($199). But this one is free
and will do for our purposes. The only thing that it can't do is simulate an interrupt, but
this can be worked around and I'll show you how later.
We are going to look at some of the examples from the last lesson and watch them in
action in the simulator. We're not going to look at all of them, but you can look at the
others on your own. The first we will look at is PROB01.ASM. I am assuming that you
followed my directions and created a directory to hold all the files listed on this page.
These should include the assembler program, the editor program, the simulator program,
and all the example files from lesson 6. Also you are in that directory at the DOS prompt.
The first thing to do is start the simulator. Do this by typing SIM51EM and press ENTER.
You are now looking at the simulator screen with all the various sections of the 8051.
The screen is divided into six sections. Each section is separated by dashed lines. The top
section is the Registers section.On the left side starting on the 8th line are the names
Addr, Opcode and Instructions. This is the Code section. On the Right starting on line 4
is the Internal RAM section. Below that is the External RAM section. Below that is the
Program memory section. Below that is the Function Key assignments and what each
does. To the left of that is two boxes, one that says File and the other says Select. Below
that is the command line with the flashing cursor.
Of these sections, we will be concerned with the Register, Code, and Internal RAM
sections, the function keys, and the command line. I will describe each as this discourse
continues. Notice that in the function key section, the F7 File IO is highlighted. Also on
the far left of that, a box that says FILE is shown. Inside that box are Name file, Read

file, and Write file. These are the commands that can be entered on the command line.
Only the capital letter of each is used. So to name a file you press N, to read a file you
press R and to write a file you press W.
First we must name the file we want to load into the simulator. Press N and enter
PROB01.HEX and press enter. Now PROB01.HEX should appear on the line. Press enter
to accept it. Now press R(READ) and Y to accept PROB01.HEX. PROB01.HEX is now
loaded into the simulator.
Looking at the Code section we see under Addr a column of 4 digit numbers. On the first
line we see a 0000 followed by 7802, MOV R0,#02. The 0000 says that this is location
0000 in Program memory ( the first location). The 7802 is the actual two byte Opcode
that is assembled from the MOV R0,#02 instruction. The 78 says that this is a MOV
immediate into R0 and the 02 is the value moved into R0. Also notice that this line is
highlighted. This is the instruction that will be executed next. Notice above the Code
section is R0 and the value below it is 00. This is what is currently in R0. Also notice that
at the top of the screen in the Register section is ACC and below that a 00. This is what is
currently in the accumulator. And lastly notice the PC and the 0000 below it. This is the
current value in the Program Counter.
The next thing we must do is to start executing the program. To do this first press F5.
Notice the left two boxes have now changed to Breakpoint and Misc. These are the
commands that can now be entered on the command line. Notice the Single step in the
right box. To single step the program one instruction, press S. Do that now, but only
once. Notice that now in the Code window the highlight is on the next line. Also notice
that R0 now has a 2 in it. We have just executed our first instruction, to put a 2 in R0.
Notice that the ACC is still 0. Also notice that the PC is now 0002. Remember that the
PC always points to the instruction that will be executed next. Now press S again. Now
the ACC has a 2 in it as does R0. Also the PC is now 0004. We have just executed the
second instruction, to move a 2 into the accumulator. We are now ready to add 2 plus
2.Now press S again. Now the accumulator has a 4 in it. This is the result of adding 2
plus 2. This also finishes this program.
Notice that in the Code window below the three instructions that were in this program
that the rest of the lines have NOP in them for the Instruction. Also notice that the
Opcode is 00. A NOP is an instruction that does nothing and happens to have an Opcode
value of 00. These are here because all the rest of Program memory has 0's in them,
which we didn't load with anything, but are interpreted as NOP's. This is normally what
you will see following the last instruction in a program.
In review, the Opcode is the actual value that the assembler assembles for an instruction
and is what the micro understands and can execute. The Instruction is the english form of
the Opcode and is for the benefit of the programmer.
To exit the simulator, first press F1 and then E and Y. This completes the first session
with the simulator. You can simulate this program again by repeating the previous steps.

Next we will simulate PROB04.ASM. Start the simulator, name PROB04.HEX, and read
it. Also you will need the list file for this program (PROB04.LST) printed out for
reference. You need to set your printer for condensed because the lines are longer than 80
characters.
About the listing file PROB04.LST, you'll notice that this resembles the source file
PROB04.ASM but with three extra columns of numbers to the left. The left most column
is line numbers and is just for reference. The next column is addresses or memory
locations. They mean different things depending on what part of the listing your looking
at. Notice that on line 3 there is an org 8. This is telling the assembler to start reserving
internal ram locations with location 8. The next line has an 8 in the second column. This
is the location reserved for the variable first. Line 5 has a 9 in this column. Location 9
is reserved for the variable second. I mention these because in the simulator there are no
labels shown but instead you'll see the numbers 8 and 9 used. This is some of what the
assembler takes care of for you. You get to reference the variables by name in the source
file, while the micro uses numbers to reference them. Now look at line 7. Here is an org
0. This tells the assembler to start assembling code at location 0000h. Notice that on line
8 there is a third column of numbers. This is the actual opcode generated by the
assembler for a given instruction. You will see these numbers in the simulator in the code
window along with the numbers in the second column.
I tell you this to show you the relationship between the listing file and the code window
in the simulator. Since there are no labels in the simulator, the listing file can be used to
interpret the code window in the simulator and keep up with where you are in the
simulator.
With the simulator started and PROB04.HEX loaded, notice that all the registers have a 0
in them to start with. Also notice the first 4 lines in the code window. These are the 4
instructions in the listing file. See the simularity between the 2nd and 3rd colums in the
listing file and the code window. Also notice that none of the lines in the listing file
above the instructions are listed in the code window. All these lines are for the assembler
only and don't generate any code.
Now single step once. Notice that in the Int Ram window, on the second line there is an
0008 and beside that an 02. This is the 02 that was loaded into first which had location
8 reserved for it by the assembler. So the result of MOV first,#2 was to put a 2 in
location 8 of the internal ram. The layout of the Int Ram window is the location followed
by 8 bytes of data. The 8th byte on that line is location 07h. The next line starts with
location 08h and goes through location 0fh. The next line starts with location 10h and
ends with location 17h, and so on.
Now single step once. Notice that location 09 in the internal ram now has a 2 in it. Single
step again and notice that there is now a 02 in the accumulator. We have just moved the 2
from location 8(first) and put it in the accumulator. Single step again and notice that the
accumulator has a 04 in it. We have just added location 9(secnd) to the accumulator with

the result in the accumulator. We are now done with the program, and the second session
of using the simulator. Exit the simulator to get ready for the next and last example.
Now start the simulator and load the PROB03.HEX file. In this example we will modify
port lines to simulate a switch being closed. Remembering back to PROB03.ASM, p1 bit
0 was where we connected the switch and p0 bit 0 is the line connected to the LED. If the
switch is closed we will have a 1 on p1 bit 0. If we have a 1 on p0 bit 0, the LED will be
lit.
Notice that in the Register window in the simulator, on the left side line 3 is P0 followed
by FF and 11111111. The FF is the hex representation of 11111111b. The 1's are the
individual bits or lines of p0, with the left most being bit 7 and the right most bit 0. The
same holds true for p1. So currently there is all 1's on p0 and all 1's on p1. So the switch
is closed and the LED is lit. Also notice that across the top of the register window is the
CY or carry flag and it is currently a 0. Also the first line in the code window has the
instruction MOV C,P1.0. This says to move port 1 bit 0 into the carry flag. Single step
once. Now see that the CY has a 1 in it. We have just moved the 1 on bit 0 p1 into the
carry flag. Single step again. This moved the 1 in the carry flag to p0 bit 0. But since we
already had a 1 on that bit, no difference was noticed. Notice that the highlighted line in
the code window is a SJMP 0000. Notice that in the listing file PROB03.LST there is a
label on line 8 called start. Also notice in the address column is a 0000h. So the label
start is at location 0000h. On line 10 of the listing file is a sjmp start. This says to
jump to the label start. Since start is at 0000h, that is the address shown in the code
window. This says to jump to location 0000h. This again illustrates the relationship of the
listing file to the code window Single step once more. Now we are back to the first
instruction.
Now we are going to set bit 0 of p1 to a 0. Do this by first pressing F6. Now press I for
internal ram. Now F for fill. Now enter 90 for the start, 90 for the end, and 00 for the
byte. Answer Y to the following prompt. Now see that p1 has a 00h or 00000000b in it.
We have not only put a 0 in bit 0 but a 0 in all the bits. We put a 90h for the start and stop
address for the fill because p1 is at address 90h in the 8051 and can't be changed. If you
notice in the listing file all the names and numbers that follow the program listing. These
are all the reserved names in the assembler and the corresponding addresses of each
inside the 8051. Find P1 and you will see the address 0090 to the right of it. This is the
address of p1 in the 8051. So we entered a start for the fill of 90 and a 90 for the end of
the fill. This selected the one location for the fill. You can specify a range of addresses
and fill all of them at once. That is why it asks for a start and an ending adress for the fill.
But since we only wanted to alter p1, we entered the same address for the start and end.
Now the switch would be open. Press F5 to switch back to Execute. Now single step
once. Notice that the carry flag now has a 0 in it. We have just moved the 0 in p1 bit 0
into the carry flag. Single step once. Notice that bit 0 of port 0 now has a 0 in it and the
rest of the bits are still 1's. The LED would now be off. Single step once. Now we're back
to the first instruction.

We can now put all 1's back into p1 to simulate the switch being closed again. Do this by
repeating the above steps only when it asks for the byte of data, enter FF (make sure
CAPS is set on your keyboard or it won't accept the entry) instead of 00. Now single step
through the instructions again and you will see a 1 in p0 bit 0. This would again lite the
LED. You can now exit the simulator. We have finished this program example.
Well this has been somewhat sticky, having to look at the listing file and the code
window, navigating the simulator, loading programs and altering register contents. But I
hope this has given you some insight into the simulator, the relationship of the listing file
to the code window, and some differences between the assembly source file and the hex
object file that is generated by the assembler. The fact that labels are dropped in the
assembly process and replaced by absolute addresses for the micro to use. How the
assembler lets you write a program using english names and labels in a form much easier
for the programmer to keep up with. Without an assembler, the task of writing a program
would be extremely difficult, since what ends up inside the micro is just numbers.
In my early days of micro building I hand assembled each instruction into the hex
opcodes and programmed an EPROM with them. The time consummed this way was
enormous. It was an exercise in converting assembly language into opcodes that, in and
of itself, was a total waste of time. I had to keep an intense concentration level while
doing this, and one mistake would cause the program not to work. I would then have to
go through my hand written program and not only check the logic of my instructions, but
whether I had properly assembled them into opcodes. I never want to do that again. The
tools I have furnished you with, I would have given practically anything to have had back
then. There were such tools but they cost several thousand dollars, way beyond my price
range. I considered my time to be cheap and went the route I did for that reason. But time
isn't cheap in reality, and anything you can use to better utilize it is a must. I probably
should have came up with the money to buy these tools back then but I didn't. One
project I worked on for Levi Strauss, took me 18 months to finish. With these tools I
could have done it in a few weeks, I'm sure. Well enough of that.
In the next lesson we will look at the second phase of writing a complete system. The
first phase is to define the system and what you want to accomplish with it. The next is to
write all the subroutines and functions that will be repeated throughout the system. These
are called the BIOS (Basic Input/Output System) routines and OS (Operating System)
routines.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html
.

BIOS and OS Routines


When writing software a common practice is to decide what functions or routines will be
needed to complete the project. These can include conversion routines, routines to scan
different pieces of the system, or any routine that can be written and then called by either
the operating loop or the interrupt routines. Typical examples could be a routine to
convert binary to decimal, convert decimal to binary, convert binary to ascii, and convert
decimal to ascii. Others might be to get current time, scan an analog point and convert it
to binary, output a character on the serial port, write a character to an LCD display, read a
character from a keyboard, and many others. By writing each of these routines, and
debugging them separately, the development time is generally reduced. Plus the task of
debugging the final software is simpler, because each of these routines have been
debugged previously and are known to work. In a PC these routines are called the BIOS
and DOS. BIOS stands for Basic Input/Output System and DOS stands for Disk
Operating System. In the devices we will build there won't be a disk but there will be an
Operating System and BIOS. In this lesson we will look at these more closely and start
writing them.
The first we will write is a routine to convert binary to decimal. In the micro, all numbers
used are binary, but we usually want to be able to display them in decimal for easy
reading.The following is a binary to decimal conversion routine. It takes a binary number
in the accumulator and returns with the decimal number in r3, r4, and r5. R3 will hold the
hundreds digit, r4 the tens digit, and r5 the units or ones digit. In an 8 bit location, the
largest number is FFh or 255. Following is the routine bin2dec (bin2dec.asm)
bin2dec:

mov
div
mov
mov
mov
div
mov
mov
ret

b,#100
ab
r3,a
a,b
b,#10
ab
r4,a
r5,b

;set to divide by 100


;divide acc by 100
;move the hundreds digit into r3
;move remainder into acc
;set to divide by 10
;divide acc by 10
;move the tens digit into r4
;move the ones digit into r5
;done, exit the routine

To this we add up front:

start:

.org h'0000

;start assembly at 0000h

mov a,#h'ff
acall bin2dec
mov a,#h'64
acall bin2dec
sjmp start

;load a FFh(255) into acc


;convert to decimal
;load a 64h(100) into acc
;convert to decimal
;jump to start

By adding this you can see how a function or subroutine is called by the operating
system. Now download bin2dec.zip , unzip it and start the simulator and load (read)
BIN2DEC.HEX. As before, all the registers are 0's to start with. Print out the listing file

BIN2DEC.LST for reference. Using the listing file is even more crucial here because we
are calling a subroutine. To follow the code window's contents, while single stepping the
program, the listing file is a must.
Notice that there are two labels in this listing. One is the start of the operating loop and
the other is the start of the binary to decimal subroutine. Also notice that the subroutine
ends with a ret instruction. This instruction is similar to a sjmp except that it returns to
the instruction after the one that called it (acall bin2dec). In this way we can do several
binary to decimal conversions with the same routine. Also when a call is performed
another register comes into play. It is called the Stack Pointer (SP).
The stack is a device to hold the return address for a call. What happens is that before
branching to the subroutine, the location of the next instruction after the call is pushed
onto the stack Pushing is another word for storing, but it relates to the stack only and
usually return addresses. When the return is encountered the return address is popped off
of the stack and placed into the PC so that the next instruction fetched is the one after the
call. This is sort of like stacking (pushing) books in a pile, one on top of another with the
most recent addition on top of the stack. Then as a book is taken off of the stack (popped)
the next one is on top. As each byte of the two byte return address is pushed onto the
stack, the stack pointer is incremented once. Then when the return is encountered and
each byte of the two byte return address is popped off of the stack, the stack pointer is
decremented.once for each byte. The end result is that the stack pointer is always pointing
to the current position on the stack where the next byte of address would be popped off.
To explain this again a little more explicitely, when the call is encountered, the stack
pointer is incremented and the first (low) byte is pushed onto the stack. Then the stack
pointer is incremented again and the second (high) byte is pushed. When the return is
encountered, the first (high) byte is popped off the stack and the stack pointer is
decremented. Then the the second (low) byte is popped off of the stack and then the stack
pointer is decremented. The stack pointer provides the address in internal ram where the
first byte that is to be popped is located. This is a little confusing to some but it happens
automatically for calls and returns.
But the stack can be used to pass data between routines and is commonly used in C and
other languages. The data to be passed is pushed onto the stack and the other routine is
called or jumped to and the data is popped off of the stack into registers for use by the
second routine. Care must be taken to make sure there are an equal number of pushes and
pops. One of the hardest bugs to find is a mismatch in pushes and pops. It causes the
program to act in wild and mysterious ways that is difficult to decipher. I don't want to
dwell on this too much but, simply put, there must be a return for every call and a pop for
every push or there will be problems.
I mention the stack pointer here because you will notice it's value changing as we single
step through the program. Each time we call the bin2dec routine, it will be incremented
by 2 and each time we return from the routine, it will be decremented by 2. Also the stack

usually starts after any variable storage, usually residing towards the end of the internal
ram.
In the 8051 there are two call instructions, the acall (Absolute Call) and the lcall (Long
Call). The acall is a shorter instruction (2 bytes) but is limited to use where the entire
program is in the first 2048 (2K) bytes of program memory. The lcall is longer (3 bytes)
but can call to anywhere in program memory. The only reason to use the acall is to save
space in program memory. It should be avoided usually and the lcall used instead.
Back to the simulator. Single step once. The accumulator now has an FFh in it. Notice the
value of the stack pointer (SP). Single step again. Now we have called the bin2dec
routine and the stack has been adjusted. Single step again. Now the b register has a 64h
(100) in it.
To digress a moment, the div instruction uses the b register and the accumulator. The
accumulator is divided by the b register and the dividend ends up in the accumulator and
the remainder is left in the b register. So to get the hundreds digit, the accumulator is
divided by 100. The hundreds digit that ends up in the accumulator is then stored in r3.
The remainder in b is moved into the accumulator and the b register is loaded with a 10.
Then the accumulator is divided by 10. The tens digit is now in the accumulator and the
remainder, which is the ones digit is in the b register. The routine is finished by moving
the tens digit from the accumulator into r4 and the ones digit in the b register into r5. The
routine is now finished and the return is executed.
Single step again. Now a 2 is in the accumulator. The remainder is in b. Single step again.
The 2 has been moved into r3. Single step again. The remainder in b is now moved into
the accumulator. Single step again. The b register has a 10 in it now. Single step again. A
5 (the tens digit) is in the accumulator and a 5 (the ones digit) is in the b register. Single
step again. R4 now has the tens digit. Single step again. R5 now has the ones digit.
Notice in the internal ram window that location 08 has a 4 and location 09 has a 0. Now
look at the location of the next instruction after the acall in the listing file. It is 0004h.
You're looking at the stack with the return address in it. Single step again. The return has
been executed and the PC is now pointing to the next instruction after the acall.
You can now single step at your leisure and watch the next conversion take place, starting
with a 64h (100) in the accumulator and ending with a 1 in r3, a 0 in r4 and a 0 in r5. You
can exit the simulator when you want and this session with the simulator is over.
Next we will write a routine to convert decimal to ascii. Ascii is used by most display
systems and by the LCD display that we will eventually use in our Micro Lab Kit. Since
as before, all the numbers used by the micro are binary. We have written a routine to
convert the binary to decimal and now we will convert that to ascii so that we can display
it.
The routine we are going to write will take the 3 digit decimal in r3, r4, and r5 and return
with 3 digit ascii in r3, r4 and r5. To convert a decimal number to ascii, all you have to do

is add 30h to it. In other words a decimal 0 is ascii 30h and a decimal 9 is ascii 39h.
Following is the dec2asc routine.
dec2asc: mov
add
mov
mov
add
mov
mov
add
mov
ret

a,r3
a,#h'30
r3,a
a,r4
a,#h'30
r4,a
a,r5
a,#h'30
r5,a

;get the hundreds digit


;add 30h to it
;move it back into r3
;get the tens digit
;add 30h to it
;move it back into r4
;get the ones digit
;add 30h to it
;move it back into r5
;done, return

We will now add this to the previous routine and call it bin2asc.zip. You can download it
and unzip it, print the listing file, start the simulator and load BIN2ASC.HEX. You can
see that we have added a call to do the conversion from decimal to ascii and the decimal
to ascii routine. Also I changed the two numbers to be converted to FEh (254) and 7Bh
(123). This is done to better see the results since each digit is different. Single step
through the instructions and take note of the internal ram window at the far right. As the
numbers are converted to ascii, the area where there are normally a bunch of periods will
now show the ascii numbers 254 or 123.
There are many more routines to write, but the time is getting near where we need some
hardware to experiment with. I hope that in this lesson you've seen how an operating
system operates and how we write subroutines to be used by the operating loop. The loop
in our example began at start and looped at the end back to start. In the finished operating
loop there will be a lot more code and many more subroutines, but the concept is
identical to our examples.
In the next lesson we are going to discuss interrupt routines and how to use them. In the
meantime I am going to start work on a printed circuit board to make the main processor
board for our Micro Lab Kit.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

Interrupts and the Operating System

In a typical system, the software can be divided into 3 possible groups. One is the
Operating Loop, another is the Interrupt Service Routines, and the last is the BIOS and
OS functions and subroutines. The Operating Loop is the main part of the system. It will
usually end up being a sequence of calls to BIOS and OS subroutines, arranged in an
order that accomplishes what we set out to do, with a little manipulation and data transfer
in between. At the same time, at least it LOOKS like it's happening at the same time, the
interrupts are being serviced as they happen. In the DS5000, there are seven possible
events that can trigger an interrupt. Two of them are from external interrupt inputs, that
can be from whatever hardware we've added to the DS5000 that we deem to need
servicing as soon as they happen. Two more are from internal timers or counters that
cause an interrupt when a certain count has occurred. One is a Power Fail that happens
when the power to the DS5000 has dropped below a certain voltage. The last two are
from transmitting or receiving data through the serial port.
To digress just a moment, there are two ways to service, or act on, events that happen in
the system. One is to scan them and the other is to use interrupts. Scanning is just what is
sounds like. Each possible event is scanned in a sequence, one at a time. This is ok for
things that don't require immediate action. Interrupts, on the other hand, cause the current
process to be suspended temporarily and the event that caused the interrupt is serviced, or
handled, immediately. Remember, from a previous lesson, that the routine that is
executed as a result of an interrupt is called the interrupt service routine, or the interrupt
handler routine.
In most systems, and ours in particular, there is a mixture of both. The choice of which to
use depends on how long you can wait to take action on an event. For instance, when a
character is received through the serial port, it must be read and stored somewhere for
later use, and done quickly enough that the next character received doesn't overwrite the
previous one. The serial port is double buffered. This means it can be receiving one
character, while still holding the last character it has previously received. Remember, or
understand, that the serial port gets it's name from the fact that characters are received as
a serial stream of individual bits, one bit at a time. When 8 bits have been received, this
byte is transferred to a one byte receive buffer, at which time an interrupt is generated,
and the serial receiver can start receiving the next character. If the operating loop is in
some conversion routine that takes a while to finish, and a character comes in on the
serial port, this conversion needs to interrupted for long enough to get the received
character and store it in a buffer area, and then return to finish the conversion routine,
without loosing any serial data.
The number of interrupts available for use is limited and part of the system designer's job
is to figure out which events need interrupts and which ones can be polled, or scanned.
Some, like the serial port and the power fail, are dedicated and can't be used for anything
else. But one of the two timer/counter interrupts and the two external interrupts can be
used for whatever the designer sees fit. I normally like to take one of the two external
interrupts and apply the 60 hz line frequency to it so that the micro is getting interrupted
60 times a second, all the time. This allows for, what appears to be, two separate pieces
of software to be running at the same time. The operating loop, doing its scheduled tasks,

and an interrupt routine that is doing several jobs that need to be done during the same
time period. An example of one of these jobs is to update a real - time system
clock/calender that can be used by the operating system, to schedule tasks based on time
of day/date. Another job is to poll some parts of the system hardware often enough to
appear to be interrupt driven, when there isn't a dedicated interrupt that could be used.
What happens is that when the 60 hz interrupt happens, certain events are scanned to see
if they have happened. These events are therefore scanned every 1/60 th of a second. In
this manner a combination of polled - interrupt driven response to an event can be used to
expand the effective number of interrupts available to the designer. It's faster than just
polling, but not as fast as a dedicated interrupt.
In the DS5000, as with any micro that has interrupt capability, there is a method by which
the interrupt gets serviced in a timely manner. Remember how a call works, as we saw in
the previous lesson. When the call is executed, the next instruction's address that would
have been executed, is pushed onto the stack, and a jump is made to the start of the called
routine. At the end of that routine, a return is executed, which pops the previously pushed
address, and causes a jump back to the next instruction after the call. An interrupt does
essentially the same thing. When the interrupt occurs, and the current instruction that is
being processed is finished, the address of the next instruction to be executed is pushed
onto the stack. Then a jump is made to a dedicated location. This particular jump is called
a vector.
It's referred to as a vector, to differentiate it from a regular jump, although it's essentially
the same thing. Each interrupt has it's own vector, or unique location where it's service
routine starts. These are hard coded into the DS5000 and can't be changed. These vector
addresses start at 0000h in program memory. Following is a map of these locations and
the interrupt assigned to each.
0000h
0003h
000Bh
0013h
001Bh
0023h
002Bh

Reset/Power UP
External Interrupt Input 0
Timer 0 Interrupt
External Interrupt Input 1
Timer 1 Interrupt
Serial I/O
Power Fail Warning

These are the starting addresses of the service routines for each of the interrupt sources in
the DS5000. Notice one that I haven't mentioned before called Reset/Power Up. When
power is applied to the DS5000, program execution starts at location 0000h. Another way
to get to this location, other than jumping to it, is to do a reset of the DS5000. There is a
pin on the DS5000 called Reset. Triggering this input causes the DS5000 to vector to
0000h and start executing just like power up. The two differences between this and a
regular interrupt is that it doesn't push any address onto the stack, and it is recognized any
time it happens and can't be turned off. The other interrupts can be disabled by the
program, and while disabled, do not generate any interrupts. Also when power up occurs
or a reset occurs, the interrupts are automatically disabled and must be enabled before
they can be used. Reset does several other things that I will mention later.

Again let me digress a moment. About the jump instructions. In some of our previous
programming examples we had a jump instruction called sjmp (Short Jump). This
instruction can only jump to addresses relatively close to it. There is another jump called
ajmp (Absolute Jump) that can get to anywhere, as long as anywhere is in the first 2K
bytes of program memory. There is a third jump called ljmp (Long Jump). It can get to
any place in program memory. The short jump is good for small loops inside a routine.
The absolute jump is good when the entire program fits in less than 2K bytes. But to get
to any place, regardless of how far away it is, the long jump is used. The sjmp and the
ajmp are 2 byte instructions and the long jump is a 3 byte instruction.
Back to interrupts. You will notice that there isn't many locations between vector
addresses. What is normally done is that at the start of each vector address, a long jump
instruction (3 bytes) is placed, that jumps to the actual start of the service routine. This
way the service routines can be anywhere in program memory. The vector address jumps
to the service routine. There is more than enough room between each vector address to
put a long jump instruction. Looking at the table above, there are 3 locations for the Reset
/ Power-Up jump instruction and at least 8 locations for each of the other vectors. When
actually writing the software, at address 0000h will be a jump instruction that jumps
around the other vector locations. I usually jump to 0030h and continue with the rest of
the program. This leaves 5 locations for the Power Fail vector, more than enough room
for the 3 byte long jump instruction needed to get to it's service routine.
Besides being able to disable all of the interrupts at once, there is a way to enable or
disable them individually. There is a special function register (SFR) called the interrupt
enable register. Each bit in this register controls one of the interrupts. When a power-up
or reset occurs, all of these bits are cleared to 0's, which disables all the interrupts. Setting
anyone of them to a 1, enables that interrupt. Also there is a bit, that if 0, disables all of
the interrupts at once, regardless of the state of the individual enables. It is also cleared by
power-up or reset. So to enable any one interrupt, the global interrupt enable must be set
(enabled) and the individual enable bit for each of the interrupts must also be set. I
usually set each of the individual enables that I want to be active, and then, just before
entering the operating loop, I enable the global enable, enabling all of the interrupts at
once.
When the DS5000 is powered up or reset, after jumping around the interrupt vectors, the
first things that are done are called housekeeping functions, like setting the stack pointer
to the start of the stack, clearing out various locations, setting up the baud rate for the
serial port, and any other things that must be done to get ready for the operation of the
system. This is called booting up. The same thing happens in your desktop PC. This code
only runs once for a power-up / reset. It's also called initialization. It sets up a known
condition to start from, so that the system always (hopefully!) starts off the same way.
Following is a program segment that shows the first few locations of program memory
with a typical example of the interrupt vectors.
boot:

.org h'0000
ljmp cont

;start assembling at address 0000h


;jump around interrupt vectors

.org
ljmp
.org
ljmp
.org
ljmp
.org
ljmp
.org
ljmp
.org
ljmp
.org

h'0003
exint0
h'000b
timer0
h'0013
exint1
h'001b
timer1
h'0023
serio
h'002b
pwrfl
h'0030

;assemble at address 0003h


;jump to external interrupt 0 routine
;assemble at address 000bh
;jump to timer 0 routine
;assemble at address 0013h
;jump to external interrupt 1 routine
;assemble at address 001bh
;jump to timer 1 routine
;assemble at address 0023h
;jump to serial I/O routine
;assemble at address 002bh
;jump to power fail routine
;assemble at address 0030h

cont:

This is how our system will look when we're done. At the label cont: we continue with
housekeeping and initialization, having jumped around the interrupt vector locations.
Remember that the .org is an assembler directive that tells the assembler that the next
instuction assembled will be at the address in the .org. By using the .org, we can place
the jumps at the appropriate vector addresses that are hard coded in the DS5000.
There are other things about interrupts that we will cover as they come up, but this lesson
was to get you used to the idea of interrupts and what they're used for in a typical system.
Remember in a previous lesson we were standing at a busy intersection waiting for the
traffic light to change, when a person came up and tapped us on the shoulder and asked
what time it was. It didn't stop us from going across the street, it just temporarily
interrupted us long enough to tell them what time it was. This is the essence of interrupts.
They interrupt normal program execution long enough to handle some event that has
occurred in the system.
Polling, or scanning, is the other method used to handle events in the system. It is much
slower than interrupts because the servicing of any single event has to wait its turn in line
while other events are checked to see if they have occurred. There can be any number of
polled events but a limited number of interrupt driven events. The choice of which
method to use is determined by the speed at which the event must be handled.
Also, we have a third option and that is a combination of polling and interrupts by using a
continuously interrupting event and polling certain other events during each iteration, or
occurrance, of the interrupt. This method is faster than straight polling but slower than a
dedicated interrupt. It also has other limitations that we will discuss later on.
In the next lesson we will start looking at the various parts of our system.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

System Resources
In a typical system there are various parts, the choice of which depends on what the
design goal of the system is. There must be a serial interface to allow the downloading of
the program into the DS5000. There also must be an oscillator to supply the DS5000 with
a clock source. To permit trouble shooting problems, we need a way to break out of any
program that is currently executing, and examine various locations in the DS5000 to help
in the debugging process. To view the states of various pins on the DS5000 we also need
a logic probe. These are the parts that we will have on the basic processor board of our
system. There will also be a power supply to provide +5 volts for the system, and a
connector to take all the pins of the DS5000 off of the basic board so that they can be
connected to a proto board to allow us to connect up what ever we want to the DS5000.
Also there will be a connector that can be connected to an X10 interface to allow us to
use any of the inexpensive X10 modules that can be found at various suppliers like Radio
Shack.
If I wasn't clear about this before, the idea I am working from is to have the basic
processor board and a proto area connected to it to allow connecting any parts we want to
the DS5000. A proto board is just a matrix of holes, with connectors, on .1" centers that a
chip, or a part, can be plugged into and wire jumpers used to interconnect that chip to the
appropriate DS5000 pins. These proto boards come in a variety of sizes and shapes. They
are available at Radio Shack and other consumer stores that handle electronic parts. They
work very well for temporary construction, or proto typing, of circuits, hence the name,
proto boards. This will allow us to add things as we go along in this course and
eventually have a prototype system that will be able to do lots of things. It, however,
won't be a finished product, but merely a prototype. It won't be in a case, and the proto
board doesn't allow for much in the way of mechanical vibration stability. But it will
serve as a training platform and to allow you to design your own projects in the future.
In addition to the basic processor board and the proto boards, we will be using an A/D
converter and a 4 line by 20 character liquid crystal display (LCD). We will also have a 4
by 4 keyboard. This will be the extent of our training system.
I'm sure, at least for some of you, that all this jargon is fairly meaningless. But I will now
start explaining it in further detail.
First, all microprocessors need a system clock to drive them. This is not a clock like the
one beside your bed but, instead, an oscillator that produces a very stable frequency of
pulses. These pulses are applied the the micro on the clock-input pin. What happens with
it inside the micro is not important other than to say that without a clock, the micro won't
work at all. We will be using an oscillator that is in it's own chip package, that doesn't
require any support parts to operate. Just apply power to it and connect it to the DS5000
and we're done with the clock.

Next we need an interface that allows us to download a program to the DS5000. This is
implemented with a chip from Maxim called the MAX232 chip. It has 2 transmitters and
2 receivers inside it. To digress just a moment, we've talked about 1's and 0's in our
earlier lessons. Then they were just numbers. But when looking at the pins of the
DS5000, they are really voltage levels. Ideally a 0 is 0 volts and a 1 is +5 volts. But in
real life a zero is close to 0 volts and a 1 is close to +5 volts. In the DS5000 specs, a 0 is
any voltage less than about .8 volts and typically about .1 volts or less and a 1 is any
voltage greater than about 2 volts and typically about 4.8 volts. These are called logic
levels. Levels between .8 volts and 2 volts are indeterminate and usually indicate a bad
chip or an open connection.
But a serial port uses a different set of levels to transmit and receive with. They conform
to the levels set out in the specs for RS-232C where a 0 is close to -5 volts and a 1 is
close to +5 volts. To connect the serial port from a PC to the DS5000 we must convert
these levels from one spec to the other. This is the job of the MAX232 chip. It can take
the RS-232 levels in from the PC, through one of it's receivers, and convert them to logic
levels to the DS5000. It can also take logic levels out of the DS5000, through one of it's
transmitters, and convert them to RS-232 levels to the PC. In our system, a serial
connection requires 3 wires, a transmit, a receive, and a ground or common. The transmit
out of the DS5000's serial port goes through a transmitter of the MAX232 chip through a
connecting cable and into the receive of the PC's serial port. The transmit out of the PC's
serial port goes through a connecting cable through a receiver in the MAX232 chip and
into the receive of the DS5000's serial port. The ground or common on the PC's serial
port is connected to the ground of the DS5000. This completes the serial port
connections. There are 4 external capacitors connected to the MAX232 chip that allow it
to function properly but my idea here is not to go into the field of electronics and
capacitors. Just know that they are needed for proper operation of the MAX232 chip.
The next chip in our arsenal is a hex inverter chip. It gets the name hex from the fact that
there are 6 gates inside. Each one is an inverter. An inverter inverts what ever level is at
the input pin and puts that out on it's output pin. In other words, if a 1 is on the input, a 0
will be on the output. Conversly if a 0 is on the input, a 1 will be on the output. How this
chip is used would be difficult for me to explain to you in terms that you would
understand. So just accept for now that we need it to complete the system.
The last chip, besides the DS5000, is the power supply regulator. It takes a voltage of
about 9 Volts DC and outputs a very stable +5 volts DC for the system power. The +5
volts is normally referred to as Vcc, a term from the transistor days, but still used today.
I must take care not to get too deep into electronics here, because that is a another course
in itself. So some of what I tell you, you'll just have to accept on blind faith. Maybe you
will decide to get further into electronics on your own. That's how I started out.
Computers came later for me. You, on the other hand, are getting the cart before the
horse. I will impart on you some aspects of electronics as they come up, but I will try, for
your sakes, to keep it to a bare minimum. After all, this course is for people who have
very little knowlege of computers or electronics. To keep your interest level up on the

micro side, I must avoid getting too deep on the electronics side. This will make it
impossible to explain everything I would like to. But, like I've said earlier, chips are like
Tinker Toys, you just connect them together and go. In most case, not a lot of electronics
knowlege is needed, just the knowlege of how to connect them together ... logic.
There is another device commonly used in micros called an A/D converter. A micro only
understands 1's and 0's and to allow a micro to look at a varying voltage level, an analog
to digital converter must be used. It does just what the name implies, it converts a varying
voltage (analog) level into a corresponding digital number. The one we will use is a
National Semiconductor ADC0808. It has 8 analog inputs and gives an 8 bit value out as
a result of the conversion. It accepts a voltage in the range of 0 volts to +5 volts. If the
voltage is 0 the digital value will be 00h. If the voltage is +5, the digital value will be
FFh. This means that there can be 256 levels, including 0, that can be measured by the
micro. Doing a little math, this means that each one bit change in the digital value is
equal to a .02 volt or 20 millivolt change in the analog voltage. So as an example, if there
is a .02 volt level into the A./D, there will be a count of 01h as a result. If there is a 2 volt
input, there would be a count of 64h (100).
An example of an analog input is a temperature sensor. A temperature sensor outputs a
voltage that is directly proportional to the ambient temperature. The one we will use is
made by National Semiconductor called an LM34. It gives a .01 volt per degree farenheit
output. For instance, if the temperature is 100 degrees, the ouput will be 1 volt (100 times
.01). Another example could be a potentiometer or variable resistor. The volume control
on your radio is a potentiometer. Another example might be to measure the voltage of a
battery. Another is to measure light intensity to determin when it's night or day. A
microphone in each room to determin if the room is occupied, to turn lights on
automatically when you enter a room.
Another device we will talk about is a liquid crystal display. The one we will use has 4
lines of 20 characters each, for a total of 80 characters. It can display all the alphabet and
numbers plus some special characters like @#$%. It takes an ascii number and displays
it. This is why we wrote a binary to ascii routine in a previous lesson, to get ready to use
this display. This display can be used to display the time or temperature or anything you
want displayed.
The last thing I would like to mention is something called X10. It has been around for
several years and is an assortment of modules that allow anyone to control lights and just
about anything else plugged into 120 volt house power. There are also door sensors,
keypads, infrared motion sensors and many other accessories. New ones are introduced
occasionally and they are very cost effective and inexpensive to own. They communicate
over the power lines in your house, without any other connection. Just plug in a control
module, plug in the appliance to be controlled into the module and your done. There is a
particular module that lets a computer communicate with the other modules in your
house. You plug this module into an outlet, plug the computer into it, and your ready to
control lights, monitor doors, motion sensors, even your telephone!. Part of our design
project will be to make an interface that will allow us to connect our micro to the X10

computer interface. The rest will be up to you and the modules you want to control. An
example of the costs: a module to control a light is $15. The computer interface is $60. A
door sensor is $20.
The rest of the parts in our system will be odds and ends. A few resistors, capacitors,
transistors, switches, cable and connectors.
With all these components, we will be able to build lots of useful functions into our
system. Here is a list of possible uses we can put our system to performing.
1. A full-featured set-back thermostat to control your home airconditioning, to save you
money in power costs!
2. A home security system.
3. With the X10 capabilities, control of all of your appliances and lights.
4. A clock/calendar that can be used by all the other functions to add time/date to the
control functions. For instance the setback thermostat can adjust the temperature in your
house after you leave for work, but not do it on weekends. Or turn on security lights at
night and turn them off in the morning. Turn your children's TV off at bed time. Set your
coffee pot up the night before and have hot coffee ready for you when you wake up.
You'll probably think of other uses that are combinations of these or new ones that I
haven't thought of yet. The possibilities are limited by your imagination, and your pocket
book, of course. Other chips can be added that allow the system to TALK to you.
All these applications can be purchased off of the shelf at stores, but the cost is more and
you don't get the satisfaction of building and customizing them to suit your needs. I hope
this prospect interests you. If it dosen't, this is probably as far as you need to go in this
course. The rest of it is devoted to reaching this conclusion. Unless you intend to buy the
hardware described in the next lesson, the rest of this course will be hard to keep up with.
It will assume that you have bought the needed hardware and are following along.
The next lesson isn't really a lesson, but instead, the parts list, schematic, and all the
sources of the parts and their prices. We are just about ready to start building the
hardware, something I've been anticipating from the beginning. I am going to build all
this for myself, along with you.
For those of you who are going to end the course here, I hope I've given you food for
thought and an insight into the world of microprocessors and computers in general. I am
planning to offer a free course in basic electronics after I finish this one, so check back at
my home page in a few months and see if it has began. Good luck to you and God bless.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple

of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

The Basic System


Now starts the good stuff. Here begins the description and construction of the basic
development system I've been referring to in previous lessons. The source for most of the
parts will be Digikey Corporation. Understand that I'm neither affiliated with nor do I
have a preference as to which you use and, depending on where you live, there are
probably other sources. I will try to list the electrical characteristics of each part so that
where ever you get them, you will get the right one. There are some parts that can't be
obtained at either place and I will list them as they come up.
One more thing. You should, if you don't already have one, get a multimeter to use along
with this course. It will prove invaluable in trouble shooting problems with the system,
and you are basically blind without one. One can be obtained for around $20 that is
digital and is quite accurate. These used to cost over a hundred dollars at the least and
several hundred for a good one. CARE SHOULD BE EXERCISED WHEN USING
ONE TO MEASURE VOLTAGES GREATER THAN +5 VOLTS! especially when
measuring AC power outlets or the like.
For more information on electronics, check out this link. This is a nice tutorial on
electonics, and should prove useful.
We will begin with the system power supply. This consists of a transformer, a bridge
rectifier, a filter capacitor, a +5 volt regulator chip and a heat sink for the regulator. The
transformer needs to be a UL listed wall mount variety with at least a 7.5 volt RMS
output and at least a .5 amp capacity.
The parts I bought were from DigiKey Corp. and the part numbers are:
Qty

Part#

Description

Price

1
1
1
1
1

T702-ND
HS121-ND
W005G-ND
P5234-ND
NJM7805FA-ND

WALL TRANSFORMER 9VAC 600 MA


PLUG-IN HEAT SINK FOR TO-220
1.5 AMP 50 V BRIDGE RECTIFIER GP
NHE RADIAL ELECT CAP 16V 470UF
IC 5V POSITIVE REGULATOR TO220

6.70
.44
.42
.66
.88

The processor parts consist of the microcontroller, an oscillator, 2 switches, 2 resistors, a


hex inverter and a capacitor. All but the microcontroller I got from Digikey. Here are the
Digikey parts
1
1

SE1232-ND
CD74HCT04E-ND

CRYSTAL OSC 11.0592MHZ CMOS 8 D


HEX INVERTER

3.45
.39

2
2

P2040-ND
CKN1059-ND

22UFD 16VDC TANTALUM CAP


TOGGLE SWITCH SPDT VERTICAL RT

1.72
8.88

The processor I bought from Dallas Semiconductor directly. The part number may vary
from the one shown, but it should be the 16 MHz, 32K X 8, 40 pin DIP, part.
1

DS5000-32-16

Soft Microcontroller

~70.00

Dallas Semiconductor can be reached for credit card orders at 1-800-336-6933 for US
orders or at 972-371-4000 for orders outside the US. Another source is Newark at 1-8004NEWARK for $62.50
The rest of the parts I bought at Digikey. I've included a description so that you could get
some of these parts at another location. Radio Shack has many of these parts, including
solar cells. Also the prices I've listed here, will, no doubt, vary over time.
Also the LCD Display can be bought from Allelectronics.com for $7.00. Their part
number is LCD-46. The solar cell can be substituted with a part number PVD-2, from the
same place. Allelectronics.com has many of these parts, and a whole lot more, at GREAT
prices. Check them out at http://www.allelectronics.com .
6
10
2
1
1
3
1
5
5
5
5
18
5
1
2
1
1
1
3
2
1
10
2
1

P2040-ND
P4551-ND
CD74HCT123E-ND
ADC0808CCN-ND
MAX232CPE-ND
2N3904-ND
2N3906-ND
100EBK-ND
330EBK-ND
470EBK-ND
4.7KEBK-ND
10KEBK-ND
27KEBK-ND
4N35QT-ND
923252-ND
P276-ND
73-1098-ND
MC14M-X-ND
HLMP-1700QT-ND
P4887-ND
CD74HCT393E-ND
BAV21DICT-ND
LM34DZ
P2105-ND

22UF 16VDC TANTALUM CAP


5.13
.001UF 50VDC POLYESTER B CAP
1.08
DUA RETRIG MONO MULTIVIBRATOR
1.60
8 BIT A/D CONV 8 CH MUX 28-DIP
6.60
IC 5 VOLT DUAL RS232 X/R 16-DIP
2.94
NPN SML SIG G.P. AMP&SWITCH TO-92
.87
PNP SMLL SIG G.P. AMP&SWITC TO-92
.29
100 OHM 1/8W 5% CARBON FILM RESISTOR
.28
330 OHM 1/8W 5% CARBON FILM RESISTOR
.28
470 OHM 1/8W 5% CARBON FILM RESISTOR
.28
4.7K OHM 1/8W 5% CARBON FILM RESISTOR
.28
10K OHM 1/8W 5% CARBON FILM RESISTOR
.56
27K OHM 1/8W 5% CARBON FILM RESISTOR
.28
TRANSISTOR OPTOISOLATOR
.43
SUPER STRIP ALLOY CONTACTS
38.50
SOLAR CELL 1.3V 55.0 X 11.0 MM
1.16
LCD MODULE 20 X 4 CHARACTER
31.92
5 FT 14 CONDUCTOR RIBBON CABLE
4.80
T-1 DIFFUSED RED LED
.90
.1UF CAP
.64
DUAL 4 BIT COUNTER
.83
.2 AMP 250 VOLT DIODE
1.00
32 TO 212 PREC F TEMPERATURE SENSOR
5.70
1UF 16VDC TANTALUM CAP
.28

I also put a connector on my LCD display which involved the male connector on the
display, a female connector to plug on it and the pins and crimper for the female
connector. You can solder the 14 conductor ribbon right to the display, saving this cost, if
you are careful. Digikey doesn't sell a 14 pin version so you have to get a 12 pin and a 2
pin connector and use them together to get 14 pins total.

1
1
1
1
30
1

WM4210-ND
WM4200-ND
WM2000-ND
WM2010-ND
WM2200-ND
WM9900-ND

.1
.1
.1
.1
.1
.1

INCH
INCH
INCH
INCH
INCH
INCH

12 PIN HEADER
2 PIN HEADER
2 PIN TERMINAL HOUSING
12 PIN TERMINAL HOUSING
PINS FOR HOUSINGS
PIN HAND CRIMP TOOL

1.38
.29
.14
.86
3.15
13.57

You will need a cable and 9 or 25 pin female serial connector to connect to your PC serial
port. The number of pins depends on the number of pins on your PC. Some PC's have
both, with the 9-pin being COM 1 and the 25-pin COM 2. But things are further
complicated if you have an internal modem. Many times the 25-pin port, or COM 2 will
be disabled and the installed modem will be set for COM 2. The cable between the PC
and the microcontroller only needs 3 wires. I have used standard flat telephone cable
(called silver satin) for this cable. It is cheap and you can get some at many different
places. It needs to be at least 10 Ft long so that the micro doesn't have to be too close to
the PC. The schematic I will furnish will show you what pins to tie together on the female
serial connector to avoid having to use more than 3 wires. I also bought a hood to cover
the female connector to hide the wires on the connector. It's not necessary but improves
the looks and gives you something to grab hold of when connecting or disconnecting the
connector with your PC. The connectors are available at Digikey
1
1
1
1

109F-ND
125F-ND
909Z
925Z-ND

9 PIN D FEMALE CONNECTOR


25 PIN D FEMALE CONNECTOR
9 PIN D HOOD
25 PIN D HOOD

1.65
3.43
2.00
3.03

Some of the above parts are for constructing the X-10 interface. You will need the
computer interface made by X-10 to complete the interface. It can be obtained from
Home Automation Systems Inc. Their WEB site is http://smarthome.com/ . You will
need a 10 Ft piece of flat telephone cord with a standard phone connector on one end to
connect to the interface module. Their order phone number is 1-800-SMARTHOME or
1-714-708-0610 for questions. The part number is
1

1135

TW523 TWO WAY INTERFACE MODULE

22.95

I think that this is all the parts needed to complete the course. You might want to wait to
buy the X-10 module if you are short of funds. It will be several lessons before we start
on that project, but you will eventually need it to complete the course. It is a very useful
interface and fairly priced. You will be able, through it, to control any light, appliance,
air-conditioning, or receptacle in your house that you buy a corresponding module for. A
module for controlling a light with dimming capabilities is about $10. To control the
other equipment in your house you will need units that cost a little more but none are
very expensive. Check out the WEB site mentioned above and you can get on their
mailing list and get a catalog of the various modules available and the prices or view
them on-line.
You will need a soldering iron to do any soldering. It should be as small a wattage as you
can get, around 15 watts, with as small a tip as you can get, to prevent overheating the

parts. They cost from less than $20 to over $100, depending on how good a one you get.
You will also need a small pair of needle nose pliers, a small pair of diagonal cutters, a
small screwdriver, and a pair of wire strippers. These can be obtained from Radio Shack
and other consumer stores that sell electronic parts. Some of the above parts can also be
obtained from Radio Shack but since I couldn't get a catalog from them I didn't list the
part numbers for them. Radio Shack also has a small device that has alligator clips
mounted on a stand that allow you to have a third hand to hold wires or parts that you
want to solder to. It is very handy and fairly cheap, around $10.
I could make and sell some of the things that need soldering to you but that would stop
you from learning how to solder. Plus I would have to charge you for my time and my
original intent for this course was not to make any money for offering it.
I must state that soldering can be HAZARDOUS TO YOUR HEALTH. THE TIP IS
VERY HOT and can give you bad burns if you come in contact with the hot tip. Also it is
desirable to solder in a well ventilated area to avoid breathing the flux fumes that result
from melting resin core solder. I have done it all my life, with no apparent problems, but I
have heard that it is unwise to breath the fumes if you don't have to. I would place a small
fan blowing on you to keep the rising fumes from being inhaled directly. I intermittently
hold my breath while the tip is in contact with the solder. With skill and a developed art,
you can minimize the amount of fumes by only applying the minimum amount of solder
in quick well directed operations. It doesn't take much solder to connect small parts, like
the temperature sensors to a cable so that it can be hung outside to get outside
temperature into the micro. Also you should ALWAYS USE SAFETY GLASSES while
working with tools and electronic parts.
I hope that you can afford to get all these parts because without them you will be at a
disadvantage in trying to follow the rest of this course. I wish that I could just give them
to you but I cant. If you can't afford all of them at once, maybe you can buy them a little
at a time and be able to buy the ones necessary to keep up with the course. If you do have
a problem with finances, look at the next lesson and only buy the parts necessary to build
the initial part of the system. I realize that we are talking around $200 to get everything,
so it may be difficult for some, but there is no way to teach this course and have you
understand it without having a system to play with and to try out the different techniques
described. What you will have if you can afford it is a development system that 10 years
ago would have cost several thousand to build, if you could build it at all.
I don't however, discourage you to continue without the hardware described in this
lesson. You may still be able to glean some good info without the hardware.
Here is the schematic for the system we are going to build. There are two versions, one is
using the DS5000 part and the other is using the DS2250 part. Both are identical in
functionality. The only difference in the schematics is the pinout of the microcontroller
chip. The DS5000 is in a standard 40 pin dip package that is easy to use with the
protoboard, but is about $70. The DS2250 is in a 40 pin simm package that requires
special handling to connect to the protoboard, but costs only $52 with a special socket to

accept the simm. I don't suggest this option unless you are confident in your soldering
abilities, since you will have to solder very small wires to a connector with pins on .05"
centers. I had some difficulty in doing this myself and I've had 30 years practice in
soldering. I would get the DS5000 and spend the extra $20 and spare yourself the
difficulties involved in getting it connected to the protoboard. The DS5000 simply plugs
into the protoboard, with no hassles. If you contact Dallas and elect to get the simm part,
tell them that you want a socket for it also. The part number for the DS2250 is the nearly
the same as the DS5000. It is a DS2250-32-16 and the socket is a DS90720-40V. I
STRONGLY suggest that you get the DS5000 part though! The warning has been given.
Don't hold me responsible if you can't get the DS2250 wired up!
NOTE: To print the schematics you will need an HP compatible printer (one that
understands the PCL language). To print a schematic type (at the DOS command line):
COPY /B MICLAB50.PCL LPT1 This example assumes which schematic and that the
printer is attached to port 1 (LPT1). Usually most PC's only have one printer port. If you
experience trouble printing a schematic, one thing you can try is, while at the DOS
command line, cycle the power on your printer and try to print it again. I've had a report
from one person that said that the bottom part of the schematic wasn't printing. This
could be because WINDOWS has set the top and bottom margins and you don't actually
have sufficient print area to print one of these schematics. By cycling power on the
printer, while at the DOS command line, you will clear out any possible setup that
WINDOWS has made to the printer and reset it to it's default, which should work.
I have recently found a program to convert the PCL files into BMP files. This is the 2250
file and this is the 5000 file in bmp format (zipped). You should be able to find numerous
programs to display and print these files in this format.
For those that have downloaded the schematics prior to about 3:30 PM CST 4/19/97 ,
you need to download them again as I had a couple of errors on the original ones. I have
also added two more ZIPS. One is a set of utilities that will allow you to work with the
DS5000 or DS2250 chips but are useless without one wired up and connected to your PC.
The other is a set of help files with chip pinouts, the 8051 instruction set, and a LCD
display work sheet for laying out how you want different things displayed and where.
This will be of more benefit later, but I'm giving them to you now. Some of these files
use the IBM block graphics character set. Most printers support these characters, but
some don't. To avoid problems, try to find an IBM emulation for your printer. (EPSON
emulation definitely won't work) These help files are text files and should print on any
printer supporting the IBM block graphics character set, which most do.
Digikey's Web Site is http://www.digikey.com/
Dallas Semiconductor's site is http://www.dalsemi.com/
National Semiconductor makes most of the chips, besides the microcontroller that are
used in this course.
National Semiconductor's site is http://www.national.com/

Newark Electronic's site is http://www.newark.com/


My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

Starting Construction
We will be starting with the System, RS-232, Power Supply, and Test sections of the
schematic from the last lesson. This requires the parts outlined by dashed lines on the
schematic (the ones you really need are listed later in this lesson. In the last lesson I
furnished you with a help file zip that contains the pinouts of all the chips. You will need
this to make the connections between the chips. I would read completely through this
lesson once before I started doing anything outlined in it.
There will be some soldering involved with building the power supply. The way I did it
was to cut off the connector on the end of the transformer (T702-ND) cable, if there is
one, and solder the cable to the two pins on the bridge(W005G-ND) marked ~, leaving
the cable from the transformer as long as possible. Then solder the 470 UF (P4234-ND)
cap across the + and - terminals of the bridge, observing the polarity of the cap, leaving
all the leads on the bridge intact, not cutting any of them. The cap has a stripe down one
side with an arrow pointing to the - lead of the cap. Connect this to the - lead of the
bridge and the other to the + lead of the bridge. Place the cap as close as possible to the
bridge, leaving the leads of the bridge as long as possible. Bend the + and - leads of the
bridge so that they line up with the IN and GND leads of the 7805 (NHM7805FA-ND)
regulator, but don't touch any of the other leads of the bridge.
Then slip the heat sink (HS121-ND) on to the 7805. I had a little trouble with this as the
clearance inside the heat sink was too tight initially to slip it onto the 7805. I spread out
the fins of the heat sink, which opened up the clearance for the 7805. One end of the heat
sink clearance is thicker than the other end. The thinnest end is where the metal tab of the
7805 slips into and the thicker end is where the body of the 7805 ends up. Bend out the
fins of the heat sink just enough to slip in the 7805, metal tab first, into the thickest end.
Slide in the 7805 until the metal tab ends up at the other end of the heat sink. One end of
the heat sink will now be pressed against the metal tab and the other end will be pressed
against the body of the 7805. This should be as tight a fit as you can get, the tighter it is
the better the heat will be conducted and radiated away from the 7805.
Solder the + lead of the bridge to the IN lead of the 7805 and the - lead of the bridge to
the GND lead of the 7805. Then solder a solid #24 or #26 wire to the OUT lead of the

7805 and another to the - lead of the bridge, somewhere between where the cap is
connected and where the 7805 is connected. These wires should be a foot long or so.
Now connect two more wires to the ~ leads of the bridge, somewhere in the middle
between where the transformer is connected and the body of the bridge. I would use red
for the OUT lead of the 7805, black for the - from the bridge, and two other colors for the
~ leads of the bridge. An alternate source for the wire needed is to strip out a foot of
"silver satin" phone cord. This is the very same flat cord that is usually used between
your phone and the receptacle that it plugs into. This is stranded, four color (red, green,
yellow and black) wire that works perfect for this and you will need some for the RS-232
cable that goes to the PC anyway. I would get at least 25 feet and possibly 50 feet of this
cable. It is cheap and is available at many places. This is the flat silver grey looking
phone cable. It comes in white and beige also. Some of it doesn't have multi colored
wires inside. I would avoid this type in preference to the four color cable. When you
make the serial cable you need the color coded wire to do it right.
Now what you appear to have is a "spider web" of parts, with the leads of the transformer
coming in from one side, and four wires coming off of the other side. This is EXACTLY
what you want. None of the leads that aren't soldered to each other should be touching
each other. Bend the leads to place at least an 1/8th of an inch clearance between them.
What I did next was to take a "hot glue gun" and stick the transformer leads just before
they connect to the bridge down to a 18" piece of 1" by 6" wooden board, near one end.
Put enough glue to secure the transformer leads so they won't pull loose by tugging on
the transformer lead. This type glue sets very fast so after you squeeze out a puddle of
glue, a half inch in diameter or so, stick the transformer leads into the puddle with the
leads going to the transformer pointing out away from the board, and do it while the glue
is still molten. By the way, the glue is VERY HOT so don't put your fingers into it,
ouch!!! I then squeezed out a little more and covered over the transformer leads. This
glue sticks well to EVERYTHING and is a handy thing to have around for repairs of all
kinds.
The Super Strips (protoboards) have a peal off stickum' on the back. What I did next was
to peal off the covers and stick down these strips, side by side (not end to end), centered
both ways on the board, the longest dimension of the protoboard running in the same
direction as the long dimension of the board. Running down each side of the strips are
two lines of connections, that are normally used for power buses. I normally use the
outside bus on either side as the positive (VCC) bus and the inside on either side as the
negative (GND) bus. This means that down each side of each protoboard you will have
both a VCC and a GND bus, side by side.
Now all this may seem rather crude, putting such high tech stuff on a piece of wood, but I
like the wood in preference to most other things. It's usually easy to obtain, cheap, and
most of all it weighs enough to give a solid base for everything that won't move around
every time you tug on a wire. Plus it's easy to screw into with small wood screws, drill,
and it's non-conductive. If you don't want to use hot glue, you can fasten the power
supply down however, or even mount it on a perf board (a board with holes on .1" centers

and copper pads on the back side to solder to). You insert the parts on the bare side and
solder them on the back side. Then make the interconnections, usually using the leads of
the parts by bending them over.
I thought about all this as I made mine, and chose to do it the way I described to you
above. It is bare bones, fairly cheap, and I just like wood! But some of you may like more
esthetically pleasing packaging than this. Do it however you choose, only keep it
electronically as I have described.You can spruce it up with an on/off power switch, by
running one lead from the transformer through it. I just unplug the transformer from the
wall when I want to turn it off. The fewer the parts, the less to go wrong.
Next comes the RS-232 cable. Using about a 10 to 15 foot length of flat phone cord, strip
off about 3 inches of the silver PVC coating, exposing the four color wire inside, taking
care not to remove any insulation off of the colored wires as you remove the silver
coating. You can do this by CAREFULLY slicing down the 3 inch length on one of the
flat sides, not cutting too deep or by using a pair of diagonal cutters gripping the wire
with the cutters at the 3 inch mark and pulling away from the rest of the length of cable.
The correct amount of pressure needs to be applied with the cutters to grip the flat
insulation tight enough to pull it off while not gripping it so tight that you pull off the
colored insulation from the wire inside. You may have to try more than once to get it
right.
Using the same technique, remove a foot of the flat insulation from the other end, 3 or 4
inches at a time. Now comes the female connector the will end up plugging into the serial
port on the PC. There are two possible connectors that you will be using, either a 9 pin or
a 25 pin female "D" connector. The D comes from the fact that the shape of the connector
resembles the letter D, wider on one side than the other. Which one you use depends on
the available serial connectors on your PC. Some have a 9 pin, some have the 25 pin, and
some have both. If you have a modem installed inside your PC, there is a possibility that
if you have both 9 and 25 pin versions, that the 25 pin one has been disabled. Your mouse
may be plugged into the 9 pin one, leaving you no connectors to plug into. That is the
way my machine is. So when I play with the micro, I unplug the mouse (since it's not
used), plug in the micro and reboot the machine, if I've been using it, to eliminate the
mouse driver being loaded. Otherwise just unplug the mouse and plug in the micro and
turn on your machine.
If you automatically start up WINDOWS when the power is turned on, it will still start up
without a mouse. After it finishes starting up just press CTRL and the F4 key and if you
are using WIN31, you will get a message asking you if you want to exit windows, answer
yes. In a few seconds,you will be at the DOS command line. If you are using WIN95, a
different window will pop up with four options listed. The third one down is to restart the
computer in the MSDOS mode. The SHUTDOWN option will be selected when the
window first comes up but you can use the down arrow key to move the dot to the
RESTART in MSDOS mode option. Then hit enter. After a few seconds, you will be at
the DOS prompt. An alternative for WIN95 is when the "Starting Windows 95" message
appears, immediately press the F8 key. You will be presented a multi option menu with

one of the options being "Command prompt only". Down arrow to highlight that option
and press enter. In a few seconds you will be at the DOS command line.
Soldering
To digress just a minute. I haven't spoken much of the technique of soldering but I take
this time to do it now. Soldering is an art that is developed with time. The basic rule is to
apply the tip (the heat) on one side of whatever and the solder to the other side. This
causes the solder to "wick" or be drawn toward the tip after it melts, wetting whatever is
in between. Another rule is to "tin" each piece that is to be joined by soldering. Tinning is
simply applying solder to each piece, individually, before you solder them together.
Another technique is called "twist and tin". This applies to stranded wire. To twist and tin
stranded wire, strip off a small amount of insulation from the wire. This exposes the
separate strands of small wire that make up the stranded wire. Now grip the exposed
wire, which will usually fray out a little when you strip it, between your thumb and finger
and apply a twisting action to tightly twist the strands back together. They will usually be
slightly twisted, indicating the direction of the twist imparted to them during
manufacture. This is the same direction you want to twist them. It doesn't take much to
twist the soft copper back together. You only need enough twist to hold all the small
wires together in a bundle. They will usually form the desired twist by pressing together
your thumb and finger on the wires, and in a twisting motion, in one direction or the
other, slowly slipping your thumb and finger off of the end of the wire, never letting up
on the pressure. Examine the bundle afterward, and likely as not, they will be nicely,
slightly twisted and together in a fairly compact spiral. It doesn't matter if at the very end
there may still be a little fraying. You will cut that off after tinning. Now apply the tip to
one side of the wire while applying the solder to the other side. If everything is right, the
solder will almost immediately start to melt and wick into the twisted wires, making a
solid wire out of the small separate wires when the solder cools. As soon as you see the
solder completely wick into the wires, you can remove the tip, letting the wire cool. You
have just "twisted and tinned" the stranded wire. You should try to not kink the wire in
the process of twisting it, but rather have a fairly straight bundle of wires before you tin
it. Also, if I say to strip, twist and tin an 1/8" of a wire, you need to really start with a 1/4"
and then cut it off to the right length when you finish tinning it. In other words, the
dimensions I give you are "finished" dimensions.
With both pieces having been tinned, you put them together and apply the tip to the
junction and the two pieces will join, the solder on each piece melting into the other,
wicking into the space between the pieces. As soon as this happens, remove the heat and
let it cool, holding the parts perfectly still until they become solid.Any movement during
this cooling down period will increase the odds that you won't have a good connection
afterwards. The soldered joint should have a good shine to it. If it looks dull, redo it until
it shines. The shorter time you have to keep the heat applied, the better. That is the art of
it, doing it with as little heating time as possible. You create less fumes and lesson the
risk of doing damage to more temperature sensitive parts.

It is also sometimes necessary to apply a minute amount of solder to the tip itself, so that
when you bring it in contact with something else, the heat is transferred to the something
else at a faster rate, due to the melted solder conforming to the shape of the something
else better. Also, there is usually a sponge, dampened with water, that is used to clean off
the tip, from time to time. You should keep the tip with a shine on it, by wiping it with a
light pressure across the sponge as you slightly rotate the tip, wiping off any excess
solder. Then apply a small amount of fresh solder to the tip, just a very small amount.
About the solder. You should always use resin core solder, of the smallest diameter that
you can get. If you buy it in a consumer electronics store like Radio Shack, it will be of
this type. The other type is acid core, used to join copper pipe together. This is what you
will find in a plumbing store. Don't use this type for electronic soldering. The resin in the
center of the solder acts as a cleaning agent, which helps the solder to cling to the copper
and to wick. The resin will wick toward the heat, bringing the solder with it. This is why
you clean the tip every so often, to keep a supply of fresh solder, with its corresponding
resin content, on the tip. The resin turns to a type of steam, the smoke you see rising from
the melting solder. It is very pungent and should not be breathed directly. Hold your
breath at the moment you contact the tip or part with the solder, and the most smoke
occurs, until you remove the heat and the smoke subsides. You can also move back away
after removing the heat, and with a small amount of air circulation, avoid breathing most
of the fumes. Although I can't prove it, I think that the fumes quickly return to a solid and
precipitate out of the air, and don't linger very long.
Now back to the "D" connector. You need something to hold the connector with while
you both bring the tinned wire into contact with the "cup" (the back side of the connector)
while at the same time pressing the tip against the previously tinned cup, all at the same
time. What has worked for me is a pair of slip joint pliers (one trade name is Channel
Locks). These pliers have several positions for the hinge point, giving more or less
spacing between the jaws, allowing you to grip parts of varying thickness. Slip the joint
so that the jaws are just wide enough to hold the connector with a little pressure. Take a
fairly thick rubber band, or a couple of skinny ones, and make several loops around the
handles, which will keep enough tension in the jaws to hold the connector without you
having to hold them together with your hand. Now take a second pair of pliers, whatever
kind, and put several wraps of rubber band on those handles and then open up the jaws
and clamp onto the handle of the first pair, at a right angle. This will create a stable
combination that will free stand on the table and hold the connector in place, with enough
weight to keep it steady while you solder to it.
Following is the pinout of both varieties of serial connectors. The pins number from left
to right (viewed from the back) with the numbers starting on the row with the most pins,
continuing on with the next row, left to right.
9 Pin Female
1
5
o o o o o

25 Pin Female
1
13
o o o o o o o o o o o o o

o o o o
6
9

o o o o o o o o o o o o
14
25

(Viewed from the solder side)


9 Pin connector directions
Cut three lengths of wire, whatever kind, about an inch long and strip, twist (if it's
stranded) and tin about an 1/8" on all the ends. Now tin all but pin 9 on the connector,
which is unused. Now solder one end of one of the wires to pin 1. Solder another to pin 6.
Now take the two loose ends and solder both of them to pin 4. That completes the first set
of jumpers. Now take the last wire and solder it to pin 7 and the other end to pin 8. Now
take the flat phone cable and strip, twist, and tin the red, green, and black wires about an
1/8" on the end that you stripped off the 3" of insulation. Leave the yellow wire
untouched. Now solder the red wire to pin 2, the green to pin 3 and the black to pin 5.
25 Pin connector directions
Cut three lengths of wire, whatever kind, about 1-1/2" long and strip, twist (if it's
stranded) and tin about an 1/8" on all the ends. Now tin pins 2,3,4,5,6,7,8, and 20 on the
connector. Solder one end of one of the wires to pin 8. Solder another to pin 6. Now take
the two loose ends and solder both of them to pin 20. That completes the first set of
jumpers. Now take the last wire and solder it to pin 4 and the other end to pin 5. Take the
flat phone cable and strip, twist, and tin the red, green, and black wires about an 1/8" on
the end that you stripped off the 3" of insulation. Leave the yellow wire untouched. Now
solder the red wire to pin 3, the green to pin 2 and the black to pin 7.
If you didn't get a hood for the connector, your done with the PC end of the cable.
Otherwise, if the hood you bought was the one listed, tie a knot in the flat cable as close
as you can to the end of the flat insulation on the connector end. Thread the cable through
one of the rubber strain reliefs (the one that best fits the cable) starting at the end that fits
inside the hood (it has a lip around it), until the knot is up against it. Lay half of the hood
on the table and fit the strain relief into place (there is a groove for the lip to fit into), then
fit the connector in its place (right at the end of the hood, but still inside just a little).
There is a lip in the hood, right at the end that the flange around the connector fits into.
During this last step, it will be necessary to bend and adjust the wires a little to fit them in
between the connector and the strain relief, squeezing them so that they fit inside the
hood. Holding the pieces in place, take the other half of the hood and fit it together with
the first. There should be no wires caught or sticking out around the hood, and the
connector should be visible at the end of the hood, and the end of the strain relief should
be sticking out the other end, a quarter inch or so, along with the cable.
If you managed all this, you're just about finished with this balancing act. Now take the
two longer, fully threaded screws that came with the hood, and insert one into one of the
holes, through the hood. Take one of the nuts and place it on the opposite end of the
screw. Notice that the hood has different looking indentions on either side of where you
inserted the screw. The head of the screw should be in the round indention and the nut

should be fitted into the hex shaped indention. Now press the nut into the hex indention,
which will force the screw out a little on the other side. Use your small flat bladed screw
driver and screw the screw into the nut, keeping the nut pressed in as far as it will fit,
until the screw starts. This keeps the nut level with the end of the screw, so it will start
into the nut without cross threading. If the screw seems to get tight with just a single turn
of the screw driver, you have started the screw in crossed. Back the screw out until you
feel it clicking against the nut and start it in again. There should be no resistance when
starting the screw into the nut. By the way, for those who don't know, turning the screw
clockwise screws in the screw and counter - clockwise unscrews it, or backs it out. Notice
that there is one round indention and one hex shaped indention on each surface of the
hood. I won't insult your intelligence by explaining what this means other than to say the
nuts go into the hex shaped indentions. Insert the other screw and nut and screw them
together. Now tighten the screws until they are snug, watching all the time that there are
no wires caught in the junction of the two halves of the hood. When you finish, you
should have a very nice, heavy, cast zinc, RS-232 connector with a 10 or 15 foot cable
attached. The connector should be held in place, sticking out the end of the hood, with the
flange of the connector held firmly by the two lips at the end of the hood. The strain relief
should be sticking out the cable end about a quarter of an inch or so, hopefully, with the
cable leaving the strain relief. On the other end of this cable, the end you stripped a foot
of flat insulation from, strip, twist, and tin 1/4" on the red, green, and black wires.
The Load and Break switches
The last thing are the two switches. Strip out about a foot of the flat phone cable for each
switch. and use the red,green, and black wires. Strip, twist, and tin about a 1/4" of wire on
each end of the six wires. These switches were intended to be mounted on a printed
circuit board and that is why they have leads that bend together in one direction and end
up level on the end. If you were to straighten out these leads one would be longer than the
rest. DON'T straighten them out, but solder the red wire to this lead. Solder the green to
the next, and the black to the last. What I did now was to put a small spot of glue on the
board and stick the first switch down, with the handle of the switch (the part you flip to
switch positions) where it sticks out past the edge of the the front surface of the board
(the one that will be closest to you when you place it on the table or where ever you
finally put it), towards the end that you stuck the transformer cable down, with the two
metal tabs that were meant to fit into a printed circuit board, pointing to the right. Repeat
this for the other switch, placing them about 2" apart. Now print on the board on the left
side of the left switch the word "LOAD". On the right side of left switch the word
"RUN". On the left side of the right switch the word "BREAK" and on the right side of
the right switch the word "NORMAL". Print these words as close to the front surface of
the board as you can so that they can be easily seen by you.
I would finish by using the glue gun to stick the RS-232 cable down to the board. Do this
at about 1 or 2 inches back from the stripped out part of the cable, and somewhere near
where you stuck down the transformer cable, with the cable going to the PC pointing
away from the board. This should leave about a foot of loose wire to connect to the
protoboard.

THIS ENDS HOT GLUING AND SOLDERING FOR NOW!!!!


Boy am I glad that's over with. I've dreaded having to explain all this, wanting to explain
it thoroughly enough for the novice, but not insult the more experienced at the same time.
I probably failed on both accounts. I would like to assume that anyone attempting this has
had some experience, but I have to assume that some have not. I hope that I was clear
enough for all.
As promised, here are the bare minimum parts needed to complete this lesson.
1
1
1
1
1
1
1
10
2
1
1
1
5
5
10
1
1
3
10
1

T702-ND
HS121-ND
W005G-ND
P5234-ND
NHM7805FA-ND
SE1232-ND
CD74HCT04E-ND
P2040-ND
CKN1059-ND
CD74HTC123E-ND
MAX232CPE-ND
2N3904-ND
330EBK-ND
470EBK-ND
10KEBK-ND
923252-ND
4N35QT-ND
HLMP-1700QT-ND
BAV21DICT-ND
P2105-ND

9V AC 600MA WALL TRANSFORMER


TO-220 HEAT SINK FOR 7805
BRIDGE RECTIFIER
470UF 16VDC ELECTROLYTIC CAP
5 VOLT REGULATOR
11.0592 MHZ OSCILLATOR
HEX INVERTER
22UF 16VDC TANTALUM CAP
SPDT SWITCH
DUAL SINGLE SHOT
DUAL RS-232 DRIVER/RECEIVER
NPN TRANSISTOR
330 OHM 1/8TH WATT RESISTOR
470 OHM 1/8TH WATT RESISTOR
10K OHM 1/8TH WATT RESISTOR
PROTOBOARD
OPTOISOLATOR
LED
DIODE
1UF 16VDC TANTALUM CAP

Depending on whether you use a 9 pin or 25 pin RS-232 connector:


1
1
1
1

109F-ND
125F-ND
909Z-ND
925Z-ND

9 PIN FEMALE "D" SERIAL CONNECTOR


25 PIN FEMALE "D" SERIAL CONNECTOR
9 PIN HOOD
25 PIN HOOD

I only listed 1 of the protoboards so if you just get 1, make sure when you place it on the
board, that you leave room for the second one when its placed on the board (you will
need two before we're through). All wires that plug into the protoboard should be no
larger than #24 solid wire. The end that plugs into the protoboard should be stripped
(twisted and tinned if stranded) about 1/4"
The first step is to connect the power supply to the protoboard.Plug the end of the red
wire into the outside bus strip on whichever side you wish. Plug the black one into one of
the inside strips. Then make two jumpers long enough to span the distance from one side
of the protoboard to the other and jumper the two inside strips together and the two
outside strips together. Both sets of busses are really divided in half in the middle of the
protoboard. You need to make four jumpers about an inch long and plug them across the
wider gap in the center on either side of the protoboard to make the busses run the

complete length of the protoboard. The design idea behind the protoboard was to have 8
busses, four on either side, that run halfway down the length of the protoboard. But we
need to join them together, in the middle, to make 4 busses, two on either side, that run
the full length of the protoboard. You'll notice in each buss strip, there are 10 sets of 5
holes each, separated by a small gap between each set. But in the center of the
protoboard, these gaps are larger than the rest. This is where you need the short jumpers.
At this point you have a protoboard with 2 power busses running along the full length of
the protoboard, on either side, the inside ones are GND and the outsides ones are VCC.
Remember this when connecting power to a chip. The inside bus is ground and the
outside bus is +5 volts. If you bought both protoboards you need to double the recipe and
then jumper the inside bus on one protoboard to the inside bus on the other. Do the same
with the outside busses on each protoboard.
Check your connections, both to the protoboards and in the supply itself. Refer to the
help files to verify the pinout of the 7805 and check it against the way you wired up the
supply. Check to make sure that the AC from the transformer is connected to the leads on
the bridge marked ~. Make sure that the + from the bridge is connected to both the + side
of the CAP and the IN lead of the 7805. Make sure the - lead of the bridge is connected to
the - lead of the CAP and the GND lead of the 7805. The 7805 is short circuit protected
so if there was a mistake, it shouldn't hurt anything. The polarity of the 470uf CAP is
important. The + side of the CAP must be connected to the + lead of the bridge and the lead to the - lead of the bridge, or damage could result to the CAP (it could pop when you
apply power).
Put on your safety glasses and power up the supply and measure the +5 volts to make
sure it's working. Then verify that all the busses are powered up with the correct readings
on each. The multimeter should be set to read DC voltage. If it has a range switch, set it
to one that will allow you to measure +5 volts without over ranging the voltmeter. The
cheaper ones are auto ranging and only need to be set to the right function, DC volts. By
the way, I was wrong about the price of a cheap multimeter. I guess it's been several
years since I bought one and the one I was thinking of at Radio Shack is really $40 (still a
good buy, but you might check around and find one a little cheaper, it needs to be digital,
though, with a display, not a meter needle). The red lead of the meter should be
connected to the VCC bus and the black one to the GND bus. If you don't get a reading of
between 4.9 to 5.2 volts, something is wrong. Turn off power and re-check your
connections. There arn't too many places that a problem could be. The most common is
that wires are touching that arn't supposed to be. The second most common is that the
outlet you plugged the wall transformer into isn't hot (you would have gotten a reading of
0). The third most common is that you reversed the connections to the 7805, with the IN
and OUT switched (the reading didn't fall within the range of 4.9 to 5.2 volts).
A trouble shooting proceedure for this would be to turn on power and measure the AC
voltage at the two ~ leads on the bridge. It should read between 9 and 11 volts. If this is
good, measure the DC volts across the + and - leads of the bridge. It should read between
12 and 14 volts. If this is good then measure from the OUT lead on the 7805 and the -

lead on the bridge. You should get between 4.9 and 5.2 VDC. If this is good, you've got
no problems with the supply and the problem is the wires from the power supply to the
protoboard.
If you were lucky and you got a good reading for the power on all the busses, then it is
time to proceed to plugging in parts. Notice that while the power strips run in the
direction of the length of the protoboard, the holes in the center of the protoboard seem to
be all together, side by side with a dividing strip down the middle, running the full length
of the protoboard, seperating them into two large areas. What really is the case is that the
whole protoboard is arranged into sets of five holes, just like the power strips. Only in the
case of the two large areas of holes, the sets of five occur as strips radiating outward in
either direction, from the dividing strip down the middle. So the sets of five run
perpendicular to the power strips on either side of the divider. I will try to draw this
below (I hope this drawing doesn't straddle a page break. If it does, put the two sheets
together to see what I'm trying to show.
----- ----- ----- ----- ----- J ----- ----- ----- ----- ----- VCC BUS
----- ----- ----- ----- ----- J ----- ----- ----- ----- ----- GND BUS
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
DIVIDER
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
----- ----- ----- ----- ----- J ----- ----- ----- ----- ----- GND BUS
----- ----- ----- ----- ----- J ----- ----- ----- ----- ----- VCC BUS

This isn't a perfect representation of the protoboard but it should clarify what I'm trying to
show. The chips are plugged in straddling the divider running down the middle. This
means that half of the pins will be plugged into one side of the divider and the other half
will be plugged into the other side of the divider. Where the letter "J" is, this is where the
power busses are jumpered so that they become two single busses that run the length of
the protoboard. Referring to the Pinout Sheets, Page 1, a chip will have an indentention in
the middle of one end OR it will have a small dot or indention next to pin 1.
With power turned off, plug in the dual single shot chip (CD74HCT123E-ND, with the
indent to the left. Although I won't say it for the other chips, always plug in the chips
with the indent to the left. Also the -ND is a naming scheme use by DigiKey and will be
dropped in this discussion. Looking at the drawing above, that would place pin 1 of the
chip plugged into the first strip of five points in the lower area, first row on the far left.
Since this is an 16 pin dip, the first 8 rows on either side of the divider are used up. When
you plug in a chip, one of the narrow variety, you use up 1 of the five tie points in each
strip, leaving only 4 that can be connected to with jumpers or parts. On the other hand,
when you plug in a wide chip like the DS5000, you use up 2 on one side and 3 on the
other side, because the chip is much wider than the divider and it covers up 3 points that

can't be used later. Also as you plug in the chips, end to end, you should skip a row to
allow for the overhang of the chip package past the end pins. Don't try to force two chips
next to each other trying to use up the rows completely. This skipped row will be used
later for something else, it won't be wasted. The next chip to plug in is the Opto isolator
chip (4N35QT). Next plug in the RS-232 driver chip (MAX232CPE) Next the hex
inverter chip (CD74HCT04E). Lastly, for now, the oscillator chip (SE1232). This should
still leave enough rows left for the DS5000. But don't plug it in yet (if you've already got
it).
You should now have 5 chips plugged in, starting at the far left of the protoboard, end to
end, with the indent or dot on each to the left. The next thing is to connect GND to all the
chips. Referring to the chip pinout help sheet, page 1, connect GND on all the chips to the
inside power bus, on one side or the other, which ever is on the side of the chip that the
GND pin is on. I won't mention it again but you need to be making jumpers, long enough
to reach but not much longer, made out of #24 solid insulated wire, stripped 1/4" on each
end. I usually stop and make up a dozen of the shorter ones for GND and the same
number of ones a little longer for VCC. You then just "plug" in the ends to the
appropriate places, sometimes using the needle nose pliers, gripping the wire just behind
the bare wire and pushing it in. Don't apply any strong force to do this, they should go in
without much effort. Sometimes the first time you use a tie point, it is more difficult to
get the wire in than the next time you insert a wire into it. Don't use large diameter wires
for jumpers, as this will loosen the tie points, and possibly cause bad connections if you
ever use a smaller wire in that hole later on. All the parts I had you buy, were partly
selected for their small diameter leads, where possible, to avoid this problem.
Now connect VCC to all the chips, following the same proceedure. By the way I have
renumbered the chips on the DS5000 version and the DS2250 version of the schematics,
to reflect the order that we placed the chips on the protoboard. This should make locating
the chips physically verses the schematic, much easier. So download the new
schematics,(sorry for the inconvenience) and throw the old ones (if you had downloaded
them previously) away. Make sure that you throw the old ones away so that you don't
accidently use them instead. On the new ones, the chip in the upper far left corner is U5
not U1. The references to these schematics in the previous lesson will also get the new
versions. The old ones have been removed.
From now on we will be referring to the schematic on a regular basis. I apologize for the
size of the schematic, with print that is difficult to read because it's so small, but I had to
get a lot on one 8-1/2" X 11" sheet. I could have divided it up into several sheets, but
following circuits across multiple sheets is more difficult than being able to see the whole
schematic at once. Also, don't be daunted by the apparent complexity of the schematic,
the various parts with their connecting lines running all over the place. We will cover all
of it, a little at a time, and at some point later you will look at this print and think, "Gee, I
understand all this". I bought a cheap pair of "Ben Franklins" (reading glasses) to use
while working with all this small stuff. They help not only in reading this print, but also
seeing what you are doing when trying to solder to small parts. I am 49 and my eyesight
is not what it was when I was 30. It'll happen to you too, some day, all of a sudden like.

Plus using a pair of these glasses, even if you don't have to, eases eye strain, when
working over extended periods of time. They are cheap and are well worth the
investment. Ignore the laughter that might be expressed by others when they see you
wearing a pair of these, and just chalk it up to their ignorance!
About the schematic. You will notice the names GND and VCC seemingly just hanging
out at the end of lines, not going anywhere. This is a standard notation for schematics.
Anywhere you see GND, it is electrically connected to every other GND as if there was a
wire drawn on the schematic, connecting them all together. You actually do connect them
all together when you plug one end of a part or a jumper into one of the GND busses.
Since the GND busses are all tied together, so too are the parts or jumpers plugged into
them. The same is true for all the points maked VCC. By doing it this way, the drawing is
much less clutterd with all these interconnecting lines not shown.
We are going to wait until the next lesson to plug in the DS5000. As you well know, this
is an expensive chip, too expensive to buy a second because you destroyed the first, by
accident of course. For now, we will do some experiments with the cheaper parts to get
you used to making interconnections on the protoboard, so that when you do plug in the
microcontroller, you'll get it right the first time. Just keep it in its protective package and
fight off the desire to plug it in with the rest, I know you want to.
Now double check all your VCC and GND power connections to each chip again before
we turn on the power. I can't say this enough times. These have to be right before you
power up the protoboard. This is the one mistake that can destroy more chips than you
can afford. If you get these backwards, Lord forbid, you will most certainly destroy that
chip. Take the time to make sure you've got it right. Have a friend or relative double
check you if you can. There will be plenty of time to play later on. There's no magic to it,
no hidden grimlins. Just use the help files I provided in the previous lesson and verify
that you've connected the right pins to each power bus.
If you're satisfied with the power connections, we can add some parts now. First, we need
to place one of the 22UF caps at the point that the wires from the power supply plug into
the protoboard. Hopefully you plugged the wires into one side of the protoboard, one into
each bus, the red into the VCC bus and the black into the GND bus. The next holes were
probably used to jumper the busses on each side of the protoboard together. These caps
have a stripe down one side, that lines up, more or less, with one of the leads. This is the
+ lead of the cap. Plug this cap into the next available holes on the bus, with the + lead
plugged into VCC and the other lead into GND. As we progress further into this I will
quit harping on getting the parts plugged in correctly and assume the lesson has been
learned. While I, in a previous disertation, referred to micros as Tinker Toys, these toys
are much less forgiving of misplaced connections that a wooden Tinker Toy is. So always
connect them with perseverience, dedication, and an unswerving attention to polarity (+
and -).
Resistor Color Code

Most resistors are marked with colored bands, that indicate their value, to those who can
read them. Here are the colors and what each means.
1st band
BLACK
BROWN
RED
ORANGE
YELLOW
GREEN
BLUE
VIOLET
GREY
WHITE
SILVER
GOLD

0
1
2
3
4
5
6
7
8
9

2nd Band
.
.
.
.
.
.
.
.
.
.

0
1
2
3
4
5
6
7
8
9

3rd Band
X
X
X
X
X
X
X
X
X
X

10
100
1000
10,000
100,000
1,000,000
10,000,000
100,000,000
1,000,000,000
10,000,000,000

X
X

.01
.1

4th Band

10%
5%

So to try this out, if we had a resistor that had the colors: orange, orange, brown,
gold this is the way that you would interpret the value. The first 2 bands indicate a 2
digit number with a fixed decimal point between them. So the first two bands would be
equal to 3.3 . The third band is a multiplier. In this case we would multiply by 100. So we
would have the value 3.3 times 100 or 330 ohms. Another example is: brown, black,
orange, gold . This would represent 1.0 times 10,000, or 10K ohms. The 4th gold band
means the value could vary + or - 5% of the total value. So a 10K resistor could be plus
or minus 500 ohms.
Place another cap on the opposite end of the bus on the other side of the protoboard. The
reason for these caps are to help eliminate noise in the VCC busses. They don't show up
on the schematic, but are necessary. Now we are going to make a logic probe out of one
of the 6 gates in the hex inverter chip. You will need a 330 ohm resistor and one of the
LED's. Put one lead of the resistor into the tie strip connected to pin 10 of the 74HCT04
(U4) chip. Plug the other end into an unused strip near the chip, like one of the rows we
skipped as we plugged in the chips. Now plug the shorter end of the LED into this same
strip, with the longer end plugged into VCC. Make a jumper, a foot long, one that will
reach to any place on either protoboard, and plug one end into pin 11 of U4. The free end
of this jumper is now the working end of our logic probe.
The way this probe will work is that if there is a '1' on the input, the LED will be lit. If
there is a '0', the LED will not be lit. Also, an open appears to the probe the same as a '1'.
So if your confident of your connections, turn on the power. The LED should lite up
immediately, indicating a '1', or an open in this case. If it dosen't, turn off power and
check your connections again. Now plug the probe into GND and notice that the LED
goes out. GND is the same as a '0'. Plug the probe into VCC. The LED is lit. VCC is the
same as a '1'. You have just installed R2 and D1and used the gate U4E. I hope you see
that the way we wired up these parts matches exactly the schematic.

Now let's connect U4A,B,C,&D along with R1 and C1and SW1 and SW2. All of these
parts are in the upper left quadrant of the print. First install R1 and C1. You'll notice that
one side of the cap goes to GND and one side of the resistor goes to VCC. You'll also
notice that one side of the cap is marked + on the schematic. This is another one of the
22UF caps. You'll notice that the UF dosen't appear next to the 22 on the schematic. That
is because all of the caps are assumed to be in microfarads (UF) unless otherwise noted.
This saves having to write UF beside each one, cluttering up the print with needless text.
Also the word OHM does not appear beside any of the resistors, for the same reason. All
the resistors are assumed to be in OHMS. The K means 1000, so a 10K is a 10,000 OHM
resistor (brown-black-orange).
What you should end up with is the - side of C1 plugged into GND, the other side
plugged into the strip for pin 1 of U4. Then one side of R1 plugged into VCC and the
other plugged into the strip for U4 pin 1. Remember the chips are numbered from left to
right, 1 through 5, the way we plugged them into the protoboard. Now jumper from U4
pin 2 to U4 pin 3. Jumper U4 pin 6 to U4 pin 9. Then U4 pin 8 to U4 pin 5. Now take the
green wire from the left switch (SW1,the one you marked LOAD/RUN) and plug it into
U4 pin 1. Plug the black wire from the same switch into GND. The red wire from this
switch won't be used at all. Now take the green wire from the right switch (SW2, the one
you marked BREAK/NORMAL and plug it into GND. Plug the red wire into U4 pin 5
and the black wire into U4 pin 9. Flip both switches so the handles are pointing to the
right (toward the side that has the ends of the leads of the switches. This puts both
switches in the positions shown on the schematic, or the "Normal Run" positions. This is
the position that you would have the switches to allow the micro to be running normally.
Double check all your connections, until you are satisfied you have them right.
With your safety glasses on, turn on the power, and the Probe LED should lite (this
assumes that the end of the probe is not plugged into anything). Plug the probe into U4
pin 2 and the LED should go out. Now flip the left switch (LOAD/RUN) to the left
(LOAD), the LED should lite. Move the probe to U4 pin 4, the LED should be out. Now
flip the left switch (LOAD/RUN) to the right (RUN), and the LED should lite. Move the
probe to U4 pin 6. The LED should be lit. Flip the right switch (BREAK/NORMAL) to
the left (BREAK) and the LED should go out. Move the probe to U4 pin 8. The LED
should be lit. Flip the right switch (BREAK/NORMAL) to the right (NORMAL) and the
LED should go out.
If all this worked as I described it to you, you have wired up, correctly, all the parts, so
far. Congradulations! You've taken a giant step from darkness, toward the light. You
should study the schematic, looking at the parts we've been working with, until you see
and understand that what I had you connect up, was done just like the schematic shows.
The main thing to understand is that the schematic shows the electrical connections but
not the physical connections. In other words the schematic doesn't show the tie strips or
the jumpers that we used in the connection process, just the fact that connections are
made, and from where to where. The another thing to get out of this lesson (there were
lots of "things") is the way the tie points work. There are always 5 tie points in each tie
strip. If you use a strip to connect two parts together, like R2 and D1 for the logic probe,

that tie strip can't be used for connecting two more parts together, even though there
seems to be some left over holes in it. The strip connects all five points together, whether
you use all of them or not. When we get done wiring up the complete schematic, there
will be lots of unused points or holes that we won't be able to use for anything else.
They'll remain unused for the duration. Thats the nature of a protoboard.
This lesson was LLLLOOOONNNNGGGG!!!!! I hope you made it through it. I listed
some parts that we didn't get to in this lesson, but I wanted you to have them so that we
could plug them into the protoboard and get the chip numbering scheme started. None of
them were very expensive and we will finish connecting them in the next lesson. You've
learned, hopefully, how to solder, read the resistor color code, learned the pinout of a
serial connector, how to strip, twist and tin a stranded wire, read a schematic, identify
parts, and use the protoboard and a logic probe! Whew!! That's quite a lot for one lesson!
For people who have had some electronics experience, I spent too much time explaining
how to do things. For those who have had no experience, I probably didn't spend enough
time. In the following lessons we will continue to connect and test more of the complete
schematic, until, like I said earlier in this lesson, you will look at the schematic and the
seeming "rats nest" mounted on the wooden board, and say to yourself, "It's done, I think
I understand it, and I did it all myself!!".
By the way, as I finish typing this, I look over to my right and my eyes fall to rest on my
wooden board with it's associated "rats nest", and on the 20 character by 4 line LCD
display is showing the time, with the seconds ticking merrily by, the date, the temperature
in the room I'm setting in, the temperature outside the window next to me, the relative
amout of light falling on the solar cell that's laying on the board, two volt meters, one that
reads 0 to 5 volts DC and the other that reads 0 to 25.5 volts DC, and the last command I
sent with my X-10 remote over the power lines in my house, all the while, every 30
seconds, a light in the next room is turning on and off, commanded by the micro. In other
words, all the software we are going to be using to complete this course is written, tested,
debugged, and running now, on my rats nest.
The rest of the lessons in this course should be comming at the rate of at least one a week,
until we finish the course. Things come up, and I have a regular job that pays the bills,
that I have to spend at least 40 hours a week at, so don't hold me to this schedule, but
that's what I'm shooting for. So hang on!! It's going to be fast and furious.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

System Construction Part 2


The last lesson got you used to connecting parts on the protoboard and reading the
schematic. Now we will finish connecting the basic system. By the way, Digikey has the
very same digital voltmeter that I use for $35. The part # is BK2700-ND . This is the
same voltmeter I use at work most of the time, and is good for most applications.
Take four of the 22UF Caps and place the first between U3 pin 2 and GND, with the +
lead on pin 2. Next place one between U3 pin 6 and GND, with the + lead on GND. Next
place one between U3 pin 1 and U3 pin 3, with the + lead on pin 1. The last placed
between U3 pin 4 and U3 pin 5, with the + lead on pin 4. Now connect the red wire from
the RS-232 cable to U3 pin 14, the green wire to U3 pin 13, and the black wire to GND.
With your safety glasses on, turn on power and measure between U3 pin 2 and GND.
You should read between 8.5 and 10 VDC. Now measure between U3 pin 6 and GND.
You should read between -8.5 and -10 VDC. Notice the opposite polarity of the second
reading. You should always connect the black lead of the voltmeter to GND and use the
red lead for the measurement. U3 pin 2 should be positive and U3 pin 6 should be
negative. These are the two power supplies generated by the MAX 232 chip used to make
the RS-232 interface levels.
Turn off power and install the DS5000 chip, placing it at the extreme right end of the
protoboard. This should place pin 20 of the DS5000 on the last tie strip on the lower right
section of the protoboard (referring to the picture of the protoboard in the previous
lesson). There should be 2 rows showing on the lower half and 3 rows showing on the
upper half of the protoboard. The DS5000 is .6" wide and covers up some of the
connection points. We want to have 2 holes in each tie strip exposed for pins 1 through
20 of the DS5000 and 3 holes in each tie strip for pins 21 through 40. To say this another
way, after the DS5000 is plugged into the protoboard, there should be 2 holes left in each
tie strip on the lower half (pins 1 thru 20) and 3 holes left in the upper half (pins 21 thru
40). As always, the indent or dot on pin 1 should be pointing to the left as you plug in the
DS5000.
Connect U6 pin 40 to VCC and U4 pin 20 to GND. These are the power connections for
the DS5000. Now place a 10K resistor (brown-black-orange) between U6 pin 31 and
VCC. Place a jumper between U5 pin 5 and U6 pin 19. This is the clock for the DS5000.
Place a jumper between U3 pin 11 and U6 pin 11. This is the RS-232 transmit from the
DS5000. Place a jumper between U3 pin 12 and U6 pin 10. This is the RS-232 receive to
the DS5000. Place a jumper between U4 pin 2 and U6 pin 9. This is the RESET to the
DS5000. Place a jumper between U4 pin 4 and U6 pin 29. This is part of the LOAD
control for the DS5000. Place a jumper between U4 pin 6 and U6 pin 13. This is the
BREAK to the DS5000.

Now double check all the connections that you have just made. In particular the power
connections to the DS5000 and the connections between U3 and U6. It is CRITICAL that
these are right or you may damage the DS5000 chip. The rest of the connections, if they
are wrong, will only cause the system not to work correctly. This should be enough of a
system to be able to talk to with the PC. Make sure the BREAK / NORMAL switch is in
the NORMAL position. The LOAD / RUN switch should be in the LOAD position.
Now connect the RS-232 connector to your PC and start up your PC. You may have to
unplug your mouse to connect the RS-232 connector. This is the most common
configuration. If your PC was already on, with the mouse connected to COM 1, you need
unload the mouse driver, if one is loaded, to eliminate any conflicts created by the mouse
driver. To do this, reboot the PC with the RS-232 connector connected. The mouse driver
will attempt to load, but will fail, because you don't have it connected to the PC. The
mouse won't be needed in any of the work with the DS5000 that we do for the rest of the
course. In the previous lesson I went into detail about the mouse and Windows. Refer to
that description to make sure that you're ready for the DS5000. You should be at the DOS
prompt, in the directory that you've downloaded all the files I've given you into. All these
files should have been unzipped before now.
We are getting close to the real good stuff, so pay close attention. One of the utilities I
have written and provided to you is one called KUCONFIG.EXE . Start this one and
follow the prompts. The typical values will be COM 1, 9600 BAUD, and DUMPI set to
4000h. If you are using COM 2, then pick that instead of COM 1, but the rest should be
the same. Finish by reviewing the selections you made and answer Y to confirm and save
them. This will create a file called KENUTILS.CFG . This is the configuration for the
other utilities you will be using. You shouldn't have to run the configuration again, unless
you change COM ports later, which you probably won't.
Now start TALK.EXE. This is the terminal emulator program that will allow you to
communicate with the DS5000 interactively. You can use any terminal program you
wish, but this one will work for our needs. At this point we are about to embark on the
rest of this course in earnest. This is the culmination of all the previous lessons, and the
one I've been anticipating with eagerness on one part and bit of reservation on the other.
If what follows doesn't work for you, this will be the most difficult to trouble shoot. If
this does work, fantastic!
Making sure of all your connections on the protoboard, and of course wearing your safety
glasses, apply power to the system. If every thing is connected correctly, you should
press the ENTER key on the PC keyboard and a message similar to:
DS5000 LOADER VERSION 2.3 COPYRIGHT (C) 1988,1990 DALLAS
SEMICONDUCTOR
should appear on the PC screen. If it did, congratulations, you've made it!!! You are now
communicating with the DS5000 chip on your own personal rats nest! If it didn't, about
all I can suggest to you is to double check all the connections starting with lesson 12

through now. Some of the things that would make this not work is the mouse driver still
loaded on your PC, the RS-232 connector not wired correctly, the COM port you used
not working, or the protoboard not wired correctly. Look at the schematic as you re-check
everything, it shows all the connections.
Now we will play with the DS5000 Loader. In the Dallas Soft Microcontroller data book
is a listing of all the commands that you can use through the loader, starting on about
page 137. The first command we will do is the configuration command (W). Type W88
and ENTER. Now type R and enter. The 88 should be displayed again. You have just
configured the DS5000 for 16K of program memory and 16K of Data memory. This is
the configuration we will use throughout this course. You shouldn't have to use this
command again, although I have had to reconfigure the DS5000 on a couple of occasions,
though I don't know why.
Next type G and enter. There will now be displayed 4, two digit hex values representing
the four I/O ports on the DS5000. Now connect the logic probe to U6 pin 1, which is port
1 bit 0. I normally don't suggest you make any connections with power applied, with the
exception of the logic probe. I would caution you about static discharge. You should not
be wearing rubber sole shoes. These shoes, coupled with certain carpet types, can
generate very high static potentials that can destroy chips! The LED should be lit now.
Type P 1 00 and enter. This should place all of the lines of port 1 to all 0's. The LED
should go out. Now type P 1 FF and enter. This should set all the lines of port 1 to all
1's. The LED should be lit. Kinda' fun, ain't it? You are now communicating with the
DS5000 and setting and clearing port lines, watching them with your logic probe.
Next type D 4000 40FF and enter. You should now see several lines of hex characters,
representing the data currently in memory starting at location 4000, for 256 locations.
This format is a little difficult to decipher. It is called Intel Hex File Format. This is the
very same format that the assembler generates, that we will download to the DS5000 as
we write programs and test them. It is described on page 141.
Next type F 00 4000 40FF and enter. This filled location 4000 thru 40FF with all 0's.
Now type D 4000 40FF and enter. Now all the data displayed should be 0's. Now type F
FF 4000 40FF and enter. Now type D 4000 40FF and enter. All the data should be 1's
now. You have just altered the data memory inside the DS5000. Cool, huh? If you try
filling (F) or dumping (D) memory at any address before 4000h, you will get
unpredictable results. This is because the loader has some security features built in that
won't allow you to look at a program in the DS5000. You can only manipulate the data
memory section. This prevents you from accidentally overwriting any program
previously loaded into the DS5000 and keeps prying eyes out of your program. Since we
divided up memory at location 4000h (when we did the W88 command), all the memory
from 0000h thru 3FFFh is program memory. All the memory from 4000h thru 7FFFh is
data memory. There is no memory above 7FFFh, so any reference to locations 8000h thru
FFFFh will produce unpredictable results.

As an exercise in getting used to playing with the DS5000, I would try moving the logic
probe around, writing different values to the ports, and watching them with the logic
probe. There is one thing, though, only port 1 will work this way. In the LOAD
condition, the other port line outputs are in the high impedence state. Only port 1 can be
manipulated with the loader and the outputs affected by the P command. You can
however, use a jumper to GND to input to the other port lines and see it with the G
command. I would only play with the D,F,G,and P commands. I wouldn't use the W
command again, but if you do, make sure you finish by setting it back to 88. You can use
the R command to read this value at any time, since it only reads the value, and doesn't
alter it. There is an explanation of the MCON register (the one you are altering with the
W command) starting on page 13. These 6 commands are about all that you can play with
that do anything useful to the DS5000. There is a Load command that downloads an Intel
Hex File to the DS5000, but we will use that in the next lesson. There is also the CTRL C
command that will restart the loader and display the opening message.
Play around and enjoy. Some of you may have thought that you would never get to this
point, but I hope you did.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

Our First Program and the HD44780


Display Driver
So far our work with the DS5000 has been to assemble a program, and load and run it in
the simulator. Now starts the process that we will repeat many times throughout the rest
of the course. We will take one of the example programs from a previous lesson and
download it to the DS5000 and watch it execute.
start: mov a,p1
mov p0,a
sjmp,start

;read the switch


;write to the led
;go to start

This is prob02.asm from lesson 6. This is an ultra simple program. It won't look like
much when we run it but it is, however, a program that does something. It reads a switch
and lights an LED. The switch will be a jumper that we connect or disconnect and not a
real switch. We will use p1 bit 0 (P1.0) as the switch input and p0 bit 0 (P0.0) for the
LED. The logic probe will be the LED. Connect the probe to P0.0 and a jumper between
GND and P1.0 .

Make sure the LOAD / RUN switch is in the LOAD position and the RS-232 connected
to the PC. Apply power to the system. The LED should be lit because all of the port lines
on the DS5000 are at a high impedence state when in the LOAD condition. On your PC
type A51 PROB02 . This will assemble the program. Now type DNLOAD PROB02 . This will
download it to the DS5000. Now place the LOAD / RUN to the RUN position. The LED
should go out. Now remove the jumper from P1.0 . The LED should lite. Re-connect the
jumper to P1.0 . The LED should be go out. If this didn't work, check your connections.
We have just traced all the steps that take us through the development process. First a
program is written or modified with a text editor. The program is assembled into an
object file. The object file is downloaded to the DS5000 and executed. Operation is
monitored with the logic probe to note any problems or whether a change has produced
the desired result. If there is a problem, the program is loaded into the simulator and
execution is monitored to reveal the problem. And then you do it all over again. This
cycle is repeated many times before even a crude system is written, and then many more
times to get to a workable prototype. Then lots more debugging and refining the system.
This is just a rough outline of the actual process. You may or may not use the logic probe
in each cycle of process, nor the simulator. I sometimes use the test led and an instruction
to light it placed at the suspected problem area for software debugging, instead of the
simulator. The simulator is good for initial testing of a program, but ultimately you wll
want to know which path a conditional jump took when a certain event occured.
Construction of microprocessor based hardware is unique in the computer field, I think,
because you work both sides of the problem, both hardware and software. In most other
fields of computers, you normally specialize in either the hardware area or the software
area. When a problem comes up, the software people always point to the hardware as the
problem and the hardware people point to the software as the culprit. Typically, the
hardware people know very little, if any, software. And the software people know very
little, if any, hardware. So, when either side views a problem, a totally different view of
the problem is arrived at by both sides.
In our area, microprocessor system design, a problem is viewed in a sort of stereo, seeing
both from a hardware point of view and at the same time, from a software point of view.
This stereo view gives an insight into a problem that will ultimately cause the correct
diagnosis to be arrived at, usually much quicker than the two sides together could. This is
because, when looking at a problem from a hardware - only point of view, no
consideration is given to software timing when trying to solve a problem, for instance.
Conversely when a softhead is trying to solve a problem, the ac ripple in the system
power supply is not up for consideration.
As we move through this course, I will attempt to keep a dual track, both hardware and
software, to reflect this stereo view so that you will eventually develope this insight
yourself.

At this point I want to review what my expectations are of you and what minimum skills
you should have attained by now.
1. You should be able to read a schematic, understanding that the schematic shows only
the electrical connections, but not the physical connections.You should be able to convert
the electrical connections into physical connections.
2. You should be able to solder. How to twist and tin a stranded wire.
3. You should be able to use a voltmeter and the logic probe. The black (-) lead of the
voltmeter is normally connected to GND, and the red (+) lead is used for taking the
reading. That the voltmeter must be on the right function, ohms, volts, etc., and if
necessary, the right range, 1,10,100, etc.
4. You should be able to use a text editor to type in a program
5. You should be able to use the assembler to assemble it.
6. You should be able to use the simulator to debug it.
7. You should be able to download the program to the DS5000.
8. You should know the function of the LOAD / RUN switch.
9. You should understand the physical layout of the protoboard. That it is made up of an
arrangement of tie strips, each having 5 tie points. That there are 4 power busses, 2 VCC
busses and 2 GND busses. You should be able to make a jumper from a piece of solid
#24 insulated wire and plug the ends into the protoboard. You should be able to connect
two parts together using a tie strip on the protoboard, realizing that no other parts can
touch the unused tie points in this stip.
10. You should be able to use the pinout help file and the schematic to connect to the
correct pin of a part on the protoboard.
11. You should be able to read the resistor color code.
12. You should be able to "talk" to the DS5000 using the loader and your PC.
13. You should be able to use basic tools; hammer, screwdriver, pliers, cutters, etc.
Don't be disturbed by my list. I don't mean that you are currently experts at any of these
skills, just that you realize that these skills are necessary to understanding the rest of the
course. I'm not going to mention how to connect the voltmeter to read a voltage, I'm just
going to say "the voltage at this point should read 3.55 VDC". I'm not going to say open
up the editor and load prob02.asm and...". I'm going to say "edit prob02.asm and... ".

For me to complete this course in some reasonable span of time, I must be able to talk to
you on a technical level, without describing the gory details each time. As usual, any time
something new comes up, I will describe it in as much detail as I can. But after that, you
will be expected to remember it, or at least to remember that you need to refer to it from
time to time.
So consider that you have reached technical puberty, and that I'm going to talk to you as
adolesents instead of children, a transition that I've been eagerly anticipating. Any time
you feel that I'm moving too fast, drop me a note, with a question, and I'll give you extra
instruction.
I've had it brought to my attention that there are several wires not connected on the
protoboard that are on the schematic. We will eventually get to these, as they are needed.
The two wires from the power supply that are just hanging loose, make sure that these
don't touch together or to any other part of the protoboard.
We are going to connect the LCD display to the system next. Looking at the schematic,
there are 11 connections between the DS5000 and the display, not counting power. Eight
of these are the data bus, and 3 lines are for control of the display data bus. This display
uses the Hitachi HD44780 display driver chip. This gives us a display that can be directly
connected to the DS5000 with minimal effort. The display is really a microprocessor
itself, with the interface logic to connect to the LC display hardware.
On the display board, there is a 14 pin connector area along the top left corner of the
board, looking at it right side up from the front (Refer to the pinout help files). I
mentioned in the previous lessons that I bought a connector male and female and used a
12" piece of 14 conductor ribbon cable for the display cable. You can just solder the
ribbon directly to the display, eliminating the connectors entirely. It will work just as well
this way, with extra care given to not flexing these connections any more than absolutely
necessary. However you do it, the ribbon at the protoboard end will be a bunch of
twisting and tinning to get solid ends to plug into the protoboard. I find that about a 1/4"
finished end on ribbon wires will be sufficient to plug into the protoboard. I would
separate the individual colored wires on this end for about 6" of the cable. This will allow
you to plug the wires in anywhere on the protoboard.
So now you've got a display with 12" of 14 conductor ribbon cable connected to the
display and the other end of the cable stripped out 6" and the individual wires tinned a
1/4" on the ends. That rolled off my tongue pretty quick, but it takes a while to do this.
The easiest way to solder the ribbon to the display is to separate the colors back about an
inch and strip, twist and tin about an 1/8th inch. Then solder the two outside wires to the
two outside connection points. This will hold the wires up to the display for easier
soldering. Solder the rest of the wires, one to each connection point, until you have them
all. A really useful device in all this is a vacuum base vise. This little beauty sucks down
on the table with a lever, and stays there for a long time. I use one all the time for making
up connectors or connections to a part. It gives you a "third hand" that is indespensible.
They cost around $60. Digikey has a Panavise that is part# 303PV-ND and 380PV-ND (it

takes both numbers) for $56 total. This is like the hot glue gun, you'll wonder how you
ever got along without one.
Now looking at the schematic, connect all the lines up to the DS5000. A method I use
when doing this is to check off each line as I plug it in. If you do this for all connections
you make on the schematic, a quick glance will reveal whether a particular connection
has been made, when trouble shooting. Other than the 11 lines previously mentioned,
there are 3 other lines. Pin 1 is the GND pin and pin 2 is the VCC pin. Pin 3 is the display
contrast adjustment line. You will need to tie a 330 ohm (R5) and a 10K ohm (R4)
resistor together with the 10K connected to VCC and the 330 connected to GND. Then
connect pin 3 of the display to the junction of R4 and R5. Suggestion: find an unused tie
strip and plug one end of the 10K into VCC and the other end into the tie strip. Plug one
end of the 330 into GND and the other end into the same tie strip. Then plug the wire
from pin 3 of the display into the same tie strip.
Double check all your connections, particularly power and ground, but they all have to be
right. You should have checked off all 14 connections to the display.
The Hitachi HD44780 Display Driver
I need to now go into an explanation of the HD44780 Host interface. As I said earlier, 8
of the connections to the display are for 8 bit data, although only 4 have to be connected
for nibble operation. The other 3 are the ones that need explanation. One is the E line.
This is an active high control line. Active high means that when this line is low, it is
inactive, it is serving no function. It could have been more accurately named STROBE.
This line, on it's rising edge, strobes data either to or from the data bus. The second line is
the R/W line. If this line is high, data is transfered from the display. If this line is low,
data is transferred to the display. The last line is the RS line. If this line is high, the
display is in the data mode and if this line is low the display is in the command/status
mode.
A typical display routine will first read the status of the display to see if it is busy, and
then make a transfer with the display. Data can be written or READ from the display. I
don't normally read data from the display, but that option is available. Each interaction
with the display is prefaced by reading the display status and waiting until the display
isn't busy. This concept of waithing until something is ready is due to the fact that the
display is really a dedicated microcontroller that's running it's own program, being a
display. When you write a byte to the display, you've really just handed it off to the
microprocessor in the display and now it goes through it's program doing what displays
do and eventually showing us a visual feedback, through looking at the display. This all
takes time for the display to do, so if you start him doing something, in all likelyhood, it
will be many microseconds before he will be ready to do something else. So we wait for
the display to get through with anything it may already be doing, and then tell him
something else to do. This holds true for many of the peripheral chips, like A/D
converteres, who are micros themselves with execution times. Notice that this type of
interface is a polling interface, not an interrupt driven interface. I can't know anything

about the status of the display without reading status from it. There is no way for the
display to signal it's state of readiness to the DS5000, except by polling status from the
display.
To read status from the display and wait until the display is ready, the following steps
would be taken. Normally the E line will be low, the RS and R/W lines can be anything,
and must be assumed to be wrong, so they are set each time an operation is required:
1.
2.
3. over1:
4.
5.
6.
(busy)
7.

set the RS line to a low (status)


set the RW line to high (read)
set the E line high
read the display
clear the E line
Test Bit 7 (BF) in the acc and loop to over1 if a one
If bit 7 is a zero, the display is ready

Step 1: sets the display bus for a read operation


Step 2: sets the display bus for a command/status operation.
Step 3: enables the display bus.
Step 4: reads in the status from the display bus into the accumulator
Step 5: disables the display bus.
Step 6: tests the READY bit to see if it is a ONE and if not, loops back to step 3
Step 7: the end of routine.
Here are the actual commands that we will use

over1:

setb
clr
setb
mov
clr
jbz

lcrw
lcrs
lcde
a,p0
lcde
acc.7,over1

The label will be different, but these are the commands that will be used.
There are some mechanical software procedures that must be used with the HD44780
every time you apply power to the system or reboot (RESET) occurs to the system. First,
a sequence of bytes must be written to the display, in a timing order, to reset the display
and set it to the proper system operating parameters. It follows a sequence that is
described in the Hitachi data sheet for the HD44780.

Following is the actual segment from our operating software that deals with the display.
There are several subroutines that this initialization uses. Their description and code
follows this initialization routine.
Subroutine: INLCD - This routine initializes the display after power up or a system
reboot. First we initialize the 3 control lines to the display.
inlcd: clr
lcrs
status mode (read)
clr
lcrw
clr
lcde

;clear rs to set data bus to command (write) /


;clear rw to set data bus to write mode
;clear E to disable display bus

The first thing that must be done is to initialize the display after power up. This is done
by first waiting at least 15 msec (milliseconds) after power up. I used 16 just to make
sure.
mov a,#h'10
lcall waitp

;wait
;16 milliseconds

The next thing is to write a 38h to the display and wait 5 milliseconds. The 38h says that
we want an 8 bit data bus, a 5 X 7 font, and a 4 line display (it is really functionally only
a 2 line display, but I'll explain that later).
mov p0,#h'38
lcall strob
mov a,#h'05
lcall waitp

;write
;38 to lcd
;wait
;5 milliseconds

The next thing is to write a 38h to the display and wait 1 millisecond
lcall strob
mov a,#h'01
lcall waitp

;38 to lcd
;wait
;1 millisecond

The next thing is to write a 38h to the display, actually checking the busy bit (reading
display status). The previous writes to the display were using instruction timings to wait
different amounts of time. This is a requirement of the display to guarantee that it is
initialized. Normally all transactions with the display are prefaced by reading display
status and waiting until not busy, before actually doing the deed. These are the only
examples where this rule isn't strictly followed.
mov a,#h'38
lcall wclcd

;38h to
;display

Next output a 0ch to the display. This turns on the display, turns off the cusor, and sets
the cursor to increment (move to the right).
mov a,#h'0c ;write
lcall wclcd ;0c to display

Next output a 01h to the display. This clears the display and places the cursor at the home
position.
mov a,#h'01 ;write
lcall wclcd ;01 to lcd

Next output a 06h to the display. This sets the cursor to increment (move to the right),
and no display shift (characters would move off the left side of display when too many
characters had been displayed).
mov a,#h'06 ;write
lcall wclcd ;06 to lcd

Next output a 10h to the display. This sets the cursor to move, the display not to shift, but
if it did shift, shift to the left.
mov a,#h'10 ;write
lcall wclcd ;10h to lcd
ret

This ends the startup initialization needed for the HD44780. Following are the
subroutines used by the display initialization routine.
Subroutine: WCLCD - This routine writes a command to the display. This function is
used when ever a command is written to the display. The routine enters with the
command in the acc. R7 is used for temporary storage of the command by the subroutine.
When the routine exits, the data has been transfered.
wclcd: mov r7,a
clr lcrs
setb lcrw
wc001: setb lcde
mov a,p0
clr lcde
clr E
jb acc.7,wc001
mov a,r7
clr lcrw
mov p0,a
setb lcde
clr lcde
mov p0,#h'ff
ret

;save the command in r7


;set lcd to command
;set lcd to read
;set E
;read display status
;
;if busy then doit again
;set lcd to write
;write data to lcd
;set E
;clr E
;tristate p0

Subroutine:WAITP - This routine waits for an amount of milliseconds in acc. Using a


clock frequency of 11.0592 Mhz, the loop count value needs to be an e6h for both 8 bit
timers. This routine enters with the acc holding the count in milliseconds that the routine
is to wait. . Uses r7 for temporary storage of count. This routine is two simple 8 bit
counters cascaded together so that each time r6 is decremented to zero, r5 is decremented
once. R6 is the low byte and r5 is the high byte.

waitp: mov r7,a


wtx02: mov r6,#h'e6
mov r5,#h'e6
wtx01: djnz r6,wtx01
wtx03: djnz r5,wtx03
djnz r7,wtx02
ret

;save # milliseconds
;initialize
;counters
;decrement r6 and jump not zero
;decrement r5 and jump not zero
;decrement count and jump not zero
;done, return

Subroutine:WDLCD - This routine writes a byte of data to the display. This routine is
always used to transfer a byte of data to the display. The routine enters with the acc
holding the byte of data. R7 is used by the subroutine to temporarily store the byte of
data. When the routine exits, the byte of data has been transfered.
wdlcd: mov r7,a

;save the data temporarily in r7

First we initialize the control lines to the display


clr lcrs
setb lcrw

;set lcd to command


;set lcd to read

Next we wait until the display is ready.


wd001: setb lcde
mov a,p0
clr lcde
jb acc.7,wd001

;set E
;read lcd busy
;clr E
;if busy then doit again

We get the data, initialize the control lines


mov a,r7
setb lcrs
clr lcrw

;set lcd to data


;set lcd to write

Write the data to the display


mov p0,a
setb lcde
clr lcde

;write data to port


;set E
;clr E

Any time we write any data out on the data bus (p0) we must always return them to all 1's
when we are done. This keeps the tristate bus working properly for the other speakers on
the bus.
mov p0,#h'ff
ret

;tristate p0

Subroutine:STROB - This routine strobes the E line to the display.


strob: setb lcde
clr lcde
ret

;set E
;clr E
;

Display Commands
The display is ascii character based. There are commands to clear the display, home the
cusor, define the cursor, set the cusor to a character position, and read and write display
data.
Command

HEX

Description

Clear display

01

Clears the display and homes the cursor.

Return home

02

Homes the cursor

Function set
and 5 X 7 font

38

Sets the data bus to 8 bits, 4 line display,

Entry mode set


display shift

06

Sets the cursor direction to right with no

Display ON/OFF
no blinking cursor

0C

turns on the display, turns off the cusor,

Cursor / Display

10

Cusor move, no display shift

Set cursor position


(20 characters)

80

This is the start of the top display line

C0

This is the start of the second display

94

This is the start of the third display

D4

This is the start of the bottom display

line
line
line

"
"
"

Here is how we could send the word "Hi" to the first two character positions on display
line 1 (far left side)
Oneln:

mov a,#h'80
lcall wclcd
mov a,#'H'
lcall wdlcd
mov a,#'i'
lcall wdlcd

;set cursor to
;start of line 1
;mov a ascii 'H'
;to the display
;mov a ascii 'i'
;to the display

The display should now have the letters "Hi" in the first two character positions of the top
line on the display. (See the table that describes the HD44780 instructions).
I am giving you an initial test program that will test your display and output a message to
it if it is working. This program is called disptest.asm .If it works, your display should
have the message "Hi there" in it. The rest of the display may look wierd, but don't worry
about that. If the program dosen't work, check your connections.

Here is a link ADC0808/ADC0809 8-Bit MuP Compatible A/D Converters with 8Channel Multiplexer to the A/D Converter data sheets, in PDF format. Here is a link
DS5000(T) to the DS5000 data sheet in PDF format.
With the ONELN example above, you should be able to modify DISPTEST.ASM and
write any characters you wish to any place on the display. Give it a try.
In the next lesson we start looking at the ADC0808 A/D converter chip and it's interface.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

The ADC0808 A/D Converter


The A/D Converter we will use is the ADC0808 by National Semiconductor. This
converter is 8 bits and has a microprocessor bus interface and single +5 power. It also has
an 8 input analog multiplexer. The interface requires 6 lines in addition to the data bus.
These are the 3 mux input select address lines and 3 control lines. The 3 mux address
lines are connected to data bus bits 0 through 2. So that a nibble (0 - 7) written to the A/D
will select one of the eight inputs. There is a bus output enable line for reading the A/D
and a End of Convert line that signals the host that the conversion is complete. Lastly
there are 2 more lines that are tied together and connected as 1 to the host. This is the
Start/ALE line. By placing a nibble (0 - 7) on the data bus and setting the Start/ALE line
to a one, the input mux selection is written to the A/D. When this line is taken back low,
the conversion process starts. The EOC line is driven low. Within a few milliseconds, the
conversion is complete and the EOC line is driven high. The OE line is set high and the
result is read from the A/D data bus. The OE line is set low, disabling the A/D output
data bus. This completes one cycle of the A/D conversion process.
There are 2 other lines that are tied, one each to the Vcc and Gnd pins. These are the
+Vref and -Vref pins. The A/D conversion is done based on the ratio of the input to the
Vref pins. In other words, if +Vref is +5.00 volts, then each bit is worth 5.00/256 or 19.53
mv (millivolts). The more accurate voltage for +Vref ( in terms of measuring a voltage)
would be 5.12 which would yeild a 20 mv / bit resolution. We could then just multiply
the value of the conversion by 20 mv and have the actual voltage on the input, accurate to
+ or - 20 mv. This would result in a 5.1 volt full scale reading if the binary value is FFh
or 255d). So full scale is 1 bit less than +Vref. Also +Vref can't be more positive than
Vcc and -Vref can't be more negative than ground. So to have a 5.12 volt +Vref, you
would have to have a +5.12 volt Vcc.

In this prototype, I just tie the Vref to Vcc and the -Vref to Gnd and take the loss of
accuracy in favor of simplicity. I use 20 mv for the value of each bit which results in an
error of about 2.35%. In most logic families, it doesn't hurt to use a Vcc of 5.12 instead of
5.00, and can actually be up to 5.25 volts in most cases without damage. So if you wanted
better accuracy you could make your system power voltage adjustable, and set it to 5.120
volts.
If you are measuring the position of a potentiometer, with either end tied to Vcc and Gnd
and the wiper as an input, then it doesn't matter what the +Vref value actually is, since
you are now looking for a ratio, not an actual value. You want to know the POSITION of
the wiper, relative to either end, not it's actual resistance or voltage. This is what the
ADC0808 was designed to work best with and it's called ratiometric.
Here are the steps in order and what each does. Initially the EOC line is high, the OE line
is low and the Start/ALE line is low:
1. A mux selection (00h) is placed on the A/D bus
2. The Start/ALE line is set high.
3. The Start/ALE line is set low
4. The EOC line is monitored until high
5. The OE line is set high
6. The converted results are read from the A/D
7. The OE line is set low.
Line 1 - Bits 0 thru 2 are the mux select lines. So to select, say input 0, a 00h is placed on
the bus. To select input 7, a 07h is placed on the bus.
Line 2 - Setting the Start/ALE line high, strobes in the mux select to the A/D. At this
point what ever voltage is being applied to input 0 is now connected to the A/D converter
input.
Line 3 - Resetting this line starts the converter converting. Shortly after this the EOC line
goes low, indicating busy.
Line 4 - The EOC line is monitored until it goes high, indicating conversion complete.
Line 5 - The OE line enables the A/D output data bus when it is high and disables it when
it is low. So setting the line high here enables the A/D output bus.
Line 6 - The converted value is read into the DS5000.
Line 7 - The A/D output bus is disabled (tri stated, or turned off), and the conversion
process is complete.
Tri State Bus

To digress just a moment, I haven't done that for a while, I need to discuss a general point
about buses and tying several things to the same point. If I have 2 regular switches and I
tie their contacts together (parallel) so that I can monitor both switches with two points, if
both switches are open, then the output is open. If one of the switches is closed, the
output is closed. If the other switch is now closed, no change is seen on the output, since
the output was already closed by the first switch. The point here is that when you tie two
things together, there can be a conflict between the two things, if they both try to signal at
the same time.
There are a couple of ways around this problem. One is to put each thing on a separate
connection. This solution requires a lot of hardware to work. The DS5000 only has about
24 pins that you can connect something to. While this may seem like a lot of connection
points, if you wanted to connect the display to its own lines, and the A/D to its own lines,
you would need 11 for the display and 11 for the A/D. That's already 22 and you haven't
connected a single switch yet.
The other approach is to use a tri state data bus. In normal logic, you have two possible
states, a 0 and a 1. Tri state adds a third level, called off. Off means just that. When the
bus is disabled, it is in the high impedance (high resistance) state, or turned off, for all
practical purposes, disconnected. While you still have the original limitation, you can't be
reading from both at the same time, you can share the bus between the display and the
A/D by reading each at a different time, one after the other.
This is what we do. There are 8 pins on each of the DS5000, the display, and the A/D
chips that are all tied together, in a seemingly impossible situation. But the connections
are only mechanical. Inside each chip are another set of switches that connect the
mechanical pins of the chip, with the logic inside. If these switches are open, the chip is,
in effect, disconnected.
There are other lines, the 3 mux select lines on the A/D that could have required 3 more
lines, had we not tied these to the data bus. These are inputs, not outputs, so the problem
of conflict doesn't enter in here. Adding these lines to the data bus have no practical
effect on the data bus operation. Since normally the display and the A/D chips have their
bus drivers turned off (boy it gets harder to keep separate, logic buses and riding buses,
we have logic bus drivers now). We simply use the bus for different things at different
times.
At the time we are placing the 00h on the bus to select input 0 on the A/D, the Start/ALE
line is what makes use of the bus at this time. It strobes the 00h into the A/D chip. The
display chip is being a display and could care less that the A/D is using the bus. A more
elemental way of looking at the problem is that on a bus, the problem of conflict arises
when more than one device is trying to TALK on the bus at the same time. The problem
of conflict never comes from how many "listeners" there are on the bus.
So the necessary evil that comes along with buses are control lines. In our case 3 lines for
the display and 3 lines for the A/D. So far, we've saved ourselves 11 connections that

would normally have to be made with separate lines, the 3 mux select lines and the 8 data
bus lines from the A/D. We have used 14 lines to make 25 connections between the
DS5000 and the display and A/D chips. We will ultimately add a keyboard to the system
that will add 1 new control line, and use all the data bus lines. Then we will have used 15
lines to make 34 connections.
Buses are terrific things, but they do have their drawbacks. Any time one of the chips that
have bus drivers fails in a way to leave drivers stuck on, through some hardware failure,
or software glitch, none of the other devices on the bus can communicate over the bus. In
essence, there is a loudmouth, screaming at the top of his lungs, in a small, crowded
room, so that no other communications can take place in the room. It's a sacrifice made
with all due diligence and forethought. If the bus "breaks" you fix it.
Now for a little about p0 on the DS5000. We are using p0 as the peripheral data bus for
the display, A/D, and eventually a keyboard. Any time we (the DS5000) writes to p0, we
must follow the operation with an instruction to write all 1's to p0. This leaves p0 in it's
tri state condition. The outputs of the DS5000 are high enough resistance that other
outputs can override the DS5000 outputs. But this only works if all the outputs are high
(hence the pull up resistor), and some one wants to bring one low. It won't work if the
line is low and some one wants to bring it high. It will stay low (or worse, burn up a
chip!). It's like the switch analogy. If both switches are closed, and you open one, the
output is still closed by the other switch, so no change occurred on the output. Both
switches have to be open for any one switch to communicate.
P0 is also unique amoung the DS5000 ports in that is is an open collector output, as
opposed to totem pole. In my design here, the display has enough of a pullup on it's
inputs, that it pulls up p0 so that no external pullups are required. If you don't use a
display, to be safe in any case, you should pull up all 8 lines with a 10K resistor to Vcc.
I don't show any on my schematic, and, indeed, I don't use any on my prototype either.
The other DS5000 ports are totem pole outputs, that don't require any pullups.
Back to the ADC0808. Here is the program as it will exist in our system software. The bit
adec is the EOC (end of convert) pin of the ADC0808. The bit adst is the Start/ALE
input pins of the ADC0808. Bit adoe is the A/D output enable pin.
adcnv:
wait2:

jnb
setb
mov
clr
mov
mov
setb

adec,wait2
adoe
a,p0
adoe
a,#h'00
p0,a
adst

;wait until EOC is a one


;enable A/D output bus
;get converted value
;disable A/D output bus
;set mux select to input 0
;put mux select on the bus
;set Start/ALE to a one (this strobes in the

adst

;clear Start/ALE to a zero (this starts the

p0,#h'ff

;set p0 to tri state bus

address)
clr
conversion)
mov

This example is only converting input 0 and never converts the other 7 inputs. Otherwise
it is exactly as it will be in the final software. This pretty well covers the display and the
A/D converter.
Now for something on how our system is going to operate. As I've mentioned in another
lesson, there are two basic ways to have events signaled to the system. One is to poll all
the devices, getting their status. Another is to have an output, like EOC on the A/D chip
generate an interrupt. We could have done that ourselves. But the DS5000 only has 2
external interrupt inputs. One of these I like to dedicate to a "Break" switch, for trouble
shooting purposes. So without adding any additional hardware to expand these, I only
have one external interrupt to use for all our interfacing needs. Not much to work with.
For our needs, this is quite sufficient. What we will do is to exercise the third option, and
that is to have a combination interrupt driven polled response. The X-10 computer
interface module has an output for the host, that is an optic coupled, 60 Hz square wave,
in sync with the 120 VAC power. We will double this to 120 Hz, for reasons I'll explain
when we get into X-10 interfacing. The parts U2, D9, and R20, along with the two loose
wires from the power supply that we haven't connected yet, are used to make a substitute
for this feature until we actually connect the TW523 interface. These parts also make an
optic coupled, 60 Hz square wave output, synched to the 120 VAC power.
In either case, the system operation is identical. With this arrangement, we will get an
int0 interrupt 120 times a second. This is exactly the rate needed to accommodate the X10 interface, with the least hardware. During each iteration of this interrupt, several
things are going to happen. First the A/D is serviced, then the display is serviced, and
eventually a keyboard will be serviced. Also the X-10 is serviced.
The other interrupt that could be happening regularly is the RS-232 serial port. When a
character is received on the serial port, an interrupt is generated that says that the receive
buffer is full. It is the responsibility of the program to get to the serial buffer and read the
contents before the next character comes in. If you don't, chaos will most surely erupt.
The DS5000 takes 12 clock cycles to make an instruction cycle. This makes the 11.0592
MHz clock turn into 921,600 instruction times or cycles per second.
With 9600 as the baud rate, there are 960 instruction cycles between characters, if
characters were coming at the maximum rate, or worst case. There are 7680 instruction
times between each 120 Hz interrupt. Since we never disable any interrupts after startup,
and an interrupt can occur and be serviced, while inside another service routine, there
shouldn't be any problems.
The things to consider when doing much of the software inside an interrupt routine, is
how much of the total supply of instruction times will be used up by the interrupt, and
will this be too many to allow the operating loop to have some crunch time. The thing to
remember, there are a finite number of instruction times in each second of time. In our
case, there are 921,600 instruction times in each second. We are going to have two

different pieces of software running all the time, one the Operating Loop, another the 120
Hz interrupt routine, along with any characters sent or received on the RS-232 link.
One thing that I like to do is to calculate shortest and longest execution times for all the
different routines, and keep a list of them, to reference when trouble shooting. This takes
a while to compile, but it is priceless if it's needed.
Back to our system. All the polling of devices on the tri state peripheral data bus will be
done during the 120 Hz interrupt routine. The operating loop will not directly
communicate with these devices. This communication will take place through the
interrupt routine, using flags and buffers.
The external data ram is used for the buffers needed to make all this work. The DS5000
has only 128 internal ram locations, which run out fast with large buffer sizes. But we
have "more ram than we can shake a stick at", in external data memory.
The display has a buffer here that is 128 bytes long. If the operating system wants to
display something, it is written to this buffer. During the 120 Hz interrupt routine, one
line of the display is refreshed. So in 4 iterations of the 120 Hz routine, the display is
completely updated. Or another way of saying it, the display is updated at a rate of 30 Hz.
I first tried to update the entire display each 120 Hz cycle, but the next interrupt would
occur before I finished servicing the previous interrupt, in other words, I ran out of time.
It took longer than 1/120th of a second to update the entire display. I chose 1 line per
cycle, for a fast real time display rate and less overhead on the host.
During each iteration of the 120 Hz interrupt, one point is converted by the A/D
converter. In 8 iterations of the 120 Hz routine, all the inputs will have been converted.
Another way of saying this is that the A/D points are scanned at a rate of 15 Hz, or 15
times a second. The 8 readings are stored in a buffer that is read by anyone wanting to
know a reading. The reason I chose this approach is that the A/D is in the process of
converting a point, almost all the time. You hardly have to say anything to him, he just
works. By doing it this way, there is very little overhead to scanning the A/D points into
the system memory. The A/D is left mostly waiting around for that poky old DS5000 to
get around to giving him his next job.Of course that inconsiderate ungrateful A/D doesn't
realize that the trusty DS5000 is taking care of a lot more than just A/D.
While all this A/D and Display work is goin' on, there are X-10 commands to send and
receive, PC commands to be honored, at the earliest possible opportunity. In addition a
security system that can turn on an X-10 device (horn for instance) to wake the dead
when needed. A real time clock keeps up with scheduled X-10 events and watches for
scheduled tasks to do, like start the coffee pot, or lower the thermostat, turn off the kids
TV maybe.
I am looking for a good Phone interface that would allow the system to be expanded to
the phone system, without going through the UL and FCC listing process to get an
acceptance sticker. When I do, we will add that to our arsenal. I know the devices exist, I

just have to get one and start playing with it. With that we can add dial in control and
status features. With a couple of other chips, we can add voice control, 900 Mhz RF
communications and 28.8 Modem interface.
Other cool interfaces would be a sonic range finder, synthesized voice playback, IDE
hard drive interface, WIRELESS BRAIN WAVE TRANSCEIVER!!....... Maybe we're
goin' a little too fer here, partner. I don't think so.
I'll let you in on a little secret. One of my pet projects (that's those you never get to) is to
get a model airplane (10 ft wing span) and mount one of those little digital camera's in the
nose, have a 900 Mhz radio link with video down link and stick, throttle, flaps, and
rudder controls, and have, on the safe end of this scheme, a chair with a monitor and
joysticks to watch and control the plane. I love simulators but none are really like flying
yet, with real time frame rates and infinite graphics. This would be really cool, and fun to
fly. You could have an air speed indicator and GPS to keep your course and maintain safe
operating distance from the tower. If the plane got too far away, the GPS would guide the
plane back to the tower and go into a slow bank around the tower, 100 ft off the ground.
Of course if you got that to work, the better one would be a glider, with a small engine to
get it launched. You would need a gravity meter, to detect up and down drafts and maybe
a level indicator, along with an altimeter and real time audio from plane. You could fly
this from your car, sitting on a hill.
Another might be a sailboat, 8 ft long or so, with motorized jib and main, tiller, camera's,
tilt meter, water speed, and movable ballast. Something on the order of an America's Cup
boat.
Another would be some kind of an air boat, or hovercraft. The ultimate, in my mind, is an
autonomous submarine that could be piloted from a boat above. I'm not sure how to do
the link between the sub and the surface. I've never played with communicating through
water. But I'll bet it can be done.
Well, enough of imagination for now. To digress another moment, I need to speak about
certain "tricks" to help in writing software drivers, or interrupt service routines. There is a
method of smoothing out digital or analog inputs to the system. For digital inputs,
switches, contacts, etc., the process is called "debouncing".
The term debounce comes from the dry contact world. It is to remove the bounce from a
contact. If you drop a ball to the floor, it doesn't stick to the floor the first time it contacts
it. It bounces back up and then down again and then up, each time getting closer to
sticking to the floor. After several bounces, the ball comes to rest on the floor. You
started with the ball not touching the floor, and then several bounces later, the ball is
touching the floor.
The same thing happens in a relay, switch, or any other contacts that come together with
a force driving them together. In the case of the ball, gravity was driving the ball together

with the floor. In a relay, the energized coil, creating an electromagnet, drives (pulls) the
contacts together. The same thing happens in the relay or switch as did with the ball. The
contacts bounce together several times before they touch continuously.
To debounce a contact, the input is sampled several times, and only then is it recognized
as having changed. To hang a cap on a noisy line is somewhat the analog electronic
equivalent of debouncing the line. The cap eats up spikes, hopefully, cleaning up the line.
I normally write a routine that scans all the inputs at once. Usually I arrange the hardware
so that all the inputs are in one port. Then I read from that port, masking out any bits that
aren't inputs, and then comparing the current status with the previous status, when you
scanned it last.
You are looking for a change between the previous, or old status, and the current status.
As the micro is powering up, the inputs are read and stored as old status, to be used for all
the succeeding scans of the inputs.
I always have an operating loop, that continuously scans the inputs, in a never ending
loop, and then jumping out to a routine associated with a particular input, when it is
sensed and debounced. After the routine is finished, I return to the loop, to stay there,
until another input has changed.
Here is a generic input scan routine, that will read and debounce the 8 inputs in port 3 of
the DS5000. This code uses a location for old status (ostat), a location for current status
(cstat), a location for debounced changes (dbchg), and 8 locations starting at the label
dbctr that are the 8 debounce counters, one for each input. All this code is shown in the
proper sequence, even though there is descriptive text in between each part.

This code reads the inputs and stores it as old status.


mov
mov

a,p3
ostat,p3

;read current status


;and store it as old status

This is the start of the operating loop. This code reads the inputs and compares them to
the old status, generating any changes as a 1's. If there isn't a change on an input, that bit
will be a 0.
begin:

mov
mov
xrl

a,p3
cstat,a
a,ostat

;read current status


;and store as current status
;generate changes

This code goes thru all 8 inputs and either increments the associated debounce counter if
there is a change, or clears the counter if there isn't a change.

n0002:

mov
mov
rrc

r0,#dbctr
r3,#h'08
a

;set to start of debounce counters


;set for 8 debounce counters
;rotate bit of change into carry

jnc

inc00

;if bit is a 0 (no change) then goto

inc
inc
djnz
sjmp
mov
sjmp

@r0
r0
r3,n0002
c0002
@r0,#h'00
n0003

;increment debounce counter


;step counter pointer
;if not last then n0002
;goto c0002
;zero debounce counter
;goto n0003

inc00
n0003:

inc00:

This code goes thru all 8 debounce counters to see if any have reached a count of 16. If
one has reached 16, that bit is set in the debounced changes byte (dbchg) and the
debounce counter is cleared. Otherwise the bit in the debounced changes byte will be
zero and the counter left untouched.
c0002:
n0004:

twoxx:

onexx:

mov
mov
mov
cjne
setb
mov
mov
rrc
mov
inc
djnz
sjmp
clr
sjmp

r0,#dbctr
r3,#h'08
a,@r0
a,#h'10,onexx
c
@r0,#h'00
a,dbchg
a
dbchg,a
r0
r3,n0004
e0000
c
twoxx

;get start of debounce counters


;set to check 8 status inputs
;get debounce counter
;if count not = 16 then goto onexx
;set carry flag
;zero debounce counter
;get debounced changes
;and shift in
;into storage
;step debounce counter pointer
;if not last then n0004
;goto e0000
;clear carry
;goto twoxx

What you have now is a byte (dbchg) representing any changes that have been
debounced. There is more that one way to proceed from here, but here's one way. I am
only showing the code for the first 3 bits (bit 0 thru bit 2) but it's the same for the rest.
e0000:

e0001:

e0002:

endlp:

mov
jnb
ljmp
mov
jnb
ljmp
mov
jnb
ljmp
ljmp

a,dbchg
acc.0,e0001
rout0
a,dbchg
acc.1,e0002
rout1
a,dbchg
acc.2,endlp
rout2
begin

;get debounced changes


;if bit 0=0, goto next input
;otherwise goto routine 0
;get debounced changes
;if bit 1=0, goto next input
;otherwise goto routine 1
;get debounced changes
;if bit 2=0, goto next input
;otherwise goto routine 2
;end of operating loop

This code represents the routine that will be executed as a result of an input change on
input 0. The nop represents all the code that you would have for servicing input 0.
rout0:

nop

;this represents the routine

This code complements the old status bit 0. This makes the old status equal to the current
status, so that when we return to the scan loop, no changes will be detected until bit 0
changes states again.

mov
xrl
mov
ljmp

a,ostat
a,#h'01
ostat,a
e0001

;get old status and complement


;old status bit 0
;and store it
;done, goto next input

This code is identical to rout0 except for this being for bit 1.
rout1:

nop
mov
xrl
mov
ljmp

a,ostat
a,#h'02
ostat,a
e0002

;your code
;complement old status
;bit 1
;and store it
;done, goto next input

This code is identical to rout0 except for this being for bit 2.
rout2:

nop
mov
xrl
mov
ljmp

a,ostat
a,#h'04
ostat,a
endlp

;your code
;complement old status
;bit 2
;and store it
;done, goto end of loop

So what all this has done is to debounce an input and execute some routine to service it.
A change on an input has to be there for 16 CONSECUTIVE scans before it is
recognized as valid. If an input doesn't have a change, it's debounce counter is always
cleared. When a counter has reached 16, it is cleared and a bit is set in the debounced
changes byte to reflect that that bit has changed and is valid.
Next, the routine for that bit is executed and at the end of this routine, the old status for
this bit is complemented, making it the same as the current status. Then the next input is
checked. Now when this bit is scanned and checked against the old status on the next
scan, there won't be a change generated for this bit until it changes states again for 16
CONSECUTIVE scans.
This not only debounces a contact, but increases the noise immunity on any input. This
should eliminate, altogether, any problems with noise on your inputs. I used a count of 16
but you can use any count value up to 255 or down to 1.
The comparable thing for analog inputs is called averaging. There are many formula's for
performing the averaging, but I use a simple one, that probably has an official name, but
it escapes me right now.
What I normally do is to have a memory location for each analog input, containing the
current value used by the operating system. As each input is scanned and converted, the
new value is added to the old value and divided by two, and then stored back in the
memory location. This has the effect of filtering and smoothing the input, at a minimal
software impact. This isn't the most complicated way of filtering, or the most accurate
and responsive, but it is very simple and takes very few clock cycles to execute.

Here is an example of this. This uses port 3 as the a/d value and temp as the current
reading.
filter:

mov a,p0
add a,temp
rra
mov temp,a

;get the conversion value


;add it to the current value
;divide it by two
;store as current

This code reads port 0 for the value. That value is then added to the current value in
memory. This result is divided by two, by shifting right one bit position. This is stored in
memory as the current value. Depending on the impedance of the input, I sometimes also
hang a .1 uf capacitor to ground at the chip input, to add some extra filtering.
At this point I am placing the final software source file for this course, mostly for those
who are wishing to progress at a faster pace. I spent several months debugging this code
and refining it. I only ask that if you use it in whole, or in part, for a commercial product,
that you keep me in mind. In the next lesson we will talk about X-10 and home control
over existing power lines and the interface we will use to connect to the TW523 X-10
Power Line Computer Interface. This interface provides an isolated connection to the
power line, that all but eliminates any transients from getting into the micro. It is opto
isolated in both directions and provides over 1000 volts of isolation.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html
.

X-10 Protocol and Control


NOTE: The X-10 protocol is patented but X-10 gives anyone that purchases a
PL513 or TW523 express permission to transmit X-10 commands. To create your
own hardware interface to the power line and transmit X-10 is expressly forbidden.
To do so is in direct violation of their patent.
This is a reference to be used later to help understand the BEGIN1.ASM code dealing
with X-10, that I will explain when I start the walk thru of the file BEGIN1.ASM in the
next lesson.
X-10 has been around for many years, and is a diverse protocol that has just recently been
expanded. My explanation here doesn't include any description, except in passing, of this
fact. I will be referring to the original X-10 protocol. Also this discussion assumes that
you live in a country that has 60 Hz power frequency (USA). I realize that some of you

will be from countries that have 50 Hz mains power, but I have no experience with this
frequency. I will say that X-10 has modules for 50 Hz power mains.
X-10 allows for up to 256 devices, divided into 16 house codes, each with 16 unit codes.
On most X-10 devices is the ability to select, with a couple of rotary switches, the house
and unit code address. The TW523 is one of the few devices that doesn't have these
selector switches. That is because the TW523 can communicate with any one of the 256
possible devices.
Originally, lighting was a big part, and still is, for the use of X-10. There are also horn
modules, thermostat modules, dry contact input modules, dry contact output modules,
appliance modules, pet feeders, infrared motion sensors, and many more. Each
recognizes and may respond to some or all of the commands available. It depends on the
module.
The X-10 Commands
There are many different X-10 modules available. One is the lighting module. This
module recognizes the ON, OFF, ALL UNITS OFF, ALL UNITS ON, ALL LIGHTS
ON, ALL LIGHTS OFF, DIM, and BRIGHT commands. Some of the newer models may
also recognize and respond to STATUS REQUEST with STATUS=ON or
STATUS=OFF and recognize the PRE-SET DIM command.
The ON and OFF commands are straight forward and either turn off or turn on a device.
The ALL UNITS ON and OFF also do the same thing, except that it is done for all
devices on a particular HOUSE code. The ALL LIGHTS ON and OFF act on all lighting
control modules on a particular HOUSE code. The DIM dims a particular lighting
module and the BRIGHT brightens a lighting module. The PRE-SET DIM sets a
particular lighting module to a DIM level somewhere between OFF and ON, without
having to send repeated DIM's or BRIGHTs to get it to that level.
The HAIL REQUEST is used to determin if there is another "talker" or transmitter within
"listening range" of the TW523. This could be another TW523. If there is another talker,
it will respond with the HAIL ACKNOWLEDGE response.
The X-10 Protocol
To control an X-10 device, the HOUSE code, the UNIT code and the FUNCTION code
must be sent for a complete command. The UNIT code and the FUNCTION code vary in
the most significant bit, called D16 in the X-10 protocol documentation. For a UNIT code
this bit is a zero and for a FUNCTION code this bit is a one. Following is a table listing
all the codes used by X-10.
House Codes
H1 H2 H3 H4

Key Codes
D1 D2 D4 D8 D16

'A'
'B'
'C'
'D'
'E'
'F'
'G'
'H'
'I'
'J'
'K'
'L'
'M'
'N'
'O'
'P'

0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1

1
1
0
0
0
0
1
1
1
1
0
0
0
0
1
1

1
1
1
1
0
0
0
0
1
1
1
1
0
0
0
0

0
0
0
0
1
1
1
1
1
1
1
1
0
0
0
0

'1'
'2'
'3'
'4'
'5'
'6'
'7'
'8'
'9'
'10'
'11'
'12'
'13'
'14'
'15'
'16'

0
1
0
1
0
1
0
1
0
1
0
1
0
1
0
1

1
1
0
0
0
0
1
1
1
1
0
0
0
0
1
1

1
1
1
1
0
0
0
0
1
1
1
1
0
0
0
0

0
0
0
0
1
1
1
1
1
1
1
1
0
0
0
0

0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0

UNITS

'ALL LIGHTS OFF'


'ALL LIGHTS ON'
'ON'
'OFF'
'DIM'
'BRIGHT'
'ALL LIGHTS OFF'
'EXTENDED CODE'
'HAIL REQUEST'
'HAIL ACKNOWLEDGE'
'PRE-SET DIM'
'EXTENDED DATA (ANALOG)'
'STATUS=ON'
'STATUS=OFF'
'STATUS REQUEST'

0
0
0
0
0
0
0
0
1
1
1
1
1
1
1

0
0
0
0
1
1
1
1
0
0
0
1
1
1
1

0
0
1
1
0
0
1
1
0
0
1
0
0
1
1

0
1
0
1
1
1
0
1
0
1
X
0
1
0
1

1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

FUNCTIONS

The X-10 protocol defines transmissions in 11 cycle message segments. Each message
segment contains a START code, a HOUSE code, and a KEY code. The KEY code can
be a UNIT or a FUNCTION, depending on the state of bit D16. The X-10 protocol states
that, except for the START code, all bits are sent in their "True" state, immediately
followed by the "inverted" state. In other words if a HOUSE code 'A' is sent (0110) this
would look like 01 10 10 01. Each of these pairs of bits would take one complete cycle of
power to be sent. The true state is sent on the first half cycle and the inverted state on the
second half cycle.
The exception is the START code, which is always sent out as 1110. Looking at the
above example of HOUSE code 'A', that data is 01101001. Notice that in that data there
is never 3 one's in a row, and never the pattern 1110. This makes the START code unique
in the data stream, and what signals the start of an X-10 message segment. A complete
message segment will take 11 cycles of power to send. In other words the data is sent out
just as it is listed in the above table, from left to right starting with the START followed
by H1, H2, H4, H8, D1, D2, D4, D8, and D16 along with the inverted bits.

Here's an example of what the data stream would look like for HOUSE code 'A' and
UNIT code '1'.
1110011010010110100101

Dividing it up in it's functional groups it would look like this.


1110 01101001 0110100101

The first four bits are the START code, the next 8 are the HOUSE code and the last 10
are the KEY code.
Dividing this up in pairs of bits, representing complete cycles of power, it would look
like this.
START

HOUSE CODE

KEY CODE

11 10

01 10 10 01

01 10 10 01 01

As you see here, the START code takes 2 cycles, the HOUSE code 4 cycles, and the
KEY code 5 cycles. This is the 11 cycle message segment. One complete command
consists of the message segment sent twice, taking 22 cycles of power to send. If the
TW523 was receiving this 22 cycle transmission, data will only be sent out to the
DS5000 during the second message segment. The proprietary chip inside the TW523 is
comparing the first message segment with the second segment as it is received and
presents data to the DS5000 during this second segment. This comparison is made to
validate the X-10 command, and that it wasn't garbled with noise during transmission.
To actually turn 'ON' HOUSE code 'A', UNIT '1', two commands are sent. The first
command selects the UNIT to be controlled and the second command is the action to be
taken, in this case to turn 'ON'. This complete transmission would take 22 cycles for the
first command, followed by 3 cycles of silence, followed by 22 cycles for the second
command, for a total of 47 cycles. There would also have to be at least 3 cycles of silence
following the second command, which would bring the total to 50 complete cycles of
power for one complete transmission, before another could be sent.
The exceptions to the rule of 3 cycles of silence between commands is in the case of
DIM, BRIGHT, EXTENDED CODE, and EXTENDED DATA. These commands are
sent with no gaps or silence between commands. The TW523 can only receive commands
separated by 3 cycles of silence.
The format of the X-10 message follows a strict order. Following is a complete X-10
message. Although I've shown it on two lines, they should be considered as one long line.
START HOUSE
|
|

UNIT
|

START HOUSE
|
|

UNIT
|

SPACE
|
|

START HOUSE
|
|

FUNCTION START HOUSE


|
|

FUNCTION
|

SPACE
|
|

My approach to X-10 transmission is to build the complete message (all 47 cycles worth)
in a buffer in data memory, and then send it out, one bit per each 120 Hz interrupt
iteration. This is one of the reasons I picked 120 Hz for the interval of the interrupt. In
this way, a minimum of CPU time is spent sending the X-10 transmission.
In my design, there is something dealing with X-10 that I omitted. That is the ability to
communicate over 3 phase power systems. I only deal with single phase power, which is
the norm for homes. To deal with 3 phase power, you must send the transmission at three
different times, separated by 120 degrees. In other words, I only send the data at one
time, not three. This minimizes the hardware and software requirements for the DS5000
and still lets you control things in the single phase environment, which covers the
majority of homes.
To explain further, X-10 requires that you modulate the power line within 50 usec of the
"zero crossing" point and that this modulation last for 1 ms. For three phase power, this
would be repeated at the 120 degree mark and the 240 degree mark, which are the zero
crossing points for the other two phases.
The TW523 outputs a square wave, in sync with the power line frequency and within the
50 microsecond window of zero crossing. This is a 60 Hz square wave. In my design I
trigger two single shots, one on the rising edge of this square wave and one on the falling
edge. I OR the outputs of these two single shots together to form the 120 Hz frequency
needed, for software simplicities sake, to generate and receive the X-10 transmission. I
also use another single shot to generate the 1 ms modulation envelope for transmitting X10. This relieves the software from having to time this interval leaving more crunch time
for everything else. I don't expect everyone to understand all this, but I give it here for the
more curious at heart. Just build the schematic as shown and it will work fine.
Also, all inputs and outputs of the TW523 are opto isolated, using 4N35 parts or
equivalents, and therefore safe to connect to a different power system, namely that of the
DS5000. These opto's provide a minimum of 7500 volts of isolation between the power
line and the DS5000.
One last thing, when a 1 is present in the X-10 data stream, this results in a 120 KHz
carrier being applied to the power line. If a zero, no carrier is present. In other words if a
120 KHz carrier is present, that is interpreted as a 1, otherwise it is a zero. To make this
even more confusing, the transmit and receive signal points on the TW523 are
INVERTED. In other words to send a 1 you really send a zero and if you are receiving a
zero, that's really a one. Sounds very confusing, but in reality you handle this by simply
inverting everything you send or receive with the TW523, in software. This way you
handle the data inside the DS5000 buffer as true data and invert it as it leaves or enters
the DS5000.

I used software for this inversion instead of external inverter gates. This is one example
of software being as good or better than hardware for a particular job. I didn't have any
spare inverter gates left and the impact on software is very minimal to perform this
inversion. On the other hand I added a single shot gate to time the 1 ms envelope
requirement for X-10 transmission, which would have used a lot of CPU time to generate
this interval in software. When designing a system, there is a continuous effort to use the
minimum amount of hardware, but at the same time to have enough hardware to simplify
the software, where it matters.
In closing, there are devices on the market that will interfere with X-10 communications.
These include baby minders, intercoms, and phone extenders, that all operate over the
power lines. The interference comes from the fact that these devices are transmitting all,
or part of the time, covering up and scrambling the X-10 signals (120 KHz carrier). If you
experience other problems with X-10, it's usually because there is something noising up
the power line. Defective fluorescent lights, motors, air conditioners, and the like, will
either cause spikes, or outright noise, either of which could cause intermittent or total loss
of communications.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html
.

A Walk Thru of BEGIN2.ASM: Bootup


Initialization
This begins a series of walk-thru's of the code in BEGIN2.ASM. This will bring the
course to a close when this is complete. I will do it in more than one lesson, because I
feel that it would be a very long lesson if I didn't. We will start with the code that leads
up to the operating loop, starting with bootup. You will need a printed copy of
BEGIN2.ASM to follow along in these next lessons, as I will leave out some of the
descriptive text that is in it, to keep these files as small as possible. You will notice, I'm
sure, that the name has changed from BEGIN1.ASM to BEGIN2.ASM. You can get the
updated files here. This rev adds Y2K compliance to the calendar, and adds more
comments to the code. The utility SETTIME.EXE changed also, for the Y2K code
update. SETTIME.EXE allows you to set the system clock/calendar from your PC. Since
we don't have a keyboard, this is the only way you can set the clock/calendar, so far.
When a micro boots up after power is applied, or is reset while running, there is always
code executed that sets up the system for operation. It is usually only executed once each
boot or reset. It's purpose is to configure any hardware registers on board the micro and

configurable hardware external to the micro, initialize any interrupt vectors, and prepare
volatile ram for operation.
Since there is no way to know what will be in all the writable locations, both registers and
ram, after power is applied, you must purposefully set it all up yourself so that by the
time you reach the operating loop, you do know exactly what is in all these locations. It is
imperative that you always start out at the operating loop with everything set up exactly
the same, every time. Without this consistent starting point, diagnosing problems is next
to impossible, and there would be random events that would occur due to differences in
these locations.
As I've stated in an earlier lesson, when power is applied, the micro always starts
executing code at location 0000, at least in this micro it does. What happens after that,
depends on the code. Looking at the source file, the first thing you see is a blurb about the
W command. I put it there to remind me that I spent several days trouble shooting a
problem relating to it.
Next you see a .command directive. The -h0 says don't include form feeds in the listing
file and the -s says don't include the symbol listing in the listing file. The symbol listing
shows all the labels and variables, and the locations where they are defined and
referenced. It is a valuable trouble shooting aid, but it wastes paper, if you're printing out
the listing file regularly. I would suggest that you remove this switch and assemble it
once to see what the symbol list looks like.
Next comes a .set directive that sets a variable, dumpx, equal to 4000h. This is used for
the dumpi subroutine, that dumps the contents of all the registers in the DS5000 into data
memory, so that you can use the DUMPI.EXE PC utility to display this information in a
neatly laid out form. I'll explain the dumpi subroutine later on.
Next comes the .org directive, that says to the assembler to start assembling the
instructions that follow, at location 0000h. Next is the first actual instruction, and the
label reset. This instruction jumps past the interrupt vectors to the label cont1.
Next comes the interrupt vectors, with a .org in front of each, so that they get assembled
in the proper locations. The DS5000 is "hard wired" to use these unique locations, so the
ljmp instructions must be placed accurately, thus the .org in front of each one.
The first vector, at location 0003h is the interrupt 0 vector, used by the 120 Hz interrupt
routine. Each time a 120 Hz interrupt occurs, this ljmp is executed, which takes you to
the start of the interrupt 0 service routine, starting at the label int60.
Next comes a reti instruction at location 000bh, that basically disables the timer 0
interrupt from executing any code. It is available for use by you by placing an ljmp
instruction and a label, pointing to the routine you wish to execute from it, in place of the
reti instruction. The reti stands for "return from interrupt", so you can see that the
only instruction that can ever be executed as a result of a timer 0 interrupt is to return

from the interrupt, which ends it, with no other instructions executed. Whatever the
DS5000 was doing when the interrupt occurred, it returns to doing, by this instruction.
Next comes a couple of instructions associated with the dumpi subroutine, which is
initiated by flipping the BREAK switch. The first instruction, clr ea at location 0013h
says to disable all interrupts. This is done so that no interrupts occur that would corrupt
the contents of the DS5000 while they are being dumped to data memory by the routine
dumpi. The next instruction jumps to the label dumpi, which is the start of the dumpi
routine.
Next is another reti instruction at 001bh that disables the timer 1 interrupt. Since timer 1
is used to generate the RS-232 serial baud clock, there is no code to execute. This is a
feature available in all 8051 micros and we use it here. The timer 1 interrupt will never be
enabled, but just in case it accidentally is, this reti instruction terminates any response to
it by the DS5000.
Next is the vector at 0023h for the RS-232 serial interrupt, which jumps to the label
rs232, the start of the RS-232 interrupt service routine. This routine handles all
communication between the PC and the DS5000. Don't confuse this communication with
the DS5000 loader, which also uses the same path for communication. The loader is
invoked by flipping the LOAD/RUN switch to the LOAD position and doesn't use any
vectors, or user code for that matter. This interrupt vector is used while the DS5000 is in
the RUN mode, communicating with a PC via the serial port.
The next vector is the Power Fail interrupt. I disable this routine with another reti
instruction at 002bh, because I don't use it in this design. The power fail interrupt is used
to execute code that saves critical data or some other code in response to an eminent
power failure. There is circuitry inside the DS5000 that senses that power is falling
toward a point that the DS5000 can't operate. This interrupt is generated before that point
arrives, giving the system a chance to do something.
Next comes two directives. The first, .segment .memory, says to define a new memory
segment, or area, called memory. Up till now, all the instructions and labels were placed
in the code segment, by default. Next comes the directive .memory which switches the
assembler to this new segment, which will represent the 128 bytes of internal ram.
Realize that these are directions for the assembler, not code to be executed. Next is a
.org 00 directive that says to start the following .rs (reserve storage) directives at 00h,
the first location in internal ram.
Next comes what looks like 32 labels, each with a .rs directive followed by a 1. Each
says to reserve 1 byte in internal ram, with the name of the label. The .rs along with the
label, is one of the niceties that go along with using an assembler. Once these .rs
directives are read by the assembler, only the label has to be used in the code. In this way
new variables can be assigned as needed without having to worry about where they
actually are. You refer to them by name, not the location. The assembler keeps up with
that boring task. These first 32 directives assign names to the 4 register banks of the

DS5000. This way data can be stored or retrieved by using direct addressing, using the
label for each instead of the actual direct address in internal ram. These first 32 locations
in internal ram must be reserved, though, so that they can be used as registers. The other
option would have been to change the .org to the next location after the last of the
register bank registers, or .org 32 or .org h'20. Remember the assembler defaults to
decimal unless you specify otherwise.
Next comes some valuable descriptive text that describes the layout of the next 16
locations, which are the bit testable locations of the DS5000. Shown are the location
address, the bit position, the direct bit address and the name of that bit, if it is used. This
helps in getting the .equ directives correct, that follow. If you are viewing this with a
program other than BR.COM or Q.EXE, this will all seem a meaningless jumble of
characters. You need to be using one of these two programs to view this file, or have
printed it on a printer that supports the IBM block graphics character set. The HP II and
III printers call this font the PC-8 font. I have supplied both of these programs to you
previously. I suggest you use BR.COM, because it doesn't allow you to change anything,
just look at it. Q.EXE is the program I use to modify the source file.
Next come the actual equates that name all the bits in these 16 locations that are used.
Equates name the bits by giving the name followed by the direct bit address. There can be
a total of 128 of these bits, 16 locations of 8 bits each. They have direct bit addresses 00h
through 7fh. Once these bits are named, only the names need be used in the code. This
allows you to move these around, without having to change your code. Yet another of the
advantages of using an assembler.
Next comes a .org h'21. This is the second location of bit testable locations. Following
that is the label flag1 and a .rs directive, which gives a name to this location.
This covers the first 48 locations in internal ram. Next come the user variables we will
use in the code. These will vary with each design, but the first 48 should always be used
as they were designed, as register banks and bit locations. If you start running out of
internal ram you could use some of the bit testable locations for user variables, but you
loose their use as bit testable locations when you do that.
Next comes .org h'30, which is the first location after the bit locations. Then follow the
user variables, which start at 30h, using .rs to name them and reserve locations for them.
By the way, the .rs directives and the .equ directives can be placed anywhere in the
.memory section. I keep them separated for the sake of clarity and readability. One of the
changes that prompted the BEGIN2.ASM file is in the label years where I reserve 2
bytes instead of 1 for the year variable. This allows for a 4 digit year, instead of a 2 digit
year, the Y2K problem in it's most basic terms.
Next comes some more valuable descriptive text describing the various pins of the
DS5000 and the DS2250. Shown are the pin numbers, the direct bit addresses, the port
number, and the name that is assigned to that pin. These are grouped by port number.
Following each layout diagram, are the actual equates, naming the pins that are used.

Notice that there is an extra line shown for the pins of port 3. This is because these pins
are different from the other 3 ports in that there is a special function defined for each one.
If that special function isn't needed, in the case of the RD line, for instance, this pin can
be given a name with an equate, and used for whatever. The RD and WR pins would be
used if we had an external ram chip connected to the DS5000. But since we don't, we can
use them for whatever, in this case for the X-10 transmit and receive connections.
This ends the definition of the internal ram, the bit locations and the port pins. Notice that
following the last .rs directive there is a label called stack. This will be the first location
of the stack, which will consume the rest of the internal ram locations. The stack is used
to temporarily store return addresses for subroutines and interrupt routines and sometimes
data. When a call is made to a subroutine, the address following the call instruction is
"pushed" onto the stack, taking up 2 bytes of the stack. When the subroutine ends, the
address is "popped" off of the stack, returning these 2 locations, to be used again. The
stack pointer, called the SP, keeps up with all this pushing and popping. All you need to
do is make sure you've left enough room for the stack to "grow". How much room you
actually need is determined, partly, by how "deep" you nest subroutines.
Nesting refers to calling one subroutine, that calls another, that calls another. You can see
that the "deeper" you go, the more locations you will need for the stack. As you finish
each subroutine, these locations will be released back to the stack. The other thing that
grows the stack is how many registers you push onto the stack at the start of a subroutine.
You almost always have to push two more bytes onto the stack, which are the acc and the
flags. So each subroutine will probably use 4 locations in the stack, minimum. If the
subroutine uses the dptr, the data pointer, which many do, that is another 2 bytes to be
pushed.
The register banks come into play here, to limit the number of things that need to be
pushed onto the stack. There are 4 banks of registers, each with 8 separate registers,
making a total of 32 (the first 32 locations of internal ram). By using these, where you
can, the number of pushes, besides the acc and flags, can be minimized. The acc and the
flags nearly always need to be pushed at the start of a subroutine, since so many
instructions use the acc and affect the flags. When you return from the subroutine, or
interrupt routine, you want the flags, and possibly the acc, to be what they were before
the call or interrupt happened. This allows you to use the acc and flags in the subroutine,
but return with them intact, like you never used them at all. You'll see this more as we go
through the subroutines and interrupt routines later.
The register banks can be switched with a single instruction. I use this feature in the
interrupt routines. The other reason that you need to push the flags is that it contains,
among other things, the register bank that was in use before you switched them in the
routine. So when you pop the flags back, just before you return from the interrupt, or
subroutine, the previous bank will be restored and things will move along, without having
to push 8 registers, saving stack space.

All the equates and reserves defined above, will be explained further as we get into the
code that follows.

Next you will see the .code directive, that tells the assembler that we are now switching
back to the code segment, and all that follows will be assembled, starting at 0030h, into
the program memory. Next is the label rxltb. This is the start of a table that is used by
the RS-232 interrupt routine, to tell this routine how many bytes to expect from the PC,
for various commands, one of which is actually used, the SETTIME command, which
sets the clock/calendar. Listed here are .db directives that have a value, that is the length
of the command, in bytes. Also, in brackets, for your benefit, is the command byte, in this
case an 80h. This will be explained further in the RS-232 interrupt service routine
description.
Next comes another .org followed by the label cont1, the label that was in the very first
instruction we assembled in the beginning of this LONG dissertation. This is where we
jumped to and where we will continue with the initialization code. This jump took us past
the last interrupt vectors and the table above, and is the actual start of the initialization
code.
First the watch dog timer (WDT) is disabled with a timed access to the pcon (Power
Control) register. This is done by first writing an aah and then a 55h to the ta (Timed
Access) register (c7h) and then writing a 00h to pcon. This also sets SMOD=0 and
disables the power fail interrupt. The WDT is a hardware device built into the DS5000
that can be used to monitor the operation of the DS5000 software. It consists of a timer
that must be reset on a regular basis (approx. every 132 ms at 11.0592 MHz) by software,
to keep it from resetting the DS5000. This keeps the DS5000 from getting lost, although
this shouldn't happen if your software is sound. It is explained in detail in the Soft
Microcontroller Data book from Dallas Semiconductor, starting on page 67. I don't use it
in this design, but it should be included in any critical design.
Next we disable all interrupts. This is in case we accidentally got here. A reset or poweron will do the same thing. There is some valuable descriptive text about the ie (Interrupt
Enable) register here. We load it with a 00h, which disables all interrupts
Next we initialize the txx and rxx pins, which are X-10 transmit and receive pins of the
DS5000, respectively. These are set to a 1. For the txx pin, this is now sending a 0 to the
TW523, which does nothing. Remember the TW523 signals are inverted so a 1 is really a
0, which puts silence on the power line. By setting the rxx pin to a 1, this allows this pin
to be an input. In the DS5000 there is no way to say how a pin will be used, whether
input or output. Inside the DS5000 is a very weak pull-up, that can be overcome quite
easily by an outside source. With the pin set to a 1, the receive output of the TW523 can
drive this line low, or leave it high. If there had been a 0 written to it, and the outside
source tried to drive it high, damage could result to the DS5000.

Next comes the initialization of the other port lines. P0 is the tristate data bus that is
connected to the display and the A/D chip. Sometimes it is an output and other times it is
an input. Writing all 1's sets it up to be an input. P1 has the various pins used by the
display and the A/D for controlling who has the tristate bus at any one time. Writing a
10h initializes all these lines to their proper state. Some need to be 1's and some need to
be 0's. More on that later.
Next comes a routine that clears all the internal data memory. This is done to always start
with a known state of the internal ram. This routine is 6 instructions long. The first sets
the starting address for the clear, contained in r0, to location 02h, or the third location.
Were are using the first two locations as part of the routine, so they can't be cleared with
the routine. R1 holds the count of how many locations we are going to clear. This is set to
h'fe or 126d, which is two less than the total of 128. The next instruction puts a zero in
the acc, which will be used to write zeros in all the other locations. Next we write the
contents of the acc (zero) to the address contained in r0 (initially a 02h, the third
location). Notice that there is a label on this line, clrm2. Next r0 is incremented, then r1
is decremented, and a jump made to clrm2 if r1 is not zero. When r1 is zero the program
falls through the djnz instruction.
Next the stack pointer is set to the address of the label stack -1. The -1 is due to the
mechanics of the stack pointer. When you push something onto the stack, the first thing
that happens is that the stack pointer is incremented before the push is done. So, in this
case, the stack pointer will now be pointing to the address of the label stack and the byte
will be stored at that location. When you pop something off of the stack, the byte is read
from the location pointed to by the stack pointer, and then the stack pointer is
decremented. The main thing about the stack pointer and pushing and popping is that
there is enough room in the stack and that there are the same number of pushes and pops
in your code (or calls or interrupts and returns). One of the hardest problems to
troubleshoot is a mismatch in the number of pushes and pops, or the order you did the
pushes verses the order you did the pops.
Next the clock/calendar is set to a time/date of 23:59:00, Feb 28,1998. This is an arbitrary
time/date that could be anything. The thing to notice is that there are 8 bytes that make up
the clock/calendar. These are secnd (not shown here, but cleared by the above routine),
minut, hours, daymo (day of the month), month, and two bytes for years. There is also a
daywk (day of the week) that is also zeroed by the above routine. This means that the
calendar is set to Sun, Feb 28, 1998, since Sun is a day of the week of 0, and Sat is day 6.
SETTIME.EXE sets all 8 bytes from your PC. From that point on, the calendar is
perpetual as long as power is applied, to include leap years.
Next all 16K of the data memory is set to ffh. This routine is 8 instructions that use the
dptr as the data pointer, r2 and r3 together as the byte counter, and the acc holds the ffh to
be written to each location. The dptr is set to 4000h, which is the first location of data
memory and the byte counter is set to 4000h with r2 being the most significant byte and
r3 the least significant byte. Then acc is loaded with ffh and then the first byte is written
with the next instruction. The dptr is then incremented and the byte counter is

decremented, and when both r2 and r3 are zero, the program falls through the last djnz
instruction, otherwise a jump is made to the label nextx, and the next location is written
with ffh.
Next the display buffer is filled with spaces. The display buffer is 128 bytes long, 4 lines
of 32 characters, even though only 20 of the 32 characters per line actually get displayed.
This is done to simplify the software that updates the display during the 120 Hz interrupt
routine. First the dptr is loaded with 4100h, the start of the display buffer. R2, the byte
counter is loaded with 80h (128d). The rest is much like the other two previous fill
routines.
Next the display buffer has a comma for the date, and two colons for the time written in
the right places. This way, when the date/time is written to the display, these three
characters don't have to be sent each time.
Next the 8 location A/D buffer, that starts at loc 4180h in data memory, is zeroed out.
Next flag1 is set to 3fh. This causes the operating loop to update the display buffer with
the date/time, the first time it runs. After that, the display buffer is updated with the
seconds, each time they change, minutes each time they change, and so forth.
Next more of the special function registers within the DS5000 are setup.
First tmod (Timer/Counter Mode Register) is set to 22h which sets both hardware
counter/timers to be 8 bit auto reload timers (Mode 2). Remember timer 1 is used as the
baud rate clock for the serial port. Timer 0 isn't used yet in this design.
Next tcon (Timer Control/External Interrupt Control Register) is set to 05h which clears
the timer overflow interrupts, stops the timers and sets int 0 and int 1 to be negative edge
triggered inputs. Negative edge means that the interrupt is generated by a high to low
transition on the interrupt input pins (int 0 and int 1) of the DS5000.
Next scon (Serial Control Register) is set to 50h. This sets the serial port to mode 1,
which is 10 bits asynchronous (1 start bit, 8 data bits, and 1 stop bit) and uses timer 1
overflow as the baud rate clock. This also enables the receiver and clears both ti (transmit
buffer empty) and ri (receive buffer full) interrupts, in case they might be set.
Next is some valuable descriptive text and a table that describes what to load into TH1 to
get different baud rates for the serial port. Part of the table is for SMOD=0 and SMOD=1.
Since we set SMOD=0 previously in the program, we are interested in the part of the
table with SMOD=0. I use a 11.0592 MHz clock in this design, so that is the column we
want to look at. A value of f4h gives us a baud rate of 2400. So I set TH1 and TL1 both
equal to f4h. We will be operating the serial port at 2400 baud, while in the RUN mode.
This is fast enough for a reasonable speed in transfers between the PC and the DS5000
and leaves plenty of time between serial interrupts to do lots of stuff, including reading
the receive serial buffer before it overruns (the next character comes in before you get

around to reading the previous one). I always start at 2400 baud and get everything
working. You can then speed up the serial port, if you want, to see how fast you can
operate without problems. It will probably operate at 9600 baud in most designs without
problems. But it depends on how much stuff you are trying to do with your design.
Next the ip (Interrupt Priority) register is loaded with a 00h, which makes all interrupts
the same priority, low. This means that while an interrupt is being serviced, no other
interrupts will be honored. If you want one particular interrupt to be able to interrupt
another interrupt service routine (ISR), you can set that interrupt to have a high priority,
by setting that bit to 1. The rule is that no high priority interrupt can interrupt another
high priority ISR and no low priority interrupt can interrupt another low priority ISR. But
a high priority interrupt can interrupt a low priority ISR. If a high and a low happen at the
same time, the high will be honored first. Generally a high priority interrupt will be one
that has to be serviced within a short time of it's occurrence.
In this design, a problem could arise if you are using X-10 control, which has a very strict
timing requirement, and at the same time you are communicating with the PC, with lots
of data. The problem would be if you are transmitting or receiving X-10 data and
receiving serial data from the PC. At 2400 baud, a continuous stream of data would
cause an interrupt every 4.16 ms. The 120Hz interrupt is occurring every 8.33 ms. If a
serial character came in just before the 120 Hz interrupt happened, the 120 Hz would
have to wait for the serial ISR to finish, before it was honored. If the serial ISR lasted 1
ms, which is quite a while, you would have missed the 50 usec window, and if you are
transmitting X-10, the data just got garbled, and that transmission would be ignored by all
X-10 devices.
Since the 120 Hz ISR only lasts about 2 ms, if you experience many missed X-10
messages, you might try setting the 120 Hz interrupt to a high priority, which shouldn't
bother the serial communications as long as you're not trying to run at too high a speed.
You could also add to that, lowering the speed of the serial port to 1200 baud. You will
have to do some timing calculations of your software to determine the best course. But
beware of high speed serial data, coupled with concurrent X-10 communications!
Next ie (Interrupt Enable Register) is loaded with 00h, which disables all interrupts, in
case they weren't already. Either a power on or reset will disable all interrupts, among
other things.
Then timer 1, the baud rate clock is started by setting the bit tr1 in tcon.
Next both external interrupts are enabled by setting bits ex0 and ex1 in ie. This doesn't
really enable anything yet, since the ea bit hasn't been set yet.
Now the first subroutine is called, that initializes the display. (See the table that describes
the HD44780 instructions). This sub (inlcd) goes through a sequence of steps, timed out,
that are required to initialize the HD44780 display driver chip on-board the display. It

uses three other subs waitp, strob, and wclcd. We will look at these later, when I cover
the subs.
Next the number 103 is placed in the display buffer. This is just something I put there to
tell me the rev level of the software. Since you can't look inside the DS5000 and see
what's there, this is the next best thing. You can use whatever number, or none, that you
wish.
Next we enable the serial interrupt, es, though we haven't enabled ea yet, so nothing is
really enabled.
Finally ea is set, which does enable the two external interrupts and the serial interrupt, all
at once. Since we set the bits in flag1, the display will shortly show the data/time, the
number 103, and lots more.
We have just finished the initialization process!!!!! Next we will look at the operating
loop. Hope everyone enjoyed this, ha! ha!
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

The DS5000 Instruction Set Description


As I promised, in an earlier lesson, I am going to go through an in-depth explaination of
ALL the DS5000 instructions. I need to do this before I go any further in my explaination
of BEGIN2.ASM, so you can better understand it.
In these descriptions, several things are used throughout, that I need to explain, to make it
all clearer. Here they are:
Rn - is one of the eight possible registers in the current register bank. Remember, there
are four possible banks of registers, one of which is the currently selected bank. Each
bank has 8 registers (r0-r7) that can be used.
direct - is one of the 256 possible direct addresses in the internal RAM of the DS5000.
Remember that the DS5000 has two distinct sections of memory. One is the on-board
internal RAM, which has 256 addresses, the first 128 being the scratchpad RAM (007fh), and the last 128 being the Special Function Registers (80-ffh). The other is the
external RAM, of which, in our case, has 32,768 addresses (0 - 3fffh). There are a few

instructions that can have two direct addresses. They are referred to as direct1 and
direct2.
rel - is a relative offset from the next instruction's address. This can be +/- 127 from that
address. Remember that the PC is incremented when the current instruction is loaded, so
the offset is relative to the address of the next instruction. This doesn't matter to you, the
programmer, because the assembler calculates this address based on the address of the
label you placed in the instruction, referring to the relative address in question. I tell you
this only for your complete understanding of what happens, and I hope it doesn't confuse
you. Relative addressing is confusing if you are having to calculate the offset yourself,
like I did in the past, before I started using an assembler. The assembler will, however,
give an error if you try to reference an address further than +/- 127 from the next
instruction's address.
Ri - is one of the two registers, in the current bank, that can be used to index (point to) an
internal RAM location. This can be either r0 or r1. Only these two can be used, and is a
hardwired feature of the DS5000.
#data - is immediate 8 bit (byte) data. This means that the instruction contains this data.
In the case of a mov a,#data , a two byte instruction, the first byte is the mov a,
command and the second byte is the data. In this case, that data would be placed in the
accumulator.
#data16 - is immediate 16 bit (2 byte) data. This is used with instructions to load a 16 bit
register, like dptr, with an address. The dptr is the only 16 bit register to use this type
of data.
@ - means "at the address pointed to by". An example would be add a,@r0. This would
add the contents of the accumulator, to the data at the address in internal RAM, pointed to
by r0, and store the result back into the accumulator.
bit - is a single bit, within a bit addressable byte, in the internal RAM. Remember that the
internal scratchpad RAM is divided up functionally into different sections. The first 32
bytes hold the 4 register banks, each with 8 registers. The next 16 bytes are the bit
addressable locations, each location having 8 bits, for a total of 128 bits. These represent
bits 00-7fh. These can be assigned labels with the .equ assembler directive, so that you
don't have to remember their values. The value for a particular bit is determined by which
bit position, in which byte, is being referenced. Bit 0, of the first bit addressable byte, is
bit 00h. Bit 7, of the last bit addressable byte, is bit 7fh. There are also several bit
addressable locations in the Special Function Registers. These occupy bit addresses 80ffh, making a total possible of 256 bit addresses.
/bit (actually bit with a bar over it) - is the complement of a bit. I can't type something
with a bar over it here, but it does exist. It simply means that if the actual state of a
particular bit is 0, this would return a 1 instead. I've never used this feature, but it's there,
none the less.

addr 11 - is an 11 bit, absolute, address. This is a somewhat diffult concept, but here
goes. The actual addressing within the DS5000, with the exception of internal RAM, is
16 bits. This allows for 65,536 unique addresses. Using 11 bit addressing limits the
number to 2048 (2K). To use this type of addressing, the assumption is made that the
effected address resides within the 2K block you are currently in. There are thirty-two 2K
blocks of memory inside the DS5000 address range, looking at it this way. The first is
0000-07ffh, the second is 0800-0fffh, the third is 1000-17ffh, and so forth. I never use
this type of addressing, due to the problem of knowing which 2k block a particular
instruction, or label, is in. Since, using an assembler, you don't know which actual
address any particular instruction or label is at, it makes it risky to use this kind of
addressing. The instruction takes the same amout of time to execute as a long address (16
bit) and the only savings is 1 byte of program memory. I guess if you were pressed for
space, you could use this type of addressing, but I have never been pressed into that
corner, so far.
addr 16 - is a 16 bit, long, address. This can be any address within the address space of
the DS5000. This addressing takes a 3 byte instruction, as opposed to 2 bytes for absolute
addressing.
( ) - means "the contents of". For instance (A) means the "contents of the accumulator".
(( )) - means "the contents of the location pointed to by the contents of". For instance
((Ri)) means the contents of the location pointed to by the contents of Ri.
Lastly, when using an assembler, a label can be assigned to any direct location, bit
location, long address, absolute address, or relative address in the DS5000, so that you
refer to it by the name instead of the actual value. This makes things much easier to keep
up with. The following descriptions don't use this, but remember it's there.
Also, the assembler has some hardcoded names that can be used to refer to different bits
and direct addresses in the Special Function Registers. For instance bit 0 of the
accumulator (direct bit address e0h) is referred to as acc.0 .
There are four flags in the DS5000 that indicate various conditions as the result of an
instruction. These are the C (carry), AC (auxillary carry), OV (overflow), and P (parity)
flags.
C is set by either an overflow or underflow of the accumulator. If you add a number to
the accumulator that results in a number greater than 255 (ffh), a carry will result. If you
subtract a larger number from a smaller number, a borrow will result. If neither of these
happen, the carry flag will be cleared. The carry flag can also be set, cleared, or
complemented by boolean instructions.
AC is set when the previous operation resulted in a carry (addition) or a borrow
(subtraction) from the high order nibble. This is used for BCD arithmetic.

OV is set when when a carry was generated into the high order bit, but not a carry out of
the high order bit, or if there was a carry out of the high order bit, but not a carry into the
high order bit. It is normally used in 2's complement arithmetic. OV is also set if a divide
by zero was executed.
P is set if the modulo-2 sum of the eight bits of the accumulator is 1 (odd parity). It's
cleared if even parity. This is an extravagant way of saying that if you add up the number
of 1's in the accumulator, and they come out odd, P is set. If there is an even number, P is
cleared.
There is one more flag (F0) that can be set by software for whatever purpose. It's really
just another bit to twiddle, but it gets saved when the PSW is pushed.
All these flags reside in the PSW (Program Status Word) register. Here is how they are
laid out.
PSW.0 (PSW bit 0) is P
PSW.1 is unused
PSW.2 is OV
PSW.5 is F0
PSW.6 is AC
PSW.7 is C
PSW.3 and 4 indicate what register bank is currently selected. PSW.3 is the low order bit
and PSW.4 is the high order bit.
00 is bank 0
01 is bank 1
10 is bank 2
11 is bank 3
This is how the register bank is selected. You load the PSW with the value that
corresponds to the register bank you want to select. a 00h selects bank 0, 08h selects bank
1, 10h selects bank 2, and 18h selects bank 3. At power up or after a reset, bank 0 is
always selected, until you select another. The main reason to use bank switching is to
allow for faster ISR's or subroutines and also lessen the need for a larger stack. By using
a register bank to hold most of the variables used by a particular routine, that runs a lot,
extra pushes and pops can be avoided that would have saved and restored these variables
in the stack, also saving precious machine cycles.
I typically use bank 0 for the operating loop, and many of the subroutines. I use another
bank for the RS-232 ISR, another for the 120 Hz ISR, and another for whatever.
The stack is a portion of internal RAM (typically towards the end of RAM) that is used
for temporary storage of addresses and data. When a call or interrupt happens, 2 bytes are
pushed for the return address. Then as many bytes as needed are also pushed, to save

their contents, for the return. This usually includes the accumulator, since it's used by so
many instructions, and the PSW, since it holds the state of which bank is being currently
used and may be holding some flag contents that will be needed upon return from the
routine. This means that, at a minimum, 4 bytes will be pushed for every interrupt or
subroutine routine. If the stack overflows (runs out of internal RAM), your system is
basically toast, and mumbles off to mama every time. So the stack is made as large as
possible.
When the system boots up, the SP register is initialized to an address. This is called the
"top of the stack". From that point on, the stack "grows downward" for each push
(towards the end of RAM). As addresses and data are "popped off" the stack, those bytes
are freed up, to be used again. The problem is that you may be inside a subroutine, when
an interrupt occurs, and that ISR may be interrupted by a higher priority interrupt. Each
of these events caused the stack to grow deeper. As each finishes, the addresses and data
are popped off the stack, and eventually it returns to the top of the stack again. The trick
is make enough room for as deep as the stack might ever get, without running out of
memory. This is usually not a problem, but it must be considered, and enough room
allowed for the stack to grow.
The instructions of the DS5000 are divided into five types. They are Arithmetic
Operation, Logical Operation, Data Transfer, Boolean Variable Manipulation, and
Program Branching. Capitalization is used in these descriptions, though no caps are used
in my actual code. Also I've included a logical explaination to the right of each
instruction. This gives a short visual summary of what the instruction does. Some are too
complicated to describe this way, and I don't.
There are a total of 111 seperate instructions, but many variations. I note any flags that
are affected by the instruction. If no flags are mentioned, then none are affected.

Arithmetic Operation
ADD A, Rn

(A)=(A) + (Rn)

Flags C, AC, OV

Adds the contents of a register to the contents of the accumulator and stores the result
back into the accumulator.

ADD A, direct

(A)=(A) + (direct)

Flags C, AC, OV

Adds the contents of a direct address to the contents of the accumulator and stores the
result back into the accumulator.

ADD A, @Ri

(A)=(A) + ((Ri))

Flags C, AC, OV

Adds the contents of the location pointed to by Ri to the contents of the accumulator and
stores the result back into the accumulator.

ADD A, #data

(A)=(A) + #data

Flags C, AC, OV

Adds the immediate data in the instruction to the contents of the accumulator and stores
the result back into the accumulator.

ADDC A, Rn

(A)=(A) + (C) + (Rn)

Flags C, AC, OV

Adds the contents of the register plus the contents of the carry flag, to the contents of the
accumulator, and stores the result back into the accumulator.

ADDC A, direct

(A)=(A) + (C) + (direct)

Flags C, AC, OV

Adds the contents of the carry flag, plus the contents of the direct location, to the contents
of the accumulator and stores the result back into the accumulator.

ADDC A, @Ri

(A)=(A) + (C) + ((Ri))

Flags C, AC, OV

Adds the contents of the location pointed to by Ri, plus the contents of the carry flag, to
the contents of the accumulator, and stores the result back into the accumulator.

ADDC A, #data

(A)=(A) + (C) + #data

Flags C, AC, OV

Adds the contents of the carry flag, plus the immediate data, to the contents of the
accumulator and stores the result back into the accumulator.

SUBB A, Rn

(A)= (A) - (C) - (Rn)

Flags C, AC, OV

The contents of the carry and the contents of the register are subtracted from the
accumulator and the result is stored back into the accumulator.

SUBB A, direct

(A)=(A) - (C) - (direct)

Flags C, AC, OV

The contents of the carry and the contents of the direct location are subtracted from the
accumulator and the result is stored back into the accumulator.

SUBB A, @Ri

(A)=(A) - (C) - ((Ri))

Flags C, AC, OV

The contents of the carry and the contents of the location pointed to by Ri, are subtracted
from the accumlator and the result is stored back into the accumulator.

SUBB A, #data

(A)=(A) - (C) - #data

Flags C, AC, OV

The contents of the carry and the immediate data are subtracted from the accumulator and
the result is stored back into the accumulator.

INC A

(A)=(A) + 1

Increments the contents of the accumulator by 1.

INC Rn

(Rn)=(Rn) + 1

Increments the contents of the register by 1.

INC direct

(direct)=(direct) + 1

Increments the contents of the direct location by 1.

INC @Ri

((Ri))=((Ri)) + 1

Increments the contents, of the location pointed to by Ri, by 1.

INC DPTR

(DPTR)=(DPTR) + 1

Increments the contents of the dptr register by 1.

DEC A

(A)=(A) -1

Decrements the contents of the accumulator by 1.

DEC Rn

(Rn)=(Rn) - 1

Decrements the contents of the register by 1.

DEC direct

(direct)=(direct) - 1

Decrements the contents of the direct location by 1.

DEC @Ri

((Ri))=((Ri)) - 1

Decrements the contents of the location pointed to by Ri, by 1.

MUL AB

(B15-8), (A7-0)=(A) * (B)

Flags C, OV

Multiplies the contents of the accumulator by the contents of register b and stores the low
order byte back into the accumulator and the high order byte back into register b. This
multiplies two 8 bit numbers with a 16 bit result. If the result is greater than 255, in other
words the b register has something other than zero in it, the OV flag will be set. The C
flag will always be cleared.

DIV AB

Flags C, OV

Divides the accumulator by the b register and places the integer part of the quotent in the
accumulator. The integer remainder is placed in the b register. The C flag is always
cleared. In the event that the b register was originally zero (divide by zero), the OV flag
will be set, indicating a divide by zero error.

DA A

Flags C, AC

This instruction is VERY complicated. It is used to adjust the accumulator after an add,
that involved BCD (binary coded decimal) numbers, so that the accumulator has the
proper BCD result in it. I'm not going to discuss this instruction any further, due to it's
complexity and the use of BCD. You can find a two page description of how it works in
Intel's "MCS 51 Microcontroller Family User's Manual". It is a handy instruction if you
are going to be using BCD numbers, otherwise you will never use it. DA A stands for
"Decimal adjust Accumulator for Addition". It could be handy for interfacing to a BCD
display. It can be done other ways, which I do.

Logical Operation
ANL A, Rn

(A)=(A) AND (Rn)

The contents of the accumulator are ANDED with the contents of the register and the
result is stored back into the accumulator.

ANL A, direct

(A)=(A) AND (direct)

The contents of the accumulator are ANDED with the contents of the direct location and
the result is stored back into the accumulator.

ANL A, @Ri

(A)=(A) AND ((Ri))

The contents of the accumulator are ANDED with the contents of the location pointed to
by Ri, and the result is stored back into the accumulator.

ANL A, #data

(A)=(A) AND #data

The contents of the accumulator are ANDED with the immediate data and the result is
stored back into the accumulator.

ANL direct, A

(direct)=(direct) AND (A)

The contents of the accumulator are ANDED with the direct location and the result is
stored back into the direct location.

ANL direct, #data

(direct)=(direct) AND #data

The contents of the direct location are ANDED with the immediate data and the result is
stored back into the direct location.

ORL A, Rn

(A)=(A) OR (Rn)

The contents of the accumulator are OR'ed with the contents of the register and the result
is stored back into the accumulator.

ORL A, direct

(A)=(A) OR (direct)

The contents of the accumulator are OR'ed with the contents of the direct location and the
result is stored back into the accumulator.

ORL A, @Ri

(A)=(A) OR ((Ri))

The contents of the accumulator are OR'ed with the contents of the location pointed to by
Ri, and the result is stored back into the accumulator.

ORL A, #data

(A)=(A) OR #data

The contents of the accumulator are OR'ed with the immediate data and the result is
stored back into the accumulator.

ORL direct, A

(direct)=(direct) OR (A)

The contents of the accumulator are OR'ed with the direct location and the result is stored
back into the direct location.

ORL direct, #data

(direct)=(direct) OR #data

The contents of the direct location are OR'ed with the immediate data and the result is
stored back into the direct location.

XRL A, Rn

(A)=(A) XOR (Rn)

The contents of the accumulator are XOR'ed with the contents of the register and the
result is stored back into the accumulator.

XRL A, direct

(A)=(A) XOR (direct)

The contents of the accumulator are XOR'ed with the contents of the direct location and
the result is stored back into the accumulator.

XRL A, @Ri

(A)=(A) XOR ((Ri))

The contents of the accumulator are XOR'ed with the contents of the location pointed to
by Ri, and the result is stored back into the accumulator.

XRL A, #data

(A)=(A) XOR #data

The contents of the accumulator are XOR'ed with the immediate data and the result is
stored back into the accumulator.

XRL direct, A

(direct)=(direct) XOR (A)

The contents of the accumulator are XOR'ed with the direct location and the result is
stored back into the direct location.

XRL direct, #data

(direct)=(direct) XOR #data

The contents of the direct location are XOR'ed with the immediate data and the result is
stored back into the direct location.

CLR A

(A)= 0

The accumulator is cleared, or zero'ed out.

CPL A

(A)=(/A)

The contents of the accumulator is complemented. All one's become zero's and all zero's
become one's.

RL A
The contents of the accumulator are rotated left by one bit. Bit 7 goes into bit 0.

RR A
The contents of the accumulator are rotated right by one bit. Bit 0 goes into bit 7.

RLC A

Flags C

The contents of the accumulator are rotated left, through the carry flag. Bit 7 goes into the
carry, and the carry goes into bit 0.

RRC A

Flags C

The contents of the accumulator are rotated right, through the carry flag. The carry goes
into bit 7 and bit 0 goes into the carry.

SWAP A

(A3-0)=(A7-4)

The upper nibble of the accumulator is swapped with the lower nibble. This can also be
looked at as a 4 bit rotate. No flags are affected.

Data Transfer
MOV A, Rn

(A)= (Rn)

The accumulator is loaded with the contents of the register.

MOV A, direct

(A)=(direct)

The accumulator is loaded with the contents of the direct location.

MOV A, @Ri

(A)=((Ri))

The accumulator is loaded with the contents of the location pointed to by Ri.

MOV A, #data

(A)= #data

The accumulator is loaded with the immediate data.

MOV Rn, A

(Rn)=(A)

The register is loaded with the contents of the accumulator.

MOV Rn, direct

(Rn)= (direct)

The register is loaded with the contents of the direct location.

MOV Rn, #data

(Rn)= #data

The register is loaded with the immediate data.

MOV direct, A

(direct)= (A)

The direct location is loaded with the contents of the accumulator.

MOV direct, Rn

(direct)= (Rn)

The direct location is loaded with the contents of the register.

MOV direct1, direct2

(direct1)= (direct2)

The direct1 location is loaded with the contents of the direct2 location.

MOV direct, @Ri

(direct)= ((Ri))

The direct location is loaded with the contents of the location pointed to by Ri.

MOV direct, #data

(direct)= #data

The direct location is loaded with the immediate data.

MOV @Ri, A

((Ri))= (A)

The location pointed to by Ri is loaded with the contents of the accumulator.

MOV @Ri, direct

((Ri))= (direct)

The location pointed to by Ri is loaded with the contents of the direct location.

MOV @Ri, #data

((Ri))= #data

The location pointed to by Ri is loaded with the immediate data.

MOV DPTR, #data16

(DPTR)= #data16

The dptr register is loaded with the 16 bit immediate data.

MOVC A, @A + DPTR

(A)= ((A) + (DPTR))

The accumulator is loaded with the location in program memory pointed to by the
original contents of the accumulator plus the contents of the dptr register. This is a handy
instruction for implementing a lookup table in program memory. Say you created a table
of ascii values that represent the numbers 0 thru 9. The ascii value for 0 would be the first
entry in the table and the ascii value for 9 would be the last entry in the table. By setting
dptr to the start of the table, if the accumulator had a 0 in it, the instruction would return
with the first entry in the table, which is the ascii value for 0, in the accumulator. Walla! I
use this one regularly.

MOVC A, @A + PC

(A)=((A) + (PC))

This instruction acts exactly as the previous one except the PC (program conter) is the
register used. I haven't came up with a good use for this instruction yet. But it is there to
use.

MOVX A, @Ri

(A)= ((Ri))

Loads the accumulator with the contents of the location, in external data memory,
pointed to by Ri. This means that the first 256 locations in external data memory could be
used by this instruction, possibly for frequently used variables or buffers. There are other
options for this instruction if you are using p2 and p0 of the DS5000 for an external
memory address/data bus. You could load p2 with the high order address bits and then
use this instruction to access the 256 locations, or the page of memory pointed to by p2.
This would allow you to create 256 pages of 256 locations, using a 64K RAM chip. I
haven't used this instruction for anything yet, but it seems like it could be useful.

MOVX @Ri, A

((Ri))= (A)

This is just like the previous instruction, except that the external location is loaded with
the contents of the accumulator.

MOVX A, @DPTR

(A)= ((DPTR))

Loads the accumulator with the contents of the location in external data memory pointed
to by dptr. This one works for all the locations in external data memory, and I use it a lot.

MOVX @DPTR, A

((DPTR))= (A)

Loads the location in external data memory pointed to by dptr, with the contents of the
accumulator. Again, I use this one a lot for larger buffers that I don't have space for in

internal data RAM.

PUSH direct

(SP)=(SP) + 1 ; ((SP))=(direct)

Increments the stack pointer (SP) and stores the contents of the direct location into the
location pointed to by sp. This is one of the most used instructions. It, along with the POP
instruction, implement the stack within the DS5000. The stack is used to temporarily
store addresses and data, to allow the use of subroutines, with less memory overhead than
would be possible without it. It also allows for relatively fast temporary storage and
retrieval of information. The stack resides in internal RAM, usually towards the end of
the internal RAM.

POP direct

(direct)=((SP)) ; (SP)=(SP) - 1

Loads the direct location with the contents of the location pointed to by the sp, then
decrememts the sp register.

XCH A, Rn

(A)=(Rn)

The contents of the accumulator and the contents of the register are swapped. This is
handy for intermediate storage of the accumulator contents, or for retrieving the contents
of a register, while also saving the contents of the accumulator.

XCH A, direct

(A)=(direct)

The contents of the accumulator and the contents of a direct location are swapped. Also
handy.

XCH A, @Ri

(A)=((Ri))

The contents of the accumulator and the contents of the location pointed to by Ri are
swapped.

XCHD A, @Ri

(A3-0)=((Ri3-0)

The low order nibble of the accumulator and the low order nibble of the location pointed
to by Ri are swapped. The high order nibbles are not affected.

Boolean Variable Manipulation

CLR C

(C)=0

The carry flag is cleared.

CLR bit

(bit)=0

The direct bit location is cleared.

SETB C

(C)=1

The carry flag is set.

SETB bit

(bit)=1

The direct bit location is set.

CPL C

(C)=(/C)

The carry flag is complemented. If it was a 0, it's now a 1, and vise versa.

CPL bit

(bit)=(/bit)

The direct bit location is complemented.

ANL C, bit

(C)=(C) AND (bit)

The carry flag is AND'ed with the direct bit location and the result is stored back into the
carry.

ANL C, /bit

(C)=(C) AND (/bit)

The carry flag is AND'ed with the complement of the direct bit location and the result is
stored back into the carry.

ORL C, bit

(C)=(C) OR (bit)

The carry is OR'ed with the direct bit location and the result is stored back into the carry.

ORL C, /bit

(C)=(C) OR (/bit)

The carry is OR'ed with the complement of the direct bit location and the result is stored
back into the carry.

MOV C, bit

(C)= (bit)

The carry flag is loaded with the contents of the direct bit location.

MOV bit, C

(bit)= (C)

The direct bit location is loaded with the contents of the carry.

Program Branching
ACALL addr 11
The PC is incremented by 2 and then it is pushed onto the stack, low byte first (2 pushes,
one for each byte) and the immediate 11 bits of the instruction are cancantenated with
(added in, but not ADDED to) the high order 5 bits of the incremented PC, creating the
16 bit address. The sp is incremented by 2 in the process. The incremented PC value and
the address to which the call is being made, must reside in the same 2K block of memory.
I never use this one.

LCALL addr 16
The PC is incremented by 2 and pushed (as the previous instruction) on to the stack, then
the PC is loaded with the immediate 16 bit address in the instruction. This is the one I
always use for a call.

RET
The PC is popped off of the stack, loading it with the address popped off (2 pops, one for
each byte). The sp is decremented by two in the process.

RETI
This does the same thing as the RET, but in addition, it re-enables the interrupts to accept
another interrupt of the same or lower level (priority). This is used to return from an

interrupt service routine. Should an interrupt of a higher level occur, it is serviced, even
though it might be in the middle of servicing a lower level interrupt.
AJMP addr 11
This does the same thing as the ACALL, except no address is pushed onto the stack.

LJMP addr 16

(PC)= addr 16

The PC is loaded with the immediate address in the instruction, which causes the next
instruction to come from the new address. Same as the LCALL except no address is
pushed onto the stack.

SJMP rel
The PC is incremented by 2 and then the relative offset is added to the PC to get the new
address. The next instruction will come from there. This is good for short jumps (+/- 127
locations from the incremented PC address).

JMP @A + DPTR

(PC)=(A) + (DPTR)

The PC is loaded with the address resulting from adding the contents of the accumulator
to the contents of the dptr register. Neither the accumulator nor dptr contents are changed.
This could be used for making a jump table. The accumulator contents would determine
what jump was executed in the table. If the dptr is set to the start of the table and the
accumulator has an even number in it, then the AJMP at that location would be executed.
If the accumulator has multiples of 3 in it, then you could use LJMP's in the table. A
seemingly handy, but tricky, instruction that I've never used, so far.

JZ rel
The PC is incremented by two (to get to the address of the next instruction, as normal). If
the accumulator contents are zero, then the relative offset in the instruction is added to the
incremented PC and the next instruction comes from there. If the accumulator isn't zero,
the next instruction after the JZ is executed.

JNZ rel
This acts just like the JZ, except the relative offset is added if the accumulator isn't zero,
or the next instruction after the JNZ is executed, if it is zero.

JC rel
The PC incremented by 2, and if the carry is set, the relative offset is added to the PC
and the next instruction comes from there. Otherwise the next instruction after the JC is
executed.

JNC rel
Like the JC except the offset is added if the carry is 0, otherwise the next instruction is
executed.

JB bit, rel
As in previous examples the PC is incremented by two. If the direct bit location is 1, the
offset is added, otherwise the next instruction is executed.

JNB bit, rel


Same as JB, except offset is added if the direct bit location is 0, otherwise the next
instruction is executed.

JBC bit, rel


Same as JB, except that if the bit is 1, it is cleared and the offset added. Otherwise the
next instruction is executed.

CJNE A,direct, rel

Flags C

Compares the accumulator with the direct location and, if they're not equal, adds the
offset to the PC. Otherwise the next instruction is executed. If A is less than direct, then C
is set. Otherwise it's cleared.

CJNE A, #data, rel

Flags C

Compares the accumulator with the immediate data and, if they're not equal, adds the
offset. Otherwise the next instruction is executed. If A is less than #data, then C is set.
Otherwise it's cleared.

CJNE Rn, #data, rel

Flags C

Compares the register with the immediate data and, if they're not equal, adds the offset.
Otherwise the next instruction is executed. If Rn is less than #data, then C is set.
Otherwise it's cleared.

CJNE @Ri, #data, rel

Flags C

Compares the location pointed to by Ri, with the immediate data and, if they're not equal,
adds the offset. Otherwise, the next instruction is executed. If @Ri is less than #data, then
C is set. Otherwise it's cleared.

DJNZ Rn, rel


Decrements the register and, if not zero, adds the offset to the PC. Otherwise, the next
instruction is executed. This is used for looping.

DJNZ direct, rel


Decrements the contents of the direct location and, if not zero, adds the offset. Otherwise,
the next instruction is executed. Used for looping.

NOP
This instruction does ablolutely nothing, except waste instruction cycles. That's it's sole
purpose. Sometimes it's necessary to wait a short time after executing an instruction,
before executing another. I use it for I/O accesses mostly. I output the select lines and
then wait a small time to let the chips setup, then read or write. It uses up 1 instruction
cycle.

The information presented here came from three sources, my experience, the Dallas
Semiconductor Soft Microcontroller Data Book, and the Intel MCS 51 Microcontroller
Family User's Manual.

My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

The Operating Loop and beyond


As I stated in an earlier lesson, there are two distinct pieces of software running all the
time in a typical micro. One is the operating loop and the other is the interrupt routine. In
this system there is an operating loop and the associated routines, and several interrupt
routines that I will explain in a later lesson. First I will explain the operating loop.
Starting at begin is the operating loop. It's called a loop because it goes through a
sequence of instructions and, at the end, jumps back to here to start another iteration.
Basically I'm checking the state of a number of bits, one at a time, and jumping to a
routine if the bit is set.
The DS5000 doesn't have an instruction to test a bit and do a long jump to a location. I
would have used that instruction if it existed. There is an instruction to test a bit and do a
short jump if it is not set. There is also an instruction to do a long jump. I combined these
two instructions to make the instruction that I needed. I'll use the first two to explain how
it works
begin:
cktxc:

jnb
ljmp
jnb

rxpp,cktxc
rscmd
txpp,opl01

It works this way. With the jnb instruction at begin, I test the bit rxpp and if it is not set,
I jump to cktxc,skipping the long jump instruction. So if the bit is set I don't jump
around the next instruction and, instead, take the long jump to rscmd, the start of the
routine that services that bit. That makes the test bit and long jump instruction. I need the
long jump so that I can place the service routine anywhere in available memory. If I were
stuck with the short jump I would be limited to having all my software reside in the first
256 bytes of memory, an impossible task at best.
The operating loop consists of several pairs of these two instructions, each one with an
associated routine that gets executed if the bit is set. At the label endlp, I jump back to
begin, closing the loop. If you want to add more routines into the operating loop, you
would insert your instructions just before this label.
The idea is to have several bits used as flags to signal various events that occur, that need
some action taken in the operating loop. These bits get set by various interrupt routines.
The operating loop scans them all the time and takes the appriate action when needed. I
will explain each routine after I've finished with the operating loop code.
Starting at begin, the first bit tested is rxpp. It is set by the rs232 interrupt service routine
(ISR) when a command has been completely received on the serial port. It is reset by the
rscmd routine, as it finishes. The next time through the operating loop, rxpp won't be set

and the ljmp to rscmd will be skipped. This way the rscmd routine is executed once for
each time the rxpp bit is set. Also when the rscmd routine is finished, it jumps to cktxc,
so that the next bit in line is now tested. You can see that, whether rxpp was set or not, it
ends up at cktxc, to test the next bit in line. All the other bits work this same way.
The next bit tested is txpp. This bit signals that the rs232 ISR has finished transmitting
all its data out the rs232 port. The tscmd routine is executed if this bit is set, and when
finished, resets txpp and jumps to opl01.
The next 6 bits tested are used for the clock/calendar. These 6 bits are set as needed, by
the 120 Hz interrupt routine, which contains the realtime clock/calendar code. They are
used to perform tasks based on the passage of time. I haven't set up much that use these,
but these 6 routines would be where you tie in any code to do things based on the time of
day/date.
The first bit is sec1. This bit indicates that the second has changed. The sec01 routine is
executed if this bit is set. Next, min1 if set, indicates the minute has changed and the
min01 routine is executed. Next, hr1 if set, indicates the hour has changed, which causes
the hrs01 routine to be executed. Next, day1 if set, indicates the day has changed, and
the day01 routine is executed. Next, mo1 if set, indicates the month has changed, and the
mon01 code is executed. Next, yr1 if set, indicates that the year has changed, and the
yrs01 code is executed.
The next bit tested is ad07. This indicates that all 8 a/d inputs have been scanned,
converted, and stored. The adcpt routine is executed if ad07 is set. The next two bits,
kbdt and x10t are not used yet, but they're included in case they're needed in the future.
The first is set if a key is pressed and the second is set when the x10 transmitter finishes
transmitting. But there is no keyboard yet, and I don't care when the x10 transmitter is
finished, yet.
The next bit, rxcp indicates that an x10 command has been received, and the rxcp1
routine will be executed if rxcp is set. This is used to monitor the x10 system and display
the command that was received.
The next bit, t0ex, was to be a general timeout timer, but it isn't used yet. The next bit,
rsex, if set, indicates that a timeout has occurred on the rs232 receiver, and the rstt1
routine is executed. If the rs232 has started receiving data, and all the data expected isn't
received, this bit is set. This routine re-initializes the rs232 receiver.
The last bit tested in the operating loop, rxt, indicates that an x10 command was
received from the rs232 port. Although this isn't used yet, it could be used where you
send a command from the PC to be sent out the x10 transmitter. In other words, you
could turn on a light from your PC through the DS5000 and the serial port. If rxt is set,
the rstt1 routine is executed, which sends the x10 command out the x10 port.

With the label endlp, a jump back to begin ends the operating loop itself. Now comes
the individual routines that are part of the operating loop.
RS-232 Receive Command Processor (rscmd)
The routine rscmd is the rs-232 receive command processor. It's purpose is to validate the
command received by the rs-232 ISR. First a pointer (dptr) is set to the start of the rs232
receive buffer, in external data memory. Then the checksum byte (r3) is cleared. Next r2
is loaded with the number of bytes received by the ISR. Then all the bytes are processed
and a checksum calculated. Next the calculated checksum is compared to the received
checksum and a jump is made to rxerr if they don't match.
Next I write a # to the display buffer, to indicate to me that the RS-232 command is good.
If there was an error, I write a * to the display. These are here only for testing purposes,
and arn't necessary for operation.
Next I check the command to see if it's valid. At the present, I only have one command,
Time Sync. It is command 80h. This command is sent by SETTIME.EXE, the time
setting utility I've included with the course. It's currently the only way to set time in the
system. I started to implement another (81h), but stopped. It would, and may still, have
been X10 Receive Command, for turning on/off things from your PC. I may develope
this further and post the new code in the furture.
Notice at the label rsnx2, there is nothing. This is where you would implement new
commands to be received from your PC. Also, right after this label is a sjmp to rsex1.
This is where the routine starts it's exit. There I zero the receive RS-232 buffer pointer (to
be ready for another command to be received), then clear the rxpp flag (that got us into
this routine via the operating loop), and goto opl01, to check the next flag in the
operating loop.
Time Sync (rstck)
The first thing that's done is to stop the 120 hz interrupt, to prevent it from currupting the
time as it is set by this routine. Only one routine at a time can be updating the clock.
Normally it's the 120 Hz ISR that does this.
Then I get the most significant 2 digits of the years from the RS-232 receive buffer and
store them in the first byte of years. Then I get the last 2 digits from the buffer and store
them in the second byte of years. This continues thru the months, day of the month, week,
hours, minutes, and seconds. Lastly I zero the "ticks" (so that the next 120 hz interrupt
will happen on time), set flags to cause the operating loop to actually update the time/date
to the display, and then start up the 120 hz interrupt again. This brings us to the label
rsex1, where we exit the routine, as explained previously.
Next is a label tscmd. This is where the Transmit RS-232 Processor would be, but it isn't
used. Then comes the label x10cm. This is where I started to implement an X10 command

processor, but quit. There are a few instructions there but the routine is incomplete at this
time.
ADCPT
This routine is ran as a result of flag ad07 being set and the operating loop checking it.
This routine converts the first 5 analog points to ascii and writes them to the display
buffer. Notice that I don't actually write them to the display chip, just to the buffer. The
only routine that actually writes to the display chip after bootup is the 120 hz ISR. During
the bootup process, the display is initialized, but after that, only the 120 hz ISR writes to
the display.
In this routine I get the first point's data (the first temperature sensor) from the A/D
buffer, convert it to ascii with the sub b2das, then write it to the display buffer and then
write a degree symbol (dfh) to the right of the reading. Next I get the photocell reading
from the A/D buffer and convert and scale it using v2das and writa a "V" (for volts) to
the right of the reading. Scaling lets you assign a value to be used to indicate what a
single bit change is worth. With the photocell, and nearly all the other voltage inputs, this
value ends up being .02 volts. This value is derived by dividing the full scale voltage (5.1
volts), by the number of bit counts (255 for 8 bits), which gives the .02 volt per bit value.
Look at v2das for more info on this.
The next point converted is the 0-5V voltmeter. This is done exactly as the photocell was,
since it is also a 5.1 volt full scale reading. Next the 25.5 volt voltmeter is converted.
Here a .1 volt per bit scale factor was used, since I divide the reading by 5 with a resistor
network (I need to update the schematic to show these resistors). So with a 25.5 volt input
on the resistors, it ends up being 5.1 volts at the A/D input. You can never exceed this
voltage level or you will damage the A/D converter chip. See the description of the
ADC0808 in lesson 15 for more info on the A/D chip. So 25.5 divided by 255 gives the
.1 volt per bit scale factor.
The last point (outside temperature) is processed just like the first point was, since they
are both temperature readings.
The Temperature readings show up as XXX. The voltage readings show up on the
display as X.XX V for the two 5.1 volt readings and XX.X V for the 25.5 volt voltmeter.
The decimal point (2eh) is also written in this routine, for each voltage reading, in the
appropriate position.
The last thing done in this routine is to clear the ad07 flag (that brought us here from the
operating loop) and go to the next bit in line in the operating loop.
SEC01
This routine is ran as a result of flag sec1 being set and the operating loop checking it.
This routine converts the seconds to ascii and writes them to the display buffer. First the

seconds count is retrieved from secnd and converted to ascii by b2das. Then the ascii is
written to the display buffer. Lastly, the sec1 flag is cleared and the next flag is checked
by the operating loop.
There is more code following the ljmp opl02 instruction. By rem'ing out this ljmp, this
code gets executed. This was a test of running something based on time. If the seconds is
zero the X10 C3 address is turned off, and if seconds is 30, the C3 address is turned on.
This caused a light in my house to turn on and off every minute. I got tired of it
happening so I disabled it. All you have to do is put a ; in front of the ljmp opl02
instruction, to rem it out, and this code will run. It ends by going back to operating loop
to check the next flag.
This code stores the house code, unit code and function code into the appropriate
variables and then calls build, which constructs the X10 message in the X10 transmit
buffer. It then sets up the X10 transmitter and enables it to run on the next 120 hz
interrupt, by setting xtrt, just before finishing. See lesson 16 for more on X10 protocol.
MIN01
It runs as a result of min1 being set and the operating loop checking it.This routine
converts the minutes to ascii and writes them to the display buffer. It works just like
sec01, only for minutes.
HRS01
It runs as a result of hr1 being set and the operating loop checking it. This routine
converts the hours to ascii and writes them to the display buffer. It works just like sec01,
only for hours.
DAY01
It runs as a result of day1 being set and the operating loop checking it. This routine
converts the day of the week and the day of the month to ascii and writes them to the
display buffer. It works just like sec01, only for day of the week and day of the month.
The sub getdy converts the day of the week to the ascii Sun, Mon, Tue, etc. for display
purposes.
MON01
It runs as a result of mo1 being set and the operating loop checking it. This routine
converts the month to ascii and writes it to the display buffer. It works just like sec01,
only for months. The sub getmo converts the month to ascii Jan, Feb, Mar, etc. for
displaying.
YRS01

It runs as a result of yr1 being set and the operating loop checking it. This routine
converts the year to ascii and writes it to the display buffer. It works just like sec01, only
for the year.
All the clock calendar bits are set by the 120 hz ISR, in response to the value
changing. For instance, if the second changes from 29 to 30, or any other change,
then the sec01 flag is set by the 120 hz ISR. Then when the operating loop scans that
bit, the appropriate routine is executed to update the display buffer, so that the new
time is displayed.

RSTT1
It runs as a result of rsex being set and the operating loop checking it. The flag rsex gets
set because the RS-232 routine started receiving a command, but didn't finish within the
time restraints, and a timeout occured. First the rstt bit is set telling the operating loop
that a timeout occured. Then the rsex bit is cleared, which had indicated that the timer
had expired. Then the strs bit is cleared, stopping the timeout checking software in the
120 hz ISR. This code prevents the RS-232 receiver from being left running, or never
finishing, due to some communication problem between the PC and the DS5000.
RXCP1
This runs as a result of rxcp being set and the operating loop checking it. Rxcp was set
due to the X10 receiver receiving a complete X10 message segment (not necessarily a
complete message). First rsfs is checked to see if this if the second message segment. If
it is, then the message is processed and the message converted to ascii and written to the
display buffer. Then the rxcp bit is cleared and the routine ends by going back to the
operating loop to check the next bit.
If it isn't the second segment, then the first house code, and unit code are stored in their
locations and the rsfs bit is set, so that the upon receiving the next segment, it will be
processed as above.
The sub getrx is used to convert the received X10 message segement into the house code
and unit code. The sub gxhas converts the house code into ascii. The sub gxdas converts
the data code (Unit or Function) into ascii.

MHXTT
This is some more incomplete code that was going to be used to process X10 commands
sent from the PC. I may finish it later.

Following are all the subroutines used by the operating loop routines. These are not in the
order they show in the code listing, but are shown here for continuity and clarity in
understanding the operating loop code. As always, the listing of BEGIN2.ASM will be
needed for understanding completely the operation of these routines. I'm not describing
each instruction in them but rather the outcome and operation, conceptually.

B2DAS
This subroutine converts a binary byte into 3 decimal digits, and then each digit into
ascii. It enters with the accumulator holding the binary byte to be converted.
Remembering the binary lesson, the decimal equivalent of ffh is 255, or 3 digits. First the
binary byte is divided by 100 to find the hundreds digit. Then the remainder is divided by
10, to get the ten's digit. The remainder of that division is the one's digit. Then a table
lookup conversion is made for each digit, which looks up the ascii value for that digit.
The routine returns with the hundreds digit ascii in r5, the tens in r6 and the ones in r7.
The lookup table used is called ascii (original huh?)

V2DAS
This subroutine takes an 8 bit a/d reading and multiplies it by a scale factor, and then
converts the result into decimal, then ascii. There is a very good description in the
program, at the beginning of this subroutine, that describes what happens. I suggest you
read it. The routine enters with the reading in the accumulator and the scale factor in
register b. It exits with the ascii thousands in r4, the hundreds in r5, the tens in r6, and the
ones in r7. The decimal point is assumed to be between r5 and r6.

BUILD
This routine constructs the X10 transmit message from the house, unit, and function
codes. This is a cryptic routine, but it works very well. When it exits, the X-10 transmit
buffer has the complete X-10 message ready for transmission. This allows the 120 hz ISR
to transmit it one bit at a time, with each iteration of the 120 hz ISR, with very little
processor overhead. It uses the subroutine shift1 to crunch the data. It also uses the
variables thcod (house code), tucod (unit code), and tfcod (function code). All you
have to do to use it is to load these variables and call build.

SHIFT1
This subroutine is used by build to construct the X-10 message.

GETDY

This sub takes the day of the week byte (0 - 6) and returns with a 3 character ascii string
for the day. (Sun, Mon, Tue, etc.) It enters with the byte in the accumulator. First the byte
is shifted left 2 bits (multiplied by 4) so that each day will be 4 characters in the lookup
table. There is a space on the end of each that isn't used, but this makes for a faster
routine, since it is easy to multiply by 4, but not 3. The ascii lookup table used is called
dofwk.

GETMO
This sub takes the month byte (1-12) and converts it into a 3 character string for the
month (Jan, Feb, Mar, etc.). It enters with the month in the accumulator. First the month
is decremented by 1, to make the number 0-11, for the lookup process. Then it is
multiplied by 4 to get a 4 character entry in the table. Each entry is padded with a space,
that is ignored. The routine returns with the first character in r5, the second in r6 and the
third in r7. It uses the table called ascmo.

GETRX
This routine gets the house code and the data code from the X-10 receive buffer. This is a
pretty cryptic routine, that took me a while to formulate. The whole idea with this routine,
as with the build routine, it to transmit and receive X-10 messages, using the 120 hz
ISR, with very little processor overhead. Take my word that it works. It returns with the
house code in r5 and the data code in r6. It also uses the subroutine shft2.

SHFT2
This subroutine is used by getrx.

GXHAS
This routine converts the binary house code into ascii. It enters with the accumulator
holding the binary house code and exits with the accumulator holding the ascii house
code. It uses the lookup table hscod.

GXDAS
This routine converts the binary function code into a 5 character string. It enters with the
accumulator holding the binary code and exits with r3 holding the first character, r4 the
second, r5 the third, r6 the fourth, and r7 the fifth.

This ends the explaination of the operating loop and all its routines and subroutines. I
tried my best to explain each well enough that you can look at the program and
understand how it works. Some of the routines like getrx and build are very cryptic
routines and took me a while to get working and debugged. I'm sorry I couldn't explain
these in more detail, but it would have been very confusing. You can study the program
and probably understand them better.
Next will come the interrupt routines.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

The Interrupt Service Routines


The first routine we will look at is the RS-232 ISR. You definitely need the software
listing to follow this explaination.
RS232
This routine handles all the communications over the serial port of the DS5000, both
receive and transmit. First the PSW, ACC and the DPTR are pushed onto the stack. Then
the current register bank is switched to bank 2. Next a test is made to determine if this
interrupt is for transmit or receive.
In the DS5000 there are two flags set by the serial port hardware. One is the ti and the
other is the ri. The ti, when set, says that the serial transmit buffer is empty, and can be
loaded with another byte for transmission. The ri, when set, says that the serial receive
buffer is full, and needs to be emptied, so that it can receive another byte.
Receive
The ti is tested to see if this is a transmit interrupt. If it is, a jump is made to sertx to
continue processing the transmit interrupt. If not, then execution continues with the next
instruction. If it wasn't a transmit, then it must be a receive. So the next thing that is done
is to clear the ri bit. If this bit isn't cleared, no more data will be received. For each byte
received this bit must be cleared, so that the next byte received can set it again, causing
another interrupt to happen. As a programmer, we only have to clear this bit, since the
serial port hardware sets it, with each byte received, causing the interrupt.
The first byte received

Next a test is made (strs) to determine if this is the first byte received. If it isn't, then a
jump is made to nxrs1 to continue receiving. If this is the first byte, several things have
to be set up. The next three instructions write a @ in the display buffer, for test purposes,
so that I could see that the DS5000 was receiving. I've never taken out this code, but you
can.
All rs232 receiving and transmitting is done through buffers located in the external data
memory. A buffer usually needs a couple of things to function. One is a pointer, that
determins where the character is to be stored in the buffer and the other is a counter to
keep up with how many characters are in the buffer. Since this buffer is in external data
memory, we will use the dptr to point to it. But it is easier and more memory efficient, to
save an offset to this pointer with 1 byte, than to save the 16 bit pointer itself. The only
limitataion to using an offset, is that a maximum of 256 bytes can be pointed to this way.
The idea is to load dptr with the start of the buffer and then add the offset to it to get the
actual location in the buffer to store the character.
So that's the way I did it. Rsrpt is the pointer offset, so we set it to 1 so that the next
interrupt will store that character in the second location in the buffer (the first location is
at offset 0). Next we get the byte from the serial port hardware receive buffer, sbuf. Then
the dptr is loaded with the starting address of the receiver buffer, 40d0h, and that byte is
stored there.
Next the received byte is tested to see if bit 7 is set. All commands must have this bit set
to be valid. I set it up this way on purpose to add a little security. If this bit isn't set, a
jump is made to rser2. Here a "^" character is written to the display buffer to let me see
that an invalid command was received and then a jump to ext03 is made to exit the ISR.
If bit 7 is set, then it is cleared and the byte temporarily saved in r7 (it is still in the
accumulator also). Next the dptr is loaded with the start of a table (rxltb), that has the
number of bytes expected for each command. This table is at location 0030h. By the use
of this table, commands with different lengths can be processed with the same ISR. The
accumulator is added to the dptr, to get to the entry for this command. If the command
was an 80h (time sync), the accumulator has a 00h in it now, since we cleared bit 7, so
that the dptr has a 0030h in it, pointing to the first entry in the table.
Then the entry from the table is loaded into the accumulator, and then stored in rstol. A
comparison will be made with rstol, as each byte is received, to see if the entire
command has been received. This lets the ISR "know" when it is done with receiving the
command.
Note: I've just discovered an error in my code here, as I read it to do this lesson. I
saved the command in r7 and then added the accumulator to dptr, to get to the
correct entry in the table. Then I loaded the accumulator from r7, to get the
command again, and then used the instruction movc a,@a+dptr to get the byte.
Problem is, this instruction adds the accumulator to dptr again! I can eliminate the
instruction before, and the six instructions following the mov dptr,#h'0030, that did

the addition. They're not needed! Not sure what I was thinking of here. It just so
happens that the only command I have implemented so far, is 80h (time sync), so
that in both additions I was adding zero, which still allows it to work. If I had
implemented another command, say 81h (maybe X-10 command, or something), it
would not work. I will post another program, BEGIN3.ASM, to correct this.
Next I set the variable rstot to 20h. This variable is used by the 120 hz ISR to time the
rs232 command receive. If this timer expires, a flag is set (rsex) for the operating loop.
More on this in the 120 hz ISR. Then I set strs, which activates the timer in the 120 hz
ISR and signals to this ISR, the next time it runs, that the first byte has already been
received. Lastly a jump is made to ext03, to exit the ISR.
All the other received bytes
If this wasn't the first byte received, then we continue at the label nxrs1. Here we load
the accumulator with the rsrpt, load dptr with the start of the receive buffer (40d0h) and
add the two together to get the place in the buffer to store this byte. The byte is read from
the serial receive buffer (sbuf) and stored in the rs232 receive buffer. Then rsrpt is
incremented then checked against tstol, to see if this is the last byte to receive. If is
isn't, a jump to ext03 exits the ISR.
If it is the last byte, rxpp is set for the operating loop, to signal that the rs232 receive is
complete. Then strs is cleared to stop the timeout timer (rstot). Next a "$" is written to
the display buffer so that I can see that the rs232 receive is complete, for test purposes.
Then a jump to ext03 to exit the ISR.

Transmit
If this interrupt was a transmit interrupt we continue at the label sertx. Then a test is
made to see if this interrupt was as a result of the last byte being sent out. R2 holds the
byte counter. If it is isn't zero, then a jump is made to tx2pc.
Last byte transmited
If this interrupt is due to the last byte having been transmitted, then ti bit is cleared, and
txpp is set, to tell the operating system that the rs232 transmit is complete. This brings us
to the label ext03, where many of the jumps above have been to. First, the registers that
were used by this ISR, are now popped off the stack.
Notice that one of them is the PSW. When we entered this ISR, we pushed all these
registers and then changed the register bank to bank 1 (instead of the normal bank 0).
When the PSW is popped here, the original bank is now selected again. Plus all the flags,
the accumulator contents, and the dptr contents are restored to the state they were in when
the interrupt happened that brought us into this ISR.

All other transmitted bytes


If this wasn't the last byte transmitted, then we are at the label tx2pc. Here the ti bit is
cleared, the byte counter r2 is decremented by 1, the byte is sent to the serial port buffer,
from the location pointed to by r0, and r0 in incremented by 1, to point to the next
location in the rs232 transmit buffer. Then a jump to ext03 exits the ISR. The two
registers, r0 and r2, must be set up before starting the transmitter. Also, to fire off the
transmitter, ti is set by software, which causes the rs232 transmit interrupt to happen. It
then continues until it is done (r2=0).
In this system, I haven't used the rs232 transmit feature so far. If you noticed the
memmap.txt file that I furnished with the course, it shows the rs232 transmit buffer
(address 40c0h) to be in external data memory, as are all the other buffers. This was done
to save space in the internal RAM. But notice in the code I just described, that I used r0
to point to the data I was transmitting. This obviously wouldn't work. This is yet another
descrepency in my code. But since I've never used that feature, it hasn't been a problem.
No where in my code do I ever set the ti bit.
I wrote this transmit code early on in the design of this DS5000 software. It would work
if the buffer was in internal RAM. A few small changes will allow the code to work with
the buffer in external data memory. We must use the dptr instead of r0 to point to the
transmit buffer, as was done in the receiver. Also, a variable rstpt is setup in the internal
RAM, but hasen't been used here. It is the transmit buffer offset pointer, just like rsrpt
was for the receiver. I will make these changes in BEGIN3.ASM, so that it matches
memmap.txt. Those are the only changes that need to be made to make this part work.

120 Hz Interrupt Service Routine (INT60)


The next interrupt routine we are going to look at is the 120 hz ISR. You may have
experienced some confusion with the name, since, in some places I call it the 60 hz and
some places I call it the 120 hz ISR. If you never use the X-10 hardware and software
(what a shame), but still want everything else to work, you would connect the opto
coupler U2, pin 5 to the DS5000 pin 12 (INT0). But this would result in 60 hz instead of
the 120 hz that comes from the X-10 hardware interface shown on the schematic. Hence
the use of both names. As we go through this software, I will note the places that would
need to be changed to work with 60 hz, or 50 hz for that matter (for you non-Americans
out there). Also, this routine runs continuously, after bootup, and is never disabled
by software.
X-10 transmitter
The first thing that is done is to save all the registers that will be used by this routine, just
like in the rs232 software. Only here we switch to register bank 1. Next a test is made to
see if we are currently transmitting X-10 data. This has to be the first thing we process,

because the X-10 data must be sent within 50 usec of when the interrupt happened. This
is a hardware constraint of the X-10 protocol.
If you notice on the schematic, there is a block with four connections (1-4), labeled
TW523. These are the connections to the X-10 TW523 tranceiver interface. It plugs into
a regular power outlet and has a standard telephone jack connector (RJ-11 type), that you
plug a telephone cord into, and connect the other end to the X-10 hardware on the
schematic. If you hold a RJ-11 plug, with the cable pointing down and the locking tab
pointing away from you (flat side pointing to you), the pins are numbered from left to
right 1 through 4. Pin 1 has a 60 hz square wave signal coming from the hardware inside
the TW523. This square wave has been designed to happen in sync with the 60 hz power
cycles to give the designer a clock to process the X-10 data with.
But the X-10 data stream that you send and receive, happens with every half cycle of the
power, or 120 hz. This is exactly why I feed the 60 hz square wave into both halves of
U7, a dual single shot. The top one fires on the negative half cycle, and the bottom one
fires on the positive half cycle. The outputs of both are OR'ed with two transistors (Q2
and Q3), to produce 120 hz pulses, that are fed to the DS5000, pin 12 (INT0). The width
of these pulses is set by the resistor capacitor combinations of R14,C6 and R15,C7. This
pulse width is 15 usec, approximately. So you have a 120 hz rate of 15 usec pulses going
to the DS5000. Each pulse causes an interrupt to the DS5000, hence the name, 120 hz
ISR.
The other parts, R10 - R11 - D4 - D5, and R12 - R13 - D6 - D7, are to clamp the voltage
levels from the TW523 so that they never exceed Vcc and Gnd. This protects the
hardware on the schematic. The circuit composed of R7 - R6 - Q1 - R8 - D2 - D3 - R9
conditions and clamps the transmit data going to the TW523. Pin 2 connects to system
ground, pin 3 is receive data from the TW523, and pin 4 is the transmit data to the
TW523.
Also you'll notice that the transmit data coming from the DS5000, pin 16, goes to half of
U1, another single shot. This single shot pulse width is set by R19,C8 to be
approximately 1 msec. The specification for the X-10 data is that it is a 1 msec pulse,
happening within 50 usec of the zero crossing of each half cycle of the 60 hz power line.
Also, if there is a pulse, that represents a '1'. No pulse represents a '0'. One last thing,
which cause me no end of confusion. The data levels to and from the TW523 are
INVERTED. No where in the specs that came with the TW523, did it mention this.
Well I started out to explain how the 120 hz ISR works, and I've just spent 4 paragraphs
explaining the X-10 hardware on the schematic. This was necessary to understand why
it's 120 hz and also to help you understand why all those parts were on the schematic.
You may not understand what I've said here about the X-10 protocol and hardware, but
all the parts on the schematic must be there for the X-10 to work. If you don't use the X10, then you don't need any of them.

That's not exactly true, however. If you do what I described previously, about not using
X-10 and, instead, connecting U2 to the DS5000, you will need R10, so that you have a
0-5 volt level at the DS5000 input, pin 12.
Back to the software. We had just tested to see if we were transmitting X-10 data. If xtrt
isn't set, we goto the label ttpro. If it is set (we are transmitting X-10), then we continue
with the next instruction. First C is cleared, then the accumulator is loaded from cxtbt.
This variable holds the byte of X-10 data that is currently being transmitted. Next we
rotate the data left one bit, to shift the next bit to be transmitted, into the carry, and then
save the data back into cxtbt.
Then the carry is tested, to see if the data to be sent is a 1 or a 0. If it's a zero, we don't
have to do anything (remember a 0 is no pulse, and a 1 is a pulse), so we jump to x10s0.
But if the data is a 1, then txx is cleared, and then set. If you look at the .equ's in the
program, txx is equated to pin 16 of the DS5000. This is the X-10 transmit data going to
U1B. When we cleared txx, this caused U1B to output a 1 ms pulse to the TW523,
sending a 1. Setting txx returned it to it's original state, which did nothing to U1B. The
pin we connected to on U1B, pin 9, is a negative edge triggered input. This means that
U1B triggers (outputs a pulse) on the negative transition of this pin. So when we cleared
txx, we made a negative transition on this pin, causing U1B to output a 1 msec pulse to
the TW523.
This took several paragraphs to explain, but from the time the interrupt happened, to the
time we cleared txx, 50 usec have not passed by yet, so everybody's happy. This is also
why the X-10 transmitter code had to be the first thing we processed. 50 usec isn't very
long a time (50 millionths of one second!). Hopefully you also see why all this hardware
is on the schematic for the X-10 communications. I've seen other implementations that
use a timer inside the DS5000 to time the 1 ms pulse, instead of having a single shot
(U1B) to do it. But this would have taken more cpu time to do it that way. U1 and all the
parts associated with it only cost a couple of dollars, but it made the software much
faster, and much easier to do. It left more time to do all the other stuff that is done in this
routine.
Again, back to the code at the label x10s0. Here we decrement cbits. This variable
holds the count, of how many bits are left in cxtbt to transmit. If this count isn't 0, we
jump to ttpro. If it is zero, then we continue with the next instruction. Next we load
cbits with an 8 and increment cxtpt. Cxtpt is the offset pointer for the X-10 transmit
buffer, just like we had for the rs232 receiver. Then we load the accumulator with this
count and check to see if it is 12h (18). If it isn't we jump to x10s1. If it is, we are
through transmitting X-10 data. We then clear xtrt, to stop executing the X-10
transmitter code, and set x10t, which tells the operating loop that we're done transmitting
X-10 data. Then we jump to ttpro, to continue with the 120 hz ISR.
If this isn't the last byte of X-10 data to be transmitted, we continue with the next
instruction at label x10s1. Here we load the dptr with the starting address of the X-10
transmit buffer (40b0h) and add the accumulator (which has still has the value of cxtpt

in it), to dptr to get to the correct position in the buffer, then get the next byte of transmit
data from the buffer and store it in cxtbt.
That completes the X-10 transmit code. Just to clarify things, the subroutine build,
constructed this data that we have been sending. It consists of 18 bytes of data, that start
at the address 40a0h in external memory (see memmap.txt). We have been transmitting it,
one bit at a time, with each iteration of the 120 hz ISR, until it was completly sent. Cool
huh?
Timeout Timers (TTPRO)
We continue at label ttpro. This is where the code for the timeout timers is located. First
stto is tested to see if the general purpose timeout timer (timr0) is active (being used).
If it's not, we jump to stex1. If it is active we continue with the next instruction. Then
timr0 is decremented by 1. Next we test to see if it has reached zero. If it hasn't we jump
to stex1. If it has reached zero, we set t0ex, which tells the operating loop that the
general purpose timeout timer has expired. I haven't used this timer for anything yet. It's
just there in case I needed one. To use this timer, you would have loaded a time into
timr0, and set stto to start it. Since this ISR runs once each 120th of a second, each
count in the timer is worth about 8.33 msec. So by loading it with 00h (256 counts), you
could time a maximum of about 2.13 seconds. If you don't use the X-10 interface, and
,therefore, arn't using 120 hz, but instead 60 hz, the maximum time would double, to 4.26
seconds, and each count would be worth about 16.66 msec. The same for 50 hz, only the
times are different. The maximum would be 5.12 seconds, and each count would be
worth 20 msec.
Continuing at the label stex1. Here is the rs232 timeout timer that was mentioned in the
rs232 description. This works exactly like the timer above, with the same times. First
strs is tested to see if the timer is active. This would have been set by the rs232
software. If it isn't, a jump is made to stex2. If it is, rstot is decremented by 1 then
tested to see if it's zero. If it isn't, a jump is made to stex2. If it has expired, rsex is set,
to tell the operating loop that the timer has expired, and strs is cleared, to stop the timer
from being processed. Otherwise this works just like the one before it (timr0), and all the
timings are the same. Note that in the rs232 routine we loaded this timer with a 20h (32).
This would mean that the timer would expire in about 267 msec. Using 1200 baud for the
serial port (the slowest rate you'll probably ever use), this would allow enough time for
32 bytes to be received before the timer expired. The time sync command is only 10
bytes long. So if this timer expires during a time sync, there is definitely a problem.
A/D Processing (STEX2)
Continuing at the label stex2, we start the code for processing the A/D readings. Here
I'm going to have to describe the A/D hardware, and the connections from it to the
DS5000, so you'll understand the code.

First adec is a pin (EOC, pin 7) from the A/D chip (ADC0808) that gets set by the a/d
chip, when a conversion is complete. It is connected to pin 25 of the DS5000, and an
.equ is made to it that names it adec. Next pin2 of the a/d (OE) and pin 28 of the cpu
(adoe) are connected. This line (when set, or high) enables the output drivers of the a/d so
that data is available at p0 on the cpu. Lastly pin24 of the cpu (adst) is connected to pin
6 (START) and pin 22(ALE) of the a/d chip. When this line goes high, the low order 3
bits of p0 are strobed into the a/d chip (A0-A2), to select the point that is going to be
converted next. When this line goes low, the conversion is started. A short time
afterwards, the EOC (adec) line goes low, indicating to the cpu that the a/d chip is busy
converting. When the conversion is complete, this line will go high.
Again, at label stex2, adec is tested to see if the conversion is complete. If it isn't, a
jump to rtce1 is made. If it is done, the accumulator is loaded with curad. This variable
holds the address of the point that was just converted, or in other words, which input was
just converted (0-7). It functions as an offset pointer into the A/D data buffer in external
memory. Next the dptr is loaded with the start of the A/D buffer (4180h), and the
accumulator is added to it to get to the correct location in the buffer. Next the previous
conversion of this point is loaded into the accumulator, and saved in r1.
Next p0 is loaded with ffh. The reason this is done is to set p0 to the input mode, so the
data from the a/d chip can be read. If any of these bits were zero's, and we tried to read
the a/d data, any bit position where these zero's were, would always read zero, even if the
data from the a/d had a 1 in it. This is the problem of a tri-state bus, that I spoke of in an
earlier lesson. Setting all of these to a one, allows any zero's on the input pins to pull that
line down.
Next, adoe is set, enabling the output drivers on the a/d chip to present the 8 bit data,
representing the converted input, to the cpu. Then the data is read in to the accumulator,
through p0. Then adoe is cleared, disabling the output drivers on the a/d. Then the current
reading is added to the previous reading, and divided by two. This does a quick average,
or filtering of the analog input, that takes very little cpu crunch time. Then the new
reading is stored in the a/d buffer.
Next curad is incremented, and then AND'ed with 07h. This has the effect of making
curad a 3 bit counter. It will never have a number in it greater than 7. This fits the bill for
selecting one of eight analog inputs to convert. Then this number is loaded into p0 and
adst is set. This loads the new address into the a/d chip, for the next conversion. Then
adst is cleared, starting the conversion. Then p0 has all 1's written to it, to leave it in the
tri-state mode.
Then the accumulator is loaded again with curad and a test made to see if all 8 points
have been converted. If not, a jump to rtce1 is made. If yes, then ad07 is set, telling the
operating loop that all 8 points have been converted. That ends the a/d processing. You
can see that only one point was processed during this iteration. It takes 8 interrupts to
convert all 8 points. If you divide 8 into 120, you see that any single point is being

converted 15 times a second.

Real Time Clock/Calendar


Next comes the real time clock/calendar code. Starting at label rtce1, ticks is
incremented. Ticks is similar to the two timeout timers, only it's always enabled, and
gets incremented once for each iteration of the 120 hz ISR. Then it is checked to see if it
has reached 120. This is the main place that needs to be changed to another number,
if you don't use 120 hz. If it hasn't reached 120, a jump is made to extx2. If it has
reached 120, that means 1 second has passed.
The rest of this clock code only runs once a second. Next r0 is loaded with the starting
address of the time buffer (this one IS in internal RAM!). Next sec1 is set, telling the
operating loop that the seconds have changed. Realize here, that the operating loop is not
running right now, the 120 hz ISR is. The operating loop won't know about anything
that's happened so far, until this ISR completely finishes.
Next the seconds are incremented and then checked to see if it has reached 60. If it hasn't
a jump is made to extx2. If it has reached 60, it is set to zero. It has just rolled over, just
like your watch does when it goes 1 second past 59. Next min1 is set, telling the
operating loop that the minutes have changed. R0 is incremented, to point to the minutes,
then the minutes are checked to see if it has reached 60. If it hasn't, a jump to extx2 is
made. This process continues through the hours and day of the week, checking each for
rollover, and setting the appropriate bit telling the operating loop that it has changed.
Finally we get to the day of the month, at the label curdy. Since each month doesn't have
the same number of days, and there are leap years to consider, we can't just check for
rollover, with a fixed number, like all the previous parts of the clock. At curdy, r0 is
incremented to point to the day of the month, and the day of the month is incremented.
Then it is saved in r11.
If you notice the program listing, towards the very beginning, you see .rs's that are to
"reserve storage" in the internal RAM, for variables. Also notice that each one is named
with a label. Then find r11, and you'll see that this refers to bank 1, register 1. Since we
are currently using bank 1, this has the effect of storing the day of the month in r1. But
there isn't an instruction mov r1,@r0. There is, however, an instruction mov r11,@r0,
which does the same thing. This is a good illustration of why it's good to give the
registers a name.
Next we load the dptr with the starting address of the day rollover table, tbl01. This has
12 entries, one for each month, that indicates the rollover for each month. Then we load
the accumulator with month, the variable that holds the current month, and retrieve the
correct entry from the table, and save it in r2. Next a test is made to see if the month is
February. If it isn't a jump is made to cntmo.

If it is February, then the accumulator is loaded with years, and a test is made to see if it's
leap year. If isn't leap year, a jump is made to cntmo. If it is leap year, the value from the
table is incremented. That brings us to cntmo, where we load the accumulator with the
rollover value and test to see if it has rolled over. If it hasn't, a jump is made to extx2.
If it has, the day of the month is set to 1. Then mo1 is set, telling the operating loop that
the month has changed. Next the month in incremented and a test made to see if it has
rolled over. If not, a jump is made to extx2. If it has then the month is set to 1, then yr1
is set to indicate that the year has changed. Next the low 2 digits of the year are tested to
see if they have rolled over. If not, then a jump is made to extx2. If they have then they
are set to 0 and the high 2 digits are incremented. That ends the real time clock code.
Display Update (See the table that describes the HD44780 instructions).
Next the display is updated. All the other times where we dealt with the display, we were
writing to the display buffer. Now we are about to write one line of the display buffer to
the display chip.
The display buffer is arranged as 4 sections of 32 bytes, each section is a line of the
display. The buffer starts at 4100h in external memory. Line 1 is the first section in the
buffer and line 4 is the last section. The first character on line 1 (left most postition) is at
4100h. The last character on line 1 is at 4113h (20th character position). That leaves 12
bytes in that section that arn't used for anything. It is the same for each line in the display
buffer. Line 2 starts at 4120h, line 3 at 4140h, and line 4 at 4160h.
Only one line of the display is written per iteration of the ISR. This is because there isn't
enough time to write all 4 lines between occurances of the 120 hz interrupt. Plus, only
writing one line per iteration, leaves a lot more time for everything else to crunch. This
means that the display is updated completely, 30 times a second, which is plenty fast
enough.
Starting at the label extx2, the accumulator is loaded with curln. This variable is a 2 bit
counter (0-3), that holds the number of the line in the display, that will be written to now,
and also doubles as the offset pointer into the table displ, and the offset pointer into the
display buffer. This table has 4 entries.
The display itself has it's own internal addressing scheme. The first line starts at address
80h, the second line starts at c0h, the third at 94h, and the fourth at d4h. You'll notice the
discrepency in the numbers. This is because there is really only TWO lines in the display,
each 40 characters long, but they wrap around to make 4 lines. The first line starts at 80h
and wraps around to the third line. The second line wraps around to the forth line. This is
the reason for the table displ. It looks up the correct internal display address for the line
that is to be written to. This way we can look at the display as having 4 lines instead of
two.

Next the entry in the table displ is loaded into the accumulator, and saved in r7. Next the
high byte of the dptr is loaded with 41h. Then the accumulator is again loaded with
curln, and multiplied by 32, to get the offset into the display buffer for this line. Then
the lower 5 bits are zero'd, just in case they arn't already. This is then loaded into the low
byte of the dptr. We now have the starting address in the display buffer, in dptr, and the
starting internal display address in r7.
Next the accumulator is loaded from r7 and the internal address is written to the display
with wclcd. This positions the display cursor at the first character position on the line.
Then r2 is loaded with a 5, and 5 characters are written to the display, using wdlcd. Then
the high byte of dptr is saved in r0 (r10) and the low byte is saved in r1 (r11). At this
point we suspend writing to the display with 15 characters yet to go.
X-10 Receiver Code
As difficult as it may seem, only about 1/2 msec (1/2000th of a second) has passed by
since the interrupt happed that started this journey down obscurity lane. Remember that I
said that one of the requirements of the X-10 protocol, was that the pulse that represented
a '1' must be there for 1 msec. We are now approximately in the middle of this pulse,
which is the best time to sample whether it's there or not. So here is where we start the X10 receiver code.
First, rxx is loaded into the carry. Rxx is equated to pin 17 on the cpu, and is the X-10
receive input. Then C is complemented because the data coming from the TW523 is
inverted. Then it is shifted into rxbyt. Rxbyt is a variable holding the last 8 samples of
the X-10 receive data. Then rxst is tested to see if we have already started receiving an
X-10 message. If we have, a jump is made to rcvxt.
If we haven't, then we test rxbyt to see if an X-10 start has just been received. In the X10 protocol, there are never 3 consecutive 1's transmitted, except at the start of an X-10
message. A start is three 1's followed by a zero, or an "e" hex. If a start hasn't just been
received, then a jump is made to extx3.
If it is a valid start, then rxst is set, indicating that we have just started receiving an X-10
message. Then the upper 4 bits of rxbyt are zero'd. Then rxbit is set to 4, indicating that
there are 4 bits left to receive, to complete the first byte of receive data. Then rxptr, the
offset pointer, is zero'd, to point to the first location of the X-10 receive buffer. Then a
jump is made to extx3.
If we had already been receiving an X-10 message, we start a label rcvxt. Here rxbit is
decremented and checked to see if we have received a complete byte of X-10 data. If we
havent', a jump is made to extx3. If we have received a complete byte (rxbit=0), then
rxbit is loaded with an 8, to receive 8 more bits. Then dptr is loaded with the start of the
X-10 receive buffer and rxptr is added to get to the current location in the X-10 buffer to
store the byte.

Then rxbyt is stored in the X-10 receiver buffer and rxptr is incremented. Rxptr is then
tested to see if we have received 3 bytes of X-10 data yet. If we haven't, then a jump is
made to extx3. If we have, then rxcp is set, telling the operating loop that an X-10
message has been received. Then rxst is cleared and rxptr and rxbyt are zero'd to
prepair for receiving the next X-10 message, when ever it comes. That completes the X10 receive code.
Finishing up with the display
Starting at the label extx3, we finish writing the last 15 bytes to the display. Then curln
is incremented, to point to the next display line. By AND'ing it with a 03h, we make it a 2
bit counter. Finally all the registers we used in this ISR are restored to their original state,
and a reti instruction is executed to exit the ISR, and return to what ever we were doing
when the interrupt happened.
In conclusion
That ends the 120 hz ISR. If you use a 16 character display, instead of the 20 I used, then
just before the label xfer9, where r2 is loaded with a 15, you would load it with an 11
instead. This ISR uses up about 2 msec.
One last note. My design goal was to do all the dirty work, dealing with the I/O, in this
routine. That leaves you, the designer, with only having to deal with the various buffers.
To get an analog reading, you read it from the A/D buffer. To put something in the
display, you write it into the display buffer.
Subroutines used by the 120 hz ISR
WCLCD
This routine writes a command to the display chip. Upon entry, the accumulator holds the
command. First the accumulator is saved in r7. Then lcrs is cleared. Lcrs is equated to
pin 21 of the DS5000 and connects to pin 4 (RS) of the display. When this line is low, the
command mode is selected. When high, the data mode is selected. Then lcrw is set. Lcrw
is equated to pin 26 of the DS5000 and connects to pin 5 (R/W) of the display. When this
line is high, it's a read, when it's low, it's a write. So basically, we've setup to do a
command read, which is really a status read.
Before we can do anything to the display, it must be ready, or idle. Starting at the label
wc001, lcde is set. Lcde is equated to pin 27 of the DS5000 and is connected to pin 6 (E)
of the display. This line is the enable line, that actually does the action we've setup. So
right now, the display is outputting it's status on the data bus (p0). Bit 7 is the only bit
that is used and if it is 1, the display is busy. So we load the accumulator from p0 and
then clear lcde.Then we test bit 7 to see if the display is ready. If not, we jump to wc001
and do it again.

Eventually bit 7 will be a zero, indicating the display is ready for action. Then the
accumulator is loaded from r7, lcdr is cleared (to write), and the command is presented
to p0. Then lcde is set and then cleared, which strobes the command into the display.
Then p0 is tri-stated and the routine exited. (See the table that describes the HD44780
instructions).

WDLCD
This routine writes data to the display. It enters with the accumulator holding the data.
First the data is saved in r7. Then lcrs is cleared (status) and lcrw is set (read). At the
label wd001, we do the same thing we did at label wc001, and wait until the display is
ready. The the accumulator is loaded with the data and lcrs is set (data), and lcrw is
cleared (write). Then the data is loaded into p0, and lcde is set then cleared, which stobes
the data into the display. Then p0 is tri-stated, and the routine exits. (See the table that
describes the HD44780 instructions).

Well this was rather long, but we are almost at the end of the course. There is one last
lesson that wraps up the course.
My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html

The Break Routine


The last ISR we will look at is the Break routine. This one is a bit different from the other
two. It never returns, like the others do. It is ran as a result of the BREAK/RUN switch
being thrown to the BREAK position. It uses INT1, the other interrupt input to the
DS5000.
It's purpose is to store the entire contents of the DS5000 registers into external data
memory. The reason this is done, is that when the DS5000 is placed in the Load mode,
many of the internal RAM locations are used for the communications to the PC, and are
destroyed before you get a chance to read them. By storing them in external memory,
every last bit of information can be looked at.
There is a strict sequence needed to get everything out of the DS5000, without loosing
anything. It stores all the data starting at location 4000h in external memory. The map of

what is where is shown in the listing. The first 128 locations hold the contents of the
internal RAM. The rest holds the various Special Function Registers that can be read out.
There are 18 of these, plus 2 that show the address of the next instruction that would have
been executed if the break switch hadn't been thrown. So the total is 148 bytes of data
saved in external memory( up through location 4093h).
This is a very usefull routine for troubleshooting problems in the code. The idea is to
throw the switch at the time you want to know the state of the DS5000. Then put the
LOAD/RUN switch to the LOAD position and run the DUMPI.EXE utility to display the
information on the PC.
Starting at the label dumpi, tst is set. Tst is equated to pin 22 of the DS5000, and
connects to the test led, so that it lights when this routine is entered. I did this just so I
could see that the break had been acknowledged by the DS5000. You can leave this in or
take it out to suit your needs.
Next the dptr is pushed onto the stack, so it can be used by this routine to more easily
store the rest of the data. Then the dptr is set to the address of dumpx + 80h. If you notice,
at the start of the program listing, dumpx is set 4000h with a .set assembler directive. So
we have just set dptr to 4080h, the first location after where all the internal RAM contents
are to be stored.
Next the accumulator is stored, so it can be used by this routine. I won't say it after this,
but the dptr points to the location where the data will be stored, and gets incremented
after each store. Also the accumulator holds the data to be stored at the location pointed
to by dptr. The accumulator first gets loaded with the data, and then it's stored by dptr.
This sequence gets executed repeatedly, untill all the data is saved in external memory.
Next the accumulator is popped, which retrieves the high byte of the dptr that was pushed
previously, and then is stored. Then the low byte of the dptr is retrieved, by popping the
accumulator again, then stored. We have now saved off the contents of the registers we
are using to perform the dump.
Next the PSW is stored, then the SP. Next the address of the next instruction that would
have been executed had the break not happened, that was pushed as a result of the
interrupt, is stored. This is done by popping the accumulator again, which gets the high
byte of the address. Then the low byte is popped into the accumulator and stored. This
ends the tricky stuff, that needed to be done first.
The rest of the stores are pretty straightforward, with the register being loaded into the
accumulator, and then stored at the location pointed to by dptr. Lastly the dptr is loaded
with 4000h and the internal RAM locations are stored. I notice a problem with this
routine. I need to load the PSW with a 00h, to make sure that we are using bank 0. I'll
change that for BEGIN3.ASM. Otherwise, if we were in one of the ISR's, the r0 and r1
that is used wouldn't end up in the proper locatons.

The first to go are r0 and r1, which are then used for the rest of the locations. When all
have been dumped, the program goes into a closed loop, and waits there forever. Now the
LOAD/RUN can be placed in the LOAD position, and DUMPI.EXE can be ran on the
PC to see the data. You can also run DUMPM.EXE to view any external memory. See
the docs in kenutils.zip for more info on this.
You'll notice, towards the end of the listing, a sort of template (msg01) and some text that
was used by a routine called stmsg. This was to output a startup message in the display.
The routine is still in the code, and you can see how the display is arranged. Following
that are various lookup tables and at the end is some X-10 information.
THIS IS THE END
That ends this course. I'm sorry I took so long, but at least it's done now. I Hope that I
gave you the itch to go further, since what I've given you here is definitly not the last
word on micros. It's just a feeble beginning. I may update this course from time to time,
to fix problems that I find. I will try to indicate it by changing dates on the various
lessons. By the time you read this I will have updated BEGIN3.ASM to reflect the things
that I noticed were wrong in BEGIN2.ASM. However begin2.asm is functional and will
run perfectly, as written. I will also update the schematic to show the parts connected to
the analog inputs of the A/D converter.
Good luck on your pursuits, and God Bless You.
Kenneth Richardson

My email address is here and I would welcome your questions or comments as you
proceed through this course. Depending on the volume of mail I get, it may take a couple
of days or so for me to get back to you, but be assured, I will get back to you. I really do
want you to understand the information I'm presenting, and not being a professional
teacher, I might not always explain things to the satisfaction of everyone, but with your
patience, I'll do my best. My home page is http://www.hkrmicro.com/personal/index.html
.