Embedded System Tutorial FreeRTOS

From Embedded Systems Learning Academy
Jump to: navigation, search

The objective of this assignment is to show you how to create a FreeRTOS task a few different ways. The FreeRTOS Tutorial is definitely a must read before going through this lesson.

FreeRTOS "Hello World" Task

A task just needs memory for its stack and an infinite loop. To prevent "hogging" the CPU, you should use a delay such that the CPU can be allocated to other tasks. Here is the simplest FreeRTOS task:

void hello_world_task(void* p)
{
    while(1) {
        puts("Hello World!");
        vTaskDelay(1000);
    }
}

int main()
{
    xTaskCreate(hello_world_task, (signed char*)"task_name", STACK_BYTES(2048), 0, 1, 0);
    vTaskStartScheduler();

    return -1;
}


C++ based FreeRTOS task

As a project gets more complex, it becomes difficult to manage initialization and share queue or semaphore handles. This was the motivation to create a C++ based FreeRTOS task.

Share "Objects"

A task can "share" its pointers, handles, or "objects" with another task by name. This way, we don't have to worry about declaring handles into a common header file, hence we do not plague the global namespace :) See the next examples on how a task can share a handle with another task by an intuitive string name.

C++ Task

/// IDs used for getSharedObject() and addSharedObject()
typedef enum {
   shared_SensorQueueId,
} sharedHandleId_t;

/// Orientation type enumeration
typedef enum {
    invalid,
    left,
    right,
} orientation_t;

class orient_compute : public scheduler_task
{
    public:
        orient_compute(uint8_t priority) : scheduler_task("compute", 2048, priority)
        {
            /* We save the queue handle by using addSharedObject() */
            QueueHandle_t my_queue = xQueueCreate(1, sizeof(orientation_t));
            addSharedObject(shared_SensorQueueId, my_queue);
        }

        bool run(void *p)
        {
            /* Compute orientation here, and send it to the queue once a second */
            orientation_t orientation = invalid;
            xQueueSend(getSharedObject(shared_SensorQueueId), &orientation, portMAX_DELAY);

            vTaskDelay(1000);
            return true;
        }
};

class orient_process : public scheduler_task
{
    public:
        orient_process (uint8_t priority) : scheduler_task("process", 2048, priority)
        {
            /* Nothing to init */
        }

        bool run(void *p)
        {
            /* We first get the queue handle the other task added using addSharedObject() */
            orientation_t orientation = invalid;
            QueueHandle_t qid = getSharedObject(shared_SensorQueueId);

            /* Sleep the task forever until an item is available in the queue */
            if (xQueueReceive(qid, &orientation, portMAX_DELAY))
            {
            }

            return true;
        }
};

Note that a better design is to minimize the use of getSharedObject(). So it is recommended that the creator of the handle add the shared object in its init(), and other tasks can store the handle in their taskEntry() function.

Add the task in main()

int main()
{
    scheduler_add_task(new orient_compute(PRIORITY_LOW));
    scheduler_add_task(new orient_process(PRIORITY_LOW));
    scheduler_start();
    return 0;
}


Assignment

This assignment is based on SJ-One board, but you can alter the requirement to fit your own hardware.

  1. Create a task (task1) that computes the orientation of the board.
    You can use the acceleration sensor to figure out the orientation of the board
    Send the orientation enumeration, such as "up", "down", "left", "right" to a queue every 1 second
  2. Create another task (task2) that waits on the queued item
    If the orientation is left or right, light up the LEDs (otherwise turn them off)
  3. Note down the observations by doing the following:
    Print a message before and after sending the orientation to the queue
    Print a message after the second task receives an item from the queue
    Use the same priority for both tasks, and note down the order of the print-outs
    Use higher priority for the receiving task, and note down the order of the print-outs.
  4. Create a terminal command: "orientation on" and "orientation off"
    If orientation is commanded on, resume the task1, otherwise suspend it
    See code below on hints of how this command can get control of another task.
  5. Answer the following questions:
    What if you use ZERO block time while sending an item to the queue, will that make any difference?
    What is the purpose of the block time during xQueueReceive() ?
// At the terminal tasks taskEntry() function :
bool terminalTask::taskEntry()
{
    cp.addHandler(orientationCmd,  "orientation", "Two options: 'orientation on' or 'orientation off'");
}

// Somewhere else:
CMD_HANDLER_FUNC(orientationCmd)
{
    // Our parameter was the orientation tasks' pointer, but you may want to check for NULL pointer first.
    scheduler_task *compute = scheduler_task::getTaskPtrByName("compute");

    // You can use FreeRTOS API or the wrapper resume() or suspend() methods
    if (cmdParams == "on") {
        vTaskResume(compute->getTaskHandle());  // Can also use: compute->resume();
    }
    else {
        vTaskSuspend(compute->getTaskHandle()); // Can also use: compute->suspend();
    }
    
    return true;
}