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 feed 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 X-10, 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 X-10 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 X-10 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 X-10 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 home page is http://www.hkrmicrop.com/personal/index.html .

On to lesson 21.