[Risolto] Connessione seriale non va sotto gli 800ms

Ho appena provato da monitor seriale di Arduino ad inviare velocemente degli input numerici e Arduino risponde istantaneamente.

Provo subito!

Mah, scusate ma ho qualche perplessità su tutto ciò...

A 9600 baud sono circa 900 byte al secondo, d'accordo che non c'è handshake hardware ed il buffer lato Arduino è relativamente piccolo, ma non vedo proprio come mandare 2 caratteri possa essere limitato a 800 ms quindi circa 400ms a carattere, come se la seriale fosse a 25 baud!

Allora, a parte evitare le "String" lato Arduino come fosse kriptonite per Superman, non conosco Java (ma esiste una ".serialWrite()" o è una tua funzione?) ma quello che vedo è che ti MANCA UN TERMINATORE! Se tu mandi due valori, diciamo 6 e 8, come li distingui da "68"? Oppure se stai mandando 84 e nel buffer è arrivato solamente la prima cifra "8"?

Ad esempio diciamo mettendo il classico LF (ma il discorso va bene con qualsiasi carattere, anche "|") qualcosa del tipo:

String velocita=Integer.toString((int)v);
arduino.serialWrite(velocita + "\n");

e quindi in Arduino aspettare che ci sia il terminatore prima di acquisire il valore.
Se tu hai fatto prove con il Monitor Seriale attento che quello accumula i caratteri digitati e li manda tutti insieme (più il Line Feed...) per cui non è la stessa cosa.

Oppure puoi mandare sempre 2 caratteri (quindi non "8" ma "08") per cui basta attendere che Serial.available() sia uguale a 2, e leggere i caratteri e convertirli in intero.

Ma, meglio ancora, puoi farlo usando un byte visto che l'intervallo dei valori interi è compatibile con un char, e visto che si tratta di un dialogo tra macchine e non con un umano, è sempre meglio progettare i propri protocollini in modo che sia tutto più agevole per le macchine...

Quindi concordo con il suggerimento di fabpolli, che ti evita persino il discorso del terminatore in quanto il pacchetto è di lunghezza fissa (1 char ossia 1 unsigned byte)...

PS poi se la seriale la metti ad una velocità maggiore, diciamo anche "solo" 38400 hai tanta di quella banda che potresti usarla anche per ben altro... :wink:

Prova anche in setup a modificare il parametro Serial.setTimeout() che di default è un po' altino

-zef-:
Prova anche in setup a modificare il parametro Serial.setTimeout() che di default è un po' altino

EUREKA!! :smiley: :smiley: :smiley: :smiley:

Una cosa così semplice eppure fondamentale! Errore mio a non aver controllato le funzioni relative alla seriale, unica cosa che posso dire in mia discolpa è che non avrei mai pensato ad una configurazione standard da 1s di attesa per la seriale.

Grazie a tutti per le risposte comunque costruttive e utili!! :wink:

Grazie specialmente a fabpolli e docdoc per le ottimizzazioni suggerite!

Permettimi di dissentire dal tuo eureka, e ti spiego il perché... quello che a te sembra la soluzione è un semplice workaround per mettere una pezza ad un malfunzionamento di base del software come ti ha descritto nel dettaglio @docdoc
E' chiaro che se esiste tale parametro c'è un perché ma non è quello di terminare la lettura al posto tuo, abbassando il timeout hai aggirato l'ostacolo, resta il fatto che c'è un bug di fondo, è un po' come usare il watchdog per sbloccare programmi che si bloccano per colpa di come sono scritti e non per quello per cui è nato (ovvero sopperire a anomalie non predicibili), per carità sembra che sia la soluzione ma in realtà non lo è, è solo un modo per "mettere la testa sotto la sabbia".
Per carità, non prenderla come un attacco personale rivolto a te, se funziona come vuoi e non ti interessa migliorare il programma... a posto felice tu e anche io ma la vera soluzione l'ha indicata @docdoc ovvero inseriere il terminatore (Es. CR+LF come fa il monito seriale se non differentemente configurato), leggere due caratteri ed uscire ecc..
E te ne dico anche un'altra, se il programma è destianto a girare per molto tempo l'uso della classe String ti porterà con buone probabilità a sperimentare blocchi imprevisti e casuali da cui difficilmente ne uscirai fuori (come più volte indicato sul forum) se non eliminando l'uso di tale classe e tornare a leggere un byte alla volta, che vada in un interno o in un array di char poco importa.
Questo pippone che ho scritto che potrebbe sembrare innutile, forse arrogante, non è rivolto a te (come già detto) ma pittosto per far capire a eventuali altri frequentatori che in futuro potrebbero imbattersi in questo topic segnato come risolto, che la "soluzione" adottata non è proprio la migliore, anzi.

scusate se m'intrometto per dire la mia, sempre non per critica ma per, se possibile, aggiunger un concetto in più.
Premetto che sottoscrivo in toto quanto scritto da fabpolli e docdoc.
Come detto da fabpolli il ridurre il time out è un accrocchio per aggirare il problema e non una vera e buona soluzione; generalmente questo tipo di soluzioni, gli accrocchi, vengono prese quando a "realizzazione concettuale" del progetto non si verifica/valuta bene su cosa si sta lavorando e che device si utilizzeranno...per fare il classico degli esempi:
PC <-> PC classe String OK
PC<->Arduino classe String NO
Arduino <-> Arduino classe String NO.
Se c'è necessità di scambio dati tra due device posso utilizzare protocolli standard, che sono conosciuti, collaudati solidi etc...oppure devo farmi io il mio...e se devo farmi il mio cosa devo considerare:
ci saranno più info nella stessa stringa?...se si come li distinguo?
come capisco che la stringa inizia e finisce?
come capisco se la stringa letta corrisponde a quella passata?
etc etc
La buona pianificazione di un progetto a volte ti evita la completa riscrittura del codice prodotto...ne migliora la manutentabilità...e magari lo rende tutto od in parte riutilizzabile per altri progetti.
Scusate il pippotto ma necessitavo di un po' di formaggio...dalle mie parti c'è un detto "xe rivà queo del formajo" (traduzione concettuale e contestualizzata -> ha parlato quello che sa tutto)...ciao

fabpolli:
Permettimi di dissentire dal tuo eureka, e ti spiego il perché... quello che a te sembra la soluzione è un semplice workaround per mettere una pezza ad un malfunzionamento di base del software come ti ha descritto nel dettaglio @docdoc
E' chiaro che se esiste tale parametro c'è un perché ma non è quello di terminare la lettura al posto tuo, abbassando il timeout hai aggirato l'ostacolo, resta il fatto che c'è un bug di fondo, è un po' come usare il watchdog per sbloccare programmi che si bloccano per colpa di come sono scritti e non per quello per cui è nato (ovvero sopperire a anomalie non predicibili), per carità sembra che sia la soluzione ma in realtà non lo è, è solo un modo per "mettere la testa sotto la sabbia".
Per carità, non prenderla come un attacco personale rivolto a te, se funziona come vuoi e non ti interessa migliorare il programma... a posto felice tu e anche io ma la vera soluzione l'ha indicata @docdoc ovvero inseriere il terminatore (Es. CR+LF come fa il monito seriale se non differentemente configurato), leggere due caratteri ed uscire ecc..
E te ne dico anche un'altra, se il programma è destianto a girare per molto tempo l'uso della classe String ti porterà con buone probabilità a sperimentare blocchi imprevisti e casuali da cui difficilmente ne uscirai fuori (come più volte indicato sul forum) se non eliminando l'uso di tale classe e tornare a leggere un byte alla volta, che vada in un interno o in un array di char poco importa.
Questo pippone che ho scritto che potrebbe sembrare innutile, forse arrogante, non è rivolto a te (come già detto) ma pittosto per far capire a eventuali altri frequentatori che in futuro potrebbero imbattersi in questo topic segnato come risolto, che la "soluzione" adottata non è proprio la migliore, anzi.

Ho cantato vittoria troppo presto. Ora mi sorge un dubbio, elaborando un software a regola d'arte con l'aggiunta del terminatore ed evitando la classe String non si presenterebbe ugualmente il problema avendo il timeout di default a 1000ms e quindi una risposta "lenta"?

ORSO2001:
scusate se m'intrometto per dire la mia, sempre non per critica ma per, se possibile, aggiunger un concetto in più.
Premetto che sottoscrivo in toto quanto scritto da fabpolli e docdoc.
Come detto da fabpolli il ridurre il time out è un accrocchio per aggirare il problema e non una vera e buona soluzione; generalmente questo tipo di soluzioni, gli accrocchi, vengono prese quando a "realizzazione concettuale" del progetto non si verifica/valuta bene su cosa si sta lavorando e che device si utilizzeranno...per fare il classico degli esempi:
PC <-> PC classe String OK
PC<->Arduino classe String NO
Arduino <-> Arduino classe String NO.
Se c'è necessità di scambio dati tra due device posso utilizzare protocolli standard, che sono conosciuti, collaudati solidi etc...oppure devo farmi io il mio...e se devo farmi il mio cosa devo considerare:
ci saranno più info nella stessa stringa?...se si come li distinguo?
come capisco che la stringa inizia e finisce?
come capisco se la stringa letta corrisponde a quella passata?
etc etc
La buona pianificazione di un progetto a volte ti evita la completa riscrittura del codice prodotto...ne migliora la manutentabilità...e magari lo rende tutto od in parte riutilizzabile per altri progetti.
Scusate il pippotto ma necessitavo di un po' di formaggio...dalle mie parti c'è un detto "xe rivà queo del formajo" (traduzione concettuale e contestualizzata -> ha parlato quello che sa tutto)...ciao

Ogni parere e consiglio è sempre ben accetto. Continuerò a lavorarci su cercando di trovare la soluzione migliore.

Essendo nuovo del forum vi chiedo un consiglio OT, lascio il [Risolto] o lo tolgo visto che a quanto pare la discussione non è giunta alla fine?

mikelefree:
Ora mi sorge un dubbio, elaborando un software a regola d'arte con l'aggiunta del terminatore ed evitando la classe String non si presenterebbe ugualmente il problema avendo il timeout di default a 1000ms e quindi una risposta "lenta"?

il timeout è una azione che sblocca, dopo un certo tempo predefinito, da una situazione di possibile stallo di carattere eccezionale.
Nel comportamento normale, una operazione non dovrebbe mai andare in timeout.
Per cui, con un software scritto correttamente, non dovresti mai avere il problema, salvo casi particolari di carattere eccezionale e impredicibile, tipo un errore di comunicazione hardware che non fa arrivare i dati nel modo corretto.

Per il "risolto", si, direi che lo puoi togliere, così non "inganni" potenziali contributori che vedendolo potrebbero passare oltre senza soffermarsi :wink:

mikelefree:
Ho cantato vittoria troppo presto. Ora mi sorge un dubbio, elaborando un software a regola d'arte con l'aggiunta del terminatore ed evitando la classe String non si presenterebbe ugualmente il problema avendo il timeout di default a 1000ms e quindi una risposta "lenta"?
Essendo nuovo del forum vi chiedo un consiglio OT, lascio il [Risolto] o lo tolgo visto che a quanto pare la discussione non è giunta alla fine?

Come hai potuto notare usando il monitor seriale se metti un terminatore non hai rallentamenti questo perché la funzione che usavi si mette a leggere dati dulla seriale finché non incontra il terminatore (che per lei è CR+LF), non appena incontra tali caratteri ritorna il controllo al tuo programma che riprende il flusso.
Se tu invece scegli un altro terminatore dovai per forza implementare una lettura "carattere x carattere" dalla seriale finché non incontri il tuo terminatore, ad esempio il carattere virgola, pipe o ciò che più ti aggrada, quindi se con la Serial.available() riscontri la presenza di dati entri nell'if li con un while leggi i caratteri e li metti in un array di char (carattere per carattere) finché non incontri il tuo terminatore oppure se i caratteri sono finiti, a quel punto esci e fai ciò che devi, ovvero se hai incontrato il terminatore processi il dato, altrimenti scarti tutto in quanto la trasmissione non è completa.
Ma vista la tua casistica specifica eviterei tutta questa complessità in quanto tu trasmetti valori che rientrano nel singolo byte per volta, quindi con una semplice Serial.read() leggi il carattere che ti è arrivato lo casti come byte in modo da poterlo trattare direttamente (il byte accetta valori da 0 a 255, tu hai da 0 a 40 quindi perfettamente lecito), non aspetti null'altro ma processi il carattere ricevuto, al prossimo giro di loop se hai ancora caratteri nella seriale ripeti la cosa, sta a te lato Java inviare un singolo byte che contenga il tuo valore, ovvero non devi inviare caratteri ma valori (Cerca Arduino Serial.write e Serial.print per maggiori chiarimenti) a quel punto non hai protocollo da gestire, hai la massima velocità possibile e non usando classi complesse avrai possibilità vicine allo zero che il tuo programma si blocchi per problemi legati all'occupazione di memoria.

Concondo anche sul rimuovere (temporanemante perché alla fine ci arrivi alla soluzione, quasi garantito :wink: ) il risolto

Intanto grazie mille a tutti per il tempo e le importanti informazioni che mi avete dato.
Ecco la soluzione attuale che sembra funzioni alla perfezione. Questa volta non esulto prima di avere un vostro riscontro.

Lato PC (Java)
La variabile che ricevo per la velocità (speed) è Float, quindi la converto in numero intero arrotondando con la funzione Math.round, poi converto il valore intero in byte che a sua volta converto in char per trasmetterlo tramite la funzione arduino.serialWrite che accetta solo caratteri o stringhe.

int velocita = Math.round(speed);
byte velo = (byte) velocita;
char v = (char) velo;
arduino.serialWrite(v);

Lato Arduino
Come mi avete consigliato e grazie al range di valori numerici limitato che ricevo sfrutto ogni byte ricevuto da Arduino per attivare i relay secondo il codice che segue.

const int r1 = 7;
const int r2 = 6;
byte V;

void setup() {
  Serial.begin(9600);
  pinMode(r1, OUTPUT);
  pinMode(r2, OUTPUT);
  digitalWrite(r1, HIGH);
  digitalWrite(r2, HIGH);
}

void loop() {
  if (Serial.available() > 0) {
    V = Serial.read();
    if(V > 0 ){
      if(V > 12 ){
        digitalWrite(r1,HIGH);
        digitalWrite(r2,LOW);
      } else{
        if(V > 2 ){
          digitalWrite(r1,LOW);
          digitalWrite(r2,HIGH);
        } else {
          digitalWrite(r1,HIGH);
          digitalWrite(r2,HIGH);
        }
      }
    } 
  }
}

Ho lasciato così il timeout di default e la reattività non è cambiata di una virgola. Sembra abbia raggiunto la soluzione grazie al vostro prezioso aiuto. Ribadisco che prima di considerare chiusa la questione attendo vostro parere. :wink:

Mi sembra possa andare così ad una prima occhiata.
Mi resta un piccolo dubbio una curiosità diciamo, vista l'alta frequenza d'aggiornamento come fanno i relé a starti dietro??? Sono davvero relé o sono SSR?

Utilizzo classici moduli relé per Arduino che si attivano in diversi range di velocità come avrai intuito dal codice.
Il dato che ricevo è un numero che muta ad una velocità ragionevole con accelerazioni e decelerazioni non così repentine e quindi non ho questo tipo di problema.

Ah ok, grazie di aver risposto alla mia curiosità :slight_smile:

mikelefree:
Ho lasciato così il timeout di default e la reattività non è cambiata di una virgola. Sembra abbia raggiunto la soluzione grazie al vostro prezioso aiuto. Ribadisco che prima di considerare chiusa la questione attendo vostro parere. :wink:

A funzionare dovrebbe funzionare, comunque mi sento di darti qualche altro piccolo suggerimento secondario. Nella parte Java tu fai:

int velocita = Math.round(speed);
byte velo = (byte) velocita;
char v = (char) velo;
arduino.serialWrite(v);

Quindi dalla variabile "speed" (di che tipo è?) fai una conversione in "long" (la Math.round() fa questo) questo long lo salvi in una variabile "int", poi la "int" la casti in un'altra variabile byte, che a sua volta riconverti in char. Non è che tu per andare da Roma a Napoli passi per Vicenza e poi Reggio Calabria, spero.. :wink:
Quindi fai prima a passare per la A1, ossia ad esempio penso tu possa fare così (non conosco bene Java, ma ci provo, e comunque non sapendo il tipo della variabile "speed" devo mantenere la round):

arduino.serialWrite((byte)Math.round(speed));

Passiamo ad Arduino:

void loop() {
  if (Serial.available() > 0) {
    V = Serial.read();
    if(V > 0 ){
      if(V > 12 ){
        digitalWrite(r1,HIGH);
        digitalWrite(r2,LOW);
      } else{
        if(V > 2 ){
          digitalWrite(r1,LOW);
          digitalWrite(r2,HIGH);
        } else {
          digitalWrite(r1,HIGH);
          digitalWrite(r2,HIGH);
        }
      }
    } 
  }
}

La variabile "V" (ma abituati ad applicare la convenzione di usare nomi che iniziano con le minuscole, le maiuscole sono in genere per le costanti e le #define :wink: ) è di tipo "byte" ossia una "unsigned" di un byte che quindi avrà valori che vanno da 0 a 255, per cui la prima if() credo che si possa ignorare.

Per le condizioni, vanno bene gli if() ovviamente ma, per pura accademia, visto che i relè funzionano a logica inversa e sapendo che per la digitalWrite LOW e HIGH sono equivalenti a 0 e 1, o anche a false e true, credo che il tuo codice sia equivalente a cose come queste (l'operatore "!" serve per la logica inversa, il resto sono le condizioni in chiaro):

byte v;

void loop() {
  if (Serial.available() > 0) 
  {
    v = Serial.read();
    // Attiva il relè 1 se v è tra 3 e 12
    digitalWrite(r1,!(v >= 3 && v <= 12));
    // Attiva il relè 2 se v è maggiore di 12
    digitalWrite(r2,!(v > 12));
    // Entrambi disattivati se v è tra 0 e 2
  }
}

Hai ragionissima. Non conosco completamente Java e sono andato dritto e malamente verso la soluzione più semplice dal mio becero punto di vista. Purtroppo in formato byte non posso inviare la variabile tramite serialWrite quindi ho fatto così:

arduino.serialWrite((char)Math.round(speed));

Per quanto riguarda il lato Arduino, beh, studiare serve a qualcosa ed è chiaro che non è quello che ho fatto io. Da autodidatta molte volte si cercano e si impastano insieme soluzioni trovate qua e là e si sorvola su tanti dettagli che rendono tutto più chiaro, veloce, funzionale, leggero etc etc...

Questa soluzione è fantastica

digitalWrite(r1,!(v >= 3 && v <= 12));

È una delle pochissime volte in cui in un forum trovo gente così educata, disponibile e preparata.

Grazie di cuore!

mikelefree:

arduino.serialWrite((char)Math.round(speed));

E' giusto.
speed è un float, Math.round di un float ritorna un int che viene castato a char

mikelefree:
È una delle pochissime volte in cui in un forum trovo gente così educata, disponibile e preparata.

Grazie di cuore!

Personalmente apprezzo molto chi, come te, è ricettivo ai suggerimenti e s'impegna a migliorare.
In tal senso, giusto per darti fastidio ancora un pochetto :slight_smile: , mi è venuta in mente un'altra possibile ottimizzazione, vado ad illustrartela poi tu deciderai se vale la pena, se è applicabile al tuo progetto ecc.
Hai un PC dove gira il programma java, risorse in abbondanza ecc., hai reso molto performante la comunicazione verso Arduino, ok perfetto. Ma se l'invio continuo di byte serve solo a gestire i relé e null'altro e visto che i casi sono pochi invece di fare flood sulla seriale così di continuo perché non sposti anche lato PC la logica? Spiego meglio quello che intendo, con una variabile d'appoggio vai a verificare se il precedente valore inviato appartiene ad un caso differente rispetto al valore attuale, se è così invii il byte e aggiorni la variabile appoggio, altrimenti non invii nulla. In questo modo il tutto è ancora più ottimizzato a scapito di dover riportare modifiche (se mai ve ne fossero) sia lato Arduino, sia lato java. Che ne pensi?

Ottimo consiglio fabpolli, di fatto trasformi la comunicazione in "inerziale" :wink: aggiornando lo stato solo in caso di cambiamenti.

Però dato che per deformazione professionale cerco sempre di trovare eventuali "problemi", penso che sia il caso allora di accertarsi che lato Arduino sia stato recepito l'ultimo comando e quindi il relè sia stato attivato/disattivato. Per cui si dovrebbe implementare nel protocollo una risposta (il classico ACK) da parte di Arduino per confermare la ricezione del comando e l'attivazione del relé (ok, del solo pin corrispondente, ma tralasciamo eventuali problemi hardware del relè). E magari anche un comando di sync ossia ogni tanto chiedere ad Arduino lo stato dei pin dei relè.

Ma forse per lo scopo del nostro amico è tutto abbastanza superfluo, visto che non sta comandando (spero) una navicella spaziale o un reattore nucleare :slight_smile:

fabpolli:
Personalmente apprezzo molto chi, come te, è ricettivo ai suggerimenti e s'impegna a migliorare.
In tal senso, giusto per darti fastidio ancora un pochetto :slight_smile: , mi è venuta in mente un'altra possibile ottimizzazione, vado ad illustrartela poi tu deciderai se vale la pena, se è applicabile al tuo progetto ecc.
Hai un PC dove gira il programma java, risorse in abbondanza ecc., hai reso molto performante la comunicazione verso Arduino, ok perfetto. Ma se l'invio continuo di byte serve solo a gestire i relé e null'altro e visto che i casi sono pochi invece di fare flood sulla seriale così di continuo perché non sposti anche lato PC la logica? Spiego meglio quello che intendo, con una variabile d'appoggio vai a verificare se il precedente valore inviato appartiene ad un caso differente rispetto al valore attuale, se è così invii il byte e aggiorni la variabile appoggio, altrimenti non invii nulla. In questo modo il tutto è ancora più ottimizzato a scapito di dover riportare modifiche (se mai ve ne fossero) sia lato Arduino, sia lato java. Che ne pensi?

Non è una cattiva idea, e, a dire il vero, ci avevo pensato prima di trovare la soluzione ai tempi di riposta. Ora che la comunicazione è efficente come volevo, sto rendendo il software più completo e di conseguenza relativamente complesso e mi trovo decisamente meglio a lavorare su Arduino che si avvicina molto di più a linguaggi che conoscono.

docdoc:
Ottimo consiglio fabpolli, di fatto trasformi la comunicazione in "inerziale" :wink: aggiornando lo stato solo in caso di cambiamenti.

Però dato che per deformazione professionale cerco sempre di trovare eventuali "problemi", penso che sia il caso allora di accertarsi che lato Arduino sia stato recepito l'ultimo comando e quindi il relè sia stato attivato/disattivato. Per cui si dovrebbe implementare nel protocollo una risposta (il classico ACK) da parte di Arduino per confermare la ricezione del comando e l'attivazione del relé (ok, del solo pin corrispondente, ma tralasciamo eventuali problemi hardware del relè). E magari anche un comando di sync ossia ogni tanto chiedere ad Arduino lo stato dei pin dei relè.

Ma forse per lo scopo del nostro amico è tutto abbastanza superfluo, visto che non sta comandando (spero) una navicella spaziale o un reattore nucleare :slight_smile:

Dal punto di vista concettuale come non darvi ragione. Credo che, per l'appunto, non comandando dispositivi di vitale importanza possa già andare bene così anche perché, come detto sopra, con Arduino mi trovo molto più a mio agio nello sperimentare rispetto a Java.

Non me ne vogliate. :wink: