Problema nel fermare un ciclo while

Salve, come da titolo sto avendo dei problemi nell'uscire da un ciclo while. Premetto che sono alle prime armi, perciò mi rendo conto che il problema per molti potrà sembrare banale. Sto usando un Arduino UNO, in pratica vorrei che una volta inviata la lettera "c" dal serial monitor si attivasse un emettitore di vibrazione che mandi a ripetizione una vibrazione, seguendo un certo ritmo, finché non viene interrotta da un altro comando (la lettera "p"). Funziona tutto bene finché non arriva il momento di inviare un nuovo comando, infatti la lettera p non viene visualizzata sul serial monitor. Di seguito una parte del codice che sto usando:

while(command == 'c'){
      for (int fadeValue = 0 ; fadeValue <= 250; fadeValue += 20){
        analogWrite(motorPin, fadeValue);
        delay(10);
        }
      for (int fadeValue = 250; fadeValue >= 0; fadeValue -= 15){
        analogWrite(motorPin, fadeValue);
        delay(30);
        }
      if(command == 'p')
        {
          break;
        }
     }

Ho letto in giro che potrei usare while(Serial.available()), credo, ma anche se fosse non ho ben capito come funzioni... quindi mi rimetto a voi. Ringrazio in anticipo chiunque voglia aiutarmi! :slight_smile:

Oltre al while, il problema sta anche nel for, che è bloccante: una volta che è partito, qualunque cosa accada viene ignorata. Anziché usare il while e il for, usa un if e una variabile che viene incrementata mentre il loop continua a girare.

Datman:
Oltre al while, il problema sta anche nel for, che è bloccante: una volta che è partito, qualunque cosa accada viene ignorata. Anziché usare il while e il for, usa un if e una variabile che viene incrementata mentre il loop continua a girare.

Ah ok, non sapevo che il for potesse bloccare. Puoi darmi qualche esempio di come posso trasformarlo da while/for a if + variabile?

Il for blocca perché finché il valore finale non è raggiunto l'esecuzione rimane confinata nel for. Una cosa simile accade nel while.
Nel tuo caso, scrivi qualcosa come: if (command == 'c') al posto del while e if (fadeValue<=250){} , incrementando il valore a ogni ciclo. La variabile dovrà essere globale. Ovviamente tutto il ciclo dovrà girare molto velocemente, senza alcun impedimento.

Io avrei bisogno che tutto ciò venisse eseguito in loop finché non accade un'altra cosa che lo interrompe. Ho già provato a usare if ma la vibrazione si interrompe subito. In pratica fadevalue mi servirebbe a portare la vibrazione alla sua intensità massima, per poi riscendere a 0, così da avere un ciclo in cui si alterna vibrazione forte, vibrazione nulla, vibrazione forte, vibrazione nulla, in loop così.

Per quello che mi serve non dovrebbe essere più indicato while di if?

Per entrare nel WHILE devi premere C
Quindi come fai a fare la IF del tasto P sei sei dentro con il tasto C ?
E se premi P non sei più dentro il while C....

if (command == 'c') {
  for (int fadeValue = 0 ; fadeValue <= 250; fadeValue += 20) {
    analogWrite(motorPin, fadeValue);
    delay(10);
  }
  for (int fadeValue = 250; fadeValue >= 0; fadeValue -= 15) {
    analogWrite(motorPin, fadeValue);
    delay(30);
  }
}

in questo modo se il comando è c allora hai la vibrazione mentra qualsiasi altra cosa non hai nulla.

Da qui puoi fare altre IF per altri tasti.

Comunque, durante i for ignorerebbe tutto...
Così funziona?...

byte fadeValue=0;
byte stato=0;
char command;
#define motorPin 11
#define UP 1
#define DN 2
#define ALTRO 3

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

void loop()
{
if (Serial.available())
  {
  command=Serial.read();
  if (command=='c') stato=UP;
  if (command=='p') stato=ALTRO;
  }

if (stato==UP)
  {
  if(fadeValue<=220)
    {
    delay(10);
    fadeValue+=20;
    }
  else stato=DN;
  }
else if (stato==DN)
  {
  if(fadeValue>=15)
    {
    delay(30);
    fadeValue-=15;
    }
  else stato=UP;
  }
analogWrite (motorPin, fadeValue);
}

Il problema sembra risolvibile tramite una if-else e uno switch-case con almeno due case.

byte state = 0;
void loop() {
    // ovviamente manca il codice per leggere da seriale
    if (command == 'c') {
       state = 1;
   else if (command == 'p') {
       state = 0;
   }

   switch (state) {
   case 1:
       // incrementa vibrazione
       // manca un if (contatore > Max) state = 2
       state = 2; 
       break;
   case 2:
       decrementa vibrazione
       // manca un if (contatore < Min) state = 1;
       state = 1;
       break;

   }


}

Quando state vale 0 (zero) il codice contenuto in case 1 e case 2 non viene eseguito.

Ciao.

vegetto1624:
Per quello che mi serve non dovrebbe essere più indicato while di if?

Dimentichi che la loop() è GIA' una ciclo while, visto che viene eseguito all'infinito
Infatti il core Arduino nasconde il programma principale (che è in C/C++)

main()
{ ...
  setup();
  while(1) loop(); 
  ...
}

vegetto1624:
qualche esempio di come posso trasformarlo da while/for a if + variabile?

Un while, con dentro due for, con dentro dei delay... un esempio più complicato non lo potevi trovare? :slight_smile:

Per scrivere in modo non bloccante bisogna esplicitare le fasi di funzionamento, che adesso invece sono implicite nell'ordine di esecuzione delle istruzioni.

C'è una fase di funzionamento in cui i for non devono essere eseguiti (FERMO), c'è una fase in cui con il primo for il PWM aumenta (FADEIN), e una in cui con il secondo for diminuisce (FADEOUT).

Bene, il progetto del programma deve partire dalle fasi necessarie scritte in modo esplicito:

if (FERMO == fase)
{
}
else if (FADEIN == fase)
{
}
else if (FADEOUT == fase)
{
}

In ogni fase vanno scritti gli eventi attesi e le azioni da compiere.
Nella fase fermo ci si aspetta solo di ricevere il comando di avvio:

if (FERMO == fase  &&  'c' == command)
{
    fase = FADEIN;
}

Per semplificare possiamo scrivere la condizione di arresto con un if prima delle fasi:

if ('c' != command) { fase = FERMO; }

if (FERMO == fase  &&  'c' == command)
{
    fase = FADEIN;
}
else if ....

Ora le fasi FADEIN e FADEOUT devono comportarsi come i for. Un generico for è composto così:

for (INIT; CONDITION; UPDATE)
{
    ACTION;
}

Bisogna fare in modo che, ad esempio nella fase FADEIN, le parti INIT CONDITION ACTION e UPDATE si svolgano nello stesso ordine:

else if (FADEIN == fase)
{
    if (inizio) { INIT;  inizio=0; }

    if   (CONDITION) { ACTION; UPDATE; }
    else             { fase = FADEOUT; }
}

Si vede che serve una variabile flag 'inizio' per indicare la condizione di prima esecuzione della fase:

if ('c' != command) { fase = FERMO; }

if (FERMO == fase  &&  'c' == command)
{
    fase = FADEIN;
    inizio = 1;
}
else if (FADEIN == fase)
{
    if (inizio) { fadeValue=0;  inizio=0; }  // INIT

    if (fadeValue <= 250)
    {
        analogWrite(motorPin, fadeValue);    // ACTION
        fadeValue += 20;                     // UPDATE
    }
    else
    { 
        fase = FADEOUT;                      // fine ciclo fadein
        inizio = 1;
    }
}
else if ...

E abbiamo il nostro for trasformato in non bloccante, MA... nell'ACTION originale dopo l'analogWrite c'è anche un delay... che qui non possiamo mettere perché, seppur breve, bloccherebbe la libera esecuzione del loop.

E quindi serve un'altra variabile flag 'attesa' per indicare che il contenuto della fase "è in pausa", cioè non eseguito, fino a timeout. E i tempi vanno calcolati come valore trascorso da un momento iniziale usando la funzione millis. Il momento iniziale è ovviamente l'istante successivo alla analogWrite (dove c'era il delay originale).

if ('c' != command) { fase = FERMO; }

if (FERMO == fase  &&  'c' == command)
{
    fase = FADEIN;
    inizio = 1;
}
else if (FADEIN == fase)
{
    if (inizio) { fadeValue=0;  inizio=0; }      // INIT

    if (attesa  && (millis()-t >= 10)) attesa = 0;

    if (!attesa)
    {
        if (fadeValue <= 250)
        {
            analogWrite(motorPin, fadeValue);    // ACTION
            t = millis();
            attesa = 1;
            fadeValue += 20;                     // UPDATE
        }
        else
        { 
            fase = FADEOUT;                      // fine ciclo fadein
            inizio = 1;
        }
    }
}
else if ...

Questo in generale, poi alcune implementazioni specifiche delle fasi possono semplificare alcune cose e ridurre il numero di righe, per esempio si possono spostare gli INIT nelle azioni delle fasi precedenti, e usare una struttura switch che consente di interrompere l'esecuzione di un case con break.

if ('c' != command) fase = FERMO;

switch (fase)
{
    case FERMO:
        if ('c' == command) { fase = FADEIN;  fadeValue = 0;  t = millis(); }
    break;

    case FADEIN:
        analogWrite(motorPin, fadeValue);
        if (millis()-t < 10) break;
        fadeValue += 20;
        if (fadeValue <= 250) t = millis(); 
        else { fase = FADEOUT;  fadeValue = 250;  t = millis(); }
    break;

    case FADEOUT:
        ....
    break;
}

Innanzitutto vorrei ringraziarvi tutti per il supporto. Ho provato i primi codici che mi sono stati forniti e, anche se con le opportune modifiche, non sono riuscito a farli funzionare...

Per quanto riguarda il prezioso intervento di Claudio_FF, purtroppo devo ammettere di non aver capito alcune cose. Immagino di dover dichiarare FERMO, FADEIN e FADEOUT, così come "fase" (anche se non mi è ben chiaro come funzioni).

if ('c' != command) { fase = FERMO; }

if (FERMO == fase  &&  'c' == command)
{
    fase = FADEIN;
    inizio = 1;
}
else if (FADEIN == fase)
{
    if (inizio) { fadeValue=0;  inizio=0; }      // INIT

    if (attesa  && (millis()-t >= 10)) attesa = 0;

    if (!attesa)
    {
        if (fadeValue <= 250)
        {
            analogWrite(motorPin, fadeValue);    // ACTION
            t = millis();
            attesa = 1;
            fadeValue += 20;                     // UPDATE
        }
        else
        {
            fase = FADEOUT;                      // fine ciclo fadein
            inizio = 1;
        }
    }
}
else if ...

Questo else if alla fine a cosa servirebbe? Perdonami, essendo alle primissime armi ho un po' di difficoltà. Se mi dicessi in quali punti devo intervenire e come, al resto potrei pensarci io.

vegetto1624:
Immagino di dover dichiarare FERMO, FADEIN e FADEOUT, così come "fase"

Sono valori costanti (da definire prima) da assegnare alla variabile fase, al posto dei nomi puoi anche scrivere direttamente dei numeri, ad esempio 0 1 2 per le tre fasi possibili, ma i nomi rendono più chiaro cosa si sta facendo.

All'avvio del programma la variabile fase va impostata al valore del primo stato attivo (FERMO).

L'ultimo else if è l'inizio della terza fase da scrivere (FADEOUT).

La variabile temporale t[tt] deve essere di tipo unsigned long.

Ho corretto il codice nel mio messaggio #7. Lì, per semplicità, ho lasciato i brevi delay di 10 e 30ms.

Ho provato il codice aggiornato, mi stampa le lettere ma il motore di vibrazione non parte :confused:
Comunque ho pensato ad un altro modo per farlo funzionare. Forse è un metodo più semplice, non so, ma per ora non funziona neanche questo (anche se per un problema diverso).

void loop(){
 
    if(Serial.available()){
      command = Serial.read();
      Serial.println("comando inviato");
      Serial.println(command);
     
    
    if(command == 'c'){
      for (int fadeValue = 0 ; fadeValue <= 250; fadeValue += 20){
        analogWrite(motorPin, fadeValue);
        delay(10);}
      for (int fadeValue = 250; fadeValue >= 0; fadeValue -= 15){
        analogWrite(motorPin, fadeValue);
        delay(30);}
        Serial.println("f");
        }
      
    if(command == 'f'){
      for (int fadeValue = 0 ; fadeValue <= 250; fadeValue += 20){
        analogWrite(motorPin, fadeValue);
        delay(10);}
      for (int fadeValue = 250; fadeValue >= 0; fadeValue -= 15){
        analogWrite(motorPin, fadeValue);
        delay(30);}
        Serial.println("c");
        }
      }
   }

in pratica vorrei "spezzare" il ciclo creato dai for con un if che rimanda ad un altro if tramite la stampa e la lettura degli stessi caratteri p ed f. Vorrei che una volta inviato il comando f avvenisse la prima vibrazione con la stampa del carattere p, così il secondo if legge il carattere, fa partire l'altra vibrazione e poi stampa il carattere f, creando alla fine un loop. Si potrebbe fare che inserendo un terzo if venga fermato il loop una volta inviata la lettera "p"?

Il codice del #7 con un LED funziona...

Scusate, magari mi sono spiegato male io. Rinnovo la domanda:
E' possibile far leggere al codice un carattere (o stringa) inviato sul monitor seriale dallo stesso codice? Cioè, se io premendo A faccio avvenire una certa cosa e alla fine di questa certa cosa voglio scrivere sul monitor la lettera B, posso far leggere ad un altro if nello stesso codice che è stata inviata la lettera B (non da me, ma dal Serial.print) e far avvenire un'altra certa cosa?

E' un procedimento inutilmente contorto... Mi ricorda quando mi chiedevo come facesse il Ping-O-Tronic a sapere esattamente quando la pallina toccava la racchetta... Come faceva?... Eh! La pallina ce la metteva lui!!! :smiley:
Allo stesso modo, se il codice scrive "B" non è necessario andare a leggerlo per sapere che lo ha scritto. Lo ha scritto lui!

Scusate, magari mi sono spiegato male io. Rinnovo la domanda:

Mica l'ho capita. Rinnovi la domanda, quindi il problema rimane il for "bloccante"?

Prova il codice a questo link

Ciao.

Adesso sono riuscito a risolvere in questo modo:

if (Serial.available() > 0) 
      command = Serial.read();
 
 if (command=='c') 
{         
 while(command != 'p')
  {
    if (Serial.available() > 0) 
       command = Serial.read(); 
 
       Serial.print("I received: ");
       Serial.println(command);
 
    for (int fadeValue = 0 ; fadeValue <= 250; fadeValue += 20)
       {
        analogWrite(motorPin, fadeValue);
        delay(50);
       }
 
    for (int fadeValue = 250; fadeValue >= 0; fadeValue -= 15)
       {
        analogWrite(motorPin, fadeValue);
        delay(50);
       }
     } 
   }