/*
    lcd_shift.c
    print and setting functions for a HD44780 compatible display
    Copyright (C) 2011  Marc Heimann <mheimann84@googlemail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <util/delay.h>
#include "lcd_shift.h"


void lcd_enable( void )
{
    PORT_OUT_ENABLE |= PIN_ENABLE;
    _delay_us( 2 );
    PORT_OUT_ENABLE &= ~PIN_ENABLE;
}

// shifts one byte to shift register 74HC164
// no delays are used because the 74HC164 is 
// fast enough (74HC164 SIPO.pdf, p. 5)
void shift_byte( uint8_t data_byte )
{
    uint8_t i;
    
    for (i = 0; i < 8; i++)
    {
        if (data_byte &  (1<<(7-i))) // from left to right
            PORT_OUT_DATA |= PIN_DATA;
        else
            PORT_OUT_DATA &= ~PIN_DATA;
        
        PORT_OUT_CLOCK |= PIN_CLOCK;
        PORT_OUT_CLOCK &= ~PIN_CLOCK;
    }
}

// chooses instruction delay (HD44780.pdf, p. 24, 25)
void send_instr( uint8_t data )
{
    if (data & RETURN_HOME)
        send_instruction( data, 1520 );
    else
        send_instruction( data, 37 );
}

// sends an instruction via the shift register to the display (RS=0)
void send_instruction( uint8_t data, uint16_t usec_delay )
{
    uint8_t data_byte;
    
    // upper 4 bit
    data_byte = 0;
    if (data & PIN_DB4) data_byte |= SHIFT_OUT_DB0_DB4;
    if (data & PIN_DB5) data_byte |= SHIFT_OUT_DB1_DB5;
    if (data & PIN_DB6) data_byte |= SHIFT_OUT_DB2_DB6;
    if (data & PIN_DB7) data_byte |= SHIFT_OUT_DB3_DB7;
    shift_byte( data_byte );
    lcd_enable();
    _delay_us( usec_delay );
    
    // lower 4 bit
    data_byte = 0;
    if (data & PIN_DB0) data_byte |= SHIFT_OUT_DB0_DB4;
    if (data & PIN_DB1) data_byte |= SHIFT_OUT_DB1_DB5;
    if (data & PIN_DB2) data_byte |= SHIFT_OUT_DB2_DB6;
    if (data & PIN_DB3) data_byte |= SHIFT_OUT_DB3_DB7;
    shift_byte( data_byte ); 
    lcd_enable();
    _delay_us( usec_delay );
}

// sends data via the shift register to the display (RS=1)
void send_data( uint8_t data )
{
    uint8_t data_byte;
    
    // upper 4 bit
    data_byte = SHIFT_OUT_RS;
    if (data & PIN_DB4) data_byte |= SHIFT_OUT_DB0_DB4;
    if (data & PIN_DB5) data_byte |= SHIFT_OUT_DB1_DB5;
    if (data & PIN_DB6) data_byte |= SHIFT_OUT_DB2_DB6;
    if (data & PIN_DB7) data_byte |= SHIFT_OUT_DB3_DB7;
    shift_byte( data_byte );
    lcd_enable();
    _delay_us( 50 ); // considering t_ADD (HD44780.pdf, p. 25, figure 10)
    
    // lower 4 bit
    data_byte = SHIFT_OUT_RS;
    if (data & PIN_DB0) data_byte |= SHIFT_OUT_DB0_DB4;
    if (data & PIN_DB1) data_byte |= SHIFT_OUT_DB1_DB5;
    if (data & PIN_DB2) data_byte |= SHIFT_OUT_DB2_DB6;
    if (data & PIN_DB3) data_byte |= SHIFT_OUT_DB3_DB7;
    shift_byte( data_byte ); 
    lcd_enable();
    _delay_us( 50 );
}

void init_lcd( void )
{
    // processor settings //
    // set port output
    PORT_DIR_CLOCK |= PIN_CLOCK;
    PORT_DIR_DATA |= PIN_DATA;
    PORT_DIR_ENABLE |= PIN_ENABLE;
    
    // set output zero
    PORT_OUT_CLOCK &= ~PIN_CLOCK;
    PORT_OUT_DATA &= ~PIN_DATA;
    PORT_OUT_ENABLE &= ~PIN_ENABLE;
    
    
    // display settings //
    // The instructions were taken from HD44780.pdf, p. 42
    
    // step 1, power on
    _delay_us( 16000 );
    
    // step 2, set 4 bit operation
    shift_byte( SHIFT_OUT_DB1_DB5 ); 
    lcd_enable();
    _delay_us( 37 );
    
    // step 3, two instead of one line 
    send_instruction( SET_4BIT_TWO_LINES, 37 );
    
    // step 4, without cursor
    send_instruction( SET_DISPLAY_ON, 37 );
}

// prints content of 'buffer' to DDRAM address 'addr' 
void print_lcd( char* buffer, uint8_t addr )
{
    uint8_t pos, i;
    
    send_instruction( SET_DDRAM_ADDRESS | addr, 37 );
    
    if (addr >= LINE_2_ADDR) // second line
    {
        pos = addr - LINE_2_ADDR;
    }
    
    else // first line
    {
        pos = addr;
    }
    
    for (i = 0; (buffer[i] != 0) && (pos < CHARACTERS_PER_LINE); i++)
    {
        send_data( buffer[i] );
        pos++;
    }
}