The DOORS OS

Even though the DOORS OS is an operating system designed to run on the HC11, there's nothing stopping you from porting the code to your processor of choice.


December 02, 2008
URL:http://www.drdobbs.com/embedded-systems/the-doors-os/embedded-systems/the-doors-os/212201487

Walter is a graduate student of mathematics at the University at Albany. He can be reached at [email protected] or found attending the monthly Connecticut Robotics Society meetings.


DOORS OS is an embedded operating system designed to run on the HC11 8-bit microcontroller. While DOORS OS requires at least 2KB of program memory and 512 bytes of storage memory (for the program's stack, variables, and so on), the more memory you have, the better off you are. I'm currently using the DOORS OS with 32KB of external RAM, which provides plenty of room for programs to expand into.

The source code, SDK, and related files for the DOORS OS are available online from Dr. Dobb's (see www.ddj.com/code/) and at www.geocities.com/waltsrobots/ doors.html. In addition to the SDK, you might also want to download the SmallC++ compiler for the HC11. The DOORS OS SDK contains several source files that let you use this compiler to create programs for the OS; for example, you can create a multitasking program using the command fork(). SmallC++ isn't required for writing DOORS OS applications, but it does make it a bit easier.

About Multitasking

A single HC11 processor is incapable of actually executing two or more programs at the same exact time. It can only run one instruction before moving on to the next. So how do you create a multitasker for this environment?

What the OS does is start running one program which, after a certain amount of time (a "timeslice"), expires. You can then start running another program. When its timeslice expires, the OS switches to the next program, then the next, until eventually the first program is again run from where it left off. The key to this is that the timeslice is so very small—approximately 4.1ms for DOORS OS—that it actually appears to be running all those programs at the same exact time, when it's really only running one and then another and so on.

So that's how this "multitasking" OS works, but how do you implement it on an HC11? The answer is in the "real-time interrupt," which triggers about every 4.1ms. When it fires, the current state of the HC11 is saved (register values, program counter, and the like) and a subroutine within the DOORS OS runs, which then switches execution to the next process in the list.

Switching Processes

The interrupt handler runs every 4.1ms, telling the OS it's time to stop running the current process and start/resume running the next. How do you know where in memory you left off? How do we know what to set the internal registers to before jumping back to the process? Luckily, most of this work is already taken care of for us by the HC11.

When the interrupt triggers, the current state of the processor is automatically pushed onto the stack. This means that the contents of every register and the program counter (telling us where we left off) now exists on the current process's stack (each process has its own stack) right before we jump to the interrupt handler (see Listing One). Following this interrupt, a simple RTI ("Return from Interrupt") command restores all the needed information and resumes running the original process. Of course, using such a command only returns you to the previously running process, while you really want to execute a different one.

  
 .          * Data pushed onto stack by process' normal operation
 .          * This can include return address, variables, etc.
 .
PC       * two bytes specifying exact point in memory that we left off
REGISTER_Y  * the next three registers take up 2 bytes each
REGISTER_X
REGISTER_D
CCR         * the Condition-Code-Register takes 1 byte
       * total of 9 bytes placed on stack after interrupt is triggered
Listing One

To switch to another process, you need to first save the current stack pointer (an internal register that "points" to the current stack position in memory), then change it so that it points to the next processes' stack. This means that for every new process you run, that process must have its own unique independent stack space in storage memory which is reserved for a process when it first starts. This also means that the OS needs to keep track of all of these stack positions in an array. You can see the definition of this array in kernel.asm in the DOORS OS SDK (see stackSave) or in Listing Two, which is a C equivalent to the assembler definition.


struct STACK_DATA
{
    void* stackPointer  // a pointer to this process' stack 
                       // position (2 bytes on an HC11)
    byte  used;        // if '1' then this entry in the array contains                     
                // a valid process; if '0', then isn't currently used
    byte  processID;    // holds this process' unique ID
};
STACK_DATA stackSave[NUM_PROCESS]; // declare an array NUM_PROCESS elements 
                          // long to hold info on all used processes
Listing Two

In short, whenever the real-time interrupt triggers, DOORS OS first saves the current stack pointer, in this array stackSave. It then searches through this array until it finds a used process slot (an element within this array that holds information on a valid, currently used process). Once found, it loads the stack pointer with the value saved within the array. Once this completes, the OS issues an RTI command causing the HC11 to load the register and PC values from this process's stack (Listing One) and resumes execution as if nothing had interrupted.

Because each process's stack is kept isolated and the register values are saved and restored correctly, every process you run would execute just as if you weren't using the multitasking OS (the only difference being that the more processes you run, the slower they'll run).

User Interface

There's more to the DOORS OS than just multitasking. In fact, one of my original design goals for this system was that it be capable of running on its own and would manage such things as running user programs when requested and even downloading those programs into memory. However, to accomplish such tasks, you need a UI.

This UI accepts commands from the user's terminal editor via the PC's serial port. The command shell (actually a separate process) continually waits for a command to come in over the HC11's serial port; when one is received, it runs the correct action. For example, if the ASCII character r is sent to the HC11, the OS begins running the user's program. Likewise, if a d is received, the OS knows that a program is being sent over and that it should be stored in memory. Additionally, an s tells the OS to terminate all running processes and reset. Since this command shell is a separate process and is always running (even when a user program is running), you can quickly shutdown and reset your system with just a single command.

The source code for the UI is in kernel.asm under the label commandShell. You can modify this code to suit your own needs. For example, if you need to add some new commands, this is the place to do it. Or alternatively, if you want to use a different I/O device such as a keypad and LCD display, you just need to alter this small subroutine.

In addition to the DOORS UI, you need some sort of interface running on your PC that lets you communicate with the OS. Technically, the minimum interface you need is a terminal editor since most of the communication between user and OS is via the serial port. Of course, if you just use a basic terminal editor, you'll be unable to access the "download program" feature. With this in mind, I wrote a simple C# application that gives you access to all features. The complete source code for this application (see Figure 1) is included in the SDK.

Figure 1: DOORS OS in action.

Startup

When you power up the HC11, the first thing that's going to happen is the stackSave array is cleared, meaning that there are no processes running. Additionally, all other internal variables are set to 0. When this is complete, the OS starts the command shell as a new process and sets the real-time interrupt to run. That's it! When the timer interrupt is run next, it automatically runs the next process, which at this point is the command shell since this is the only process that's been started.

When users eventually send the run command (r is the default), the shell starts the user's program as a new process. This means that both the user program and the command shell will run at the same time (and hopefully peacefully). Should the user send the shutdown command (s), this entire process repeats and you're left with the command shell again.

Starting a Process

In kernel.asm is startProcess, a subroutine that's responsible for setting up all internal variables and the process's stack so that a new process can begin running the next time a process switch occurs. Looking at that function (see Listing Three, available at www.ddj.com/code/), you see that it requires two arguments. The first argument (stored in register D) is the actual memory address of the process to start ("Where in memory do we jump to in order to start running this process?"). Second, register Y holds a True/False value specifying whether you need an exact copy of the current process's stack for the new process (used by the fork() command). By creating an exact copy, the new process shares all the current data that the current process is using such as variable data, return addresses, and so on.

When this process is called, it first attempts to find an empty slot in the stackSave array. Once an array element is found, it copies the current process's stack (if requested to do so by the argument in register Y). Once that's complete, it moves on to setting up the new process's stack space by pushing onto it the new process's location in memory (register D), along with some other data that's required by the RTI command of an HC11.

The startProcess procedure finishes by adding 1 to the total number of running processes variable and by returning the process ID in register A. This process ID is simply a unique 8-bit number that's given to each and every process that's started. This ID can be used in the future should you ever want to stop a process.

Stopping a Process

After you start a new process, there may come a time when you want to stop it. This is done with the stopProcess subroutine in kernel.asm. This function requires only one argument—the process ID stored in register A. Once run, it searches through the list of currently running processes (the stackSave array, Listing Two). When a matching process ID is found, that entry is deleted and the process ceases execution.

Using the DOORS OS

If this operating system can be loaded into memory at any location, how do you design programs so that they can call these functions? If these subroutines aren't even a part of your user program (but instead reside only in the HC11's memory), how does the compiler/assembler know where they're located (remember, this OS can be loaded into just about any memory address)?

The simple solution would be to remember where in memory you placed the OS, then hard-code the addresses of the start and stop process functions into user programs. However, this approach presents several problems (What happens if you move the OS to a different memory location?). A better solution—and the one I actually used—is to have the OS keep a "pointer" to itself in memory location $0000 (the start of internal RAM on an HC11). Since this pointer is always in the same exact location, your programs will always be able to look it up.

So upon reset, the DOORS OS stores in address $0000 the address of the subroutine runCommand (in kernel.asm and Listing Four, available at www.ddj.com/code/). This subroutine is the one that the user program calls to access all required OS functions. When run, this subroutine checks to see what value register Y is (this is the only argument required by this function). If it's a 0, then startProcess is run; if it's a 1, then stopProcess is run. Likewise, if it's a 2, the time elapsed since reset is returned, or a 3, then the startProcess command is run with the request for an exact stack copy to be made.

With all this in place, you can see that it is easy to call an OS function. For example, to start a new process (without stack copy), load all internal registers with the needed data (mentioned before and as shown in Listing Five; available online), then store the value 0 in Y to specify that you want to run the startProcess command. This is followed by looking up the memory address that's stored at address $0000 and jumping to that address. runCommand takes it from there! Listing Five is a sample program in assembler that starts two processes.

Using SmallC++

The DOORS OS SDK includes the SmallC directory and readme.txt file with instructions on how to setup the SmallC++ compiler (www.geocities.com/waltsrobots/ smallc.html) to work with the OS. All that's required is to copy a few different files to different locations.

Once you've done this, your C++ programs can access OS features. By including the DoorsOS.h file in your program, you have access to the startProcess(), stopProcess(), getTime(), and fork() functions.

If you're familiar with the UNIX system call fork(), the DOORS OS version is virtually identical. When called, it creates an exact copy of the current process (creating an exact copy of the calling process's stack, which includes variables and return addresses) and returns control to the "parent" (the original calling process), which resumes running for the remainder of its time. Eventually, however, the "child" process (the new process that was created after the call to fork()) will run and resume execution from the same position after the fork() call.

Since both the parent and child processes resume execution from the same exact point (right after the call to fork()), how do you know which is which? Well, the fork() function returns a valid process ID (a value greater than 0) if the current process is the parent. Otherwise, a 0 is returned. Using this return value, a process can know exactly whether or not it's the parent or the new child process. Listing Five (available online) provides an example.

Conclusion

Even though this DOORS OS was originally designed to run on the HC11, there's nothing preventing you from porting the code over to your processor of choice. If you have any questions at all, please to send me an e-mail.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.