OK

Při poskytování služeb nám pomáhají soubory cookie. Používáním našich služeb vyjadřujete souhlas s naším používáním souborů cookie. Více informací

Úvodní stránka » Arduino » Arduino v příkladech - V.díl - Menu na LCD

Arduino v příkladech - V.díl - Menu na LCD

Arduino bez možnosti nastavení, to není ono. Potřebujete vyměnit čidlo, strčíte ho do kapsy vezmete notebook, kabel a jdete na to. No, čas se dá strávit i příjemněji. Umíme pracovat s pamětí EEPROM, tak si pojďme napsat program, který nám umožní měnit výchozí hodnoty za běhu pouze za pomoci jednoho potenciometru a dvou tlačítek. Tedy mimo tvorby menu si ukážeme, jak ošetřit záchvěvy tlačítka a návrat do továrního nastavení.

Využijeme program z druhého dílu, ze kterého pro zjednodušení odstraníme část výpočtu nádrže, přidáme pár funkcí a součástek. Měřit tedy budeme pouze napětí a nastavovat budeme minimální a maximální hodnotu rozsahu čtení napětí. Všechno ostatní bude chybné čtení. Na displeji zobrazíme pouze % využití rozsahu.

Potřebné součástky

Zapojení:

Potenciometrem připojeným k A0 budeme simulovat čidlo a nastavovat měřené napětí, potenciometrem připojeným k A1 budeme nastavovat mezní napětí po 0,1V, tlačítkem připojeným k pinu 3 budeme provádět výběr (SELECT) a tlačítkem připojeným k pinu 2 budeme potvrzovat jednotlivé kroky (ENTER), které budeme vypisovat na displej.

Vše máme z minulých projektů.

//------------------------------------------------------------------
// inicializace zakladnich knihoven
#include <Wire.h>               // knihovna pro komunikaci I2C
#include <LiquidCrystal_I2C.h>  // knihovna pro komunikaci s diplejem
#include <EEPROM.h>             // knihovna pro zapis do pameti eeprom
//------------------------------------------------------------------
// nastaveni nazvu pinu
#define inputVoltage   A0     // vstup pro merení napeti
#define inputSettings  A1     // vstup pro nastaveni
#define ENTER           2     // vstup pro tlacitko potvrzeni
#define SELECT          3     // vstup pro tlacitko vyberu modu
// ostatní piny si popiseme, ale nechame zakomentovane
// #define SDA      A4       // komunikace I2C displej
// #define SCL      A5       // komunikace I2C displej
//------------------------------------------------------------------
// promenne ktere je nutno zadat
const int timeout = 500; // prodleva mezi ctenim v milisekundach
const int timeoutButton = 400; // prodleva mezi stisknuitim tlacitka v milisekundach
const long timeoutUpdate = 60000; // prodleva do prepnuti modu 0 v milisekundach
//------------------------------------------------------------------
// ostatni promenne
int value; // promenna pro ulozeni hodnoty cteni z analogoveho portu
int lastValue; // promenna pro ulozeni predchozi hodnoty cteni z analogoveho portu
unsigned long readingTime; // cas posledniho cteni
unsigned long readingUpdate; // cas posledniho stisku tlacitka
boolean displayUpdate = true; // nastaveni zda se ma aktualizovat displej
byte mode = 0; // nastaveni modu rozbrazeni a ceteni
int read_volume; // ctena analogova hodnota
int last_read_volume; // predchozi analogova hodnota
//------------------------------------------------------------------
//nastaveni nazvu, adresy a typu displeje
LiquidCrystal_I2C LCD(0x27, 16, 2); // nazev, adresa, pocet znaku na radku, pocet radku
//------------------------------------------------------------------
void setup() { // inicializace programu
  LCD.init(); // inicializujeme displej
  LCD.backlight(); // rozsvitime pozadi

  // nastavime pin na vstup a vyuzijeme funkce pullup abychom nemuseli ke tlacitku pripojovat rezistor
  pinMode(ENTER, INPUT_PULLUP);
  pinMode(SELECT, INPUT_PULLUP);

  // pomoci funkce si overime zda nam odpovidaji hodnoty EEPROM pro dalsi zpracovani
  // EEPROM(0) - ukladame hodnotu min. rozsahu * 10
  // EEPROM(1) - ukladame hodnotu max. rozsahu * 10
  if (EEPROM.read(0) > 50 || EEPROM.read(1) > 50) { // kdyz nejsou hodnoty v povolenem rozsahu
    EEPROM.update(0, 0); // nastav minimalni hodnotu rozsahu
    EEPROM.update(1, 50); // nastav maximalni hodnotu rozsahu
  }
}
//------------------------------------------------------------------
void loop() { // hlavni smycka programu
  //--------------------------------------------------------------------
  // podle nastaveneho modu spustime akci
  //--------------------------------------------------------------------
  if (mode == 0) { // kdyz je mod 0 zobrazeni cteni napeti
    // kazdych 200 milisekund (timeout) precteme data
    if (displayUpdate == true) { // zkontrolujeme zda je na disleji pozadovany text (true = neni)
      displayUpdate = false;
      LCD.clear(); // vymaze pripadna data
      LCD.setCursor (0, 0); // nastavime pozici kurzoru na 1 misto 1 radku
      LCD.print("Vyuziti: "); // na displeji zobrazime text
    }
    if ((unsigned long)(millis() - readingTime) >= timeout) { // pouzivame misto delay(); cekame na uplynuti timeout
      readingTime = millis(); // nastavime novy cas pro pristi zpracovani
      lastValue = value; // ulozime predchozi hodnotu cteni
      value = analogRead(inputVoltage); // precteme hodnotu analogoveho portu A0
      // zkontrolujeme zda je cteni v povolenem rozsahu
      if (value >= EEPROM.read(0) * (1024 / 50) && value <= EEPROM.read(1) * (1024 / 50)) { // pokud ano pokracujeme ve cteni
        // overime zda je cteni jine
        if (value != lastValue) { // pokud se hodnoty cteni lisi aktualizujeme displej
          // zobrazime vyuziti rozsahu cteni
          read_volume = map(value, EEPROM.read(0) * (1024 / 50), EEPROM.read(1) * (1024 / 50), 0, 100); // vypocteme procenta
          LCD.setCursor (9, 0); // nastavime pozici kurzoru na 9 misto 1 radku
          LCD.print("      "); // vymazeme zobrazeny text
          LCD.setCursor (9, 0); // nastavime pozici kurzoru na 9 misto 1 radku
          LCD.print((String)read_volume + " %"); // na displeji zobrazime hodnoti
        }
      }
      else { // jinak zobrazime chybu
        LCD.setCursor (9, 0); // nastavime pozici kurzoru na 9 misto 1 radku
        LCD.print("      "); // vymazeme zobrazeny text
        LCD.setCursor (9, 0); // nastavime pozici kurzoru na 9 misto 1 radku
        LCD.print("ERROR"); // na displeji zobrazime chybu
      }
    }
  }
  else if (mode == 1 && displayUpdate == true) { // zobrazeni minimalni nastavene hodnoty
    displayUpdate = false;
    LCD.clear(); // vycistime displej
    LCD.setCursor (0, 0); // nastavime pozici kurzoru na 1 misto 1 radku
    LCD.print("Min. hodnota:"); // zobrazime text pro zobrazeni hodnoty
    LCD.setCursor (0, 1); // nastavime pozici kurzoru na 1 misto 2 radku
    LCD.print((String) ((float)EEPROM.read(0) / 10) + "V"); // zobrazime text pro zobrazeni hodnoty
    LCD.setCursor (8, 1); // nastavime pozici kurzoru na 9 misto 2 radku
    LCD.print("<Zmenit>"); // zobrazime text pro zobrazeni hodnoty
  }
  else if (mode == 2 && displayUpdate == true) { // zobrazeni maximalni nastavene hodnoty
    displayUpdate = false;
    LCD.clear(); // vycistime displej
    LCD.setCursor (0, 0); // nastavime pozici kurzoru na 1 misto 1 radku
    LCD.print("Max. hodnota:"); // zobrazime text pro zobrazeni hodnoty
    LCD.setCursor (0, 1); // nastavime pozici kurzoru na 1 misto 2 radku
    LCD.print((String) ((float)EEPROM.read(1) / 10) + "V"); // zobrazime text pro zobrazeni hodnoty
    LCD.setCursor (8, 1); // nastavime pozici kurzoru na 9 misto 2 radku
    LCD.print("<Zmenit>"); // zobrazime text pro zobrazeni hodnoty
  }
  else if (mode == 3 && displayUpdate == true) { // zobrazeni pro ulozeni tovatniho nastaveni
    displayUpdate = false;
    LCD.clear(); // vycistime displej
    LCD.setCursor (0, 0); // nastavime pozici kurzoru na 1 misto 1 radku
    LCD.print("Tovarni nastav."); // zobrazime text pro zobrazeni hodnoty
    LCD.setCursor (8, 1); // nastavime pozici kurzoru na 9 misto 2 radku
    LCD.print("<Ulozit>"); // zobrazime text pro zobrazeni hodnoty
  }
  else if (mode == 11) { // zobrazeni nastaveni min hodnoty
    if (displayUpdate) {
      displayUpdate = false;
      LCD.clear(); // vycistime displej
      LCD.setCursor (0, 0); // nastavime pozici kurzoru na 1 misto 1 radku
      LCD.print("Nastav Min."); // zobrazime text pro zobrazeni hodnoty
      LCD.setCursor (8, 1); // nastavime pozici kurzoru na 9 misto 2 radku
      LCD.print("<Ulozit>"); // zobrazime text pro zobrazeni hodnoty
      // tyto dva radky zajisti zobrazeni hodnot pri opakovanem stisknuti vyber
      LCD.setCursor (0, 1); // nastavime pozici kurzoru na 1 misto 2 radku
      LCD.print((String)((float)read_volume / 10) + "V"); // zobrazime text pro zobrazeni hodnoty
    }
    lastValue = value; // ulozime predchozi hodnotu cteni
    value = analogRead(inputSettings); // precteme hodnotu analogoveho portu A1
    last_read_volume = map(lastValue, 0, 1023, 0, 51); // vypocteme rozsah napeti minuleto cteni
    read_volume = map(value, 0, 1023, 0, 51); // vypocteme rozsah napeti noveho cteni
    if (last_read_volume != read_volume) {
      LCD.setCursor (0, 1); // nastavime pozici kurzoru na 1 misto 2 radku
      LCD.print((String)((float)read_volume / 10) + "V"); // zobrazime text pro zobrazeni hodnoty
    }
  }
  else if (mode == 12) { // zobrazeni nastaveni max hodnoty
    if (displayUpdate) {
      displayUpdate = false;
      LCD.clear(); // vycistime displej
      LCD.setCursor (0, 0); // nastavime pozici kurzoru na 1 misto 1 radku
      LCD.print("Nastav Max."); // zobrazime text pro zobrazeni hodnoty
      LCD.setCursor (8, 1); // nastavime pozici kurzoru na 9 misto 2 radku
      LCD.print("<Ulozit>"); // zobrazime text pro zobrazeni hodnoty
      // tyto dva radky zajisti zobrazeni hodnot pri opakovanem stisknuti vyber
      LCD.setCursor (0, 1); // nastavime pozici kurzoru na 1 misto 2 radku
      LCD.print((String)((float)read_volume / 10) + "V"); // zobrazime text pro zobrazeni hodnoty
    }
    lastValue = value; // ulozime predchozi hodnotu cteni
    value = analogRead(inputSettings); // precteme hodnotu analogoveho portu A1
    last_read_volume = map(lastValue, 0, 1023, 0, 51); // vypocteme rozsah napeti minuleto cteni
    read_volume = map(value, 0, 1023, 0, 51); // vypocteme rozsah napeti noveho cteni
    if (last_read_volume != read_volume) {
      LCD.setCursor (0, 1); // nastavime pozici kurzoru na 1 misto 2 radku
      LCD.print((String)((float)read_volume / 10) + "V"); // zobrazime text pro zobrazeni hodnoty
    }
  }
  //--------------------------------------------------------------------
  // zkontrolujeme tlacitko vyber modu
  //--------------------------------------------------------------------
  if (!digitalRead(SELECT)) { // kdyz je stisknuto tlacitko vyber
    if ((unsigned long)(millis() - readingTime) >= timeoutButton) { // kdyz uplynula doba blokovani tlacitka
      readingTime = millis(); // nastavime novy cas pro povoleni dalsiho cteni
      readingUpdate = millis(); // nastav novou prodlevu pro kontrolu necinnosti
      displayUpdate = true; // povol aktualizaci displeje
      if (mode == 0) mode = 1;
      else if (mode == 1) mode = 2;
      else if (mode == 2) mode = 3;
      else if (mode == 3) mode = 0;
      else if (mode == 11) mode = 1;
      else if (mode == 12) mode = 2;
    }
  }
  //--------------------------------------------------------------------
  // pracujeme s tlacitkem potvrzeni
  //--------------------------------------------------------------------
  if (mode != 0) { // kdyz neni mod 0 kontroluj tlacitko enter a cas nastaveni
    if ((unsigned long)(millis() - readingUpdate) >= timeoutUpdate) { // kdyz uplynul cas necinnosti vrat se na mode 0
      displayUpdate = true; // povol aktualizaci displeje
      mode = 0;
    }
    if (!digitalRead(ENTER)) { // kdyz je stisknuto tlacitko vyber
      if ((unsigned long)(millis() - readingTime) >= timeoutButton) { // kdyz uplynula doba blokovani tlacitka
        readingTime = millis(); // nastavime novy cas pro povoleni dalsiho cteni
        readingUpdate = millis(); // nastav novou prodlevu pro kontrolu necinnosti
        displayUpdate = true; // povol aktualizaci displeje
        if (mode == 1) mode = 11;
        else if (mode == 2) mode = 12;
        else if (mode == 3) {
          mode = 0;
          EEPROM.update(0, 0); // minimalni hodnota rozsahu
          EEPROM.update(1, 50); // maximalni hodnota rozsahu

        }
        else if (mode == 11) {
          mode = 1;
          // kdyz je min hodnota mensi nez max
          if (read_volume < EEPROM.read(1)) EEPROM.update(0, read_volume); // uloz minimalni hodnotu rozsahu
          else { // jinak zobraz chybu
            LCD.clear(); // vycistime displej
            LCD.setCursor (0, 0); // nastavime pozici kurzoru na 1 misto 1 radku
            LCD.print("Chyba rozsahu"); // zobrazime text pro zobrazeni hodnoty
            delay(2000);
          }
        }
        else if (mode == 12) {
          mode = 2;
          // kdyz je max hodnota vetsi nez min
          if (read_volume > EEPROM.read(0)) EEPROM.update(1, read_volume); // uloz maximalni hodnotu rozsahu
          else { // jinak zobraz chybu
            LCD.clear(); // vycistime displej
            LCD.setCursor (0, 0); // nastavime pozici kurzoru na 1 misto 1 radku
            LCD.print("Chyba rozsahu"); // zobrazime text pro zobrazeni hodnoty
            delay(2000);
          }
        }
      }
    }
  }
}

Program, je napsán hodně přímočaře. Každá funkce krok po kroku opakuje příkazy, tak jak by měly být a pro zjednodušení, jsem ani nijak neřešil práci s pamětí.

Co nám program dělá:

Na začátku se podívá (setup), jestli jsou data v EEPROM v povoleném rozsahu a pokud ne, nastaví nám EEPROM. Ušetříme si tak práci s postupem – nahraj a nastav, nahraj a spusť.

Pak začne provádět kontrolu 3 parametrů, podle kterých spouští příslušné části programu.

1) Parametr mode určuje, co budeme dělat:

  • mode = 0, pracuje na tom k čemu je určeno, měří, zobrazuje, spíná, odesílá, atd
  • mode = X, cokoliv jiného než 0 znamená že začne nastavovat parametry. Zastaví se tak běh části mode = 0, protože nastavovat za běhu může přinést neočekávané výsledky.

2) Pak neustále kontrolujeme zda není stisknuto tlačítko select. Pokud ano, přepne nám mezi jednotlivými módy.

3) A na konci kontrolujeme zda jsme v módu nastavení a pak se kontroluje tlačítko potvrzení výběru.

Do programu jsou ještě přidány tři funkce:

  • jedna, která kontroluje zda nastavujeme a pokud ne, tak po jedné minutě nečinnosti nám ukončí mód nastavení a vrátí se ke standardnímu běhu. Doporučuji používat.
  • druhá, kterou najdete pod vznešeným názvem debounce, zajišťuje, aby když zmáčknete tlačítko, nespínalo jako zběsilé, ale provedlo co má a pak po dobu 400 milis bude nedostupné. Tím ošetříte záchvěvy tlačítka a současně zajistíte opakování funkce, když ho podržíte stisknuté. Toto řešení prostě funguje, je jednoduché a dá se využít i pro funkci přerušení INTERRUPT. Bez ošetření záchvěvů tlačítka se stane nastavování utrpením.
  • třetí kontroluje zda je minimální nastavovaná hodnota menší než maximální nastavovaná hodnota a naopak. Není na škodu, když jsou programy alespoň trochu blbuvzdorné.

Co je klíčové a proč si to celé ukazujeme, je práce s menu a nastavením.

Menu nám umožňuje nastavovat parametry, aniž bychom museli připojovat počítač a opět programovat. Většinu projektů co má displej takto koncipuji. Vzpomeňte na to až začnete napouštět bazén s ochranou proti zaplavení, viz minulé články. Až se dostaneme ke grafickým displejům, tam tyto dovednosti využijete.

Když připravuji menu, začnu diagramem závislostí procesu (pro znalé ghantův diagram). Když se dostanu do třetí úrovně menu, tak se bez toho ztratím.

Diagram menu pro náš projekt:

Stačí nakreslit na papír, funguje to stejně dobře. Diagram nám ukazuje postup spínání tlačítek a dá se podle toho napsat funkční menu na první dobrou i pro toho, kdo to dělá poprvé. Stisknu tlačítko select a když je mód 0, přepínám do módu 1 atd. Když je mód 1 udělej toto, atd. Teď už je to jednoduché a to je celé kouzlo tvorby menu.

Jenom pro zobrazení dvou způsobů jak pracovat s tlačítky v menu. Napsal jsme jinak select a enter. Select nám nastavuje pouze číslo módu a program pak podle dotazu provede příslušnou akci – k tomu potřebujeme tu značku. Enter nám podle módu přímo provádí konkrétní funkce a nečeká na žádné dotazy. Podle této logiky se dá samozřejmě naprogramovat i tlačítko select.

Nastavení se provádí tak, že se uloží hodnoty do paměti EEPROM. Pracujeme s desetinným číslem, které se do EEPROM ukládá složitě, tak ukládáme celá čísla, která si pro zobrazení vydělíme deseti.

A to je vše. Jeden potenciometr a dvě tlačítka a ono to funguje.

Na závěr si ukážeme ten samý program, jak bych ho napsal pro sebe.

Vypadá složitě a delší. Ale stáhněte si ho Arduino Mode. Najednou je mnohem přehlednější. Funkce loop je krátká a přehledná, ostatní pěkně v záložkách, neztrácíme se v závorkách, když potřebujeme něco upravit uděláme to jenom jednou, prakticky nic se neopakuje. No a k dobru jsme ušetřili 20 byte paměti Flash a 108 byte paměti SRAM. Těch 20 byte Flash vypadá směšně, ale když vezmete v úvahu kolik zabere inicializace makra "F", zas tak špatné to není.

K makru "F". Někomu může přijít divné že funguje i mimo serial, ale jedná se o třídu print a dobře napsanou knihovnu LiquidCrystal_I2C. Tedy funguje.

Příště se podíváme jak připojit Arduino k perifériím, jak s nimi komunikovat, na co si dát pozor a hlavně na konverzi úrovní.

JB

jaroslav.bohac@arduinotech.cz

Přidat komentář

Zvýrazněné položky jsou povinné.

Přehled komentářů

  1. Nefunkční odkaz (Šimoník, 25.4.2016 14:12:14) Odpovědět

    Odkaz na stažení mi nefunguje.

    PF:
    Omlouvám se, napraveno ....

  2. funkcia display update (Roman, 10.2.2017 20:36:49) Odpovědět

    chcel som použiť funkciu display update v mojom projekte , ale mi v ňom bezí funkcia času cez millis a ked pouzijem funkciu display update tak mi čas zamrzne, vedeli by ste mi s tým pomôcť ?


TOP produkty

Arduinotech GSM shield

Arduinotech GSM shield
877 Kč s DPH

NodeMCU s ESP8266

NodeMCU s ESP8266
350 Kč s DPH

Kontakt

Ing. Petr Foltýn
Kunčice pod Ondřejníkem 814, 73913
TOPlist