Copyright: Copyright © 2004 Softools, Inc.  All rights reserved.


Help file produced by

HELLLP!

www.guysoftware.com/helllp.html

(If this topic is presented in file testing the

author should use the HELLLP! I

button to define a contents topic)


Copyright © 2004 Softools, Inc.  All rights reserved.yesyesyesyesSoftools Rabbit TurboTask3yes1TurboTaskyesyes26/04/04


Table of Contents
	Contents	find	close
TurboTask End User License

Chapter 1 - TurboTask Basics

What is TurboTask?

TurboTask features?

Why use TurboTask?

Why use TurboTask instead of uC/OS-II?

What does TurboTask do?

What does TurboTask NOT do?

What is Multitasking?

Why use multitasking?

Types of scheduling

What is priority inversion?

Can existing code be multitasked?

Is debugging multitasking code difficult?

What is reentrant code?

What is non-reentrant code?

When can global memory be shared?

Which scheduling mode is better?

What is a good timeshare time period?

How can I minimize TurboTask overhead?

Does TurboTask increase interrupt latency?

Chapter 2 - Multitasking functions

Task priority

Multitasking functions

CANCEL_GROUP

CANCEL_TASK

CREATE_TASK

CREATE_TASK_EX

DELAY

RESCHEDULE

RESUME_TASK

RESUME_GROUP

RUN_TASK

RUN_TASK_EX

SCH_BLOCK

SCH_PRIORITY

SCH_ROUND_ROBIN

SCH_UNBLOCK

SET_PRIORITY

SUSPEND_TASK

SUSPEND_GROUP

TIME_SHARE

Chapter 3 - Interrupt, timing and other functions

GET_TIME

INT_ENTER

INT_EXIT

INT_RETURN

RUN_INTERVAL

SET_TIME

TASK_STATUS

TIMER

Chapter 4 - Message Boxes

BOX_EQUAL

BOX_ERROR

CLEAR_BOX

DEC_BOX

INC_BOX

INVERT_BOX

READ_BOX

READ_BOX_EX

READ_BOX_WAIT

RESET_BOX

SET_BOX

TEST_BOX

WRITE_BOX

WRITE_BOX_EX

Chapter 5 - Queues

READ_QUEUE_WAIT

RESET_QUEUE

WRITE_QUEUE

QUEUE_COUNT_WAIT

Chapter 6 – Semaphores

LOCK_SEMAPHORE

UNLOCK_SEMAPHORE

Chapter 7 - Creating TurboTask programs

TTConfig.c

Macros and their purpose:

TurboTask.h

TurboTask.lib

Chapter 8 - Multitasking and C considerations


Help file produced by  HELLLP! v3.2 , a product of Guy Software, on 04/26/2004 for Bill Auerbach.

The above table of contents will be automatically completed and will also provide an excellent cross-reference for context strings and topic titles.  You may leave it as your main table of contents for your help file, or you may create your own as a topic.

By default the title screen below will be displayed when the help file is first opened and the table of contents will appear beside it. You may choose a different opening topic by using the the [I] button on the HELLLP! toolbar.

HINT:  If you do not wish some of your topics to appear in the table of contents as displayed to your users (you may want them ONLY as PopUps), move the lines with their titles and contexts to below this point.  If you do this remember to move the whole line, not part.  As an alternative, you may wish to set up your own table of contents, see Help under The Structure of a Help File.

  Do not delete any codes in the area above the Table of Contents title, they are used internally by HELLLP!


Title Screen
TurboTask

Softools, Inc.

Copyright (c) 2004 Softools, Inc.  All rights reserved.

Softools, WinIDE, TurboTask, SCRabbit, SASMRabbit are trademarks of Softools, Inc.


TurboTask End User License

End User License

Because of the nature of the TurboTask product that is distributed in library form that is incorporated into an application, the following completes the TurboTask license agreement.  This is in addition to the Softools, Inc. standard single-user license agreement for use of the software.

Opening files of the installation and using the software constitutes your acceptance and full compliance to the following items regarding the use of TurboTask:

1.	TurboTask may be distributed only as part of one or more programs created with Softools' development tools.  The use of TurboTask with any other development tools is prohibited.

1.	TurboTask program files or any derivative thereof may not be distributed in LIB or OBJ form and may only be distributed in executable binary form as part of a larger application.

1.	TurboTask functions may not be made part of a BIOS, callable library, or anything similar such that doing so would allow programs downloaded or added to the system later by another non-licensed developer to make use of and call TurboTask functions preexisting in the system.

1.	A program can be downloaded or added to a system such that this program contains TurboTask as part of its complete program image provided a licensed TurboTask developer develops it.

1.	The license to develop TurboTask controlled programs does not extend to the end users of the original licensee.

1.	None of the files shipped with TurboTask may be distributed either in part or on whole by the end user or company purchasing the TurboTask license to any party.

1.	The TurboTask function list and program documentation may not be redistributed either in part or in whole by any mechanism to any party.

1.	TurboTask is sold as-is with no warrantee for performance, ability to use or fitness for any purpose.

1.	If TurboTask was received as part of a Softools development tool package without added cost to you, the amount of support provided for TurboTask will be made at our discretion and may be limited or delayed.  Generally we will not accept programs or samples that are not working to be looked at without charging a fee.


Chapter 1 - TurboTask Basics

What is TurboTask?

TurboTask  is a real-time operating system (RTOS) for use in an embedded Rabbit program.  An RTOS is also sometimes referred to as a kernel.  An RTOS usually at a minimum will provide multitasking features to a program. TurboTask can be used to manage CPU processing time and peripherals, handle data queuing, and provide communication and timing among tasks.

TurboTask features?

φ•	TurboTask provides up to 255 tasks can run either with priority-based scheduling, round-robin (sometimes called time-slice) scheduling or cooperative scheduling (with added program control to allow this).  Each task is considered a thread in the application.

•	TurboTask provides up to 256 16-bit message boxes with a number of thread-safe functions for reading and writing boxes.  Tasks can be suspended with optional timeouts on a few variations of box manipulation functions.

•	TurboTask provides functionality on top of the Queue support in the Softools runtime libraries to allow suspending or delaying tasks until a queue is written to or contains a specific number of bytes.  The Queue support I the runtime libraries was originally available only as part of the TurboTask system.

•	TurboTask provides up to 255 semaphores for controlling resources and hardware.  Typically this is used to control access that must be performed sequentially or by only one thread at a time until it is no longer needed.  One or more tasks can be suspended waiting for the semaphore to be released.  A delay is optional to detect cases where a resource is tied up for too long.  One major problem with semaphores is that priority inversion can occur (discussed later) but TurboTask eliminates the problem by directly controlling it.

Why use TurboTask?

TurboTask allows you to better manage microprocessor with as minimal an impact and overhead taken from the available microprocessor resources.  TurboTask is 1/4th to 1/10th the size of other real time operating systems, mostly because others are written in C and carry the size and runtime overhead associated with C programs.  This isn't to imply that all programs should be written in assembly language, but software controlling the execution of the entire program and which adds a finite amount of overhead to a system should do so with the smallest overhead possible.

Why use TurboTask instead of uC/OS-II?

uC/OS-II is a popular real time kernel for embedded programs. It is well written, portable, and popular.  Because something is widely used or popular doesn't make it a better product (Windows® may come to mind) or a product well suited to your needs.  Though we approve of a very portable product, moving from processor to processor shouldn't be a feature ranked ahead of performance that is lost for this benefit.  For a fast 16-bit or 32-bit processor, uC/OS-II is probably a fine choice – these processors provide better compilers and more expendable performance for a system written in C than an 8-bit microprocessor has.  For an 8-bit microprocessor, the size of uC/OS-II and worse the performance hit may be a significant percentage of the total system speed and space available.  When this becomes too a large percentage and yet it is called a real-time operating system, it sounds like an oxymoron.  Microprocessors like the Rabbit running in the 20's to 40's of MHz cannot afford the size or performance penalty of an RTOS written in C.  This is especially true for Rabbit systems that are pushing the envelope running a TCP/IP stack and one or more protocols on top.   TurboTask is tiny (it's probably difficult to get it any smaller being written in 100% assembly code) and it is very fast (Softools has been writing Z-80 through Rabbit assemble code for 15 years).  As you see later, we give you interrupt latency caused by TurboTask in clock cycles – something uC/OS-II can never do.  One final note – uC/OS-II is documented to not prevent priority inversion, and in fact makes it more difficult to prevent in your code.  Though anyone can code constructs to cause priority inversion, semaphores are more prone to causing priority inversion in states you may not have considered your program getting into.  TurboTask implements semaphores which automatically and completely eliminate priority inversion caused by semaphores.

What does TurboTask do?

TurboTask allows you to multitask quickly and efficiently in one of two modes, either priority based or round robin scheduling.  Preemptive timesharing is provided for round robin scheduling.  TurboTask  allows you to better manage CPU resources with a minimal amount of code.  TurboTask was written in 100% efficient assembly code taking every byte and clock cycle into account and for basic multitasking it can occupy less than 1k of code space while being fully accessible from C.

What does TurboTask NOT do?

TurboTask does not give you more CPU speed or “horsepower”.  All real-time operating systems use CPU resources to do their job.  If your system has bottlenecks and is not keeping up, TurboTask will not be the magic solution to speed it up.  However, if your system polls devices and sits in loops waiting for short delays, TurboTask can help to allow you process other code instead of polling and waiting.  Managing the CPU better could have the effect that you can get more done and reduce bottle-necks.  But the CPU still executes the same number of instructions per second.

What is Multitasking?

Multitasking allows a program to safely run distinct sections of code, sometimes called threads and called tasks in TurboTask at the “same time”.  As you are aware, the CPU executes only one instruction at a time. Multitasking allows you to switch between sections of code, giving the appearance that the CPU is doing two or more things at once.  In reality, it is doing a little bit of each in succession.  It is possible to run the same code in multiple tasks depending on what the code does and only if the code is reentrant.

•	Preemptive multitasking

Preemptive multitasking is a type of multitasking where the task is stopped at any possible time and at any possible instruction, and CPU time is switched to another task.  In other words, a task switch occurs.  In preemptive multitasking, an interrupt from a periodic timer or from a peripheral usually cause preemptive activity.  In fact, an interrupt service routine preempts execution of code while it services the interrupt.  However, while it simply returns back to the point of interruption, TurboTask could resume execution in another task.

•	Cooperative multitasking

Cooperative multitasking occurs when the programmer yields execution to another task programmatically by calling functions in TurboTask that allow this.  One example is when a task is required to delay for a specific time or for an event to occur.  In this case, the programmer requests a task switch and allows TurboTask to execute other code in the meantime.  A system managed by TurboTask that is designed to be completely cooperative is possible but a preemptive system is more of the nature of TurboTask.

Why use multitasking?

Multitasking allows you to better utilize CPU resources.  Multitasking allows you to appear to be doing more than one thing at a time.  The users of your program may like that they can go on to another task while the program continues with the previous operation in the background (e.g. printing a receipt).  Multitasking allows you to design a program that is event driven.  This allows you to react and handle events from the system when they occur instead of looking for an event and processing it when found.  If one event has higher priority than another (e.g., a system overheat signal), TurboTask can immediately run the higher priority task regardless of what was being done, and get back to the lower priority ones when it has completed with the higher priority ones.  If polling had been employed and a lower priority function was just invoked before the higher priority one, it would go unrecognized until polled (programmatically) in the code or until the lower priority code has completed.

Types of scheduling

TurboTask provides the following modes of scheduling for your application.  There is no one good choice that fits all applications.

•	Preemptive priority-based:

In preemptive priority-based scheduling, higher priority tasks always run before lower priority tasks.  In this mode, all tasks have a unique priority.  Remember in TurboTask the lower the value, the higher the priority. Generally, a medium priority task (or tasks) runs the main program and polls for non-interrupting devices.  It may start other tasks (possibly higher priority than itself if something needs to be done or is detected by polling) or it may simply let interrupts or events start higher priority tasks.  If it starts a lower priority task, there must be a provision where the main task is suspended or the lower priority task won't run.

•	Preemptive priority-based with round robin arbitration:

Preemptive priority-based scheduling, with added round robin arbitration, is simply a variant of preemptive scheduling.  If two or more tasks have the same priority they are scheduled in round-robin fashion provided that they yield in some way.  If the scheduler is entered another task of the same priority will run.  The only use for this method is if two tasks are high priority and have more processing to do than you want to run without any other task being able to run.  By cooperatively yielding, two high priority tasks can work together using cooperative multitasking.  If one of the tasks is waiting or not active, there is very little overhead in the cooperative calls because the scheduler returns immediately to the same task having found none others at the same (or higher) priority.

•	Preemptive round robin:

Preemptive round-robin scheduling is also called time-share or time-slice scheduling.  In this mode, priority has no effect on the tasks (even though RUN_TASK requires it).  Each task runs for a specified period and preemptively suspended (at any point) and the next ready task is run.  If the task yields to another task (perhaps because it is polling a peripheral) the next task will run for the remainder of the time-slice period.  If a task needs to run longer without disable interrupts the scheduler can be blocked from switching tasks.  Tasks continue to run in sequential order like this except under one condition.  If a task becomes ready to run because of an interrupt (INT_EXIT is called) this task will run next.  This was done to improve interrupt response where a task is made ready because of code in the interrupt service routine.  If this happens, the sequence is interrupted and continues from the interrupted task to the next task.  Should the order that tasks run matter, the tasks are run in the order of their priority.  In other words the priority sets the order of the task list.

•	Cooperative:

Cooperative multitasking implements task scheduling strictly under application control.  In this case round-robin scheduling must be selected and the time-share interval must be zero.  Each task must yield to another task using any method that suspends a task (a delay, waiting on a message box, queue, or semaphore, or a direct call to reschedule).  Timing is still performed but a delay is only as accurate as the application allows by rescheduling. Without preemptive scheduling accurate program timing is not possible.

What is priority inversion?

Priority inversion, although it sounds more like a techie buzzword than something to be concerned about, is a serious condition.  It is misunderstood and probably allowed to occur in a program because the programmer using a real-time kernel does not understand it and how it occurs.  A software bug that was specifically caused by priority inversion created one of the US space program failures (fortunately it was un-manned).

Consider 3 tasks with priorities A, B and C with A being the highest and C being the lowest.  We'll call these Task A, Task B and Task C.  Priority inversion occurs when Task A is waiting on a resource that is locked by Task C, and Task C  is interrupted by Task B.  This prevents Task A from being able to complete because Task B preempted Task C.  Task A got its priority inverted (became lower in priority effectively) because it is waiting on Task B that is lower in priority and shouldn't occur.  This often happens with semaphores because as tasks queue up waiting on a semaphore other tasks can be preempted as shown.  It takes very careful study and planning of a system to prevent priority inversion.  Sometimes it will occur with the only effect being that a higher priority task is delayed longer than necessary.  If the delay doesn't cause catastrophic problems it probably does go unnoticed.  If the priority inversion is severe, a high priority task can become locked out or the program can fail being in this state unexpectedly.

TurboTask completely eliminates priority inversion with its handling of semaphores.  You might wonder how a real-time operating system can prevent this when it has no knowledge of the interaction of the tasks in the system.  It's fairly straightforward providing the system supports more than one task at the same priority.  By design uC/OS-II doesn't support this ability, which is why preventing priority inversion in uC/OS-II isn't trivial.  In the example above, when Task A requests the semaphore locked by Task C, the Task C priority is raised by TurboTask to that of the waiting task with the highest priority (A in this example).  This prevents Task B from preempting the task that has the semaphore lock.  When the semaphore is released (by Task C which now has priority A), TurboTask restores the priority of Task C (back to C).  Task B (which would have preempted Task C before) and Task A are both ready to run.  Task A is higher priority and as designed gets the semaphore lock and CPU ahead of Task B.  You see how TurboTask has prevented priority inversion and a potentially serious problem.

Consider a program where Task B preempts Task A, but Task A did something to release Task B in the first place.  Task A was expected to complete before Task B was enabled to run (of course it would be best to delay enabling Task B until Task A completes).  Because Task A has higher priority the programmer could guarantee (theoretically) that it would complete.  With the semaphore in the mix and with the priority inversion demonstrated, Task A did not complete and Task B runs, possibly working with memory or hardware which Task A normally would have finished processing.  You can see the real problem caused by priority inversion.

Can existing code be multitasked?

Probably.  If the code is reentrant and all the code it calls is reentrant, generally it will multitask without errors.  If the code is run in a task but it is not reentered, it will run in a multitasking program.  In this case the major concern is that global variables NOT be shared among tasks.  Not only must the code be considered for reentrancy, but also so must all non-local variable usage.

Is debugging multitasking code difficult?

If the code being debugged is reentrantly used, debugging is very difficult because you have a hard time knowing which thread or task the call was made from.  Otherwise, a single task can be debugged normally but you should avoid stepping into TurboTask functions because you may wind up in another task’s code and find out you’re debugging the wrong code.  Also, should an errant C pointer trash TurboTask’s data structures or another task’s stack, it’s difficult to debug why one task is crashing when what really occurred was that another task with a bug caused the problems in the good task.

What is reentrant code?

Reentrant code is code that can be interrupted at any point by TurboTask and entered again from another task and have both threads through the task always provide accurate results.  For example, if 3 tasks are written that do nothing more than call strlen with a string, it’s probable at some point in time that all three tasks will be in the middle of strlen while TurboTask is switching tasks.  If strlen is reentrant (which it is in SCRabbit), the expected result will always be returned.  Not all C compilers create reentrant code.  Not all C code and C library code is reentrant.  Softools’ SCRabbit C compiler and SCRabbit.LIB library are reentrant (with the exception of strtok and sprintf - see Chapter 7).  C code which uses arguments and stack-based locals only is always reentrant.  Remember, a C local pointer which points to a global or static variable and modifies it may not be reentrant.

What is non-reentrant code?

Code which shares global variables or keeps information it needs from call to call in global or static memory is not reentrant.  Static variables within functions as a general rule make the code non-reentrant.  Code which attempts to be reentrant by saving global memory to local memory on entry, changing the global during function execution, and restoring the global on exit still is not reentrant.  Doing so makes the code reentrant for use by an interrupt service routine (since the ISR calls the function reentrantly and it saves and restores the global).  When TurboTask changes tasks if another tasks calls the same function but it doesn’t complete, it doesn’t restore the global and it is then corrupted for the first task when it resumes.

When can global memory be shared?

In a multitasking program, shared global memory must be protected for both reading and writing.  The only exception is when the CPU can modify the memory in one instruction (since an instruction cannot be interrupted).  When writing code in C you can never count on the underlying code so always protect global accesses (reads or writes) with di() and ei() interrupt disable and enable code.  Please remember that the Rabbit block copy instructions (e.g., LDIR) are interruptible after each byte copy and therefore must be protected.  The downside is that interrupts may be disabled for too long a period if the block operation is lengthy.

Which scheduling mode is better?

Both priority and round robin scheduling have their advantages and disadvantages.  From a program's safety point of view, priority scheduling can be a problem if a high priority task gets “stuck” and no other code gets a chance to run.  This is because a lower priority task cannot run until all higher priority tasks have given up control or ended.  On the other hand, if an event occurs and a higher priority task becomes ready to run, it will run immediately.  Round robin scheduling has a feature which makes the next task the ready to run task when it is made ready by an event, but this behavior isn’t guaranteed as it is with priority based scheduling.  Many systems benefit from round robin timesharing multitasking as it allows two or more sections of code to be always running.  Critical task scheduling is still possible by suspending tasks but even when not doing so generally very good response time can be realized with round robin scheduling.

What is a good timeshare time period?

The timeshare period defines how many timer tics a task will run before execution is forced to the next task waiting to run.  If a system is set for 60 tics per second (and the TurboTask TIMER function is called this often or startTTtimer is used), using a timeshare value of 10 means every 10 tics (6 times per second) a task switch will occur.  It takes some time to save and restore a task’s state so task switches do not come for free.  If TurboTask were told to switch tasks every timer tic and if a task switch took 1/5000th of a second, 5% of the CPU time could be spent switching tasks.  With a timeshare value of 10, this drops to 0.5%.  These numbers are exemplary only - task switch times are difficult to calculate and like compiler benchmarks they can be made to look great by the vendor stating them.  Larger timeshare values only mean that sections of code will get longer times to run at each task swap.  If tasks frequently suspend execution on an event, timesharing may happen by preemptive task swaps more so than by timesharing.

How can I minimize TurboTask overhead?

Minimizing TurboTask overhead whenever possible is a good thing to do.  One good example is the TurboTask TIMER or startTTtimer function.  This function specifies the timing period used by the TurboTask functions (e.g. DELAY delays for a specified number of timer tics).  If you have a timer interrupt generating 100 tics per second but you don’t need 10mS timing resolution in TurboTask, save the overhead and call TurboTask only as needed.  If 50 mS timing will suffice, call TIMER only once for every 5 real timer interrupts.  More savings can be gained by not calling TurboTask when it’s not needed or by not using features when they are not needed.  If you are writing to queues or boxes in interrupt service routines but you do not want a task switch to occur immediately rather just when TurboTask has control next (though this is probably a rare circumstance), exit the ISR with INT_RETURN instead of INT_EXIT.

Does TurboTask increase interrupt latency?

TurboTask will disable interrupts for very brief periods to allow operations to be thread-safe and to protect user data (message boxes, queues, and semaphores) and internal TurboTask data structures.  TurboTask never enables interrupts if they are disabled when a TurboTask function is called with the exception of the interrupt service routine exit handlers.  A few functions (noted in the function’s documentation) must not be called from within interrupt service routines.  If the scheduler is interrupted, they change TurboTask data structures in an unprotected manner.  This design decision allowed us to execute the scheduler without turning off interrupts.  The function description indicates how long interrupts are disabled in terms of CPU clock cycles.


Chapter 2 - Multitasking functions

Task priority

Tasks are executed with a priority set using function RUN_TASK.  The lower the number specified, the higher the priority of the task.  For priority based scheduling, no lower priority task runs until all higher priority ones are suspended or canceled.  Great care must be used to prevent a task from using (hogging) all CPU resources by not releasing control or by getting stuck in a loop which prevents all other tasks from executing.  The priority can also be changed using function SET_PRIORITY.  A task may change its own priority or another tasks.  As discussed earlier, a task may temporarily have it priority raised to prevent priority inversion.

In round robin scheduling, the priority has no effect, but it is still used with RUN_TASK and this fact could be useful.  TurboTask maintains a list of tasks that are ready to execute or that are suspended (i.e. any non-canceled task).  This list is sorted from highest to lowest priority.  This makes task scheduling very quick, as you will see.  And the code that keeps the list sorted is only run when tasks are run, canceled or when their priority is changed.  In priority based scheduling, TurboTask essentially starts at the top of the list looking for the first task able to execute.  This requires only a handful of instructions.  In round robin scheduling, it simply sequences through this list.  An occasional skip is possible if a task becomes ready because of an event, but this was done to allow fast response time to an event instead of just letting the scheduler get to it when it does.  So the priority can be used in round robin scheduling to define the order of task execution.  Although it rarely makes a difference in most programs, it’s a “free” feature so we let you know about it.  A more useful feature of the priority in round robin scheduling is that tasks can be grouped and acted upon using SUSPEND_GROUP and RESUME_GROUP.

Multitasking functions

The following functions relate to multitasking and scheduling operations.  Some of these configure the TurboTask scheduler while others manage tasks and their priorities.

CREATE_TASK is used to create a task.  If mutually exclusive operations are required, the same task number can be used.  Doing this saves system resources.  A task that returns is made ready to run again.  It need not be recreated using CREATE_TASK.

RUN_TASK will make the task ready to run.  Depending on several factors, it may or may not execute immediately.

CANCEL_TASK will cancel a task and stop it from running.  If another task cancels a task, it is possible for the canceled task to be executing, in which case it will be halted abruptly.  This could be undesirable if the task was expected to complete it job.  TASK_STATUS can be used to check if a task is running.  A task can cancel itself or simply return.  The return goes back to the RUN_TASK function that initially started the task and it will be canceled.

DELAY will suspend a task for a specified period measured in timer ticks defined as the number of calls to function TIMER. During this suspension, other tasks will execute.

SUSPEND_TASK will stop a task regardless of what it is doing until the task is resumed. This is like a DELAY but it is infinite.  It is a little more efficient than using DELAY because the delay time for the task isn't maintained.

RESUME_TASK resumes a task that has been suspended with SUSPEND_TASK.

RESCHEDULE allows a task to give control to another task.  In priority scheduling, another task is allowed to run if it has the same priority as the task but not lower.  If no task has the same priority, RESCHEDULE wasted code and the call because it will return finding the current task as the highest priority task.  In round robin scheduling, the next task in sequence always runs.

SET_PRIORITY allows setting and changing a task's priority.  It must not be called from interrupt service routines.

SCH_PRIORITY and SCH_ROUND_ROBIN select the scheduling mode.  TurboTask defaults to round robin scheduling if it is not specified.  The scheduling period for round robin scheduling is set using TIME_SHARE.

SCH_BLOCK can be used to block scheduling without disabling interrupts.

TASK_STATUS can be used to find out what a task’s current status is.

TIMER is a function that provides the timing functions for all of TurboTask.  TIMER is implemented for you in the startTTtimer function of the standard Rabbit runtime libraries.


CANCEL_GROUP

Prototype:

char CANCEL_GROUP (char _priority);

Parameters:

_priority	The priority of the tasks to cancel.

Returns:

E_OK	0 - No error.

E_NONE	5 - No tasks match priority

Effects:

Any task matching _priority is canceled.  This causes the scheduler to be executed to run another task if the current task is canceled.

Notes:

If any canceled task is waiting on a semaphore, the semaphore is adjusted to reflect that the task was canceled.

Interrupt status:

Interrupts disabled: Same as for CANCEL_TASK.

Restrictions:

Cannot be used in interrupt service routines.

Registers used:

AF, BC, DE, HL

See also:

CANCEL_TASK RESUME_TASK RESUME_GROUP SUSPEND_TASK SUSPEND_GROUP


CANCEL_TASK

Prototype:

char CANCEL_TASK (char _task);

Parameters:

_task	The task number of the task.  This must not be o or greater than the number of tasks defined in TTCONFIG.C.

Returns:

E_OK	0 - No error.

E_CANCELED	1 - Task number is canceled.

E_RANGE	2 - Task number is not in range.

Effects:

The task is set to the canceled state.  This causes the scheduler to be executed if the current task is canceled.

Notes:

If the task is currently canceled an error results.

If a task is canceled but is (or could be) waiting on either a message box or queue, RESET_BOX or RESET_QUEUE MUST BE USED to clear the state of the message box or queue.

If the canceled task is waiting on a semaphore, the semaphore is unlocked to allow another task to access the resource being locked.  This has been done to allow a task to cancel another task which has been determined to have locked a semaphore for too long.  Of course, this use is for an extreme situation only.

Interrupt status:

Interrupts disabled: At most 35 + (N * 7) Clock cycles (N = number of tasks currently not canceled).

Restrictions:

Cannot be used in interrupt service routines.

Registers used:

AF, BC, DE, HL

See also:

CANCEL_GROUP CREATE_TASK RUN_TASK


CREATE_TASK
CREATE_TASK_EX

Prototype:

char CREATE_TASK (char _task, void (*_addr) (void), void *_stack);

char CREATE_TASK_EX (char _task, void (*_addr) (unsigned, unsigned),
void *_stack);

Parameters:

_task	The task number of the task.  This must not be 0 or greater than the number of tasks defined in TTCONFIG.C.

_addr	The address of a function to execute when the task is run.  For CREATE_TASK, this must be a function returning void with a void argument list.  For CREATE_TASK_EX, this must be a function returning void with 2 unsigned arguments.

_stack	The end address of a stack to be used for the task.  This should be a static buffer in memory.  Using an array of char plus the sizeof the array is recommended.  Since the microprocessor predecrements on a PUSH, the address can be one location past the end of the array.

Returns:

E_OK	0 - No error.

E_CANCELED	1 - Task number is not canceled.

E_RANGE	2 - Task number is not in range.

Effects:

The task is created as a canceled task.

Notes:

If the task number is being reused but the task is not canceled an error results.

Unlike queues and message boxes, tasks are numbered from1 and specifying task 0 results in an error.

If the task returns, it is implicitly canceled and made ready to run as if CREATE_TASK were called again.  This allows the program to call RUN_TASK periodically knowing that it will be ready to run again when it completes.

CREATE_TASK_EX is simply a second entry point for CREATE_TASK so that the function address used with it matches the prototype for the function used in RUN_TASK_EX.  Otherwise, a cast is required on the function address.  It is possible to create a single parameter function - a cast may be used here to suppress the pointer conversion warning.

 	There is no one best size to recommend for the size of the stack to use.  This must be carefully weighed after looking at the stack usage, especially when using C and local stack-based variables.

Interrupt status:

Interrupts are not affected.

Restrictions:

Cannot be used in interrupt service routines.

Registers used:

AF, BC, DE, HL

See also:

RUN_TASK CANCEL_TASK CANCEL_GROUP


DELAY

Prototype:

void DELAY (unsigned int _delay);

Parameters:

_delay	The number of timer tics to suspend the task.

Returns:

E_OK	0 - No error.

E_DELAY_ZERO	9 - Delay cannot be 0.

Effects:

The scheduler is executed to run other tasks until the _delay time has expired, at which time the task is allowed to resume.

Notes:

A delay value of 0 is not allowed.  In TurboTask this indicates an infinite delay which can be achieved using SUSPEND_TASK.

The delay period has the resolution of the frequency of the calls to TIMER.

In priority scheduling, when a task executes a DELAY and it expires, if the task has the highest priority it will run immediately.  In round robin scheduling, the task is made ready but will not be executed until its position in the list is reached.

DELAY waits up to _delay timer tics, not exactly that number.  If you need to have a wait guaranteed to be a certain period, use that number plus one with DELAY.  In other words, a DELAY (1) waits up to one tic.  If a timer tic is microseconds from happening after the DELAY, the delay could be almost instantaneous.  Using DELAY (2) guarantees at least a one tic delay but the delay could last up to (but not including) two full tic periods.

Interrupt status:

Interrupts disabled: 28 clock cycles.

Restrictions:

None

Registers used:

AF, DE, HL

See also:

SUSPEND _TASK


RESCHEDULE

Prototype:

void RESCHEDULE (void);

Parameters:

None

Returns:

Nothing

Effects:

The scheduler is executed to allow a user-initiated task swap.

Notes:

With priority scheduling, RESCHEDULE will pass control to another task, even if it has lower priority.  The time that the tasks gives up CPU control varies but it will regain control the very next time the scheduler is executed.  If the task that gains control doesn’t cause a reschedule and if no interrupt causes one, the higher priority task may never get control back.  It is safer to surround the RESCHEDULE with TIME_SHARE (1) and TIME_SHARE (0).

With round robin scheduling, RESCHEDULE switches tasks to the next task in the list.  A common use of RESCHEDULE is when polling hardware for an event.  If the event hasn’t occurred, a RESCHEDULE executed a loop will allow other tasks to run.  If the event is critical or is known to be imminent (a tolerable delay to tie up the system waiting), RESCHEDULE could delay the response because all tasks get a time share period to execute.  The number of tasks minus one is the time that will elapse before the task has a chance to poll again.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

HL

See also:

TIME_SHARE


RESUME_TASK

Prototype:

char RESUME_TASK (char _task);

Parameters:

_task	The task number of the task.  This must not be greater than the number of tasks defined in TTCONFIG.C or 0.

Returns:

E_OK	0 - No error.

E_CANCELED	1 - Task number is canceled.

E_RANGE	2 - Task number is not in range.

E_SUSPEND	4 - Task is not suspended.

Effects:

The task is set to be ready to execute.  This causes the scheduler to be executed if this task has higher priority than the current one.

Notes:

If the task is currently canceled or not suspended an error results.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, BC, DE, HL

See also:

CANCEL_TASK CANCEL_GROUP RESUME_GROUP SUSPEND_TASK SUSPEND_GROUP


RESUME_GROUP

Prototype:

char RESUME_GROUP (char _priority);

Parameters:

_priority	The priority of the tasks to resume.

Returns:

E_OK	0 - No error.

E_NONE	5 - No tasks match priority

Effects:

Any task matching _priority is set to be executed.  This causes the scheduler to be executed if any task has higher priority than the current one

Notes:

None

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, BC, DE, HL

See also:

CANCEL_TASK RESUME_TASK SUSPEND_TASK SUSPEND_GROUP


RUN_TASK
RUN_TASK_EX

Prototype:

char RUN_TASK (char _task, char _priority);

char RUN_TASK_EX (char _task, char _priority, unsigned _param1,

unsigned _param2);

Parameters:

_task	The task number of the task.  This must not be greater than the number of tasks defined in TTCONFIG.C or 0.

_priority	The priority to assign the task.  The lower the number, the higher the priority.

_param1
_param2	Two 16-bit parameters of user-defined meaning that can be passed to the task.

Returns:

E_OK	0 - No error.

E_CANCELED	1 - Task number is not canceled.

E_RANGE	2 - Task number is not in range.

E_INIT	3 - Task not initialized by CREATE_TASK.

Effects:

The task is set to execute.  This may cause the scheduler to be executed and this function may not return immediately.

Notes:

If the task is currently not canceled an error results.

If the task has the highest priority, it will execute immediately.

For RUN_TASK_EX, the task must be a function defined to accept 2 unsigned parameters.

RUN_TASK_EX is simply another entry point for RUN_TASK.  The code required to set up the 2 arguments was only a few instructions so is common to both functions.

If only one parameter is required, simply use a 0 or the second.  It is possible to define a function accepting one parameter.  Casts can be used to remove the warning in the CREATE_TASK or CREATE_TASK_EX call (either name can be used).

Interrupt status:

Interrupts disabled: At most 61 + (N * 7) clock cycles (N = number of tasks currently not canceled).

Restrictions:

Cannot be used in interrupt service routines.

Registers used:

AF, BC, DE, HL

See also:

CREATE_TASK CANCEL_TASK SET_PRIORITY


SCH_BLOCK

Prototype:

void SCH_BLOCK (void);

Parameters:

None

Returns:

Nothing

Effects:

The scheduler is blocked from switching tasks under all circumstances.

Notes:

This function can be used to block scheduling without disabling processor interrupts.  When scheduling is unblocked, TurboTask will resume scheduling.

Care must be used to unblock scheduling or no other task will run.

If the task returns with scheduling blocked, it will be unblocked automatically.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

HL

See also:

SCH_ROUND_ROBIN SCH_PRIORITY SCH_UNBLOCK


SCH_PRIORITY

Prototype:

void SCH_PRIORITY (void);

Parameters:

None

Returns:

Nothing

Effects:

The scheduler is executed in a priority scheduling mode.

Notes:

The current task may not gain control if a higher priority task is able to run.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, HL

See also:

SCH_BLOCK SCH_ROUND_ROBIN SCH_UNBLOCK


SCH_ROUND_ROBIN

Prototype:

void SCH_ROUND_ROBIN (void);

Parameters:

None

Returns:

Nothing

Effects:

The scheduler is executed in a round robin scheduling mode starting with the last task run with the highest priority.

Notes:

The current task may not gain control if it in not the firs task in the list.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, HL

See also:

SCH_BLOCK SCH_PRIORITY SCH_UNBLOCK


SCH_UNBLOCK

Prototype:

void SCH_UNBLOCK (void);

Parameters:

None

Returns:

Nothing

Effects:

The scheduler is unblocked and task switching and scheduler activities are resumed.

This function could enter the scheduler instead of returning if during the scheduler block a task became ready or the time share period has expired.

Notes:

None

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, HL

See also:

SCH_BLOCK SCH_ROUND_ROBIN SCH_PRIORITY


SET_PRIORITY

Prototype:

char SET_PRIOROTY (char _task, char _priority);

Parameters:

_task	The task number of the task.  This must not be greater than the number of tasks defined in TTCONFIG.C or 0.

_priority	The priority to set for _task.  0 is highest, 255 is lowest.

Returns:

E_OK	0 - No error.

E_BAD	1 - Task number is canceled.

E_RANGE	2 - Task number is not in range.

Effects:

The scheduler may be entered if the change in priority results in a task having higher priority than the caller.

Notes:

If the current task is changed to a higher priority it will continue to run.

If more than one task has the same priority, the first task with the same priority will always run even if others are ready to run.  In other words, tasks with the same priority are not scheduled round robin when more than one is ready to run.

SET_PRIORITY is not necessary if the priority used with RUN_TASK does not need to be changed.  RUN_TASK does not call SET_PRIORITY so some code savings will result by not using SET_PRIORITY.

Interrupt status:

Interrupts disabled: At most 61 + (N * 7) clock cycles (N = number of tasks currently not canceled).

Restrictions:

Cannot be used in interrupt service routines.

Registers used:

AF, BC, DE, HL

See also:

CREATE_TASK RUN_TASK


SUSPEND_TASK

Prototype:

char SUSPEND_TASK (char _task);

Parameters:

_task	The task number of the task.  This must not be greater than the number of tasks defined in TTCONFIG.C or 0.

Returns:

E_OK	0 - No error.

E_CANCELED	1 - Task number is canceled.

E_RANGE	2 - Task number is not in range.

E_SUSPEND	3 - Task is already suspended.

Effects:

The task is set to the suspended state.  This causes the scheduler to be executed to run another task if the current task is suspended.

Notes:

If the task is currently canceled or suspended an error results.

A task that is waiting on a semaphore must not be suspended.

The only operations allowed on a suspended task are a RESUME_TASK, RESUME_GROUP or CANCEL_TASK.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, DE, HL

See also:

CANCEL_TASK RESUME_TASK RESUME_GROUP SUSPEND_GROUP


SUSPEND_GROUP

Prototype:

char SUSPEND_GROUP (char _priority);

Parameters:

_priority	The priority of the tasks to suspend.

Returns:

E_OK	0 - No error.

E_NONE	5 - No tasks match priority

Effects:

Any task matching _priority is set to the suspended state.  This causes the scheduler to be executed to run another task if the current task is suspended.

Notes:

The only operations allowed on a suspended task are a RESUME_TASK, RESUME_GROUP or CANCEL_TASK.

A task that is waiting on a semaphore must not be suspended.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, BC, DE, HL

See also:

CANCEL_TASK RESUME_TASK RESUME_GROUP SUSPEND_TASK


TIME_SHARE

Prototype:

void TIME_SHARE (unsigned _interval);

Parameters:

_interval	The number of tics a task will execute before being preempted to run another task.

Returns:

Nothing

Effects:

None

Notes:

This function sets the time share interval and is effective for both round robin and priority scheduling.  In priority scheduling, this is effective only when there is more than one task at the highest priority.

A value of 1 is the smallest value, but this causes a task switch on each call to TIMER.  This may be desirable if the TIMER call is of a lower frequency.  But for higher rates, excessive time could be spend switching tasks.

If a task uses RESCHEDULE or executes any of the TurboTask functions that cause a suspension (e.g. DELAY) the next task in the list will execute.  This means one cannot guarantee how long the interval will be before a task executes again.  But it does guarantee that no task will ever run longer than the period specified.

Generally, when running with 100 TIMER calls per second (a timer interrupt every 10mS), a time share value of 5 or 10 yields a good spreading out of processor resources with only a very slight amount of overhead involved in task switching.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

HL, AF

See also:

SCH_ROUND_ROBIN SCH_PRIORITY SCH_BLOCK SCH_UNBLOCK


Chapter 3 - Interrupt, timing and other functions

Interrupts

Most TurboTask functions can be used within interrupt service routines.  A few modify TurboTask structures and therefore must not be used in interrupt service routines.  Doing so could leave the scheduler in a bad state and even cause a crash.  If any of the functions listed below must be used in response to an interrupt service routine, a message box should be used.  A task should be dedicated to wait on the write to the box and it should execute the function, thus removing it’s call from the ISR to a task.  If priority based scheduling is used, this task should have the highest priority of all tasks so when the ISR exits this task is run immediately.  Round robin scheduling has a mechanism to allow a task made ready by an interrupt service routine to be executed next.

Any TurboTask function that will be called from within an ISR must occur between calls to INT_ENTER and INT_EXIT.  Failure to do so could cause TurboTask to resume to the scheduler and the scheduler is not reentrant.  This could cause the program to crash.

Functions restricted from use in ISRs

The following functions must not be used in ISRs:

SET_PRIORITY, RUN_TASK, and CANCEL_TASK

Servicing the interrupt hardware

If the interrupt needs to be serviced in software in order to be re-enabled or cleared, this must be done prior to calling INT_EXIT.  INT_EXIT will not return to the task even if a task has not  become readied.  Should one become ready, INT_EXIT executes the scheduler, which resumes another task.  Therefore, you must service the hardware first (e.g. read the timer registers to clear the interrupt).

Traditional ISRs

INT_RETURN can never enter the scheduler so the ISR can be done traditionally.  However, in this case no task will be resumed because of an occurrence within the ISR.  Tasks made ready will eventually be execute the next time the scheduler is executed, but it will not happen instantly.

Calling INT_EXIT

This interrupt exit handler does NOT return to the caller.  Therefore, the stack must be completely restored (naturally excluding the return address) to the pre-interrupt state before calling INT_EXIT or stack underflow will eventually occur.  Interrupts must be disabled but IP must not have been pushed.  TurboTask will execute the IPRES before either returning or going to the scheduler.  Doing it in this fashion allows TurboTask to handle interrupt bursts from a device without concern for too much stack being required.  Your ISR may use an IPSET sooner if it is safe to do so but TurboTask will always do this as well.

For the ease of saving and restoring registers in C, 4 macros are provided to perform this task:

INT_SAVE()	Saves AF, BC, DE, HL, IX & IY.

INT_SAVE_ALL()	Saves AF, AF', BC, DE, HL, BC', DE', HL', IX & IY.

INT_RESTORE()	Restores AF, BC, DE, HL, IX, & IY.

INT_RESTORE_ALL()	Restores AF, AF', BC, DE, HL, BC', DE', HL', IX & IY.

Use of these macros should always be in a void function which contains NO local variables.

Interrupt, timing and other functions

The following functions relate to interrupt processing and TurboTask functions for use in interrupt service routines, for system timing, and a few other miscellaneous functions.


GET_TIME

Prototype:

unsigned long GET_TIME (void);

Parameters:

None

Returns:

The current free running system time.

Effects:

None

Notes:

Calls to TIMER increment a 32-bit counter on each call.  GET_TIME returns this count.

GET_TIME always starts from 0 on each program run.

GET_TIME can be used to determine elapsed time between events.

GET_TIME is only as accurate as the number of calls to TIMER.

This timer has no relation to the RTC or the milliseconds or seconds timers in the standard Rabbit runtime libraries.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, BC, DE, HL

See also:

SET_TIME TIMER


INT_ENTER

Prototype:

void INT_ENTER (void);

Parameters:

None

Returns:

Nothing

Effects:

Blocks further scheduling from occurring.

Notes:

INT_ENTER indicates that interrupt processing is underway and MUST BE USED BEFORE CALLING ANY TurboTask FUNCTION. This is critical. It ensures that all TurboTask functions do not enter the scheduler since it may have been interrupted and it is not reentrant.  INT_EXIT or INT_RETURN must be used to match the INT_ENTER call.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, HL

See also:

INT_EXIT INT_RETURN


INT_EXIT

Prototype:

void INT_EXIT (void);

Parameters:

None

Returns:

Nothing (doesn't return)

Effects:

Enables interrupts!

INT_EXIT can resume to the scheduler if this is the last of any nested INT_ENTER calls and if the scheduler isn’t executing in another thread.  Any TurboTask calls made within these pairs of functions that caused a task to become ready and to preempt this task will cause that task to gain control immediately.  Since INT_EXIT does not return, any interrupt processing code must occur before this call.

Notes:

INT_EXIT indicates that interrupt processing is complete.

INT_EXIT must be called with only the ISR return address on the stack and interrupts must be in a state to be restored using IPRES.  IP must not be pushed!

Interrupt status:

Interrupts are enabled before resuming this task or resuming another task.

Restrictions:

None

Registers used:

None.

See also:

INT_ENTER INT_RETURN


INT_RETURN

Prototype:

void INT_RETURN (void);

Parameters:

None

Returns:

Nothing (doesn't return)

Effects:

INT_RETURN, unlike INT_EXIT will never resume to the scheduler and always returns.  Use this function when you do not wish the scheduler to be executed.  Using INT_RETURN could defer running a task that became ready by TurboTask calls made in the ISR but there may be times when the ISR must return immediately.

Notes:

INT_RETURN indicates that interrupt processing is complete.

INT_RETURN must be called with only the ISR return address on the stack and interrupts must be in a state to be restored using IPRES.  IP must not be pushed!

Interrupt status:

Interrupts are enabled before resuming this task.

Restrictions:

None

Registers used:

None

See also:

INT_ENTER INT_EXIT


RUN_INTERVAL

Prototype:

void RUN_INTERVAL (unsigned _interval);

Parameters:

_interval	The interval value to delay the task.

Returns:

E_OK	0 - No error.

E_DELAY_ZERO	9 - Interval cannot be 0.

Effects:

The task is suspended so that the code following the RUN_INTERVAL function is executed at the specified interval.

Notes:

RUN_INTERVAL should be used in a looping task to allow the code to run at a specific interval.  The time used by the code in the loop is factored out of the delay.  This allows the code that follows to run at the specified interval.

If the code executed takes longer than the interval specified, no delay takes place and the excess time is applied to the next interval.  DELAY and other functions which can cause a delay can be used in the loop being timed by RUN_INTERVAL.

Interrupt status:

Interrupts disabled: 26 and 28 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL

See also:

DELAY


SET_TIME

Prototype:

void SET_TIME (unsigned long _time);

Parameters:

_time	The 32-bit value to change the counter to.

Returns:

Nothing

Effects:

None

Notes:

SET_TIME can be used to reset the count returned by GET_TIME.

The timer starts at 0 when TurboTask is initialized.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, BC, DE, HL

See also:

GET_TIME TIMER


TASK_STATUS

Prototype:

const task_status *TASK_STATUS (char _task);

Parameters:

_task	The task number of the task.  This must not be greater than the number of tasks defined in TTCONFIG.C or 0.

Returns:

A pointer to a constant structure containing information about _task.

typedef struct {

char	state;	// Task state (see above)

char	priority;	// Task priority

unsigned	delay;	// Task delay tics left if in delay

char	box_queue_sem;	// Box/Queue/Semaphore task delayed on

unsigned	box_mask;	// Box mask for box waits

unsigned	queue_count;	// Count in queue for resuming task

unsigned	sp; 	// Task initial stack pointer

unsigned	entry;	// Task entry point

unsigned	sp_save;	// Task sp save area

char	MMU[3];	// Task MMU save area

char	error;	// Error on read wait functions

char	task_num;	// Task number when created

void	*sprintf;	// Pointer safe-store for sprintf

unsigned long	reserved;	// Used internally

}	task_status;

Effects:

None

Notes:

The return type is a pointer to const and must not be used in a cast to a non-const pointer such that the structure can be modified.  Doing so will likely cause problems for the task and TurboTask.

Using a pointer to the structure points to dynamically changing data.  The pointer to the task’s information doesn’t change but the contents are continuously being updated.  If a snap-shot of the task is required, assigning the structure to a temporary may be necessary while processing the data.  For example:

task_status status = *TASK_STATUS(1);

If a task needs to get its own status (possibly to know its task number), the macro CURRENT_TASK can be used.  There may be other reasons – we've been asked how to do this this enough times to warrent adding this macro but we never considered when this would really be necessary.

The following macros defined in TurboTask.h can be used to facilitate getting information about the tasks state from the structure (_st is a pointer to a task_status structure):

ST_CANCELED(_st)	  // Nonzero: task is canceled

ST_SUSPENDED(_st)	  // Nonzero: task is suspended

ST_QUEUE_COUNT(_st) // Nonzero: task is delayed on queue count

ST_QUEUE_WRITE(_st) // Nonzero: task is delayed on queue write

ST_BOX_EQU(_st) 	  // Nonzero: task is delayed on box match

ST_BOX_TEST(_st) 	  // Nonzero: task is delayed on any bits in box

ST_BOX_WRITE(_st)	  // Nonzero: task is delayed on box write

ST_SEMAPHORE(_st)   // Nonzero: task is delayed on a semaphore

ST_DELAYED(_st) 	  // Nonzero: task is delayed

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, DE, HL

See also:

TTDEMO.C demo program.


TIMER

Prototype:

void TIMER (void);

Parameters:

None

Returns:

Nothing

Effects:

TIMER increments the system time returned by GET_TIME.

TIMER decrements the delay count for all tasks currently in a delay suspension (for any reason).

TIMER decrements the time share count if TIME_SHARE has been specified.  If 0, the count is reset to the value specified by the TIME_SHARE function.

TIMER can make one or more tasks ready to run and can also cause a forced task swap under either of the following conditions:

1. A task in a finite delay has its delay updated to 0.

2. The time share period set by TIME_SHARE has expired.

If a task becomes ready which is higher in priority than the current task, a task switch will occur – the current task could be suspended ay any point because of this.

TIMER is called by startTTtimer and is required only if you want to providing timing which does not use the Rabbit periodic timer (e.g. Timer B).  If you are using startTTtimer do not also call TIMER from your program.

Notes:

TIMER must be called between calls to INT_ENTER and INT_EXIT following the stack requirements for INT_EXIT.

Although task can be made ready to run by TIMER, it always returns because generally it is called from a timer ISR.  The INT_EXIT following the call to TIMER will cause a task swap if warranted.

The timer value is not used internally by TurboTask.  Therefore, changing it while tasks are in timed delays will not be harmful.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, BC, DE, HL

See also:

GET_TIME SET_TIME INT_ENTER INT_EXIT startTTtimer


Chapter 4 - Message Boxes

Uses for message boxes

Message boxes are used for communications between tasks.  They are completely thread-safe for both reading and writing.  Interrupts do not need to be disabled when accessing them and they may be accessed in interrupts service routines.  If a task is suspended waiting on a box, another task is blocked from reading the box using functions that zero a box after it is read.  Boxes can be used in the following (and more) ways:

•	To signal to a task that an event it is waiting for has occurred.  For example, a task waiting for a push of a button or keypad entry could wait until the button ISR or keypad ISR activates and writes to the box.  For the keypad, the actual key could be written to the box.

•	To hold a shared value that is protected by TurboTask.  For example, a system with a bank of LEDs of which one or more individual LEDs is used by more than one task could store the LED state in a message box.  When the LED’s box is modified, a task is resumed which can read the box and update the hardware.

•	To hold a count or a bit mapped state which a task is waiting on for a specific count, bit, or match to be resumed.  For example, a task is to be suspended until two buttons are pressed together.  Each button ISR can set and clear a bit to follow the button’s state and the task can wait until both bits are set.

Note that if a function that reads a box uses an infinite delay, no overhead is used by TurboTask until the box is modified.

Some functions treat message box values of 0 as special.  This allows TurboTask to return to a suspended task either the value when written or a delay time-out indication by returning 0.  If the box functions that accept 0 for the value are used to wake up a task suspended with a finite delay, an error function must be used to test for a time-out (because the returned value of 0 could have occurred by a write of a 0 value).

Message box functions

The following pages describe the functions in TurboTask for reading and writing message boxes.  All functions accept a message box number and this must not be the same or exceed the number of boxes defined in the system (since they are counted from 0).  This number is defined when TTCFG.H is assembled.


BOX_EQUAL

Prototype:

unsigned int BOX_EQUAL (char _box, unsigned int _value, unsigned int _delay);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

_value	The value in the box to be tested.

_delay	The number of timer tics to suspend the task.

Returns:

The current value of the box if no error has occurred.

On an error, a 0 is returned an the error is stored and can be retrieved using BOX_ERROR.  Possible errors:

E_RANGE	2 - Box number is not in range.

E_BOX_ERR	9 - Another task is waiting on this box.

E_MASK_ZERO	12 - The value in _value cannot be 0.

Effects:

The task is suspended until either the delay expires or the box is written and one or more bits in the mask are set.

Notes:

A delay of 0 specifies an infinite delay.  No resources are used in this case.  A write to the box by another task with the box value matching _value will resume and this function call will return.

If another task is waiting on _box, more than one task cannot wait on the same box and an error results.

Interrupt status:

Interrupts disabled: 28 clock cycles two times before entering scheduler, 28 clock cycles when the function resumes.

Restrictions:

None

Registers used:

AF, BC, DE, HL


BOX_ERROR

Prototype:

char BOX_ERROR (void);

Parameters:

None

Returns:

Any error that may have occurred for the previous message box functions in the current task from the last BOX_ERROR use up to the latest use.

Effects:

The error indicator is reset to E_OK (0).

Notes:

If multiple errors have occurred since the last call to BOX_ERROR, only the last error is returned.

This function is included in lieu of passing a pointer to a variable to store the error.  This was done because most message box function errors can be checked at compile time or because the function returns indication of an error.

This function may be required with READ_BOX_WAIT_EX when a finite delay is specified.  Otherwise, a returned value of zero can’t be distinguished from a time-out.

Interrupt status:

Interrupts are not affected.

Restrictions:

None

Registers used:

AF, DE, HL


CLEAR_BOX

Prototype:

char CLEAR_BOX (char _box, unsigned int _bits);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

_bits	One or more bits in the box to clear.

Returns:

E_OK	0 - No error.

E_RANGE	2 - Box number is not in range.

Effects:

A bit of 1 in _bits clears the equivalent bit in the box.

If a task is waiting on message box _box and the results of this function resume the task, the scheduler will be executed.

Notes:

None

Interrupt status:

Interrupts disabled: 47 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL


DEC_BOX

Prototype:

char DEC_BOX (char _box);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

Returns:

E_OK	0 - No error.

E_RANGE	2 - Box number is not in range.

Effects:

The box is decremented, wrapping at 0.

If a task is waiting on message box _box and the results of this function resume the task, the scheduler will be executed.

Notes:

None

Interrupt status:

Interrupts disabled: 43 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL


INC_BOX

Prototype:

char INC_BOX (char _box);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

Returns:

E_OK	0 - No error.

E_RANGE	2 - Box number is not in range.

Effects:

The box is incremented, wrapping at 0FFFFh.

If a task is waiting on message box _box and the results of this function resume the task, the scheduler will be executed.

Notes:

None

Interrupt status:

Interrupts disabled: 43 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL


INVERT_BOX

Prototype:

char INVERT_BOX (char _box, unsigned int _bits);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

_bits	One or more bits in the box to invert (toggle).

Returns:

E_OK	0 - No error.

E_RANGE	2 - Box number is not in range.

Effects:

A bit of 1 in _bits inverts the equivalent bit in the box.

If a task is waiting on message box _box and the results of this function resume the task, the scheduler will be executed.

Notes:

None

Interrupt status:

Interrupts disabled: 47 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL


READ_BOX

Prototype:

unsigned int READ_BOX (char _box);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

Returns:

The current value of the box if no error has occurred.

On an error, a 0 is returned and the error is stored and can be retrieved using BOX_ERROR.  Possible errors:

E_RANGE	2 - Box number is not in range.

E_BOX_ERR	9 - Another task is waiting on this box.

Effects:

The box is set to 0.

Notes:

If a task is suspended waiting for some activity on the message box, 0 is returned.

Interrupt status:

Interrupts disabled: 45 clock cycles.

Restrictions:

None

Registers used:

AF, DE, HL


READ_BOX_EX

Prototype:

unsigned int READ_BOX_EX (char _box);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

Returns:

The current value of the box if no error has occurred.

On an error, a 0 is returned and the error is stored and can be retrieved using BOX_ERROR.  Possible errors:

E_RANGE	2 - Box number is not in range.

Effects:

None

Notes:

A box may be checked with READ_BOX_EX even when another task is waiting on the message box.

READ_BOX_EX can be used to read values of 0 from a box.

Interrupt status:

Interrupts not affected.

Restrictions:

None

Registers used:

AF, DE, HL


READ_BOX_WAIT

Prototype:

unsigned int READ_BOX_WAIT (char _box, unsigned int _delay);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

_delay	The number of timer tics to suspend the task.

Returns:

The current value of the box if no error has occurred.

On an error, a 0 is returned and the error is stored and can be retrieved using BOX_ERROR.  Possible errors:

E_RANGE	2 - Box number is not in range.

E_BOX_ERR	9 - Another task is waiting on this box.

Effects:

The task is suspended until either the delay expires or the box is written to.

The box is set to zero when a nonzero value is read and returned.

Notes:

A delay of 0 specifies an infinite delay.  No resources are used in this case.  A write to the box by another task will resume this task and this function call will return.

WRITE_BOX_EX can be used to write a value of 0 to a box.  Since READ_BOX_WAIT returns a 0 on a time-out, READ_BOX_WAIT should not be used with a finite delay and require the use of the value 0 as well.

If another task is waiting on _box, more than one task cannot wait on the same box and an error results.

Interrupt status:

Interrupts disabled: 58 clock cycles when box already has a non-0 value, 37 and 28 clock cycles when the box is 0.

Restrictions:

None

Registers used:

AF, BC, DE, HL


RESET_BOX

Prototype:

char RESET_BOX (char _box);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

Returns:

E_OK	0 - No error.

E_RANGE	2 - Box number is not in range.

Effects:

The message box is zeroed and the state indicating which task is suspended on the box is cleared.

Notes:

RESET_BOX must be called if a task is canceled which could be suspended on this box.  But if this is used without a cancel, the task can never be resumed by writing to the box (because it has been reset).  Order doesn't matter, but of course canceling a task and allowing other tasks to continue writing to its box may have ill effects when the task is resumed.

A better solution instead of RESET_BOX is to communicate with the task via the box that it should be canceled.  For example, reserving a message box value that indicates the task must be canceled is one method.  The state of a task can be tested - if it is not waiting on a message box or queue, it can be canceled without further action.  Otherwise, the message box can be written with this special value and the task will cancel itself.

Interrupt status:

Interrupts disabled: 34 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL


SET_BOX

Prototype:

char SET_BOX (char _box, unsigned int _bits);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

_bits	One or more bits in the box to set.

Returns:

E_OK	0 - No error.

E_RANGE	2 - Box number is not in range.

Effects:

A bit of 1 in _bits sets the equivalent bit in the box.

If a task is waiting on message box _box and the results of this function resume the task, the scheduler will be executed.

Notes:

None

Interrupt status:

Interrupts disabled: 45 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL


TEST_BOX

Prototype:

unsigned int TEST_BOX (char _box, unsigned int _bits, unsigned int _delay);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

_bits	The bits in the box to be tested.

_delay	The number of timer tics to suspend the task.

Returns:

The current value of the box if no error has occurred.

On an error, a 0 is returned an the error is stored and can be retrieved using BOX_ERROR.  Possible errors:

E_RANGE	2 - Box number is not in range.

E_BOX_ERR	9 - Another task is waiting on this box.

E_MASK_ZERO	12 - The mask in _bits cannot be 0.

Effects:

The task is suspended until either the delay expires or the box is written and one or more bits in the mask are set.

Notes:

A delay of 0 specifies an infinite delay.  No resources are used in this case.  A write to the box by another task with any bits matching the mask will resume this task and this function call will return.

If another task is waiting on _box, more than one task cannot wait on the same box and an error results.

Interrupt status:

Interrupts disabled: 28 clock cycles two times before entering scheduler, 28 clock cycles when the function resumes.

Restrictions:

None

Registers used:

AF, BC, DE, HL


WRITE_BOX

Prototype:

char WRITE_BOX (char _box, unsigned int _data);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

_data	The 16-bit value to write to the box.  0 is not allowed.

Returns:

E_OK	0 - No error.

E_RANGE	2 - Box number is not in range.

E_BOX_ZERO	6 - Box value (_data) cannot be 0.

E_BOX_FULL	7 - Box is not empty.

Effects:

WRITE_BOX may not return immediately if the writing to the box causes a reschedule to occur.

Notes:

If WRITE_BOX is called from within an ISR, it must be wrapped with INT_ENTER and INT_EXIT function calls.

WRITE_BOX fails if the box has a nonzero value or a value of zero is used.  Use WRITE_BOX_EX for these cases.

If another task is waiting on _box, more than one task cannot wait on the same box and an error results.

Interrupt status:

Interrupts disabled: 45 clock cycles, 34 clock cycles if the box is full.

Restrictions:

None

Registers used:

AF, BC, DE, HL


WRITE_BOX_EX

Prototype:

char WRITE_BOX_EX (char _box, unsigned int _data);

Parameters:

_box	The box number from 0 to N-1 where N is the number of boxes defined in the system.

_data	The 16-bit value to write to the box.

Returns:

E_OK	0 - No error.

E_RANGE	2 - Box number is not in range.

Effects:

WRITE_BOX_EX may not return immediately if the writing to the box causes a reschedule to occur.

Notes:

If WRITE_BOX_EX is called from within an ISR, it must be wrapped with INT_ENTER and INT_EXIT function calls.

WRITE_BOX_EX does not test for a zero value and allows a value of zero to be written.

If another task is waiting on _box, more than one task cannot wait on the same box and an error results.

Interrupt status:

Interrupts disabled: 29 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL


Chapter 5 - Queues
Chapter 5 – Queues

Queues

Queue support is provided in the Rabbit C standard compiler library.  It is discussed in more detail here because this queue support originally was part of TurboTask and was included in the standard library because of its efficiency and features.

Uses for queues

Queues can be used for the normal purposes queues are usually used for.  TurboTask provides additional features for queue support.  Although one can use queue handling code already provided in the application, there are reasons to use TurboTask queues in place of the original code in the program.

TurboTask queues are fully reentrant (except for reading) allowing multiple tasks and ISRs to write to the same queue.

TurboTask queues can be used to enable and disable transmit interrupts automatically to allow sending data with a UART easily.  Coupled with Softools C compiler's efficient way to print to a “device”, you can “print” to a TurboTask queue and it will handle enabling the transmit interrupts to facilitate transmitting the data.

One set of efficient assembly language code in TurboTask can be used for one or many queues.

TurboTask can suspend tasks until a queue is written or until a queue has a specified number of data bytes stored.

However, TurboTask is not as efficient as hard-coded queue handlers that use absolute memory addresses (i.e. labels) and fixed counts.  TurboTask maintains an information structure for each queue and there is some cost to loading this data for queue manipulation versus using fixed addresses and counts.

To follow up on queues being thread-safe (reentrant) except for reading, this was done to minimize disabling interrupts when reading from queues.  This approach was chosen in favor of disabling interrupts for a longer period for one simple reason.  There seems to be no reasonable program that would read from a queue from two different tasks at the same time.  If it is required for reading a queue, the programmer can explicitly disable and enable interrupts in his application code.

Note that if a function that reads a queue uses an infinite delay waiting for a write to the queue, TurboTask incurs no overhead until the queue is written to or the count is reached.

Queue functions

The following pages describe the functions in TurboTask for queue support that are not included in queue support with the Rabbit C libraries.  For the other functions refer to the Rabbit C compiler library documentation.

Remember, QCreate must be used to initialize a queue.

For interrupt latency numbers the following interrupt latency is provided for 2 queue functions in the Rabbit library:

QAdd - 132 clock cycles worst case.

QClear – 21 clock cycles.


READ_QUEUE_WAIT

Prototype:

char READ_QUEUE_WAIT (queueInfo *_queue, unsigned int _delay, char *_err);

Parameters:

_queue	Pointer to a queueInfo structure.

_delay	The number of timer tics to suspend the task.

_err	A pointer to a character that will receive an error indicator.

Returns:

The next value from the queue.  If the queue is empty, the value is undefined.

If _err is not a NULL pointer, it will contain the following:

E_OK	0 - No error.

E_RANGE	2 - Queue number is not in range.

E_QUEUE_EMPTY	8 - Queue is empty.

E_QUEUE_ERR	9 - A task is waiting for queue.

E_QUEUE_TIME	11 – The task has resumed because the delay time has expired.

Effects:

The queue contains one less data value.

If the queue is empty, the scheduler will be executed to suspend the task until the queue is written.

If the queue becomes empty and interrupt enabling and disabling support is being used, the stop interrupt handler will be called.

A delay of 0 specifies an infinite delay.  No resources are used in this case.  A write to the queue by another task will resume this task and this function call will return.

Notes:

If another task is waiting on _queue, the queue cannot be read by this task and an error results.

If the _err pointer is NULL, no error information will be available.

Interrupt status:

Interrupts disabled: 28 clock cycles.

Restrictions:

Two tasks or a task and an ISR cannot both read the same queue simultaneously.

Registers used:

AF, BC, DE, HL, IY


RESET_QUEUE

Prototype:

char RESET_QUEUE (queueInfo *_queue);

Parameters:

_queue	Pointer to a queueInfo structure.

Returns:

E_OK	0 - No error.

E_RANGE	2 - Queue number is not in range.

Effects:

The queue is emptied and the state indicating which task is suspended on the queue is cleared.

Notes:

RESET_QUEUE must be called if a task is canceled which could be suspended on this queue.  But if this is used without a cancel, the task can never be resumed by writing to the queue (because it has been reset).  Order doesn't matter, but of course canceling a task and allowing other tasks to continue writing to its queue may have ill effects when the task is resumed.

If the task is suspended with an infinite delay, a good solution does not exist for canceling the task in a more structured manner.

This function can also be used to empty a queue but it MUST NOT be called while a task is suspended on this queue or the task will never be resumed.

Interrupt status:

Interrupts disabled: 45 clock cycles.

Restrictions:

None

Registers used:

HL, IY


WRITE_QUEUE

Prototype:

char WRITE_QUEUE (queueInfo *_queue, char _data);

Parameters:

_queue	Pointer to a queueInfo structure.

_data	The data byte to add to the queue.

Returns:

E_OK	0 - No error.

E_RANGE	2 - Queue number is not in range.

E_QUEUE_FULL	7 - A task is waiting for queue.

Effects:

The queue contains one more data value.

If a task is waiting on a write to the queue or a count of data in the queue which this write satisfies, the task will resume and the call to WRITE_QUEUE will not immediately occur.

If the queue is empty and interrupt enabling and disabling support is being used, the start interrupt handler will be called.

Notes:

QAdd can be used to add to the queue but it has no effect on any task.  If a task waiting on the queue to be written to, WRITE_QUEUE must be used.

Interrupt status:

Interrupts disabled: 132 clock cycles worst case.

Restrictions:

None

Registers used:

AF, BC, DE, HL, IY


QUEUE_COUNT_WAIT

Prototype:

unsigned char QUEUE_COUNT_WAIT (queueInfo _q, unsigned _delay, unsigned _count);

Parameters:

_queue	Pointer to a queueInfo structure.

_delay	The number of timer tics to suspend the task.

_count	The number of bytes that must be in the queue.

Returns:

E_OK	0 - No error.

E_QUEUE_ERR	9 - A task is waiting for queue.

E_QUEUE_TIME	11 – The task has resumed because the delay time has expired.

Effects:

The task resumes when at least count bytes are in the queue.  It is possible that more bytes are in the queue than the count specified only if QAdd is used to write to the queue.

A delay of 0 specifies an infinite delay.  No resources are used in this case.  A write to the queue by another task that causes the queue to have count or more bytes will resume this task and this function call will return.

Notes:

None.

Interrupt status:

Interrupts disabled: 28 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL, IY


Chapter 6 - Semaphores
Chapter 6 – Semaphores

Semaphores

Semaphores are mechanisms to safely share a system resource.  Usually it's for a resource that is not reentrant or must be accessed from beginning to end without another thread accessing it.  Typically the resource is one that takes a bit of time to access.  This is to say, a semaphore wouldn't be used to protect a variable or even a buffer because all accesses to it can be protected by disabling microprocessor interrupts.  When disabling processor interrupts causes too much interrupt latency, a semaphore is the best solution to share the resource without disabling interrupts.  Because other tasks can preempt the task using the resource, it's important that there be no time limit on how quickly (or slowly) the resource is used.  If interrupts can’t be disabled but the task also cannot be preempted, consider functions SCH_BLOCK and SCH_UNBLOCK to prevent task switching without disabling interrupts while still allowing multiple threads to share the resource safely.

Although a message box might appear to work for preventing multiple accesses to a resource, more than one task cannot wait on a single message box.  Furthermore, once more than 1 task is waiting on the resource, it is a requirement to give the waiting task with the highest priority the resource.

Uses for semaphores

Semaphores should be used to prevent access by more than one task to a system resource.  The resource could be a peripheral like an LCD, or a serial device where the data being output can't have data intermixed from another thread.  A function like printf that outputs to a device should use a semaphore.  Other uses might be an SPI device, EEPROM, Compact Flash or other device.  Semaphores should also be used for code that is not reentrant.  If a function uses global or static variables in order to be more efficient or smaller, it's use should be protected with a semaphore.

Working with semaphores

A semaphore is in one of two states.  It can be locked, in which state any task attempting to lock the semaphore is suspended (with an optional delay).  Or it can be unlocked, in which case the first task to lock it gets the CPU time immediately and any other task is prevented from locking the semaphore.

When a task is finished with the semaphore, it unlocks the semaphore. If no other task has requested a lock, the task continues immediately.  If other tasks are waiting on the semaphore but have lower priorities than the task holding the lock, again the unlocking task continues.  If any task has a higher priority than the unlocking task, it runs immediately leaving the unlocking task suspended.  This is an important aspect – the task unlocking the semaphore may not get control immediately after doing do.

If a task tried to lock a semaphore that is already locked, but the locking task has a priority lower than the task trying to lock the semaphore, the task holding the lock will have its priority raised to that of the waiting task.  As discussed above, this eliminates priority inversion.  When the task unlocks the semaphore, its priority will be restored and the waiting task (still having a higher priority) will have access to lock the semaphore unless another task is also waiting with yet higher priority than the first one waiting.

The implication of this functionality is that a semaphore is not a first-come, first-served mechanism.  It is driven entirely by priority of the waiting tasks.


LOCK_SEMAPHORE

Prototype:

char LOCK_SEMAPHORE (char _sem, unsigned _delay);

Parameters:

_sem	The semaphore number 1 to N of the semaphore to lock.

_delay	The number of timer tics to suspend the task waiting for the semaphore to be locked.

Returns:

E_OK	0 - No error.

E_RANGE	2 – The semaphore number is 0 or larger than the number of defined semaphores.

E_SEM_TIME	11 – The task has resumed because the delay time has expired.

Effects:

If the semaphore is not locked by another task (thread), it is locked and the task continues.  If the semaphore is locked by the current task, the lock is counted and the task continues.   If the semaphore is locked by another task, the task is suspended until the delay time expires (unless it is 0) or until all higher priority tasks waiting on the semaphore execute and unlock it.

A delay of 0 specifies an infinite delay.  No resources are used in this case.

Notes:

The semaphore must be unlocked by the task when the resource is no longer needed.  If the task has acquired more than one lock of the semaphore, it must call UNLOCK_SEMAPHORE the same number of times before the semaphore is unlocked and accessible  by another task.  In other words, locking and unlocking muct be balanced.

If E_SEM_TIME is returned, the semaphore is not locked by the current task.  The delay could be used to indicate a task has locked a resource for too long.  What happens in this case makes it difficult to recommend a solution.  An attempt to lock the semaphore again could be tried or in a dire event the task with the lock could just be canceled.  Canceling a task with a lock on a semaphore ensure the semaphore is unlocked.

There is very little overhead used on locking the semaphpre on either the first or multiple calls.  This allows a free semaphore to be used with minial overhead.

See the discussion above of how semaphores are implemented.

Interrupt status:

Interrupts disabled: 28 clock cycles.

Restrictions:

None

Registers used:

AF, BC, DE, HL


UNLOCK_SEMAPHORE

Prototype:

char UNLOCK_SEMAPHORE (char _sem);

Parameters:

_sem	The semaphore number 1 to N of the semaphore to lock.

Returns:

E_OK	0 - No error.

E_RANGE	2 – The semaphore number is 0 or larger than the number of defined semaphores.

E_SEM_LOCKED	14 – The semaphore is not locked by this task.

Effects:

The semaphore is unlocked.  If no other tasks are waiting the task continue to run.  If one or more tasks are waiting on the semaphore, the highest priority task is unsuspended and the scheduler is executed to change tasks.

Notes:

E_SEM_LOCKED is returned if the task does not have a lock on the semaphore.  This could happen due to a bug in which LOCK_SEMAPHORE wasn't called.  In this case the resource was accessed possibly with ill effects since other tasks were not locked out and could have accessed it.  It can also occur if LOCK_SEMAPHORE was called with a non-0 delay and the delay has expired thereby not locking the semaphore.

If this task's priority has been raised because a higher priority task is waiting on the semaphore, the priority of this task is restored.

Because the scheduler may be executed to change tasks, the call to UNLOCK_SEMAPHORE may not return immediately.

See the discussion above of how semaphores are implemented.

Interrupt status:

Interrupts disabled: 88 clock cycles + N * 61 clock cycles (worst case; N = number of tasks not canceled of higher priority than the task locking the semaphore).

Restrictions:

None

Registers used:

AF, BC, DE, HL


Chapter 7 - Creating TurboTask programs

Required files in a TurboTask program:

In order to add TurboTask functionality to a program the following files are required:

TTConfig.c

This file defines static, compile-time constants for TurboTask controlled programs.  Generally, this should be modified  any time any of the following changes occur:

1.	You need more tasks running at one time than you had previously specified or you need to increase the stack space for those that were defined.

1.	You need more message boxes than you had previously specified.

1.	You need more semaphores than you had previously specified.

TTCONFIG.C, defines the number of tasks, message boxes and semaphores using macros (#define) and these are used to define variables that hold these limits and to define the actual compile-time storage requirements for these items.

Macros and their purpose:

#define TT_NUM_TASKS n

TT_NUM_TASKS defined here to n, a number from 1 to 255 represents the maximum number of tasks that need to execute at the same time that TurboTask can support for this application.  If the application consists of 12 tasks, but only 5 are ever active at one time, then entering 5 here provides the most efficient use of memory and processor resources.  In this case, if the application accidentally asks a sixth task to start, TurboTask will return an error indicating that the task cannot be created.

Each task requires 4 bytes of program storage, 22 bytes of RAM, plus the amount of RAM allocated for the tasks' stacks.

This number entered here must be from 1 to 255.  All tasks numbers start at one.  Keep this in mind since message box and queue numbers start at zero.

#define TT_NUM_BOXES	 n

TT_NUM_BOXES defined here to n, a number from 0 to 255 represents the maximum number of messages boxes supported in the application.  Message boxes are numbered from 0 to n-1.

The amount of RAM used is 3 times the number of message boxes.

#define TT_NUM_SEMAPHORES n

TT_NUM_SEMAPHORES defined here to n, a number from 0 to 254 represents the maximum number of semaphores supported in the application.  Semaphores are numbered from 1 to n.

The amount of RAM used is 4 times the number of semaphores.

TurboTask.h

You most likely will want to include this in all of your C files that will access TurboTask functions and data structures.

TurboTask.lib

This must be linked into your program so SLINK can find all of the required components of TuroTask.  Note that TurboTask is a true library and only the parts of it you use will be included into the final program.

TTCONFIG.C can be included anywhere in the link as it defines only constants and RAM storage.

Segments in TurboTask

TurboTask uses only segment FARCODE for code, CONST for constants and BSS for variable storage.  TurboTask uses only 18 bytes of RAM over what is defined in the program for message boxes and semaphores.

Running with TurboTask

TurboTask can easily be added to an existing program or a program can be built from scratch.  There are only a few simple changes to be made because TurboTask was designed to allow it to be added to a program with minimal changes to the program and its design.

Function main

C function main needs to initialize TurboTask and create and run the first task.  Technically it can create all tasks but only one can be run.  The first task will be required to run the remaining tasks.

You continue to use the standard SCRabbit startup CSTART.ASM and you execute it and any other initialization you must have done first.  The stack size defined in cstart can be very small – only enough for the calls to initialize TurboTask and run the first task.  If other initialization is done in function main this needs to be accounted for when sizing the stack.  32 bytes is sufficient for TurboTask initialization.  TurboTask does NOT enable processor interrupts with an ipset instruction.  Also, TurboTask defaults to round robin scheduling.

char stack1[512];

void	main (void)

{

TT_INIT();

startTTtimer( 100, 2 );

ipset1();

CREATE_TASK (1, Task1, stack1+sizeof(stack1));

RUN_TASK (1, 3);

}

TT_INIT initializes TurboTask data structures.  It does not change anything else in the system – hardware or software.

StartTTtimer starts the Rabbit periodic timer, in this example at 100 tics per second with an IP of 2. This function handles the timer as well as calling the TurboTask TIMER function.  Using 100 tics per second allows for nice 10mS timing.  A delay using 100 will wait for 1 second.  Although you may be tempted to make this number larger, doing so adds overhead to the entire program.  The TIMER call has to update the system time and for each task in a delay it has to update the delay and resume the task when the delay reaches zero.

CREATE_TASK and RUN_TASK are self-explanatory with the added note that the RUN_TASK call will not return.


Chapter 8 - Multitasking and C considerations

Standard C isn't completely reentrant

Many people find this statement hard to believe.  However, there is at least one circumstance where C is known to not be reentrant.  Fortunately, it is due to a rarely used C library function and hence usually dismissed.  In addition, a Softools' SCRabbit library function is also not reentrant.  Keep in mind that the code generated by SCRabbit is completely reentrant except in the cases where the compiler option 'Convert automatic locals to static' is used.

sprintf

The C function sprintf, as implemented in Softools' library, is not reentrant.  To be more specific, it is reentrant if sprintf is interrupted by an interrupt and the interrupt service routine itself calls sprintf.  It is not reentrant if sprintf is interrupted and TurboTask switches tasks to another task that calls sprintf.  The subtle difference is that the in first case, sprintf is interrupted by the same thread (using the same CPU stack) while in the second case, another thread is using sprintf.

The supplied TurboTask library contains a modified sprintf for use in (and only within) a TurboTask program.  This is a thread-safe sprintf which may be used by multiple tasks concurrently.  In order to use the modified sprintf you must be sure TurboTask.lib is linked ahead of SCRABBIT.LIB.  Failing to do so will put the non-reentrant sprintf in your program.

Update: Version 1.65.00 and later of the WinIDE contains an updated sprintf that is fully reentrant for TurboTask threads.

strtok

The standard C library function strtok is not reentrant.  It is not reentrant because of the way it was designed.  It's definition is such that it should remember where previous strtok calls left off in scanning the string.  This means it must keep a pointer in a static (preserved) location in memory.  Although we could have "fixed" strtok as was done for sprintf, it is rarely used in embedded applications.