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 led,c start ;p1 bit 0 is now called switch ;p0 bit 0 is now called led ;get the state of the switch and put in ;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 r0 @r0,#2 r0,#buffer a,@r0 r0 a,@r0 ;reserve two locations for the data ;set r0 to the start of the buffer ;put a 2 into the first location of the ;increment r0 to point to the second byte ;put a 2 into the second location of the ;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 buffer inc r0 mov @r0,#2 buffer mov mov inc add 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 ;increment r0 to point to the second byte ;put a 2 into the second location of the ;set r0 to the start of the buffer ;put a 2 into the first location of the

.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:


.org h'0000 start: mov a,#h'ff acall bin2dec mov a,#h'64 acall bin2dec sjmp start ;start assembly at 0000h ;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 cont:

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

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 1 1 1 1 1 Part# T702-ND HS121-ND W005G-ND P5234-ND NJM7805FA-ND Description 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 Price 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 X X X X X X X X X X X X 3rd Band 10 100 1000 10,000 100,000 1,000,000 10,000,000 100,000,000 1,000,000,000 10,000,000,000 .01 .1 10% 5% 4th Band

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
setb clr setb mov clr jbz lcrw lcrs lcde a,p0 lcde acc.7,over1

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 Clear display Return home Function set and 5 X 7 font Entry mode set display shift Display ON/OFF no blinking cursor Cursor / Display Set cursor position (20 characters) line line line " 94 " D4 " This is the start of the bottom display This is the start of the third display HEX 01 02 38 Description Clears the display and homes the cursor. Homes the cursor Sets the data bus to 8 bits, 4 line display,

06

Sets the cursor direction to right with no

0C

turns on the display, turns off the cusor,

10 80 C0

Cusor move, no display shift This is the start of the top display line This is the start of the second display

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 adst p0,#h'ff ;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 ;clear Start/ALE to a zero (this starts the ;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.
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

n0002:

jnc inc00 n0003: inc inc djnz sjmp mov sjmp

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

;if bit is a 0 (no change) then goto ;increment debounce counter ;step counter pointer ;if not last then n0002 ;goto c0002 ;zero debounce counter ;goto 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: 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

twoxx:

onexx:

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: 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

e0001:

e0002:

endlp:

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 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1

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

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

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

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

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'

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 11 10 HOUSE CODE 01 10 10 01 KEY CODE 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 The carry flag is cleared.

(C)=0

CLR bit The direct bit location is cleared.

(bit)=0

SETB C The carry flag is set.

(C)=1

SETB bit The direct bit location is set.

(bit)=1

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 .

You might also like