// Tipo de acesso: GPIO ou I2C usando o pcf8574.
//	Para o pcf8574 a comunicação é 4 bits e consideramos que as seguintes ligações:
//		P0: LCD_D4
//		P1: LCD_D5
//		P2: LCD_D6
//		P3: LCD_D7
//		P4: LCD_RS
//		P5: LCD_RW
//		P6: LCD_E

// O módulo display é fabricado com um controlador de exibição.
// Este controlador tem dois registradores:	um registrador de instrução (IR) e um registrador de dados (DR).
// O registrador IR armazena os comandos, tal como limpar o display, fazer um shift e entre outros comandos.
// O registrador DR armazena temporiaramente o dado que vai ser escrito na DDRAM/CGRAM, ou lido delas

// RS RW Operação
// 0  0  Envia um comando para o display.
// 0  1  Lê o flag de ocupado (BF=DB7) e o endereço de contagem (AC=DB[6:0]) de acesso a DDRAM/CGRAM.
// 1  0  Escreve dado contido no registrador DR para DDRAM/CGRAM.
// 1  1  Lê o dado DDRAM/CGRAM para o DR.

// DDRAM: memória de dados de exibição.
//	Esta DDRAM é usada para armazenar os dados de exibição. O valor escrito nessa memória é um apontador para CGRAM e CGROM
//
//		Apontador para o CGROM e CGRAM (addr 0 a 7)
// 		Linha	LCD	16 colunas	LCD	20 colunas
// 		1 		00 - 15 		00 - 19
// 		2 		20 - 35			20 - 39
// 		3		64 - 79			64 - 83
// 		4       84 - 99  		84 - 103

// CGROM: Memória ROM dos caracteres. Contém os bits da formação de cada caractere. Valores fixos de fábrica
//	O endereço de cada caractere corresponde o seus respectivo valor ASCII

// CGRAM: Memória de caracteres personalizados
//	Essa memória podemos construir novos caracteres a serem exibidas no display.
//	Para caracteres 5×8 pontos podemos salvar até oito padrões, e para 5×10 pontos podemos salvar somente quatro padrões.
//	Os endereços de acesso a esses caractres são entre 0 a 8.

// Registrador de acesso a DDRAM/CGRAM
// 	O endereço (reg AC) é incrementado automaticamente após escrever ou ler o dados das memórias

// INSTRUÇÕES
// RS RW | D7  D6  D5  D4  D3  D2  D1  D0
// 0  0  | 0   0   0   0   0   0   0   1 		Clear Display: Limpa o display e zera o contador de endereços (AC) da DDRAM
// 0  0  | 0   0   0   0   0   0   1   - 		Return Home: Zera o contador de endereços da DDRAM, faz com que o display reinicie o deslocamento da posição original. Não altera o conteúdo da DDRAM
// 0  0  | 0   0   0   0   0   1   ID  SF 		Entry Mode Set: Define a direção de movimento do cursor e especifica a direção de deslocamento dos caracteres no display
//		 											ID: 0 – decrementa, 1 – incrementa contador de endereços
//		 											SF: 0 – não acompanha, 1 – acompanha o delocamento do display
// 0  0  | 0   0   0   0   1   DA  CA  BL 		Definições diversas
//													DA: 0 – desliga, 1 – liga display
//													CA: BL
//													0	0	Desliga cursor
//													0	1	Cursor OVR (Cursor cheio)
//													1	0	Cursor INS (Cursor underline)
//													1	1	Cursor piscando ( alternando entre OVR e INS)
// 0  0  | 0   0   0   1   SC  RL  -   - 		Movimento de caracteres e cursor no display sem alterar o conteúdo da DDRAM
//													SC: 0 – move o cursor, 1 – desloca os caracteres
//													RL: 0 – desloca para a esquerda, 1 – desloca para a direita
// 0  0  | 0   0   1   DL  NL  FC  -   - 		Function Set: Define o comprimento de dados da interface
//													DL: 0 – 4-bits, 1 – 8-bits
//													NL: 0 – 1 linha, 1 – 2 linhas
//													FC: 0 – matriz 5x7, 1 – matriz 5x10
// 0  0  | 0   1   A5  A4  A3  A2  A1  A0 		Set CGRAM Addrress: Armazena o endereço especificado no contador de endereços da CG RAM. Os dados são escritos ou lidos da CGRAM após este comando.
// 0  0  | 1   A6  A5  A4  A3  A2  A1  A0 		Set CGRAM Addrress: Armazena o endereço especificado no contador de endereços da DDRAM Os dados são escritos ou lidos da DDRAM após este comando.
// 0  1  | BF  A6  A5  A4  A3  A2  A1  A0       Read Busy Flag and register address DDRAM/CGRAM
// 1  0  | D7  D6  D5  D4  D3  D2  D1  D0		Escreve dado do DR para a DDRAM ou CGRAM
// 1  1  | D7  D6  D5  D4  D3  D2  D1  D0		Lê dado DDRAM ou CGRAM para o DR

// TEMPOS
// Esperar 40ms após ligar display antes de começar enviar os comandos.
// Comandos 0x01 e 0x02 levam para ser processado 1.64ms os demais 40us

#include "liquid_crystal.h"

u8 _row_offsets[4];
u8 _numlines;
u8 _displaycontrol;
u8 _displaymode;
u8 _displayfunction;

// espera até o display estiver desocupado
static void lcd_busy(void)	{
	while (lcd_read_command() & 0x80); // Se bit 7 for 1 sinaliza display está ocupado
}

static void lcd_SetRowOffsets(int row0, int row1, int row2, int row3) {
  	_row_offsets[0] = row0;
  	_row_offsets[1] = row1;
  	_row_offsets[2] = row2;
  	_row_offsets[3] = row3;
}

static inline void lcd_command(u8 command) {
  	lcd_busy();
	lcd_write_command(command);
}

// Inicializa o display
// parametros:
//	cols: Número de colunas do LCD
//	lines: Número de linhas do LCD
//  dotsize: Tamanho da fonte em pontos: LCD_5x8DOTS ou LCD_5x10DOTS
void lcd_Init(u8 cols, u8 lines, u8 dotsize) {
	#if (LCD_CONNECTION == LCD_CONNECTION_PCF8574)
	_displayfunction = LCD_4BIT_MODE | LCD_1LINE | dotsize;
	#elif (LCD_CONNECTION == LCD_CONNECTION_GPIO_4BITS)
	lcd_config_ports();
	_displayfunction = LCD_4BIT_MODE | LCD_1LINE | dotsize;
	#elif (LCD_CONNECTION == LCD_CONNECTION_GPIO_8BITS)
	lcd_config_ports();
	_displayfunction = LCD_8BIT_MODE | LCD_1LINE | dotsize;
	#endif

  	if (lines > 1) _displayfunction |= LCD_2LINE;
  	_numlines = lines;

  	lcd_SetRowOffsets(0x00, 0x40, 0x00 + cols, 0x40 + cols);

  	// for some 1 line displays you can select a 10 pixel high font
  	if ((dotsize != LCD_5x8DOTS) && (lines == 1)) _displayfunction |= LCD_5x10DOTS;

  	// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
  	// according to datasheet, we need at least 40ms after power rises above 2.7V
  	// before sending commands. CPU can turn on way before 4.5V so we'll wait 50
  	delay_ms(LCD_DELAY_INIT);

    // this is according to the hitachi HD44780 datasheet
    // page 45 figure 23
    // Send function set command sequence
	lcd_write_init(0x30);	// Function set (Interface is 8 bits long.)
    delay_ms(5);  				// wait more than 4.1ms
	lcd_write_init(0x30);	// Function set (Interface is 8 bits long.)
	delay_us(150);				// wait more than 100us
	lcd_write_init(0x30);	// Function set (Interface is 8 bits long.)

	#if (LCD_CONNECTION == LCD_CONNECTION_PCF8574) || (LCD_CONNECTION == LCD_CONNECTION_GPIO_4BITS)
	lcd_write_init(LCD_FUNCTION_SET | LCD_4BIT_MODE);
	#endif

	// finally, set # lines, font size, etc.
	lcd_command(LCD_FUNCTION_SET | _displayfunction);

  	// turn the display on with no cursor or blinking default
  	_displaycontrol = LCD_DISPLAY_ON | LCD_CURSOR_OFF | LCD_BLINK_OFF;
  	lcd_Display();

  	// clear it off
  	lcd_Clear();

  	// Initialize to default text direction (for romance languages)
  	_displaymode = LCD_ENTRY_LEFT | LCD_ENTRY_SHIFT_DECREMENT;
  	// set the entry mode
  	lcd_command(LCD_ENTRY_MODE_SET | _displaymode);
}

void lcd_Clear(void) {
  lcd_command(LCD_CLEAR_DISPLAY);  // clear display, set cursor position to zero
  delay_ms(LCD_DELAY_HOME);  // this command takes a long time!
}

void lcd_Home(void) {
  lcd_command(LCD_RETURN_HOME);  // set cursor position to zero
  delay_ms(LCD_DELAY_HOME);  	// this command takes a long time!
}

void lcd_SetCursor(u8 col, u8 row) {
  const uint max_lines = sizeof(_row_offsets) / sizeof(*_row_offsets);
  if ( row >= max_lines ) row = max_lines - 1;    // we count rows starting w/0
  if ( row >= _numlines ) row = _numlines - 1;    // we count rows starting w/0

  lcd_command(LCD_SET_DDRAM_ADDR | (col + _row_offsets[row]));
}

// Turn the display on/off (quickly)
void lcd_NoDisplay(void) {
  _displaycontrol &= ~LCD_DISPLAY_ON;
  lcd_command(LCD_DISPLAY_CONTROL | _displaycontrol);
}

void lcd_Display(void) {
  _displaycontrol |= LCD_DISPLAY_ON;
  lcd_command(LCD_DISPLAY_CONTROL | _displaycontrol);
}

// Turns the underline cursor on/off
void lcd_NoCursor(void) {
  _displaycontrol &= ~LCD_CURSOR_ON;
  lcd_command(LCD_DISPLAY_CONTROL | _displaycontrol);
}

// TODO colocar cursor(on/off) e para outras funções também
void lcd_Cursor(void) {
  _displaycontrol |= LCD_CURSOR_ON;
  lcd_command(LCD_DISPLAY_CONTROL | _displaycontrol);
}

// desliga o cursor
void lcd_NoBlink(void) {
  _displaycontrol &= ~LCD_BLINK_ON;
  lcd_command(LCD_DISPLAY_CONTROL | _displaycontrol);
}

// Pisca o cursor
void lcd_Blink(void) {
  _displaycontrol |= LCD_BLINK_ON;
  lcd_command(LCD_DISPLAY_CONTROL | _displaycontrol);
}

// These commands scroll the display without changing the RAM
void lcd_ScrollDisplayLeft(void) {
  lcd_command(LCD_CURSOR_SHIFT | LCD_DISPLAY_MOVE | LCD_MOVE_LEFT);
}

void lcd_ScrollDisplayRight(void) {
  lcd_command(LCD_CURSOR_SHIFT | LCD_DISPLAY_MOVE | LCD_MOVE_RIGHT);
}

// This is for text that flows Left to Right
void lcd_LeftToRight(void) {
  _displaymode |= LCD_ENTRY_LEFT;
  lcd_command(LCD_ENTRY_MODE_SET | _displaymode);
}

// This is for text that flows Right to Left
void lcd_RightToLeft(void) {
  _displaymode &= ~LCD_ENTRY_LEFT;
  lcd_command(LCD_ENTRY_MODE_SET | _displaymode);
}

// This will 'right justify' text from the cursor
void lcd_AutoScroll(void) {
  _displaymode |= LCD_ENTRY_SHIFT_INCREMENT;
  lcd_command(LCD_ENTRY_MODE_SET | _displaymode);
}

// This will 'left justify' text from the cursor
void lcd_NoAutoScroll(void) {
  _displaymode &= ~LCD_ENTRY_SHIFT_INCREMENT;
  lcd_command(LCD_ENTRY_MODE_SET | _displaymode);
}

// Imprime um caractere no display, no endereço atual da DDRAM apontado pelo reg AC.
//	O reg AC é incrementado automaticamente
// Esta função também é usada pelo rprintf
int lcd_PutChar(n16 ch) {
	lcd_busy();
	lcd_write_data((u8)ch);
	return (int)ch;												// Retorna com o caractere a ser TX
}

// Salva caracteres personalizados nas posições 0 a 7 da memória CGRAM que serão acessadas
//	escrevendo valores de 0 a 7 na DDRAM
// Paramêtros:
//	location: Endereço CGRAM. Valores de 0 a 7
//	charmap: Ponteiro para o vetor de 8 posições que constitui a formação do novo caractere
void lcd_CreateChar(u8 location, u8 charmap[]) {
	u8 addr = lcd_read_command() & 0x7F; // pega o endereço da DDRAM atual

	location &= 0x7; // we only have 8 locations 0-7
  	lcd_command(LCD_SET_CGRAM_ADDR | (location << 3)); // ajusta o endereço deslocando sempre oito bytes porque o buffer é de tamanho 8
  	uint i;	for (i=0; i<8; i++) lcd_PutChar(charmap[i]);

  	lcd_command(LCD_SET_DDRAM_ADDR | addr); // atualiza o endereço DDRAM e instrui o LCD a trablhar com acesso a DDRAM
}


#if (LCD_USE_PRINTF == pdON)
#include "stdio_uc.h"
static uint devicePrintf = 0;
u8 _cols;
void lcd_printfInit(uint cols, uint dev) {
	_cols = cols;
	devicePrintf = dev;
	rprintf_RegisterDev(dev, lcd_PutChar);								// Anexa a função ao printf
}

// faz o mesmo que rprintf, porém acrescenta espaços vazios para preencher toda a linha,
//	apagando os caracteres impressos anteriormente
void lcd_printf(const cpchar fmt, ...) {
	string s;
	tPrint output;

	output.device = 0;
	output.buf = s;					// Sinaliza qual buffer para armazenar a formatação de string. Também sinaliza que é uma formatação de string e não uma impressão em dipositivo
	output.isSformat = 1;			// Sinaliza que é um sformat

	va_list args;					// Cria var para captura de argumentos (fmt) no stack da chamada de função
	va_start(args, fmt);			// Captura os argumentos após fmt
	doprint(&output, fmt, args);	// Faz a formatação da string com ftm e args e manda imprimir
	va_end(args);					// Desmonta args

	uint len = strlen(s);
	uint x; for (x=len;x<_cols;x++) s[x]=' ';
	rprintf(devicePrintf, "%s", s);
}
#endif
