|
|
Tasks
|
Defines |
| #define | interrupt_store_t uint8_t |
| | A flag indicating whether interrupts were enabled - used by disable_interrupts() and restore_interrupts().
|
Functions |
| task_t * | reserve_task (uint16_t stacklen, uint8_t pri, mutex_t *memory_mutex) |
| | Tasks are kept in a linked list in memory - this function reserves an "empty" task on that list, ready to be subsequently utilised by a call to create_task().
|
| task_t * | create_task (void(*proc)(void *), void(*cleanup)(), void *init_data, uint16_t stacklen, uint8_t pri, mutex_t *memory_mutex) |
| | Create a task, ready to be run.
|
| yavrtos_result_t | stop_task (task_t *t, uint8_t wait_for_mutexes) |
| | Stop a task.
|
| void | yield () __attribute__((naked)) |
| | Stop executing the current task and try and execute a higher-priority task or another task of the same priority.
|
| void | task_switcher_start (void(*idle)(void *), void *idle_data, uint16_t idle_stacklen, uint16_t system_stacklen) __attribute__((naked)) |
| | Start the whole process running.
|
| interrupt_store_t | disable_interrupts () |
| | Disable interrupts system-wide.
|
| void | restore_interrupts (interrupt_store_t interrupts) |
| | Restore the state of the system-wide interrupts.
|
Variables |
| task_t * | current_task |
| | The current task.
|
Detailed Description
A task performs a function in the application, and an application can have a number of tasks running "simultaneously". Tasks are a function with the signature void task_func(void *init), and optionally a "cleanup" function void task_cleanup() that is called when the task is exiting. The value of the init argument to the task function is specified when the task is created.
Tasks are created by create_task(), and can be stopped by stop_task(). Tasks are held in memory in a linked list - spaces may be reserved on that list for future tasks using reserve_task().
Tasks can suspend themselves when they are unable to do any work (e.g. a task that reads a microcontroller peripheral could suspend itself when the peripheral has no data available), hence allowing other tasks that can do work to run, and hence the application as a whole can get the maximum use of the CPU's processing power. The ways a task can suspend itself are
Every task has a priority. The rule is that the RTOS will not allow the lowest-priority tasks to run until all higher-priority tasks have suspended themselves. If there are more than one highest-priority not-suspended tasks, then the RTOS will switch between them once every tick interrupt (and/or whenever one of the tasks calls yield()).
A priority of zero indicates an "idle" task - a task that is run when there are no other tasks that can run. There must always be at least one idle task, and idle tasks can neither be stopped nor can they do anything to suspend themselves (e.g. they cannot wait for a mutex to be released), as the microcontroller needs at least one task to be always available for scheduling. Usually there is just one idle task that just sleeps the CPU. A recommended idle task is void idle_task(void *p) {
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_enable();
sei();
sleep_cpu();
}
A task switch can occur
- whenever anything happens to a mutex, semaphore or mailbox that may cancel the suspension of a higher priority task (e.g. when a semaphore has its value increased)
- when a task calls yield()
- when the system tick interrupt happens - see Interrupt Service Routines for more information about the tick interrupt.
From the point of view of the RTOS, the microcontroller can be doing one of three things
- executing code before the RTOS has been started with a call to task_switcher_start(). When the microcontroller is in this state, the RTOS is obviously unable to "suspend" what the microcontroller is doing, and hence none of the API calls that could cause such a suspension may be used
- executing a task, which can be an "idle" task of zero priority, or a "non-idle" task of non-zero priority. When executing an idle task, a task suspension is disallowed (as there must always be something that the microcontroller can do when it is otherwise unable to do anything).
- executing an ISR, including the tick interrupt ISR. Again, the RTOS is unable to suspend the ISR.
Therefore there are restrictions on when certain API calls may be made - these restrictions are summarised on the api usage restrictions page.
See the example application for an example of how to start and run some simple tasks.
Tasks can be stopped by other tasks or by ISRs - see stop_task(). When a task is stopped, mutexes and mailboxes are released - see task_stopper()
Define Documentation
| #define interrupt_store_t uint8_t |
Function Documentation
| task_t* create_task |
( |
void(*)(void *) |
proc, |
|
|
void(*)() |
cleanup, |
|
|
void * |
init_data, |
|
|
uint16_t |
stacklen, |
|
|
uint8_t |
pri, |
|
|
mutex_t * |
memory_mutex | |
|
) |
| | |
Create a task, ready to be run.
- Attention:
- create_task() can call malloc(), and disables interrupts during any call to malloc(). Therefore, depending on the amount of time your malloc() algorithm takes, and depending on what your time margin for the launch of ISRs is, create_task() could disable interrupts for "too long". See the description of the
memory_mutex argument, and How do I use malloc() and free() safely?
The arguments are
-
proc - this is the task function. Upon entry, interrupts will be enabled, and the value of the argument will be that of init_data. This function does not need to contain an infinite loop - if it returns (and if the priority is greater than zero), a stop will be performed on the task.
-
cleanup - this is the task cleanup function, which will be called as the task is dying. May be null if the task doesn't need to clean up after itself. See task_stopper()
-
init_data - this is the value that will be given in the argument to proc when it starts
-
stacklen - the number of bytes to reserve for the task stack. Note that it must be long enough to hold a copy of the value of all 32 registers when a task switch occurs, and it needs a couple of extra bytes to handle interrupts, function calls etc.
-
pri - the priority of the task. The highest-priority available task will "hog" the CPU. If the task has a priority of zero (an "idle" task), then it may not suspend itself on a mutex, semaphore or mailbox, and it may not be stopped.
-
memory_mutex - if not null, and if called from within a non-idle task, this mutex will be locked on to during the call to malloc(), otherwise interrupts will be disabled during the call to malloc() - see How do I use malloc() and free() safely?
This function will scan through the task list in memory, looking for one that is not being used with a matching pri and a stacklen that is at least as large as that required. If it cannot find such a task, it will create a brand new one. See reserve_task()
| interrupt_store_t disable_interrupts |
( |
|
) |
|
Disable interrupts system-wide.
Returns a value which is non-zero if interrupts were enabled. Can be called from anywhere.
| task_t* reserve_task |
( |
uint16_t |
stacklen, |
|
|
uint8_t |
pri, |
|
|
mutex_t * |
memory_mutex | |
|
) |
| | |
Tasks are kept in a linked list in memory - this function reserves an "empty" task on that list, ready to be subsequently utilised by a call to create_task().
- Attention:
- reserve_task() calls malloc(), and disables interrupts during the call to malloc(). Therefore, depending on the amount of time your malloc() algorithm takes, and depending on what your time margin for the launch of ISRs is, reserve_task() could disable interrupts for "too long". See the description of the
memory_mutex argument, and How do I use malloc() and free() safely?
The arguments are
stacklen - the number of bytes to reserve for the task stack. Note that it must be long enough to hold a copy of the value of all 32 registers when a task switch occurs, and it needs a couple of extra bytes to handle interrupts, function calls etc.
pri - the priority of the task. The highest-priority available task will "hog" the CPU. If the task has a priority of zero (an "idle" task), then it may not suspend itself on a mutex, semaphore or mailbox.
memory_mutex - if not null, and if called from within a non-idle task, this mutex will be locked on to during the call to malloc(), otherwise interrupts will be disabled during the call to malloc() - see How do I use malloc() and free() safely?.
| void restore_interrupts |
( |
interrupt_store_t |
interrupts |
) |
|
Restore the state of the system-wide interrupts.
A non-zero argument enables interrupts. Can be called from anywhere.
Stop a task.
The arguments are
t the task to stop
wait_for_mutexes - if not zero, the task will not be stopped until it has released all of its mutexes. If zero, then the tasks will stop immediately. Note that if you are using a memory mutex, you must set wait_for_mutexes when stopping any task that uses the memory mutex - see How do I use malloc() and free() safely?
This function can be called by the current task (stop_task(current_task, ...)), or by a higher-priority task, or by an ISR, and may be called on any non-zero-priority task.
If this function is called by the current task, and wait_for_mutexes isn't set, then the "cleaning up" of the task (task_stopper()) will start executing immediately - i.e. the stop_task(current_task, 0) call won't "return". If wait_for_mutexes is set, then the lock_off() that releases the tasks' last mutex won't return - the task_stopper() will run instead.
If this function is called by a higher priority task, then it will not return until the task in question has completely stopped executing.
If this function is called from within an ISR, it will return immediately.
The possible return values are
Note that this function does not support timeouts while waiting for a task to stop.
See task_stopper() for a description of what happens to the task that is being stopped.
| void task_switcher_start |
( |
void(*)(void *) |
idle, |
|
|
void * |
idle_data, |
|
|
uint16_t |
idle_stacklen, |
|
|
uint16_t |
system_stacklen | |
|
) |
| | |
Start the whole process running.
Note that this method will never return. The arguments are
idle - the "idle" function (the function to execute when we have nothing else to do)
idle_data - the value of the argument to idle when it starts
idle_stacklen - the length of the stack for the "idle" function - see create_task()
system_stacklen - the length of the system stack - this is the stack that will be in use during all ISRs
A recommended idle task is - void idle_task() {
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_enable();
sei();
sleep_cpu();
}
See the example application for an example of how to start the task switcher.
Note that, if you are using the TASK_ISR() macro to give your ISRs access to the system stack (and you should!), then all interrupts must remain disabled right up until this function is called. Interrupts will be enabled as soon as the first task switch starts.
Stop executing the current task and try and execute a higher-priority task or another task of the same priority.
Note that all API calls that could theoretically cause a higher-priority task to be re-enabled will call yield(), which will cause an automatic and immediate task switch to that higher-priority task. (This also means that if there is another task of the same priority that hasn't been disabled, all such API calls will cause a task switch to that task).
Obviously this function can only be called by tasks, and it will "return" the next time it is the turn of the current task to execute.
Variable Documentation
The current task.
This value may be used in stop_task() to stop the current task
|