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:  jnb    rxpp,cktxc
        ljmp   rscmd
cktxc:  jnb    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.


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.


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.


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.


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.


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.


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.


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.


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.


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.


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.


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?)


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.


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.


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


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.


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.


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.


This subroutine is used by getrx.


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.


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 home page is .

On to lesson 20.