blob: 4ed3690e15869659b2e2dfa3ea9d5af19581bf33 [file] [log] [blame]
Tasks and Priority Management
=============================
**Target Platform: Arduino M0 Pro** (or legacy Arduino Zero or Zero Pro,
but not Arduino M0)
This lesson is designed to teach core OS concepts and strategies
encountered when building applications using Mynewt. Specifically, this
lesson will cover tasks, simple multitasking, and priority management
running on an Arduino M0 Pro.
.. contents::
:local:
:depth: 2
Prerequisites
-------------
Before starting, you should read about Mynewt in the
:doc:`Introduction <../../index>` section and
complete the
:doc:`QuickStart <../../get_started/index>`
guide and the
:doc:`Blinky <../blinky/arduino_zero>`
tutorial. Furthermore, it may be helpful to take a peek at the :doc:`task
documentation <../../../os/core_os/task/task>` for
additional insights.
Equipment
---------
You will need the following equipment:
- Arduino M0 Pro (or legacy Arduino Zero or Zero Pro, but not Arduino
M0)
- Computer with Mynewt installed
- USB to Micro USB Cable
Build Your Application
----------------------
To save time, we will simply modify the Blinky application. Well add
the Task Management code to the Blinky application. Follow the :doc:`Arduino
Zero Blinky
tutorial <../blinky/arduino_zero>` to
create a new project and build your bootloader and application. Finally,
build and load the application to your Arduino to verify that everything
is in order. Now lets get started!
Default Main Task
-----------------
During Mynewt system startup, Mynewt creates a default main task and
executes the application ``main()`` function in the context of this
task. The main task priority defaults to 127 and can be configured with
the ``OS_MAIN_TASK_PRIO`` system configuration setting.
The blinky application only has the ``main`` task. The ``main()``
function executes an infinite loop that toggles the led and sleeps for
one second.
Create a New Task
-----------------
The purpose of this section is to give an introduction to the important
aspects of tasks and how to properly initialize them. First, lets
define a second task called ``work_task`` in main.c (located in
apps/blinky/src):
.. code-block:: c
struct os_task work_task;
A task is represented by the
:doc:`os_task <../../../core_os/task/task>`
struct which will hold the tasks information (name, state, priority,
etc.). A task is made up of two main elements, a task function (also
known as a task handler) and a task stack.
Next, lets take a look at what is required to initialize our new task.
Task Stack
~~~~~~~~~~
The task stack is an array of type ``os_stack_t`` which holds the
program stack frames. Mynewt gives us the ability to set the stack size
for a task giving the application developer room to optimize memory
usage. Since were not short on memory, our ``work_stack`` is plenty
large for the purpose of this lesson. Notice that the elements in our
task stack are of type ``os_stack_t`` which are generally 32 bits,
making our entire stack 1024 Bytes.
.. code-block:: c
#define WORK_STACK_SIZE OS_STACK_ALIGN(256)
Note: The ``OS_STACK_ALIGN`` macro is used to align the stack based on
the hardware architecture.
Task Function
~~~~~~~~~~~~~
A task function is essentially an infinite loop that waits for some
event to wake it up. In general, the task function is where the
majority of work is done by a task. Lets write a task function for
``work_task`` called ``work_task_handler()``:
.. code-block:: c
void
work_task_handler(void *arg)
{
struct os_task *t;
g_led_pin = LED_BLINK_PIN;
hal_gpio_init_out(g_led_pin, 1);
while (1) {
t = os_sched_get_current_task();
assert(t->t_func == work_task_handler);
/* Do work... */
}
}
The task function is called when the task is initially put into the
*running* state by the scheduler. We use an infinite loop to ensure that
the task function never returns. Our assertion that the current tasks
handler is the same as our task handler is for illustration purposes
only and does not need to be in most task functions.
Task Priority
~~~~~~~~~~~~~
As a preemptive, multitasking RTOS, Mynewt decides which tasks to run
based on which has a higher priority; the highest priority being 0 and
the lowest 255. Thus, before initializing our task, we must choose a
priority defined as a macro variable.
Lets set the priority of ``work_task`` to 0, because everyone knows
that work is more important than blinking.
.. code-block:: c
#define WORK_TASK_PRIO (0)
Initialization
~~~~~~~~~~~~~~
To initialize a new task we use ``os_task_init()``
which takes a number of arguments including our new task function,
stack, and priority.
Add the ``init_tasks()`` function to initialize ``work_task`` to keep
our main function clean.
.. code-block:: c
int
init_tasks(void)
{
/* … */
os_stack_t *work_stack;
work_stack = malloc(sizeof(os_stack_t)*WORK_STACK_SIZE);
assert(work_stack);
os_task_init(&work_task, "work", work_task_handler, NULL,
WORK_TASK_PRIO, OS_WAIT_FOREVER, work_stack,
WORK_STACK_SIZE);
return 0;
}
Add the call to ``init_tasks()`` in ``main()`` before the ``while``
loop:
.. code-block:: c
int
main(int argc, char **argv)
{
...
/* Initialize the work task */
init_tasks();
while (1) {
...
}
}
And thats it! Now run your application using the newt run command.
.. code-block:: console
$ newt run arduino_blinky 0.0.0
When GDB appears press C then Enter to continue and *wait, why
doesnt our LED blink anymore?*
Review
^^^^^^
Before we run our new app, lets review what we need in
order to create a task. This is a general case for a new task called
mytask:
**1)** Define a new task, task stack, and priority:
.. code-block:: c
/* My Task */
struct os_task mytask
/* My Task Stack */
#define MYTASK_STACK_SIZE OS_STACK_ALIGN(256)
os_stack_t mytask_stack[MYTASK_STACK_SIZE];
/* My Task Priority */
#define MYTASK_PRIO (0)
**2)** Define task function:
.. code-block:: c
void
mytask_handler(void *arg)
{
while (1) {
/* ... */
}
}
**3)** Initialize the task:
.. code-block:: c
os_task_init(&mytask, "mytask", mytask_handler, NULL,
MYTASK_PRIO, OS_WAIT_FOREVER, mytask_stack,
MYTASK_STACK_SIZE);
Task Priority, Preempting, and Context Switching
------------------------------------------------
A preemptive RTOS is one in which a higher priority task that is *ready
to run* will preempt (i.etake the place of) the lower priority task
which is *running*. When a lower priority task is preempted by a higher
priority task, the lower priority tasks context data (stack pointer,
registers, etc.) is saved and the new task is switched in.
In our example, ``work_task`` (priority 0) has a higher priority than
the ``main`` task (priority 127). Since ``work_task`` is never put into
a *sleep* state, it holds the processor focus on its context.
Lets give ``work_task`` a delay and some simulated work to keep it
busy. The delay is measured in os ticks and the actual number of ticks
per second is dependent on the board. We multiply ``OS_TICKS_PER_SEC``,
which is defined in the MCU, by the number of seconds we wish to delay.
.. code-block:: c
void
work_task_handler(void *arg)
{
struct os_task *t;
g_led_pin = LED_BLINK_PIN;
hal_gpio_init_out(g_led_pin, 1);
while (1) {
t = os_sched_get_current_t:ask();
assert(t->t_func == work_task_handler);
/* Do work... */
int i;
for(i = 0; i < 1000000; ++i) {
/* Simulate doing a noticeable amount of work */
hal_gpio_write(g_led_pin, 1);
}
os_time_delay(3 * OS_TICKS_PER_SEC);
}
}
In order to notice the LED changing, modify the time delay in
``main()`` to blink at a higher frequency.
.. code-block:: c
os_time_delay(OS_TICKS_PER_SEC/10);
Before we run the app, lets predict the behavior. With the newest
additions to ``work_task_handler()``, our first action will be to sleep
for three seconds. This allows the ``main`` task, running ``main()``, to
take over the CPU and blink to its hearts content. After three seconds,
``work_task`` will wake up and be made *ready to run*. This causes it to
preempt the ``main`` task. The LED will then remain lit for a short
period while ``work_task`` loops, then blink again for another three
seconds while ``work_task`` sleeps.
You should see that our prediction was correct!
Priority Management Considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When projects grow in scope, from blinking LEDs into more sophisticated
applications, the number of tasks needed increases alongside complexity.
It remains important, then, that each of our tasks is capable of doing
its work within a reasonable amount of time.
Some tasks, such as the Shell task, execute quickly and require almost
instantaneous response. Therefore, the Shell task should be given a high
priority. On the other hand, tasks which may be communicating over a
network, or processing data, should be given a low priority in order to
not hog the CPU.
The diagram below shows the different scheduling patterns we would
expect when we set the ``work_task`` priority higher and lower than the
``main`` task priority.
.. figure:: ../pics/task_lesson.png
:alt: Task Scheduling
Task Scheduling
In the second case where the ``main`` task has a higher priority,
``work_task`` runs and executes work when the ``main`` task sleeps,
saving us idle time compared to the first case.
**Note:** Defining the same priority for two tasks fires an assert in
``os_task_init()`` and must be avoided. Priority 127 is reserved for main
task, 255 for idle task.