//------------------------------------------------------------------------------
//	ST7032 LCDモジュール(16 x 2)　秋月版　制御コマンド
//
//	# lcd_st7032 [-a <addr>] [-i] [-h] <text> [<line#>]
//
//	<text>	ASCII文字(SJIS:カナ)
//	<line#>	行番号：	0 or 1
//
//	option
//		-a <addr>:	I2C上のアドレス（デフォルト：0x3E)
//		-i:		LCDの初期化を行う
//		-h:		ヘルプ表示
//
//	2行表示に対して、これぞれの行に文字を表示します。
//	基本的に16文字固定表示でスクロールはしません。
//	カーソルも表示しません。
//
//	コンパイル方法
//		gcc -o lcd_st7032 lcd_st7032.c -l bcm2835 -lrt
//
//	copyright (c) 2016 Kazuhiro WATANABE All right reserved.
//	mailto:jj1req@ca.mbn.or.jp
//
//------------------------------------------------------------------------------

#include <bcm2835.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// BCM2835のI2Cクロック周波数の設定値
//	BCM2835_I2C_CLOCK_DIVIDER_2500	2500 = 10us = 100 kHz(IIC spec.)
//	BCM2835_I2C_CLOCK_DIVIDER_626	 622 = 2.504us = 399.3610 kHz
//	BCM2835_I2C_CLOCK_DIVIDER_15	 150 = 60ns = 1.666 MHz (default at reset)
//	BCM2835_I2C_CLOCK_DIVIDER_148	 148 = 59ns = 1.689 MHz
#define CLK_DIV BCM2835_I2C_CLOCK_DIVIDER_2500
//#define CLK_DIV BCM2835_I2C_CLOCK_DIVIDER_148
//#define CLK_DIV BCM2835_I2C_CLOCK_DIVIDER_626
//
// キャラクタ液晶ディスプレーへの接続
#define	LCD_ADDRESS		0x3e		// Write Address(Write Only)
//
// First Byte of I2C Data
//	Co RS 0 0 0 0 0 0
#define	LCD_COMMAND		0x00		// Following data is command with single bye
#define	LCD_DATA		0x40		// Following data is data byte with single byte
//
// Commands
// Function Set
//	0 0 1 DL N DH 0 IS
//		DL : Interface data length control bit
//			1: 8-bit bus mode with MPU.
//			0: 4-bit bus mode with MPU.
//				When in 4-bit bus mode, it needs to transfer 4-bit data by two times.
//		N : Display line number control bit
//			1: 2-line display mode is set.
//			0: 1-line display mode.
//		DH : Double height font type control bit
//			1: double height mode(5x16 dot),RAM address can only use 00H~27H.
//			0: normal (5x8 dot).
//		IS : normal/extension instruction select
//			1: extension instruction be selected (refer extension instruction table)
//			0: normal instruction be selected (refer normal instruction table)
#define LCD_FUNCTION_SET_NORM	0x38		// 8bit bus, 2 line, normal font, following normal instructions
#define LCD_FUNCTION_SET_EXT	0x39		// 8bit bus, 2 line, normal font, following extension instructions
//
// Bias selection/Internal OSC frequency adjust, BS will be invalid when external bias resistors are used 
//	0 0 0 1 BS F2 F1 F0
//		BS: bias selection
//			1: the bias will be 1/4
//			0: the bias will be 1/5
//		F2,F1,F0 : Internal OSC frequency adjust, When CLS connect to high, that instruction can adjust OSC and Frame frequency.
//			F2 F1 F0	3.0V	5.0V
//			 0  0  0	122	120
//			 0  0  1	131	133
//			 0  1  0	144	149
//			 0  1  1	161	167
//			 1  0  0	183	192
//			 1  0  1	221	227
//			 1  1  0	274	277
//			 1  1  1	347	347
#define	LCD_OSC			0x14		// Internal OSC Frequency, BIAS=1/4, 183Hz at 3.3V 2line
//
// Contrast set(low byte)
//	0 1 1 1 C3 C2 C1 C0
//		C3,C2,C1,C0:Contrast set(low byte)
#define	LCD_CONTRAST_L		0x73		// Low byte of contrast
//
// Power/ICON control/Contrast set(high byte)
//	0 1 0 1 ION BON C5 C4
//		Ion: set ICON display on/off
//			1: ICON display on.
//			0: ICON display off.
//		Bon: switch booster circuit, Bon can only be set when internal follower is used (OPF1=0, OPF2=0).
//			1: booster circuit is turn on.
//			0: booster circuit is turn off.
//		C5,C4 : Contrast set(high byte)
//			C5,C4,C3,C2,C1,C0 can only be set when internal follower is used (OPF1=0,OPF2=0).
//			They can more precisely adjust the input reference voltage of V0 generator.
//			The details please refer to the supply voltage for LCD driver.
#define	LCD_CONTRAST		0x56		// Power/ICON control/Contrast
//
// Follower control
//	0 1 1 0 FON Rab2 Rab1 Rab0
//		Fon: switch follower circuit, Fon can only be set when internal follower is used
//			1: internal follower circuit is turn on.
//			0: internal follower circuit is turn off.
//		Rab2,Rab1,Rab0 : V0 generator amplified ratio, Rab2,Rab1,Rab0 can only be set when internal follower is used
#define LCD_FOLLOWER	0x6c
//
// Clear Display
//	0 0 0 0 0 0 0 1
#define LCD_CLEAR	0x01
//
// Display ON/OFF
//	0 0 0 0 1 D C B
//		D : Display ON/OFF control bit
//			1: entire display is turned on.
//			0: display is turned off, but display data is remained in DDRAM.
//		C : Cursor ON/OFF control bit
//			1: cursor is turned on.
//			0: cursor is disappeared in current display, but I/D register remains its data.
//		B : Cursor Blink ON/OFF control bit
//			1: cursor blink is on, that performs alternate between all the high data and display character at the cursor position.
//			0: blink is off.
//#define LCD_ON		0x0f
#define LCD_ON		0x0c
#define LCD_OFF		0x08
//
// Command Recovery Time
//#define LCD_RECOVERY	100LL	// 100us
#define LCD_RECOVERY	1000LL	// 1000us

#define ON		1
#define OFF		0

uint8_t 	I2C_addr;

//******************************************************************************
//*	I2Cアクセス時のエラー要因を表示します。
//*
//* Function Name
//*	void get_reaosn( bcm2835I2CReasonCodes res, char *str )
//* Arguments
//*	bcm2835I2CReasonCodes res	error code
//*	char str			message buffer
//* Return
//*	none
//*
//******************************************************************************
void get_reaosn( bcm2835I2CReasonCodes res, char *str )
{
	switch( res )
	{
	case BCM2835_I2C_REASON_OK:
		strcpy( str, "Success" );
		break;
	case BCM2835_I2C_REASON_ERROR_NACK:
		strcpy( str, "Received a NACK" );
		break;
	case BCM2835_I2C_REASON_ERROR_CLKT:
		strcpy( str, "Received Clock Stretch Timeout" );
		break;
	case BCM2835_I2C_REASON_ERROR_DATA:
		strcpy( str, "Not all data is sent / received" );
		break;
	default:
		strcpy( str, "Unknown error" );
	}
}

//******************************************************************************
//*	LCDモジュールにコマンドの書き込みを行います
//*
//* Function Name
//*	int lcd_write_command( char data )
//* Arguments
//*	char data	byte data
//* Return
//*	int res		0: normal end
//*			1: error
//*
//******************************************************************************
int lcd_write_command( char data )
{
	bcm2835I2CReasonCodes res;
	char str[80];
	char data_array[2];

	data_array[0] = LCD_COMMAND;
	data_array[1] = data;

	if( res=bcm2835_i2c_write( data_array, 2 ) )
	{
		get_reaosn( res, str );
		printf( "Write error: %s[%d]\n", str, res );
		bcm2835_delayMicroseconds( LCD_RECOVERY );
		return( 1 );
	}
	bcm2835_delayMicroseconds( LCD_RECOVERY );
	return( 0 );
}

//******************************************************************************
//*	LCDモジュールにデータの書き込みを行います
//*
//* Function Name
//*	int lcd_write_data( char data )
//* Arguments
//*	char data	byte data
//* Return
//*	int res		0: normal end
//*			1: error
//*
//******************************************************************************
int lcd_write_data( char data )
{
	bcm2835I2CReasonCodes res;
	char str[80];
	char data_array[2];

	data_array[0] = LCD_DATA;
	data_array[1] = data;

	if( res=bcm2835_i2c_write( data_array, 2 ) )
	{
		get_reaosn( res, str );
		printf( "Write error: %s[%d]\n", str, res );
		bcm2835_delayMicroseconds( LCD_RECOVERY );
		return( 1 );
	}
	bcm2835_delayMicroseconds( LCD_RECOVERY );
	return( 0 );
}

//******************************************************************************
//*	LCDモジュールに対して表示のON/OFFを行います
//*
//* Function Name
//*	void lcd_on_off( int flag )
//* Arguments
//*	flag	0: OFF
//*		1: ON
//* Return
//*	none
//*
//******************************************************************************
void lcd_on_off( int flag )
{
	if( flag )	lcd_write_command( LCD_ON );
	else		lcd_write_command( LCD_OFF );
}

//******************************************************************************
//*	LCDモジュールに対して表示のクリアを行います
//*
//* Function Name
//*	void lcd_clear( void )
//* Arguments
//*	none
//* Return
//*	none
//*
//******************************************************************************
void lcd_clear( void )
{
	lcd_write_command( LCD_CLEAR );
	bcm2835_delay( 10 );
}

//******************************************************************************
//*	LCDモジュールに対して初期化を行います
//*
//* Function Name
//*	void lcd_init( void )
//* Arguments
//*	none
//* Return
//*	none
//*
//******************************************************************************
void lcd_init( void )
{

	// I/O ピンの初期化
	if( !bcm2835_i2c_begin() )
	{
		printf( "I2C initialize error!!\n" );
		exit(1);
	}

	// I2C設定
	//bcm2835_i2c_set_baudrate( 50000 );
	bcm2835_i2c_setClockDivider( CLK_DIV );
	bcm2835_delay( 200 );

	// 初期化フェーズ 1
	lcd_write_command( LCD_FUNCTION_SET_NORM );
	bcm2835_delay( 200 );

	// 初期化フェーズ 2
	lcd_write_command( LCD_FUNCTION_SET_EXT );

	// 初期化フェーズ 3
	lcd_write_command( LCD_OSC );

	// 初期化フェーズ 4
	lcd_write_command( LCD_CONTRAST_L );

	// 初期化フェーズ 5
	lcd_write_command( LCD_CONTRAST );

	// 初期化フェーズ 6
	lcd_write_command( LCD_FOLLOWER );
	bcm2835_delay( 400 );

	// 初期化フェーズ 7
	lcd_write_command( LCD_FUNCTION_SET_NORM );

	// Display CLEAR & ON
	lcd_clear();
	lcd_on_off( ON );
}

//******************************************************************************
//*	I2Cインタフェースを開放します。
//*
//* Function Name
//*	void lcd_close( void )
//* Arguments
//*	none
//* Return
//*	none
//*
//******************************************************************************
void lcd_close( void )
{
	bcm2835_i2c_end();
	bcm2835_close();
}

//******************************************************************************
//*	行番号からLCDモジュールの行先頭のアドレスに変換
//*
//* Function Name
//*	int lcd_location( int line )
//* Arguments
//*	int line		target position 0:1st Line, 1:2nd Line, 2:3rd Line, 4:4th Line
//* Return
//*	ret:		0: normal end
//*			-1:error
//*
//******************************************************************************
int lcd_location( int line )
{
	char	addr;

	switch( line )
	{
	case 0:
		addr = 0x80;
		break;
	case 1:
		addr = 0x80 | 0x40;
		break;
	default:
		printf( "Illegal line number:[%d]\n", line );
		return( -1 );
	}
	
	lcd_write_command( addr );
	return( 0 );
}

//******************************************************************************
//*	LCDモジュールに文字列を表示します。
//*
//* Function Name
//*	void lcd_print( int line, char *str )
//* Arguments
//*	int line		target position 0:first line, 1:second line
//*	char *str		string data
//* Return
//*	none
//*
//******************************************************************************
void lcd_print( int line, char *str )
{
	int	i;
	int	len;
	char	data;

	lcd_location( line );
	for( i=0; i<16; i++)
	{ 
		lcd_write_data( 0x20 );
	}

	lcd_location( line );
	len = strlen(str);
	if( len > 16 ) len = 16;
	for( i=0; i<len; i++ )
	{
		lcd_write_data( str[i] );
	}
}

//******************************************************************************
//*	ヘルプ表示
//*
//* Function Name
//*	void print_help( void )
//* Arguments
//*	mome
//* Return
//*	none
//*
//******************************************************************************
void print_help( void)
{
	 fprintf( stderr, "%s", 
		 "\nUsage: lcd_st7032 [-i] [-h]  <message> [<line#>]"
		 "\n\n"
		 "********************** Parameters **********************\n"
		 "  message    : ASCII character string( include SJIS katakana)\n"
		 "  line#      : Line number to display 0 or 1(default:0)\n"

		 "Options:\n"
		 "  -a <addr>  : Target address on I2C bus.\n"
		 "  -i         : Initialize LCD\n"
		 "  -h         : Print this help\n"
		 "********************************************************\n"
		"\n" );
}

int main( int argc, char *argv[] )
{
	int	line = 0;;
	int	c;
	int	init_flag = 0;

	I2C_addr = LCD_ADDRESS;

	// Get command options
	while( (c = getopt(argc, argv, "a:ih")) != -1 )
	{
		switch (c)
                {
		case 'a':
			I2C_addr = strtol( optarg, NULL, 0 );
			break;
		case 'i':
			init_flag++;
			break;
		case 'h':
			print_help();
			return(0);
		default:
			/*NOTREACHED*/
			;
		}
	}
	argc -= optind;
	argv += optind;

	// ライブラリ初期化
	if( !bcm2835_init() )
	{
		printf( "bcm2835 library initialize error!!\n" );
		return(1);
	}

 	// I2C設定
	bcm2835_i2c_setSlaveAddress( I2C_addr );

	if( init_flag )
	{
		lcd_init();
	}

	if( argc == 2 ) line = (int)strtol( argv[1], NULL, 0 );
	lcd_print( line, argv[0] );
	bcm2835_close();
	return( 0 );
}
