LEDチカチカ(RTOS) †RTOS †RTOSとはなんぞやというのは、なかなか説明が難しいものではあるのですが、基本的には複数のタスクで動作し、タスク間でのリソースを共有出来る仕組みを持ったOSといったところでしょうか。 書籍では、リアルタイムOSと組み込み技術の基礎がとても詳しく解説されていると思いますので、オススメです。 今回使用するFreeRTOSは、サイズが小さくシンプルで、色々なマイコンに対応しているので、覚えておくと移植も楽なのではないかと思います。 FreeRTOSのリファレンスマニュアルやチュートリアルは、公式サイトで有償で販売されています。 私はまだ買ってませんけどw 構成 †構成は、LEDチカチカ(RTOS)の方と変わりません。 プロジェクト †まずは、新規プロジェクトを作りますが、前回とは選ぶプロジェクトが違います。 MCUはLPC1769を選び、途中Advanced OptionでFreeRTOSのrootを選べますが、とりあえずはこのままで。 FreeRTOS_なんとかというフォルダは、FreeRTOS用のファイルであり、ここは自分でいじることはありません。 ざっと、デフォルトで作られたmain.cを見てみると、2つのタスクが作られて、vUserTask1は一定時間ごとに状態を反転させ、vUserTask2は一定時間ごとにカウンタを増やしているのがわかります。 main関数は、これらのタスクを生成して、スタートさせているだけです。 次にFreeRTOSConfig.hを見てみましょう。 とりあえず直しておく必要があるのは、configCPU_CLOCK_HZです。 #define configCPU_CLOCK_HZ ( ( unsigned long )SystemCoreClock ) ただし、実はこれではビルドが通りません。 このため、CMSISライブラリを参照する必要がありますが、ledtest01の時には、CMSIS Library to link project toを設定するところがあり、CMSISv2p00_LPC17XXを選択しましたが、今回はその指定がありませんでした。 ledtest01の時と同じように、CMSISv2p00_LPC17xxと、DriverLibを使用してLEDの点滅をさせたいので、このプロジェクトに対して、これらを使えるように、設定を変更します。 ledtest02のプロジェクトを、右クリックからプロパティを開きます。 次に、「C/C++ General」の「Paths and Symbols」を開いて、includeとライブラリの設定をします。 そして、Librariesタブに、CMSISv2p00_LPC17xxと、DriverLibを追加します。 最後に、Library Pathsタブに、/CMSISv2p00_LPC17xx/Debugと、/DriverLib/Debugを追加します。 これでヘッダファイルを参照出来るようになったので、FreeRTOSConfig.hの先頭で、LPC17xx.hをincludeするようにしておきましょう。 #include "LPC17xx.h" ビルドを行い、ledtest02.axfが出来上がればOKです。 ビルドは通りましたが、実はまだ、ledtest01とはプロジェクトの設定が違うところがあります。 ledtest01とledtest02の定義を比べてみましょう。
まず、__REDLIB__と__NEWLIB__の定義ですが、これはどのCライブラリを使用するか、という選択になります。 C++を使わない限りは、RedLibを使用するのがいいと思いますので、ここはledtest01にあわせて変えておきましょう。 次にledtest01のみで定義されていた__USE_CMSISですが、これは文字通り、CMSISライブラリを使うためのものです。 ledtest02で定義されているGCC_ARMCM3は、FreeRTOSが適切な設定をincludeするために使用していますので、これはこのままにしておきます。 FreeRTOSConfig.hの他の設定については、今のところはいじりません。 基本的なプロジェクトの設定はこんなところかと思いますが、最後にledtest01とledtest02のcr_startup_lpc17.cの違いを見ておきましょう。 extern void xPortSysTickHandler(void); extern void xPortPendSVHandler(void); extern void vPortSVCHandler( void ); これは、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と同じです。 xTaskCreateはタスクの生成を行うマクロですが、第1引数にタスク関数の指定、第2引数にタスク名、第3引数にタスクのスタックサイズ、第4引数にタスクに渡すパラメータ、第5引数にタスクの優先度、第6引数にタスクハンドルを受け取る変数の参照を指定します。 第1引数のタスク関数は、戻り値なし(void)でvoid*型の関数を指定する必要があります。 第2引数のタスク名は、FreeRTOSConfig.h内のconfigMAX_TASK_NAME_LEN定義の文字数以下で、タスク名を指定します。 #define configMAX_TASK_NAME_LEN ( 12 ) 第3引数のスタックサイズには、そのタスクで使用するスタックの割り当てサイズを指定します。 本当に必要なサイズが、実際にどれくらいかというのを厳密に調べるのは結構大変なのですが、FreeRTOSにはスタック領域の残量が最小でどれくらいだったかというのがわかる機能もありますので、それを指標に決めることも出来ます。 今回はひとまず、64を指定します。 第4引数のパラメータですが、今回はタスクに何も渡すものがないので、NULLを指定しておきます。 第5引数のタスク優先度ですが、0からFreeRTOSConfig.h内のconfigMAX_PRIORITIES-1までの値が指定出来ます。 #define configMAX_PRIORITIES ( ( unsigned portBASE_TYPE ) 5 ) ただし、CPUが何も実行するタスクがない場合、FreeRTOSが自動的に生成するアイドルタスクが実行されますが、この優先度は最低優先度の0になっています。 今回、LEDタスクとしての優先度は、アイドルタスクの次に低い優先度としておきます。 第6引数のタスクハンドルですが、タスクハンドルを後で使用することがない場合には、受け取る必要もないので、NULLを指定します。 vTaskStartSchedulerは、アイドルタスクを作成して、各タスクの実行を開始する命令です。 最後に、タスク関数の中身ですが、基本的にはledtest01の無限ループの内容そのものです。 vTaskDelayは、指定されたtick数分、タスクを待ち状態にするという関数ですが、FreeRTOSConfig.h内のconfigTICK_RATE_HZで、毎秒1000tickという指定をしているので、1tickは1msと同義になります。 #define configTICK_RATE_HZ ( ( portTickType ) 1000 ) 今回は、LEDタスク以外にはアイドルタスクしかないので、vTaskDelayでタスクが500msの待ち状態になっている間は、アイドルタスクが動いていることになります。 実行・デバッグ †ビルドして実行してみます。 ただ、タスクという概念で動作するようになったので、今後色々なことを並行してやるのに、便利な作りになりました。 スタックサイズ †今回、スタックサイズに64(*4=128バイト)を指定しましたが、これが実際にどれくらい使われているかを調べてみます。 FreeRTOSは、xTaskCreateでタスクを生成する時に、指定された分のスタック領域を確保し、内容が0xa5で埋められます。 タスク処理中に任意のタイミングで、uxTaskGetStackHighWaterMarkという関数を呼ぶと、確保されているメモリの先頭から、0xa5でなくなる箇所まで遡り、遡った数/4の値を返します。 LedTask関数を、以下のように書き換えてみましょう。 void LedTask(void *param) { uint32_t state = 0; unsigned portBASE_TYPE freesize = 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)); freesize = uxTaskGetStackHighWaterMark(NULL); } } uxTaskGetStackHighWaterMarkをコールする箇所にブレークポイントを仕掛けておき、止まったらステップ実行して、freesizeに入る値を確認してみます。 ということは、35(140バイト)/64(256バイト)で、140バイトが空いているということになります。 もちろん、これは実際に動作した結果そうなっているというだけのことなので、この値が絶対にスタックの最大値だというわけではありません。 自分でタスクのプログラムを書く場合、どこが一番深いネストで、最大のスタックサイズを取りうるところはどこなのかというのは、常に意識しておく必要があります。 また、これ以外にも、スタックオーバーフローを検知してフック関数を呼んでもらう方法等があります。 |