//----------------------------------------------
//Firmware pro přenosný číslicově řízený stabilizovaný zdroj symetrického napětí, který lze ovládat z PC pomocí vytvořené utility v LabVIEW
//Diplomová práce 2020
//Vytvořil David Hynek
//----------------------------------------------

//******************moje proměnné

//nastavení pinů pro regulaci napětí
//        Kladná větev, Záporná větev
int pins_reg[] = {10, 9};

//nastavení pinů pro snímání napětí akumulátoru a nabíjení
int pinAKU = A2;
int pinNAB = A3;

//nastavení pinu pro zapnutí zdroje
int pinZAP = 6;

//nastavení pinů pro enkodér
int pinCLK = 3;
int pinDT = 4;
int pinSW = 5;

//promněnné pro funkci obsluhující enkodér
int pozice = 0;
int pozice_last = 0;
int pozice_last1 = 0;
bool otoceni;
int stav_last;
int stavCLK;
int stavSW;

//promněnné používané při ovládání displeje
int hodnota1;
int volty1 = 3;
char text1[16];
char text2[16];
int hodnota2;
int volty2 = 3;
int zmena = 0;
int n = 0;
bool vystup;

//promněnné určené k ovládání chodu programu
int program = 1;  //promněnná pro volbu programu
int stav_tlac = 0;  //aktuální stav tlačítka
int last_stav_tlac; //předchozí stav tlačítka
int cas_obnovy1 = 200; //čas překrelení displeje 2 řádek
int cas_obnovy2 = 1000; //čas překrelení displeje 1 řádek
long znacka1 = 0; //časová značka 1
long znacka2 = 0; //časová značka 1

//pole s hodnoty kroku nastavovaných napětí
int napeti[] = {3, 6, 9, 12, 15, 18, 21, 24, 30, 36, 42, 48, 54, 57, 60, 63, 66}; //krok výstupního napětí
int dec_klad[] = {209, 222, 233, 227, 235, 236, 236, 232, 228, 210, 217, 213, 220, 222, 222, 228, 255}; //hodnoty střídy PWM pro kladnou větev
int dec_zap[] = {151, 184, 201, 198, 211, 214, 216, 217, 209, 203, 207, 210, 215, 221, 236, 238, 255};  //hodnoty střídy PWM pro zápornou větev

//pole se symbolem baterie pro displej
byte akuChar[] = {
  0x04,
  0x1F,
  0x11,
  0x11,
  0x11,
  0x1F,
  0x1F,
  0x1F
};

//******************moje proměnné

//******************knihovny

#include <Wire.h> //pro komunikaci přes I2C
#include <LiquidCrystal_I2C.h>  //pro ovládání LCD displeje přes I2C
LiquidCrystal_I2C lcd(0x27, 16, 2);  // nastavení displeje, adresa 0x27, 16 řádků, 2 sloupce

//******************knihovny

//******************inicializace

void setup()
{
  for (int i = 0; i < 2; i++)
    pinMode(pins_reg[i], OUTPUT);   // nastavení pinů řádků na výstup

  //inicializace enkodéru
  pinMode(pinSW, INPUT_PULLUP); //nastavení pinu SW tlačítka enkodéru na vstup s pull-up rezistorem
  pinMode(pinCLK, INPUT); //nastavení pinu CLK enkodéru
  pinMode(pinDT, INPUT);  //nastavení pinu DT enkodéru
  stav_last = digitalRead(pinCLK);  //načtení prvního stavu enkodéru (kvůi porovnání)

  //nastvení pinů pro měření napětí akumulátoru a nabíječky
  pinMode(pinAKU, INPUT);
  pinMode(pinNAB, INPUT);

  //nastavení pinu pro zapnutí měničů
  pinMode(pinZAP, OUTPUT);
  digitalWrite(pinZAP, 0);


  lcd.init(); //inicializace displeje
  lcd.backlight();  //zapnutí podsvětlení
  lcd.createChar(0, akuChar); //uložení vlastního symbolu baterie do paměti displeje

  Serial.begin(9600); //spuštění sériové linky
}

//******************inicializace

//******************funkce pro obsluhu enkodéru

void enkoder()
{
  stavCLK = digitalRead(pinCLK); //načtení aktuálního stavu pinu CLK
  if (stavCLK != stav_last) //pokud platí, tak došlo k otočení enkodéru
  {
    otoceni = 1;
    if (digitalRead(pinDT) != stavCLK) //pokud platí, tak byl pin CLK změněn jako první a směr otáčení je doprava
    {
      pozice = pozice + 1;
      if (pozice > 16) //nulování promněnné pozice
        pozice = 0;
    }
    else  //pokud neplatí tak byl pin DT změněn jako první a směr otáčení je doleva
    {
      if (pozice < 1)
        pozice = 16;
      else
        pozice = pozice - 1;
    }
  }

  stav_last = stavCLK;  //zapamatování posledního stavu pinu CLK

  stav_tlac = digitalRead(pinSW);  //uložení stavu tlačítka
  if (stav_tlac != last_stav_tlac)  //nastane pokud se změní stav tlačítka
  {
    if (stav_tlac == 0) //nastane při stisku tlačítka, při log. 0
    {
      program++;  //další efekt
      zmena = 1;
      if (program == 5) //znovu první program
        program = 1;
    }
    last_stav_tlac = stav_tlac; //uložení aktuálního stavu tlačítka pro příští kontrolu
  }
}

//******************funkce pro obsluhu enkodéru


//******************funkce pro změření napětí akumulátoru

float napeti_aku()
{
  float hodnota = analogRead(pinAKU); //načtení napětí pinu připojného přes dělič k akumulátoru
  hodnota = hodnota * (5.0 / 1024.0); //převod na skutečnou velikost napětí
  hodnota = hodnota * 4.0296; //převod na skutečnou velikost napětí
  return hodnota;
}

//******************funkce pro změření napětí akumulátoru

//******************funkce pro vykreslení prvního řádku displeje, který zobrazuje napětí a nabití akumulátoru

void displej_radek1()
{
  float hodnota = napeti_aku(); //zavolání funkce pro změření napětí akumulátoru
  lcd.setCursor(0, 0);  //zápis znaků na displej
  lcd.write(0);
  lcd.setCursor(1, 0);
  lcd.print(hodnota, 1);
  lcd.setCursor(5, 0);
  lcd.print("V");
  lcd.setCursor(6, 0);
  lcd.print(char(58));
  lcd.setCursor(12, 0);
  lcd.print(char(58));
  for (int a = 0; a < 5; a++)
  {
    lcd.setCursor((7 + a), 0);
    lcd.print(char(32));
  }
  int mez;
  if (hodnota > 12.8) //meze pro grafické znázornění nabití akumulátoru
    mez = 5;
  else if (hodnota > 12.6)
    mez = 4;
  else if (hodnota > 12.4)
    mez = 3;
  else if (hodnota > 12.2)
    mez = 2;
  else if (hodnota > 12.0)
    mez = 1;
  else
    mez = 0;
  for (int n = 0; n < mez; n++)
  {
    lcd.setCursor((7 + n), 0);  //zobrazení grafického znázornění nabití akumulátoru na displeji
    lcd.print(char(255)); //symbol plného obdelníku
  }
}

//******************funkce pro vykreslení prvního řádku displeje, který zobrazuje napětí a nabití akumulátoru


//******************funkce pro vykreslení druhého řádku displeje, který zobrazuje stav a napětí na výstupu, nastavení střídy PWM podle nastaveného napětí

void displej_radek2()
{
  if (program == 1) //zapnutí nebo vypnutí výstupu
  {
    if (pozice != pozice_last)
      vystup = !vystup;
    digitalWrite(pinZAP, vystup);

  }
  if (program == 2) //nastavení symetrického napětí
  {
    pozice_last1 = pozice;
    volty1 = napeti[pozice];
    volty2 = napeti[pozice];
    analogWrite(10, dec_klad[pozice]);
    analogWrite(9, dec_zap[pozice]);
  }
  if (program == 3) //nastavení kladné větve zdroje
  {
    volty1 = napeti[pozice];
    analogWrite(10, dec_klad[pozice]);
  }
  if (program == 4) //nastavení záporné větve zdroje
  {
    if (zmena == 1)
      pozice = pozice_last1;
    volty2 = napeti[pozice];
    analogWrite(9, dec_zap[pozice]);
  }
  zmena = 0;


  if (vystup == 0)  //připravení zápisu na displej pomocí funkce sprintf
    sprintf(text1, " VYP   +%2dV -%2dV", volty1, volty2);
  if (vystup == 1)
    sprintf(text1, " ZAP   +%2dV -%2dV", volty1, volty2);

  n++;
  if (n > 4)  //část pro vytváření blikání vybranných znaků při aktivní funkci
  {
    if (n == 7)
      n = 0;
    if (program == 1)
    {
      text1[1] = 32;
      text1[2] = 32;
      text1[3] = 32;

    }
    if (program == 2)
    {
      text1[10] = 32;
      text1[15] = 32;

    }
    if (program == 3)
    {
      text1[10] = 32;

    }
    if (program == 4)
    {
      text1[15] = 32;

    }
  }
  pozice_last = pozice;
  lcd.setCursor(0, 1);
  lcd.print(text1); //zobrazení připravených dat na displeji
}

//******************funkce pro vykreslení druhého řádku displeje, který zobrazuje stav a napětí na výstupu, nastavení střídy PWM podle nastaveného napětí

//******************funkce pro vykreslení druhého řádku displeje při připojeném PC, který zobrazuje stav a napětí na výstupu, nastavení střídy PWM podle PC

void displej_radek2_pc(char prikaz)
{
  if (prikaz == "99") //zapnutí výstupu
  {
    vystup = 0;
    digitalWrite(pinZAP, vystup);
  }
  if (prikaz == "100")  //vypnutí výstupu
  {
    vystup = 1;
    digitalWrite(pinZAP, vystup); //nastavení napětí odeslaného z programu v LabVIEW z PC
  }
  if (prikaz >= "0" and prikaz <= "15")
  {
    pozice = prikaz;
    volty1 = napeti[pozice];
    volty2 = napeti[pozice];
    analogWrite(10, dec_klad[pozice]);
    analogWrite(9, dec_zap[pozice]);
  }
  if (vystup == 0)
    sprintf(text1, " VYP   +%2dV -%2dV", volty1, volty2);
  if (vystup == 1)
    sprintf(text1, " ZAP   +%2dV -%2dV", volty1, volty2);
  lcd.setCursor(0, 1);
  lcd.print(text1);
}

//******************funkce pro vykreslení druhého řádku displeje při připojeném PC, který zobrazuje stav a napětí na výstupu, nastavení střídy PWM podle PC

//******************funkce main

void loop()
{
  if (analogRead(pinNAB) > 1000)  //platí při připojení nabíječky
  {
    digitalWrite(pinZAP, 0);  //nucené vypnutí zdroje
    if (millis() > (cas_obnovy1 + znacka1))  //pokud proběhl nastavený čas, tak obnov displej
    {
      lcd.setCursor(0, 1);
      lcd.print("   !NABIJENI!   ");
      znacka1 = millis(); //uložení aktuálního času
    }
    if (millis() > (cas_obnovy2 + znacka2))  //pokud proběhl nastavený čás, tak obnov displej
    {
      displej_radek1();
      znacka2 = millis(); //uložení aktuálního času
    }
  }
  else if (Serial.available() > 0)  //nastane při připojení k PC
  {
    if (millis() > (cas_obnovy2 + znacka2))  //pokud proběhl nastavený čas, tak obnov displej
    {
      displej_radek1();
      lcd.setCursor(14, 0);
      lcd.print("PC");
      znacka2 = millis(); //uložení aktuálního času
    }
    if (millis() > (cas_obnovy1 + znacka1))  //pokud proběhl nastavený čas, tak obnov displej
    {
      char prijata_data = Serial.read();
      displej_radek2_pc(prijata_data);
      znacka1 = millis(); //uložení aktuálního času
    }

  }
  else  //normální režim zdroje
  {
    enkoder();
    if (millis() > (cas_obnovy1 + znacka1))  //pokud proběhl nastavený čas, tak obnov displej
    {
      displej_radek2();
      znacka1 = millis(); //uložení aktuálního času
    }
    if (millis() > (cas_obnovy2 + znacka2))  //pokud proběhl nastavený čas, tak obnov displej
    {
      displej_radek1();
      znacka2 = millis(); //uložení aktuálního času
    }
  }
}

//******************funkce main
