#analog
#norelated
#contents
* LEDチカチカ(RTOS) [#i310dd56]

** RTOS [#u1ca3865]
RTOSとはなんぞやというのは、なかなか説明が難しいものではあるのですが、基本的には複数のタスクで動作し、タスク間でのリソースを共有出来る仕組みを持ったOSといったところでしょうか。~
[[Wikipedia:http://ja.wikipedia.org/wiki/%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%BF%E3%82%A4%E3%83%A0%E3%82%AA%E3%83%9A%E3%83%AC%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0]]や、[[このあたり:http://www.kumikomi.net/archives/2008/12/32rtos.php?page=6]]が参考になるでしょうか。~

書籍では、[[リアルタイムOSと組み込み技術の基礎:http://www.amazon.co.jp/exec/obidos/ASIN/4789833283/shella0x19-22]]がとても詳しく解説されていると思いますので、オススメです。~
μITRONなんかは、非常に有名ですね。~

今回使用するFreeRTOSは、サイズが小さくシンプルで、色々なマイコンに対応しているので、覚えておくと移植も楽なのではないかと思います。~

FreeRTOSのリファレンスマニュアルやチュートリアルは、[[公式サイト:http://www.freertos.org/]]で有償で販売されています。~
が、APIリファレンスくらいであれば、[[公式サイト:http://www.freertos.org/]]の左側のメニューから、FreeRTOS->API Referenceで見ることができますので、まずはこれで感じを掴んでから、さらに深く知りたい場合には購入してみてもいいかもしれません。~

私はまだ買ってませんけどw~

** 構成 [#gb2699d7]
//LPC1769には、基板上に最初からLEDがついています。~
//なので、これを点滅させてみましょう。~
//
//LPC1769のピンアサインについては、インストールされた中にある、Getting StartedのPDFを見てください。~
//後半の方(P31〜P33あたり)のページに、LPC1769のピンアサインが載っています。~
//
//LEDの記載を見ると、P0[22]と書いてあるのがわかります。~
//これは、ポート0の22番ピンということになります。~
//
//#ref(ledpin.png,left,nowrap,LEDピンアサイン)
//
//このピンの出力を、周期的に変化させてやれば、LEDチカチカが実現できることになります。~
//
//** プロジェクト 
//まずは、新規プロジェクトを作りましょう。~
//QuickStartPanelのNew projectから、LPC176Xの「C Project」を選択して、適当なプロジェクト名をつけます。~
//ここでは、「ledtest01」としました。~
//
//MCUはLPC1769を選び、CMSIS Library to link project toは、CMSISv2p00_LPC17XXが選択されていることを確認してください。~
//Code Read Protectについては、コードを読み出されないようにするためのセキュリティ設定ですので、ここは外しておいて構いません。~
//
//#ref(ledproject.png,left,nowrap,LEDプロジェクト設定)
//
//プロジェクトが作成されたら、右クリックからプロパティを開きます。~
//「Project References」は、CMSISv2p00_LPC17xxと、DriverLibの両方にチェックを入れておきましょう。~
//これで、変数や関数の定義を、プロジェクトをまたいで遡ることが出来ます。~
//
//#ref(prjref.png,left,nowrap,プロジェクト参照)
//
//次に、「C/C++ General」の「Paths and Symbols」を開いて、includeとライブラリの設定をします。~
//
//まず、IncludesタブのLanguagesがGNU Cのところを選択すると、既に/CMSISv2p00_LPC17xx/incの設定が入っているかと思います。~
//ここに、/DriverLib/incも追加します。~
//
//#ref(includs.png,left,nowrap,Includeパス設定)
//
//次にLibrariesタブの設定ですが、ここもすでに、CMSISv2p00_LPC17xxの設定が入っていると思います。~
//同様に、DriverLibを追加します。~
//ここは、「DriverLib」と記載するだけにしておいて下さい。~
//
//#ref(libraries.png,left,nowrap,ライブラリ設定)
//
//最後に、Libraries Pathsの設定も同様にします。~
//
//#ref(libpaths.png,left,nowrap,ライブラリパス設定)
//
//ここまでで、プロジェクトの設定は終わりですので、反映させて一旦ビルドをかけてみましょう。~
//QuickStartPanelからBuildを実行して、ledtest01.axfが出来上がればOKです。
//
//「Project References」で指定した、CMSISv2p00_LPC17xxと、DriverLibのそれぞれのインクリメンタルビルドが行われてから、ledtest01のビルドが行われます。~
//
//
//** ソースコード 
//以下、ソースコード。~
//まず、最初から作られているmain.cをベースに、以下のincludeを追加します。~
//
// // TODO: insert other include files here
// #include "lpc17xx_pinsel.h"
// #include "lpc17xx_gpio.h"
//
//次に、指定時間waitするためのカウンタをグローバル変数として用意し、そのカウンタをインクリメントする割り込み関数、実際のwaitを行う関数をそれぞれ用意します。~
//
// // TODO: insert other definitions and declarations here
// volatile uint32_t msTicks = 0;
// void SysTick_Handler(void) {
// 	msTicks++;
// }
// void systick_delay (uint32_t delayTicks) {
//   uint32_t currentTicks = msTicks;
//   while ((msTicks - currentTicks) < delayTicks);
// }
//
//そして、メイン関数は以下のようになります。~
//
// int main(void) {
// 	// TODO: insert code here
// 	PINSEL_CFG_Type pincfg;	// ピン設定構造体
// 	uint32_t state = 0;
// 
// 	// 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));
// 
// 	SysTick_Config(SystemCoreClock / 1000);
// 
// 	while (1) {
// 		systick_delay(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));
// 	}
// 	return 0;
// }
//
//** ソースコード解説 
//まず、メイン関数の先頭からですが、LEDを使用するために、LEDがつながっているポート0の22番ピンの設定をします。~
//ピンの設定用構造体PINSEL_CFG_Typeは以下のようになっており、これに必要な値を設定してPINSEL_ConfigPinをコールすることにより、そのピンが使えるようになります。~
//(デフォルト値のままであればそのまま使えますが、一応……)~
//
// typedef struct
// {
// 	uint8_t Portnum;	/**< Port Number, should be PINSEL_PORT_x,
// 				where x should be in range from 0 to 4 */
// 	uint8_t Pinnum;		/**< Pin Number, should be PINSEL_PIN_x,
// 				where x should be in range from 0 to 31 */
// 	uint8_t Funcnum;	/**< Function Number, should be PINSEL_FUNC_x,
// 				where x should be in range from 0 to 3 */
// 	uint8_t Pinmode;	/**< Pin Mode, should be:
// 				- PINSEL_PINMODE_PULLUP: Internal pull-up resistor
// 				- PINSEL_PINMODE_TRISTATE: Tri-state
// 				- PINSEL_PINMODE_PULLDOWN: Internal pull-down resistor */
// 	uint8_t OpenDrain;	/**< OpenDrain mode, should be:
// 				- PINSEL_PINMODE_NORMAL: Pin is in the normal (not open drain) mode
// 				- PINSEL_PINMODE_OPENDRAIN: Pin is in the open drain mode */
// } PINSEL_CFG_Type;
//
//PINSELやGPIOの定義は、DriverLibの中にあるヘッダの定義です。~
//これらは、解凍したペリフェラルドライバライブラリの中にある、「LPC1700 Peripheral Driver Library Manual.chm」に、どのような命令があってどのような値が設定できるのか、詳しく載っていますので、DriverLibの中身を使う際にはこれを見て下さい。~
//
//どのような設定をすればいいかは、ユーザーマニュアルを見て確認します。~
//
//ユーザーマニュアルは[[NXP社のHPから検索して:http://www.nxp.com/technical-support-portal/#/tid=50809,sid=56890,bt=,tab=usermanuals,p=1,rpp=,sc=,so=]]ダウンロードできます。~
//[[LPC17xx User manual:http://www.nxp.com/documents/user_manual/UM10360.pdf]]を落として下さい。~
//
//落としたら、「Chapter 8: LPC17xx Pin connect block」を開きます。~
//「Table 74」を見ると、今回使用するP0[22]は「Table 80」に記載があるようなので、そこを見ます。~
//
//#ref(table74.png,left,nowrap,Table 74)
//
//「Table 80」を見ると、P0[22]は、Function00(0)の時はGPIO(汎用的なI/Oピン)として、Function01(1)の時はRTS1、Function11(3)の時はTD1として動作するように書かれています。~
//
//#ref(table80.png,left,nowrap,Table 80)
//
//この、RTS1やTD1というのは何かというのは、「Table 73. Pin description」に記載があり、RTS1はUART1という機能のTD1はCAN1という機能の出力ピンとして使えるというのがわかります。~
//
//#ref(table73.png,left,nowrap,Table 73)
//
//今回は、GPIOとしてこのピンを使いますので、機能は0番を指定する必要があり、PINSEL_CFG_TypeのFuncnumにはPINSEL_FUNC_0(0)を設定します。~
//PINSEL_CFG_TypeのOpenDrainは、オープンドレインにはしないので、PINSEL_PINMODE_NORMAL(0)を設定します。~
//PINSEL_CFG_TypeのPinmodeは、内蔵プルアップのままにするので、PINSEL_PINMODE_PULLUP(0)を設定します。~
//さらに、PortnumはPINSEL_PORT_0で、PinnumがPINSEL_PIN_22なのはいいですね。~
//詳細については、ユーザーマニュアルの同じ所に載っていますので、確認してみて下さい。~
//
//最後に、この構造体を指定してPINSEL_ConfigPinを呼べば、これでP0[22]のピンを使用する準備は整いました。~
//----
//次に、そのピンに対して、実際に操作を行います。~
//
//GPIO_SetDirは、指定したポートの、立てたビットのピンの入出力を設定します。~
//PINSEL_PORT_0の、PINSEL_PIN_22ビットのピンを、出力(1)用に設定します。~
//
//これでH/Lを出力できるようになったので、まずはLEDをON状態に初期化します。~
//GPIOの命令は、GPIO_SetValueで指定したピンをHighに、GPIO_ClearValueで指定したピンをLowにします。~
//一つの命令でH/Lを設定するわけではないので、注意が必要です。~
//
//ユーザーマニュアルの「Chapter 9: LPC17xx General Purpose Input/Output (GPIO)」を読むと理解が深まると思いますが、GPIOのレジスタには、SETレジスタとCLRレジスタがあり、これをラッパーしているのが前述の命令ということになります。~
//
//#ref(setclr.png,left,nowrap,SETとCLR)
//
//まずはON状態で初期化するので、GPIO_SetValueにPINSEL_PORT_0の、PINSEL_PIN_22ビットのピンを指定すれば、この時点でLEDが点灯します。~
//----
//次に、SysTick_Configで、1msの割り込みを発生させます。~
//これは、DriverLibのSYSTICK_InternalInitを使用してもいいはずですが、これはCMSISライブラリの方の命令です。~
//
//SysTick_Configは、引数に指定したクロックで割り込みを発生させます。~
//ここでは、システムコアクロックを1000で割った値を入れているので、1ms毎に割り込みが発生することになります。~
//
//引数にmsを指定すればいいSYSTICK_InternalInitよりわかりにくいですが、こちらを選んだのには理由がありますので、後述します。~
//
//SysTick_Configは、System Tick Timerの割り込みを発生させますが、この割り込み時に実行される関数は、今回のledtest01プロジェクトを作成した時に自動的に生成されている、cr_startup_lpc176x.c内に書かれています。~
//この中に、g_pfnVectorsという割り込み関数テーブルがあり、割り込み発生時にはこのテーブルを参照してSysTick_Handlerが呼ばれますが、実体はWEAKつきで宣言されており、無限ループで何もしないようになっています。~
//
//今回、同名のSysTick_Handlerという関数を作った事により、WEAK付きの元々の関数は無視され、新たに作成した関数がSystem Tick Timerの割り込みでコールされるようになります。~
//----
//さて、後は、無限ループの中身ですが、最初は1msの割り込みカウンタが引数の500回を超えるまでwaitするサブ関数です。~
//500ms待って、LEDをトグルさせ、1秒周期の点滅にします。~
//
//waitが終わったら、その時のポート0の状態を読み出し、ローカル変数に保存しています。~
//
//次に、22番ピンがHighであればLowにするため、22番ピンの元の状態を指定してGPIO_ClearValueを呼びます。~
//元の状態がLowであれば、GPIO_ClearValueには0が指定されるので、状態としては何も変わらないことになります。~
//
//さらに、22番ピンがLowであればHighにするため、22番ピンの元の状態を反転させたものを指定してGPIO_SetValueを呼びます。~
//元の状態がHighであれば、GPIO_SetValueには0が指定されるので、状態としては何も変わらないことになります。~
//
//** 実行・デバッグ 
//ソースが出来て、ビルドが通ったら、いよいよ実行です。~
//LPC1769と一体になっている、LPC-LinkとPCをUSBで繋いで、QuickStartPanelのDebugを押します。~
//
//#ref(debug.png,left,nowrap,デバッグ)
//
//初回は、ドライバのインストールなどがあり、時間がかかるかもしれません。~
//待つかキャンセルかを聞かれるダイアログが表示されることもあるようです。~
//また、USBを接続しなおしたりした場合にも、エミュレータ選択ダイアログが表示されることがありますので、その場合は接続しなおして再検索をするといいようです。~
//
//無事に実行できると、自動的にmain関数の先頭でbreakがかかります。~
//Step Over(F6)でステップ実行を行い、最初のGPIO_SetValueでLEDが点灯することが確認できると思います。~
//
//Resume(F8)を押せば、LEDが点滅します。~
//Suspendで、その時実行している箇所で停止します。~
//Terminate(Ctrl+F2)でデバッガは停止し、マイコン自体はそのまま動作します。~
//
//お手軽にIDEでデバッグ出来るのは楽でいいですね。~
//
//#u2b(v1xaoRxPuSY,w=640,h=360)
//
//** クロックの設定 
//デバッガで停止中に、SysTick_Configの引数に指定している、SystemCoreClockにカーソルをあててみて下さい。~
//ポップアップが表示されて、100000000という数字が値として設定されているのがわかると思います。~
//(このために、SYSTICK_InternalInitじゃなくてSysTick_Configを使用していました)
//
//#ref(scc.png,left,nowrap,SystemCoreClock)
//
//実はこのLPC1769、このままでは100MHzでしか動作していません。~
//LPC1769より前のLPC1768ではよかったのですが、LPC1769は120MHzでの動作が可能ですので、これを修正しておきましょう。~
//
//この設定は、CMSISv2p00_LPC17xxライブラリの方で行われています。~
//CMSISv2p00_LPC17xxプロジェクトのsystem_LPC17xx.cを開いて、PLL0CFG_Valの値を変更します。~
//
// #define CLOCK_SETUP           1
// #define SCS_Val               0x00000020
// #define CLKSRCSEL_Val         0x00000001
// #define PLL0_SETUP            1
// #define PLL0CFG_Val           0x00050077
// //#define PLL0CFG_Val           0x00050063
// #define PLL1_SETUP            1
// #define PLL1CFG_Val           0x00000023
// #define CCLKCFG_Val           0x00000003
// #define USBCLKCFG_Val         0x00000000
// #define PCLKSEL0_Val          0x00000000
// #define PCLKSEL1_Val          0x00000000
// #define PCONP_Val             0x042887DE
// #define CLKOUTCFG_Val         0x00000000
//
//これで、120MHzで動作するようになりました。~
//こちらの詳細につきましては、以下のblogがとてもわかりやすいと思います。~
//参考にさせていただきました。
//
//[[PS3とLinux、電子工作も-LPCXpresso LPC1769の120MHzクロック設定:http://todotani.cocolog-nifty.com/blog/2011/07/lpcxpresso-lp-1.html]]
//
//以上で、非RTOS版のLEDチカチカは終了です。~
構成は、[[LEDチカチカ(RTOS)]]の方と変わりません。~
基板上のLEDを、FreeRTOSを使用して点滅させます。~

** プロジェクト [#oee75559]
まずは、新規プロジェクトを作りますが、前回とは選ぶプロジェクトが違います。~
QuickStartPanelのNew projectから、LPC176Xの「FreeRTOS Project」を選択して、適当なプロジェクト名をつけます。~
ここでは、「ledtest02」としました。~

MCUはLPC1769を選び、途中Advanced OptionでFreeRTOSのrootを選べますが、とりあえずはこのままで。~
すると、以下の様な構成で、プロジェクトが出来上がります。~

#ref(ledtest02.png,left,nowrap,プロジェクト構成)

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の両方にチェックを入れておきます。~

#ref(prjref.png,left,nowrap,プロジェクト参照)

次に、「C/C++ General」の「Paths and Symbols」を開いて、includeとライブラリの設定をします。~
IncludesタブのLanguagesがGNU Cのところを選択し、/CMSISv2p00_LPC17xx/incと、/DriverLib/incを追加します。~

#ref(includes.png,left,nowrap,Includeパス設定)

そして、Librariesタブに、CMSISv2p00_LPC17xxと、DriverLibを追加します。~

#ref(libraries.png,left,nowrap,ライブラリ設定)

最後に、Library Pathsタブに、/CMSISv2p00_LPC17xx/Debugと、/DriverLib/Debugを追加します。~

#ref(librarypaths.png,left,nowrap,ライブラリパス設定)

これでヘッダファイルを参照出来るようになったので、FreeRTOSConfig.hの先頭で、LPC17xx.hをincludeするようにしておきましょう。~

 #include "LPC17xx.h"

ビルドを行い、ledtest02.axfが出来上がればOKです。~

ビルドは通りましたが、実はまだ、ledtest01とはプロジェクトの設定が違うところがあります。~
プロジェクトのプロパティに、Symbolsというタブがあり、色々なシンボルとそれに対する値が定義されていると思います。~
Show built-in valuesというチェックを外すと、このプロジェクトに定義されているもののみが表示されます。~

ledtest01とledtest02の定義を比べてみましょう。~

|ledtest01のみ|ledtest02のみ|h
|__REDLIB__|__NEWLIB__|
|__USE_CMSIS||
||GCC_ARMCM3|
||PACK_STRUCT_END|

まず、__REDLIB__と__NEWLIB__の定義ですが、これはどのCライブラリを使用するか、という選択になります。~
[[CodeRedのサポートページ:http://support.code-red-tech.com/CodeRedWiki/RedlibAndNewlib]]に書いてある通り、NewLibは標準のライブラリ、RedLibはCodeRed社独自のライブラリということになります。~
RedLibの特徴としては、C90準拠でサイズが小さいため、メモリの制約が厳しい時に有効ですが、C++プロジェクトでは使用できないということになるようです。~

C++を使わない限りは、RedLibを使用するのがいいと思いますので、ここはledtest01にあわせて変えておきましょう。~
使用ライブラリの切り替えは、QuickStartPanelのQuick Settingsから、Set library typeで、Redlib(none)を選びます。~
すると、シンボル定義や、リンクするライブラリの切り替えを全部自動でやってくれます。~
nohostやSemihostについては、デバッグ用だったりするので、とりあえずここでは選びません。~

#ref(sellib.png,left,nowrap,ライブラリ選択)

次にledtest01のみで定義されていた__USE_CMSISですが、これは文字通り、CMSISライブラリを使うためのものです。~
この定義があると、cr_startup_lpc17.cの中で、CMSISライブラリのSystemInitという初期化関数を呼び、主にクロック関係の初期化をしてくれます。~
やっておいた方がいいと思うので、ledtest02にもこの定義を追加しておきましょう。~
SymbolsタブでAddボタンを押し、以下のように入れておきます。~

#ref(usecmsis.png,left,nowrap,定義追加)

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にタスクを一定時間休止するような機能や、タイマーが用意されていますので、そちらを使うことになります。~

** ソースコード [#d48a41c4]
まずはざっくり全部消してしまって、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;
 }

** ソースコード解説 [#g9c0489b]
まず、メイン関数ですが、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の待ち状態になっている間は、アイドルタスクが動いていることになります。~

** 実行・デバッグ [#t028524b]
ビルドして実行してみます。~
当然ながら、見た目はledtest01の時と変わりません。~

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

** スタックサイズ [#ueae9e98]
今回、スタックサイズに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倍近い値を確保していますので、問題ないでしょう。~

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

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


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS