#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タスクが処理しきる前にこのキューを食いつぶすような送り方をすることは、よほどないでしょう。~ ---- *** メイン [#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モジュール [#ve9bd177] 次に、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); } } ---- *** LCDモジュール [#tfde4bc2] 次に、LCDモジュールです。~ まずは、外部ヘッダのlcd.h。~ #ifndef LCD_H_ #define LCD_H_ // include定義 #include "lpc17xx_pinsel.h" #include "apldevice.h" #include "apltask.h" #include "aplqueue.h" 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; // LCDモジュールデータ typedef struct _tagLCD_DATA{ xTaskHandle htask; // タスクハンドル xQueueHandle hque; // キューハンドル PORTPIN_SET portpin[LCD_PORTPIN_TYPE_MAX]; // ポートピンセット uint8_t ready; // 準備状態 } LCD_DATA, *LPLCD_DATA; // 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 ); } ---- *** APLモジュール [#bce77d3d] 最後に、APLモジュールです。~ まずは、外部ヘッダのapl.h。~ #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_ */ 次に、内部用ヘッダのaplin.h。~ #ifndef APLIN_H_ #define APLIN_H_ // APLタスク void apl_task( void *param ); #endif /* APLIN_H_ */ 最後に、APLモジュール本体の、apl.cです。~ // 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); } } ** ソースコード解説 [#t6a7ed0f] 共通定義のところはある程度書いたので、メインから。~ *** メイン [#sd61156e] 先頭はお決まりのincludeとCRPです。~ まずは、LED、LCD、APLと、各モジュールのヘッダをincludeし、ハンドルとして使うためのモジュールを管理するデータの実体を宣言しています。~ main関数の先頭で、それらの領域を初期化します。~ 次に、LEDとLCDで使うポートとピンの設定をしています。~ LEDは一つのみですが、LCDは6本使うので、enum型で指定するようにしています。~ 次に、各モジュールを使えるように初期化します。~ こちらは領域の初期化ではなく、GPIOの設定等をしています。~ APLモジュールは、実際にはやることがないので、呼ぶだけで何もしていませんが。~ そして、APLモジュールでLCDを操作するために、LCDのハンドルをAPLモジュールに設定しておきます。~ 最後に、各モジュールのタスクやキューを生成して、スケジューラ実行で実際にタスクを走らせています。~ 同じモジュールのインスタンスを複数作れることを考えて、タスク名も指定できるようにしています。~ ハンドル的な考え方以外は、そんなに大したことしてないですね。~ ---- *** LEDモジュール [#f1865ffc] 次に、LEDモジュールです。~ 外部ヘッダのled.hには、まずモジュールデータとして、自分自身のタスクハンドルと、自分が使うポートとピンを記憶しておく構造体を持ちます。~ LED_ONOFF_MODEのenumは、ONで初期化、OFFで初期化を選べるように作りました。~ 内部用ヘッダのledin.hは、外部には公開しないタスク関数のみの定義になります。~ LEDモジュール本体の、led.cについては、基本的には[[LEDチカチカ(RTOS)]]でやったものをモジュール化しただけです。~ 今は周期的な点滅を繰り返しているだけですが、そのうち外部からの指定で、点滅パターンを変えられるようにしたりなんかすると良さげかもしれないですね。~ ---- *** LCDモジュール [#y8d62d65] 次に、今回のメイン、LCDモジュールです。~ 外部ヘッダのlcd.hには、まず、ポートピンのenumを定義しています。~ モジュールデータとして、自分自身のタスクハンドルとコマンドを受け取るためのキューハンドル、ポートピンセットの構造体を持ちます。~ そしてもう一つ、LCDは初期化に多少の時間を必要とするため、タスクの先頭で初期化処理を行います。~ このため、LCDを使用する他タスクは、LCDの初期化が終わっているかどうかを確認した上で、LCDを使う必要があります。~ この、初期化が終わっているかどうかを保存しておくフラグも、モジュールデータに持つことにします。~ あとは、各種機能のプロトタイプですね。~ 内部用ヘッダのlcdin.hは、LCDの送信モード、キューで受け取った処理を切り分けるためのイベントコード、内部関数のプロトタイプになります。~ 最後に、LCDモジュール本体の、lcd.cですが、基本的な流れとしては、LCD各種機能用の命令が外部から呼ばれたら、必要なデータをキューへと詰めて、LCDタスク側でそれを受け取り処理します。~ LCD_Startでは、タスクハンドルと共に、このためのキューハンドルを作成しています。 lcdin_taskでは、まずlcdin_devinitを呼んでLCDの初期化を行い、初期化が終わったらモジュールデータの準備フラグを立てます。~ 他のモジュールは、LCD_GetReadyを呼んでこの準備フラグを参照することにより、LCDの初期化が終わっているかどうかを確認できます。~ 本来、複数のタスクから同時に参照・変更するような変数は、ミューテックスやセマフォで保護する必要がありますが、今回はタスク側からしか変更せず、参照タイミングもシビアなものではないので、特別なことはしていません。 lcdin_devinitでの初期化は、HD44780のデータシート通りです。~ 「Figure 24 4-Bit Interface」のところですね。~ 1ms以上の待ちが必要なものは、TaskDelayで待ちを入れています。~ 1ms未満のものであれば、lcdin_snd4bitでのenableパルス送信時に1ms待っているので、ここで吸収できていると考えます。~ コマンドは2つくらいテーブルに持っていますが、他に使うものが増えたら、初期化部分も含めて、順次追加していきたいと思います。~ LCD_SendCharやLCD_SendNumber、コマンド系については、LCDタスクへQUE_DATAを送信する時、内容をそのままQUE_DATAに詰めて送信していますが、LCD_SendStringだけは可変長の文字列なので、FreeRTOSが管理しているHeap領域を使用して文字列をコピーし、コピーした領域のポインタを送信するようにしています。~ ---- *** APLモジュール [#sc6d1fe0] 最後のAPLモジュールは、タスク起動時にLCDが使用できるまで待って、5秒おきに文字、数字、文字列の表示と、「LCDテストチュウ」という表示を繰り返すという処理をしています。~ AVRの時みたいに、ソースコードがShift-JISだとそのまま書けるんですが、Eclipseで作ると、ソースファイルのエンコードはUTF-8なので、「テストチュウ」は16進数で書いています。~ ---- ** 実行・デバッグ [#u6ecacd5] #u2b(-lMBHMhbqmg,w=640,h=360)