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 b,#100      ;set to divide by 100
          div ab          ;divide acc by 100
          mov r3,a        ;move the hundreds digit into r3
          mov a,b         ;move remainder into acc
          mov b,#10       ;set to divide by 10
          div ab          ;divide acc by 10
          mov r4,a        ;move the tens digit into r4
          mov r5,b        ;move the ones digit into r5
          ret             ;done, exit the routine

To this we add up front:

          .org h'0000     ;start assembly at 0000h

start:    mov a,#h'ff     ;load a FFh(255) into acc
          acall bin2dec   ;convert to decimal
          mov a,#h'64     ;load a 64h(100) into acc
          acall bin2dec   ;convert to decimal
          sjmp start      ;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 a,r3      ;get the hundreds digit
         add a,#h'30   ;add 30h to it
         mov r3,a      ;move it back into r3
         mov a,r4      ;get the tens digit
         add a,#h'30   ;add 30h to it
         mov r4,a      ;move it back into r4
         mov a,r5      ;get the ones digit
         add a,#h'30   ;add 30h to it
         mov r5,a      ;move it back into r5
         ret           ;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 home page is http://www.hkrmicrop.com/personal/index.html .

On to lesson 9.