RISOLTO - variabili locali vs variabili globali

Buonasera, sto facendo un po' di esercizi preliminari con arduino e mi sono intestardito a voler far eseguire un codice senza utilizzare la classe String. Mi spiego meglio: l'obiettivo è accendere/spegnere un led tramite stringhe di caratteri (nel mio caso "on" e "off"). Il codice utilizzando String è questo in basso e non ho problemi (lo posto solo a beneficio di chi sta approcciando alla programmazione; occorre settare il monitor seriale su "nessun fine riga").

const unsigned int LED_PIN=13;
const unsigned int BAUD_RATE=9600;
const unsigned int PAUSE=100;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(BAUD_RATE);
  Serial.println("Warning: setta il monitor seriale su nessun fine riga");
  Serial.println("on:\taccende il LED");
  Serial.println("off:\tspegne il LED");
}

void loop() {
 leggidaseriale();
}

void leggidaseriale() {                             //se ci sono dati da leggere
String comando{};
  if(Serial.available()>0) {                                         
    comando=Serial.readString();                  //memorizzo in comando
    if (comando=="on"){                           //se "on" alzo il segnalo e stampo sul monitor ON
         digitalWrite(LED_PIN,HIGH);
         Serial.println("LED ON");
    }
    else if (comando=="off"){                     //se off abbasso il segnalo e stampo sul monitor OFF
      digitalWrite (LED_PIN, LOW);
      Serial.println("LED OFF");
    }
    else {                                        //altrimenti non riconosco il comando
      Serial.print(comando);
      Serial.println(" è un comando sconosciuto");                         
    }
  }
}

Ma io voglio utilizzare gli array per un impiego più efficiente delle risorse e...mi sono arenato. Cioè, Il codice che riporto di seguito (dal quale ho rimosso tutti i riferimenti all'accensione/spegnimento del led in quanto superflui ai fini della domanda e suppone il monitor seriale questa volta impostato su NL) ha un comportamento anomalo in quanto il contatore "i" viene correttamente incrementato da "i++" ma alla successiva reiterazione riparte da 0 e quindi sovrascrivo il successivo carattere letto sulla seriale nella prima posizione dell'array. Il problema sparisce dichiarando "i" come variabile globale. La stessa cosa capita con la stringa "comando": se la dichiaro localmente, ad ogni reitarazione del while viene reinizializzata a vuota. Pertanto, affinchè il programma funzioni correttamente, "i" e "comando" devono essere entrambi globali; "c" invece è indifferente perchè comunque viene valorizzata ad ogni lettura da seriale e non serve mantenere il suo stato precedente. Io non ho nessuna necessità di avere queste 2 variabili visibili all'esterno della funzione leggidaseriale() quindi per questione stilistica le preferirei locali.

Qualcuno sa spiegarmi perchè "i" (e allo stesso modo "comando") viene azzerata ad ogni iterazione del while se la dichiaro localmente?

const unsigned int BAUD_RATE=9600;

char c;
char comando[4]={};  
int i=0; 


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

void loop() {
  leggidaseriale();
}

void leggidaseriale() {
                       
  while (Serial.available()>0) {         //finche' ci sono dati da leggere sulla porta seriale
        c=Serial.read();
        Serial.println(i);
        if (c != '\n') {                 //se il carattere non è \n lo memorizzo  nell i-esima posizione dell'array e incremento il contatore
          comando[i]= c;
          i++; 
        }    
       else {                           //quando incontro \n termino la stringa, stampo l'array e riporto a 0 il contatore per successive letture
          comando[i]='\0';
          Serial.print("la stringa è: ");
          Serial.println(comando);
          i=0;
        }   
  }
}

Come le dichiari in locale?

Per lo OP

È il comportamento standard di C

Se vuoi che non sia visibile al di fuori della funzione MA abbia vita lunga quanto il programma devi dichiararle 'static'

Una buona lettura al K&R e passa la paura

fratt: Come le dichiari in locale?

Buongiorno, guarda semplicemente allo stesso modo ossia: taglio da globale e incollo in locale. Ovviamente le dichiaro sempre in via esclusiva..o globali o all'interno della funzione "leggidaseriale".

Come dice anche Standardoil è normale che le variabili si "resettino" tra una chiamata e l'altra della funzione. Se invece si resettano anche all'interno della stessa chiamata c'è qualcosa che non va.

A meno che i caratteri su seriale non arrivino troppo lentamente e quindi il serial.available() resituisce false, la funzione termina e viene richiamata al successivo giro di loop() e quindi le variabili locali vengono ricreate da zero.

Prova a mettere il codice che presenta il problema.

Standardoil: Per lo OP

È il comportamento standard di C

Se vuoi che non sia visibile al di fuori della funzione MA abbia vita lunga quanto il programma devi dichiararle 'static'

Una buona lettura al K&R e passa la paura

Susami ma non é il comportamento standard del C e K&R sarebbero entrambi d'accordo. Il comportamento standard prevede che una variabile dichiarata localmente sia visibile solo all'interno di quella funzione. Se una variabile la qualifichiamo come static allora in luogo di un'allocazione automatica il compilatore alloca memoria staticamente e quindi quelle celle restano valorizzate per tutta l'esecuzione del programma. Il caso che io ho posto peró é diverso: se dichiaro "i" localmente, questa viene riazzerata all'interno delle iterazioni dello stessociclo while.

Se il comportamento cambia dichiarandola globale o locale, il problema è dovuto alla causa che ti è stata indicata.

Tu affermi che invece si resetta dentro il while, sicuro che i due passaggi che ti aspetti siano nello stesso while lo siano effettivamente? Se alla seriale arriva un carattere alla volta nel tempo di esecuzione della tua funzione, il tuo while farà una sola iterazione e poi uscirà pattumando le variabili locali. La funzione verrà richiamata N volte senza fare nulla e poi all'arrivo del successivo carattere rientrerà nel while facendo un solo giro ma con le variabili intonse sovrascrivendo il precedente valore e via così.

Se il valore si resettasse a causa del codice, questo comportamento lo avresti anche per variabili definite globali o static.

Maurizio

fratt: Come dice anche Standardoil è normale che le variabili si "resettino" tra una chiamata e l'altra della funzione. [u]Se invece si resettano anche all'interno della stessa chiamata c'è qualcosa che non va.[/u]

A meno che i caratteri su seriale non arrivino troppo lentamente e quindi il serial.available() resituisce false, la funzione termina e viene richiamata al successivo giro di loop() e quindi le variabili locali vengono ricreate da zero.

Prova a mettere il codice che presenta il problema.

Avviene esattamente quello che hai indicato e che ti ho sottolineato e messo in grassetto nel quote. ecco il codice che non va:

const unsigned int BAUD_RATE=9600;

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

void loop() {
  leggidaseriale();
}

void leggidaseriale() {
  
char c;
char comando[4]={}; 
int i=0;

  while (Serial.available()>0) {         //finche' ci sono dati da leggere sulla porta seriale
        c=Serial.read();
        Serial.println(i);
        if (c != '\n') {                 //se il carattere non è \n lo memorizzo  nell i-esima posizione dell'array e incremento il contatore
          comando[i]= c;
          i++;
        }   
       else {                           //quando incontro \n termino la stringa, stampo l'array e riporto a 0 il contatore per successive letture
          comando[i]='\0';
          Serial.print("la stringa è: ");
          Serial.println(comando);
          i=0;
        }   
  }
}

come puoi vedere, l'unica differenza é che le dichiarazioni qui sono all'interno della funzione leggidaseriale().

Il caso che io ho posto peró é diverso: se dichiaro "i" localmente, questa viene riazzerata all'interno delle iterazioni dello stessociclo while.

secondo me è qua che ti "sbagli"...tu pensi che la funzione rimanga nel while...mentre secondo me esce...pensa alla velocità di esecuzione di quella funzione, quindi di verifica del while, e dei caratteri che entrano nella seriale...se vuoi rimanere in quel while dovresti verificare che il carattere arrivato non sia il fine stringa...ma a sto punto ti conviene usare una Serial.readBytesUntil() che gestisce anche il timeout

justis67:
Susami ma non é il comportamento standard del C e K&R sarebbero entrambi d’accordo.

Non è lavoro mio convincerti
Ne risolverti i tuoi problemi

Buon viaggio

maubarzi: Se il comportamento cambia dichiarandola globale o locale, il problema è dovuto alla causa che ti è stata indicata.

Tu affermi che invece si resetta dentro il while, sicuro che i due passaggi che ti aspetti siano nello stesso while lo siano effettivamente? Se alla seriale arriva un carattere alla volta nel tempo di esecuzione della tua funzione, il tuo while farà una sola iterazione e poi uscirà pattumando le variabili locali. La funzione verrà richiamata N volte senza fare nulla e poi all'arrivo del successivo carattere rientrerà nel while facendo un solo giro ma con le variabili intonse sovrascrivendo il precedente valore e via così.

Se il valore si resettasse a causa del codice, questo comportamento lo avresti anche per variabili definite globali o static.

Maurizio

Ciao Maurizio, e grazie mille. Si, il comportamento cambia se le dichiaro globali o locali. Se le dichiaro locali, ad ogni iterazione del while i torno a 0. Ho capito questo metteto un Serial.print(i) all'inizio del While e uno dopo i++. Se le dichiaro globali invece si comporta come atteso. Per determinare con certezza se é legato alle tempistiche con cui leggidaseriale() viene eseguita proveró ad introdurre dei delay in modo che il successivo carattere sia disponibile sulla seriale prima del termine del ciclo while. Che ne dici come "strategia di debug?

Sono d'accordo con @ORSO2001, più volte è stato riscontrato quanto indicato da lui, ovvero non è disponibile nessun carattere e quindi esci dal while. Ma visto che hai il debug dovresti analizzaro, se è come sosteniamo noi dovresti veder passare i singoli caratteri e poi la dicitura "la stringa è" con solo l'ultimo carattere, solo parte della stringa o anche vuota. Se l'intendo invece è quello di non rendere bloccante la procedura invece non concordo sull'uso del readBytesUntil che ti blocca tutto. In linea generale si dovrebbe preferire l'uso di un "protocollo" che permetta di determinare se il messaggio ricevuto è terminato, corretto o no. Di moti ne esistono molti, ti cito come esempio quello usato dalla comunicazione seriale dei display Nextion, per detrminare se un comando è terminato si attendono tre caratteri 0xFF consecutivi, solo allora si processa quanto arrivato dalla seriale. Nel tuo caso potrebbe essere un CR+LF, un solo CR o LF, ecc. a te scegliere. Posso aggiungere che sarebbe bene anche aggiungere qualcosa che permetta di determinare se quanto ricevuto sia valido aggiungendo un controllo tipo CRC o similare ma la cosa si complica e la prevederei in un secondo momento. Poi, come suggerito da altri utenti del forum, l'uso di stringhe umanamente decodificabili è uno spreco di risorse. Il protocollo di dialogo tra due dispositivi deve essere pensato per il dispositivo non per l'umano, nel tuo caso potrebbe essere ridotto all'invio di uno zero o un uno al posto di "on" e "off", fermo restando gli altri consigli dati.

ORSO2001: secondo me è qua che ti "sbagli"...tu pensi che la funzione rimanga nel while...mentre secondo me esce...pensa alla velocità di esecuzione di quella funzione, quindi di verifica del while, e dei caratteri che entrano nella seriale...se vuoi rimanere in quel while dovresti verificare che il carattere arrivato non sia il fine stringa...ma a sto punto ti conviene usare una Serial.readBytesUntil() che gestisce anche il timeout

Grazie mille...sia tu che Maurizio mi avete messo sulla strada giusta. Appena torno a casa mi metto all'opera!

justis67: Per determinare con certezza se é legato alle tempistiche con cui leggidaseriale() viene eseguita proveró ad introdurre dei delay in modo che il successivo carattere sia disponibile sulla seriale prima del termine del ciclo while. Che ne dici come "strategia di debug?

Metti

Serial.println('i=0');

prima del while dentro la tua funzione e vedrai subito che ti riempirai di "inizializzazioni" tra la lettura di un carattere e l'altro. Ad ogni i=0 corrisponde una nuova esecuzione della funzione con relativa reinizializzazione delle variabili locali...

fabpolli: Sono d'accordo con @ORSO2001, più volte è stato riscontrato quanto indicato da lui, ovvero non è disponibile nessun carattere e quindi esci dal while. ...

Grazie per aver del tutto ignorato il mio messaggio dove facevo la stessa diagnosi :'( Vabbeh, ti perdono ;) ma solo perchè sei tu :)

Maurizio

maubarzi:
Grazie per aver del tutto ignorato il mio messaggio dove facevo la stessa diagnosi :’(
Vabbeh, ti perdono :wink: ma solo perchè sei tu :slight_smile:

Maurizio

Hai ragionissima, ammetto che non ho avuto voglia di leggere tutto il thread, chiedo venia mi faccio perdonare con un punto karma :slight_smile:

fabpolli: Sono d'accordo con @ORSO2001, più volte è stato riscontrato quanto indicato da lui, ovvero non è disponibile nessun carattere e quindi esci dal while. Ma visto che hai il debug dovresti analizzaro, [u]se è come sosteniamo noi dovresti veder passare i singoli caratteri[/u] e poi la dicitura "la stringa è" con solo l'ultimo carattere, solo parte della stringa o anche vuota. Se l'intendo invece è quello di non rendere bloccante la procedura invece non concordo sull'uso del readBytesUntil che ti blocca tutto. In linea generale si dovrebbe preferire l'uso di un "protocollo" che permetta di determinare se il messaggio ricevuto è terminato, corretto o no. Di moti ne esistono molti, ti cito come esempio quello usato dalla comunicazione seriale dei display Nextion, per detrminare se un comando è terminato si attendono tre caratteri 0xFF consecutivi, solo allora si processa quanto arrivato dalla seriale. Nel tuo caso potrebbe essere un CR+LF, un solo CR o LF, ecc. a te scegliere. Posso aggiungere che sarebbe bene anche aggiungere qualcosa che permetta di determinare se quanto ricevuto sia valido aggiungendo un controllo tipo CRC o similare ma la cosa si complica e la prevederei in un secondo momento. Poi, come suggerito da altri utenti del forum, l'uso di stringhe umanamente decodificabili è uno spreco di risorse. Il protocollo di dialogo tra due dispositivi deve essere pensato per il dispositivo non per l'umano, nel tuo caso potrebbe essere ridotto all'invio di uno zero o un uno al posto di "on" e "off", fermo restando gli altri consigli dati.

Buongiorno fab si, avviene esattamente quello che hai descritto. (vedi grassetto nel quote). In pratica, metteto un serial.print(i) a inizio while e dopo l'incremento i++; l'output che avevo era del tipo (supponenendo di avere digitato "ciao") 0 c 1 0 i 1 etc.

perdonami se non ti riporto l'output preciso ma non sono a casa. Per quanto concerne la scelta dei "comandi" concordo con te al 1000%. In realtá sto seguendo degli esercizi su un libro che prevedevano l'accensione del led con valori interi. Poi come stimolo di "fine capitolo" suggeriva di cambiare il codice usando prima caratteri e poi stringhe...solo scopo didattico ;).

Grazie mile a te, Maurizio e ORSO2001...[u]TOP[/u]!

giusto per correttezza...maubarzi è il primo che ha indicato la "via" in modo esplicito e descrittivo; io mi sono sovrapposto al suo messaggio....la soluzione che ti ho proposto la Serial.readBytesUntil() va bene per le due righe di codice che hai...se il programma dovesse ampliarsi e il "blocco" non è opzione accettabile si deve andare, come detto da fabpolli, sulla stesura e gestione di un protocollo...e qua di possibilità ce ne sono una marea.

ueh ueh, io stavo solo scherzando, con fabpolli, non c'è mica un primato da certificare o delle royalties da pagare... ;D

Maurizio

Visto il sua "avatar" associare la sua soluzione a "questa è la via" è perfetto :) ;)

Non avevo pensato a un buffer static, ma in effetti perché no? Così in un momento di creatività e licenza poetica ho generato questo :stuck_out_tongue:

char* ricevuto(Stream &stream){
    const  uint16_t MAXBUF = 65;      // 64 chr + '\0'
    static uint8_t  buf[MAXBUF];
    static uint16_t i = 0;
    while (stream.available()){
        char c = stream.read();
        if ('\r' == c) continue;
        if ('\n' == c){
            buf[i] = '\0';
            i = 0;
            return buf;}
        else{
            buf[i] = c;
            i += i < MAXBUF-1;}}
    return NULL;}