RTOSとはなんぞやというのは、なかなか説明が難しいものではあるのですが、基本的には複数のタスクで動作し、タスク間でのリソースを共有出来る仕組みを持ったOSといったところでしょうか。
Wikipediaや、このあたりが参考になるでしょうか。
書籍では、リアルタイムOSと組み込み技術の基礎がとても詳しく解説されていると思いますので、オススメです。
μITRONなんかは、非常に有名ですね。
今回使用するFreeRTOSは、サイズが小さくシンプルで、色々なマイコンに対応しているので、覚えておくと移植も楽なのではないかと思います。
FreeRTOSのリファレンスマニュアルやチュートリアルは、公式サイトで有償で販売されています。
が、APIリファレンスくらいであれば、公式サイトの左側のメニューから、FreeRTOS->API Referenceで見ることができますので、まずはこれで感じを掴んでから、さらに深く知りたい場合には購入してみてもいいかもしれません。
私はまだ買ってませんけどw
構成は、LEDチカチカ(RTOS)の方と変わりません。
基板上のLEDを、FreeRTOSを使用して点滅させます。
まずは、新規プロジェクトを作りますが、前回とは選ぶプロジェクトが違います。
QuickStartPanelのNew projectから、LPC176Xの「FreeRTOS Project」を選択して、適当なプロジェクト名をつけます。
ここでは、「ledtest02」としました。
MCUはLPC1769を選び、途中Advanced OptionでFreeRTOSのrootを選べますが、とりあえずはこのままで。
すると、以下の様な構成で、プロジェクトが出来上がります。
FreeRTOS_なんとかというフォルダは、FreeRTOS用のファイルであり、ここは自分でいじることはありません。
自分でいじるのは、srcフォルダにある、cr_startup_lpc17.c、main.cと、FreeRTOSConfig.hになります。
ざっと、デフォルトで作られたmain.cを見てみると、2つのタスクが作られて、vUserTask1は一定時間ごとに状態を反転させ、vUserTask2は一定時間ごとにカウンタを増やしているのがわかります。
それぞれに関連性はないので協調して動くものではないですが、これらが見かけ上同時に、並行して動くんだなということは、なんとなくわかると思います。
main関数は、これらのタスクを生成して、スタートさせているだけです。
RTOSのイメージが、なんとなく掴めるでしょうか。
次にFreeRTOSConfig.hを見てみましょう。
この中身をいじることにより、FreeRTOSの機能をカスタマイズすることが可能です。
とりあえず直しておく必要があるのは、configCPU_CLOCK_HZです。
これは、CPUクロックを設定する必要があるので、ledtest01で使ったのと同じ、SystemCoreClockを設定しておきます。
#define configCPU_CLOCK_HZ ( ( unsigned long )SystemCoreClock )
ただし、実はこれではビルドが通りません。
SystemCoreClockというのは、元々CMSISライブラリのsystem_LPC17xx.cに定義されているものであり、CMSISライブラリのincフォルダにあるsystem_LPC17xx.hでexternされています。
このため、CMSISライブラリを参照する必要がありますが、ledtest01の時には、CMSIS Library to link project toを設定するところがあり、CMSISv2p00_LPC17XXを選択しましたが、今回はその指定がありませんでした。
FreeRTOSを選んだ時には、FreeRTOSのソースが組み入れられるだけで、デフォルトではCMSISライブラリを使えるようには出来ていないようです。
ledtest01の時と同じように、CMSISv2p00_LPC17xxと、DriverLibを使用してLEDの点滅をさせたいので、このプロジェクトに対して、これらを使えるように、設定を変更します。
ledtest02のプロジェクトを、右クリックからプロパティを開きます。
まず、「Project References」で、CMSISv2p00_LPC17xxと、DriverLibの両方にチェックを入れておきます。
次に、「C/C++ General」の「Paths and Symbols」を開いて、includeとライブラリの設定をします。
IncludesタブのLanguagesがGNU Cのところを選択し、/CMSISv2p00_LPC17xx/incと、/DriverLib/incを追加します。
そして、Librariesタブに、CMSISv2p00_LPC17xxと、DriverLibを追加します。
最後に、Library Pathsタブに、/CMSISv2p00_LPC17xx/Debugと、/DriverLib/Debugを追加します。
これでヘッダファイルを参照出来るようになったので、FreeRTOSConfig.hの先頭で、LPC17xx.hをincludeするようにしておきましょう。
#include "LPC17xx.h"
ビルドを行い、ledtest02.axfが出来上がればOKです。
ビルドは通りましたが、実はまだ、ledtest01とはプロジェクトの設定が違うところがあります。
プロジェクトのプロパティに、Symbolsというタブがあり、色々なシンボルとそれに対する値が定義されていると思います。
Show built-in valuesというチェックを外すと、このプロジェクトに定義されているもののみが表示されます。
ledtest01とledtest02の定義を比べてみましょう。
ledtest01のみ | ledtest02のみ |
__REDLIB__ | __NEWLIB__ |
__USE_CMSIS | |
GCC_ARMCM3 | |
PACK_STRUCT_END |
まず、__REDLIB__と__NEWLIB__の定義ですが、これはどのCライブラリを使用するか、という選択になります。
CodeRedのサポートページに書いてある通り、NewLibは標準のライブラリ、RedLibはCodeRed社独自のライブラリということになります。
RedLibの特徴としては、C90準拠でサイズが小さいため、メモリの制約が厳しい時に有効ですが、C++プロジェクトでは使用できないということになるようです。
C++を使わない限りは、RedLibを使用するのがいいと思いますので、ここはledtest01にあわせて変えておきましょう。
使用ライブラリの切り替えは、QuickStartPanelのQuick Settingsから、Set library typeで、Redlib(none)を選びます。
すると、シンボル定義や、リンクするライブラリの切り替えを全部自動でやってくれます。
nohostやSemihostについては、デバッグ用だったりするので、とりあえずここでは選びません。
次にledtest01のみで定義されていた__USE_CMSISですが、これは文字通り、CMSISライブラリを使うためのものです。
この定義があると、cr_startup_lpc17.cの中で、CMSISライブラリのSystemInitという初期化関数を呼び、主にクロック関係の初期化をしてくれます。
やっておいた方がいいと思うので、ledtest02にもこの定義を追加しておきましょう。
SymbolsタブでAddボタンを押し、以下のように入れておきます。
ledtest02で定義されているGCC_ARMCM3は、FreeRTOSが適切な設定をincludeするために使用していますので、これはこのままにしておきます。
もう一つのPACK_STRUCT_ENDですが、これは現状、どこでも使用されていません。
ただ、FreeRTOSのサンプルプログラムで、構造体定義のパディングをなくすために使用しているようです。
現状あってもなくても影響はないはずですが、とりあえずはそのままにしておきます。
FreeRTOSConfig.hの他の設定については、今のところはいじりません。
基本的なプロジェクトの設定はこんなところかと思いますが、最後にledtest01とledtest02のcr_startup_lpc17.cの違いを見ておきましょう。
内容としては、ledtest01の方が新しいようなのですが、ledtest02では、FreeRTOS用に書き換えられている箇所があります。
以下の3関数が、g_pfnVectorsという割り込み関数テーブルの中に「The SysTick handler」、「The PendSV handler」、「SVCall handler」として設定されています。
extern void xPortSysTickHandler(void); extern void xPortPendSVHandler(void); extern void vPortSVCHandler( void );
これは、FreeRTOSがタスクスケジューリングをするために使うものなので、これらの割り込みは、自前では使うことができません。
SysTick handlerはledtest01で使用しましたが、FreeRTOSで使用するので、同じような時間管理はできません。
ただし、FreeRTOSにタスクを一定時間休止するような機能や、タイマーが用意されていますので、そちらを使うことになります。
まずはざっくり全部消してしまって、ledtest01の最初の部分だけ持ってきましょう。
#ifdef __USE_CMSIS #include "LPC17xx.h" #endif #include <cr_section_macros.h> #include <NXP/crp.h> // Variable to store CRP value in. Will be placed automatically // by the linker when "Enable Code Read Protect" selected. // See crp.h header for more information __CRP const unsigned int CRP_WORD = CRP_NO_CRP; // TODO: insert other include files here #include "lpc17xx_pinsel.h" #include "lpc17xx_gpio.h"
次に、FreeRTOSを使うためのヘッダをincludeします。
基本のヘッダと、今回はタスクを使うので、タスクのヘッダを指定します。
#include "FreeRTOS.h" #include "task.h"
タスクで使うスタック領域のサイズとプライオリティを定義しておきます。
#define LED_STACK_SIZE 64 #define LED_TASK_PRIO tskIDLE_PRIORITY + 1
先にタスクの処理を書いてしまいます。
void LedTask(void *param) { uint32_t state = 0; while (1) { vTaskDelay(500); state = GPIO_ReadValue(PINSEL_PORT_0); GPIO_ClearValue(PINSEL_PORT_0, state & (1 << PINSEL_PIN_22)); GPIO_SetValue(PINSEL_PORT_0, ~state & (1 << PINSEL_PIN_22)); } }
そして、メイン関数は以下のようになります。
int main(void) { // TODO: insert code here PINSEL_CFG_Type pincfg; // ピン設定構造体 // LED初期化 pincfg.Funcnum = PINSEL_FUNC_0; pincfg.OpenDrain = PINSEL_PINMODE_NORMAL; pincfg.Pinmode = PINSEL_PINMODE_PULLUP; pincfg.Portnum = PINSEL_PORT_0; pincfg.Pinnum = PINSEL_PIN_22; PINSEL_ConfigPin(&pincfg); GPIO_SetDir(PINSEL_PORT_0, (1 << PINSEL_PIN_22), 1 ); GPIO_SetValue(PINSEL_PORT_0, (1 << PINSEL_PIN_22)); xTaskCreate( LedTask, ( signed portCHAR * ) "LedTask", LED_STACK_SIZE, NULL, LED_TASK_PRIO, NULL ); vTaskStartScheduler(); return 0; }
まず、メイン関数ですが、LEDがつながっているポート0の22番ピンの設定とON初期化はledtest01と同じです。
その後、LEDタスクを生成して、タスク動作をスタートさせるという流れになっています。
xTaskCreateはタスクの生成を行うマクロですが、第1引数にタスク関数の指定、第2引数にタスク名、第3引数にタスクのスタックサイズ、第4引数にタスクに渡すパラメータ、第5引数にタスクの優先度、第6引数にタスクハンドルを受け取る変数の参照を指定します。
第1引数のタスク関数は、戻り値なし(void)でvoid*型の関数を指定する必要があります。
また、この関数はreturnしてはいけません。
無限ループ等で関数から抜けないようにしておく必要があります。
LedTask関数も、そのように作っています。
第2引数のタスク名は、FreeRTOSConfig.h内のconfigMAX_TASK_NAME_LEN定義の文字数以下で、タスク名を指定します。
デフォルトでは12文字までですね。
終端文字を入れて12文字なので、実質11文字になります。
#define configMAX_TASK_NAME_LEN ( 12 )
第3引数のスタックサイズには、そのタスクで使用するスタックの割り当てサイズを指定します。
スタックとは、ローカル変数や関数コール時の引数等を一時的に保存するための領域で、基本的には大きいサイズのローカル変数を用意したり、関数コールを繰り返して深いネストまで行くと、消費量が多くなります。
本当に必要なサイズが、実際にどれくらいかというのを厳密に調べるのは結構大変なのですが、FreeRTOSにはスタック領域の残量が最小でどれくらいだったかというのがわかる機能もありますので、それを指標に決めることも出来ます。
今回はひとまず、64バイトを指定します。
第4引数のパラメータですが、今回はタスクに何も渡すものがないので、NULLを指定しておきます。
第5引数のタスク優先度ですが、0からFreeRTOSConfig.h内のconfigMAX_PRIORITIES-1までの値が指定出来ます。
デフォルトでは5なので、0(低優先度)〜4(高優先度)の優先度が指定出来ます。
#define configMAX_PRIORITIES ( ( unsigned portBASE_TYPE ) 5 )
ただし、CPUが何も実行するタスクがない場合、FreeRTOSが自動的に生成するアイドルタスクが実行されますが、この優先度は最低優先度の0になっています。
このため、優先度0はアイドルタスク専用としておき、1〜4までを使うのがいいと思います。
今回、LEDタスクとしての優先度は、アイドルタスクの次に低い優先度としておきます。
今後、優先度の高い他のタスクを動作させるようになり、そのタスクが大幅に処理時間を食うような事があった場合、優先度の低いLEDタスクには処理が回らず、LEDの点滅が行われなくなり、目で状況が確認できるという設計も出来るかもしれません。
第6引数のタスクハンドルですが、タスクハンドルを後で使用することがない場合には、受け取る必要もないので、NULLを指定します。
例えば、別のタスクからLEDタスクを削除したり、サスペンドしたりレジュームしたりする場合は、タスクハンドルが必要になるので、ここで受け取っておいたハンドルを使用します。
vTaskStartSchedulerは、アイドルタスクを作成して、各タスクの実行を開始する命令です。
アイドルタスクとLEDタスクが開始され、優先度の高いLEDタスクから動き始めます。
最後に、タスク関数の中身ですが、基本的にはledtest01の無限ループの内容そのものです。
一つ違うのは、500ms待つための関数が、タスクを待ち状態にさせるものに変更になっているという点です。
vTaskDelayは、指定されたtick数分、タスクを待ち状態にするという関数ですが、FreeRTOSConfig.h内のconfigTICK_RATE_HZで、毎秒1000tickという指定をしているので、1tickは1msと同義になります。
#define configTICK_RATE_HZ ( ( portTickType ) 1000 )
今回は、LEDタスク以外にはアイドルタスクしかないので、vTaskDelayでタスクが500msの待ち状態になっている間は、アイドルタスクが動いていることになります。