/** 
 * ---------------------------------------------------------------+ 
 * @desc        HD44780 LCD Driver 
 * ---------------------------------------------------------------+ 
 *              Copyright (C) 2020 Marian Hrinko.
 *              Written by Marian Hrinko (mato.hrinko@gmail.com)
 *
 * @author      Marian Hrinko
 * @datum       18.11.2020
 * @file        hd44780.c
 * @tested      AVR Atmega16a
 *
 * @depend      hd44780.h
 * ---------------------------------------------------------------+
 * @usage       default set 16x2 LCD
 *              4-bit with 3 control wires (RW, RS, E)
 */

// include libraries
#include <stdio.h>
//#include <util/delay.h>
//#include <avr/io.h>
#include "define.h"
#include "BoardCfg.h"
#include "hd44780.h"
#include "timer.h"

// +---------------------------+
// |         Power on          |
// | Wait for more than 15 ms  |   // 15 ms wait
// | after VCC rises to 4.5 V  |
// +---------------------------+
//              |
// +---------------------------+ 
// |  RS R/W DB7 DB6 DB5 DB4   |   
// |   0   0   0   0   1   1   |   // Initial sequence 0x30
// | Wait for more than 4.1 ms |   // 4.1 ms us writing DATA into DDRAM or CGRAM
// +---------------------------+
//              |
// +---------------------------+
// |  RS R/W DB7 DB6 DB5 DB4   |   
// |   0   0   0   0   1   1   |   // Initial sequence 0x30
// | Wait for more than 0.1 ms |   // 100 us writing DATA into DDRAM or CGRAM
// +---------------------------+
//              |
// +---------------------------+
// |  RS R/W DB7 DB6 DB5 DB4   |   // Initial sequence 0x30
// |   0   0   0   0   1   1   |   // 37 us writing DATA into DDRAM or CGRAM 4 us tadd - time after busy flag disapeared
// | Wait for more than 45 us  |   // 37 us + 4 us = 41 us * (270/250) = 45us
// +---------------------------+  
//              |
// +---------------------------+   // 4bit mode 0x20 !!! MUST BE SET TIME, BF CHECK DOESN'T WORK CORRECTLY !!!
// |  RS R/W DB7 DB6 DB5 DB4   |   // 
// |   0   0   0   0   1   0   |   // 37 us writing DATA into DDRAM or CGRAM 4 us tadd - time after busy flag disapeared
// | Wait for more than 45 us  |   // !!! MUST BE SET DELAY TIME, BUSY FLAG CHECK DOESN'T WORK CORRECTLY !!!
// +---------------------------+
//              |
// +---------------------------+
// |  RS R/W DB7 DB6 DB5 DB4   |   // Display off 0x08
// |   0   0   0   0   1   0   |   // 
// |   0   0   1   0   0   0   |   // 
// |    Wait for BF Cleared    |   // Wait for BF Cleared
// +---------------------------+
//              |
// +---------------------------+
// |  RS R/W DB7 DB6 DB5 DB4   |   // Display clear 0x01
// |   0   0   0   0   0   0   |   //
// |   0   0   0   0   0   1   |   //
// |    Wait for BF Cleared    |   // Wait for BF Cleared
// +---------------------------+
//              |
// +---------------------------+
// |  RS R/W DB7 DB6 DB5 DB4   |   // Entry mode set 0x06
// |   0   0   0   0   0   0   |   // 
// |   0   0   0   1   1   0   |   // shift cursor to the left, without text shifting
// |    Wait for BF Cleared    |   // Wait for BF Cleared
// +---------------------------+

/**
 * @desc    LCD display clear
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_DisplayClear (void)
{
	// Diplay clear
	HD44780_SendInstruction(HD44780_DISP_CLEAR);
}

/**
 * @desc    LCD display on
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_DisplayOn (void)
{
	// send instruction - display on
	HD44780_SendInstruction(HD44780_DISP_ON);
}

/**
 * @desc    LCD cursor on, display on
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_CursorOn (void)
{
	// send instruction - cursor on
	HD44780_SendInstruction(HD44780_CURSOR_ON);
}

/**
 * @desc    LCD cursor off
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_CursorOff (void)
{
	// send instruction - cursor on
	HD44780_SendInstruction(HD44780_CURSOR_OFF);
}

/**
 * @desc    LCD cursor blink, cursor on, display on
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_CursorBlink (void)
{
	// send instruction - Cursor blink
	HD44780_SendInstruction(HD44780_CURSOR_BLINK);
}

/**
 * @desc    LCD draw char
 *
 * @param   char
 *
 * @return  void
 */
void HD44780_DrawChar (char character)
{
	// Diplay clear
	HD44780_SendData(character);
}

/**
 * @desc    LCD draw string
 *
 * @param   char *
 *
 * @return  void
 */
void HD44780_DrawString (char *str)
{
	unsigned char i = 0;
	// loop through 5 bytes
	while (str[i] != '\0') {
		//read characters and increment index
		HD44780_SendData(str[i++]);
	}
}

/**
 * @desc    Got to position x,y
 *
 * @param   char
 * @param   char
 *
 * @return  char
 */
char HD44780_PositionXY (char x, char y)
{
	if (x > HD44780_COLS || y > HD44780_ROWS) {
		// error
		return ERROR;
	}
	// check which row
	if (y == 0) {
		// send instruction 1st row
		HD44780_SendInstruction(HD44780_POSITION | (HD44780_ROW1_START + x));
	} else if (y == 1) {
		// send instruction 2nd row
		HD44780_SendInstruction(HD44780_POSITION | (HD44780_ROW2_START + x));
	}
	// success
	return 0;
}

/**
 * @desc    Shift cursor / display to left / right
 *
 * @param   char item {HD44780_CURSOR; HD44780_DISPLAY}
 * @param   char direction {HD44780_RIGHT; HD44780_LEFT}
 *
 * @return  char
 */
char HD44780_Shift (char item, char direction)
{
	// check if item is cursor or display or direction is left or right
	if ((item != HD44780_DISPLAY) && (item != HD44780_CURSOR)) {
		// error
		return ERROR;
	}
	// check if direction is left or right
	if ((direction != HD44780_RIGHT) && (direction != HD44780_LEFT)) {
		// error
		return ERROR;
	}
	
	// cursor shift
	if (item == HD44780_CURSOR) {
		// right shift
		if (direction == HD44780_RIGHT) {
			// shit cursor / display to right / left
			HD44780_SendInstruction(HD44780_SHIFT | HD44780_CURSOR | HD44780_RIGHT);
		} else {
			// shit cursor / display to right / left
			HD44780_SendInstruction(HD44780_SHIFT | HD44780_CURSOR | HD44780_LEFT);
		}
		// display shift
	} else {
		// right shift
		if (direction == HD44780_RIGHT) {
			// shit cursor / display to right / left
			HD44780_SendInstruction(HD44780_SHIFT | HD44780_DISPLAY | HD44780_RIGHT);
		} else {
			// shit cursor / display to right / left
			HD44780_SendInstruction(HD44780_SHIFT | HD44780_DISPLAY | HD44780_LEFT);
		}
	}
	// success
	return 0;
}

/**
 * @desc    LCD init - initialisation routine
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_Init (void)
{
	// set E as output
	//SETBIT(HD44780_DDR_E, HD44780_E);
	// set RS as output
	//SETBIT(HD44780_DDR_RS, HD44780_RS);
	// set RW as output
	//SETBIT(HD44780_DDR_RW, HD44780_RW);
	
	// set DB7-DB4 as output
//	HD44780_SetDDR_DATA4to7();
	
	// clear RS
	//  CLRBIT(HD44780_PORT_RS, HD44780_RS);
	LCD_RS_PIN = 0;
	// clear RW
	//  CLRBIT(HD44780_PORT_RW, HD44780_RW);
	LCD_RW_PIN = 0;
	// clear E
	//  CLRBIT(HD44780_PORT_E, HD44780_E);
	LCD_E_PIN = 0;
	
	// delay > 15ms
	_delay_ms(16);
	
	
	// Busy Flag (BF) cannot be checked in these instructions
	// ---------------------------------------------------------------------
	// Initial sequence 0x30 - send 4 bits in 4 bit mode
//	HD44780_SendInstruction(HD44780_INIT_SEQ);JFM
	HD44780_Send4bitsIn4bitMode(HD44780_INIT_SEQ);
	// delay > 4.1ms
	_delay_ms(5);
	
	
	// pulse E
	//HD44780_PulseE();
	// delay > 100us
	_delay_us(110);
	
	HD44780_Send4bitsIn4bitMode(HD44780_INIT_SEQ);

	
	// pulse E
//	HD44780_PulseE();
	// delay > 45us (=37+4 * 270/250)
	_delay_us(50);
	
	
	HD44780_Send4bitsIn4bitMode(HD44780_INIT_SEQ);
	// 4 bit mode 0x20 - send 4 bits in 4 bit mode
	HD44780_Send4bitsIn4bitMode(HD44780_4BIT_MODE);
	// pulse E
	//HD44780_PulseE();
	// delay > 45us (=37+4 * 270/250)
	_delay_us(50);
	// ----------------------------------------------------------------------
	
	// 4-bit & 2-lines & 5x8-dots 0x28 - send 8 bits in 4 bit mode
	HD44780_SendInstruction(HD44780_4BIT_MODE | HD44780_2_ROWS | HD44780_FONT_5x8);
	
	// display off 0x08 - send 8 bits in 4 bit mode
	HD44780_SendInstruction(HD44780_DISP_OFF);
	
	// display clear 0x01 - send 8 bits in 4 bit mode
	HD44780_SendInstruction(HD44780_DISP_CLEAR);
	
	// entry mode set 0x06 - send 8 bits in 4 bit mode
	HD44780_SendInstruction(HD44780_ENTRY_MODE);
}

/**
 * @desc    Check Busy Flag (BF) in 4 bit mode
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_CheckBFin4bitMode (void)
{
	unsigned char input = 0;
	
	Sleep(5);
	return;
	
	// clear DB7-DB4 as input
	HD44780_ClearDDR_DATA4to7();
	// set pull-up resistors for DB7-DB4 
//	HD44780_SetPORT_DATA4to7();
	
	// clear RS
//	CLRBIT(HD44780_PORT_RS, HD44780_RS);
	LCD_RS_PIN = 0;
	// set RW - read instruction
//	SETBIT(HD44780_PORT_RW, HD44780_RW);
	LCD_RW_PIN = 1;
	
	// test HIGH level on PIN DB7
	// after clear PIN DB7 should continue
	// -------------------------------------
	//  us:     0.5|0.5|0.5
	//          ___     ___
	//   E: ___/   \___/   \__
	//           ___     ___
	// DB7: \___/   \___/   \__
	//
	while (1) {
		
		// Read upper nibble
		// --------------------------------
		// Set E
//		SETBIT(HD44780_PORT_E, HD44780_E);
		LCD_E_PIN = 1;
		// PWeh > 0.5us
		_delay_us(0.5);
		// read upper nibble (tDDR > 360ns)
		input = HD44780_PIN_DATA;
		// Clear E
//		CLRBIT(HD44780_PORT_E, HD44780_E);
		LCD_E_PIN = 0;
		// TcycE > 1000ns -> delay depends on PWeh delay time
		// delay = TcycE - PWeh = 1000 - 500 = 500ns
		_delay_us(0.5);
		
		// Read lower nibble
		// --------------------------------
		// Set E
//		SETBIT(HD44780_PORT_E, HD44780_E);
		LCD_E_PIN = 1;
		// PWeh > 0.5us
		_delay_us(0.5);
		// read lower nibble (tDDR > 360ns)
		//input |= (unsigned char)(HD44780_PIN_DATA >> 4);
		char IsBusy = (HD44780_DATA7 == 1);
		
		// Clear E
//		CLRBIT(HD44780_PORT_E, HD44780_E);
		LCD_E_PIN = 0;
		// TcycE > 1000ns -> delay depends on PWeh delay time
		// delay = TcycE - PWeh = 1000 - 500 = 500ns
		_delay_us(0.5);
		
//		// check if DB7 is cleared
//		if (!(input & (1 << HD44780_DATA7))) 
		if (IsBusy == 0) 
		{
			// if BF cleared -> end loop
			break;
		}
	}
	
	// clear RW
//	CLRBIT(HD44780_PORT_RW, HD44780_RW);
	LCD_RW_PIN = 0;
	
	// set DB7-DB4 as output
	HD44780_SetDDR_DATA4to7();
}

/**
 * @desc    Check Busy Flag (BF) in 8 bit mode
 *
 * @param   void
 * @return  void
 */
void HD44780_CheckBFin8bitMode (void)
{
}

/**
 * @desc    LCD send instruction
 *
 * @param   unsigned short int 
 *
 * @return  void
 */
void HD44780_SendInstruction (unsigned short int data)
{
	// Clear RS
//	HD44780_PORT_RS &= ~(1 << HD44780_RS);
	LCD_RS_PIN = 0;
	
	// 4bit mode
	// ------------------------------------------
	if (HD44780_MODE == HD44780_4BIT_MODE) {
		// send required data in required mode
		HD44780_Send8bitsIn4bitMode(data);
		// check busy flag
		HD44780_CheckBFin4bitMode();
		// 8 bit mode
		// ------------------------------------------
	} else if (HD44780_MODE == HD44780_8BIT_MODE) {
		// send required data in required mode
		HD44780_Send8bitsIn8bitMode(data);
		// check busy flag
		HD44780_CheckBFin8bitMode();
	}
}

/**
 * @desc    LCD send data
 *
 * @param   unsigned short int
 *
 * @return  void
 */
void HD44780_SendData (unsigned short int data)
{
	// Set RS
//	SETBIT(HD44780_PORT_RS, HD44780_RS);
	LCD_RS_PIN = 1;
	
	// 4bit mode
	// ------------------------------------------
	if (HD44780_MODE == HD44780_4BIT_MODE) {
		// send required data in required mode
		HD44780_Send8bitsIn4bitMode(data);
		// check busy flag
		HD44780_CheckBFin4bitMode();
		// 8 bit mode
		// ------------------------------------------
	} else if (HD44780_MODE == HD44780_8BIT_MODE) {
		// send required data in required mode
		HD44780_Send8bitsIn8bitMode(data);
		// check busy flag
		HD44780_CheckBFin8bitMode();
	}
	
	// Clear RS
//	CLRBIT(HD44780_PORT_RS, HD44780_RS); 
	LCD_RS_PIN = 0;
}

/**
 * @desc    LCD send 4bits instruction in 4 bit mode
 *
 * @param   unsigned short int
 *
 * @return  void
 */
void HD44780_Send4bitsIn4bitMode (unsigned short int data)
{
	// Set E
//	SETBIT(HD44780_PORT_E, HD44780_E);
	LCD_E_PIN = 1;
	// send data to LCD
	HD44780_SetUppNibble(data);
	// PWeh delay time > 450ns
	_delay_us(0.5);
	// Clear E
//	CLRBIT(HD44780_PORT_E, HD44780_E);
	LCD_E_PIN = 0;
	// TcycE > 1000ns -> delay depends on PWeh delay time
	// delay = TcycE - PWeh = 1000 - 500 = 500ns
	_delay_us(0.5);
}

/**
 * @desc    LCD send 8bits instruction in 4 bit mode
 *
 * @param   unsigned short int
 *
 * @return  void
 */
void HD44780_Send8bitsIn4bitMode (unsigned short int data)
{
	// Send upper nibble
	// ----------------------------------
	// Set E
//	SETBIT(HD44780_PORT_E, HD44780_E);
	LCD_E_PIN = 1;
	// send data to LCD
	HD44780_SetUppNibble(data);
	// PWeh delay time > 450ns
	_delay_us(0.5);
	// Clear E
//	CLRBIT(HD44780_PORT_E, HD44780_E);
	LCD_E_PIN = 0;
	// TcycE > 1000ns -> delay depends on PWeh delay time
	// delay = TcycE - PWeh = 1000 - 500 = 500ns
	_delay_us(0.5);
	
	// Send lower nibble
	// ----------------------------------
	// Set E
//	SETBIT(HD44780_PORT_E, HD44780_E);
	LCD_E_PIN = 1;
	// send data to LCD
	HD44780_SetUppNibble(data << 4);
	// PWeh delay time > 450ns
	_delay_us(0.5);
	// Clear E
//	CLRBIT(HD44780_PORT_E, HD44780_E);
	LCD_E_PIN = 0;
	// TcycE > 1000ns -> delay depends on PWeh delay time
	// delay = TcycE - PWeh = 1000 - 500 = 500ns
	_delay_us(0.5);
}

/**
 * @desc    LCD send 8bits instruction in 8 bit mode
 *
 * @param   unsigned short int
 *
 * @return  void
 */
void HD44780_Send8bitsIn8bitMode (unsigned short int data)
{
	// Set E
	SETBIT(HD44780_PORT_E, HD44780_E);
	// send data to LCD
	HD44780_SetUppNibble(data);
	// send data to LCD
	HD44780_SetLowNibble(data);
	// PWeh delay time > 450ns
	_delay_us(0.5);
	// Clear E
	CLRBIT(HD44780_PORT_E, HD44780_E);
	// TcycE > 1000ns -> delay depends on PWeh delay time
	// delay = TcycE - PWeh = 1000 - 500 = 500ns
	_delay_us(0.5);
}

/**
 * @desc    LCD send upper nibble
 *
 * @param   unsigned short int
 *
 * @return  void
 */
void HD44780_SetUppNibble (unsigned short int data)
{
	
//	LCD_DB4_PIN = 0;
//	LCD_DB5_PIN = 0;
//	LCD_DB6_PIN = 0;
//	LCD_DB7_PIN = 0;
	unsigned short test = data;
	
	if (data & 0x80) 
	{  
		LCD_DB7_PIN = 1; 
	}
	else
	{
		LCD_DB7_PIN = 0; 
	}
	if (data & 0x40) 
	{  
		LCD_DB6_PIN = 1; 
	}
	else
	{
		LCD_DB6_PIN = 0;
	}
	if (data & 0x20) 
	{  
		LCD_DB5_PIN = 1;  
	}
	else
	{
		LCD_DB5_PIN = 0;
	}
	if (data & 0x10) 
	{  
		LCD_DB4_PIN = 1;  
	}
	else
	{
		LCD_DB4_PIN = 0;
	}
		
	
	// clear bits DB7-DB4
//	CLRBIT(HD44780_PORT_DATA, HD44780_DATA7);
//	CLRBIT(HD44780_PORT_DATA, HD44780_DATA6);
//	CLRBIT(HD44780_PORT_DATA, HD44780_DATA5);
//	CLRBIT(HD44780_PORT_DATA, HD44780_DATA4);
//	// set DB7-DB4 if corresponding bit is set
//	if (data & 0x80) { SETBIT(HD44780_PORT_DATA, HD44780_DATA7); }
//	if (data & 0x40) { SETBIT(HD44780_PORT_DATA, HD44780_DATA6); }
//	if (data & 0x20) { SETBIT(HD44780_PORT_DATA, HD44780_DATA5); }
//	if (data & 0x10) { SETBIT(HD44780_PORT_DATA, HD44780_DATA4); }
}

/**
 * @desc    LCD send lower nibble
 *
 * @param   unsigned short int
 *
 * @return  void
 */
void HD44780_SetLowNibble (unsigned short int data)
{
	// clear bits DB7-DB4
	CLRBIT(HD44780_PORT_DATA, HD44780_DATA3);
	CLRBIT(HD44780_PORT_DATA, HD44780_DATA2);
	CLRBIT(HD44780_PORT_DATA, HD44780_DATA1);
	CLRBIT(HD44780_PORT_DATA, HD44780_DATA0);
	// set DB7-DB4 if corresponding bit is set
	if (data & 0x08) { SETBIT(HD44780_PORT_DATA, HD44780_DATA3); }
	if (data & 0x04) { SETBIT(HD44780_PORT_DATA, HD44780_DATA2); }
	if (data & 0x02) { SETBIT(HD44780_PORT_DATA, HD44780_DATA1); }
	if (data & 0x01) { SETBIT(HD44780_PORT_DATA, HD44780_DATA0); }
}

/**
 * @desc    LCD pulse E
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_PulseE (void)
{
	// Set E
	//  SETBIT(HD44780_PORT_E, HD44780_E);
	LCD_E_PIN = 1;
	// PWeh delay time > 450ns
	_delay_us(0.5);
	// Clear E
	//  CLRBIT(HD44780_PORT_E, HD44780_E);
	LCD_E_PIN = 0;
	// TcycE > 1000ns -> delay depends on PWeh delay time
	// delay = TcycE - PWeh = 1000 - 500 = 500ns
	_delay_us(0.5);
}

/**
 * @desc    Set PORT DB4 to DB7
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_SetPORT_DATA4to7 (void)
{
	// set DB4-DB7  
	LCD_DB4_PIN = 1;
	LCD_DB5_PIN = 1;
	LCD_DB6_PIN = 1;
	LCD_DB7_PIN = 1;
	
	//	SETBIT(HD44780_PORT_DATA, HD44780_DATA4);
	//	SETBIT(HD44780_PORT_DATA, HD44780_DATA5);
	//	SETBIT(HD44780_PORT_DATA, HD44780_DATA6);
	//	SETBIT(HD44780_PORT_DATA, HD44780_DATA7);
}

/**
 * @desc    Clear DDR DB4 to DB7
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_SetDDR_DATA4to7 (void)
{
	// set DB4-DB7  
	LCD_DB4_PIN_DIR = PIN_OUTPUT;
	LCD_DB5_PIN_DIR = PIN_OUTPUT;
	LCD_DB6_PIN_DIR = PIN_OUTPUT;
	LCD_DB7_PIN_DIR = PIN_OUTPUT;
	//  CLRBIT(HD44780_DDR_DATA, HD44780_DATA4);
	//  CLRBIT(HD44780_DDR_DATA, HD44780_DATA5);
	//  CLRBIT(HD44780_DDR_DATA, HD44780_DATA6);
	//  CLRBIT(HD44780_DDR_DATA, HD44780_DATA7);
}

/**
 * @desc    Set DDR DB4 to DB7
 *
 * @param   void
 *
 * @return  void
 */
void HD44780_ClearDDR_DATA4to7 (void)
{
	LCD_DB4_PIN_DIR = PIN_INPUT;
	LCD_DB5_PIN_DIR = PIN_INPUT;
	LCD_DB6_PIN_DIR = PIN_INPUT;
	LCD_DB7_PIN_DIR = PIN_INPUT;
	//  SETBIT(HD44780_DDR_DATA, HD44780_DATA4);
	//  SETBIT(HD44780_DDR_DATA, HD44780_DATA5);
	//  SETBIT(HD44780_DDR_DATA, HD44780_DATA6);
	//  SETBIT(HD44780_DDR_DATA, HD44780_DATA7);
}
