Hi All
One question which is often asked is about how to create new tasks. The basics to this are in the document about the operating system basics but it would be interesting to have various examples so here are some:
1. Every task requires first a unique name. For example the watchdog task has the name "Wdog". This string is quite useful when working in debug mode in the tasking part of the code since the name can be displayed. However the actual scheduling works only with the first letter of the name - this is since it is much more efficient to compare just the first letter than a string and keeps with the goals of the uTasker which are to achieve a high level of code size and speed efficiency but still keep a useful degree of comfort.
2. In order to work with the task a simple define is required:
#define TASK_WATCHDOG 'W' // watchdog task
which corresponds to the first letter of the name.
3. In order to add a new task, simply define a name with unique first letter or number, or any other sign. Try to match the name to the tasks job but still do this without colliding with an existing name - capitals and small letters are of course not equal.
#define TASK_MY_NEW_ONE 'n' // my new task task
"new task.." // the name we will give it
4. Every task requires a function which is called when it is scheduled
#include "config.h"
#define OWN_TASK TASK_MY_NEW_ONE
void fnMyNewTask(TTASKTABLE *ptrTaskTable) // task function
{
}
but before it can actually be used it must be defined in the task table as well as in the node list.
Note that the task table entry defines the properties of the task (at least when it first starts) but it doesn't cause the task actually to be created. It is only created when its reference is ALSO in the node list. The idea behind this is that various node lists can be defined (eg. one for each node in a distributed system) which then create only the tasks which are actually needed for it's own work.
const UTASK_TASK ctNodes[] = { // we use a single fixed configuration (single node)
DEFAULT_NODE_NUMBER, // configuration our single node
TASK_MY_NEW_ONE,
0, // end of single configuration
// insert more node configurations here if required
0 // end of configuration list
};
extern void fnMyNewTask(TTASKTABLE *);
const UTASKTABLEINIT ctTaskTable[] = {
{ "Wdog", fnTaskWatchdog, NO_QUE, 0, (DELAY_LIMIT)( 0.2 * SEC ), UTASKER_GO}, // watchdog task (runs immediately and then periodically)
{ "new task..",fnMyNewTask, NO_QUE, 0, 0, UTASKER_STOP}, // new task not doing much yet
};
When the system starts, the resources for the new task will now be created. In this case the task will have no input queue and no timers associated with it. In fact it will not do much and never be scheduled by the tasker.
It could however be activated from another task in the system when it calls uTaskerStateChange(TASK_MY_NEW_ONE, UTASKER_ACTIVATE);
This will cause the new task to be scheduled once before it is set back to the UTASKER_STOP state.
If it is started using uTaskerStateChange(TASK_MY_NEW_ONE, UTASKER_GO); it will cause the task to be scheduled every time the tasker cycles. This will then convert the task into a polling task.
However it can of course also be immediately started as a polling task by already defining it like that in the task list.
{ "new task..",fnMyNewTask, NO_QUE, 0, 0, UTASKER_GO},
5. Now it may be that you have some code which you would like to add to the project which looks something like this:
int main(void)
{
fnInitialiseSomeThings();
while (1) {
if (event1 != 0) {
fnHandleEvent1();
}
if (event2 != 0) {
fnHandleEvent2();
}
if (event3 != 0) {
fnHandleEvent3();
}
}
}
SUch code don't assume any operating system and is build around polling for events to take place, which may be flagged from interrupt routines or by other events. The question is how to allow this code or may be several similar modules which are constructed like this to operate in a way that all receive processing rources and all can live along side the uTasker demo project (or similar) with its TCP/IP resouces?
The answer is in this case thankfully very simple. Since it is based on polling anyway we simply add each module to a polling task:
void fnMyNewTask(TTASKTABLE *ptrTaskTable)
{
static iTaskState = 0;
if (iTaskState == 0) {
fnInitialiseSomeThings();
iTaskState = 1;
}
if (event1 != 0) {
fnHandleEvent1();
}
if (event2 != 0) {
fnHandleEvent2();
}
if (event3 != 0) {
fnHandleEvent3();
}
}
Now we have these modules and the complete demo project working along side each other.
6. If for some reason one of the modules needs to start delayed this can be achieved by defining the task characteristics accordingly:
{ "new task..",fnMyNewTask, NO_QUE, (DELAY_LIMIT)(5.10 * SEC), 0, UTASKER_STOP},
Now this task will be started after a delay of 5.1s. However it will only be started once but it can turn itself into a polling task by simply making the following change:
if (iTaskState == 0) {
fnInitialiseSomeThings();
iTaskState = 1;
uTaskerStateChange(OWN_TASK, UTASKER_GO); // switch to polling mode of operation
}
7. If the polling rate of the task, which is presently as fast as the task list is being worked through, is not needed to be so fast it could be set to a user-defined rate by changing the task definition again (and removing the uTaskerStateChange(OWN_TASK, UTASKER_GO); command):
{ "new task..",fnMyNewTask, NO_QUE, (DELAY_LIMIT)(5.10 * SEC), (DELAY_LIMIT)(0.10 * SEC), UTASKER_STOP},
Now the first time the task is scheduled is after 5.1s and afterwards it will be scheduled (polled) every 100ms.
8. The polling is in fact using a monostable task time which was created according to the task configuration. The task still has no input queue, but this if OK because the monostable task timer wakes the task for scheduling without needing to place any messages into its queue. The disadvantage of this is simply that it is only possible to have a single timer event. If the different event should be polled at different rates we could do this by using a different technique. (Note that when using GLOBAL_TIMER_TASK support a single task can also have several timers associated to it - this is described in the corresponding document!).
{ "new task..",fnMyNewTask, SMALL_QUEUE, (DELAY_LIMIT)(5.10 * SEC), 0, UTASKER_STOP},
void fnMyNewTask(TTASKTABLE *ptrTaskTable)
{
static iTaskState = 0;
QUEUE_HANDLE PortIDInternal = ptrTaskTable->TaskID; // queue ID for task input
unsigned char ucInputMessage[SMALL_QUEUE]; // reserve space for receiving messages
if (iTaskState == 0) {
fnInitialiseSomeThings();
iTaskState = 1;
uTaskerMonoTimer( OWN_TASK, (DELAY_LIMIT)(1.5*SEC), E_TIMER_EVENT1 );
}
while ( fnRead( PortIDInternal, ucInputMessage, HEADER_LENGTH )) { // check input queue
switch ( ucInputMessage[ MSG_SOURCE_TASK ] ) { // switch depending on message source
case TIMER_EVENT:
switch (ucInputMessage[ MSG_TIMER_EVENT ]) {
case E_TIMER_EVENT1:
fnHandleEvent1();
uTaskerMonoTimer( OWN_TASK, (DELAY_LIMIT)(0.1*SEC), E_TIMER_EVENT2 );
break;
case E_TIMER_EVENT2:
fnHandleEvent2();
uTaskerMonoTimer( OWN_TASK, (DELAY_LIMIT)(0.05*SEC), E_TIMER_EVENT3 );
break;
case E_TIMER_EVENT3:
fnHandleEvent3();
uTaskerMonoTimer( OWN_TASK, (DELAY_LIMIT)(1.5*SEC), E_TIMER_EVENT1 );
break;
default:
break;
}
break;
default;
break;
}
}
}
Now the task is controlling its own delays on an event basis [1.5s, 100ms, 50ms]. It needs now an input queue so that it can distinguish between the timer events. Its task definition still has a 5.1s delay before it is first scheduled and this causes it to also to be defined with the appropriate timer resources.
9. If there is no start delay required, the following can be used:
{ "new task..",fnMyNewTask, SMALL_QUEUE, (DELAY_LIMIT)(NO_DELAY_RESERVE_MONO), 0, UTASKER_ACTIVATE},
This will cause it to immediately be scheduled once at start. It is however created with the required timer resources which it will use later.
Using these simple techniques quite interesting things can already be performed.
In the next part of this series of examples I will show how the operating system support and its drivers can enable a more modular software design of a project which is easier to maintain and expand. These features will allow the simple reconstruction of projects which are using less efficient polling techniques to become fully event driven. Tasks will only operate when there is really work to do.
Regards
Mark