- 追加された行はこの色です。
- 削除された行はこの色です。
#analog
#norelated
#contents
* LCD文字表示(RTOS) [#bb712a69]
** MAPLE board [#ba36d854]
頑張ってハンダ付けしましょうw~
そんなに大変な作業ではないです。~
まぁ、私はフリー拡張スロットのコネクタのハンダ付け、一部失敗して斜めについてますけどね……。~
** LCDモジュール [#k233bed7]
MAPLE boardについてきたのは、[[TC1602E-25A:http://www.linkman.jp/user/shohin.php?p=61364]]という型番のLCDでした。~
このLCDは、4または8ビットモード(HD44780互換)ということですから、HD44780のデータシートを探してくることにします。~
というわけで、[[alldatasheet:http://www.alldatasheet.jp/datasheet-pdf/pdf/63673/HITACHI/HD44780.html]]から拾って来ました。~
基本的には、AVRの時にやった[[LCDサンプル]]と変わりません。~
** 構成 [#y147a72c]
MAPLE board附属の回路図を見れば分かる通り、DB0〜DB3は接続されていませんので、4ビットモードでの動作となります。~
また、R/WがGNDに接続されていますので、Writeモード固定ということになります。~
Writeモード固定なので、Busyフラグチェックは出来ませんから、コマンド実行後は実行時間分待つ必要があります。~
LPC1769は、LPC1768と同じなので、MAPLE boardの取扱説明書「8.3 LPCXPresso LPC1768 使用時」というところのピン配置を見ると、どのピンが何に割当たっているのかわかります。~
以下のようになっているみたいですね。~
|ポート番号|ピン番号|LCD|h
|P0|15|D5|
|P0|16|D6|
|P0|17|D4|
|P2|1|RS|
|P2|2|E|
|P2|3|D7|
** プロジェクト [#o8640e7e]
まずは、ledtest02と同じように、FreeRTOSの新規プロジェクトを作って、ひと通り設定をしておきます。~
プロジェクト名は、「lcdtest」としました。~
元々のLEDと今回のLCD、それに全体的な管理をするアプリケーションとしてのタスクを作って動かすようにしてみましょう。~
それぞれをモジュールとして管理し、モジュールを管理するためのデータをハンドル的に扱えるようにします。~
データをハンドル化し、ロジックと切り離すことによって、LEDが2つとか、LCDが2つとかになった場合でも、それぞれを独立して動かせるようになるはずです。~
今回の最終的なsrcフォルダ以下の構成は、以下のようになります。~
アプリケーションモジュール、LCDモジュール、LEDモジュールをフォルダごとに分け、それぞれに外部公開ヘッダ、内部用ヘッダ、ソースファイルがあります。~
#ref(folder.png,left,nowrap,プロジェクトフォルダ構成)
** ソースコード [#r84ebfbc]
*** 共通定義等 [#k9e1191b]
LEDのソースにもありましたが、ピンをGPIOとして使うのでも、ペリフェラルの機能を使うのでも、ポート番号とピン番号の指定が必要になります。~
なので、セットで扱えるような構造体を用意しておきます。~
共通で使うので、src直下に置いておきましょう。~
ファイル名はapldevice.hとしました。~
#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_ */
----
次に、各タスクのプライオリティ定義を用意しておきましょう。~
タスク間の関係は、アプリケーション全体で相互に意識しなければならないので、これもsrc直下に置いて共通で使います。~
ファイル名はapltask.hとし、これをincludeすればFreeRTOSのタスク機能が使えるように、FreeRTOS.hとtask.hを参照します。~
#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_ */
タスクプライオリティの関係は、アプリケーションとしての動作をアイドルの一つ上に、表示系をその一つ上としました。~
スタックサイズも、全体のリソース管理という意味から、一箇所にまとまっていた方がいいと思うので、一緒に記載しています。~
また、LEDサンプルの時に使ったvTaskDelayのラッパーとして、TaskDelayという関数を定義しておきます。~
TaskDelayの実体は、apltask.cファイルをsrc直下に作って、以下のように作っておきます。~
#include "apltask.h"
void TaskDelay( portTickType time )
{
vTaskDelay( time / portTICK_RATE_MS );
}
vTaskDelayは、引数がtick数であり、msではありません。~
前回書きましたが、FreeRTOSConfig.h内のconfigTICK_RATE_HZで、毎秒1000tickという指定をしているので、たまたま1tickが1msと同義になっているだけにすぎません。~
configTICK_RATE_HZの値を書き換えてしまうと、ms単位の動作を期待しているのに、実際にsleepする時間が変化してしまいます。~
このため、msを指定できるラッパー関数を用意して、それに対応します。~
参考までに、portTICK_RATE_MSは、FreeRTOS内で以下のように定義されています。~
#define portTICK_RATE_MS ( ( portTickType ) 1000 / configTICK_RATE_HZ )
----
タスクの次は、イベントキューを使うための定義を用意します。~
イベントキューは、LCDに表示するためのデータをLCDタスクに送信するために使います。~
ファイル名はaplqueue.hとし、これをincludeすればFreeRTOSのキュー機能が使えるように、FreeRTOS.hとqueue.hを参照します。~
こちらも、キュー全体のリソース管理という意味から、src直下に置いて共通で使います。~
#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_ */
キューのデータは汎用的にQUE_DATAとして定義し、これを送受信します。~
タスクで受け取って動作を切り分けるためのイベントコード、そのパラメータ、パラメータでは足りないデータを送りたい時に、それをぶら下げるためのポインタを定義しておきます。~
そして、LCDのキューとしては、QUE_DATAを32個分までためられるものとします。~
LCDの表示領域は、16文字*2行の最大32文字なので、一つのQUE_DATAに1文字をぶら下げたら最大サイズを食うことになりますが、まとめて書きたい時は文字列として一つのQUE_DATAで送ればいいですし、LCDタスクが処理しきる前にこのキューを食いつぶすような送り方をすることは、よほどないでしょう。~
//まずはざっくり全部消してしまって、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"
//
//先にタスクの処理を書いてしまいます。~
//
// 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;
// }
//
----
*** メイン [#cdae8a84]
まず先に、main.cの中身を書いてしまいます。~
#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;
}
----
//** ソースコード解説
//まず、メイン関数ですが、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倍近い値を確保していますので、問題ないでしょう。~
//
//自分でタスクのプログラムを書く場合、どこが一番深いネストで、最大のスタックサイズを取りうるところはどこなのかというのは、常に意識しておく必要があります。~
//メモリの容量制限の厳しいマイコンでは、特に意識しないとなりませんね。~
//
//また、これ以外にも、スタックオーバーフローを検知してフック関数を呼んでもらう方法等があります。~
//使う機会があれば、そのうち書いてみたいと思います。~