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
- Libovolné Arduino – v článku je použito UNO
- LCD I2C display 16x2
- 2 ks otočný potenciometr 10KΩ
- 2 ks tlačítek
- propojovací kabely
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
Přidat komentář
Přehled komentářů
-
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 .... -
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ť ?
-
menu
(Ladislav, 6. 11. 2020 18:53:28)
Odpovědět
Dobrý den
Potřeboval bych pomoc nebo nápovědu jak rozvětvit menu. Potřebuji 1.výběr které obsahují několik položek.
Z toho potřebuji výběr 1 položky , která obsahuje zase několik položek a z toho jednu položku u které zobrazím hodnotu. Neví jak toho docílit.
Děkuji Lada -
Nefunguje
(Tom, 1. 3. 2021 9:22:36)
Odpovědět
Ač v poznámkách níže je uvedeno, že chyba ve stahování je odstraněna, mne stále stažení nefunguje.