LEDチカチカ(RTOS)

RTOS

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を追加します。

Includeパス設定

そして、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をかけたものが確保されることになります。
つまり、64*4で256バイトのスタックが確保されます。

第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の待ち状態になっている間は、アイドルタスクが動いていることになります。

実行・デバッグ

ビルドして実行してみます。
当然ながら、見た目はledtest01の時と変わりません。

ただ、タスクという概念で動作するようになったので、今後色々なことを並行してやるのに、便利な作りになりました。
タスクのデバッグも、普通にブレークポイントをはるだけで可能です。

スタックサイズ

今回、スタックサイズに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という値が帰って来ました。

ということは、35(140バイト)/64(256バイト)で、140バイトが空いているということになります。

もちろん、これは実際に動作した結果そうなっているというだけのことなので、この値が絶対にスタックの最大値だというわけではありません。
今回はたまたま、深い関数コールのネストが行われずにスタックがあまり使われなかっただけかもしれませんが、サイズとしてはこれの2倍近い値を確保していますので、問題ないでしょう。

自分でタスクのプログラムを書く場合、どこが一番深いネストで、最大のスタックサイズを取りうるところはどこなのかというのは、常に意識しておく必要があります。
メモリの容量制限の厳しいマイコンでは、特に意識しないとなりませんね。

また、これ以外にも、スタックオーバーフローを検知してフック関数を呼んでもらう方法等があります。
使う機会があれば、そのうち書いてみたいと思います。


添付ファイル: fileusecmsis.png 1462件 [詳細] filesellib.png 1455件 [詳細] filelibrarypaths.png 1217件 [詳細] filelibraries.png 1271件 [詳細] fileincludes.png 1405件 [詳細] fileprjref.png 1272件 [詳細] fileledtest02.png 1554件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2013-02-13 (水) 01:02:54 (4316d)