#include <Wire.h>
#include <LiquidCrystal_I2C.h>

/*
  Display LCD 16x2 con interfaccia I2C.

  Prima di caricare questo programma:
  1. eseguire i2c_scanner.ino;
  2. leggere l'indirizzo rilevato;
  3. riportarlo in INDIRIZZO_LCD.

  Collegamenti ATmega328P / UNO R3:
  SDA -> A4
  SCL -> A5
  GND -> GND
  VCC -> alimentazione conforme al modulo
*/

constexpr uint8_t INDIRIZZO_LCD = 0x27;
constexpr uint8_t COLONNE_LCD = 16;
constexpr uint8_t RIGHE_LCD = 2;

constexpr unsigned long INTERVALLO_AGGIORNAMENTO_MS = 1000UL;
constexpr unsigned long INTERVALLO_CONTROLLO_MS = 5000UL;

LiquidCrystal_I2C lcd(
  INDIRIZZO_LCD,
  COLONNE_LCD,
  RIGHE_LCD
);

bool displayDisponibile = false;

unsigned long ultimoAggiornamento = 0;
unsigned long ultimoControllo = 0;

bool dispositivoPresente(uint8_t indirizzo)
{
  Wire.beginTransmission(indirizzo);
  uint8_t risultato = Wire.endTransmission();

  return risultato == 0;
}

void stampaIndirizzo(uint8_t indirizzo)
{
  if (indirizzo < 0x10)
  {
    Serial.print('0');
  }

  Serial.print(indirizzo, HEX);
}

void inizializzaDisplay()
{
  lcd.init();
  lcd.backlight();
  lcd.clear();

  lcd.setCursor(0, 0);
  lcd.print("LCD 16x2 I2C");

  lcd.setCursor(0, 1);
  lcd.print("Avvio completato");

  displayDisponibile = true;

  Serial.print("Display inizializzato all'indirizzo 0x");
  stampaIndirizzo(INDIRIZZO_LCD);
  Serial.println();
}

void controllaDisplay()
{
  bool presente = dispositivoPresente(INDIRIZZO_LCD);

  if (presente && !displayDisponibile)
  {
    Serial.println("Display rilevato");
    inizializzaDisplay();
  }
  else if (!presente && displayDisponibile)
  {
    displayDisponibile = false;
    Serial.println("Display non raggiungibile");
  }
}

void aggiornaDisplay(unsigned long tempoCorrente)
{
  if (!displayDisponibile)
  {
    return;
  }

  unsigned long secondi = tempoCorrente / 1000UL;

  char secondaRiga[17];

  snprintf(
    secondaRiga,
    sizeof(secondaRiga),
    "Tempo:%8lus",
    secondi
  );

  lcd.setCursor(0, 0);
  lcd.print("LCD 16x2 I2C   ");

  lcd.setCursor(0, 1);
  lcd.print(secondaRiga);
}

void setup()
{
  Serial.begin(115200);
  Wire.begin();

  Serial.println("Avvio controllo LCD I2C");

  controllaDisplay();

  if (!displayDisponibile)
  {
    Serial.print("Nessuna risposta all'indirizzo 0x");
    stampaIndirizzo(INDIRIZZO_LCD);
    Serial.println();
  }
}

void loop()
{
  unsigned long tempoCorrente = millis();

  if (
    tempoCorrente - ultimoControllo
    >= INTERVALLO_CONTROLLO_MS
  )
  {
    ultimoControllo = tempoCorrente;
    controllaDisplay();
  }

  if (
    tempoCorrente - ultimoAggiornamento
    >= INTERVALLO_AGGIORNAMENTO_MS
  )
  {
    ultimoAggiornamento = tempoCorrente;
    aggiornaDisplay(tempoCorrente);
  }
}
