LCD文字表示(RTOS)

MAPLE board

頑張ってハンダ付けしましょうw
そんなに大変な作業ではないです。
まぁ、私はフリー拡張スロットのコネクタのハンダ付け、一部失敗して斜めについてますけどね……。

LCDモジュール

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
P015D5
P016D6
P017D4
P21RS
P22E
P23D7

プロジェクト

まずは、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モジュールです。
まずは、外部ヘッダの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モジュール

次に、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モジュール

最後に、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);
	}
}

ソースコード解説

共通定義のところはある程度書いたので、メインから。

メイン

先頭はお決まりのincludeとCRPです。

まずは、LED、LCD、APLと、各モジュールのヘッダをincludeし、ハンドルとして使うためのモジュールを管理するデータの実体を宣言しています。
main関数の先頭で、それらの領域を初期化します。

次に、LEDとLCDで使うポートとピンの設定をしています。
LEDは一つのみですが、LCDは6本使うので、enum型で指定するようにしています。

次に、各モジュールを使えるように初期化します。
こちらは領域の初期化ではなく、GPIOの設定等をしています。
APLモジュールは、実際にはやることがないので、呼ぶだけで何もしていませんが。

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

最後に、各モジュールのタスクやキューを生成して、スケジューラ実行で実際にタスクを走らせています。
同じモジュールのインスタンスを複数作れることを考えて、タスク名も指定できるようにしています。

ハンドル的な考え方以外は、そんなに大したことしてないですね。


LEDモジュール

次に、LEDモジュールです。

外部ヘッダのled.hには、まずモジュールデータとして、自分自身のタスクハンドルと、自分が使うポートとピンを記憶しておく構造体を持ちます。
LED_ONOFF_MODEのenumは、ONで初期化、OFFで初期化を選べるように作りました。

内部用ヘッダのledin.hは、外部には公開しないタスク関数のみの定義になります。

LEDモジュール本体の、led.cについては、基本的にはLEDチカチカ(RTOS)でやったものをモジュール化しただけです。
今は周期的な点滅を繰り返しているだけですが、そのうち外部からの指定で、点滅パターンを変えられるようにしたりなんかすると良さげかもしれないですね。


LCDモジュール

次に、今回のメイン、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モジュール

最後のAPLモジュールは、タスク起動時にLCDが使用できるまで待って、5秒おきに文字、数字、文字列の表示と、「LCDテストチュウ」という表示を繰り返すという処理をしています。
AVRの時みたいに、ソースコードがShift-JISだとそのまま書けるんですが、Eclipseで作ると、ソースファイルのエンコードはUTF-8なので、「テストチュウ」は16進数で書いています。


実行・デバッグ


添付ファイル: filefolder.png 765件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2013-02-24 (日) 15:04:19 (2460d)