Kde mám chybu - switch

Wiring, C++, C, Java, ...
Pravidla fóra
Toto subfórum slouží k řešení obecných otázek kolem programování (konstrukce, knihovny, alokace paměti, ...)
tosik
Příspěvky: 90
Registrován: 11 led 2020, 14:48
Reputation: 0

Kde mám chybu - switch

Příspěvek od tosik » 11 led 2020, 15:06

Zdravím.

začínám s Arduinem a teď zkouším příkaz Switch Case. Snažím se ze sériového monitoru načíst zadanou hodnotu a podle zadání spustit příslušnou větev CASE. Tam pak bude jednoduchý program na blikání LED. Když zadám 0 nebo 1, tak se mi vytiskne správná CASE větev, ale když dám 2, tak se mi na sériovém monitoru vytiskne větev s CASE pro jedničku (1). Co mám špatně? Řekl bych, že špatně načítám číslo, teď mám v IDE volbu Chybný konec řádky - zadám 1, odešlu a vytiskne se mi jedenkrát hláška, že jsem zadal 1. Ale pokud dám v IDE volbu Nová řádka, vytiskne se nejprve první hláška s číslem 5 a poté druhá hláška s číslem 0. Kód uvádím níže. Vím, že by to šlo i bez toho SWITCH a funkcí, to už mám a jede to, ale chtěl jsem se na tom naučit pracovat s funkcema...

V první záložce mám této kód:

int led = 13; // pin LED, která bude blikat
int stav; // zde uloží přijatou hodnotu ze sériové linky
int pocetBliknuti; // zpracovaná hodnota určující, kolikrát se bude blikat
int poc; // pomocná proměnná, zde cyklus bude ukládat počet opakování
int maxBlik = 5; // omezení počtu blikání
int kontrola; // 0 - vypnuto, 1 - zadání OK, 2 - chybné zadání ze SL, 3 - stav, kdy se fce nebude vykonávat
//====================================================================

void setup() {
pinMode(led, OUTPUT); // nastavení výstupu
Serial.begin(9600); // zahájení komunikace
Serial.print("Zadej počet blikání. Můžeš zadat pouze celé kladné číslo větší v rozsahu 1 až ");
Serial.print(maxBlik);
Serial.println(". ");
Serial.println("Nula znamená, že se nebude blikat.");
Serial.println("============================================================================");
}

//===================================================================

void loop() {
kontrola = 3;

cteniSerialLinky();

switch (kontrola){
case 0:
Serial.println("Spustí se: První case");
kontrola=3;
break;
case 1:
Serial.println("Spustí se: Druhé case");
kontrola=3;
break;
case 2:
Serial.println("Spustí se: Třetí case");
kontrola=3;
break;
}
}


Funkce mám definované na druhé záložce:

void cteniSerialLinky() {
if (Serial.available() > 0) { // pokud se něco objeví na sériové lince
int stav = Serial.parseInt(); //načte to a udělá z toho číslo
Serial.println(" ");
Serial.print("Přijato : ");
Serial.println(stav);

//--------Vyhodnocení správnosti zadání na sériovou linku -------------
if (0 < stav && stav <= maxBlik){ //pokud je zadání větší než nula, tolikrát se bude blikat
kontrola = 1; // zadání je v pořádku
pocetBliknuti=stav;
Serial.print("==Budu ");
Serial.print(pocetBliknuti);
Serial.println("-krát blikat==");

} else if (stav == 0) { // jesli je zadána nula, pak tiskne NEBLIKÁM, stejně jako zadání písmena místo čísla
kontrola = 0; // vypnutí blikání
Serial.println("== Nebude se blikat ==");

} else { // zadání záporného čísla, by měl vyhodnotit oznámením
kontrola = 2; // zadání není v pořádku
Serial.println("== Chybné zadání ==");

}
}
return kontrola;
}



Poradí mi prosím někdo, co mám špatně? Proč se mi nesplní podmínka pro spuštění CASE 2?

Díky moc.

Martin
While_do_while_LED_BLIK_fce.ino.ino
(1.92 KiB) Staženo 141 x
Funkce.ino
(1.13 KiB) Staženo 136 x

AstroMiK
Příspěvky: 592
Registrován: 08 pro 2017, 19:05
Reputation: 0

Re: Kde mám chybu - switch

Příspěvek od AstroMiK » 11 led 2020, 16:10

Zkoušel jsem ten kód.
Když se v sériovém terminálu zvolí "Chybný konec řádky", tak program funguje podle očekávání.
Obrázek:
serkom.gif

Když se ale použije nějaké ukončení řádky, tak to znamená, že se za zadaným číslem odešle automaticky ještě jeden, nebo 2 znaky, které se v následující smyčce loop() vyhodnotí funkcí parseInt() jako 0.

Já pro tento případ používám řešení, že se po funkci parseInt() vymaže všechno, co se za číslem v sériové lince nachází.
Pak začne další smyčka loop() s prázdným bufferem.

Kód: Vybrat vše

void cteniSerialLinky() {
   if (Serial.available() > 0) { // pokud se něco objeví na sériové lince
    int stav = Serial.parseInt(); //načte to a udělá z toho číslo

    // ------------
    delay(10);                                      // chvilku pauza na nacteni pripadneho zbytku komunikace
    while(Serial.available())  Serial.read();       //  vsechno ostatni bez uzitku zahod
    // ------------
    
    ... pokračování programu

PS. Nemáš v programu ošetřené zadání písmen. Když někdo zadá třeba písmeno "x", vyhodnotí se to jako 0. Asi by stálo za to vyhodnotit to jako "== Chybné zadání =="

tosik
Příspěvky: 90
Registrován: 11 led 2020, 14:48
Reputation: 0

Re: Kde mám chybu - switch

Příspěvek od tosik » 11 led 2020, 16:35

Díky za odpověď.

Ale nefunguje to správně. Mrkni na ten obrázek, co jsi tu dal, pokud pošlu dvojku, má to vypsat, že se spustí třetí CASE, ne druhé. to se stane až u zadání 6. Proč?

Mrknu na to tvé řešení.

Znaky bych taky chtěl ošetřit, ale zastavil jsem se na tomhle. V původním řešení, bez CASE, mi to znaky vyhodnocovalo jako chybu. Problém je i s desetinným číslem. To pak vytiskne nadvakrát, nejprve celé číslo a pak z desetinné části to udělá celé číslo a jede to znovu. :o

AstroMiK
Příspěvky: 592
Registrován: 08 pro 2017, 19:05
Reputation: 0

Re: Kde mám chybu - switch

Příspěvek od AstroMiK » 11 led 2020, 16:50

Já tam chybu nevidím. Možná teda nechápu zadání.

Proč by se mělo při čísle 2 spouštět třetí case?
Třetí case znamená špatné zadání (záporné číslo, nebo číslo větší než 5).
Číslo 2 je ale v pořádku.
Pro čísla 1 až 5 bude vždycky platit druhé case.

AstroMiK
Příspěvky: 592
Registrován: 08 pro 2017, 19:05
Reputation: 0

Re: Kde mám chybu - switch

Příspěvek od AstroMiK » 11 led 2020, 16:52

.... Desetinná čísla se zadávají s desetinnou tečkou a musí se použít funkce parseFloat().

tosik
Příspěvky: 90
Registrován: 11 led 2020, 14:48
Reputation: 0

Re: Kde mám chybu - switch

Příspěvek od tosik » 11 led 2020, 17:27

Protože třetí CASE má být právě pro ošetření chybného zadání.
Už jsem na to přišel, mám to špatně naprogramované. Třetí CASE se spustí až při hodnotě větší, než je nastavená v maxBlik, což má být hodnota pro omezení zadání. neuvědomil jsem si, že tím pádem mi to ovlivní i příkaz SWITCH. Musím to předělat. 0 = zastavit blikání, 1 - zadaná hodnota v rozsahu 1 - maxBlik, podle toho tolikrát blikej a 2 je pro chybné zadání. Dal jsem prostě dohromady jablka a hrušky... A co se týče desetinného čísla, právě bych ho potřeboval vyhodnotit jako chybné zadání, protože blikat, 5.36987 krát je blbost... :-) Díky za pomoc.

AstroMiK
Příspěvky: 592
Registrován: 08 pro 2017, 19:05
Reputation: 0

Re: Kde mám chybu - switch

Příspěvek od AstroMiK » 11 led 2020, 19:59

To rozpoznání desetinného čísla bude asi trochu problém.

Napadly mě dvě řešení, ale ani jedno není ideální.

První nápad:
Ze zadaného řetězce se zkusí získat pomocí parseFloat() desetinné číslo, pak se to číslo převede na int a porovná se z původním číslem.
Když jsou stejná, bylo to celé číslo, když jsou různá, bylo to desetinné číslo.

Kód: Vybrat vše

int stav;
float a = Serial.parseFloat();
int b = (int) a;
if (a==b)    stav = b;          // bylo zadané celé číslo
else         stav = 9999;       // bylo zadané desetinné číslo, 'stav' se nastaví někam mimo povolený rozsah
Tohle sice funguje, ale je nutné jako oddělovač desetinné části použít tečku.
Pokud by někdo zadal čárku, vyhodnotilo by se zadané číslo vždycky jako celé.
Docela velký problém je také v tom, že float je nepřesné a samo se nějak podivně zaokrouhluje.
Takže mohou občas vzniknou i nepředvídatelné situace - třeba při zadání nul v desetinné části

Kód: Vybrat vše

Příklady:
5.36987   -->>  9999       (OK - číslo je desetinné)
5         -->>   5         (OK - číslo je celé)
5,36987   -->>   5      (chyba - mělo se vyhodnotit, jako desetinné číslo)
21.0      -->>  21         (OK - číslo je celé)
21.00     -->>  9999    (chyba - mělo se vyhodnotit jako celé číslo 21)

----


Druhý nápad spočíval v tom, že se zkusí v zadaném řetězci najít 2 celá čísla
Když je druhé číslo 0, znamená to, že bylo zadáno celé číslo.
V tomhle případě se využije vlastnost funkce parseInt(), která vrátí nulu i v případě, že žádné číslo není zadané (trvá to trochu delší dobu, než vyprší timeout).

Kód: Vybrat vše

int stav;
int a = Serial.parseInt();      // v zadaném řetězci se najde první celé číslo
int b = Serial.parseInt();      // v zadaném řetězci se najde druhé celé číslo
if (b==0)    stav = a;          // bylo zadané celé číslo
else         stav = 9999;       // bylo zadané desetinné číslo, 'stav' se nastaví někam mimo povolený rozsah
V tomhle případě nezáleží na oddělovači. Může to být tečka, čárka, nebo dokonce i jakékoliv písmeno.
Existuje ale jeden případ, kdy se po zadání desetinného čísla to číslo vyhodnotí jako celé.
Stane se to tehdy, když je desetinná část rovna číslu 65536 (nebo jeho násobkům).

Kód: Vybrat vše

Příklady:
5.36987   -->>  9999         (OK - číslo je desetinné)
5         -->>   5           (OK - číslo je celé)
5.00      -->>   5           (OK - číslo je celé)
5,36987   -->>  9999         (OK - číslo je desetinné)
5,65536   -->>   5       (chyba  - číslo je desetinné, výsledkem by mělo být 9999)
5.65536   -->>   5       (chyba  - číslo je desetinné, výsledkem by mělo být 9999)
5x123     -->>  9999         (OK - zadaný nesmysl)
m54q75    -->>  9999         (OK - zadaný nesmysl)
m54abcd   -->>  54        (chyba - zadaný nesmysl, výsledkem by mělo být 9999)
----


Další řešení by bylo brát ze sériové linky znak po znaku a to výsledné číslo si nějak poskládat a testovat, jestli neobsahuje nepovolené znaky.
To je ale o dost složitější.

Má někdo další nápady?



---
Já osobně to většinou řeším tak, že použiju funkci parseInt() nebo parseFloat() - podle potřeby.
A když uživatel zadá nějaký nesmysl, tak to neřeším, ale jen zpátky napíšu, co z toho zadaného řetězce ta funkce rozpoznala.

Příklad:
Chci po uživateli, aby zadal celé číslo, použiju parseInt().
Když zadá například "abc3.689", funkce parseInt() z toho rozluští trojku. Já pak uživateli napíšu, že zadal 3 a s tou trojkou dál počítám.

Uživatelský avatar
kiRRow
Příspěvky: 1152
Registrován: 07 kvě 2019, 07:03
Reputation: 0
Bydliště: Opava

Re: Kde mám chybu - switch

Příspěvek od kiRRow » 11 led 2020, 20:44

Nejde to ošetřit na straně vstupu ? ... nedovolit uživateli už jen ten nesmysl zadat.

tosik
Příspěvky: 90
Registrován: 11 led 2020, 14:48
Reputation: 0

Re: Kde mám chybu - switch

Příspěvek od tosik » 12 led 2020, 09:17

Díky za vyčerpávající výklad o desetinných číslech... No tak v současné chvíli je pro mne asi opravdu jednodušší nezadat to desetinné číslo. Zatím to je jen pro mne, tak snad to sám nezpackám... :-D Myslel jsem si, že to bude jednodušší. Ta druhá varianta je taky použitelná, pravděpodobnost, že se zadá zrovna 65536 či jeho násobky je celkem malá. A co se týče té kombinace znaku a čísla, nešlo by to ošetřit tak, že pokud ta proměnná obsahuje nějaký znak, mimo čísla, tak to prostě vyhodnotí chybou zadání?

AstroMiK
Příspěvky: 592
Registrován: 08 pro 2017, 19:05
Reputation: 0

Re: Kde mám chybu - switch

Příspěvek od AstroMiK » 12 led 2020, 09:54

Tak jsem zkusil napsat program pro testování jednotlivých zadaných znaků.
Myslím, že to už je téměř "neprůstřelné".
Ale jen to vyhodnocení zabere víc paměti, než celý zbytek programu.

Zkus si projít tenhle kód:
(Je to jen o tom vyhodnocení zadaného řetězce.)

Kód: Vybrat vše



void setup(void)
  {
    Serial.begin(9600);
  }



void loop(void)
  {
    if (Serial.available())                                // kdyz se objevi nejaka data na seriove lince ...
      {
        delay(10);                                         // chvilku pauza na precteni celeho retezce ze seriove linky do prijimaciho bufferu (staci prvnich 10 znaku)

        char prijato[10];                                  // vsechno ze seriove linky se ulozi znak po znaku do retezce 'prijato'
        byte ukazatel = 0;                                 // ukazatel na aktualni index znaku v retezci
        while (Serial.available())
          {
            char znak = Serial.read();
            prijato[ukazatel] = znak ; 
            ukazatel ++;
            if (ukazatel == 9) break;                      // maximalni delka zadaneho retezce je 10 znaku, pri dosazeni teto delky se cteni z linky prerusi
          }

        prijato[ukazatel] = 0 ;                            // ukoncovaci znak retezce (terminator)


        while (Serial.available())
          {
            Serial.read();                                 // pokud by v bufferu zbylo jeste neco, tak se to bez uzitku smaze
            delay(2);                                      // zase chvilku pauza  - pro pripad, ze by do bufferu porad neco prichazelo, tak cteni nesmi byt rychlejsi, nez plneni        
          }




        boolean je_to_cele_cislo = true;                   // na zacatku se prepoklada, ze je zadane cele cislo
        
        for (byte i = 0 ; i < ukazatel ; i ++)             // retezec se bude prochazet znak po znaku (zaverecny terminator se uz netestuje)
          {
            if (prijato[i] == 13 or prijato[i] == 10)      // pri pripadnem konci radky se smycka FOR prerusi
              {
                if (i == 0) je_to_cele_cislo = false;      // specialni pripad, kdy neni zadano zadne cislo (jen prazdny retezec)
                                                           //     (v tom pripade nebylo zadane cele cislo)
                break;                                     // predcasne vyskoceni ze smycky FOR
              }


            if (prijato[i] < '0' or prijato[i] > '9')      // vlozeny retezec smi obsahovat jen cisla mezi '0' a '9'
              {
                je_to_cele_cislo = false;                  // pokud je nektery znak mimo tento rozsah, neni to cele cislo
              }

            if (i == 0 and prijato[0] == '+')              // specialni pripad, kdy na prvnim zadanem miste muze byt i znak '+'
              {
                je_to_cele_cislo = true;
              }          

          }



        if (je_to_cele_cislo == true)                      // na zaver uz jen vyhodnoceni a pripadny prevod z pole char[] na unsigned long
          {
            Serial.print("bylo zadano cele cislo: ");
            unsigned long vysledek = atoi(prijato);
            Serial.println(vysledek);
          }
        else
          {
            Serial.print("bylo zadano desetinne, zaporne, nebo nesmyslne cislo: ");
            Serial.println(prijato);
          }


        
      }

  
  }


Když zadaný řetězec obsahuje cokoliv mimo znaků '0' až '9', je to vyhodnoceno jako chybné zadání (záporné, desetinné, nebo nesmyslné číslo).
Kromě jednoho případu, kdy na začátku může být znak '+'. Ten nevadí (ale musí být jen na začátku).
Nezáleží na tom, jestli někdo zadal desetinnou tečku, nebo čárku. Prostě je takový znak nepovolený a výsledek je vyhodnocen jako nesmyslné, nebo desetinné číslo.
Je tam i ošetřený případ, kdy se nevloží žádný znak. V tom případě je to vyhodnoceno jako nesmyslné číslo.

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 12 hostů