Embedded System Tutorial FreeRTOS
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.
Contents
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.
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.
- 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
- 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)
- 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.
- 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.
- 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;
}