/* ---------------------------------------------------------------------------
 * AVR_MLIB - HD 44780 LCD Display Driver
 * www.mlab.cz miho 2008
 * ---------------------------------------------------------------------------
 * LCD display driver for standard Hitachi 1/2/4 line character LCD modules
 * for AVR processors. It uses 4 or 8 bit interface without readback.
 * In the Examples section there is a demo application for this library.
 * ---------------------------------------------------------------------------
 * 00.00 2008/03/28 First Version
 * ---------------------------------------------------------------------------
 */


// What should be set and done before here
// ---------------------------------------
//
// #include <stdio.h>					// If you want to use printf, ...
//
// #define LCD_DATA				B		// 4 or 8 bits field (lsb bit of the port)
// #define LCD_DATA_BIT			4
// 
// #define LCD_RS				D		// Register Select (port and bit)
// #define LCD_RS_BIT			4		
//
// #define LCD_E				D		// Enable (port and bit)
// #define LCD_E_BIT			3
//
//
// // LCD Display Parameters
// #define LCD_INTERFACE_BITS	4		// 4 or 8 bit interface
// #define LCD_LINES			1		// 1 or 2 or 4 lines
// #define LCD_CHARS			20		// usualy 16 or 20, important for 4 line display only
//
// #include "lcd_hd44780.h"				// Use LCD Library
//
//
// How to use the library 
// ----------------------
//
// void lcd_init(void)		// Init LCD Display
//
// void lcd_home()			// Goto Home
//
// void lcd_clear()		// Clear Display
//
// void lcd_clear_home()	// Clear Display and Goto Home with no Cursor
//
// void lcd_cursor_on()	// Switch Cursor On
//
// void lcd_cursor_off()	// Switch Cursor Off
//
// void lcd_cursor_left()	// Move Cursor Left
//
// void lcd_cursor_right()	// Move Cursor Right
//
// void lcd_gotoxy(uint8_t x, uint8_t y)		// Move to Position (1,1 is the first position)
//
// int lcd_putc(char c)	// LCD Char Output
//
// int lcd_putc_stream(char c, FILE *unused)	// LCD Char Output (for Stream Library)
//
//
// How to use printf
// -----------------
//
// 1) Define FILE structure
//
// static FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putc_stream, NULL, _FDEV_SETUP_WRITE);
//
// 2) Connect it with standard output
//
// stdout = &lcd_stream;		// Connect stdout to LCD Stream
//
// 3) Use printf
//
// printf("\fHello World!\n------------");
//
// 4) Use special chars
//
//		\f	- clear display and goto home
//		\n	- goto the beginning of the next line
//		\r	- goto to the beginning of curent line
//		\b	- backspace
//		\v	- start and end definition of user defined char
//
//
// How to use User Defined symbols
// -------------------------------
//
// That is easy. Just print the definition to lcd. Look at the example
//
// printf("\v" "\x10" LCD_CHAR_BAT50 "\v");		// definition (redefines CGRAM content of the LCD)
// printf("Battery Status \x10");				// usage
//
//	\v				starts the definition
//	\x10			first (of eight) user defined char
//	LCD_CHAR_BAT50	half battery symbol, you can define more symbols here (up to 8)
//	\v				end of definition
//


// Check Defined Values and use Default Values if possible
// -------------------------------------------------------

// 1 / 2 / 4 Line
#ifndef LCD_CHARS
	#if LCD_LINES > 2
		#error "LCD: Undefined LCD_CHARS"
	#else
		// Dafault Value
		#define LCD_CHARS 20
	#endif
#endif

#ifndef LCD_LINE_1
	// Address of the 1st char on the 1st line
	#define LCD_LINE_1	0
#endif

#ifndef LCD_LINE_2
	// Address of the 1st char on the 2nd line
	#define LCD_LINE_2	64			
#endif

#ifndef LCD_LINE_3
	// Address of the 1st char on the 3rd line
	#define LCD_LINE_3	LCD_CHARS	
#endif

#ifndef LCD_LINE_4
	// Address of the 1st char on the 4th line
	#define LCD_LINE_4	(LCD_LINE_2 + LCD_CHARS)
#endif

// Data Interface
#if LCD_INTERFACE_BITS == 4
	#define LCD_DATA_MASK	(0x0F << LCD_DATA_BIT)
#elif LCD_INTERFACE_BITS==8
	#define LCD_DATA_MASK	(0xFF << LCD_DATA_BIT)
#else
	#error "LCD: Wrong Value: LCD_INTERFACE_BITS"
#endif

#if LCD_DATA_MASK > 0xFF
	#error "LCD: Value too Big: LCD_DATA_BIT"
#endif


// Need Delay Library
// ------------------

#ifndef F_CPU
	#error "LCD: Undefined F_CPU"
#endif
#include <util/delay.h>				// Delay Routines


// Need IO Pins
// ------------

#include <avr/io.h>					// Device Specific Defines

#define GLUE(a,b)			a##b
#define PORT(a)			    GLUE(PORT,a)
#define PIN(a)				GLUE(PIN,a)
#define DDR(a)				GLUE(DDR,a)


#define LCD_E_PORT			PORT(LCD_E)
#define LCD_E_DDR			DDR(LCD_E)

#define LCD_RS_PORT			PORT(LCD_RS)
#define LCD_RS_DDR			DDR(LCD_RS)

#define LCD_DATA_PORT		PORT(LCD_DATA)
#define LCD_DATA_DDR		DDR(LCD_DATA)

#ifdef LCD_RW
#define LCD_RW_PORT			PORT(LCD_RW)
#define LCD_RW_DDR			DDR(LCD_RW)
#endif


// LCD Chip Commands
// -----------------

// Comand Clear LCD Display
#define LCD_HD44780_CLR 		0x01

// Command Home Cursor
#define LCD_HD44780_HOME		0x02

// Command Entry Mode (increment/decrement, shift/no shift)
#define LCD_HD44780_ENTMODE(inc, shift) \
	(0x04 | ((inc)? 0x02: 0) | ((shift)? 1: 0))

#define LCD_HD44780_ENTMODE_DEF		LCD_HD44780_ENTMODE(1,0)	// Increment Position, No Shift

// Command Display Controll (display on/off, cursor on/off, cursor blinking on/off)
#define LCD_HD44780_DISPCTL(disp, cursor, blink) \
	(0x08 | ((disp)? 0x04: 0) | ((cursor)? 0x02: 0) | ((blink)? 1: 0))

#define LCD_HD44780_CURSORON		LCD_HD44780_DISPCTL(1,1,0)	// on, cursor on, 
#define LCD_HD44780_CURSOROFF		LCD_HD44780_DISPCTL(1,0,0)	// on, cursor off

// Command Cursor or Display Shift (shift display/cursor, left/right)
#define LCD_HD44780_SHIFT(shift, right) \
	(0x10 | ((shift)? 0x08: 0) | ((right)? 0x04: 0))

#define LCD_HD44780_CURSORLEFT		LCD_HD44780_SHIFT(0,0)
#define LCD_HD44780_CURSORRIGHT		LCD_HD44780_SHIFT(0,1)

// Command Function Set ( 4/8-bit interface / 1 or 2 lines )
#define LCD_HD44780_4BIT1LINE	0x20	// 4-bit 1-line  font 5x7
#define LCD_HD44780_4BIT2LINES	0x28	// 4-bit 2-lines font 5x7
#define LCD_HD44780_8BIT1LINE	0x30	// 8-bit 1-line  font 5x7
#define LCD_HD44780_8BIT2LINES	0x38	// 8-bit 2-lines font 5x7

// Select Apropriate Mode
#if LCD_INTERFACE_BITS==4
	#if LCD_LINES == 1
		#define LCD_HD44780_FNSET	LCD_HD44780_4BIT1LINE	// 4-bit 1-line
	#else
		#define LCD_HD44780_FNSET	LCD_HD44780_4BIT2LINES	// 4-bit 2-lines
	#endif
#elif LCD_INTERFACE_BITS==8
	#if LCD_LINES == 1 
		#define LCD_HD44780_FNSET	LCD_HD44780_8BIT1LINE	// 8-bit 1-line
	#else
		#define LCD_HD44780_FNSET	LCD_HD44780_8BIT2LINES	// 8-bit 2-lines
	#endif
#endif


// User Defined Chars
// ------------------

// Definitions only. 
// Because these definitions may be sent to lcd via printf, 
// it is impossible to contain 0 bytes (end of string in C) 
// so we ored 0x80 to each byte

#define LCD_CHAR_SPACE  "\x80\x80\x80\x80\x80\x80\x80\x80"      /* space (blank char)        */
#define LCD_CHAR_BAT100 "\x8E\x9F\x9F\x9F\x9F\x9F\x9F\x1F"      /* symbol battery full       */
#define LCD_CHAR_BAT50  "\x8E\x9F\x91\x91\x93\x97\x9F\x1F"      /* symbol baterry half       */
#define LCD_CHAR_BAT0   "\x8E\x9F\x91\x91\x91\x91\x91\x1F"      /* symbol baterry empty      */
#define LCD_CHAR_UP     "\x80\x84\x8E\x95\x84\x84\x84\x80"      /* symbol arrow up           */
#define LCD_CHAR_DOWN   "\x80\x84\x84\x84\x95\x8E\x84\x80"      /* symbol arrow down         */
#define LCD_CHAR_LUA    "\x84\x8E\x91\x91\x9F\x91\x91\x80"      /* A s carkou                */
#define LCD_CHAR_LLA    "\x81\x82\x8E\x81\x9F\x91\x8F\x80"      /* a s carkou                */
#define LCD_CHAR_HUC    "\x8A\x8E\x91\x90\x90\x91\x8E\x80"      /* C s hackem                */
#define LCD_CHAR_HLC    "\x8A\x84\x8E\x90\x90\x91\x8E\x80"      /* c s hackem                */
#define LCD_CHAR_HUD    "\x8A\x9C\x92\x91\x91\x92\x9C\x80"      /* D s hackem                */
#define LCD_CHAR_HLD    "\x85\x83\x8D\x93\x91\x91\x8F\x80"      /* d s hackem                */
#define LCD_CHAR_LUE    "\x84\x9F\x90\x90\x9E\x90\x9F\x80"      /* E s carkou                */
#define LCD_CHAR_LLE    "\x81\x82\x8E\x91\x9F\x90\x8E\x80"      /* e s carkou                */
#define LCD_CHAR_HUE    "\x8A\x9F\x90\x9E\x90\x90\x9F\x80"      /* E s hackem                */
#define LCD_CHAR_HLE    "\x8A\x84\x8E\x91\x9F\x90\x8E\x80"      /* e s hackem                */
#define LCD_CHAR_LUI    "\x84\x8E\x84\x84\x84\x84\x8E\x80"      /* I s carkou                */
#define LCD_CHAR_LLI    "\x82\x84\x80\x8C\x84\x84\x8E\x80"      /* i s carkou                */
#define LCD_CHAR_HUN    "\x8A\x95\x91\x99\x95\x93\x91\x80"      /* N s hackem                */
#define LCD_CHAR_HLN    "\x8A\x84\x96\x99\x91\x91\x91\x80"      /* n s hackem                */
#define LCD_CHAR_LUO    "\x84\x8E\x91\x91\x91\x91\x8E\x80"      /* O s carkou                */
#define LCD_CHAR_LLO    "\x82\x84\x8E\x91\x91\x91\x8E\x80"      /* o s carkou                */
#define LCD_CHAR_HUR    "\x8A\x9E\x91\x9E\x94\x92\x91\x80"      /* R s hackem                */
#define LCD_CHAR_HLR    "\x8A\x84\x96\x99\x90\x90\x90\x80"      /* r s hackem                */
#define LCD_CHAR_HUS    "\x8A\x8F\x90\x8E\x81\x81\x9E\x80"      /* S s hackem                */
#define LCD_CHAR_HLS    "\x8A\x84\x8E\x90\x8E\x81\x9E\x80"      /* s s hackem                */
#define LCD_CHAR_HUT    "\x8A\x9F\x84\x84\x84\x84\x84\x80"      /* T s hackem                */
#define LCD_CHAR_HLT    "\x8A\x8C\x9C\x88\x88\x89\x86\x80"      /* t s hackem                */
#define LCD_CHAR_LUU    "\x82\x95\x91\x91\x91\x91\x8E\x80"      /* U s carkou                */
#define LCD_CHAR_LLU    "\x82\x84\x91\x91\x91\x93\x8D\x80"      /* u s carkou                */
#define LCD_CHAR_CUU    "\x86\x97\x91\x91\x91\x91\x8E\x80"      /* U s krouzkem              */
#define LCD_CHAR_CLU    "\x86\x86\x91\x91\x91\x91\x8E\x80"      /* u s krouzkem              */
#define LCD_CHAR_LUY    "\x82\x95\x91\x8A\x84\x84\x84\x80"      /* Y s carkou                */
#define LCD_CHAR_LLY    "\x82\x84\x91\x91\x8F\x81\x8E\x80"      /* y s carkou                */
#define LCD_CHAR_HUZ    "\x8A\x9F\x81\x82\x84\x88\x9F\x80"      /* Z s hackem                */
#define LCD_CHAR_HLZ    "\x8A\x84\x9F\x82\x84\x88\x9F\x80"      /* z s hackem                */


// Program
// -------


static int8_t lcd_posx;		// Mirror Register with Position X (1..LCD_CHARS)
#if LCD_LINES > 1
static int8_t lcd_posy;		// Mirror Register with Position Y (1..LCD_LINES)
#endif


// Send a Nibble or Byte to the LCD COntroller
static void
lcd_send_nibble(uint8_t rs, uint8_t data)
{
	// Select Register or Data
	if (rs)
		LCD_RS_PORT |= (1<<LCD_RS_BIT);
 	else
		LCD_RS_PORT &= ~(1<<LCD_RS_BIT);

	// Put 4bit/8bit data
  	LCD_DATA_PORT = (LCD_DATA_PORT & ~LCD_DATA_MASK) | ((data<<LCD_DATA_BIT)&LCD_DATA_MASK);
 	_delay_us(1);	// Data Setup Time

	// Click Enable on and off
	LCD_E_PORT |= 1<<LCD_E_BIT;
 	_delay_us(1);
	LCD_E_PORT &= ~(1<<LCD_E_BIT);
	_delay_us(40);
}


// Send a Byte to the LCD Controller
#if LCD_INTERFACE_BITS == 4
static void
lcd_send_byte(uint8_t rs, uint8_t data)
{
 	lcd_send_nibble(rs, data >> 4);		// High Order Data
 	lcd_send_nibble(rs, data);			// Low Order Data
}
#else
	#define lcd_send_byte lcd_send_nibble
#endif


// Send a Command to the LCD Controller (RS=0)
#define lcd_send_cmd(n)		lcd_send_byte(0, (n))


// Send a Data Byte to the LCD Controller (RS=1)
#define lcd_send_data(n)	lcd_send_byte(1, (n))


// Goto Home
void 
lcd_home()
{
	lcd_send_cmd(LCD_HD44780_HOME);		// Zero Cursor Position and Offset
	#if LCD_LINES > 1
		lcd_posx=lcd_posy=1;
	#else
		lcd_posx=1;
	#endif
  	_delay_ms(2);
}


// Clear Display
void 
lcd_clear()
{
	lcd_send_cmd(LCD_HD44780_CLR);		// Clear Memory
  	_delay_ms(2);
}


// Switch Cursor On
void
lcd_cursor_on()
{
	lcd_send_cmd(LCD_HD44780_CURSORON);
}


// Switch Cursor Off
void
lcd_cursor_off()
{
	lcd_send_cmd(LCD_HD44780_CURSOROFF);
}


// Clear Display and Goto Home with no Cursor
void
lcd_clear_home()
{
	lcd_clear();		// Clear Memory
	lcd_home();			// Zero Cursor Position and Offset
	lcd_cursor_off();	// No Cursor
}


// Move to Position (1,1 is the first position)
void lcd_gotoxy(uint8_t x, uint8_t y)
{
	uint8_t Adr;

	Adr=x-1;
	#if LCD_LINES > 1
	switch (y)
	{
		case 2:
			Adr+=LCD_LINE_2;
			break;
		#if LCD_LINES > 2
		case 3:
			Adr+=LCD_LINE_3;
			break;
		case 4:
			Adr+=LCD_LINE_4;
			break;
		#endif
	}
	#endif

	lcd_send_cmd(0x80 | (Adr & 0x7F) );
	lcd_posx=x;
	#if LCD_LINES > 1
	lcd_posy=y;
	#endif
}


// Increment Position
void 
lcd_inc_pos()
{
	// Next Position
	lcd_posx++;

	// Correct End of Line
	#if LCD_LINES == 1
		if (lcd_posx > 40)
			lcd_posx = 1;
	#elif LCD_LINES == 2
		if (lcd_posx > 40)
		{
			lcd_posx = 1;
			lcd_posy++;		// on the Next Line
		}
	#elif LCD_LINES > 2
		if ( ((lcd_posy & 1) && (lcd_posx > LCD_CHARS))	// Odd Lines are Short
			|| (lcd_posx > 40-LCD_CHARS) )				// Memory is up to 40 Bytes
		{
			lcd_posx = 1;	// Position 1
			lcd_posy++;		// on the Next Line
		}
	#endif

	// Correct End of Last Line
	#if LCD_LINES > 1
	if (lcd_posy > LCD_LINES)
	{
		lcd_posy = 1;
	}
	#endif
}

// Decrement Position
void
lcd_dec_pos()
{
	// Correct Beginning of Line
	if (--lcd_posx==0)						// Step Left
	{										// If Beginning of the Line
		#if LCD_LINES > 1
			if(--lcd_posy==0);				// Step Up
				lcd_posy = LCD_LINES;		// If we are on Top Go to the Bottom
		#endif
		#if LCD_LINES <= 2
			lcd_posx = 40;				
		#else
			if(lcd_posy & 1)				// If Odd Line (the Short One)
				lcd_posx = LCD_CHARS;		// Set End of the Short Line
			else							// Else
				lcd_posx = 40-LCD_CHARS;	// Set End of Long Line
		#endif
	}
}

// Move Cursor Left
void
lcd_cursor_left()
{
	lcd_send_cmd(LCD_HD44780_CURSORLEFT);
	lcd_dec_pos();
}


// Move Cursor Right
void
lcd_cursor_right()
{
	lcd_send_cmd(LCD_HD44780_CURSORRIGHT);
	lcd_inc_pos();
}


// Init LCD Display
void
lcd_init(void)
{
	// Port Init Direction
	LCD_E_PORT  &= ~_BV(LCD_E_BIT);			// Enable off
	LCD_E_DDR  |= _BV(LCD_E_BIT);			// Enable as Output
  	LCD_RS_DDR |= _BV(LCD_RS_BIT);			// Register Select as Output
	#ifdef LCD_RW
		LCD_RW_DDR |= _BV(LCD_RW_BIT);		// Read Write as Output
	#endif
    LCD_DATA_DDR |= LCD_DATA_MASK;			// Data as Output

	// Initial Delay
  	_delay_ms(40);		// Delay for Vcc

	// Sync 8/4 bit Interface
	#if LCD_INTERFACE_BITS == 4
	  	lcd_send_nibble(0, LCD_HD44780_8BIT1LINE >> 4);	// 8 bit mode - sync nibble/byte
  		_delay_ms(4.1);
	  	lcd_send_nibble(0, LCD_HD44780_8BIT1LINE >> 4);
  		_delay_us(100);
	  	lcd_send_nibble(0, LCD_HD44780_8BIT1LINE >> 4);
		// Set 4 bit mode
  		lcd_send_nibble(0, LCD_HD44780_FNSET >> 4);
	#elif  LCD_INTERFACE_BITS == 8
	  	lcd_send_nibble(0, LCD_HD44780_8BIT1LINE);		// 8 bit mode - sync nibble/byte
  		_delay_ms(4.1);
		lcd_send_nibble(0, LCD_HD44780_8BIT1LINE);
  		_delay_us(100);
	  	lcd_send_nibble(0, LCD_HD44780_8BIT1LINE);
	#endif

	// Set and Init
	lcd_send_cmd(LCD_HD44780_FNSET);  		// 4/8 bits 1/2 lines	
	lcd_send_cmd(LCD_HD44780_ENTMODE_DEF);	// increment/decrement, shift/no shift
	lcd_clear_home();						// display on, no cursor, clear and home
}


// LCD Char Output
int
lcd_putc(char c)
{
	static uint8_t mode=0;

	switch (c)
	{
		case '\f':	
			lcd_clear_home();       // Clear Display
			break;

		case '\n':	
			#if LCD_LINES > 1
				if (lcd_posy <= LCD_LINES)	// Go to the Next Line
					lcd_posy++;
			#endif

 		case '\r':	
			#if LCD_LINES > 1
				lcd_gotoxy(1,lcd_posy);		// Go to the Beginning of the Line
			#else
				lcd_home();
			#endif
			break;

		case '\b':
			lcd_cursor_left();		// Cursor (Position) Move Back
			break;

		default:
			if (mode==0 && c=='\v')	// Startr of Definition String
			{
				mode=1;				// Mode Next Char will be Defined Char 
				break;
			}
			if (mode==1)			// First Char is Position Number
			{
				lcd_send_cmd(0x40 | ((c & 0x07)<<3) );	// Set CGRAM Address
				mode++;				// Mode Define Char Patern
				break;
			}
			if (mode==2 && c=='\v')	// End of Definition String
			{
				mode=0;
				#if LCD_LINES > 1
					lcd_gotoxy(lcd_posx,lcd_posy);
				#else
					lcd_gotoxy(lcd_posx,1);
				#endif
				break;
			}
			if (mode != 2)			// Ordinary Chars
			{
				if (c<0x20)			// Remap User Defind Char
					c &= 0x07;		// 	from rage 0x10-0x1F to 0x00-0x0f
				lcd_inc_pos();		// Next Position
				}
			lcd_send_data(c);		// Send Byte to LCD
			break;
	}
	
	return 0;	// Success
}


// LCD Char Output (for Stream Library)
#ifdef _STDIO_H_
static int
lcd_putc_stream(char c, FILE *unused)
{
	return lcd_putc(c);
}
#endif
