Comunicazione Master/multislave

Buongiorno a tutti,
dopo le varie piccole esperienze con Arduino, ho deciso di lanciarmi in qualcosa di decisamente più complesso.
vagando per il web, ho trovato la tesina di esame di Massari che ha creato una serie di schede interconnesse tra loro tramite rs485.
tra le schede realizzate, ne esiste una utilizzando appunto un modulo arduino.
Prendendo quindi spunto dal suo codice, ho iniziato a creare tre scetch: un master e due slave.
dal codice di massari, poichè già testati, ho preso le varie funzioni per la creazione della trama da inviare, invio trama, lettura trama, decodifica trama e elaborazione pacchetto.
mi rendo perfettamente conto che il progetto è molto complicato per quelle che sono le mie conoscenze/competenze ma...
avrei bisogno di un input per risolvere il primo scoglio con il quale mi sto scontrando.
non allego le varie funzioni che, come ho detto, fanno il loro dovere egregiamente per non appesantire il post.

poichè il master non avrà nessuna entrata/uscita collegate (almeno per il momento), pensavo di interrogare i vari slave in pooling

void loop() {

  byte indirizzo;
  byte tipo=12;
  byte dato1=0;
  byte dato2=0;
  int i;
  int infinito;
  
  while (infinito=1) {  //creo un ciclo infinito
    for (i=2;i<num_slave-1;i++) {
      if (!Serial.available()) {
        code_data_to_send(i,tipo,0,0);
      }
      else {
        serialEvent();  
      }
    }
  }
}

la funzione code_data_to_send invia correttamente le trame (verificato tramite monitor seriale sullo slave) e i due slave, dopo aver eseguito ogni funzione, "reagiscono" correttamente.

guardando invece il monitor seriale del master, mi rendo conto che:
al primo invio entrambi gli slave inviano l'ack ma dal secondo invio del master, benchè entrambi abbiano ricevuto, decodificato e elaborato il pacchetto, solo il primo slave invia l'ack!

presumo che il problema sia legato ai tempi di esecuzione e che in maniera casuale, si verifica l'evento appena descritto.

mi potreste indicare la giusta direzione per far si che, il master, dopo aver inviato il suo comando, aspetti un tempo X entro il quale lo slave deve rispondere?

spero di aver descritto correttamente il mio problema.

grazie in anticipo

Ciao...così senza sapere come funziona il tuo sketch mi è venuto questo :

void loop() {

  byte indirizzo;
  byte tipo = 12;
  byte dato1 = 0;
  byte dato2 = 0;

  int infinito;

  byte ID = 2;
  boolean waitForAnswer = false;
  boolean dataIncoming = false;
  unsigned long waitTime = 0;

  while (infinito = 1) {
    if (!waitForAnswer) {
      code_data_to_send(ID, tipo, 0, 0);
      waitForAnswer = true;
      waitTime = millis();
    }
    else {
      if (millis() - waitTime < 2000 || dataIncoming ) {
        if (Serial.available()) {
          dataIncoming = true;
          serialEvent(); // CI VUOLE QUALCHE COSA CHE AZZERI IL TEMPO SE SERIALEVENT ESEGUITA!!!
        }
        else if (!Serial.available()) {
          dataIncoming = false;
        }
      }
      else if (millis() - waitTime > 2000 && !dataIncoming) {
        ID++;
        if (ID < num_slave - 1 ) {
          ID = 2;
        }
        waitForAnswer = false;
      }
    }
  }

come scritto nella riga commentata si dovrebbe trovare il modo (nella serialEvent ?) di escludere il conteggio del tempo altrimenti come minimo le trasmissioni vengono eseguite ogni 2 secondi

Grazie per la risposta.
Ho testato il codice che mi suggerisci. ho ridotto il time-out a 500ms e credo di poterlo ridurre ancora. in ogni caso, a meno che, lo slave interrogato non vada in stallo o non sia ko, se riceve una qualsiasi trama valida, invierà la sua risposta.
C'è un solo problema. ho aggiunto un paio di serial.print utili per il debug ed ho notato che ID va oltre num_slave -1. La variabile ID l'ho dichiarata long come la variabile num_slave

Grazie e buona serata

La notte si dice porti consiglio.
ho modificato il codice in questo modo

void loop() {

  byte indirizzo;
  byte tipo = 12;
  byte dato1 = 0;
  byte dato2 = 0;

  int infinito;

  byte ID = 2;
  boolean waitForAnswer = false;
  boolean dataIncoming = false;
  unsigned long waitTime = 0;

  while (infinito = 1) {
    if (!waitForAnswer) {
      Serial.print("invio a ");
      Serial.println(ID);
      code_data_to_send(ID, tipo, 0, 0);
      waitForAnswer = true;
      waitTime = millis();
    }
    else {
      if (millis() - waitTime < 500 || dataIncoming ) {
        if (Serial.available()) {
          dataIncoming = true;
          serialEvent(); // CI VUOLE QUALCHE COSA CHE AZZERI IL TEMPO SE SERIALEVENT ESEGUITA!!!
        }
        else if (!Serial.available()) {
          dataIncoming = false;
        }
      }
      else if (millis() - waitTime > 500 && !dataIncoming) {  //se il timeout è finito e non ci sono dati in ingresso
        if (ID < num_slave + 1) {
        ID++;
        } else {
            ID = 2;
           }
        waitForAnswer = false;
      }
    }
  }
}

finche num_slave è inferiore a 9, funziona tutto correttamente. Se la pongo uguale a 10, mi conta uno slave in piu...

Per il momento penso di poter proseguire.
grazie ancora

Sempre alle prese con questa parte di codice...
ho collegato due slave, nei quali ho caricato il medesimo sorgente modificando opportunamente l'indirizzo.
il codice del master nella parte che, a mio avviso ha un problema è il seguente

void loop() {

  byte indirizzo;
  byte tipo = 12;
  byte dato1 = 0;
  byte dato2 = 0;

  int infinito;

  byte ID = 2;
  boolean waitForAnswer = false;
  boolean dataIncoming = false;
  unsigned long waitTime = 0;

  while (infinito = 1) {
    if (!waitForAnswer) {
      code_data_to_send(ID, tipo, 0, 0);
      waitForAnswer = true;
      waitTime = millis();
    }
    else {
      if (millis() - waitTime < timeout || dataIncoming ) { //se non è finito il timeout o ci sono dati in ingresso
        if (Serial.available()) {
          dataIncoming = true;
          serialEvent(); // CI VUOLE QUALCHE COSA CHE AZZERI IL TEMPO SE SERIALEVENT ESEGUITA!!!
        }
        else if (!Serial.available()) {
          dataIncoming = false;
        }
      }
      else if (millis() - waitTime > timeout && !dataIncoming) {  //se il timeout è finito e non ci sono dati in ingresso
        if (ID < num_slave + 1) {
          Serial.println(ID);
        ID++;
        } else {
            ID = 2;
           }
        waitForAnswer = false;
      }
    }
  }
}

Lo slave con indirizzo 3, funziona correttamente.
Quello con indirizzo 2 invece, intanto invia due volte l'ack.
Inoltre, nella seguente funzione

void packet_data_elaboration() {
  if(adress_reciver_ric == my_adr) {
    switch (adress_sender_ric) {
      case 2:
        switch (type_ric) {
          case 6:
          Serial.print("Ack ricevuto da ");
          Serial.println(adress_sender_ric);
          break ;

          case 33:
          if (data1_ric == 1) {
            digitalWrite(Verde, HIGH);
          }
          if (data1_ric == 0) {
            digitalWrite(Verde, LOW);
          }
          break ;
        }
      case 3:
      switch (type_ric) {
          case 6:
          Serial.print("Ack ricevuto da ");
          Serial.println(adress_sender_ric);
          break ;

          case 33:
          if (data1_ric == 1) {
            digitalWrite(Rosso, HIGH);
          }
          if (data1_ric == 0) {
            digitalWrite(Rosso, LOW);
          }
          break ;
        }
    }
  }
}

mentre lo slave 3 accende il led Rosso come voluto, lo slave 2 accende sia il led Verde che quello Rosso!

Allego il codice del master e dello slave.

Grazie e buona serata

test0302_master.ino (10.5 KB)

test0302_slave.ino (10.1 KB)

qual'è il problema che riscontri?

Buongiorno Orso.
Innanzitutto, il master invia due richieste al modulo con indirizzo 2 il quale, se non ha variazioni di stato da comunicare, risponde con due ack.
Inoltre mentre lo slave con indirizzo 3, qualora venisse premuto il pulsante, fa accendere correttamente il led corrispondente sul master, lo slave con indirizzo 2, fa accendere contemporaneamente entrambi i led.

forse non varrà granchè ma, nella funzione void packet_data_elaboration(), nei due case 2 e 3 inserisci il break alla fine...quelli che hai aggiunto internamente, dato che non sono labeled, penso ti facciano solo uscire dal case secondario all'interno del case principale ...e quindi ogni volta entrambi i case principali 2 e 3 vengono "valutati"...prova e fammi sapere

mettendo il break nel primo switch, esce dal ciclo e non analizza l'ack inviato dallo slave 3.
ho provato a modificare il loop mettendo ID = 3 e modificando quindi gli indirizzi degli slave in 3 e 4.
lo slave 3 invia l'ack e, se il pulsante viene premuto, il led corrispondente si accende.
in compenso, il master, non invia la trama allo slave 4 il quale, ovviamente, non risponde. nel funzione void packet_data _elaboration degli slave, per il momento, ho fatto in modo che, se la trama è corretta ma indirizzata ad altro slave, si abbia un breve lampeggio del led.

Ciao...giusto per essere sicuri...io intendevo nella funzione del master inserire i break in questo modo:

void packet_data_elaboration() {
  if (adress_reciver_ric == my_adr) {
    switch (adress_sender_ric) {
      case 2:
        switch (type_ric) {
          case 6:
            Serial.print("Ack ricevuto da ");
            Serial.println(adress_sender_ric);
            break ;

          case 33:
            if (data1_ric == 1) {
              digitalWrite(Verde, HIGH);
            }
            if (data1_ric == 0) {
              digitalWrite(Verde, LOW);
            }
            break ;
        }
        break;  // AGGIUNTO
      case 3:
        switch (type_ric) {
          case 6:
            Serial.print("Ack ricevuto da ");
            Serial.println(adress_sender_ric);
            break ;

          case 33:
            if (data1_ric == 1) {
              digitalWrite(Rosso, HIGH);
            }
            if (data1_ric == 0) {
              digitalWrite(Rosso, LOW);
            }
            break ;
        }
        break;  // AGGIUNTO
    }
  }
}

nello slave invece, non se che senso abbia il seguente switch, farei così:

  if ((adress_reciver_ric == my_adr) && (adress_sender_ric == master)) {
    //Serial.println(type_ric);
    switch (type_ric) {
      case 12:
        if (MemPulsanti == Pulsanti) {
          code_data_to_send(1, 6, 0, 0) ;
        }
        else {
          code_data_to_send(1, 33, Pulsanti, 0);
        }
        MemPulsanti = Pulsanti;
        break; // AGGIUNTO
    }
  } else {
    digitalWrite(Led, HIGH);
    delay(50);
    digitalWrite(Led, LOW);
    ;
    //code_data_to_send(1,6,0,0);  //send ack
  }

intanto vorrei non tenessi presente del mio precedente messaggio. stavo provando a modificarlo ma si è spento il pc...

mettendo il break nel primo switch, esce dal ciclo e non analizza l'ack inviato dallo slave 3.

Ho effettuato le seguenti modifiche
posto ID = 3
modificato la riga if (ID < num_slave + 1) in if (ID < num_slave + 2)
nella funzione packet_data_elaboration modificati i due indirizzi.
ovviamente modificato anche gli indirizzi degli slave
e il problema rimane ma sullo slave 4.

nella funzione packet_data_elaboration lo slave che non funziona correttamente è sempre il primo!

leggo quanto hai appena scritto

si, il break lo avevo messo in quella posizione.
circa l'uso di uno switch sugli slave, è solo per poter aggiungere successivamente altre funzioni nel caso in cui il messaggio ricevuto non sia 12.

ad ogni modo, ho aggiunto i due break negli slave e non cambia nulla.
inoltre, ti confermo che il problema è relativo sempre al primo slave della funzione packet_data_elaboration del master. ovvero...se nel primo case metto l'indirizzo 3, sarà lo slave 3 ad inviare due volte l'ack e far accendere entrambi i led. lo slave 4 funzionerà correttamente. se invece, il primo case della funzione prevede l'analisi dello slave 4, il problema si "sposta" su quest'ultimo!

prova così:
aggiungi una variabile globale boolean "dataSent" che inizializzi a "false"; nella funzione send_data(), come ultima istruzione, la metti a "true";nella funzione void packet_data_elaboration() metti un "&dataSent" come parametro in ingresso....nel primo if() di questa funzione aggiungi un "&& dataSent" e come ultima istruzione di questa funzione imposti a "dataSent = false"...in questo modo la funzione la esegui veramente solo una volta dopo che hai inviato i dati...prova se ti va e fammi sapere

ORSO2001:
prova così:
nella funzione void packet_data_elaboration() metti un "&dataSent" come parametro in ingresso....

orso, purtroppo, sono alle prime armi...
cosa intendi?

void packet_data_elaboration(dataSent) {

in tal caso, quando chiamo la funzione, non devo passare il parametro?

scusa...davo per scontato che conoscessi la differenza tra passaggio per valore o per reference di una variabile...allora prova questo come master e dimmi se funziona...in pratica quando invii una richiesta alzi un flag che abilita l'elaborazione dei dati ricevuti...una volta elaborati, una sola volta, resetta questa abilitazione e necessiti di un secondo invio per poter elaborare di nuovo i dati...etc...

master.ino (11.3 KB)

Buongiorno Orso.

intanto ci tengo a ringraziarti per la pazienza e per la disponibilità.
Ho potuto testare il codice che mi hai postato e, per le prove che ho potuto fare, direi che funziona tutto correttamente.
Come ho potuto scrivere nella presentazione, "non mi piace la pappa pronta" !!
Questo weekend saro' fuori per lavoro quindi cerchero' di capire il funzionamento della modifica che mi hai suggerito non appena possibile.

Grazie ancora!

Sto cercando di capire il codice postato da Orso, ma mi mancano purtroppo le basi.
nello specifico, non capisco

void packet_data_elaboration(boolean &_dataSent)

qualcuno sa consigliarmi un testo sul quale studiare questo genere di sintassi?

Grazie

p.s.: per i moderatori: poichè intendo proseguire questo progetto, per eventuali (dico eventuali perchè sono un ottimista nato..) ulteriori problemi, uso lo stesso tread o ne apro di nuovi?
grazie!

DaddyLee:
p.s.: per i moderatori: poichè intendo proseguire questo progetto, per eventuali (dico eventuali perchè sono un ottimista nato..) ulteriori problemi, uso lo stesso tread o ne apro di nuovi?
grazie!

SE l'argomento è lo stesso, puoi tranquillamente continuare su questo thread :slight_smile:

Guglielmo

ciao DaddyLee,

provo a spiegarla "semplice"; tu sai che ad una funzione puoi passare valori/varibili di qualsiasi tipo scrivendo per esempio:

void miaFunzione(int a, int b) {
  a += b;
}

in questo caso la funzione "miaFunzione" accetta/richiede 2 variabili int "a" e "b" ; al suo interno esegue la somma di "a" e "b"...quindi potresti usarla in questo modo:

int c = 5;
int d = 6;
miaFunzione(c,d);

al suo interno la funzione eseguirà "a+=b"...dove "a" prende il valore (attenzione ho scritto valore) di "c" e "b" prende il valore di "d"...quindi eseguirai una semplice somma tra 5 e 6 assegnado ad "a" il nuovo valore 11...le due variabili passare "c" e "d" continueranno a mantenere i rispettivi valori 5 e 6.

se invece scrivessi la funzione:

void miaFunzione2(int &a, int b) {
  a += b;
}

quel "&a" vuol dire che di quella variabile non passerai il valore ma il suo riferimento (indirizzo di memoria) che ti renderà possibile manipolare, all'interno della funzione, la variabile passata; di fatto "a" diventa un "alias" della variabile passata.
quindi se esegui questo:

int c = 5;
int d = 6;
miaFunzione2(c,d);

e dopo vai a stampare il valore di "c" questo non sarà più 5 ma 11.

quindi per come ti ho modificato la funzione tu gli passi, per riferimento, una boolean che sarà verificata al suo interno e nel caso modificata.

Spiegazione semplice e chiarissima!!!

dataSent, viene dichiarata come "false".
Al termine della routine di invio dati, il suo stato viene invece posto a 1 per indicare appunto che l'invio dei dati è avvenuto. A questo punto, nella chiamata alla routine per l'elaborazione del pacchetto, "passo" la variabile ma facendo in modo che il suo stato possa essere modificato per le successive parti del programma. In uscita dalla routine di elaborazione del pacchetto, dataSent viene posta nuovamente a 0, ovvero al suo stato dichiarato inizialmente.

Nel tuo esempio, in uscita dalla prima funzione, a, b, c e d, varranno rispettivamente 11, 6, 5 e 6. Mentre nel secondo caso, il valore di c sarà 11.
A questo punto chiedo:

  • dichiaro inizialmente la variabile dataSent come boolean ma senza assegnare un valore
  • nella funzione di invio dati, assegno inizialmente alla mia variabile il valore "false"
  • al termine dell'invio, cambio lo stato della variabile in "true"
  • nella routine di elaborazione del pacchetto, metto il valore dataSent in and con l'indirizzo del ricevente
  • eseguo la routine e, in uscita, rimetto il valore della variabile a 0

Dopo la tua prima spiegazione, avevo proceduto in questo modo ma avevo ricevuto dei messaggi di errore gia in fase di compilazione.
Avendo dichiarato la mia variabile fuori dal loop, questa non è globale e quindi a "disposizione" di ogni successiva routine??

Chiedo scusa per queste domande che potrebbero essere scontate e banali. Uno dei miei piu grossi difetti è che sono cocciuto...e non mi piace la pappa pronta!
Vorrei quindi capire per essere più autonomo...
Ripeto quindi la richiesta/domanda di prima: quali testi, cartacei o online, mi consigli di studiare?

Ancora grazie per la spiegazione e per la pazienza!
Buona domenica