/*-----------------------------------------------------------------------------*
*  LCD_IF.C		LCD interface routines
*
*	(c) Didier Juges 2008
*
*	Designed for a Noritake CU16025ECBP-U5J Vacuum Fluorescent Display (VFD), 
*	but generally compatible with the Hitachi H44780 LCD display controller 
*	interface (some timing may need to be adjusted).
*
*	A compile option is available to accomodate using either lower nibble
*	of LCD_PORT to drive the data bus (#define USE_LOWER_BITS), or the upper 
*	nibble (#define USE_UPPER_BITS)
*
*	If USE_UPPER_BITS is defined, the display is wired as follows:
*	- DB4-7 -> P0.4-7 (DB7 = Busy Flag)
*	- E     -> P0.3 (can be changed)
*	- R/W   -> P0.2 (can be changed)
*	- RS    -> P0.1 (can be changed)
*	(P0.0 available)
*
*	If USE_LOWER_BITS is defined, the display is wired as follows:
*	- DB4-7 -> P0.0-3 (DB3 = Busy Flag)
*	- E     -> P0.4 (can be changed)
*	- R/W   -> P0.5 (can be changed)
*	- RS    -> P0.6 (can be changed)
*	(P0.7 available)
*
*	These routines use the Busy Flag (DB7) which is both an input and an output.
*	Therefore, make sure DB4-7 of LCD_PORT are configured as open-collector 
*	(not push-pull) for normal 8051 chips (SiLabs for instance).
*
*	For Phillips and TI chips which have data direction PxDDRL and PxDDRH
*	registers, use appropriate compile option (#define PHILLIPS) and read 
*	comments in LCD_Init()
*
*	Control lines E, R/W and RS should be configured as push-pull if available.
*	For chips with normal open collector and internal pull-up on their IO port, 
*	make sure the internal pull-ups are sufficient to guarantee clean pulses, 
*	particularly on the LCD_EN. An external pull-up resistor may be required
*	(~3.3kohm @ 3.3V, ~4.7kohm @ 5V), particularly with the Noritake VFDs.
*	If you use the BUSY_FLAG, the LCD_RW and BUSY_FLAG pins should also have 
*	external pull-ups 
*
*	Timing is provided through the WaitTicks() routine, which inserts a delay of
*	about 1mS per tick.
*
*	For Noritake Vacuum Fluorescent Displays, the following timing applies:
*		* wait 260mS after power up (Vcc > 4.75) before addressing the display.
*		* Display Clear instruction takes 2.3mS max.
*		* The last character written is briefly displayed again in the new 
*		  cursor position during the high period of the E signal. 
*		  This is not visible on an LCD, but the brightness of the VFD makes it 
*		  visible sometimes. 
*		  To limit problems, the high period of the E signal should be kept as 
*		  short as possible (5uS or less).
*
*	These functions take advantage of the BUSY_FLAG, which makes then pretty
*	fast, but require to actively control the state of the LCD_RW pin and also
*	require to be able to read the BUSY_FLAG, meaning the output port must be
*	periodically changed from output to input. This is easier with some chips 
*	than others.
*
*	On a Silabs C8051F530 running at 25 MHz with a Noritake display, it takes 
*	~140uS to print 5 characters. It takes longer on most LCD displays because
*	the Hitachi 44780 controller is slower than the Noritake controller and delays
*	have to be inserted.
*
*	Notes found on the web:
*
*		Reading data back is best used in applications which require data to be 
*		moved back and forth on the LCD (such as in applications which scroll data 
*		between lines). The "Busy Flag" can be polled to determine when the last 
*		instruction that has been sent has completed processing. 
*		In most applications, it's enough to just tie the "R/W" line to ground 
*		because there is no need to read anything back. 
*		This simplifies the application because when data is read back, the 
*		microcontroller I/O pins have to be alternated between input 
*		and output modes.
*
*		For most applications when there really is no reason to read from the LCD, 
*		tie "R/W" to ground and just wait the maximum amount of time 
*		for each instruction (4.1 msecs for clearing the display or moving the 
*		cursor/display to the "home position", 160 usecs for all other commands). 
*		As well as making application software simpler, it also frees up a 
*		microcontroller pin for other uses. Different LCDs execute instructions at 
*		different rates and to avoid problems later on (such as if the LCD is 
*		changed to a slower unit), simply use the maximum delays given above. 
*
*	Example of usage:
*		LCD_Init();							// Init LCD Controller
*		LCD_Clear( 3 );						// clear both display lines
*		LCD_PrintCode( 1, LCDInitMsg, 1 );	// Print product name on line 1
*		LCD_PrintCode( 2, VersionMsg, 0 );	// print firmware version on line 2
*		StartTimer( LCD_TIMER, 2000 );		// start 2 second delay
*		while( ! TimerReady( LCD_TIMER ))	
*			;
*		LCD_Print( 2, buf, 0 );				// print data on line 2
*------------------------------------------------------------------------------*/
#include "general.h"
#include "tmr_if.h"
#include "lcd_if.h"

/* ----- User Changable constants ----- */
sbit LCD_EN 			= P1_3;			// normal state is low
sbit LCD_RW 			= P1_2;
sbit LCD_RS				= P1_1;
#define LCD_PORT		P1
// #define USE_UPPER_BITS or USE_LOWER_BITS of LCD_PORT for 4 bits mode
#define USE_UPPER_BITS
//#define USE_LOWER_BITS

#define _HITACHI_44780_			// comment out for Noritake display

/* end of User Changable constants ----- */

#define LCD_TIMEOUT		10		// number of mS to wait for Busy Flag

#if defined USE_UPPER_BITS
sbit BUSY_FLAG			= P1_7; // this is DB7 of the LCD controller bus

#define LCD_MASK			0x0f	// set bits used for LCD data bus 
									// to 0, others to 1 (to set as input)
#define LCD_INIT_1			0x30
#define LCD_INIT_2			0x20
#else
#define LCD_MASK			0xf0
#define LCD_INIT_1			0x03
#define LCD_INIT_2			0x02
#endif

#define INSTR_REG			FALSE
#define DATA_REG			TRUE

/* ----- global variables ----- */


/* ----- local variables ----- */


/*---------------------------------------------------------------------*
* Local function prototypes (public prototypes are in lcd_if.h)
*----------------------------------------------------------------------*/
bit WriteLCD( bit reg, uchar out );
void IRWriteLCD( uchar);
void DRWriteLCD( uchar);
void IRWriteLCD8( uchar);
bit WaitLCD( void );
void SetLCDIn( void );
void SetLCDOut( void );
void ClockEn( void );
void SetLCDRs( void );
void ClrLCDRs( void );
void SetLCDRw( void );
void ClrLCDRw( void );
void clr_LCDBuf( void );


/*=====================================================================*
* Public Functions
*======================================================================*/

/*---------------------------------------------------------------------*
* FUNCTION: LCD_Init()
*
*  initialize LCD module per specs
*
* NOTE: the Noritake display needs at least 260mS after Vcc rise > 4.75V
*----------------------------------------------------------------------*/
void LCD_Init( void ){

	// 1st part of initialization is in 8 bit mode, but only 
	// the upper bits matter.
	
	// For PHILLIPS chips (with PxDDRx registers) these should be
	// configured as outputs prior to calling this function, or
	// add the Port init code here

    WaitTicks( 15 );        // Wait more than 15ms after Vcc is up (Noritake needs longer)
    LCD_PORT = LCD_INIT_1;  // Startup Sequence
    ClockEn();				// raise E, wait >100uS, lower E
    WaitTicks( 10 );         // Wait more than 4.1ms
    ClockEn();  
    WaitTicks( 10 );         // Wait more than 0.1ms
    ClockEn(); 
	WaitTicks( 10 );   
    LCD_PORT = LCD_INIT_2;
    ClockEn();
    WaitTicks( 10 );         // Wait more than 0.1ms

	// LCD is now initialized, rest of setup is in 4 bits mode

    IRWriteLCD( 0x28 );   	// Function Set
                            // DL=0 4bit, N=1 2Line, F=0 5x7
	WaitTicks( 10 );
    IRWriteLCD( 0x0C );   	// Display on/off control
                            // D=1 Disp on, C=0 Curs off, B=0 Blink off    
	WaitTicks( 10 );
	IRWriteLCD( 0x06 );   	// Entry Mode Set
                            // I/D=1 Increment, S=0 No shift
	WaitTicks( 10 );
    IRWriteLCD( 0x01 );   	// Clear Display
    WaitTicks( 20 );         // Wait more than 1.64ms

} // end LCD_Init()

/*---------------------------------------------------------------------*
* FUNCTION: LCD_Clear()
*
* 	to clear line 1: LCD_Clear( 1 );
*	to clear line 2: LCD_Clear( 2 );
*	to clear both lines: LCD_Clear( 3 );
*---------------------------------------------------------------------*/
void LCD_Clear( uchar line ){

	uchar i=0;

	if( line == 3 ){
		// clear by instruction (fast)
		IRWriteLCD( 0x01 );
		// wait 4.1 mS
		WaitTicks( 4 );
	}else{	// 1 line at a time
		// Position the cursor
		if( line & 1 ){
			IRWriteLCD( 0x80 );
			while( i < LCD_SIZE ){
				DRWriteLCD( ' ' );
#ifdef _HITACHI_44780_
				WaitTicks( 2 );
#else
				WaitLCD();
#endif			
				i++;
			}
		}
#ifdef _HITACHI_44780_
		WaitTicks( 2 );
#endif
		if( line & 2 ){
			IRWriteLCD( 0xC0 );
			i = 0;
			while( i < LCD_SIZE ){
				DRWriteLCD( ' ' );
#ifdef _HITACHI_44780_
				WaitTicks( 2 );
#else
				WaitLCD();
#endif
				i++;
			}
		}
#ifdef _HITACHI_44780_
		WaitTicks( 2 );
#endif
	}
} // end LCD_Clear()

/*---------------------------------------------------------------------*
* FUNCTION: LCD_Print()
*
*	line: 1 or 2 (for 2 line display)
*	*chr_ptr: pointer to string to print in data space, NULL terminated
*	offset: offset from left of display where to print string
*			(characters skipped are not blanked, allowing to refresh
*			a portion of a line without affecting the rest)
*---------------------------------------------------------------------*/
void LCD_Print( uchar line, uchar *chr_ptr, uchar offset ){

	// position the cursor
	if( line == 1 )
		IRWriteLCD( 0x80 + offset );	// address of left-most character to print
	else
		IRWriteLCD( 0xC0 + offset );
	// Write out the data
	chr_ptr--;
	while( *(++chr_ptr) != '\0' ){
#ifdef _HITACHI_44780_
		WaitTicks( 2 );
#else
		WaitLCD();
#endif
		DRWriteLCD( *chr_ptr );
	}
#ifdef _HITACHI_44780_
	WaitTicks( 2 );						// not necessary with Noritake display
#endif

} // end LCD_Print()

/*---------------------------------------------------------------------*
* FUNCTION: LCD_PrintCode()
*
*	Same as LCD_Print except that pointer to code space
*---------------------------------------------------------------------*/
void LCD_PrintCode( uchar line, uchar code *chr_ptr, uchar offset ){

	// position the cursor
	if( line == 1 )
		IRWriteLCD( 0x80 + offset );
	else
		IRWriteLCD( 0xC0 + offset );
	// Write out the data
	chr_ptr--;
	while( *(++chr_ptr) != '\0' ){
#ifdef _HITACHI_44780_
		WaitTicks( 2 );
#else
		WaitLCD();
#endif
		DRWriteLCD( *chr_ptr );
	}
#ifdef _HITACHI_44780_
	WaitTicks( 2 );						// not necessary with Noritake display
#endif

} // end LCD_PrintCode()


/*======================================================================*
* Private Functions
*======================================================================*/



/*---------------------------------------------------------------------*
* FUNCTION: IRWriteLCD()
*
*  Writes LCD Instruction ( 4-bits mode, MSB first )
*---------------------------------------------------------------------*/
void IRWriteLCD( uchar outbyte){

	uchar i;

	i = outbyte;                // save for later

	SetLCDOut();                // Configs Port bits to output
	i = i & 0xf0;               // Strip low nibble
#if defined USE_LOWER_BITS
	i = i/16;					// Shift high to low
#endif
	LCD_PORT = i;               // Write high nibble
	ClrLCDRs();                 // Instruction
	ClrLCDRw();                 // Write
	ClockEn();                	// Trigger

	i = outbyte;                // Later is now!

	i = (i & 0x0f);             // Strip high nibble
#if defined USE_UPPER_BITS
	i = i*16;					// Shift low nibble to high
#endif
	LCD_PORT = i;               // Write low nibble
	ClrLCDRs();                 // Instruction
	ClrLCDRw();                 // Write
	ClockEn();

} // end IRWriteLCD()

/*---------------------------------------------------------------------*
* FUNCTION: DRWriteLCD()
*
*  Writes LCD Data ( 4-bits mode, MSB first )
*---------------------------------------------------------------------*/
void DRWriteLCD( uchar outbyte){

	uchar i;

	i = outbyte;                // preserve outbyte for later

	SetLCDOut();                // Configs Port bits to output
	i = i & 0xf0;               // Strip low nibble
#if defined USE_LOWER_BITS
	i = i/16;					// Shift high to low
#endif
	LCD_PORT = i;               // Write high nibble
	SetLCDRs();                 // Data
	ClrLCDRw();                 // Write
	ClockEn();

	i = outbyte;                // Later is now!

	i = (i & 0x0f);             // Strip high nibble
#if defined USE_UPPER_BITS
	i = i*16;					// Shift low to high
#endif
	LCD_PORT = i;               // Write high nibble
	SetLCDRs();                 // Data
	ClrLCDRw();                 // Write
	ClockEn();

} // end DRWriteLCD()


/*---------------------------------------------------------------------*
* FUNCTION: WaitLCD()
*
*	Wait for Display to be 'not-busy'
*	Wait for a max of LCD_TIMEOUT mS before giving up
*
*	return value: 	1: BUSY_FLAG = 1 (TRUE) => was busy (problem with display!!!)
*					0: BUSY_FLAG = 0 (FALSE) => not busy
*
*	WaitLCD appears broken as on May 2008 (dj), so I use simple
*	delays in LCD_Print() functions
*---------------------------------------------------------------------*/
#ifndef _HITACHI_44780_
bit WaitLCD( void ){

	bit bi, bt;
	uchar tmr;
	uchar i;

	//P0_7 = 0;	// ***TEST

	tmr = ReadTicks() + LCD_TIMEOUT;
	bt = FALSE;

	SetLCDIn();		// set busy flag line as input
	ClrLCDRs();		// instruction
	SetLCDRw();		// set display to *talk* mode

	do{
		for( i=0; i<5; i++ )	// wait 1uS
			;
		LCD_EN = 1;
		for( i=0; i<5; i++ )	// wait 1uS
			;
		bi = BUSY_FLAG;
		LCD_EN = 0;
		bt = (ReadTicks() < tmr?TRUE:FALSE);
	}while( bi && bt );     // We wait here until BF goes low

	//P0_7 = 1;	// ***TEST

	ClrLCDRw();		// set display back to *listen* mode (default)

	return( bi );

} // end WaitLCD()
#endif

/*---------------------------------------------------------------------*
* FUNCTION: SetLCDIn()
*
*  Configures P0.0 - P0.2 as outputs, P0.3 as Input,
*  P0.4 - 6 as outputs, P0.7 as input
*
*	Note: this function only works with USE_LOWER_BITS
*	(easy to fix, but got lazy...)
*---------------------------------------------------------------------*/
#ifndef _HITACHI_44780_
void SetLCDIn( void ){


#if defined PHILLIPS
	BUSY_FLAG = 0;    // We'll drive this fellow low before we switch him to input.
	P0DDRL = 0x11010101;	/* P0.0-2 as CMOS outputs, P0.3 as input */
	P0DDRH = 0x11010101;	/* P0.4-6 as CMOS outputs, P0.7 as input */
#else
	BUSY_FLAG = 1;			// write 1 to make pin an input
#endif

} // end SetLCDIn()
#endif

/*---------------------------------------------------------------------*
* FUNCTION: SetLCDOut()
*
*  Configures P0.0 - P0.3 as Outputs
*  P0.4 - P0.6 as outputs, P0.7 as input
*
*	Note: see SetLCDIn()
*---------------------------------------------------------------------*/
void SetLCDOut( void ){

#if defined PHILLIPS
	P0DDRL = 0x01010101;
	P0DDRH = 0x11010101;
#else
	// do nothing
#endif

} // end SetLCDOut()

/*---------------------------------------------------------------------*
* FUNCTION: ClockEn()
*---------------------------------------------------------------------*/
void ClockEn( void ){

	uchar i;

	LCD_EN = 1;
	// delay 100uS minimum for LCD displays, 
	// 5uS maximum for Noritake VFDs to limit character flashing
	for( i=0; i<5; i++ )
		;
	LCD_EN = 0;

} // end ClockEn()

/*---------------------------------------------------------------------*
* FUNCTION: SetLCDRs()
*
*  Sets register select (instruction)
*---------------------------------------------------------------------*/
void SetLCDRs( void ){

	// Reference the instruction register (commands)
	LCD_RS = 1;

} // end SetLCDRs()

/*---------------------------------------------------------------------*
* FUNCTION: ClrLCDRs()
*
*  Clears register select (data)
*---------------------------------------------------------------------*/
void ClrLCDRs( void ){

   // Reference the data register (data)
   LCD_RS = 0;

} // end ClrLCDRs()

/*---------------------------------------------------------------------*
* FUNCTION: SetLCDRw()
*
*  Sets data read
*---------------------------------------------------------------------*/
#ifndef _HITACHI_44780_
void SetLCDRw( void ){

   // Data register read (data)
   LCD_RW = 1;

} // end SetLCDRw()
#endif

/*---------------------------------------------------------------------*
* FUNCTION: ClrLCDRw()
*
*  Sets data write
*---------------------------------------------------------------------*/
void ClrLCDRw( void ){

   // Data register write (data)
   LCD_RW = 0;

} // end ClrLCDRw()

// end of file


