64 of 158
58 End Sub Sub MyTask() ' set the interval timer to 5 seconds Call SetInterval(5.0) Do WaitForInterval() <stuff-to-do-periodically> Loop End Sub In this partial example, a task is set up to perform some important activity periodically. If this were part of a system to control the chlorine level in a pool, MyTask might read a transducer that indicates chlorine level and, if it’s below a certain level, cause a known amount of chlorine solution to be injected into the circulation stream. One important aspect of creating and using tasks is the allocation of a task stack. The task stack has two components: a task control block and the portion used as an execution stack. The task control block is a fixed size (see Section 3.27 for details) and resides either at the beginning (for VM mode devices) or at end (for native mode devices) of the task stack. The remainder of the task stack is dedicated to the execution stack for the task. The execution stack is used for local variables in the task routine, parameters passed to other subroutines and functions that might be invoked as well as local variables used in those routines, and space required for expression evaluation. If a task stack is allocated that is much larger than is actually necessary, User RAM is wasted since no other task can use the excess space. On the other hand, if a task stack is smaller than required, data items preceding or following the task stack in memory will be overwritten. This will generally cause your application to misbehave and may result in the processor resetting. This begs the question, of course, of what is the optimum task stack size. Unfortunately, this is not as easy to answer as it might seem. The difficulty in answering this question lies in the fact that stack use is dynamic and may depend on external factors such as a signal applied by an external device or a user pressing a key. Not only that, the stack use may depend on the order or the timing of such external events. Further, recursive invocation of routines adds to the dynamic nature of stack use and is impossible to account for (in most cases) using any kind of static analysis. For VM mode ZX devices (e.g. the ZX-24a), the ZBasic compiler automatically estimates the minimum task stack size. Information about the estimate, including the stack use of individual routines, is displayed in the .map file (assuming that one is generated as is the default). If the size of the stack that you have allocated is smaller than the estimated size plus a safety margin, the compiler will generate a warning indicating the deficiency and indicating the minimum allocation required. By default, the safety margin is 10 bytes but you may specify a larger or smaller safety margin (including zero) using Register.StackMargin. For native mode ZX devices (e.g. the ZX-24n), the minimum task stack size cannot be determined at compile time. To assist you in determining the appropriate task stack size a special System Library function is provided that determines, at run time, how much unused space exists in the task stack. By exercising your application and periodically checking the amount of unused task stack space exists, you may be able to determine empirically a suitable minimum task stack size. See the description of System.TaskHeadRoom() 3.5.1 Advanced Multi-tasking Options For advanced users, a task’s main routine may also be defined with parameters. This may be useful to provide information for the task to perform its function rather than doing so using global variables. Parameters for the task are specified in a comma-separated list enclosed in parentheses just as they are for a subroutine invocation. The number and types of the parameters specified must match those of the task being invoked.