μT-Kernel 3.0 and FreeRTOS

Detailed look at the specification of FreeRTOS from the viewpoint of μT-Kernel 3.0

Porting techniques of modules developed on FreeRTOS for μT-Kernel programmers

When you select a real-time OS (RTOS) for device control, the latest version of μT-Kernel 3.0, which inherits the features from the ITRON specification OS series, has a high market share and conforms to the international standard IEEE 2050-2018, is the first choice. Meanwhile, FreeRTOS, which Amazon acquired from Real Time Engineer, also has become a noticeable candidate.

This document describes the technical and conceptual differences between the two RTOSs as prerequisite knowledge for porting a module developed on FreeRTOS to μT-Kernel 3.0.

In this case study, μT-Kernel 3.0 is installed on Infineon’s evaluation board CY8CKIT-062S2-43012. The goal is to port the Wi-Fi sample app provided by Infineon on top of it. This sample app included FreeRTOS. As a result, this document discusses how the code created for FreeRTOS can be rewritten to run on μT-Kernel 3.0.

FreeRTOS -> μT-Kernel 3.0

Now, we have to change the underlying FreeRTOS to μT-Kernel 3.0 for the application. How can we do that?

Upon investigation, we found a directory named COMPONENT_FREERTOS/ in which functions needed by applications, drivers, and libraries were implemented using the FreeRTOS API.
In the end, we found it is necessary to create a parallel directory, COMPONENT_MTKERNEL3/, next to it, and implement the necessary features (functions) in μT-Kernel 3.0.

Both μT-Kernel 3.0 and FreeRTOS are pre-emptive multitasking RTOSs, but there are many minor differences between them.

1. Comparison: Difference between μT-Kernel 3.0 and FreeRTOS

Since the purpose of this project is to “replace FreeRTOS (API) with μT-Kernel 3.0 (API) used in cypress application,” features that exist in FreeRTOS but not in μT-Kernel 3.0 need to be handled specially. For the purpose of confirming which features not in μT-Kernel 3.0 need to be supplanted by other features, a comparison of the features of the two OSs based on the features of FreeRTOS is shown below.

FeatureFreeRTOSμT-Kernel 3.0Note
Task CreationCreate and delete tasks
Task ControlWaiting for execution, suspend/resume, etc.
Task UtilitiesBasic feature is the same.
RTOS Kernel Control
Direct To Task NotificationsFeatures similar to tk_slp_tsk/tk_wup_tsk
QueuesN/ASimilar feature can be implemented with message buffers
Queue SetsN/AExtended feature of Queue
Stream BuffersN/A
Message BuffersFreeRTOS is based on Stream Buffers, so we need to inherit/emulate the restrictions.
Semaphore / MutexesμT-Kernel 3.0. does not have recursive mutex.
Software TimersCyclic handlers and alarm handlers
Event Groups (or ‘flags’)Event flag
FreeRTOS-MPU SpecificN/A
Co-routinesN/ANon-pre-emptive multitasking

The feature rows in the table above were created based on FreeRTOS API Reference. The following table is a continuation of the table based on μT-Kernel 3.0 features that are not included in the comparison table above.
(There are more items, but only the main features are picked up.)

FeatureFreeRTOSμT-Kernel 3.0Note
Task exception handlingN/ASpecification only. Not implemented in μT-Kernel 3.0.
MailboxN/A
RendezvousN/ANot in the specification of μT-Kernel 3.0. However, it is included in the implementation.
Fixed-size memory poolN/A
Interrupt managementN/A
System managementN/A
Subsystem managementN/ASpecification only. Not implemented in μT-Kernel 3.0.
Debug supportN/A(*Note)

(*Note: FreeRTOS provides APIs such as vTaskList() to obtain a list of tasks, but there are no such APIs for queues, semaphores, message buffers, etc., and the features to reference objects are not sufficient and uniform.
For this reason, vTaskList() is considered to be a simple task-specific utility rather than a debugging support feature, and is therefore marked as “N/A.”  

For the first half of the comparison table, the following should also be considered.

  • Queues and Queue Sets, and Stream Buffers and Message Buffers
    Although Queues and Queue Sets are separated, they can be regarded as one item.
    Although Stream Buffers and Message Buffers are separate, Message Buffers use Stream Buffers, so they could be combined as a single feature.
    Then the number of N/A in μT-Kernel 3.0 will inevitably decrease.
  • Semaphore and Mutexes
    Aren’t Semaphore/Mutexes separate features?
    Treated separately, Semaphore is also checked as ✔ in μT-Kernel 3.0.
  • Queue can be replaced by message buffer.
    Since μT-Kernel 3.0 does not have queue, the value here is N/A.
    However, this feature can be emulated similarly using mailbox or message buffer.
    Moreover, while FreeRTOS Message Buffers have restrictions because they are based on Stream Buffers, μT-Kernel 3.0’s message buffers do not have such restrictions.
  • Co-routines
    Co-routines are a feature for non-pre-emptive multitasking and are not actively used in RTOSs that claim to be “pre-emptive.” In other words, ✔ or N/A for this item is meaningless.

In the end, a comparison chart should be considered only as a superficial quick reference.
If there is a feature that you “want to use” for an application, it would be better to compare the specifications of both OSs in detail focusing on such a feature.

When replacing features of applications created using FreeRTOS with those created using μT-Kernel 3.0, “detailed differences not shown in a comparison chart” are very important.
Detailed feature-by-feature comparison of μT-Kernel 3.0 and FreeRTOS follows.

Some features (which should be provided to the Wi-Fi application) are neither in FreeRTOS nor in μT-Kernel 3.0. Each of these features is replaced by another similar feature. If another feature is used instead, the APIs and features of the original OS cannot be compared based on our experience, so no explanation is provided.

2. Files and features that were compared

In ModusToolbox, functions required by applications, drivers and libraries are grouped in files under COMPONENT_FREERTOS/. A set of files for μT-Kernel 3.0 under COMPONENT_MTKERNEL3/ were created by referencing the original files.
The actual files created are as follows.

  • mtb_shared/abstraction-rtos/release-v1.5.0/source/COMPONENT_MTKERNEL3/cyabs_rtos_mtkernel3.c
  • mtb_shared/abstraction-rtos/include/COMPONENT_MTKERNEL3/cyabs_rtos_impl.h
  • mtb_shared/wifi-mw-core/release-v3.4.0/lwip-whd-port/COMPONENT_MTKERNEL3/arch/sys_arch.c
  • mtb_shared/wifi-mw-core/release-v3.4.0/lwip-whd-port/COMPONENT_MTKERNEL3/arch/cc.h
  • mtb_shared/wifi-mw-core/release-v3.4.0/lwip-whd-port/COMPONENT_MTKERNEL3/arch/sys_arch.h
  • mtb_shared/clib-support/release-v1.2.0/COMPONENT_MTKERNEL3/cy_mutex_pool.c

※ Although there were multiple revisions of each implementation, this time COMPONENT_MTKERNEL3/ was placed alongside COMPONENT_FREERTOS/ used in the Wi-Fi TCP Client sample.

Additional Note: Meaning of each implementation:
There are different release-vX.Y.Z directories, where X, Y, Z are numbers for reach revision.

For example, we have the following files.

  • mtb_shared/abstraction-rtos/release-v1.5.0/source/COMPONENT_FREERTOS/cyabs_rtos_freertos.c
  • mtb_shared/abstraction-rtos/release-v1.6.0/source/COMPONENT_FREERTOS/cyabs_rtos_freertos.c

The content of cyabs_rtos_freertos.c is different for each implementation. Each “implementation” refers to the content of the files for each “-vX.Y.Z” subdirectory.

A summary of each file is as follows.

(1) cyabs_rtos_mtkernel3.c  

It seems to be a wrapper to generalize RTOS functions and provide them to ModusToolbox sample applications.
This file uses the following features of μT-Kernel 3.0 and FreeRTOS, and the differences therein are explained.

In addition, queue is also provided, but since μT-Kernel 3.0 does not include queue feature, message buffer is used instead. Queue is omitted in the comparison because it is implemented using different features in FreeRTOS and μT-Kernel 3.0.

(2) sys_arch.c

This file is used to provide mutual exclusion mechanisms, etc. for lwIP, and the following features are used.

  • Mutex
  • Semaphore
  • Mailbox

A feature not included in cyabs_rtos_mtkernel3.c is the mailbox.
I would like to compare the differences in mailbox feature, but FreeRTOS does not have mailbox (queue is used instead). For this reason, it is not possible to compare features and individual APIs, so these are also omitted.

(3) cy_mutex_pool.c

This provides clib with RTOS feature, and it uses mutex. The explanation of the differences in mutexes has already been given and it is omitted here.

(4)  Header file

The porting work also requires the addition of header files, but since these are not directly related to the explanation of the differences between μT-Kernel 3.0 and FreeRTOS, which is the main focus of this article, header files explanation is omitted.

3. Feature-by-feature comparison

cyabs_rtos_mtkernel3.c contains a collection of wrapper functions to generalize RTOS functions and provide them to ModusToolbox sample applications.

The following features are implemented.

The following describes the APIs of each OS used to implement each feature and the differences between them.

*Queue is not compared.
FreeRTOS provides queue feature, so queue is implemented by using it.
Since μT-Kernel 3.0 does not have queue feature, message buffer is used instead.
Since there are no directly corresponding features and individual API comparisons are not possible, explanation of queue feature is omitted.

3-1. Task control

Create and start task

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_create_thread()xTaskCreateStatic()tk_cre_tsk(), tk_sta_tsk()

Create a task and start execution.
The similar feature can be realized by replacing xTaskCreateStatic() with tk_cre_tsk() and tk_sta_tsk().

In FreeRTOS, xTaskCreateStatic() creates and executes the task in one go.
Since it says “Create,” I thought it was only for creation, but it is not. Indeed, looking at the task state transition diagram of FreeRTOS, there is no “dormant state (DORMANT)” (of μT-Kernel 3.0), so it will be in READY state (or RUNNING state) if created. For this reason, inside the wrapper function, the same behavior is implemented on μT-Kernel 3.0 by starting a task by tk_sta_tsk() after creating the task by tk_cre_tsk().

Figure 1 FreeRTOS task state transition diagram
Figure 2 Task state transition diagram of μT-Kernel

In FreeRTOS, memory for tasks was allocated with cy_rtos_create_thread() and passed to the API as a stack. It is necessary to adjust the timing of releasing this memory area, so a somewhat cumbersome processing is involved, and the following APIs are also used.

  • pvPortMalloc() … Allocate memory
  • xSemaphoreCreateBinary() … Create a binary semaphore

Since μT-Kernel does not require such processing, it only needs to call tk_cre_tsk() and tk_sta_tsk().
Although μT-Kernel also allows the application to specify the task stack area, this feature is not used under ordinary circumstances.
FreeRTOS also has xTaskCreate(), which does not require the application to preallocate memory.

Exit task

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_exit_thread()vTaskDelete()tk_exd_tsk()

Task termination and deletion is executed by vTaskDelete() on FreeRTOS and tk_exd_tsk() on μT-Kernel 3.0.
However, FreeRTOS also allocates memory with cy_rtos_create_thread(), as explained in the “create and start task” section, so this memory needs to be released. Moreover, it seems that the timing of the release needs to be adjusted, and a slightly cumbersome processing is added. (I suspect this will be unnecessary if xTaskCreate() is used instead.)

On the other hand, on μT-Kernel 3.0, a task can be terminated and deleted by calling only tk_exd_tsk().
In μT-Kernel 3.0, it is also possible to retain tasks without deleting them (see the task state transition diagram).
If a task is to be put into DORMANT state without deleting it, it is terminated with tk_ext_tsk(). In this case, the task can be started again (executed from the beginning of the task) with tk_sta_tsk().

  • tk_ext_tsk() … Exit task (exit)
  • tk_exd_tsk() … Exit and delete task (exit & delete)

Terminate task

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_terminate_thread()vTaskDelete()tk_ter_tsk(),tk_del_tsk()

FreeRTOS does not have the feature to terminate a task forcibly, so it is deleted with vTaskDelete(). As mentioned earlier, when terminating a task, the memory allocated in the library must be released by the application.

Since μT-Kernel 3.0 does not require such processing, it only needs to call tk_ter_tsk() and tk_del_tsk() in succession.
Naturally, with μT-Kernel 3.0, it is possible to terminate a task and then restart it if it is not deleted. However, since it is necessary to make the application behave the same as the FreeRTOS version, the task is deleted.

Note that it is not recommended to use tk_ter_tsk() in principle on μT-Kernel 3.0.
Forcibly terminating a task may cause a system malfunction although it is not limited to μT-Kernel 3.0 alone.
For example, the task does not have a chance to release allocated memory or other kernel objects.
However, in practice, there are cases where it is necessary to terminate a task forcibly, so such an API is also provided.

Check the running state of the task

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_is_thread_running()eTaskGetState()tk_ref_tsk()

Determine whether the specified task is running or not.
In FreeRTOS, the checking if a task is running is implemented with eTaskGetState(), but μT-Kernel 3.0 does not have such a single-purpose API, so checking is implemented using tk_ref_tsk(), which can obtain various information of a task at once.

Get Task Identifier

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_get_thread_handle()xTaskGetCurrentTaskHandle()tk_get_tid()

These APIs were simply swapped as is since there are similar APIs supported by both OSs.

Task Wait and Release Task Wait

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_wait_thread_notification()ulTaskNotifyTake()tk_slp_tsk(), tk_can_wup()
cy_rtos_set_thread_notification()xTaskNotifyGive(),
vTaskNotifyGiveFromISR(),
portEND_SWITCHING_ISR()
tk_wup_tsk()

This is a feature to wait for and release waiting tasks.
FreeRTOS seems to have various APIs (and macros) for this feature, but the implementation uses only the simplest APIs.
The basic correspondence between FreeRTOS API and μT-Kernel 3.0 API is as follows.

FeatureFreeRTOSμT-Kernel 3.0
WaitulTaskNotifyTake()tk_slp_tsk()
Release waitxTaskNotifyGive()tk_wup_tsk()

The major difference between FreeRTOS and μT-Kernel 3.0 regarding this feature is whether queuing is used for wait release requests. In μT-Kernel 3.0, the number of wait release requests is added (incremented) when tk_wup_tsk() is called with no task waiting. In tk_slp_tsk(), if the number of wait release requests is 1 or more, it will not enter wait (decrement and continue processing), so until the number of wait release requests reaches 0, it will not enter wait state even after issuing tk_slp_tsk(). On the other hand, FreeRTOS does not queue requests to release waits (from 0 to 1, but not 2 or more).
For this reason, a simple-minded implementation, i.e., replacement of APIs, would have resulted in a difference in behavior between the two. There is a chance that the wait may happen in unexpected places. So the implementation under μT-Kernel 3.0 clears the number of queuing wake-up requests after the wait by tk_slp_tsk() is released. By doing this (clearing the queue of wait requests), we maintain the make-believe situation where wait requests are no longer queued in µT-Kernel 3.0. This makes the behavior close to that of task wait and wakeup behavior of FreeRTOS.

By the way, FreeRTOS has separate APIs to be called from tasks and interrupt handlers (ISRs). The API to be called from ISR has a name suffixed with FromISR(), and the API with such a name must be called from within ISRs. For this reason, cy_rtos_wait_thread_notification() has an argument that allows the caller to specify whether the calling context is a task or an ISR.
On the other hand, the same API can be used by both tasks and ISRs (called “task independent portion” in μT-Kernel 3.0) in μT-Kernel 3.0. μT-Kernel 3.0 determines whether an API can be called or not in a particular context, and an error is returned if it cannot be called. Furthermore, in principle, if an API does not cause wait, it can be called from the “task independent portion,” and there is no need to be aware of the caller’s context when the API for wait release is used, as in FreeRTOS.

Incidentally, FreeRTOS also has a feature in the task notification feature to put the task that issues the wait release API into the wait state when the task on the other side is not in the wait state. Although μT-Kernel 3.0’s tk_slp_tsk() / tk_wup_tsk() do not have such a feature, a similar behavior can be realized with a message buffer created with a buffer size of 0.

Delay Task

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_delay_milliseconds()vTaskDelay()tk_dly_tsk()

Change the task to the wait state for the specified time. Features are the same on both OSs.

However, for FreeRTOS API, the wait time needs to be converted to ticks. Macros for this are available, but these are somewhat cumbersome.
Moreover, the following is written on page 62 of “Mastering the FreeRTOS Real Time Kernel.”

Note: It is not recommended to specify times in ticks directly within the application, but instead to use the pdMS_TO_TICKS() macro to specify times in milliseconds, and in so doing, ensuring times specified within the application do not change if the tick frequency is changed.

The author of the paragraph above wrote this as they understood the disadvantages of specifying a timeout in ticks. If so, I feel that they should specify “milliseconds for timeout” in the specification. It can be said that ITRON specification OS and μT-Kernel are the ones which brought this required processing to the specification level (or decided on it as a specification).

3-2. Mutex

Create mutex

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_init_mutex2()xSemaphoreCreateMutex(),
xSemaphoreCreateRecursiveMutex()
tk_cre_mtx()

This function creates a mutex.
In FreeRTOS, both (normal) mutexes and recursive mutexes are available.
On the other hand, μT-Kernel 3.0 has (normal) mutexes but no recursive mutexes. (If necessary, this would be implemented using a combination of normal mutexes, tk_get_tid() to obtain the task ID, counter variables, etc.).

The only sample I have currently installed (using FreeRTOS) is the Wi-Fi application, but if you check inside this application code, cy_rtos_init_mutex2() is called to create a recursive mutex. However, the application-side implementation only uses a simple combination of cy_rtos_get_mutex() and cy_rtos_set_mutex(), and no recursive mutexes seems to be required for real.

In addition, there are differences in the mechanisms to prevent priority inversion. While only priority inheritance protocols are available for FreeRTOS mutexes, μT-Kernel 3.0 supports both priority inheritance protocols and priority ceiling protocols.

Mutex featureFreeRTOSμT-Kernel 3.0
(Normal) mutex
Recursive mutexN/A
Priority inheritance protocol
Priority ceiling protocolN/A

Delete, lock and unlock mutex

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_deinit_mutex()vSemaphoreDelete()tk_del_mtx()
cy_rtos_get_mutex()xSemaphoreTake(),
xSemaphoreTakeRecursive()
tk_loc_mtx()
cy_rtos_set_mutex()xSemaphoreGive(),
xSemaphoreGiveRecursive()
tk_unl_mtx()

Porting is not an issue here, since the corresponding API is available for each of the features under the two OSs.
However, FreeRTOS requires a distinction between calling contexts (calling a function that has a name without FromISR or calling a function that has a name with FromISR), which makes implementation cumbersome when preparing such a wrapper function.

3-3. Semaphore

As for semaphores, APIs are available for each, so they can be ported without problem.
However, in FreeRTOS, it is still cumbersome in that “call context must be distinguished” and “wait time must be specified in ticks. (This is probably because I get used to μT-Kernel.)

Create and delete semaphores

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_init_semaphore()xSemaphoreCreateCounting()tk_cre_sem()
cy_rtos_deinit_semaphore()vSemaphoreDelete()tk_del_sem()

These create and delete semaphores.
Both OSs can create binary semaphores and counting semaphores.
However, μT-Kernel 3.0 allows users to select not only FIFO but also task priority order as the task wait release policy, which expands the range of semaphore applications. On the other hand, FreeRTOS has only FIFO as its wait release policy.

Wait on and release semaphores

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_get_semaphore()xSemaphoreTake(),
xSemaphoreTakeFromISR()
tk_wai_sem()
cy_rtos_set_semaphore()xSemaphoreGive(),
xSemaphoreGiveFromISR(),
portYIELD_FROM_ISR()
tk_sig_sem()

The feature of wait on and release semaphores is the same under both OSs.
However, FreeRTOS limits the number of resources that can be operated to 1, even for counting semaphores.
In μT-Kernel 3.0, an arbitrary number of resources can be specified, and it is possible to acquire/release multiple resources at once.

In FreeRTOS, semaphore resource acquisition can also be called from the task independent part when no wait occurs.
A dedicated API xSemaphoreGiveFromISR() is available to acquire resources if they are available (if not, the API terminates immediately with an error code).
On the other hand, in the μT-Kernel 3.0 implementation, API tk_wai_sem() for semaphore resource acquisition cannot be issued from the task independent part. Even if TMO_POL, which does not generate a wait, is specified for the timeout, an E_CTX error will occur.

Since the API in μT-Kernel 3.0 can check the counts, it is possible to check whether semaphore resources are available and change the behavior. However, even in this case, resource operation (decrementing) is not possible, so the behavior will not be the same as in FreeRTOS. For this reason, a call from the task-independent portion of the wrapper function is simply treated as an error.

Now, there is actually a bigger difference than the above two.
In FreeRTOS, if task switching is required as a result of controlling semaphores from within an interrupt handler, the application developer must explicitly call portYIELD_FROM_ISR().
If this API is not called, task switching does not occur when returning to the task context from the interrupt handler, and the task is switched only at the next time the task switch is explicitly called. This is the FreeRTOS specification, and similar processing is required for other Communication Objects as well.  
In μT-Kernel 3.0, such additional processing is completely unnecessary, and the proper task is automatically dispatched when the interrupt handler is exited. This is called “delayed dispatching.”
This specification and behavior are explained in “2.5.2 Task-Independent Portion and Quasi-Task Portion” of “μT-Kernel 3.0 Specification.
Caution: For those who are familiar with T-Kernel and ITRON specification OS, this is the normal behavior, and if you start using FreeRTOS with this assumption of automatic delayed processing, there is a high risk of creating bugs that are quite difficult to detect. (It sometimes works under different timing conditions …)  
I would think that the OS would know whether the dispatch of a different task becomes necessary or not due to semaphore operation, so it should automatically switch the tasks when the ISR returns, but FreeRTOS does not do that. It may mean that the developer can control the timing of the switch so that unnecessary processing does not occur.
I understand that there are advantages to this specification, but I personally do not agree with it because it seems to leave a room for creating unnecessary problems.

3-4. Event flag and event group

Create event flag

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_init_event()xEventGroupCreate()tk_cre_flg()

The “event group” of FreeRTOS corresponds to the “event flag” of μT-Kernel 3.0. So the explanation is given under the title of “Event flag.”

Although the event flag is a similar feature to event group, μT-Kernel 3.0 and FreeRTOS have the following differences.

Features of event flagFreeRTOSμT-Kernel 3.0
Number of bits of flagSelect 8 bits or 24 bitsDepends on target system (unsigned int)
Queuing of waiting tasksFIFO onlyFIFO or priority order can be selected
Number of waiting tasksMultiple tasks can waitOne or several can be selected

For μT-Kernel 3.0, the number of bits in the flag is automatically determined by the bit width of the CPU (used language system) since it is specified as unsigned int type. This is a consideration to ensure efficient processing of event flags.
On the other hand, FreeRTOS allows either 8 or 24 bits, depending on the configUSE_16_BIT_TICKS setting. configUSE_16_BIT_TICKS is a macro to define the TickType_t type, but the number of bits seems to depend on it for some mysterious reasons.

typedef TickType_t           EventBits_t;

TickType_t is defined as 16 bits or 32 bits according to configUSE_16_BIT_TICKS as follows in FreeRTOS source code.

#if ( configUSE_16_BIT_TICKS == 1 )
        typedef uint16_t     TickType_t;
#else
        typedef uint32_t     TickType_t;
#endif

It may be the same as μT-Kernel 3.0 uses unsigned int type for event flag, but I think we should include another wrapper macro to hide the direct reference to configUSE_16_BIT_TICKS.

Delete event flag

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_deinit_event()vEventGroupDelete()tk_del_flg()

Both OSs have the feature to delete event flags, and there is no difference between them.

Set event flag

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_setbits_event()xEventGroupSetBits(),
xEventGroupSetBitsFromISR()
tk_set_flg()

The feature of setting the specified bit is the same for both OSs.
However, FreeRTOS has different APIs for flag setting depending on the context to be invoked, so the context must be distinguished on the application. In μT-Kernel 3.0, the same API to set flags is used irrespective of the caller’s context.

As explained in the semaphore section, in FreeRTOS, if task switching is required as a result of manipulating event flag from within an interrupt handler, the developer must explicitly call portYIELD_FROM_ISR(). This should also be noted.

*For some reason, the original (FreeRTOS) cy_rtos_setbits_event() did not include a call to portYIELD_FROM_ISR(). I wonder why.

Clear event flag

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_clearbits_event()xEventGroupClearBits(),
xEventGroupClearBitsFromISR()
tk_clr_flg()

The feature of clearing the bits of an event is the same for both OSs.
However, the value of event flag to be cleared is specified by 1 bit value in the corresponding argument position in FreeRTOS, and the value of event flag to be cleared is specified by 0 bit value in corresponding argument position in μT-Kernel 3.0 (logical ‘AND’ operation). Neither can be said to be good or bad as a specification, but it is a subtle point that requires attention during development as well as porting.

Check event flag

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_getbits_event()xEventGroupGetBits()tk_ref_flg()

Obtain the current event flag bit. Features are the same on both OSs.

Wait for event flag

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_waitbits_event()xEventGroupWaitBits()tk_wai_flg()

Wait for the specified bit to become 1. If multiple wait bits are specified, both OSs can specify whether to wait for all bits to be set or only one of the bits to be set.
Both OSs also allow the API to specify whether or not to clear the bit(s) when the condition is satisfied.
However, FreeRTOS only allows the user to specify whether or not to clear the bit(s) specified as the wait condition, while μT-Kernel 3.0 also allows the user to clear all bits.

How to clear bitFreeRTOSμT-Kernel 3.0
Do not clear bit
Clear wait bit
Clear all bitsN/A

Note that the return value is a bit pattern in FreeRTOS (see below).

The value of the event group at the time either the event bits being waited for became set, or the block time expired.

Therefore, whether a timeout has occurred must be verified by comparing the return value with the bit pattern.

3-5. Timer

Since both OSs have alarm handler and cyclic handler, ✔ is placed in the feature-by-feature comparison table.
However, the timing of specifying startup time parameters in the alarm handler differs between μT-Kernel 3.0 and FreeRTOS.
The timing for specifying the startup time when the callback function (timer handler) is called is as follows.

Feature descriptionFreeRTOSμT-Kernel 3.0
Alarm handlerCreation timeAt start
Cyclic handlerCreation timeCreation time

If you are developing a new application, it may not be a big difference, but if you are porting it from one OS to the other, it may be quite cumbersome.

The context in which the timer handler is executed is also different.
The timer handler is executed as a task independent portion (interrupt handler) in μT-Kernel 3.0. Thus, it is basically guaranteed to be executed with priority over tasks.

In FreeRTOS, on the other hand, callback functions are invoked from a task dedicated to timers and executed in the task context. 

Timer callback functions execute in the context of the timer service task.

Note that the timer service task is treated similarly to other tasks, so the timer may not always run (even when the startup time comes) depending on its priority.
The task priority of the timer service task can be specified by configTIMER_TASK_PRIORITY, and on most systems, is defined as follows.
(Download the FreeRTOS source code from https://www.freertos.org/ to check.)

#define configTIMER_TASK_PRIORITY                  ( configMAX_PRIORITIES - 1 )

configMAX_PRIORITIES is the number of different task priorities in FreeRTOS. FreeRTOS has task priorities from 0:lowest to (configMAX_PRIORITIES-1):highest. In other words, the timer service task is set to run at the highest priority in most systems. However, it is possible to create tasks with the same priority, so the timer task may not have priority over all other tasks.
On the other hand, there were many configurations where configTIMER_TASK_PRIORITY was specified by an immediate value, not referencing configMAX_PRIORITIES. In that case, if we are not careful, we will probably see a lot of blocking of the timer service task. (There may be a valid reason behind such a setting, but I have not checked that far.)

Furthermore, in FreeRTOS, timer activation is achieved by sending a command to the queue of the timer service task. Therefore, if the queue is already full, the task that wanted to start the timer may have to wait, and a timeout error may occur “on starting the timer” even.
In μT-Kernel 3.0, timer operation requests do not cause errors due to accumulated requests.

Create timer

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_init_timer()xTimerCreate()tk_cre_alm(), tk_cre_cyc()

Create timer
μT-Kernel 3.0 has two features: alarm handler and cyclic handler. In FreeRTOS, they are grouped together as a single feature as a software timer, but you can specify whether they fire one-shot or repeatedly by specifying options. The one-shot timer corresponds to the alarm handler in μT-Kernel 3.0, and the repetitive timer corresponds to the cyclic handler.
The functions are almost the same, but the following differences require attention when implementing (porting).

  • μT-Kernel 3.0 allows specifying the cyclic handler to start immediately after its creation, or defer when it starts.
    The cyclic handler of μT-Kernel 3.0 can start the timer at the same time of creation. It is also possible to specify the startup time when the handler is first started. FreeRTOS does not have such a detailed configuration function.
  • FreeRTOS can change the startup time.
    FreeRTOS provides an API called xTimerChangePeriod() to change the timer startup time. The cyclic handler of μT-Kernel 3.0 requires the startup time to be specified at the time of creation, and it cannot be changed later.
    The alarm handler of μT-Kernel 3.0 specifies the timer startup time at the invocation time, so this API is not necessary.
  • Arguments of the callback function (timer handler) are different.
    In μT-Kernel 3.0, the extended information (void *exinf) specified at the time of creation is passed as an argument of the callback function (timer handler), while in FreeRTOS, a timer handle (variable of TimerHandle_t type) specified at the time of timer creation is passed.

Start a timer

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_start_timer()xTimerChangePeriod(), xTimerStart()tk_sta_alm(), tk_sta_cyc()

The feature of starting a timer is the same.
However, as explained in “Creating timers,” FreeRTOS sets the startup time at the start of the timer.
We can specify the startup time of the alarm handler of μT-Kernel 3.0 via the initial task API invocation, so the feature is on a par with FreeRTOS. On the other hand, the cyclic handler of μT-Kernel 3.0 requires the startup time to be specified at the time of creation. The parameter specified for startup time cannot be changed later.

As explained earlier, the timer feature of FreeRTOS is provided by the timer service task, and the API that controls the timer sends operation requests to this task via a command queue. For this reason, it should be noted that an error may occur when the queue fills up.

In contrast, μT-Kernel 3.0 allows the determination whether or not an error occurs with the return value, which simplifies processing when the control flow returns from the API. However, note that when an error occurs, the bits of the flag are not read out (they become indeterminate values), so you need to be careful if you would like to use bit patterns.

Stop timer

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_stop_timer()xTimerStop()tk_stp_alm(), tk_stp_cyc()

Stop the timer. Features are the same on both OSs.

Check the timer startup status

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_is_running_timer()xTimerIsTimerActive()tk_ref_alm(), tk_ref_cyc()

Obtain the startup status of the timer.
FreeRTOS has an API dedicated to this feature, but μT-Kernel 3.0 uses tk_ref_alm() and tk_ref_cyc() to collectively obtain various information on a timer such as startup status.

Delete timer

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_deinit_timer()xTimerDelete()tk_del_alm(), tk_del_cyc()

Delete the timer. Features are the same on both OSs.

In addition to the above, FreeRTOS provides various APIs for resetting timers, changing timer types, etc.
The following is the list of such APIs (API names with FromISR() suffix are omitted).

FreeRTOS API related to the timer
xTimerCreate()
xTimerCreateStatic()
xTimerIsTimerActive()
xTimerStart()
xTimerStop()
xTimerChangePeriod()
xTimerDelete()
xTimerReset()
pvTimerGetTimerID()
vTimerSetReloadMode()
vTimerSetTimerID()
xTimerGetTimerDaemonTaskHandle()
xTimerPendFunctionCall()
pcTimerGetName()
xTimerGetPeriod()
xTimerGetExpiryTime()
xTimerGetReloadMode()

Similar APIs in μT-Kernel 3.0 are as follows. This clearly shows how simple the API of μT-Kernel 3.0 is (API names with _u() suffix are omitted).

Cyclic handlerAlarm handlerFeature
tk_cre_cyctk_cre_almCreate
tk_del_cyctk_del_almDelete
tk_sta_cyctk_sta_almStart
tk_stp_cyctk_stp_almStop
tk_ref_cyctk_ref_almRefer

I do not think it is a bad thing to have many functions like FreeRTOS (I personally like to be able to control the kernel objects in detail), but I think it is a little difficult to grasp and use them all when there are so many API functions (especially because of the long function names).

Gets system operating time

Wrapper functionFreeRTOS APIμT-Kernel 3.0 API
cy_rtos_get_time()xTaskGetTickCount()tk_get_otm()

In the FreeRTOS API, time is processed by the number of ticks, so the elapsed time is obtained also as number of ticks. Therefore, the application must convert to milliseconds if necessary.
In μT-Kernel 3.0, this calculation is not necessary as the time to be acquired by the API is defined as milliseconds.

4. Other differences

The differences between μT-Kernel 3.0 and FreeRTOS that were confirmed during the component rewriting are as explained above.
There were many other differences, which are summarized below.

4-1. FreeRTOS includes round robin processing?!

The FreeRTSO user guide has the following explanation. 

This requires sharing processing time between tasks of equal priority if they are ready to run simultaneously.

What does “run simultaneously” mean? What does “sharing processing time between tasks of equal priority” mean?
The reference was found in the AWS documentation site, but I was not quite convinced, so I checked other sites that explain FreeRTOS, and some explain it as “round robin” and “time slicing.”
Now I’m sure that’s what they meant. In FreeRTOS, if there are multiple tasks in executable state, and if they have the same priority, they seem to be executed in a round-robin fashion.

Is this an appropriate specification as an RTOS?
As someone has used TRON RTOSs starting with ITRON specification OS, I do not understand it at all, but I guess there is a school of thought to “incorporate the round-robin method into the basic design of RTOSs” (?).
This was a shock to me because when explaining ITRON specification OS and T-Kernel, I have always explained that “A round-robin OS will not achieve real-time processing.”
Although it is possible to disable round robin scheduling in FreeRTOS configuration settings, round robin is enabled in the default settings, so the fundamental design philosophy of FreeRTOS must be different from that of μT-Kernel 3.0.

Incidentally, it is possible to implement round-robin-like scheduling feature in μT-Kernel as well.
The same behavior can be achieved by issuing tk_rot_rdq() with a cyclic handler. Alternatively, the task may explicitly issue tk_rot_rdq(). The method that uses tk_rot_rdq() allows switching tasks after some meaningful processing is completed, so the effect on running tasks can be minimized.

4-2. There is no fixed-size memory pool.

FreeRTOS does not have fixed-size memory pool.
In embedded devices, memory pools should probably use fixed-size for the following reasons.

  • It is simple, so acquisition and release operations are lightweight.
  • There is no risk of fragmentation.

Although I understand the desire to cut memory usage as much as possible in a system where RAM is scarce, variable-size memory pools can be quite wasteful when the management area is included in the total usage consideration. I guess it would be a trade-off in the end, but since RAM is quite large in modern embedded systems, wouldn’t it be okay for typical systems to use fixed-size memory pool even if it is somewhat large when it is reserved as a buffer?

4-3. OS boot method

FreeRTOS initially starts a user application starting from the main() function, which kicks the FreeRTOS scheduler to start the RTOS itself.
On the other hand, applications start with an initial task in μT-Kernel 3.0. Although the main() function is included therein, it is not shown to the application developer.
This is an issue of one’s taste. Neither is better than the other.
I am unimpressed at showing too much under the hood to developers of applications on RTOS, but for those who were first taught that “C programs start with the main() function,” it may be easier to understand that the RTOS seems to start with the main() function. This will be especially easy to understand at the stage of introducing the use of an OS to someone versed in a non-OS type development practice. However, the use of main() visible to the user seems redundant when one becomes accustomed to the use of an RTOS.

4-4. Different APIs for tasks and ISRs

As already explained, FreeRTOS has separate APIs for tasks and for Interrupt Service Routine (ISR). This is very difficult to use because of different degree of one’s familiarity to the separation. I feel that FreeRTOS leaves it to developers to deal with these cumbersome details.
In a sense, there is a high degree of freedom to the behavior of the system, but there are many precautions that must be taken, and this may eventually become a burden on the developers.

4-5. Initial semaphore count

Initial values can be specified in μT-Kernel 3.0 when creating semaphores.
In FreeRTOS, the initial value is fixed at 0. It is set by returning resources after creation.
In FreeRTOS, detailed APIs are provided separately, and in μT-Kernel 3.0, the feature is organized together consistently to some extent.
I think that μT-Kernel 3.0 is superior in terms of API understandability.

4-6. Data queues

μT-Kernel 3.0 has no data queue.
It can be substituted by μT-Kernel 3.0 message buffer or mailbox, but they do not have the function of inserting at the beginning. However, in mailboxes, it is possible to order messages by message priority by specifying the sort order attribute, and it would be possible to realize similar behavior, if not exactly the same, behavior.

4-7. Task priority

FreeRTOS has the lowest task priority designated by 0 and the highest priority by (configMAX_PRIORITIES-1). In μT-Kernel 3.0, 1 is the highest task priority and CNF_MAX_TSKPRI is the lowest priority.
I suppose this is a matter of familiarity, but wouldn’t it be easier to understand if we can refer to the highest priority by an immediate value to say, “the highest is 1?”

4-8. Return codes

In FreeRTOS, the identifier (or the return value) of the created object is not uniform.: it may be a structure (Queue, Semaphore, Mutex, …), void* (Message Buffer), or an integer value (Task).
For example, they are as follows.

  • The return value of xTaskCreate() is either pdPASS or errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY.
  • The return value of xTaskCreateStatic() is NULL on failure, and a non-NULL value on success.

FreeRTOS adds a character at the beginning of the API to indicate the return type (see below). This may be easy for someone reading the code to understand, but awkward to use for the implementor.

First characterType
pcchar*
pvvoid*
vvoid
uxUBaseType_t
xBaseType_t, TaskHandle_t, …

On the other hand, the following convention is used for member OS of the TRON OS family, such as μT-Kernel 3.0.

  • Return value < 0 … error
  • Return value ≧ 0 … normal

Since it is designed emphasizing education, μT-Kernel 3.0 is easier for the developers to work with.

4-9. Idle tasks

FreeRTOS automatically provides idle task.
The power-saving function seems to be implemented in the idle task, so it may be easy for developers to have this task automatically prepared by the FreeRTOS.
The power-saving function of μT-Kernel 3.0 is to be implemented in low_pow(). Since this function is called directly by the dispatcher when there are no more tasks to execute, there is no need to create an idle task to implement power saving.

4-10. Message buffer

Both FreeRTOS and μT-Kernel 3.0 have features of message buffer. However, the FreeRTOS message buffer is implemented using stream buffer, so it has the following restrictions.

IMPORTANT NOTE: Uniquely among FreeRTOS objects, the stream buffer implementation (so also the message buffer implementation, as message buffers are built on top of stream buffers) assumes there is only one task or interrupt that will write to the buffer (the writer), and only one task or interrupt that will read from the buffer (the reader). It is safe for the writer and reader to be different tasks or interrupts, but, unlike other FreeRTOS objects, it is not safe to have multiple different writers or multiple different readers. If there are to be multiple different writers then the application writer must place each call to a writing API function (such as xStreamBufferSend()) inside a critical section and use a send block time of 0. Likewise, if there are to be multiple different readers then the application writer must place each call to a reading API function (such as xStreamBufferReceive()) inside a critical section and use a receive block time of 0.

The gist of the message is as follows.

  • Since the message buffer is implemented using the stream buffer, its usage has the same restrictions as the stream buffer.
  • The stream buffer is implemented with the following assumptions.
    • There is only one Writer (task or interrupt that writes to the buffer).
    • There is only one Reader (task or interrupt that reads from the buffer).
  • There is no problem with Writer and Reader existing separately.
  • It is not safe to have multiple Writers or Readers.
  • If multiple Writers are needed, the API for writing should be placed in the critical section and implemented with zero send block time.
  • If multiple Readers are needed, the API for reading should be placed in the critical section and implemented with zero receive block time.

Since the API is provided for the message buffer feature, it would seem that the last two items should have been incorporated into the message buffer API, but this is not the case. (Is it because it would be useless if there is only one Writer and one Reader?)

This may be due to the basic design philosophy, but I feel that FreeRTOS has too many requests for application developers.
μT-Kernel 3.0 naturally has no such restrictions, so it can be used to develop applications without worrying about such problems.


The above explains the differences between μT-Kernel 3.0 and FreeRTOS that were encountered when the applications implemented using FreeRTOS were ported to use μT-Kernel 3.0.
There may be many other differences, but this is all I have verified so far.
I hope this will help you select the RTOSs and carry out porting to the selected RTOS.