FreeRTOS is a popular real-time operating system kernel for embedded devices, that has been ported to more than 35 microcontrollers. It is distributed under the GPL with an optional exception. The exception permits users' proprietary code to remain closed source while maintaining the kernel itself as open source, thereby facilitating the use of FreeRTOS in proprietary applications.
FreeRTOS is designed to be small and simple. The kernel itself consists of only three or four C files. To make the code readable, easy to port, and maintainable, it is written mostly in C, but there are a few assembly functions included where needed (mostly in architecture-specific scheduler routines).
FreeRTOS provides methods for multiple threads or tasks, mutexes, semaphores and software timers. A tick-less mode is provided for low power applications. Thread priorities are supported. In addition there are several schemes of memory.
OpenWSN Integration
In OpenWSN FreeRTOS has been encapsulate and hidden behind our very simple scheduler.h interface. In that way our code stays agnostic to the underlaying scheduler.
#ifndef __SCHEDULER_H #define __SCHEDULER_H /** \addtogroup kernel \{ \addtogroup Scheduler \{ */ #include "opendefs.h" //=========================== define ========================================== typedef enum { TASKPRIO_NONE = 0x00, // tasks trigger by the stack rx TASKPRIO_STACK_LOWMAC = 0x01, TASKPRIO_STACK_HIGHMAC = 0x02, TASKPRIO_STACK_6TOP = 0x03, TASKPRIO_STACK_IP = 0x04, TASKPRIO_STACK_ROUTING = 0x05, TASKPRIO_STACK_TRANSPORT = 0x06, // tasks going up the stack - sendone and timers TASKPRIO_SENDDONE_TIMERS_MAC = 0x07, TASKPRIO_SENDDONE_TIMERS_6TOP = 0x08, TASKPRIO_SENDDONE_TIMERS_IP = 0x09, TASKPRIO_SENDDONE_TIMERS_ROUTING = 0x0a, TASKPRIO_SENDDONE_TIMERS_TRANSPORT = 0x0b, //app tasks - down the stack TASKPRIO_APP_HIGH = 0x0c, TASKPRIO_APP_MED = 0x0d, TASKPRIO_APP_LOW = 0x0e, TASKPRIO_MAX = 0x0f, } task_prio_t; #define TASK_LIST_DEPTH 10 //=========================== typedef ========================================= typedef void (*task_cbt)(void); typedef struct task_llist_t { task_cbt cb; task_prio_t prio; void* next; uint16_t counter; } taskList_item_t; //=========================== module variables ================================ typedef struct { taskList_item_t taskBuf[TASK_LIST_DEPTH]; taskList_item_t* task_list; uint8_t numTasksCur; uint8_t numTasksMax; } scheduler_vars_t; typedef struct { uint8_t numTasksCur; uint8_t numTasksMax; } scheduler_dbg_t; //=========================== prototypes ====================================== void scheduler_init(void); void scheduler_start(void); void scheduler_push_task(task_cbt task_cb, task_prio_t prio); /** \} \} */ #endif
The implementation is still simple, the main idea is to have 3 FreeRTOS tasks handling the operation of the OpenWSN protocol stack.
- Rx Task: handles callbacks triggered by packets comming from the MAC layer and pushes them up. The usual case is when a packet is received at the Low MAC and this is pushed up the stack until it reaches the target layer (e.g ICMPv6). This task has the highest priority.
- Send Done and timers Task: Once an ACK from a sent packet is received, this confirms the succes on the packet transmission and this information is pushed up the stack for those tasks waiting that notification. Note that this is similar than the previous case but however we prefer to separate in another tasks with lowest priority than that of receiving a packet. This task also handles OpenWSN timer callbacks such RPL DIO timer or 6top maintenance tasks.
- App Task: This task is used to move packets down the stack. Basically applications or upper layers use it to send down the packet to the High MAC until it is inserted into the MAC layer queue. It has the lowest priority.
In OpenWSN, the MACROS DISABLE_INTERRUPTS and ENABLE_INTERRUPTS are used to protect critical sections. When using a RTOS, however, we do not want to disable the operation of the RTOS but instead we want some sort of mutual exclusion to disable task preemption. This is achieved by a global lock semaphore
#define INTERRUPT_DECLARATION() (rtos_sched_v.xStackLock != NULL ? (rtos_sched_v.xStackLock=rtos_sched_v.xStackLock) : ( rtos_sched_v.xStackLock = xSemaphoreCreateMutex())) #define DISABLE_INTERRUPTS() xSemaphoreTakeFromISR( (rtos_sched_v.xStackLock), &globalPriorityTaskWoken ) #define ENABLE_INTERRUPTS() xSemaphoreGiveFromISR( (rtos_sched_v.xStackLock),&globalPriorityTaskWoken )
The scheduler operation has been implemented as follows:
- Each task looks into the callback list for those callbacks that fall in its priority range (see task_prio_t enum). If a callback for that particular priority is found then the freertos task executes it and subsequently block in its sempahore until is triggered again by the scheduler. This trigger happens when a new callback for a certain priority is pushed into the list.
void scheduler_push_task(task_cbt cb, task_prio_t prio) { BaseType_t xHigherPriorityTaskWoken; xHigherPriorityTaskWoken = pdFALSE; //=== step 1. insert the task into the task list scheduler_push_task_internal(cb, prio); debugpins_slot_toggle(); //=== step 2. toggle the appropriate semaphore so the corresponding handler takes care of it if (prio < SCHEDULER_STACK_PRIO_BOUNDARY) { xSemaphoreGiveFromISR(rtos_sched_v.xRxSem, &xHigherPriorityTaskWoken); } else if (prio >= SCHEDULER_STACK_PRIO_BOUNDARY && prio < SCHEDULER_SENDDONETIMER_PRIO_BOUNDARY) { xSemaphoreGiveFromISR(rtos_sched_v.xSendDoneSem, &xHigherPriorityTaskWoken); } else if (prio >= SCHEDULER_SENDDONETIMER_PRIO_BOUNDARY && prio <= SCHEDULER_APP_PRIO_BOUNDARY //includes TASKPRIO_MAX ) { xSemaphoreGiveFromISR(rtos_sched_v.xAppSem, &xHigherPriorityTaskWoken); } else { leds_error_blink(); while (1) ; } portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }
As said the FreeRTOS tasks unlock when a callback is pushed and execute the task that it has been inserted.
/** \brief Handles application packets, brinding them down the stack until they are queued, ready for the lowwer MAC to consume. */ static void vAppTask(void* pvParameters) { taskList_item_t* pThisTask = NULL; xSemaphoreTake(rtos_sched_v.xAppSem, portMAX_DELAY); //take it for the first time so it blocks right after. while (1) { xSemaphoreTake(rtos_sched_v.xAppSem, portMAX_DELAY); debugpins_fsm_toggle(); scheduler_find_next_task_and_execute( SCHEDULER_SENDDONETIMER_PRIO_BOUNDARY, SCHEDULER_APP_PRIO_BOUNDARY, pThisTask); } }
Developing applications with FreeRTOS
One interesting thing to do once a RTOS is supported is to have some application level tasks that handle the application activity without constraining the OpenWSN activity. This can be easily achived by letting the application to create a FreeRTOS task.
TODO (WIP)