#include <Arduino.h>
/*

                 *****************************************
                 **********   LCD 2x16 (8 bit)  **********
                 *****************************************

  En la década de los '80, del pasado siglo, Hitachi desarrolló el controlador
de pantalla LCD 'HD44780', contaba con el juego de caracteres ASCII, además
de japoneses y símbolos, en total puede mostrar hasta 80 caracteres, se hizo 
tan popular, que numerosas pantallas de terceros fabricantes utilizan su interfaz,
así como su juego de instrucciones por compatibilidad, convirtiéndose de facto
en un estándar sin pretender serlo, y ya va para 42 años.

                        https://www.sparkfun.com/datasheets/LCD/HD44780.pdf

  Volviendo al LCD, posee 16 líneas:

  - VSS         GND
  - VCC         +5V
  - Vo          Ajuste del contraste Nota(2)
  - RS          Selección de registo
  - R/W         Lectura /Escritura
  - EN          Habilitación
  - D0 a D7     Datos 8 bit
  - A           Ánodo Led retroiluminación
  - K           Cátodo

  VRG (10/05/2022)
*/

// Declaración de variables y ctes

// #define lcd_Linea_Uno 0x00           // ver Nota (1)
// #define lcd_Linea_Dos 0x40 
const uint8_t lcd_Linea_Uno = 0x00;     // Direcciones DDRAM comienzo de la línea 1
const uint8_t lcd_Linea_Dos = 0x40;     // y de la línea 2

// Interface paralelo 8 bit del LCD

const uint8_t DB[8] = {2, 3, 4, 5, 6, 7, 8, 9};   // Bus datos DB[7...0]      
const uint8_t PE =  10;                           // Enable
const uint8_t RS =  11;                           // Registro de Datos/Instrucciones

// Instrucciones del LCD

const uint8_t lcd_Clear =           0b00000001;   // pone en todos los caracteres el ASCII 'espacio'
const uint8_t lcd_Home =            0b00000010;   // regresa el cursor a la primera posición de la primera línea
const uint8_t lcd_EntryMode =       0b00000110;   // desplaza el cursor de izquierda a derecha en lectura/escritura
const uint8_t lcd_DisplayOff =      0b00001000;   // apaga la pantalla
const uint8_t lcd_DisplayOn =       0b00001100;   // pantalla activada, cursor desactivado, carácter sin parpadeo
const uint8_t lcd_FunctionReset =   0b00110000;   // reinicia la pantalla
const uint8_t lcd_FunctionSet8bit = 0b00111000;   // interface de 8 bits, pantalla de 2 líneas, fuente de 5x7 (5x8)
const uint8_t lcd_SetCursor =       0b10000000;   // establecer la posición del cursor

char mensaje_1[] = "VICENTE";                     // Mensaje inicial para depuración
char mensaje_2[] = "Rey Garcia";

//  Prototipo de Funciones

void lcd_init_8b();                               // inicializar el LCD para un interface de 8 bits 
void lcd_escribir_texto(char[]);                  // escribir una cadena
void lcd_escribir_caracter(uint8_t);              // escribir un carácter
void lcd_escribir(uint8_t);
void lcd_instruccion(uint8_t);

//  Inicialización

void setup() {
 for (int i = 0; i <= 7; i++)                     // Interface Bus de Datos 8 bits
  pinMode(DB[i], OUTPUT);
 
  pinMode(PE, OUTPUT);                            // Interface Bus de Control
  pinMode(RS, OUTPUT);                            // PE enable, RS

 lcd_init_8b();                                   // inicializar el LCD
 lcd_escribir_texto(mensaje_1);
 lcd_instruccion(lcd_SetCursor | lcd_Linea_Dos);  // saltar a la segunda línea
  delayMicroseconds(80);                          // minimo 40 uS retardo
 lcd_escribir_texto(mensaje_2);
}

//  ------------ Programa Principal ----------
void loop() {

}

//          ------------ FIN --------------


void lcd_init_8b(){

    delay(100);                               // retardo inicial (mayor de 40 ms)

    lcd_instruccion(lcd_FunctionReset);       // Primera parte de la secuencia de inicialización
    delay(10);                                // 4,1ms min.
    lcd_instruccion(lcd_FunctionReset);    
    delayMicroseconds(200);                   // 100µS min.
    lcd_instruccion(lcd_FunctionReset);   
    delayMicroseconds(200);                   // 100µS min.
    lcd_instruccion(lcd_FunctionSet8bit);     // establercer modo, líneas y fuente
    delayMicroseconds(80);                    // 40uS min
    lcd_instruccion(lcd_DisplayOff);          // poner display OFF
    delayMicroseconds(80);                    // 40µs min
    lcd_instruccion(lcd_Clear);               // Borrar RAM de pantalla
    delay(4);                                 // 1,64ms min
    lcd_instruccion(lcd_EntryMode);           // establecer las características
    delayMicroseconds(80);                    // 40µS min
    lcd_instruccion(lcd_DisplayOn);           // Enciende la pantalla
    delayMicroseconds(80);                    // 40µs min
}

//-------------------------------------------------------------------------------------------
void lcd_escribir_texto(char texto[]) {
  int i = 0;  

    while (texto[i] != '\0') {                 // recoger caracteres hasta la marca final '\0'
            lcd_escribir_caracter(texto[i]);
        i++;
        delayMicroseconds(80);
    }
}

//-------------------------------------------------------------------------------------------
void lcd_escribir_caracter(uint8_t  lcd_dato) {
    digitalWrite(RS, HIGH);                       // seleccionar Registro de Datos (RS = 1)
    digitalWrite(PE, LOW);                        // inicialmente Enable = 0
    lcd_escribir(lcd_dato);                       // escribir datos
}

//-------------------------------------------------------------------------------------------
void lcd_instruccion(uint8_t lcd_instruccion) {
    digitalWrite(RS, LOW);                        // seleccionar Registro de Instrucción (RS = 0)
    digitalWrite(PE, LOW);                        // inicialmente Enable = 0
    lcd_escribir(lcd_instruccion);                // escribir instrucción
}

//-------------------------------------------------------------------------------------------
void lcd_escribir(uint8_t  lcd_bye) {

for (int i = 0; i <= 7; i++){ 
      if (lcd_bye & 1 << i)
              digitalWrite(DB[i], HIGH);          // poner a '1' 
        else
              digitalWrite(DB[i], LOW);           // o a '0'
 }
                                                  // tiempo de establecimiento de la dirección (40 nS)
    digitalWrite(PE, HIGH);                       // Preparar permiso de escritura por flanco descendente.
            delayMicroseconds(1);                 // tiempo de establecimiento de los datos (80 nS) y anchura de PE (230 nS)
    digitalWrite(PE, LOW);                        // completar impulso de escritura
            delayMicroseconds(1);                 // tiempo de retención de los datos (10 nS) y tiempo del ciclo de PE (500 nS)
}

/*
Direccionamiento de la DDRAM:

  Indicamos al controlador dónde queremos que se almacene el primer carácter ASCII que se le envía, este
actualizará automáticamente su puntero de dirección y colocará el siguiente carácter que se envíe
en la dirección de memoria adyacente (se puede especificar si deseamos aumentar o disminuir el contador de direcciones),
normalmente se incrementa, por lo que el siguiente carácter se colocará en la siguiente dirección automáticamente.
Después de almacenar un código ASCII en la dirección 0x27, coloca el siguiente código en la dirección 0x40.
De manera similar, se incrementa desde la dirección 0x67 hasta las 0x00.

Estos son los 80 bytes de DDRAM en el controlador HD44780

LCD 40x2:

        línea 1  -->   0x00, 0x01, 0x02, ...     ...0x27
        línea 2  -->   0x40, 0x41, 0x42, ...     ...0x67

LCD 20x4:
        línea 1  -->   0x00, 0x01, 0x02, ...     ...0x13
        línea 2  -->   0x40, 0x41, 0x42, ...     ...0x53
        línea 1  -->   0x14, 0x15, 0x16, ...     ...0x27
        línea 2  -->   0x54, 0x55, 0x56, ...     ...0x67

LCD 20x2:

        línea 1  -->   0x00, 0x01, 0x02, ...     ...0x13
        línea 2  -->   0x40, 0x41, 0x42, ...     ...0x53

LCD 16x4:

        línea 1  -->   0x00, 0x01, 0x02, ...     ...0x0F
        línea 2  -->   0x40, 0x41, 0x42, ...     ...0x4F
        línea 3  -->   0x10, 0x01, 0x02, ...     ...0x1F
        línea 4  -->   0x50, 0x41, 0x42, ...     ...0x5F               

LCD 16x2:

        línea 1  -->   0x00, 0x01, 0x02, ...     ...0x0F
        línea 2  -->   0x40, 0x41, 0x42, ...     ...0x4F

LCD 16x1:

        línea 1  -->   0x00, 0x01, 0x02, ...     ...0x07
        línea 2  -->   0x40, 0x41, 0x42, ...     ...0x47        

LCD 40x4:

  La pantalla LCD de 40 x 4 se trata esencialmente como dos dispositivos de 40 x 2.
Utiliza dos chips de controlador HD44780, por lo tanto, tiene dos mapas de memoria separados,
cada uno con el mismo rango de direcciones. Se accede a las memorias de forma individual mediante un pin  de selección,

        línea 1  -->   0x00, 0x01, 0x02, ...     ...0x27
        línea 2  -->   0x40, 0x41, 0x42, ...     ...0x67
        línea 3  -->   0x00, 0x01, 0x02, ...     ...0x27
        línea 4  -->   0x40, 0x41, 0x42, ...     ...0x67

*/
/*
Nota (1)

 Casi siempre se piensa que el uso de la directiva '#define' favorece el rendimiento del compilado
evitando la declaración de una variable se ahorra tiempo y espacio, y es verdad hasta cierto punto,
pero, en cualquier nivel de optimización del compilador no habrá una diferencia relevante,
ya que los valores constantes 'cosnt' se sustituyen ya en el mismo instante de la compilación.
La gran ventaja de usar 'const' es la de verificar tipos y hacer que el código sea conocido por el depurador,
por lo que realmente no hay una razón de peso para no usar variables 'const'.
*/