#analog
#norelated
#contents
* LCD文字表示(RTOS) [#bb712a69]
* LEDチカチカ(RTOS) [#i310dd56]

** MAPLE board [#ba36d854]
頑張ってハンダ付けしましょうw~
そんなに大変な作業ではないです。~
まぁ、私はフリー拡張スロットのコネクタのハンダ付け、一部失敗して斜めについてますけどね……。~
** 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]]が参考になるでしょうか。~

** LCDモジュール [#k233bed7]
MAPLE boardについてきたのは、[[TC1602E-25A:http://www.linkman.jp/user/shohin.php?p=61364]]という型番のLCDでした。~
このLCDは、4または8ビットモード(HD44780互換)ということですから、HD44780のデータシートを探してくることにします。~
書籍では、[[リアルタイムOSと組み込み技術の基礎:http://www.amazon.co.jp/exec/obidos/ASIN/4789833283/shella0x19-22]]がとても詳しく解説されていると思いますので、オススメです。~
μITRONなんかは、非常に有名ですね。~

というわけで、[[alldatasheet:http://www.alldatasheet.jp/datasheet-pdf/pdf/63673/HITACHI/HD44780.html]]から拾って来ました。~
今回使用するFreeRTOSは、サイズが小さくシンプルで、色々なマイコンに対応しているので、覚えておくと移植も楽なのではないかと思います。~

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

** 構成 [#y147a72c]
MAPLE board附属の回路図を見れば分かる通り、DB0〜DB3は接続されていませんので、4ビットモードでの動作となります。~
また、R/WがGNDに接続されていますので、Writeモード固定ということになります。~
Writeモード固定なので、Busyフラグチェックは出来ませんから、コマンド実行後は実行時間分待つ必要があります。~
私はまだ買ってませんけどw~

LPC1769は、LPC1768と同じなので、MAPLE boardの取扱説明書「8.3 LPCXPresso LPC1768 使用時」というところのピン配置を見ると、どのピンが何に割当たっているのかわかります。~
以下のようになっているみたいですね。~
** 構成 [#gb2699d7]
構成は、[[LEDチカチカ(RTOS)]]の方と変わりません。~
基板上のLEDを、FreeRTOSを使用して点滅させます。~

|ポート番号|ピン番号|LCD|h
|P0|15|D5|
|P0|16|D6|
|P0|17|D4|
|P2|1|RS|
|P2|2|E|
|P2|3|D7|
** プロジェクト [#oee75559]
まずは、新規プロジェクトを作りますが、前回とは選ぶプロジェクトが違います。~
QuickStartPanelのNew projectから、LPC176Xの「FreeRTOS Project」を選択して、適当なプロジェクト名をつけます。~
ここでは、「ledtest02」としました。~

** プロジェクト [#o8640e7e]
まずは、ledtest02と同じように、FreeRTOSの新規プロジェクトを作って、ひと通り設定をしておきます。~
プロジェクト名は、「lcdtest」としました。~
MCUはLPC1769を選び、途中Advanced OptionでFreeRTOSのrootを選べますが、とりあえずはこのままで。~
すると、以下の様な構成で、プロジェクトが出来上がります。~

元々のLEDと今回のLCD、それに全体的な管理をするアプリケーションとしてのタスクを作って動かすようにしてみましょう。~
それぞれをモジュールとして管理し、モジュールを管理するためのデータをハンドル的に扱えるようにします。~
データをハンドル化し、ロジックと切り離すことによって、LEDが2つとか、LCDが2つとかになった場合でも、それぞれを独立して動かせるようになるはずです。~
#ref(ledtest02.png,left,nowrap,プロジェクト構成)

今回の最終的なsrcフォルダ以下の構成は、以下のようになります。~
アプリケーションモジュール、LCDモジュール、LEDモジュールをフォルダごとに分け、それぞれに外部公開ヘッダ、内部用ヘッダ、ソースファイルがあります。~
FreeRTOS_なんとかというフォルダは、FreeRTOS用のファイルであり、ここは自分でいじることはありません。~
自分でいじるのは、srcフォルダにある、cr_startup_lpc17.c、main.cと、FreeRTOSConfig.hになります。~

#ref(folder.png,left,nowrap,プロジェクトフォルダ構成)
ざっと、デフォルトで作られたmain.cを見てみると、2つのタスクが作られて、vUserTask1は一定時間ごとに状態を反転させ、vUserTask2は一定時間ごとにカウンタを増やしているのがわかります。~
それぞれに関連性はないので協調して動くものではないですが、これらが見かけ上同時に、並行して動くんだなということは、なんとなくわかると思います。~

** ソースコード [#r84ebfbc]
*** 共通定義等 [#k9e1191b]
LEDのソースにもありましたが、ピンをGPIOとして使うのでも、ペリフェラルの機能を使うのでも、ポート番号とピン番号の指定が必要になります。~
なので、セットで扱えるような構造体を用意しておきます。~
共通で使うので、src直下に置いておきましょう。~
ファイル名はapldevice.hとしました。~
main関数は、これらのタスクを生成して、スタートさせているだけです。~
RTOSのイメージが、なんとなく掴めるでしょうか。~

 #ifndef APLDEVICE_H_
 #define APLDEVICE_H_
 
 // ポートピンセット
 typedef struct _tagPORTPIN_SET{
 	uint8_t	port_num;	// ポート番号
 	uint8_t pin_num;	// ピン番号
 } PORTPIN_SET, *LPPORTPIN_SET;
 
 #endif /* APLDEVICE_H_ */
次にFreeRTOSConfig.hを見てみましょう。~
この中身をいじることにより、FreeRTOSの機能をカスタマイズすることが可能です。~

----
とりあえず直しておく必要があるのは、configCPU_CLOCK_HZです。~
これは、CPUクロックを設定する必要があるので、ledtest01で使ったのと同じ、SystemCoreClockを設定しておきます。~

次に、各タスクのプライオリティ定義を用意しておきましょう。~
タスク間の関係は、アプリケーション全体で相互に意識しなければならないので、これもsrc直下に置いて共通で使います。~
ファイル名はapltask.hとし、これをincludeすればFreeRTOSのタスク機能が使えるように、FreeRTOS.hとtask.hを参照します。~
 #define configCPU_CLOCK_HZ			( ( unsigned long )SystemCoreClock )

 #ifndef APLTASK_H_
 #define APLTASK_H_
 
 #include "FreeRTOS.h"
 #include "task.h"
 
 #define APL_TASK_PRIORITY		( tskIDLE_PRIORITY + 1 )
 #define LED_TASK_PRIORITY		( tskIDLE_PRIORITY + 2 )
 #define LCD_TASK_PRIORITY		( tskIDLE_PRIORITY + 2 )
 
 #define APL_STACK_SIZE	64
 #define LED_STACK_SIZE	64
 #define LCD_STACK_SIZE	64
 
 void TaskDelay( portTickType time );
 
 #endif /* APLTASK_H_ */
ただし、実はこれではビルドが通りません。~
SystemCoreClockというのは、元々CMSISライブラリのsystem_LPC17xx.cに定義されているものであり、CMSISライブラリのincフォルダにあるsystem_LPC17xx.hでexternされています。~

タスクプライオリティの関係は、アプリケーションとしての動作をアイドルの一つ上に、表示系をその一つ上としました。~
スタックサイズも、全体のリソース管理という意味から、一箇所にまとまっていた方がいいと思うので、一緒に記載しています。~
このため、CMSISライブラリを参照する必要がありますが、ledtest01の時には、CMSIS Library to link project toを設定するところがあり、CMSISv2p00_LPC17XXを選択しましたが、今回はその指定がありませんでした。~
FreeRTOSを選んだ時には、FreeRTOSのソースが組み入れられるだけで、デフォルトではCMSISライブラリを使えるようには出来ていないようです。~

また、LEDサンプルの時に使ったvTaskDelayのラッパーとして、TaskDelayという関数を定義しておきます。~
TaskDelayの実体は、apltask.cファイルをsrc直下に作って、以下のように作っておきます。~
ledtest01の時と同じように、CMSISv2p00_LPC17xxと、DriverLibを使用してLEDの点滅をさせたいので、このプロジェクトに対して、これらを使えるように、設定を変更します。~

 #include "apltask.h"
 
 void TaskDelay( portTickType time )
 {
 	vTaskDelay( time / portTICK_RATE_MS );
 }
ledtest02のプロジェクトを、右クリックからプロパティを開きます。~
まず、「Project References」で、CMSISv2p00_LPC17xxと、DriverLibの両方にチェックを入れておきます。~

vTaskDelayは、引数がtick数であり、msではありません。~
前回書きましたが、FreeRTOSConfig.h内のconfigTICK_RATE_HZで、毎秒1000tickという指定をしているので、たまたま1tickが1msと同義になっているだけにすぎません。~
configTICK_RATE_HZの値を書き換えてしまうと、ms単位の動作を期待しているのに、実際にsleepする時間が変化してしまいます。~
このため、msを指定できるラッパー関数を用意して、それに対応します。~
#ref(prjref.png,left,nowrap,プロジェクト参照)

参考までに、portTICK_RATE_MSは、FreeRTOS内で以下のように定義されています。~
次に、「C/C++ General」の「Paths and Symbols」を開いて、includeとライブラリの設定をします。~
IncludesタブのLanguagesがGNU Cのところを選択し、/CMSISv2p00_LPC17xx/incと、/DriverLib/incを追加します。~

 #define portTICK_RATE_MS			( ( portTickType ) 1000 / configTICK_RATE_HZ )		
#ref(includes.png,left,nowrap,Includeパス設定)

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

タスクの次は、イベントキューを使うための定義を用意します。~
イベントキューは、LCDに表示するためのデータをLCDタスクに送信するために使います。~
ファイル名はaplqueue.hとし、これをincludeすればFreeRTOSのキュー機能が使えるように、FreeRTOS.hとqueue.hを参照します。~
こちらも、キュー全体のリソース管理という意味から、src直下に置いて共通で使います。~
#ref(libraries.png,left,nowrap,ライブラリ設定)

 #ifndef APLQUEUE_H_
 #define APLQUEUE_H_
 
 #include "FreeRTOS.h"
 #include "queue.h"
 
 // イベントキューデータ
 typedef struct _tagQUE_DATA{
 	uint8_t	evcode;	// イベント番号
 	int32_t	param;	// パラメータ
 	void	*data;	// イベントデータ
 } QUE_DATA, *LPQUE_DATA;
 
 #define LCD_QUE_LENGTH	32
 
 #endif /* APLQUEUE_H_ */
最後に、Library Pathsタブに、/CMSISv2p00_LPC17xx/Debugと、/DriverLib/Debugを追加します。~

キューのデータは汎用的にQUE_DATAとして定義し、これを送受信します。~
タスクで受け取って動作を切り分けるためのイベントコード、そのパラメータ、パラメータでは足りないデータを送りたい時に、それをぶら下げるためのポインタを定義しておきます。~
#ref(librarypaths.png,left,nowrap,ライブラリパス設定)

そして、LCDのキューとしては、QUE_DATAを32個分までためられるものとします。~
LCDの表示領域は、16文字*2行の最大32文字なので、一つのQUE_DATAに1文字をぶら下げたら最大サイズを食うことになりますが、まとめて書きたい時は文字列として一つのQUE_DATAで送ればいいですし、LCDタスクが処理しきる前にこのキューを食いつぶすような送り方をすることは、よほどないでしょう。~
これでヘッダファイルを参照出来るようになったので、FreeRTOSConfig.hの先頭で、LPC17xx.hをincludeするようにしておきましょう。~

----
 #include "LPC17xx.h"

*** メイン [#cdae8a84]
ビルドを行い、ledtest02.axfが出来上がればOKです。~

まず先に、main.cの中身を書いてしまいます。~
ビルドは通りましたが、実はまだ、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 "led/led.h"
 #include "lcd/lcd.h"
 #include "apl/apl.h"
 
 static LED_DATA	led_data;
 static LCD_DATA	lcd_data;
 static APL_DATA	apl_data;
 
 int main(void) {
 	// TODO: insert code here
 	PORTPIN_SET	portpinset;
 
 	// 管理データ初期化
 	LED_InitHandle( &led_data );
 	LCD_InitHandle( &lcd_data );
 	APL_InitHandle( &apl_data );
 
 	// LEDポートピン設定
 	portpinset.port_num	= PINSEL_PORT_0;
 	portpinset.pin_num	= PINSEL_PIN_22;
 	LED_Setting( &led_data, &portpinset );
 
 	// LCDポートピン設定
 	portpinset.port_num	= PINSEL_PORT_0;
 	portpinset.pin_num	= PINSEL_PIN_17;
 	LCD_Setting( &lcd_data, LCD_PORTPIN_TYPE_DATA4, &portpinset );
 	portpinset.pin_num	= PINSEL_PIN_15;
 	LCD_Setting( &lcd_data, LCD_PORTPIN_TYPE_DATA5, &portpinset );
 	portpinset.pin_num	= PINSEL_PIN_16;
 	LCD_Setting( &lcd_data, LCD_PORTPIN_TYPE_DATA6, &portpinset );
 	portpinset.port_num	= PINSEL_PORT_2;
 	portpinset.pin_num	= PINSEL_PIN_1;
 	LCD_Setting( &lcd_data, LCD_PORTPIN_TYPE_RS, &portpinset );
 	portpinset.pin_num	= PINSEL_PIN_2;
 	LCD_Setting( &lcd_data, LCD_PORTPIN_TYPE_E, &portpinset );
 	portpinset.pin_num	= PINSEL_PIN_3;
 	LCD_Setting( &lcd_data, LCD_PORTPIN_TYPE_DATA7, &portpinset );
 
 	// モジュール初期化
 	LED_Init( &led_data, LED_ONOFF_ON );
 	LCD_Init( &lcd_data );
 	APL_Init( &apl_data );
 
 	// ハンドル設定
 	APL_SetLCDHandle( &apl_data, &lcd_data );
 
 	// タスク生成
 	LED_Start( &led_data, ( int8_t* )"LED" );
 	LCD_Start( &lcd_data, ( int8_t* )"LCD" );
 	APL_Start( &apl_data, ( int8_t* )"APL" );
 
 	// スケジューラ実行
 	vTaskStartScheduler();
 
 	return 0;
 }
 #include "lpc17xx_pinsel.h"
 #include "lpc17xx_gpio.h"

----
次に、FreeRTOSを使うためのヘッダをincludeします。~
基本のヘッダと、今回はタスクを使うので、タスクのヘッダを指定します。~

*** LEDモジュール [#ve9bd177]
 #include "FreeRTOS.h"
 #include "task.h"

次に、LEDモジュールです。~
まずは、外部ヘッダのled.h。~
タスクで使うスタック領域のサイズとプライオリティを定義しておきます。~

 #ifndef LED_H_
 #define LED_H_
 
 // include定義
 #include "lpc17xx_pinsel.h"
 #include "apldevice.h"
 #include "apltask.h"
 
 // LEDモジュールデータ
 typedef struct _tagLED_DATA{
 	xTaskHandle	htask;		// タスクハンドル
 	PORTPIN_SET	portpin;	// ポートピンセット
 } LED_DATA, *LPLED_DATA;
 
 typedef enum _tagLED_ONOFF_MODE{
 	LED_ONOFF_OFF,
 	LED_ONOFF_ON
 } LED_ONOFF_MODE;
 
 // LEDハンドル初期化
 int LED_InitHandle( LPLED_DATA handle );
 
 // LEDハード設定
 int LED_Setting( LPLED_DATA handle, LPPORTPIN_SET lpportpin );
 
 // LEDモジュール初期化
 int LED_Init( LPLED_DATA handle, LED_ONOFF_MODE onoff );
 
 // LEDタスクスタート
 int LED_Start( LPLED_DATA handle, const int8_t* const name );
 
 #endif /* LED_H_ */
 #define LED_STACK_SIZE 64
 #define LED_TASK_PRIO tskIDLE_PRIORITY + 1

次に、内部用ヘッダのledin.h。~
先にタスクの処理を書いてしまいます。~

 #ifndef LEDIN_H_
 #define LEDIN_H_
 
 // LEDタスク
 void led_task( void *param );
 
 #endif /* LEDIN_H_ */

最後に、LEDモジュール本体の、led.cです。~

 // include定義
 #include "lpc17xx_pinsel.h"
 #include "lpc17xx_gpio.h"
 #include "led.h"
 #include "ledin.h"
 
 // LEDハンドル初期化
 int LED_InitHandle( LPLED_DATA handle )
 {
 	if( NULL == handle ){
 		return -1;
 	}
 	handle->portpin.port_num	= PINSEL_PORT_0;
 	handle->portpin.pin_num		= PINSEL_PIN_0;
 	handle->htask				= NULL;
 	return 0;
 }
 
 // LEDハード設定
 int LED_Setting( LPLED_DATA handle, LPPORTPIN_SET lpportpin )
 {
 	if( NULL == handle || NULL == lpportpin ){
 		return -1;
 	}
 	handle->portpin = *lpportpin;
 	return 0;
 }
 
 // LEDモジュール初期化
 int LED_Init( LPLED_DATA handle, LED_ONOFF_MODE onoff )
 {
 	PINSEL_CFG_Type	pincfg;	// ピン設定構造体
 
 	if( NULL == handle ){
 		return -1;
 	}
 	if( NULL != handle->htask ){
 		return -1;
 	}
 
 	// LED初期化
 	pincfg.Funcnum		= PINSEL_FUNC_0;
 	pincfg.OpenDrain	= PINSEL_PINMODE_NORMAL;
 	pincfg.Pinmode		= PINSEL_PINMODE_PULLUP;
 	pincfg.Portnum		= handle->portpin.port_num;
 	pincfg.Pinnum		= handle->portpin.pin_num;
 	PINSEL_ConfigPin( &pincfg );
 	GPIO_SetDir( handle->portpin.port_num, ( 1 << handle->portpin.pin_num ), 1 );
 	if( LED_ONOFF_OFF == onoff ){	// OFFで初期化
 		GPIO_ClearValue( handle->portpin.port_num, ( 1 << handle->portpin.pin_num ));
 	} else {						// ONで初期化
 		GPIO_SetValue( handle->portpin.port_num, ( 1 << handle->portpin.pin_num ));
 	}
 	return 0;
 }
 
 // LEDタスクスタート
 int LED_Start( LPLED_DATA handle, const int8_t* const name )
 {
 	if( NULL == handle ){
 		return -1;
 	}
 	if( NULL != handle->htask ){
 		return -1;
 	}
 	// タスク生成
 	if( pdTRUE != xTaskCreate( led_task, name, LED_STACK_SIZE, handle, LED_TASK_PRIORITY, &handle->htask )){
 		return -1;
 	}
 	return 0;
 }
 
 // LEDタスク
 void led_task( void *param ){
 	LPLED_DATA	handle	= ( LPLED_DATA )param;
 	uint32_t	state	= 0;
 //	unsigned portBASE_TYPE freesize = 0;
 
 void LedTask(void *param) {
 	uint32_t state = 0;
 	while (1) {
 		vTaskDelay(500);
 		state = GPIO_ReadValue( handle->portpin.port_num );
 		GPIO_ClearValue( handle->portpin.port_num, state & (1 << handle->portpin.pin_num));
 		GPIO_SetValue( handle->portpin.port_num, ~state & (1 << handle->portpin.pin_num));
 //		freesize = uxTaskGetStackHighWaterMark(NULL);
 		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));
 	}
 }

----
そして、メイン関数は以下のようになります。~

*** LCDモジュール [#tfde4bc2]

次に、LCDモジュールです。~
まずは、外部ヘッダのlcd.h。~

 #ifndef LCD_H_
 #define LCD_H_
 int main(void) {
 	// TODO: insert code here
 	PINSEL_CFG_Type pincfg;	// ピン設定構造体
 
 // include定義
 #include "lpc17xx_pinsel.h"
 #include "apldevice.h"
 #include "apltask.h"
 #include "aplqueue.h"
 	// 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));
 
 typedef enum _tagLCD_PORTPIN_TYPE{
 	LCD_PORTPIN_TYPE_RS,
 	LCD_PORTPIN_TYPE_E,
 	LCD_PORTPIN_TYPE_DATA4,
 	LCD_PORTPIN_TYPE_DATA5,
 	LCD_PORTPIN_TYPE_DATA6,
 	LCD_PORTPIN_TYPE_DATA7,
 	LCD_PORTPIN_TYPE_MAX
 } LCD_PORTPIN_TYPE;
	xTaskCreate( LedTask, ( signed portCHAR * ) "LedTask", LED_STACK_SIZE, NULL, LED_TASK_PRIO, NULL );
 
 // LCDモジュールデータ
 typedef struct _tagLCD_DATA{
 	xTaskHandle		htask;							// タスクハンドル
 	xQueueHandle	hque;							// キューハンドル
 	PORTPIN_SET		portpin[LCD_PORTPIN_TYPE_MAX];	// ポートピンセット
 	uint8_t			ready;							// 準備状態
 } LCD_DATA, *LPLCD_DATA;
 	vTaskStartScheduler();
 
 // LCDハンドル初期化
 int LCD_InitHandle( LPLCD_DATA handle );
 
 // LCDハード設定
 int LCD_Setting( LPLCD_DATA handle, LCD_PORTPIN_TYPE type, LPPORTPIN_SET lpportpin );
 
 // LCDモジュール初期化
 int LCD_Init( LPLCD_DATA handle );
 
 // LCDタスクスタート
 int LCD_Start( LPLCD_DATA handle, const int8_t* const name );
 
 // LCD準備状態取得
 int LCD_GetReady( LPLCD_DATA handle );
 
 // LCD文字送信
 int LCD_SendChar( LPLCD_DATA handle, char ch );
 
 // LCD文字列送信
 int LCD_SendString( LPLCD_DATA handle, char *str );
 
 // LCD数字送信
 int LCD_SendNumber( LPLCD_DATA handle, int32_t num );
 
 // LCD消去
 int LCD_Clear( LPLCD_DATA handle );
 
 // LCD位置設定
 int LCD_SetPos( LPLCD_DATA handle, uint8_t x, uint8_t y );
 
 #endif /* LCD_H_ */

次に、内部用ヘッダのlcdin.h。~

 #ifndef LCDIN_H_
 #define LCDIN_H_
 
 // 送信モード
 typedef enum _tagLCDIN_SENDMODE{
 	LCDIN_SENDMODE_COMMAND,
 	LCDIN_SENDMODE_DATA
 } LCDIN_SENDMODE;
 
 // イベントコード
 typedef enum _tagLCDIN_EVENTCODE{
 	LCDIN_EVENTCODE_SENDCHAR,		// 文字表示
 	LCDIN_EVENTCODE_SENDSTR,		// 文字列表示
 	LCDIN_EVENTCODE_SENDNUMBER,		// 数字表示
 	LCDIN_EVENTCODE_SENDCOMMAND		// コマンド送信
 } LCDIN_EVENTCODE;
 
 // LCDタスク
 void lcdin_task( void *param );
 
 // LCDデバイス初期化
 void lcdin_devinit( LPLCD_DATA handle );
 
 // LCDデバイス4bit送信
 void lcdin_snd4bit( LPLCD_DATA handle, LCDIN_SENDMODE mode, uint8_t cmd );
 
 // LCDデバイスコマンド送信
 void lcdin_sendcmd( LPLCD_DATA handle, uint32_t cmd );
 
 // LCDデバイス文字送信
 void lcdin_sendchar( LPLCD_DATA handle, char data );
 
 // LCDデバイス数字送信
 void lcdin_sendnumber( LPLCD_DATA handle, int32_t data );
 
 // LCDイベント送信
 int lcdin_sendevent( LPLCD_DATA handle, LPQUE_DATA lpque );
 
 #endif /* LCDIN_H_ */

最後に、LCDモジュール本体の、lcd.cです。~

 // include定義
 #include "lpc17xx_pinsel.h"
 #include "lpc17xx_gpio.h"
 #include "lcd.h"
 #include "lcdin.h"
 
 // LCDコマンド
 typedef enum _tagLCDIN_CMD_MODE{
 	LCDIN_CMD_CLEAR,	// クリア
 	LCDIN_CMD_SETPOS,	// 位置設定
 } LCDIN_CMD_MODE;
 
 static const uint8_t LCDCmd[] = {
 	0x01,	// クリア
 	0x80,	// 位置設定
 };
 
 static const uint8_t LCDCmdWait[] = {
 	0x01,	// クリア
 	0x00,	// 位置設定
 };
 
 // LCDハンドル初期化
 int LCD_InitHandle( LPLCD_DATA handle )
 {
 	int	i	= 0;	// ループ変数
 	if( NULL == handle ){
 		return -1;
 	}
 	for( i = 0; i < LCD_PORTPIN_TYPE_MAX; i++ ){
 		handle->portpin[i].port_num	= PINSEL_PORT_0;
 		handle->portpin[i].pin_num	= PINSEL_PIN_0;
 	}
 	handle->htask	= NULL;
 	handle->hque	= NULL;
 	handle->ready	= 0;
 	return 0;
 }
 
 // LCDハード設定
 int LCD_Setting( LPLCD_DATA handle, LCD_PORTPIN_TYPE type, LPPORTPIN_SET lpportpin )
 {
 	if( NULL == handle || 0 > type || LCD_PORTPIN_TYPE_MAX <= type || NULL == lpportpin ){
 		return -1;
 	}
 	handle->portpin[type] = *lpportpin;
 	return 0;
 }
 
 // LCDモジュール初期化
 int LCD_Init( LPLCD_DATA handle )
 {
 	PINSEL_CFG_Type	pincfg;		// ピン設定構造体
 	int				i	= 0;	// ループ変数
 
 	if( NULL == handle ){
 		return -1;
 	}
 	if( NULL != handle->htask ){
 		return -1;
 	}
 
 	// LCD初期化
 	pincfg.Funcnum		= PINSEL_FUNC_0;
 	pincfg.OpenDrain	= PINSEL_PINMODE_NORMAL;
 	pincfg.Pinmode		= PINSEL_PINMODE_PULLUP;
 	for( i = 0; i < LCD_PORTPIN_TYPE_MAX; i++ ){
 		pincfg.Portnum	= handle->portpin[i].port_num;
 		pincfg.Pinnum	= handle->portpin[i].pin_num;
 		PINSEL_ConfigPin( &pincfg );
 		GPIO_SetDir( handle->portpin[i].port_num, ( 1 << handle->portpin[i].pin_num ), 1 );
 		GPIO_ClearValue( handle->portpin[i].port_num, ( 1 << handle->portpin[i].pin_num ));
 	}
 	return 0;
 }
 
 // LCDタスクスタート
 int LCD_Start( LPLCD_DATA handle, const int8_t* const name )
 {
 	if( NULL == handle ){
 		return -1;
 	}
 	if( NULL != handle->htask || NULL != handle->hque ){
 		return -1;
 	}
 	// キュー生成
 	handle->hque = xQueueCreate( LCD_QUE_LENGTH, sizeof( QUE_DATA ));
 	if( NULL == handle->hque ){
 		return -1;
 	}
 	// タスク生成
 	if( pdTRUE != xTaskCreate( lcdin_task, name, LCD_STACK_SIZE, handle, LCD_TASK_PRIORITY, &handle->htask )){
 		vQueueDelete( handle->hque );
 		handle->hque = NULL;
 		return -1;
 	}
 	return 0;
 }
 
 // LCD準備状態取得
 int LCD_GetReady( LPLCD_DATA handle )
 {
 	if( NULL == handle ){
 		return -1;
 	}
 	return handle->ready;
 }
 
 // LCDタスク
 void lcdin_task( void *param ){
 	LPLCD_DATA	handle	= ( LPLCD_DATA )param;
 	char		*str	= NULL;		// 文字列表示用
 	QUE_DATA	que_data;	// イベントキューデータ
 	unsigned portBASE_TYPE freesize = 0;
 
 	lcdin_devinit( handle );
 
 	while (1) {
 		if( pdTRUE != xQueueReceive( handle->hque, &que_data, portMAX_DELAY )){
 			continue;
 		}
 		switch( que_data.evcode ){
 		case LCDIN_EVENTCODE_SENDCHAR :		// 文字表示
 			lcdin_sendchar( handle, ( char )que_data.param );
 			break;
 		case LCDIN_EVENTCODE_SENDSTR :		// 文字列表示
 			str = que_data.data;
 			if( NULL != str ){
 				while( *str ){
 					lcdin_sendchar( handle, *str );
 					str++;
 				}
 				vPortFree( que_data.data );
 			}
 			break;
 		case LCDIN_EVENTCODE_SENDNUMBER :	// 数字表示
 			lcdin_sendnumber( handle, que_data.param );
 			break;
 		case LCDIN_EVENTCODE_SENDCOMMAND :	// コマンド送信
 			lcdin_sendcmd( handle, ( uint32_t )que_data.param );
 			break;
 		default :
 			break;
 		}
 		freesize = uxTaskGetStackHighWaterMark(NULL);
 	}
 }
 
 // LCDデバイス初期化
 void lcdin_devinit( LPLCD_DATA handle )
 {
 	// 初期化コマンド送信
 	TaskDelay( 50 );
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b0011 );
 	TaskDelay( 5 );
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b0011 );
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b0011 );
 
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b0010 );
 
 	// Function Set
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b0010 );
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b1000 );
 
 	// Display on/off
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b0000 );
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b1111 );
 
 	// Clear Display
 	lcdin_sendcmd( handle, ( LCDCmdWait[LCDIN_CMD_CLEAR] << 8 ) | LCDCmd[LCDIN_CMD_CLEAR] );
 
 	// Entry Mode
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b0000 );
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, 0b0110 );
 
 	handle->ready = 1;
 }
 
 // LCDデバイス4bit送信
 void lcdin_snd4bit( LPLCD_DATA handle, LCDIN_SENDMODE mode, uint8_t cmd )
 {
 	uint8_t	i	= 0;	// ループ変数
 
 	if( LCDIN_SENDMODE_COMMAND == mode ){
 		GPIO_ClearValue( handle->portpin[LCD_PORTPIN_TYPE_RS].port_num, ( 1 << handle->portpin[LCD_PORTPIN_TYPE_RS].pin_num ));	// コマンドモード
 	} else {
 		GPIO_SetValue( handle->portpin[LCD_PORTPIN_TYPE_RS].port_num, ( 1 << handle->portpin[LCD_PORTPIN_TYPE_RS].pin_num ));	// データモード
 	}
 	for( i = LCD_PORTPIN_TYPE_DATA4; i < LCD_PORTPIN_TYPE_MAX; i++ ){
 		GPIO_ClearValue( handle->portpin[i].port_num, ( 1 << ( handle->portpin[i].pin_num )));
 		GPIO_SetValue( handle->portpin[i].port_num, ((( cmd >> ( i - LCD_PORTPIN_TYPE_DATA4 )) & 1 ) << ( handle->portpin[i].pin_num )));
 	}
 	GPIO_SetValue( handle->portpin[LCD_PORTPIN_TYPE_E].port_num, ( 1 << handle->portpin[LCD_PORTPIN_TYPE_E].pin_num ));	// Enable
 	TaskDelay( 1 );
 	GPIO_ClearValue( handle->portpin[LCD_PORTPIN_TYPE_E].port_num, ( 1 << handle->portpin[LCD_PORTPIN_TYPE_E].pin_num ));	// NotEnable
 	TaskDelay( 1 );
 }
 
 // LCDデバイスコマンド送信
 void lcdin_sendcmd( LPLCD_DATA handle, uint32_t cmd )
 {
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, ( cmd >> 4 ) & 0x0f );
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_COMMAND, cmd & 0x0f );
 	if(( cmd >> 8 ) & 0xff ){
 		TaskDelay(( cmd >> 8 ) & 0xff );
 	}
 }
 
 // LCDデバイス文字送信
 void lcdin_sendchar( LPLCD_DATA handle, char data )
 {
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_DATA, data >> 4 );
 	lcdin_snd4bit( handle, LCDIN_SENDMODE_DATA, data );
 }
 
 // LCDデバイス数字送信
 void lcdin_sendnumber( LPLCD_DATA handle, int32_t data )
 {
 	int32_t	tmpnum	= data;	// 数字変換用
 	int8_t	keta	= 0;	// 数字変換用
 	int8_t	i= 0, j = 0;	// ループ変数
 
 	if( 0 > data ){
 		lcdin_sendchar( handle, '-' );
 		data = -data;
 	}
 	if( 0 == data ){
 		tmpnum++;	// 0の桁を1とするため
 	}
 	while( 0 != tmpnum ){
 		tmpnum /= 10;
 		keta++;
 	}
 	for( i = keta - 1; i >= 0; i-- ){
 		tmpnum = 1;
 		for( j = 0; j < i; j++ ){
 			tmpnum *= 10;
 		}
 		lcdin_sendchar( handle, 48 + data / tmpnum );
 		data %= tmpnum;
 	}
 }
 
 // LCDイベント送信
 int lcdin_sendevent( LPLCD_DATA handle, LPQUE_DATA lpque )
 {
 	if( NULL == handle->hque ){
 		return -1;
 	}
 	if( pdTRUE != xQueueSend( handle->hque, lpque, 0 )){
 		return -1;
 	}
 	return 0;
 }
 
 // LCD文字送信
 int LCD_SendChar( LPLCD_DATA handle, char ch )
 {
 	QUE_DATA	que_data;
 
 	if( NULL == handle ){
 		return -1;
 	}
 
 	que_data.evcode	= LCDIN_EVENTCODE_SENDCHAR;
 	que_data.param	= ( int32_t )ch;
 	que_data.data	= NULL;
 
 	return lcdin_sendevent( handle, &que_data );
 }
 
 // LCD文字列送信
 int LCD_SendString( LPLCD_DATA handle, char *str )
 {
 	QUE_DATA	que_data;
 	size_t		size	= 0;	// 確保サイズ
 	int			ret		= -1;	// 結果格納用
 	char		*ptr	= NULL;	// コピー用
 
 	if( NULL == handle || NULL == str ){
 		return -1;
 	}
 	ptr = str;
 	while( *ptr ){
 		size++;
 		ptr++;
 	}
 	if( 0 == size ){
 		return -1;
 	}
 
 	que_data.evcode	= LCDIN_EVENTCODE_SENDSTR;
 	que_data.param	= 0;
 	que_data.data	= pvPortMalloc( size );
 	if( NULL == que_data.data ){
 		return -1;
 	}
 	ptr = ( char* )que_data.data;
 	while( *str ){
 		*ptr = *str;
 		str++;
 		ptr++;
 	}
 
 	ret = lcdin_sendevent( handle, &que_data );
 	if( 0 > ret ){
 		vPortFree( que_data.data );
 	}
 	return ret;
 }
 
 // LCD数字送信
 int LCD_SendNumber( LPLCD_DATA handle, int32_t num )
 {
 	QUE_DATA	que_data;
 
 	if( NULL == handle ){
 		return -1;
 	}
 
 	que_data.evcode	= LCDIN_EVENTCODE_SENDNUMBER;
 	que_data.param	= num;
 	que_data.data	= NULL;
 
 	return lcdin_sendevent( handle, &que_data );
 }
 
 // LCD消去
 int LCD_Clear( LPLCD_DATA handle )
 {
 	QUE_DATA	que_data;
 	uint32_t	cmd = ( LCDCmdWait[LCDIN_CMD_CLEAR] << 8 ) | LCDCmd[LCDIN_CMD_CLEAR];
 
 	if( NULL == handle ){
 		return -1;
 	}
 
 	que_data.evcode	= LCDIN_EVENTCODE_SENDCOMMAND;
 	que_data.param	= ( int32_t )cmd;
 	que_data.data	= NULL;
 
 	return lcdin_sendevent( handle, &que_data );
 }
 
 // LCD位置設定
 int LCD_SetPos( LPLCD_DATA handle, uint8_t x, uint8_t y )
 {
 	QUE_DATA	que_data;
 	uint32_t	cmd = ( LCDCmdWait[LCDIN_CMD_SETPOS] << 8 ) | LCDCmd[LCDIN_CMD_SETPOS];
 
 	if( NULL == handle ){
 		return -1;
 	}
 	if( 0 != y ){
 		cmd += 0x40;
 	}
 	cmd += x;
 
 	que_data.evcode	= LCDIN_EVENTCODE_SENDCOMMAND;
 	que_data.param	= ( int32_t )cmd;
 	que_data.data	= NULL;
 
 	return lcdin_sendevent( handle, &que_data );
 }

----
** ソースコード解説 [#g9c0489b]
まず、メイン関数ですが、LEDがつながっているポート0の22番ピンの設定とON初期化はledtest01と同じです。~
その後、LEDタスクを生成して、タスク動作をスタートさせるという流れになっています。~

*** APLモジュール [#bce77d3d]
xTaskCreateはタスクの生成を行うマクロですが、第1引数にタスク関数の指定、第2引数にタスク名、第3引数にタスクのスタックサイズ、第4引数にタスクに渡すパラメータ、第5引数にタスクの優先度、第6引数にタスクハンドルを受け取る変数の参照を指定します。~

最後に、APLモジュールです。~
まずは、外部ヘッダのapl.h。~
第1引数のタスク関数は、戻り値なし(void)でvoid*型の関数を指定する必要があります。~
また、この関数はreturnしてはいけません。~
無限ループ等で関数から抜けないようにしておく必要があります。~
LedTask関数も、そのように作っています。~

 #ifndef APL_H_
 #define APL_H_
 
 // include定義
 #include "apldevice.h"
 #include "apltask.h"
 #include "lcd/lcd.h"
 
 // APLモジュールデータ
 typedef struct _tagAPL_DATA{
 	xTaskHandle	htask;	// タスクハンドル
 	LPLCD_DATA	hlcd;	// LCDハンドル
 } APL_DATA, *LPAPL_DATA;
 
 // APLハンドル初期化
 int APL_InitHandle( LPAPL_DATA handle );
 
 // LCDハンドル設定
 int APL_SetLCDHandle( LPAPL_DATA hapl, LPLCD_DATA hlcd );
 
 // APLモジュール初期化
 int APL_Init( LPAPL_DATA handle );
 
 // APLタスクスタート
 int APL_Start( LPAPL_DATA handle, const int8_t* const name );
 
 #endif /* APL_H_ */
第2引数のタスク名は、FreeRTOSConfig.h内のconfigMAX_TASK_NAME_LEN定義の文字数以下で、タスク名を指定します。~
デフォルトでは12文字までですね。~
終端文字を入れて12文字なので、実質11文字になります。~

次に、内部用ヘッダのaplin.h。~
 #define configMAX_TASK_NAME_LEN		( 12 )

 #ifndef APLIN_H_
 #define APLIN_H_
 
 // APLタスク
 void apl_task( void *param );
 
 #endif /* APLIN_H_ */
第3引数のスタックサイズには、そのタスクで使用するスタックの割り当てサイズを指定します。~
スタックとは、ローカル変数や関数コール時の引数等を一時的に保存するための領域で、基本的には大きいサイズのローカル変数を用意したり、関数コールを繰り返して深いネストまで行くと、消費量が多くなります。~

最後に、APLモジュール本体の、apl.cです。~
本当に必要なサイズが、実際にどれくらいかというのを厳密に調べるのは結構大変なのですが、FreeRTOSにはスタック領域の残量が最小でどれくらいだったかというのがわかる機能もありますので、それを指標に決めることも出来ます。~

 // include定義
 #include "apl.h"
 #include "aplin.h"
 
 // APLハンドル初期化
 int APL_InitHandle( LPAPL_DATA handle )
 {
 	if( NULL == handle ){
 		return -1;
 	}
 	handle->htask	= NULL;
 	handle->hlcd	= NULL;
 	return 0;
 }
 
 // LCDハンドル設定
 int APL_SetLCDHandle( LPAPL_DATA hapl, LPLCD_DATA hlcd )
 {
 	if( NULL == hapl || NULL == hlcd ){
 		return -1;
 	}
 	hapl->hlcd = hlcd;
 	return 0;
 }
 
 // APLモジュール初期化
 int APL_Init( LPAPL_DATA handle )
 {
 	if( NULL == handle ){
 		return -1;
 	}
 	if( NULL != handle->htask ){
 		return -1;
 	}
 
 	return 0;
 }
 
 // APLタスクスタート
 int APL_Start( LPAPL_DATA handle, const int8_t* const name )
 {
 	if( NULL == handle ){
 		return -1;
 	}
 	if( NULL != handle->htask ){
 		return -1;
 	}
 	// タスク生成
 	if( pdTRUE != xTaskCreate( apl_task, name, APL_STACK_SIZE, handle, APL_TASK_PRIORITY, &handle->htask )){
 		return -1;
 	}
 	return 0;
 }
 
 void apl_task( void *param ){
 	LPAPL_DATA	handle	= ( LPAPL_DATA )param;
 	unsigned portBASE_TYPE freesize = 0;
 
 	// LCD準備待機
 	while( 1 != LCD_GetReady( handle->hlcd )){
 		TaskDelay(100);
 	}
 
 	while (1) {
 		LCD_SendChar( handle->hlcd, 'A' );
 		LCD_SendNumber( handle->hlcd, 0 );
 		LCD_SendChar( handle->hlcd, 'B' );
 		LCD_SendNumber( handle->hlcd, 1234567890 );
 		LCD_SetPos( handle->hlcd, 0, 1 );
 		LCD_SendChar( handle->hlcd, 'C' );
 		LCD_SendNumber( handle->hlcd, 2 );
 		LCD_SendChar( handle->hlcd, 'D' );
 		LCD_SendNumber( handle->hlcd, -987654321 );
 		TaskDelay(5000);
 		LCD_Clear( handle->hlcd );
 		LCD_SendString( handle->hlcd, "LCD\xc3\xbd\xc4\xc1\xad\xb3" );
 		TaskDelay(5000);
 		LCD_Clear( handle->hlcd );
 		freesize = uxTaskGetStackHighWaterMark(NULL);
 	}
 }
今回はひとまず、64を指定します。~
実際のメモリサイズとしては、ここで指定した値に4をかけたものが確保されることになります。~
つまり、64*4で256バイトのスタックが確保されます。~

** ソースコード解説 [#t6a7ed0f]
第4引数のパラメータですが、今回はタスクに何も渡すものがないので、NULLを指定しておきます。~

共通定義のところはある程度書いたので、メインから。~
第5引数のタスク優先度ですが、0からFreeRTOSConfig.h内のconfigMAX_PRIORITIES-1までの値が指定出来ます。~
デフォルトでは5なので、0(低優先度)〜4(高優先度)の優先度が指定出来ます。~

*** メイン [#eebb2aea]
 #define configMAX_PRIORITIES		( ( unsigned portBASE_TYPE ) 5 )

先頭はお決まりのincludeとCRPです。~
ただし、CPUが何も実行するタスクがない場合、FreeRTOSが自動的に生成するアイドルタスクが実行されますが、この優先度は最低優先度の0になっています。~
このため、優先度0はアイドルタスク専用としておき、1〜4までを使うのがいいと思います。~

まずは、LED、LCD、APLと、各モジュールのヘッダをincludeし、ハンドルとして使うためのモジュールを管理するデータの実体を宣言しています。~
main関数の先頭で、それらの領域を初期化します。~
今回、LEDタスクとしての優先度は、アイドルタスクの次に低い優先度としておきます。~
今後、優先度の高い他のタスクを動作させるようになり、そのタスクが大幅に処理時間を食うような事があった場合、優先度の低いLEDタスクには処理が回らず、LEDの点滅が行われなくなり、目で状況が確認できるという設計も出来るかもしれません。~

次に、LEDとLCDで使うポートとピンの設定をしています。~
LEDは一つのみですが、LCDは6本使うので、enum型で指定するようにしています。~
第6引数のタスクハンドルですが、タスクハンドルを後で使用することがない場合には、受け取る必要もないので、NULLを指定します。~
例えば、別のタスクからLEDタスクを削除したり、サスペンドしたりレジュームしたりする場合は、タスクハンドルが必要になるので、ここで受け取っておいたハンドルを使用します。~

次に、各モジュールを使えるように初期化します。~
こちらは領域の初期化ではなく、GPIOの設定等をしています。~
APLモジュールは、実際にはやることがないので、呼ぶだけで何もしていませんが。~
vTaskStartSchedulerは、アイドルタスクを作成して、各タスクの実行を開始する命令です。~
アイドルタスクとLEDタスクが開始され、優先度の高いLEDタスクから動き始めます。~

そして、APLモジュールでLCDを操作するために、LCDのハンドルをAPLモジュールに設定しておきます。~
----

最後に、各モジュールのタスクやキューを生成して、スケジューラ実行で実際にタスクを走らせています。~
同じモジュールのインスタンスを複数作れることを考えて、タスク名も指定できるようにしています。~
最後に、タスク関数の中身ですが、基本的にはledtest01の無限ループの内容そのものです。~
一つ違うのは、500ms待つための関数が、タスクを待ち状態にさせるものに変更になっているという点です。~

ハンドル的な考え方以外は、そんなに大したことしてないですね。~
vTaskDelayは、指定されたtick数分、タスクを待ち状態にするという関数ですが、FreeRTOSConfig.h内のconfigTICK_RATE_HZで、毎秒1000tickという指定をしているので、1tickは1msと同義になります。~

----
 #define configTICK_RATE_HZ			( ( portTickType ) 1000 )

*** LEDモジュール [#a20634e5]
今回は、LEDタスク以外にはアイドルタスクしかないので、vTaskDelayでタスクが500msの待ち状態になっている間は、アイドルタスクが動いていることになります。~

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

外部ヘッダのled.hには、まずモジュールデータとして、自分自身のタスクハンドルと、自分が使うポートとピンを記憶しておく構造体を持ちます。~
LED_ONOFF_MODEのenumは、ONで初期化、OFFで初期化を選べるように作りました。~
ただ、タスクという概念で動作するようになったので、今後色々なことを並行してやるのに、便利な作りになりました。~
タスクのデバッグも、普通にブレークポイントをはるだけで可能です。~

内部用ヘッダのledin.hは、外部には公開しないタスク関数のみの定義になります。~
** スタックサイズ [#ueae9e98]
今回、スタックサイズに64(*4=128バイト)を指定しましたが、これが実際にどれくらい使われているかを調べてみます。~

LEDモジュール本体の、led.cについては、基本的には[[LEDチカチカ(RTOS)]]でやったものをモジュール化しただけです。~
今は周期的な点滅を繰り返しているだけですが、そのうち外部からの指定で、点滅パターンを変えられるようにしたりなんかすると良さげかもしれないですね。~
FreeRTOSは、xTaskCreateでタスクを生成する時に、指定された分のスタック領域を確保し、内容が0xa5で埋められます。~
そして、スタックという特性上、確保されたメモリの後ろ側から使用します。~
スタック領域が使用された場合、基本的にはそこのメモリアドレスの内容を書き換えるわけですから、値が変わっていれば、そこは使用された領域だというのがわかります。

----
タスク処理中に任意のタイミングで、uxTaskGetStackHighWaterMarkという関数を呼ぶと、確保されているメモリの先頭から、0xa5でなくなる箇所まで遡り、遡った数/4の値を返します。~

*** LCDモジュール [#b6745d09]
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);
 	}
 }

*** APLモジュール [#w988710c]
uxTaskGetStackHighWaterMarkをコールする箇所にブレークポイントを仕掛けておき、止まったらステップ実行して、freesizeに入る値を確認してみます。~
今回は、35という値が帰って来ました。~

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

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

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

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


トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS