INK®

T H E C O M P U T E R A P P L I C A T I O N S J O U R N A L

independent tasks. The tasks don’t need to worry about returning control A Minimalist FEATURE to a high-level function, and each task may run for a different amount of time ARTICLE before control transfers to another Multitasking task. In some executives, a task may even stop running altogether and wait on some resource to become available Richard Man & before it is run again. Executive Christina Willrich Since there’s only one CPU, in reality only one task is run at a time. What a multitasking executive does is to periodically take control of the CPU and allow other tasks to run. So, how does a multitasking system work? In a cooperative multitasking system, each task voluntarily yields control to the system. In the world of PCs, the Mac OS (until OS X) is an example of a cooperative multitasking system and so is Windows 3.1 and below (except, strangely, for the DOS boxes under Windows 3.1). In a preemptive multitasking system typical embedded (e.g., Windows NT), the system inter- a program reads data rupts a running task if other tasks from input devices must be run. A preemptive system is A multitasking execu- (ADC, keypad, serial more powerful than a cooperative one port, etc.) and, after some processing, because it’s easier to program and it tive might seem like generates some output (LCD, pulses enables tasks to run more fairly. sent to drive motors, etc.). Under a preemptive system, priori- a nice enhancement, One simple and crude method of ties can be assigned to tasks so the going through this is to have a scheduler will run higher priority but the costs are control loop as the main function, loop- tasks first. A high-priority task that is ing through all the things needing to be waiting for a resource may a prohibitive, right? done. Listing 1 shows you an example. lower priority task if the resource The problem is that often you don’t becomes available. The multitasking Well, maybe they want to perform all the steps every executive we describe here is the time the code goes through the loop. preemptive type. don’t have to be. Also, each must tempo- Despite the benefits of a multitask- rarily return to the control loop after ing executive, it’s often not cost effec- With µexec, even some period of time and be able to tive for most simple programs—or for resume from where it leaves off the people on a budget—to purchase a simple embedded next time it’s called. full-blown multitasking executive. While this technique works for some Here, we show you a small multitask- programs can be programs, it’s usually more error prone ing executive, aptly named µexec and harder to use than using a multi- (pronounced myoo-exec), written in written as multiple tasking executive. Listing 2 shows a ANSI C and some assembly routines. program with a multitasking executive. The supplied code is written for and tasks. Listing 2 looks similar to Listing 1, tested on a Motorola ’HC11, but it but the program is partitioned into should be easily ported to other micro- www.circuitcellar.com 20 Issue 101 December 1998 Circuit Cellar INK® execute with one stack and it is easier Listing 1—This sample embedded program uses a control loop. Each time through the loop, several tasks are done in sequence. to be conservative with just that stack. Unfortunately, you have to pay the Control loop: price for using a multitasking executive. check sensor 1 check sensor 2 compute pi to 347 digits AND TIMESLICE contemplate and meditate The scheduler chooses which task activate death ray 1 to run. The period of time a task is drive motor allowed to run is called its timeslice. goto control loop Under µexec, tasks are run to com- pletion of their timeslices unless they are explicitly yielding control to the controllers of comparable power. does not take an argument or return a system or hogging the for The code is quite small, in fact, result. Any such function can be made another timeslice. Although µexec compiling to only 700 bytes using the into a task via UEXC_CreateTask. doesn’t assign priorities, tasks can ImageCraft ICC11 ’HC11 C compiler. Tasks can be created and killed have different timeslice lengths, so µexec is a preemptive multitasking dynamically, even within other tasks. you can cause a compute-intensive system, but to keep the code small, its Normally, a task function should not task to run longer if necessary. tasks all have the same priority level. terminate (i.e., it should execute an The main purpose of the µexec con- So, you can enjoy the benefits of a ). If it does, µexec deletes trol system, then, is to interrupt the multitasking executive without paying it from the internal data structures. processor at the end of a task’s time- a lot in cost or resource consumption. Typically, you create some tasks in slice and invoke the scheduler to choose This article first gives a high-level your main routine and then start the another task to run. µexec uses a timer description of µexec, including some scheduler running. After that, the to interrupt the CPU at regular intervals. of the design choices. Next, we describe processor executes your tasks and never Almost all have the data structures, followed by API returns to the main routine. timer interrupt functions that can be functions and assembly routines. Most Each task needs its own stack, used for this purpose. For example, on porting efforts are only needed in modi- which is supplied to the system when the ’HC11, there are five timer-registers, fying the assembly routines, so skip a task is created. Unfortunately, stack each one of which can be set up to to that section if you’re interested in overrun (i.e., when a task uses stack generate an interrupt when the timer- porting µexec to your favorite micro- space that is beyond the range of the register’s value matches the value of controllers or compilers. supplied stack) can be a deadly problem. the free-running timer counter. The last section gives a summary µexec checks the stack pointer of a The time period when such an and ideas for enhancements. If you just task to ensure that it’s within bounds, interrupt occurs is called a tick, and want to use µexec, see the API descrip- but this check may not be of much its timeslice is some multiple of tions for the interface functions. value since a stack overrun may have ticks. A tick’s value should be chosen Under µexec, a task runs for a period already damaged important data, includ- such that the processor is not inter- of time until a timer interrupt inter- ing µexec’s internal data structures. rupted too frequently, but it shouldn’t rupts it, and the µexec control system So, it’s best to be conservative and be so long that other tasks do not get chooses another task to run. This pro- allocate a large stack for a task. In fact, to execute in a timely manner. cess repeats indefinitely—at least until if your system crashes mysteriously, try For the ’HC11, we chose a value of the system crashes, or are allocating larger stacks for your tasks. 2000 cycles per tick, or about 25 µs (accidentally) disabled, or the embedded Stack overrun is a general problem for a ’HC11 running with an 8-MHz system runs out of battery juice. in embedded programs. Multiple tasks clock. The default timeslice is five exacerbate the problem because each ticks, or 125 µs. You may want differ- TASK FUNCTION task uses its own stack. If there is only ent values for your system. A µexec task is a C function that one control loop, then all the functions INTERRUPT DRIVER Sometimes your program may need Listing 2—This program is the same as in Listing 1, but it is partitioned into separate tasks. The control to perform some function at a regular loop is part of the multitasking executive. interval, and task scheduling may be too slow and unpredictable. µexec gives Task 1: check sensor 1 Task 2: check sensor 2 a hook to the timer-— Task 3: compute pi to 347 digits if the global variable UEXC_Interrupt- Task 4: contemplate and meditate Diver is nonnull, then it’s assumed Task 5: activate death ray 1 to contain the address of a function. Task 6: drive motor The system timer-interrupt han- dler then calls this function before it www.circuitcellar.com Circuit Cellar INK® Issue 101 December 1998 21 performs other work. To use this feature, assign the address of your Listing 3—The key data structure of a multitasking executive is its task control block. µexec’s task control block is described here as a C structure. function to be called to this variable.

enum {T–CREATED, T_READY}; TASK CONTEXT typedef struct TaskControlBlock An important consideration is how { to maintain the task’s context so that struct TaskControlBlock *next; µexec can return control to a previously unsigned char tid; /* task id */ unsigned char state; /* task state, do not use enum since stopped task. Because µexec normally compiler may allocate more space */ regains control through a timer inter- unsigned char ticks; /* how many ticks does task execute */ rupt, and that interrupt already saves unsigned char current_ticks; /* number of ticks remaining */ the CPU states (e.g., the register values) void (*func)(void); /* function to call for that task */ unsigned char *stack_start; /* stack low value */ on the stack, it is sufficient to only unsigned char *stack_end; /* stack high value */ save the stack pointer of an inter- unsigned char *sp; /* current value of stack pointer */ rupted task at the time the handler } was entered. TaskControlBlock; To resume a task, µexec reloads the stack pointer and uses the Return- FromInterrupt instruction to restore pass information between the assembly To choose the next task to run, control to the stopped task. From a and C routines (of course, C routines µexec follows the next pointer field of task’s point of view, a timer interrupt calling other C routines use the stan- the current task and sets the current- hits, and some time later, the interrupt dard C calling format). task variable accordingly. To ensure handler returns and execution contin- Only two global variables are needed. that there is always at least one task ues. The fact that other tasks get a There are some minor differences in to run, the system creates a null task. chance to execute while the task is how each compiler handles global It calls UEXC_Defer in an infinite loop, stopped is invisible to the task. names. For example, some compilers enabling other tasks to run. If no other There is one additional place where prepend an underscore before a global task is there, the system runs this null the task context must be saved—when name if it’s to be used by an assembly task indefinitely. a task voluntarily gives up control module, but these differences are easy A task is identified by its task ID, using the UEXC_Defer function. In this to handle. which is kept in the tid field. ticks case, UEXC_Defer constructs an inter- INTERRUPTS is the number of ticks that the task rupt-stack frame so that no special case Interrupts must be carefully enabled should execute before scheduling occurs handling is needed to resume the task. and disabled inside µexec. If this is done (i.e., its timeslice value). current_ This scheme doesn’t work with improperly, unpredictable results occur. ticks is the number of ticks left in processors using a separate interrupt For example, if interrupts are left the execution of this task. stack from the user stack. Also, if a enabled while global data structures func is the pointer to the C function multitasking executive provides re- are being manipulated, a data structure for the task. The stack start and end source-waiting functions (semaphores, may be in an inconsistent state and values are for debugging purposes, such mailboxes, etc.), there are other places further accesses in an interrupt handler as detecting stack overrun. sp is the where task context must be saved. will crash the system. Of course, if current value of the stack pointer. It In these scenarios, it’s simpler to interrupts are inadvertently disabled must be within the range of the start save the entire CPU context in the when resuming a task, the timer inter- and end stack values. task data structure and not rely on the rupt is also disabled and multitasking interrupt stack. Since µexec does not will stop. GLOBAL VARIABLES provide resource-waiting functions, A simple enhancement is to use As most programmers have learned, and since most small microcontrollers the presented in most global variables are generally not part don’t use a separate interrupt stack, microcontrollers to detect these kinds of good code design. But, there are using the interrupt-stack frame to save of system-crash errors. However, you situations where their use is warranted. the task’s context is fast and effective. need to have a mechanism for the For example, under µexec, we use system to either restart itself or report two global variables to pass information CODE COMMUNICATION the errors. between C and assembly routines. The Most of the µexec routines are alternative is to use normal argument written in C. A small number are in TASK CONTROL BLOCK passing between the routines, but that assembly, mostly to manipulate inter- The data structure TaskControl- would be compiler and rupt-stack frames. Block (see Listing 3) describes a task. dependent, making it difficult to port The format of passing arguments µexec keeps all tasks in a circular µexec using other compilers or to between routines is compiler-dependent, linked list and a global variable keeps other microcontrollers: so we opted to use global variables to track of the current task. www.circuitcellar.com 22 Issue 101 December 1998 Circuit Cellar INK® void (*uexc_current_func)(void); task. If your task function invokes *free_list_ptr; unsigned char *uexc_current_sp; other functions or uses local variables, you should allocate a bigger stack. A task-control block is allocated from uexc_current_func is the pointer the free_list_ptr every time a to the function for the current task task is created. UEXC_Createtask and is needed only to start a new task. µexec needs to allocate memory to fails if no more task-control blocks uexc_current_sp is the current stack hold the task-control blocks and the are available. pointer or the address of the sp field tasks’ stacks. This allocation can be The system initializes free_list_ of the current task. The latter is used done by using the C-library function ptr with the elements in free_list, by UEXC_Defer to tell the assembly memory-management routines malloc so you can adjust the total number of routine where to store the stack pointer and free or by using statically allocated task-control blocks by changing the after the interrupt frame is created. global arrays. value of NUM_TASKS. When a task is µexec uses a few other global vari- Since µexec is meant to be used in killed or when a task function returns, ables and macros, such as static a small system, the overhead of and its task-control block is released back TaskControlBlock *current_ possible fragmentation of the memory to the free_list_ptr pool. task;. This global variable points to space by using malloc and free are the task-control block of the current of concern. So, we use a simple array API task. All the tasks are linked in a cir- allocator instead. However, you can In this section, we present the user- cular list, so the next task to execute easily modify the system to use malloc callable functions. The function proto- is given by current_task->next. and free. types and user-definable macros are in void (*UEXC_InterruptDriver) Task stacks are supplied as an argu- the file uexec.h. (void); contains the address of a ment to UEXC_CreateTask. You These functions are written in ANSI function to call whenever the timer should define statically allocated arrays C, and they shouldn’t need modifica- interrupt triggers. If you have more and supply them to UEXC_CreateTask: tion to compile for other processors than one function to call, you can similar to the ’HC11 or under differ- chain them together. static unsigned char ent compilers. As well, some of the The NUM_TASKS macro is the maxi- task_stack[UEXC_MIN_STACK_SIZE]; internal C functions shouldn’t require mum number of task-control blocks any modification for porting purposes. that can be allocated. You should change All the task-control blocks are allocated The function int UEXC_Create- this to match the number of tasks you from a global array: Task(void (*func)(void), un- have in your system. signed char stack[], unsigned The macro UEXC_MIN_STACK_SIZE static TaskControlBlock stack_size, int ticks); creates defines the minimum stack size for a free_list[NUM_TASKS], a task given the function func. Each task must have its own stack, given by the argument . Listing 4—This trivial program tests µexec’s basic functionality. Two tasks are created, and each one stack prints out a different character on the output. This stack is the lowest address of the array (i.e., address of the zero’th element). Since stack usually grows #include "uexec.h" from high addresses to lower addresses, unsigned char task_zero_stack[UEXC_MIN_STACK_SIZE]; the size of the stack is needed and is unsigned char task_one_stack[UEXC_MIN_STACK_SIZE]; given by the argument stack_size. With the stack and stack size, the void Zero(void) stack pointer can be checked against { while (1) the bounds. If you port µexec to a putchar('0'); processor that needs stack alignment, } you need to align the stack properly. is the number of ticks in void One(void) ticks { this task’s timeslice. If it is zero, then while (1) the value given by the macro DEFAULT_ putchar('1'); TICKS is used. } UEXC_CreateTask obtains a task- void main(void) control block from the free list, initial- { izes it with the supplied arguments, UEXC_CreateTask(Zero, task_zero_stack, sizeof task_zero_stack), 0); and links the task-control block into UEXC_CreateTask(One, task_one_stack, sizeof (task_one_stack), 0); the circular task list. This function UEXC_StartScheduler(); } returns an integer task identifier. If the task function returns, then it acts as if the function UEXC_KillTask is www.circuitcellar.com Circuit Cellar INK® Issue 101 December 1998 23 called with the task’s ID. uexc_current_sp, it calls a function ture, which is a powerful program- void UEXC_Defer(void); gives to see if the current task’s timeslice ming paradigm particularly suitable up the rest of the timeslice and lets has run out. for programming autonomous robots. other tasks run. This function calls an If it has, then a new task is sched- If you’re interested, we recommend assembly routine to construct an uled and control transfers to the new checking out Mobile Robots, Inspira- interrupt-stack frame so that the task task. If the timeslice has not run out, tion to Implementation [1] for more can be resumed by using Return- handler returns via ReturnFrom- detailed information on subsumption FromInterrupt. This process is Interrupt. programming. consistent when a task is interrupted You must arrange for this handler by the timer and its timeslice runs out. to be invoked for the appropriate timer ENHANCEMENTS void UEXC_KillTask(int tid); interrupt. In most cases, this means In summary, µexec is a small, is a function that kills a task with the putting the address of this function in simple to use, simple to port, and yet ID tid. It searches the task list to find the interrupt vector table. If you are useful multitasking executive. Its the matching task and reclaims its using a debug monitor, typically the resource usage is modest, taking only storage after it unlinks it from the list. monitor provides a RAM-based about 700 bytes on an ’HC11. void UEXC_HogProcessor pseudo-vector table. With this executive, even simple (void); hogs the processor and gives The function UEXC_SavregsAnd- embedded programs can be written as the current task another timeslice. If Resched is called by UEXC_Defer to multiple tasks, gaining you the ben- this function is called repeatedly before create a fake interrupt stack. efit of having a preemptive executive. its timeslice is up, then no other tasks UEXC_StartNewTask is called to Naturally, there are plenty of pos- (except for the interrupt driver function) run a new task. The stack is set up so sible enhancements, such as adding are run. It simply assigns the current_ that if the task function ever returns, the watchdog timer function that ticks field of the task-control block then an internal function, UEXC_Kill- comes with most microcontrollers to with the ticks field. Self, is called. UEXC_KillSelf is the system. As well, you might int UEXC_StartScheduler equivalent to UEXC_KillTask choose to modify the system so that (void); is the code that starts the (current_task->tid). error conditions can be reported via multitasking scheduler. It should be The UEXC_Resume is a function your system’s output mechanisms called in your main() routine after it that gets the stack pointer from uexc_ (serial port, LCD, or even just a blink- creates some tasks. current_sp and does a ReturnFrom- ing LED). This function never returns to the Interrupt. Or, why not modify the system so caller, and control transfers to the The function UEXC_StartTimer that it prints out the maximum stack created tasks unless no task was cre- can be written in C, but we put it in usage for each task? You can even add ated prior to this call being made. It the set of assembler functions because a name field to the task control block starts the timer interrupt and then it is microcontroller specific. It needs so that diagnostics can be printed calls the internal function Schedule to set the timer registers to enable the with the task’s name. I to transfer control to a task. timer interrupt at INTERRUPT_CYCLES Finally, later. void (*UEXC_Interrupt Richard Man and Christina Willrich is not actually a TOC4 is used in the supplied code. Driver)(void); are the owners of ImageCraft, a com- function but a pointer to a function. If Typically, a TOC function can be pany which specializes in low-cost nonnull, it should contain the address associated with an output pin, but in professional ANSI C tools for micro- of a function you wish to call at each this case, the output pin function is controllers, plus ’HC12 hardware and timer interrupt. disabled. BDM debug Pods. You may contact The value given by INTERRUPT_ them via www.imagecraft.com. ASSEMBLY FUNCTIONS CYCLES is the system tick and must These functions need to be modified be adjusted for different system clock when porting µexec or if you choose speeds. As supplied, it is defined to be SOFTWARE to use a different timer counter instead 2000 (cycles). of TOC4 (Timer Output Compare 4) UEXC_RestartTimer can also be Complete source code for µexec is under ’HC11. They don’t take any written in C. It simply reenables the available via the Circuit Cellar and arguments or return any value, which timer interrupt at INTERRUPT_CYCLES ImageCraft web sites. should make them easy to port. In later. total, there are less than 50 assembly source lines, 10 of which deal with GIVE IT A TRY REFERENCE the timer registers and can actually be The simple example shown in written in C. Listing 4 creates two tasks that print [1] J. L. Jones and A. M. Flynn, Mobile Robots, Inspiration to UEXC_SystemInterrupt is the out 0s and 1s. In addition to traditional timer interrupt handler. After storing programming uses, µexec can be used Implementation, A.K. Peters, the stack pointer to the global variable to implement subsumption architec- Natick, MA, 1993. www.circuitcellar.com 24 Issue 101 December 1998 Circuit Cellar INK® SOURCES 68HC11 microcontroller Motorola MCU Information Line (512) 328-2268 Fax: (512) 891-4465 www.mcu.motsps.com ICC11 ’HC11 C complier ImageCraft (650) 493-9326 (650) 493-9329 www.imagecraft.com

www.circuitcellar.com Circuit Cellar INK® Issue 101 December 1998 25