Skip to the content.

Torna all’indice>Simulatore con await/async

SIMULATORE PROTOCOLLO CSMA PER ARDUINO E TINKERCAD

csma

Utilizzo

1) Premere il tasto codice in alto a destra per aprire la finestra del codice 2) Premere il tasto monitor seriale in basso sulla finestra del codice 3) Selezionare dal menu a tendina in alto a destra il dispositivo di cui si vuole vsualizzare il codice 4) Avviare la simulazione con il tasto apposito in alto a destra 5) Osservare i messaggi di log nella finestra del monitor serial in basso a destra - Se si osserva il monitor dell’arduino N.1 si ritrova in output il log del ricevitore che informa sull’arrivo di un nuovo messaggio dati e l’nvio automatico di un nuovo messaggio di controllo di ack al trasmettitore. - Se si osserva il monitor dell’arduino N.2 si ritrova in output il log del trasmettitore N2 che informa sull’arrivo di un nuovo messaggio dati e l’nvito a premere il pulsante msgBtn per inviare un nuovo messaggio dati al ricevitore. - Se si osserva il monitor dell’arduino N.3 si ritrova in output il log del trasmettitore N3 che informa sull’arrivo di un nuovo messaggio dati e l’nvito a premere il pulsante msgBtn per inviare un nuovo messaggio dati al ricevitore.

Il log di un generico dispositivo informa sulla presenza sul BUS di eventuali messaggi e li mostra evidenziando i valori dei campi che lo costituiscono: Ciò accade a prescindere dal fatto che un messaggio sia destinato a quel dispositivo o meno, se è destinato a lui evidenzia l’evento con il log "msg destinato a me".

Descrizione schema

Il collegamento tra i due dispositivi in avviene attraverso un mezzo a BUS reale costituito da un collegamento di tipo half duplex realizzato dai 3 fili marroni che convergono sul nodo con etichetta BUS node. I 3 fili sono assimilabili ad un’unico filo che collega la porta 3 di ogni dispositivo. Tutti i dispositivi sono collegati elettricamente in parallelo con piedini corrispondenti (di porta e di GND) collegati insieme.

Il protocollo è il classico seriale RS232 realizzato in SW sulla stessa porta digitale di ogni dispositivo tramite la libreria SoftwareSerial. La libreria di base realizza un canale full duplex con due link su due porte, una di TX ed una di RX ma, in questo progetto, è stata adattata a funzionare in half duplex su un’unico link. La soluzione scelta per ottenere ciò è abbastanza semplice e consiste nel mantenere uno stato di default del link, che è quello di ricezione attivo quando nessuno trasmette, e nel commutare lo stato di quella stazione che deve trasmettere da RX a TX solo per il tempo necessario a completare la trasmissione del suo messaggio, subito dopo questa viene rimessa nella condizione di default.

Il collegamento a BUS half duplex realizzato tramite un unico filo e la libreria SoftwareSerial è effettivamente praticamente realizzabile sempre, però, tenendo conto che esso non è percorso da segnali bilanciati per cui non è adatto per lunghe distanze ma solo per collegamenti corti dell’ordine di qualche metro.

Messaggi di log

Premendo, in rapida succesione, entrambi i tasti Tx1 Btn o Tx2 Btn sui trasmettitori si abilita la trasmissione di un messaggio verso il ricevitore. Nella finestra di log si legge la catena di eventi innescata:

Log ad un trasmettitore

Premi il tasto per trasmettere un messaggio.
Attendo che si liberi il canale: 
Transmitted: 
(:1),(:2),(:1),(7:55),(:11),(c:99),(i:105),(a:97),(o:111),(p:112),(:0),(	:9),(Ë:203),
Sent data: Attendo ack o timeout: 
Received: 
(:2),(:1),(:1),(:129),(:5),
msg destinato a me
DA: 2
SA mio: 2
SI: 129
Ricevuto ack: 
Premi il tasto per trasmettere un messaggio.
Received: 
(:1),(:3),(:1),(7:55),(:11),(c:99),(i:105),(a:97),(o:111),(p:112),(:0),
msg NON destinato a me
Received: 
(:3),(:1),(:1),(:129),(:5),
msg NON destinato a me

Log al ricevitore

Premi il tasto per trasmettere un messaggio.
Received: 
(:1),(:2),(:1),(7:55),(:11),(c:99),(i:105),(a:97),(o:111),(p:112),(:0),
msg destinato a me
DA: 1
SA mio: 1
SI: 55
Ricevuto:ciaop
Transmitted: 
(:2),(:1),(:1),(:129),(:5),(­:173),(¯:175),
receiver ack sent 
Received: 
(:1),(:3),(:1),(7:55),(:11),(c:99),(i:105),(a:97),(o:111),(p:112),(:0),
msg destinato a me
DA: 1
SA mio: 1
SI: 55
Ricevuto:ciaop
Transmitted: 
(:3),(:1),(:1),(:129),(:5),(:144),(o:111),
receiver ack sent 

Un blink su un led per ogni scheda indica l’avvenuta trasmissione di un messaggio.

Formato dei messaggi

Il formato del messaggio è uniforme per messaggi di ack o dati ed è composto da 5 campi di lunghezza fissa di un byte più un sesto campo finale di lunghezza variabile che contiene il data da trasmettere.

Il messaggio da trasmettere è memorizzato in una coda di trasmissione di 64 byte che viene svuotata e riutilizzata per ogni nuovo messaggio da trasmettere.

Il messaggio ricevuto è memorizzato in una coda di ricezione di 64 byte che viene svuotata e riutilizzata per ogni nuovo messaggio ricevuto.

Nella presente implementazione del protocollo CSMA le due code coincidono per non si può ricevere un messaggio mentre se ne trasmette un’altro.

I campi del messaggio sono:

I messaggi dati hanno nel campo I il valore MSG (55) e hanno un BYTE_CNT variabile maggiore di 5. il log Arrived: (:2),(:1),(:1),(7:55),( :11),(c:99),(i:105),(a:97),(o:111),(p:112),(:0), pay: 6-11 indica che il TX 2 ha inviato al RX 1 del gruppo 1 un messaggio 55 (dati) che contiene la stringa “ciaop” ed avente 11 byte complessivi di cui 6 fissi.

I messaggi di controllo ack hanno nel campo I il valore ACK (129) e hanno un BYTE_CNT sempre di 5. Il log Arrived: (:1),(:2),(:1),(:129),(:5),Ricevuto ack: indica che il TX 1 ha inviato al RX 2 del gruppo 1 un messaggio 129 (ack) di 5 byte complessivi sempre fissi.

Calcolo CRC

Il protocollo di trasmisione utilizzato è di tipo confermato per cui prevede che un TX, contestualmente alla trasmissione di un messaggio, attivi un timer di trasmissione che ha lo scopo di misurare il tempo di arrivo del messaggio di conferma (ack) che deve essere inviato dal ricevitore per certificare la corretta ricezione del messaggio trasmesso. Allo scadere di un tempo massimo di attesa impostato sul timer, detto timeout, il trasmettitore è costretto ad effettuare la ritrasmissione del messaggio che non è stato ancora confermato, e farà ciò per tutte le volte in cui il timeout del timer di trasmissione scadrà, fino ad un numero massimo di tentativi prestabilito (solitamente tra 7 e 10).

La corretta ricezione è verificata dal ricevitore confrontando l’impronta (riassunto) del messaggio trasmesso (calcolata sul trasmettitore) con l’impronta del messaggio ricevuto calcolata sul ricevitore a partire dalla copia ricevuta. Se le due impronte coincidono la trasmissione è andata a buon fine e il ricevitore invia l’ack al trasmetitore, se non coincidono l’ack non viene inviato e si aspetta una nuova copia.

L’impronta calcolata dal trasmettitore viene inserita in un campo particolare del messaggio detto FCS (Frame Check Sequence) ed è genericamente calcolata con degli algoritmi di rilevazione e correzione di errore detti ciclici o CRC (Cyclic Redundancy Check). Normalmente il campo FCS è in coda al messaggio. Nel nostro caso è in coda e ha una lunghezza di 2 byte. La funzione che lo calcola è riportata di seguito:

//lo calcola dal primo byte del messaggio (header compreso)
uint16_t calcCRC(uint8_t u8length)
{
    unsigned int temp, temp2, flag;
    temp = 0xFFFF;
    for (unsigned char i = 0; i < u8length; i++)
    {
        temp = temp ^ u8Buffer[i];
        for (unsigned char j = 1; j <= 8; j++)
        {
            flag = temp & 0x0001;
            temp >>=1;
            if (flag)
                temp ^= 0xA001;
        }
    }
    // Reverse byte order.
    temp2 = temp >> 8;
    temp = (temp << 8) | temp2;
    temp &= 0xFFFF;
    // the returned value is already swapped
    // crcLo byte is first & crcHi byte is last
    return temp;
}

Fasi CSMA

Significa Carrier Sensing Multiple Access cioè protocollo di Accesso Multiplo con Ascolto della Portante (prima della trasmissione) Una stazione trasmittente:

Programmazione sequenziale

L’algoritmo è realizzato in maniera sequenziale nel tempo pianificando il blocco del flusso di esecuzione di parti del codice per un certo tempo di attesa. Il tempo di attesa potrebbe essere:

In entrambi i casi il flusso di esecuzione rimane in attesa della valutazione periodica (polling) di un flag che rappresenta l’accadere di un evento, il timeout di un timer preimpostato nel caso del Wait(t), l’arrivo di un messaggio nel caso del WaitUntil(pollingInput).

Eventi di input

Gli eventi di input rilevanti che potrebbero essere valutati durante lo svolgimento dell’algoritomo sono:

Thread di ricezione e trasmissione

In realtà, il polling degli eventi è fatto all’interno di un’unica funzione chiamata poll() che viene periodicamente richiamata all’interno del loop principale del sistema:

void loop() 
{
	poll(&rxobj);
	.............
}

La poll()modifica delle variabili globali con funzione di indicazione di evento dette flag (bandierine) che sono specifiche per ogni evento e che vengono valutate all’interno dei thread del sistema (i thread condividono le stesse variabili globali).

I thread rappresentano blocchi di codice la cui esecuzione si svolge in maniera parallela ed indipendente e, per uno stesso dispositivo fisico, sono essenzialmente due: un thread di ricezione ed un thread di trasmissione.

Arduino non possiede capacità di multithreading reale di tipo preemptive (competitvo con prerilascio) e realizzato in HW, ciònonostante è comunque possibile realizzare in SW una schedulazione di tipo non preemptive (collaborativa senza prerilascio) utilizzando i protothread.

Ricevitore

Protocollo CSMA in ricezione pseudocodice:

While(true){
	WaitUntil(dataFrameArrived());
	if(!duplicate()){ 
		deliver(frame) 
	}
	send(ack_frame);
}

Definizione del thread di ricezione messaggio

pt ptRcv;
int rcvThread(struct pt* pt) {
  PT_BEGIN(pt);
  // Loop forever
  while(true) {
	PT_WAIT_UNTIL(pt, dataFrameArrived());
	rcvEventCallback();
	sendAck();  
  }
  PT_END(pt);
}

Trasmettitore

Protocollo CSMA in trasmissione in pseudocodice:

N=1;
while(N <= max){
	waitUntil(channelFree()); 
	send(data_frame); 	
	waitUntil(ackOrTimeout()); 
	if(ack_received){ 
		exit while;
	}else{
		/* timeout scaduto: ritrasmissione*/
		/* timeout scaduto: ritrasmissione*/ 	
		t=random()*WNDW*2^n;
		wait(t);
		N=N+1;
	}
}
/* troppi tentativi: rinuncio a trasmettere*/	

Definizione del thread di trasmissione messaggio

pt ptSend;
int sendThread(struct pt* pt) {
  PT_BEGIN(pt);
  // Loop forever
  while(true) {
	 Serial.println("Premi il tasto per trasmettere un messaggio.");
	 PT_WAIT_UNTIL(pt, digitalRead(txBtn));
	 digitalWrite(led, HIGH);
	 PT_SLEEP(pt, 50);
	 digitalWrite(led, LOW);
	 PT_SLEEP(pt, 50);	
	 n = 0;  //azzera conteggio
	 while(n < MAXATTEMPTS){
		Serial.println("Attendo che si liberi il canale: ");
		PT_WAIT_UNTIL(pt, channelFree()); 
		sendData(&txobj);
		Serial.println("Attendo ack o timeout: ");
		PT_WAIT_UNTIL(pt, ackOrTimeout());
		if(ack_received()){
			n = MAXATTEMPTS;
			Serial.println("Ricevuto ack: ");
		}else{
			Serial.print("Timeout n: ");
			Serial.print(n);
			Serial.print(": ritrasmissione tra: ");
			tt = getBackoff();
			Serial.print((float) tt/1000);
			Serial.println(" secondi");
			/* timeout scaduto: ritrasmissione*/
			PT_SLEEP(pt, tt);
			Serial.print(" Ritrasmesso.");
			n++;			
		}
	 }
  }
  PT_END(pt);
}

Backoff a finestra variabile

L’algoritmo ALOHA proposto utilizza un backoff a finestra variabile in cui la finestra di trasmissione aumenta esponenzialmente (con la potenza del 2) ad ogni incremento del numero di tentativi andati a vuoto. La finestra di trasmissione è detta anche finestra di contesa e rappresenta l’intervallo temporale in cui ogni stazione calcola individualmente un tempo random di accesso a partire dal quale cominciare a trasmettere.

long getBackoff(){
	return random(0, WNDW*pow(2,n));
}

Codice completo di tutte le stazioni collegate al BUS

Il codice di seguito è comune a tutte le stazioni e possiede entrambe le funzioni di trasmissione e ricezione. Tutte le stazioni sono dotate di indirizzo univoco che deve essere quindi diverso per tutte e deve essere impostato nel blocco setup() del dispositivo modificando il parametro attuale mysa della funzione init()..

Ricevitore

init(mySerial, dirPin, 1, 1, 0); // port485, txpin, mysa, mygroup4, speed=9600

Trasmettitore 1

init(mySerial, dirPin, 2, 1, 0); // port485, txpin, mysa, mygroup4, speed=9600

Trasmettitore 2

init(mySerial, dirPin, 3, 1, 0); // port485, txpin, mysa, mygroup4, speed=9600

Codice completo di tutte le stazioni collegate al BUS

Torna all’indice>Simulatore con await/async