/* ---------------------------------------------------------------------------
 * 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
 * ---------------------------------------------------------------------------
 */

#include "lcd_hd44780.h"

// 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

#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
}