4 Arduino Master Slave in RS485

Ciao,

innanzitutto provo a rispondere alla domanda di peppe e pietro. Mi pare giusto dato che mi sono inserito di "prepotenza" nel thread.

Ovviamente la mia risposta potrebbe essere discussa insieme proprio perchè non sono sicuro che l'architettura che ho in mente sia quella corretta.

Come ho già detto, a differenza di peppe, io non immagino una logica di controllo sugli slaves perchè questo mi costringerebbe ad aggiornarne il firmware in caso di modifiche. Preferirei avere una logica centralizzata con gli slaves che fanno solo da sensori/attuatori.

Ciò premesso penso che il sistema dovrebbe funzionare in questo modo.

1 - il master chiede ad ogni slave il suo stato (per esempio lo stato dei pulsanti ad esso collegati oppure l'ultimo valore di temperatura letto)
2 - conoscendo lo stato di uno slave invia un comando ad uno o più slaves (per esempio sullo slave 2 è stato premuto il pulsante 1 e comanda allo slave 3 di chiudere il relè 2 ed allo slave 4 di chiudere il relè 3)

Dal punto di vista del protocollo seriale, pensando a qualcosa di simile a quello indicato all'inizio di questo thread, il master chiede lo stato ad ogni slave inviando un pacchetto con un determinato codice comando (per esempio RichiestaStatoInput = 1). Lo slave risponde con un pacchetto con codice comando RispostaStatoInput = 2. In questo caso deve comunicare un valore e lo fa utilizzando i bytes del pacchetto predisposti per eventuali dati. Il master deve quindi comandare la chiusura dei relè e lo fa inviando al relativo slave un pacchetto con codice comando ImpostaStatoOutput = 3 utilizzando ancora i bytes di dati per comunicare il valore di Stato che vuole impostare.

Ovviamente si tratta solo di una traccia, il protocollo andrebbe approfondito nel dettaglio anche in funzione di quello che si deve implementare.

Ciao.
Vittorio.

E ritorno anche al mio dubbio...

50 ms sono una eternità, il tempo medio per la transazione master/slave è 4-5 ms, il timeout mediamente si imposta tra 10-20 ms ed è una condizione di errore

certo, con 4-5 ms i conti tornano... ho però qualche dubbio. In questo tempo, il master deve inviare la richiesta e lo slave deve rispondere; pensi che sia fattibile con Arduino? Appena ho un po' di tempo ci provo.

E poi non è che il master debba terminare tutto il ciclo di interrogazioni quando deve spedire un comando.

questo mi piace :slight_smile: In pratica appena riceve una variazione da uno slave esegue la logica di controllo per verificare se deve comandare qualcosa ed invia i comandi necessari.

Il controllo degli slave sarà fatto nei momenti di idle del sistema.

questo invece non lo capisco. Mi pare, come dice anche astrobeed, che il sistema interroghi sempre gli slaves, altrimenti come fa a sapere se c'e' stata una variazione o meno. E comunque immagino che la logica di controllo venga eseguita in un tempo brevissimo; credo che il master passi la maggior parte del tempo ad interrogare gli slaves.

Ciao.

Allora dopo tutto questo parlare peché non ci uniamo in u n unico gruppo e iniziamo
a mettere le basi per un "simple protocol" da usare per un Arduino Master che
collegato con due slave esegue un controllo dello stato pin dei due slave in scansione e in continuazione e appena riceve uno stato High dei pin input 2,3,4 di ogni slave esegue un digitalWrite High dei suoi Pin Output 2,3,4 5,6,7 8,9,10

Slave1 pin2,3,4 ---- segue Master pin2,3,4
Slave2 pin2,3,4 ---- segue Master pin5,6,7
Slave3 pin2,3,4 ---- segue Master pin8,9,10

Da dove iniziamo??

@vittorio68:
la mia affermazione sul controllo degli slave nei periodi di idle era fatta relativamente ad un master che dovesse anche interagire con l'utente, lo avevo precisato che appunto in modalità "interattiva" dedicasse la sua attenzione al "padrone" piuttosto che alle sue periferiche.
Ovviamente questo dipende da come fai la lettura degli slave, se usi uno scheduler tipo il mio leOS e l'interrogazione la fai in background, puoi sempre ricevere dati senza fermare il loop principale del codice.

pietro78:
Allora dopo tutto questo parlare peché non ci uniamo in u n unico gruppo e iniziamo
a mettere le basi per un "simple protocol" da usare per un Arduino Master che
collegato con due slave esegue un controllo dello stato pin dei due slave in scansione e in continuazione e appena riceve uno stato High dei pin input 2,3,4 di ogni slave esegue un digitalWrite High dei suoi Pin Output 2,3,4 5,6,7 8,9,10

Slave1 pin2,3,4 ---- segue Master pin2,3,4
Slave2 pin2,3,4 ---- segue Master pin5,6,7
Slave3 pin2,3,4 ---- segue Master pin8,9,10

Da dove iniziamo??

Pietro...l'idea mi piace...scrivere qualcosa noi da mettere a disposizione di tutti (visto che si trova davvero poco) quello da cui partire sarebbe inventarci il protocollo

rispondendo a Vittorio...
la mia idea di avere un codice in esecuzione all'interno degli slave è solo per i comandi delle luci e per il sensore gas sostanzialmente...ciò per avere un'affidabilità maggiore e un tempo di risposta immediato, la sirena del sensore gas in questo caso è allocata all'interno dello slave in questione...nel caso di una fuga di gas..lo slave dovrebbe rilevare il gas, inviare il comando di accensione della sirena al master e il master dovrebbe restituirlo allo slave dicendo di attivare la sirena...mettiamo caso ci fosse un errore di comunicazione o qualsiasi altro problema per cui il segnale non riesce ad arrivare al master...la sirena non si attiva...è solo una questione di affidabilità, praticamente lo slave mi esegue il codice inviando solo un feedback al master alla prima interrogazione...comunque se vogliamo fare un codice insieme sono a disposizione...cominciamo a buttare giù qualcosa...

Dovreste creare una serie di livelli di sicurezza. Se l'operazione è banale, come ad esempio accendere un faretto quando inizia a fare buio, potete usare un livello basso, spedendo al master la lettura del sensore ed attendendo che il master spedisca il comando.
Ma se il livello di sicurezza è alto, lo slave è abilitato ad invertire la sequenza: operare in proprio attivando una certa cosa ed informando il master del problema.

Allora dopo tutto questo parlare peché non ci uniamo in u n unico gruppo e iniziamo
a mettere le basi per un "simple protocol"

Qualche tempo fa avevo fatto qualche prova con un paio di Arduino Mega (scelti sopratutto per la maggiore capacità di memoria...) ed un paio di SN75716BP per creare una RS485. Appena ho un po' di tempo rimetto insieme gli sketch di prova che avevo fatto e comincio a postarli.

Ciao.
Vittorio.

leo72:
Dovreste creare una serie di livelli di sicurezza. Se l'operazione è banale, come ad esempio accendere un faretto quando inizia a fare buio, potete usare un livello basso, spedendo al master la lettura del sensore ed attendendo che il master spedisca il comando.
Ma se il livello di sicurezza è alto, lo slave è abilitato ad invertire la sequenza: operare in proprio attivando una certa cosa ed informando il master del problema.

Cosa consigli???

Una semplice tabella con i livelli di sicurezza.
Ad ogni operazione che deve eseguire lo slave assegnate un livello. Se il livello è base, fate la solita trafila: spedire al master il dato, attendere la decisione del master. Questo va bene, ad esempio, per la lettura di una fotoresistenza.
Se il livello è superiore (allarme fuga gas), potete decidere se:

  1. inviare il dato al master e mettere un timeout alla sua risposta, per evitare che una caduta dello stesso o un problema sulla linea impedisca allo slave di prendere una decisione. Oppure
  2. attivare una contromisura (es.: sirena) in maniera indipendente ed informare il master dell'accaduto.

Il master potrà avvisare l'utente con un sms/email/led, a seconda di come è stato programmato.

Visti i pochi ritagli di tempo da parte mia per iniziare a stilare questa famosa tabella di protocollo bisogna pur cominciare da qualche base ...

Ora vista la mia scarsa propensione a programmare un protocollo mi affido a persone più competenti e più afferrate nel campo e soprattutto mi affido all'unione delle menti geniali allo scopo di iniziare a lavorare su qualcosa....
Vi prego buttate giù un qualcosa che sia anche solo un'esempio basilare per iniziare a far camminare i neuroni...Grazie e comunque senza obblighi... :slight_smile: :slight_smile: :stuck_out_tongue: :stuck_out_tongue: :wink: :wink:

Ragazzi, vi prego di scusare la mia assenza a causa di svariati impegni lavorativi....dunque creiamo una base di partenza...e partiamo dal master....mettendo per ipotesi che noi vogliamo creare un protocollo semplice utilizziamo 6 byte... e quindi

byte data [6]
// il primo byte è il classico NULL, inviato per primo come meno significativo 0x00
byte Address; // il secondo byte è quello di indirizzo del dispositivo che invia
byte SlaveAddress; // il terzo byte è quello di indirizzo del dispositivo che riceve
byte Data; // il quarto byte richiama la funzione da eseguire...nel caso di invio di lettura digitale entrambi i byte di data riportanto il valore di 10 bit
byte Data2; // il quinto byte specifica cosa eseguire
byte Checksum; // l'ultimo byte invia un feedback al master indicando che l'operazione è stato eseguita

correggetemi se scrivo castronerie...(Leo72 aiutami tu che vedo che sei il più esperto qui...)
poi....

#define Address  xx; //impostiamo l'indirizzo del dispositivo 
const int PinRS485Enable = 02; //impostiamo l'abilitazione del trasferimento dati sul pin 2 ( i pin 0 e 1 sono utilizzati per la comunciazione seriale)
const int ErrorComLed = 03; //reputo molto utile l'utilizzo di un led che ci segnali eventuali errori di comunicazione

poi sul void setup() scriveremo:

pinMode(PinRS485Enable, OUTPUT); // settiamo il pin di controllo della trasmissione come output
pinMode(ErrorComLed, OUTPUT); // settiamo il pin del led di errore come output

Come inizio non è male...anzi almeno sappiamo da dove partire ma adesso viene il bello...

Come costruire i byte da te citati...o meglio come costruire la tabellina di riferimento per le funzioni...aspettiamo...

byte data [6]
// il primo byte è il classico NULL, inviato per primo come meno significativo 0x00
byte Address; // il secondo byte è quello di indirizzo del dispositivo che invia
byte SlaveAddress; // il terzo byte è quello di indirizzo del dispositivo che riceve
byte Data; // il quarto byte richiama la funzione da eseguire...nel caso di invio di lettura digitale entrambi i byte di data riportanto il valore di 10 bit
byte Data2; // il quinto byte specifica cosa eseguire
byte Checksum; // l'ultimo byte invia un feedback al master indicando che l'operazione è stato eseguita

Ok dai, quindi siccome vogliamo fare un SimpleProtocol propongo di fare questa tabellina in un file.h da includere nel sorgente, il problema reale che ho riscontrato nei sorgenti che si trovano in giro nel web è appunto il non capire a che serve quella determinata stringa che magari sta solamente dichiarando cosa deve fare quel comando....quindi propongo di semplificare al massimo e lasciare all'utente che va a modificare il codice sorgente soltanto decidere cosa eseguire su quel pin ecc....che ne dici?...un sorta di brutta copia del Wire...

dimenticavo....ovviamente nel void setup()

Serial.begin(9600); //inizializzo la porta seriale e la imposto a 9600 baud...

Concordo pienamente nel compilare un file.h per i comandi a livello "open" per lasciare la possibilità a chiunque di personalizzare anche il sorgente...
Per la porta seriale pensavo chiaramente di poter usare almeno per il Master la classe SoftwareSerial per il bus485 e quindi usa re una porta virtuale e lasciare chiaramente la Com sui pin 0 e 1 per la comunicazione con PC e monitoraggio delle serialPrint ... che pensi si può fare ??

E intanto andiamo avanti in attesa che si introduca sempre più gente...+ne siamo e meglio è ....

Ottima idea quella della SoftwareSerial... quindi procediamo in questo senso...abbiamo bisogno di altra gente che si aggreghi però...qui di lavoro ce n'è....ma il mio tempo è molto limitato purtroppo...quindi quando posso butto giù qualche idea....fattevi avanti anche voi...con pezzi di codice...a pezzo a pezzo buttiamo giù qualcosa di concreto...

Come promesso ho scritto un paio di sketch, uno per il master e uno per lo slave per fare qualche prova. Si tratta di poche righe di codice tanto per cominciare a partire. In particolare, a mio avviso bisogna rivedere il byte di start (attualmente un solo byte sempre 255) per fare in modo che la sequenza di start non possa presentarsi all'interno del pacchetto.

Il codice simula l'interrogazione di 16 slaves (numerati da 1 a 16 perchè 0 è il master...). Quando interroga uno slave, attende la risposta per 5 secondi, se non la riceve invia nuovamente la richiesta allo stesso slave. Questo è utile solo per questo semplice sketch, il protocollo finale dovrà funzionare in maniera diversa (tipo isolare lo slave che non risponde...).

Il protocollo è simile a quello già discusso. Probabilmente un solo byte di dati è poco ma, ancora una volta, è solo per provare.

La funzione RS485Write disabilita la ricezione della seriale e attiva il pin che abilita la trasmissione. Attende quindi la fine della trasmissione per ripristinare il funzionamento in ricezione.

Ho usato un Arduino Mega sfruttando la Serial2. Lo slave invece utilizza Serial1. Non c'e' alcun motivo per fare questo. Nel mio caso dipende semplicemente dal fatto che ho usato due shields autocostruiti in tempi diversi sui quali avevo utilizzato due seriali diverse. Basta modificare lo sketch facendo attenzione a modificare anche i flag utilizzati nella RS485Write.

Ecco il codice del master. Scrivo quello dello slave in un post successivo.

// Protocollo
// 0 - start = 255
// 1 - sender address
// 2 - destination address
// 3 - command
// 4 - data
// 5 - checksum

int pinEnableWrite = 5;
byte idxSlave;
byte idxRead;
byte command = 1;
bool nextRequest;
unsigned long lastRequest;
unsigned long last1;

void setup() 
{
	Serial.begin(9600);
	Serial2.begin(115200);

	pinMode(pinEnableWrite, OUTPUT);     
	digitalWrite(pinEnableWrite, LOW);
	
	idxSlave = 1;
	idxRead = 0;
	
	nextRequest = true;
}

void loop() 
{
	if (nextRequest)
	{
                            if (idxSlave == 1) last1 = millis();
                
		//Serial.print("Richiesta slave: ");
		//Serial.println(idxSlave, DEC);
		
		byte buffer[6];
		buffer[0] = 255;
		buffer[1] = 0;
		buffer[2] = idxSlave;
		buffer[3] = command;
		buffer[4] = 0;
		buffer[5] = buffer[1] ^ buffer[2] ^ buffer[3] ^ buffer[4];
		
		RS485Write(buffer, 6);
		
		nextRequest = false;
		lastRequest = millis();
	}
	else
	{
		unsigned long Now = millis();
		if (Now - lastRequest > 5000)
		{
			nextRequest = true;
		}
		else
		{
			if (Serial2.available())
			{
				byte B = Serial2.read();
				
				//if (idxRead == 0) Serial.print("Risposta: ");
				
				//Serial.print(B, DEC);
				//Serial.print(" ");
				
				idxRead++;
				if (idxRead == 6) 
				{
					//Serial.println(" ");
					
					idxRead = 0;
					
					idxSlave++;
					if (idxSlave == 17) 
					{
						Now = millis() - last1;
						Serial.print("Durata ciclo = ");
						Serial.println(Now, DEC);
						idxSlave = 1;
					}

					nextRequest = true;
				}
			}
		}
	}
	
	delay(1);
}

void RS485Write(byte* packet, byte len)
{
	UCSR2B &= ~(1<<RXEN0);  //disable RX   
	UCSR2A = UCSR2A |(1 << TXC0);  
	Serial2.flush();

	digitalWrite(pinEnableWrite, HIGH);
	Serial2.write(packet, len);

	while (!(UCSR2A & (1 << TXC0))); // attendi fine trasm.

	digitalWrite(pinEnableWrite, LOW);
	UCSR2B |= (1<<RXEN0);   //enable RX
}

ecco invece il codice dello slave.

Non avendo 16 arduino, semplicemente il codice risponde sempre a prescindere dal destination address. Tanto per customizzare la risposta, il byte di dati è uguale all'ID slave moltiplicato per due.

Il byte di checksum viene calcolato (sia nel master che nello slave) ma non viene controllato.

Il codice però attende un byte 255 per memorizzare i byte ricevuti.

// Protocollo
// 0 - sender address
// 1 - destination address
// 2 - command
// 3 - data
// 4 - checksum

int pinEnableWrite = 5;
byte command = 2;
byte idxSlave;
byte idxRead;
byte buffer[6];

void setup() 
{
	Serial.begin(9600);
	Serial1.begin(115200);

	pinMode(pinEnableWrite, OUTPUT);     
	digitalWrite(pinEnableWrite, LOW);
	
	idxRead = 0;
}

void loop() 
{
	if (Serial1.available())
	{
		buffer[idxRead] = Serial1.read();
		//if (idxRead == 0) Serial.print("Richiesta: ");
		//Serial.print(buffer[idxRead], DEC);
		//Serial.print(" ");
		if (idxRead > 0  ||  buffer[0] == 255) idxRead++;
	}
	
	if (idxRead == 6)
	{
		//Serial.println(" ");
		//delay(10);
		
		idxSlave = buffer[2];
		buffer[0] = 255;
		buffer[1] = idxSlave;
		buffer[2] = 0;
		buffer[3] = command;
		buffer[4] = idxSlave * 2;
		buffer[5] = buffer[1] ^ buffer[2] ^ buffer[3] ^ buffer[4];
		
		RS485Write(buffer, 6);
		
		idxRead = 0;
	}

	delay(1);
}

void RS485Write(byte* packet, byte len)
{
	UCSR1B &= ~(1<<RXEN0);  //disable RX   
	UCSR1A = UCSR1A |(1 << TXC0);  
	Serial1.flush();

	digitalWrite(pinEnableWrite, HIGH);
	Serial1.write(packet, len);

	while (!(UCSR1A & (1 << TXC0))); // attendi fine trasm.

	digitalWrite(pinEnableWrite, LOW);
	UCSR1B |= (1<<RXEN0);   //enable RX
}

5 secondi mi sembrano un'eternità..
Ed in quei 5 secondi che cosa farebbe il master? resterebbe in attesa della risposta dallo slave? Ma così potrebbe ignorare eventuali segnali di allarme degli altri slave.

no, no, aspetta... come ho scritto, questa cosa l'ho inserita solo in questo sketch di prova e soltanto perchè all'inizio ho fatto qualche prova collegando lo slave solo in un secondo momento. Fatto così, il master mi interrogava sempre lo stesso slave fino a che non lo collegavo.

Gli sketch che ho postato non vogliono essere un esempio di funzionamento reale, ma solo un punto di partenza per vedere due arduino che parlano tra loro secondo una architettura master/slave, cioè con uno che interroga e l'altro che risponde.

Avrai notato che non viene nemmeno gestito un timeout entro il quale lo slave interrogato deve rispondere.

Come ho scritto, il protocollo reale deve provvedere per esempio ad isolare lo slave che non risponde ossia deve evitare di interrogarlo ulteriormente. In particolare, il comportamento potrebbe essere il seguente:

  • quando uno slave non risponde entro pochissimi ms, l'evento viene ignorato per un certo numero di volte (per esempio 3) per gestire il caso in cui lo slave è momentaneamente occupato (se questo è possibile).
  • se non risponde per 3 tentativi consecutivi, viene messo in stato "DOWN" ed il master passa ad interrogarlo solo ad intervalli molto più lunghi, per esempio ogni 10 secondi (per implementare una sorta di plug&play) oppure su richiesta di una supervisione (per gestire il collegamento di un nuovo slave in maniera manuale).