ユーシーテクノロジ株式会社

UCT — μT-Kernel 3.0 Migration Guide

μT-Kernel 3.0 と FreeRTOS

FreeRTOS からμT-Kernel 3.0 への移行を検討する開発者向け
機能比較と機能毎の移植要点

このページについて Overview

UCTがGitHubで公開しているInfineonのマイコン向けμT-Kernel 3.0を移植した時の経験をもとにしたチュートリアルです。

今回の事例では、InfineonのPSoC 6評価ボード(CY8CKIT-062S2-43012)にμT-Kernel 3.0を搭載し、InfineonがModusToolboxで提供しているWi-FiサンプルアプリをFreeRTOSからμT-Kernel 3.0に移植しました。サンプルアプリにはFreeRTOSが含まれていたため、FreeRTOS用に作られたコードをμT-Kernel 3.0版に書き換える作業が必要となりました。

本解説では、FreeRTOSベースで作られたモジュールをμT-Kernel 3.0上に移植する場合のノウハウとして、両RTOSの機能の差異やコンセプトの違いについて述べています。

目次

  1. 比較表:μT-Kernel 3.0 と FreeRTOS の違い
  2. 対象ファイルと比較対象機能
  3. 機能毎の比較
    1. タスク制御
    2. ミューテックス
    3. セマフォ
    4. イベントフラグ、イベントグループ
    5. タイマ
  4. その他の違い
    1. FreeRTOSにはラウンドロビンの処理が含まれている
    2. 資料は原則英語
    3. 固定長メモリプールがない
    4. OSの起動方式
    5. タスク用とISR用でAPIが異なる
    6. セマフォの初期値
    7. データキュー
    8. タスク優先度
    9. 戻り値
    10. アイドルタスク
    11. メッセージバッファ
1. 比較表:μT-Kernel 3.0 と FreeRTOS の違い Feature Comparison

1. 比較表:μT-Kernel 3.0 と FreeRTOS の違い Feature Comparison

今回の目的は「FreeRTOS(のAPI)をμT-Kernel 3.0(のAPI)に置き換える」ことなので、FreeRTOSにはあるがμT-Kernel 3.0にはない機能が問題となります。μT-Kernel 3.0にない機能は別の機能で置き換える必要があることを確認する目的でFreeRTOSの機能を元に両OSの機能を比較すると以下のようになります。

◯:対応あり △:一部対応または仕様のみ ×:対応なし(別途実装が必要)

機能 FreeRTOS μT-Kernel 3.0 備考
Task Creationタスク生成、削除
Task Control実行待ち、強制待ち/再開など
Task Utilities基本機能は同じ。
RTOS Kernel Control
Direct To Task Notificationstk_slp_tsk/tk_wup_tsk に類似の機能
Queues×メッセージバッファで類似の機能を実装可能
Queue Sets×Queue の拡張機能
Stream Buffers×
Message BuffersFreeRTOSはStream Buffersベースなのでその制限を引き継ぐ
Semaphore / MutexesμT-Kernel 3.0に再帰ミューテックスはない。
Software Timers周期ハンドラ、アラームハンドラ
Event Groups (or 'flags')イベントフラグ
FreeRTOS-MPU Specific×
Co-routines×非プリエンプティブ方式のマルチタスク

上の比較表に含まれていないμT-Kernel 3.0の機能をベースにして表の続きを作ると以下になります。

機能 FreeRTOS μT-Kernel 3.0 備考
タスク例外処理機能×μT-Kernel 3.0も仕様のみ。実装はされていない。
メールボックス×
ランデブ×仕様範囲外μT-Kernel 3.0では仕様範囲外。実装には含まれる。
固定長メモリプール×
割込み管理機能×
システム状態管理機能×
サブシステム管理機能×μT-Kernel 3.0も仕様のみ。実装はされていない。
デバッグサポート機能×(*注)
(*注) FreeRTOSにはタスクのリストを取得するvTaskList()といったAPIが用意されているが、キューやセマフォ、メッセージバッファなどに対しては、このようなAPIは用意されておらず、統一的な機能ではない。このため、vTaskList()はデバッグサポート機能ではなく単なるタスク専用のユーティリティであると判断して×にした。
比較表の位置づけ:比較表というのは参考程度のものと考えるべきである。アプリケーションとして「この機能を使いたい」という機能があれば、それを中心に考えて両OSの仕様を詳細に比較する方がよいだろう。
また、アプリケーションに提供すべき機能によってはFreeRTOSにない機能やμT-Kernel 3.0にない機能も存在する。このような機能についてはそれぞれ別の機能を使って置き換えているので、個別のAPIや機能の比較はできない。
2. 対象ファイルと比較対象機能 Porting Files

2. 対象ファイルと比較対象機能 Porting Files

ModusToolboxでは、アプリケーションやドライバ、ライブラリが必要としている関数がCOMPONENT_FREERTOS/ のディレクトリの中にまとめられています。それを参考にCOMPONENT_MTKERNEL3/ を作り、必要な機能(関数)をμT-Kernel 3.0で実装する必要があります。
実際に作成したファイルは以下のとおりです。

※各実装にはリビジョンが複数あったが、今回はWi-FiのTCP ClientサンプルでCOMPONENT_FREERTOS/ の並びにCOMPONENT_MTKERNEL3/ を配置した。

各ファイルの概要は以下のとおりである。

(1) cyabs_rtos_mtkernel3.c

RTOSの機能を汎用化してModusToolboxのサンプルアプリケーションに提供するためのラッパーらしい。このファイルではμT-Kernel 3.0とFreeRTOSの以下の機能を利用しているので、これらの差異について説明する。

この他に、キューも用意されているが、μT-Kernel 3.0にはキューが含まれていないので、メッセージバッファで代用した。FreeRTOSとμT-Kernel 3.0で別の機能を用いて実装しているのではキューという機能の比較にならないのでこの機能については省略する。

(2) sys_arch.c

lwIPに対して排他制御機構などを提供するためのファイルであり、以下の機能が利用されている。

cyabs_rtos_mtkernel3.c にない機能としてはメールボックスが含まれている。メールボックス機能の差異について比較したいところだが、FreeRTOSにはメールボックスがない(Queueで代用)。このため機能や個別のAPIの比較はできないのでこれも省略する。

(3) cy_mutex_pool.c

clib用に提供するRTOS機能であり、ミューテックスが利用されている。ミューテックスの差異については既に説明しているので、ここでは省略する。

(4) ヘッダファイル

移植作業ではヘッダファイルの追加も必要であるが、本記事の主眼であるμT-Kernel 3.0とFreeRTOSの差異の説明とは直接関係しないので、これらの説明は省略する。

3. 機能毎の比較 API Comparison

3. 機能毎の比較 API Comparison

cyabs_rtos_mtkernel3.c には、RTOSの機能を汎用化してModusToolboxのサンプルアプリケーションに提供するためのラッパー関数が集められている。以下、各機能を実装するために利用されている各OSのAPIとその違いについて説明する。

※キューは比較対象外
FreeRTOSにはキューの機能があるのでそれを利用して実装されている。μT-Kernel 3.0にはキューの機能がないので、メッセージバッファで代用している。直接対応する機能がなく、個別のAPIの比較はできないので説明は省略する。

3-1. タスク制御

タスクの生成と開始

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_create_thread()xTaskCreateStatic()tk_cre_tsk(), tk_sta_tsk()

タスクを生成し、実行を開始する。xTaskCreateStatic() を tk_cre_tsk() と tk_sta_tsk() に置き換えることで同じ機能を実現できる。

FreeRTOS では xTaskCreateStatic() で生成と実行が同時に行われる。"Create" とあるので、生成だけかと思ったがそうではなく、生成と同時に起動されている。確かにFreeRTOSのタスク状態遷移図を見ると、(μT-Kernel 3.0の)「休止状態(DORMANT)」がないので、生成すればREADY状態(またはRUNNING状態)になってしまう。このため、μT-Kernel 3.0では tk_cre_tsk() でタスクを生成した後で、tk_sta_tsk() によってタスクを開始することで同じ動作にしている。

FreeRTOSのタスク状態遷移図
図1 FreeRTOSのタスク状態遷移図
μT-Kernelのタスク状態遷移図
図2 μT-Kernelのタスク状態遷移図

なお、FreeRTOS では cy_rtos_create_thread() でタスク用のメモリを確保してスタックとしてAPIに渡していた。このメモリ領域を解放するタイミングを調整する必要があるのでやや面倒な処理が入っており、以下のAPIも合わせて利用されている。

  • pvPortMalloc() … メモリ確保
  • xSemaphoreCreateBinary() … バイナリセマフォの生成

μT-Kernel ではその必要はないので、単純に tk_cre_tsk() と tk_sta_tsk() を呼び出すだけで済んでいる。

タスクの終了

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_exit_thread()vTaskDelete()tk_exd_tsk()

タスクの終了と削除はFreeRTOSであれば vTaskDelete()、μT-Kernel 3.0であれば tk_exd_tsk() である。ただし、FreeRTOSの方はタスクの生成と開始で説明したとおり、cy_rtos_create_thread() でメモリの確保も行っているのでこのメモリの解放が必要となる。しかも、解放するタイミングの調整が必要らしく、若干面倒な処理が追加されていた。

一方、μT-Kernel 3.0では tk_exd_tsk() のみでタスクの終了と削除が実行できる。また、μT-Kernel 3.0ではタスクを削除せずに残しておくこともできる(タスク状態遷移図を参照)。タスクを削除せずに休止状態にする場合は tk_ext_tsk() で終了する。この場合、tk_sta_tsk() でタスクを再度起動(タスクの最初から実行)することができる。

  • tk_ext_tsk() … タスクの終了 (exit)
  • tk_exd_tsk() … タスクの終了と削除 (exit & delete)

タスクの強制終了

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_terminate_thread()vTaskDelete()tk_ter_tsk(), tk_del_tsk()

FreeRTOSにはタスクを強制終了(Terminate)する機能はないので、vTaskDelete()で削除している。また、先に述べたとおりタスクを終了する場合はライブラリ内で確保したメモリの解放処理が必要である。

μT-Kernel 3.0ではそのような処理は必要ないので、tk_ter_tsk() と tk_del_tsk() を連続して呼び出すだけで済む。当然、μT-Kernel 3.0であればタスクを強制終了するだけで削除しないでおけばタスクを再度起動させることも可能であるが、機能的にFreeRTOS版と同じ動作にする必要があるのでタスクの削除まで実行している。

μT-Kernel 3.0では tk_ter_tsk() は原則として使用しないことを推奨しているので注意が必要である。μT-Kernel 3.0に限った話ではないが、強制的にタスクを終了させるとシステムに異常をきたす可能性がある。単純には確保したメモリやカーネルオブジェクトを解放せずに残してしまうことがある。ただし、実際には強制的に終了しなければならない場合もあるので、このようなAPIも用意されている。

タスクの実行状態を確認

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_is_thread_running()eTaskGetState()tk_ref_tsk()

指定されたタスクが実行中かどうかを判定している。FreeRTOS では eTaskGetState() で実装するが、μT-Kernel 3.0ではそのような単機能なAPIは用意していないので、タスクの各種情報を一括で取得できる tk_ref_tsk() を用いて実装した。

タスク識別子の取得

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_get_thread_handle()xTaskGetCurrentTaskHandle()tk_get_tid()

このAPIは両OSで対応しているAPIがあるのでそのまま置き換えた。

タスクの待ちとタスクの待ち解除

ラッパー関数FreeRTOSの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()

タスクの待ちと待ち解除の機能である。FreeRTOSではこの機能のためにいろいろなAPI(やマクロ)が用意されているようだが、本機能はその中でも単純なAPIを利用して実装されていた。FreeRTOSとμT-Kernel 3.0のAPIの基本的な対応は以下になる。

機能FreeRTOSμT-Kernel 3.0
待ちulTaskNotifyTake()tk_slp_tsk()
待ち解除xTaskNotifyGive()tk_wup_tsk()

本機能でのFreeRTOSとμT-Kernel 3.0の大きな違いは待ち解除要求をキューイングするかどうかだろう。μT-Kernel 3.0では、タスクが待っていない状態でtk_wup_tsk()を呼び出すと待ち解除要求の数が追加(インクリメント)されていく。tk_slp_tsk()では、待ち解除要求の数が1以上であれば待ちに入らない(デクリメントして処理を継続)ので、待ち解除の要求数が0になるまでは、tk_slp_tsk()を発行しても待ちにはならない。一方、FreeRTOSでは待ち解除の要求はキューイングされない(0から1にはなるが、2以上にはならない)。このため、単純に実装すると両者の挙動に違いがでるので、μT-Kernel 3.0では tk_slp_tsk() の待ちが解除された後で起床要求のキューイング数をクリアする処理を追加してある。

ところで、FreeRTOSではタスクから呼び出すAPIと割込みハンドラ(ISR)から呼び出すAPIが別になっている。ISRから呼び出すAPIは 〜FromISR() という名称になっており、こちらを呼び出さなければならない。このため、cy_rtos_wait_thread_notification() には呼び出したコンテキストがタスクかISRかを呼び出し元に指定させるための引数が用意されている。一方、μT-Kernel 3.0ではタスクでもISR(μT-Kernel 3.0では「タスク独立部」と呼ぶ)でも同じAPIを利用可能である。呼び出してよいかどうかはRTOS側で判定し、呼び出せない場合はエラーが返される。しかも、原則として待ちが発生しないAPIであれば「タスク独立部」から呼出可能であり、FreeRTOSのように待ち解除用のAPIで呼び出し元のコンテキストを意識する必要はない。

タスクの遅延

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_delay_milliseconds()vTaskDelay()tk_dly_tsk()

タスクを指定された時間だけ待ち状態に移行させる。機能としては同じである。

ただし、FreeRTOSのAPIでは待ち時間をティックに変換する必要がある。マクロは用意されているが、これはいささか面倒である。しかも、"Mastering the FreeRTOS Real Time Kernel" のp.62には以下のように書かれている。

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.
(注:アプリケーション内で直接ティック単位で時間を指定することは推奨しません。その代わりに pdMS_TO_TICKS() マクロを使用してミリ秒単位で時間を指定することを推奨します。そうすることで、ティック周波数が変更されてもアプリケーションで指定された時間が変更されなくなります。)

ティックでタイムアウトを指定することのデメリットを理解しているからこのように書いているのであれば、「タイムアウトにはミリ秒を指定する」と仕様で決めてくれてもいいような気がする。この処理を仕様に持って行った(仕様として決めた)のがITRONであり、μT-Kernelであるとも言える。

3-2. ミューテックス

ミューテックスの生成

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_init_mutex2()xSemaphoreCreateMutex(),
xSemaphoreCreateRecursiveMutex()
tk_cre_mtx()

本関数ではミューテックスを生成する。FreeRTOS では(通常の)ミューテックスと再帰ミューテックスが利用可能である。一方のμT-Kernel 3.0には(通常の)ミューテックスはあるが、再帰ミューテックスはない。(必要であれば通常のミューテックスとタスクIDを取得するtk_get_tid()、カウンタ変数などを組み合わせて実装することになる。)

現在インストールしてある(FreeRTOSを利用している)サンプルはWi-Fiだけだが、このコードの中を確認すると cy_rtos_init_mutex2() は再帰的ミューテックスを生成する設定で呼び出されている。ただし、アプリケーション側の実装はシンプルな cy_rtos_get_mutex()〜cy_rtos_set_mutex() の組みになっており、再帰ミューテックスを必要としている箇所はないようであった。

この他に優先度逆転を防ぐための機構にも違いがある。FreeRTOSのミューテックスでは優先度継承プロトコルしか利用できないが、μT-Kernel 3.0は優先度継承プロトコルと優先度上限プロトコルに対応している。

ミューテックス機能FreeRTOSμT-Kernel 3.0
(通常の)ミューテックス
再帰ミューテックス×
優先度継承プロトコル
優先度上限プロトコル×

ミューテックスの削除、ロック、アンロック

ラッパー関数FreeRTOSの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()

これらはそれぞれに対応したAPIが用意されているので、特に問題なく移植できる。ただし、FreeRTOSでは呼び出しコンテキストの区別が必要(FromISRのない関数を呼ぶか、FromISRのある関数を呼ぶか)であり、このようなラッパーを用意する場合には実装が面倒になっている。

3-3. セマフォ

セマフォについてはそれぞれに対応したAPIが用意されているので、特に問題なく移植できる。ただし、FreeRTOSでは「呼び出しコンテキストの区別が必要」な点、「待ちの時間指定をティックで指定する」といった点がやはり面倒くさい。(このあたりはμT-Kernelに慣れているせいもあるのだろう。)

セマフォの生成、削除

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_init_semaphore()xSemaphoreCreateCounting()tk_cre_sem()
cy_rtos_deinit_semaphore()vSemaphoreDelete()tk_del_sem()

セマフォの生成、削除という意味では同じ機能である。両OS共にバイナリセマフォとカウンティングセマフォを生成可能である。ただし、μT-Kernel 3.0ではタスクの待ち解除方式としてFIFOだけでなくタスク優先度順も選択することができるので、セマフォの応用の幅が広がる。一方、FreeRTOSの待ち解除方式はFIFOのみである。

セマフォ資源の獲得、解放

ラッパー関数FreeRTOSの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()

セマフォ資源の獲得/解放という機能としては同じである。ただし、FreeRTOS ではカウンティングセマフォであっても操作できる資源数が1に制限されている。μT-Kernel 3.0では任意の資源数を指定可能であり、一度に複数個の資源を獲得/解放することも可能である。

さて、実は上記の2点よりも、大きな違いがある。FreeRTOSでは割込みハンドラでセマフォを制御した結果としてタスクの切り替えが必要になった場合、開発者が明示的に portYIELD_FROM_ISR() を呼び出さなければならない。このAPIを呼び出さない場合、割込みハンドラからタスクコンテキストに戻ってもタスク切り替えが発生せず、次回明示的にタスクスイッチが呼ばれた時にタスクが切り替わることになる。これはFreeRTOSの仕様であり、他のCommunication Objectでも同様の処理が必要となる。μT-Kernel 3.0ではこのような処理は一切不要であり、割込みハンドラを終了する時点で自動的にディスパッチされる仕様になっている。これを「遅延ディスパッチ(delayed dispatching)の原則」という。

※元の(FreeRTOSの)cy_rtos_setbits_event() には、なぜか portYIELD_FROM_ISR() を呼び出す処理が含まれていなかったが…。

3-4. イベントフラグ、イベントグループ

イベントフラグの生成

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_init_event()xEventGroupCreate()tk_cre_flg()

FreeRTOSの「イベントグループ」がμT-Kernel 3.0の「イベントフラグ」に相当する。ここでは「イベントフラグ」で説明する。

イベントフラグ自体は類似の機能ではあるが、μT-Kernel 3.0とFreeRTOSには以下の違いがある。

イベントフラグの機能FreeRTOSμT-Kernel 3.0
フラグのビット数8ビット or 24ビットを選択処理系に依存(unsigned int)
待ちタスクのキューイングFIFOのみFIFO、または優先度順を選択可能
待ちタスク数複数タスクの待ちが可能1個、または複数個を選択可能

フラグのビット数はμT-Kernel 3.0ではunsigned int型になっているので、CPUのビット幅(処理系)によって自動的に決まる。これは効率的に処理できるようにするための配慮である。一方のFreeRTOSでは configUSE_16_BIT_TICKS の設定によって使用可能なビット数が8ビットか24ビットとなる。

configUSE_16_BIT_TICKS はTickType_t型を定義するためのマクロだが、なぜかこれに依存するらしい。コードでは以下のように定義されており:

typedef TickType_t EventBits_t;

TickType_t はconfigUSE_16_BIT_TICKSに従って16ビット or 32ビットと定義されている:

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

イベントフラグの削除

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_deinit_event()vEventGroupDelete()tk_del_flg()

両OSともにイベントフラグを削除する機能であり、特に違いはない。

イベントフラグのセット

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_setbits_event()xEventGroupSetBits(),
xEventGroupSetBitsFromISR()
tk_set_flg()

指定されたビットをセットする機能としては両OSとも同じである。ただし、FreeRTOSではフラグのセットについても呼び出すコンテキストによってAPIが異なるので、アプリケーション側でコンテキストを区別しておかなければならない。μT-Kernel 3.0ではコンテキストによってフラグをセットするAPIを区別する必要はない。

なお、セマフォでも説明したとおり、FreeRTOSでは割込みハンドラでセマフォを制御した結果としてタスクの切り替えが必要になった場合、開発者が明示的に portYIELD_FROM_ISR() を呼び出さなければならない。この点も注意が必要である。

イベントフラグのクリア

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_clearbits_event()xEventGroupClearBits(),
xEventGroupClearBitsFromISR()
tk_clr_flg()

イベントのビットをクリアする機能としては両OSとも同じである。ただし、FreeRTOS では1のビットがクリアされ、μT-Kernel 3.0では0のビットがクリアされる。仕様としてどちらが良いとも悪いとも言えないが、移植だけでなく開発時にも注意が必要な点である。

イベントフラグの確認

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_getbits_event()xEventGroupGetBits()tk_ref_flg()

現在のイベントフラグのビットを取得する。機能としては同じである。

イベントフラグ待ち

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_waitbits_event()xEventGroupWaitBits()tk_wai_flg()

指定されたビットが1になるのを待つ。複数の待ちビットが指定されていた場合、全てのビットがセットされるのを待つか、いずれか1つのビットがセットされるのを待つのかを指定できる点も同じである。条件成立時にビットをクリアするか否かを指定できる点も同じである。
ただし、FreeRTOS では、待ち条件として指定されたビットをクリアするか否かを指定できるだけであるが、μT-Kernel 3.0では全ビットをクリアすることも可能である。

ビットクリア方法FreeRTOSμT-Kernel 3.0
ビットをクリアしない
待ちビットをクリアする
全ビットをクリアする×

また、FreeRTOS では戻り値がビットパターンになっている点(以下)も注意が必要である。

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

このため、タイムアウトが発生したかどうかは戻り値をビットパターンと比較して検証しなければならない。これに対してμT-Kernel 3.0では戻り値でエラーか否かを判定できる仕様になっているので、APIからリターンした時の処理が簡単になる。ただし、エラー発生時はフラグのビットが読み出されない(不定値となる)ので、ビットパターンを利用したい場合は注意が必要である。

3-5. タイマ

どちらもアラームハンドラと周期ハンドラの機能があるので、両OS方とも機能としては◯となる。ただし、アラームハンドラでは μT-Kernel 3.0 と FreeRTOS で時間のパラメータを指定するタイミングが異なる。コールバック関数(タイマハンドラ)を呼び出す時間を指定するタイミングは以下のとおりである。

機能FreeRTOSμT-Kernel 3.0
アラームハンドラ生成時開始時
周期ハンドラ生成時生成時

新規に開発するのであれば特に問題はないが、相互に移植するとなると結構面倒なことになるかもしれない。

また、タイマハンドラが実行されるコンテキストも異なる。μT-Kernel 3.0ではタスク独立部(割込みハンドラ)として実行される。このため、基本的にタスクよりも優先して実行されることが保証されている。一方、FreeRTOSではコールバック関数がタイマ専用のタスクから呼び出されてタスクコンテキストで実行される。タイマサービスタスクの優先度は configTIMER_TASK_PRIORITY で指定可能となっていて、多くのシステムは以下のように定義されている:

#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )

つまり、多くのシステムではタイマサービスタスクの優先度は最高に設定されている。ただし、同じ優先度のタスクを作ることも可能なので、必ずしも全てのタスクに優先されることにはならない。さらに、タイマの起動はこのタイマサービスタスクのキューに対してコマンドを送ることで実現しているため、キューがいっぱいになるとエラーが発生することもあるので注意が必要である。

タイマの生成

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_init_timer()xTimerCreate()tk_cre_alm(), tk_cre_cyc()

タイマを生成する。μT-Kernel 3.0にはアラームハンドラと周期ハンドラの2つの機能がある。FreeRTOSではソフトウェアタイマとして1つの機能としてまとめられているが、オプションの指定でワンショットか繰り返して動作するかを指定できる。ワンショットのタイマがμT-Kernel 3.0のアラームハンドラ、繰り返しタイマが周期ハンドラに相当する。機能としてはほぼ同じだが、以下のような違いがあるので実装(移植)の際には注意が必要である。

  • μT-Kernel 3.0では生成直後の起動、周期ハンドラ起動タイミングの指定が可能:μT-Kernel 3.0の周期ハンドラでは生成と同時にタイマを起動することもできる。また、周期ハンドラを最初に起動する時間を指定することも可能である。FreeRTOSには、このような細かい設定をする機能はない。
  • FreeRTOSでは起動時刻の変更が可能:FreeRTOSには、タイマの時間を変更するための xTimerChangePeriod() というAPIが用意されている。μT-Kernel 3.0の周期ハンドラでは生成時に起動時刻を指定する必要があり、後からは変更はできない。なお、μT-Kernel 3.0のアラームハンドラは開始時に時間を指定するので、このAPIは必要ない。
  • コールバック関数(タイマハンドラ)の引数が異なる:μT-Kernel 3.0では生成時に指定した拡張情報(void *exinf)がコールバック関数(タイマハンドラ)の引数として渡されるが、FreeRTOSではタイマ生成時に決定されるタイマハンドル(TimerHandle_t型の変数)が渡される。

タイマの開始

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_start_timer()xTimerChangePeriod(), xTimerStart()tk_sta_alm(), tk_sta_cyc()

タイマを開始するという機能としては同じである。ただし、「タイマの生成」で説明したとおり、FreeRTOSではタイマの開始時に時間を設定する。μT-Kernel 3.0のアラームハンドラは起動時間をAPI指定することができるので機能としては同じになる。一方、μT-Kernel 3.0の周期ハンドラでは起動時に時間を変更することはできない(生成時に指定する)。

また、先に説明したとおりFreeRTOSのタイマ機能は、タイマサービスタスクによって提供されており、タイマを制御するAPIは、このタスクにコマンドキューを介して操作リクエストを送信している。このため、キューがいっぱいになるとエラーが発生することもあるので注意が必要である。

タイマの停止

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_stop_timer()xTimerStop()tk_stp_alm(), tk_stp_cyc()

タイマを停止する。機能としては同じである。

タイマの起動状態の確認

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_is_running_timer()xTimerIsTimerActive()tk_ref_alm(), tk_ref_cyc()

タイマの起動状態を取得する。FreeRTOSにはこの機能専用のAPIがあるが、μT-Kernel 3.0では tk_ref_alm()、tk_ref_cyc() によって起動状態を含めたタイマの各種情報を一括して取得する。

タイマの削除

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_deinit_timer()xTimerDelete()tk_del_alm(), tk_del_cyc()

タイマを削除する。機能としては同じである。

FreeRTOS には上記の他に、タイマのリセット、タイマ種類の変更などのいろいろなAPIが用意されている。並べてみるとこんな感じである(〜FromISR() は省略):

FreeRTOSのタイマ関連のAPI
xTimerCreate()
xTimerCreateStatic()
xTimerIsTimerActive()
xTimerStart()
xTimerStop()
xTimerChangePeriod()
xTimerDelete()
xTimerReset()
pvTimerGetTimerID()
vTimerSetReloadMode()
vTimerSetTimerID()
xTimerGetTimerDaemonTaskHandle()
xTimerPendFunctionCall()
pcTimerGetName()
xTimerGetPeriod()
xTimerGetExpiryTime()
xTimerGetReloadMode()

一方のμT-Kernel 3.0では以下のとおりであり、μT-Kernel 3.0のAPIがいかにシンプルかがよくわかる(〜_u() は省略):

周期ハンドラアラームハンドラ機能
tk_cre_cyctk_cre_alm生成
tk_del_cyctk_del_alm削除
tk_sta_cyctk_sta_alm開始
tk_stp_cyctk_stp_alm停止
tk_ref_cyctk_ref_alm参照

システム稼働時間の取得

ラッパー関数FreeRTOSのAPIμT-Kernel 3.0のAPI
cy_rtos_get_time()xTaskGetTickCount()tk_get_otm()

FreeRTOSのAPIでは時間はティック数で処理されているので、取得される経過時間もティック数となる。このため、アプリケーションでは必要に応じてミリ秒に変換する必要がある。μT-Kernel 3.0ではAPIで取得する時間はミリ秒と規定されているのでこの処理は必要ない。

4. その他の違い Additional Differences

4. その他の違い Additional Differences

コンポーネントの差し替え作業時に確認したμT-Kernel 3.0とFreeRTOSの差異は上記で説明したとおりである。その他にもいろいろと違いがあったので、以下にその点についてまとめておく。

4-1. FreeRTOSにはラウンドロビンの処理が含まれている!?

FreeRTOS では実行可能状態のタスクが複数あり、それらが同一優先度の場合はラウンドロビンで動作するらしい。これは、同時に実行する準備ができている場合に、等しい優先順位のタスク間で処理時間を共有することを意味する。

μT-Kernel でもラウンドロビン的な機能を実装することは可能である。周期ハンドラを使って tk_rot_rdq() を発行すれば同じような動作になる。もしくは、タスクから明示的に tk_rot_rdq() を発行するのでもよい。tk_rot_rdq() を利用する方式だとある程度処理が完了してからタスクをスイッチすることができるので、実行中のタスクへの影響は限定的にできる。

4-2. 資料は原則英語

μT-Kernel 3.0は原本が日本語である。一方のFreeRTOSは原則英語。AWSが公開している日本語のページにも、先頭に以下のように書かれている。

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

以前の機械翻訳と比べれば格段に読みやすくなっている。だが、込み入った説明になってくると日本語の意味がわからないこともままある。こんな時はやはり直接英文を確認しなければならない。

4-3. 固定長メモリプールがない

FreeRTOSには固定長メモリプールがない。組み込み機器ではメモリプールは以下の理由で、固定長を使用すべきだろう。

RAMが乏しいシステムでは少しでもメモリ使用量を削りたいという気持ちはよくわかるが、管理領域を含めて考えると可変長メモリプールは結構無駄が多い。トレードオフになるとは思うが、最近のシステムはRAMも大きいので、バッファとして確保する場合は多少大きくてもいいのではないだろうか?

4-4. OSの起動方式

FreeRTOSは、main()関数からユーザーアプリケーションが始まっていて、その中でFreeRTOSのスケジューラをキックしてRTOSを起動している。一方、μT-Kernel 3.0ではアプリケーションは初期タスクから始まる。main()関数も含まれてはいるが、それはアプリ開発者には見せていない。

RTOS上のアプリケーションの開発者に対してあまり下回りを見せるのは感心しない気もするが、最初に「C言語のプログラムはmain()関数から始まる」と教わった人にはmain()関数から始まってくれるのはわかりやすいかもしれない。特に、Non-OSからOSを導入する段階では理解しやすいだろう。ただし、RTOSに慣れてくるとこの部分は冗長に感じられてくる。

4-5. タスク用とISR用でAPIが異なる

既に説明したが、FreeRTOSではAPIがタスク用とISR(Interrupt Service Routine)用とに分けられている。ISRから呼び出すAPIは 〜FromISR() という名称になっており、こちらを呼び出さなければならない。これはすごく使いにくい。FreeRTOSはこういった細かい対応を開発者に任せている気がする。自由度が高いとも言えるが、注意事項が多く、その分開発者に負担になるのではないだろうか。

4-6. セマフォの初期値

μT-Kernel 3.0ではセマフォの生成時に初期値を指定できる。一方の、FreeRTOSでは初期値は0に固定。生成後に資源を返却することで設定する。FreeRTOSでは細かいAPIが別々に用意されていて、μT-Kernel 3.0ではある程度機能がまとめて整理されている。APIのわかりやすさとしてはμT-Kernel 3.0の方が優れていると言えると思う。

4-7. データキュー

μT-Kernel 3.0にはデータキューはない。μT-Kernel 3.0のメッセージバッファやメールボックスで代替可能だが、先入れで受信するという機能はない。ただし、メールボックスでは属性の指定でメッセージの並び順をメッセージの優先度順にすることが可能であり、全く同じではないにしても近い動作を実現することはできるだろう。

4-8. タスク優先度

FreeRTOSのタスク優先度は0が最低で、(configMAX_PRIORITIES-1)が最高となっている。μT-Kernel 3.0は1が最高で、CNF_MAX_TSKPRI が最低である。これは慣れの問題だとも思うが、「最も高いのが1」と即値で決められている方が理解しやすいのではないだろうか?

4-9. 戻り値

FreeRTOSでは生成したオブジェクトの識別子(戻り値)が構造体だったり(Queue, Semaphore, Mutex, …)、void*(Message Buffer)だったり、整数値(Task)だったりして統一されていない。例えば、以下になっている:

FreeRTOSではAPIの先頭に戻り値の型を示す文字が追加されてはいる(下記)が、これはコードを読む側にとってはわかりやすいかもしれないが、実装する側にとっては使いにくい。

先頭の文字
pcchar*
pvvoid*
vvoid
uxUBaseType_t
xBaseType_t, TaskHandle_t, …

一方、μT-Kernel 3.0を含めてTRON系のOSでは以下が原則である。

教訓を重視して設計されていることもあり、このあたりはμT-Kernel 3.0の方が開発しやすい。

4-10. アイドルタスク

FreeRTOSには自動的にアイドルタスクが用意される。省電力機能はアイドルタスクに実装するらしいので、開発者にとってはとっつきやすいかもしれない。一方、μT-Kernel 3.0の省電力機能は low_pow() に実装することになっている。この関数は実行すべきタスクがなくなった場合にディスパッチャが直接呼び出しているので、省電力を実装する目的でアイドルタスクを生成する必要はない。

4-11. メッセージバッファ

FreeRTOS にも μT-Kernel 3.0 にもメッセージバッファがある。ただし、FreeRTOS のメッセージバッファはストリームバッファを利用して実装されているため以下のような制限事項がある。

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.

μT-Kernel 3.0のメッセージバッファにはそのような制限はなく、複数のタスクからアクセスできる。

FreeRTOSからμT-Kernel 3.0への移行・移植について、お気軽にご相談ください。

お問い合わせ・ご相談はこちら →