頑張ってハンダ付けしましょうw
そんなに大変な作業ではないです。
まぁ、私はフリー拡張スロットのコネクタのハンダ付け、一部失敗して斜めについてますけどね……。
MAPLE boardについてきたのは、TC1602E-25Aという型番のLCDでした。
このLCDは、4または8ビットモード(HD44780互換)ということですから、HD44780のデータシートを探してくることにします。
というわけで、alldatasheetから拾って来ました。
基本的には、AVRの時にやったLCDサンプルと変わりません。
MAPLE board附属の回路図を見れば分かる通り、DB0〜DB3は接続されていませんので、4ビットモードでの動作となります。
また、R/WがGNDに接続されていますので、Writeモード固定ということになります。
Writeモード固定なので、Busyフラグチェックは出来ませんから、コマンド実行後は実行時間分待つ必要があります。
LPC1769は、LPC1768と同じなので、MAPLE boardの取扱説明書「8.3 LPCXPresso LPC1768 使用時」というところのピン配置を見ると、どのピンが何に割当たっているのかわかります。
以下のようになっているみたいですね。
ポート番号 | ピン番号 | LCD |
P0 | 15 | D5 |
P0 | 16 | D6 |
P0 | 17 | D4 |
P2 | 1 | RS |
P2 | 2 | E |
P2 | 3 | D7 |
まずは、ledtest02と同じように、FreeRTOSの新規プロジェクトを作って、ひと通り設定をしておきます。
プロジェクト名は、「lcdtest」としました。
元々のLEDと今回のLCD、それに全体的な管理をするアプリケーションとしてのタスクを作って動かすようにしてみましょう。
それぞれをモジュールとして管理し、モジュールを管理するためのデータをハンドル的に扱えるようにします。
データをハンドル化し、ロジックと切り離すことによって、LEDが2つとか、LCDが2つとかになった場合でも、それぞれを独立して動かせるようになるはずです。
今回の最終的なsrcフォルダ以下の構成は、以下のようになります。
アプリケーションモジュール、LCDモジュール、LEDモジュールをフォルダごとに分け、それぞれに外部公開ヘッダ、内部用ヘッダ、ソースファイルがあります。
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タスクが処理しきる前にこのキューを食いつぶすような送り方をすることは、よほどないでしょう。
まず先に、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モジュールです。
まずは、外部ヘッダの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_ */
次に、内部用ヘッダの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; 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); } }