markh@csd4.csd.uwm.edu (Mark William Hopkins) (05/20/91)
The 8051MX: a real-time 8-bit multitasking controller. (0) OVERVIEW In case you've never heard of the 8051MX, don't feel stumped. It doesn't exist, I just made it up! :) Hence this article: a new chip proposal. The 8051MX is an upwardly compatible extension of the 8051 8-bit controller that incorporates my real-time multitasking kernel. This kernel is so damn simple, and so small, that it seems to make perfect sense to see it implemented in hardware. The difference, however, is that in hardware it is possible to check for conflicts between processes, whereas the software kernel cannot effectively do that. The best way to describe this architecture is: A Signal-Driven Threaded Multi-Processor. (1) ARCHITECTURE These are the primitive operations of the MX extension of the 8051 chip: pause #Sig resume #Sig where "Sig" is a "signal-descriptor" used to mediate task-switching. There are 16 in all, but the first several should be thought of as being associated with hardware interrupt flags. So compatible assemblers will have options that enable the automatic generation of the appropriate interrupt-handlers and perhaps interrupt-initialization code on reset. First, "pause #Sig" will freeze the current process, placing it in a queue associated with this particular signal, Sig. The chip can be designed to have a queue capacity of only 1 (for exclusive access), or a larger limited number (for chaining processes onto a signal), if feasible. This process will be frozen until the corresponding "resume #Sig" is called, and then it resumes right after where the pause was called. When the process "resume"-ed either returns or pauses again, then control returns at the point after the "resume" was called. Therefore, resume is an indirect call of sorts. spawn DPTR sets up a stack segment for the process pointed to by the DPTR register and then initiates it. Spawn "returns" when this process executes a pause or a return, and the process ID is returned in a new special function register. Setting a flag will allow the user to define the address for the "spawn"-ed process to exit to, else a default exit routine "_Exit" is carried out on return. There are a preset number of process ID's available (8), numbered from 0 and up, and if there is no available process, the value -1 (ff, in hex) is returned. exit #PID causes the process indicated by the process ID (PID) to abort, if it is currently active. Therefore, next time this process is "resume"-ed, it will jump immediately to its predefined exit address. getpid sets the special function register mentioned above to the current process ID. status #PID returns the status of the process indicated into a special function register. A Priority Stack is used to enable task preemption in a way that is consistent with the interrupt priority structure. Both pause and exit will pop the next process to go to off this stack, and both resume and spawn push the current process onto the stack. The stack, and internal registers are all initialized on reset. Exiting from the main process is equivalent to jumping to an infininte idle loop, and puts the chip into idle mode. Effectively, the chip is ALWAYS considered idle, with "main" being spawned on a reset. Interrupt-handlers, of course, are still active even after the main process exits, so that processes that are in the background are still in effect. All the new instructions are implemented with the reserved A5 opcode (which is all you have left on this chip :)). (2) EXAMPLE Suppose you have a keypad hooked up to the chip, with external interrupt 0 used to flag a key-press, and the data from the key-pad going into port 2. Using the multi-tasking feature above, reading a key will be as simple as this: GetKey: pause #SIG_IE0 mov A, P2 ret where the user or assembler defines SIG_IE0 and sets up the appropriate interrupt-handler: ORG 3 resume SIG_IE0 reti Hey! It's that easy, but now you don't have to worry about manually threading all your processes and idle-loops together. The hardware does it for you! So you can concentrate on just writing straight-line code without the nasty asynchronism rearing its ugly head. Of course, you still have to worry about initializing the interrupt mode and enabling it. (3) COMMAND SHELL Most development 8051 chips will come with a BASIC interpreter masked into internal ROM. This interpreter effectively implements a jump table for commands, and intercepts interrupts with its own set of interrupt handlers and "vectors" user interrupt-handlers out to some other fixed address. But now, you have the potential to have an extended BASIC interpreter that effectively behaves like a command shell. There are no longer any user-defined interrupt handler addresses, as all interrupts now are tied to given "signals". The programmer only needs to use the "pause" statement on that signal to create an interrupt handler in his or her application. No need to worry about vectoring to the appropriate address. Secondly, the interpreter now has a library of routines that can be called in a relatively easy way by using the "resume" statement with the appropriate signal ... much the same way that library functions are called in the IBM PC using the 8086's INT command. The pause, resume, spawn, etc, are all defined at the user level in the BASIC as well. Thus, you now have a command shell to a small real-time operating system that is capable of multi-processing.