Skip to the content.

Torna all’indice >versione in Python

SERIALE ADUINO

L’interfaccia seriale di Arduino possiede due registri dati, uno di ricezione (registro RX) da accedere in lettura ed uno di trasmissione (registro TX) da accedere in scrittura. Il primo è collegato alla porta RX della seriale, il secondo, ovviamente, a quella TX. Entrambi hanno la dimensione di un byte, quindi pochissimo spazio a disposizione, ma entrambi posseggono un registro di stato ed eventi di segnalazione (interrupt) che permettono di gestire prontamente la scrittura o la lettura di nuovi dati sui registri facendo in modo che non vengano mai persi.

La gestione dei registri è affidata a due ISR, una ISR di ricezione, _rx_complete_irq(), ed una ISR di trasmissione, _tx_udr_empty_irq(void).

Ciascun registro dati e collegato ad una coda realizzata in SW e della lunghezza di 64 byte: una coda di ricezione ed una coda di trasmissione, detti anche buffer di ricezione e buffer di trasmissione.

Il lavoro delle due ISR in risposta agli eventi esterni è:

Ma anche l’applicazione genera degli eventi di lettura e scrittura che possono accadere in momenti qualsiasi asincroni con quelli di una ricezione o una trasmissione sulla seriale. In RX che succede se arriva un dato e l’applicazione non è pronta a leggerlo? O si conserva o va perso. In TX che succede se l’applicazione decide di trasmettere un dato ma il canale è occupato da una trasmissione pendente? O riprova più tardi (deve fare un polling) oppure si perde. Le code sono la soluzione a tutto ciò e servono proprio da memoria tampone per gestire queste asincronie:

Quindi, a regime sul buffer di ricezione c’è un equilibrio dinamico (variabile nel tempo) tra byte scritti dalla ISR di RX e byte letti dalla applicazione, che comunque non deve attendere troppo a leggere il buffer pena il suo overflow (64 byte si riempiono quasi subito).

Allo stesso modo, a regime sul buffer di trasmissione c’è un equilibrio dinamico (variabile nel tempo) tra byte letti dalla ISR di TX e byte scritti dalla applicazione, che comunque non deve scrivere troppi dati troppo spesso pena l’overflow del buffer di trasmissione.

La funzione Serial.overflow() verifica se si è verificato un overflow del buffer RX. La chiamata a questa funzione cancella il flag di overflow, il che significa che le chiamate successive restituiranno false a meno che un altro byte di dati non sia stato ricevuto e scartato nel frattempo.

I due buffer, di trasmissione e ricezione, vengono realizzati con una struttura dati (spesso implementata con array) detta coda circolare che fa in modo che si possano aggiornare dinamicamente le posizioni degli indicatori di testa e di coda senza dover eseguire spostamenti del contenuto delle celle nel buffer.

Adesso è possibile capire con più consapevolezza il significato delle istruzioni di Arduino che sovraintendono alla gestione della seriale.

Serial.available() restituisce il numero di byte non ancora letti (dalla applicazione) che ci sono sulla coda di ricezione. Chiaramente il suo numero può variare da 0 a 64. Il polling nel loop() di questa funzione, testando che sia maggiore di zero, ci dice se, in un certo momento, sono arrivati nuovi byte dalla seriale. Esempio di polling della Serial.available():

void loop(){
	if(Serial.available() > 0 ){  	//anche while va bene!
		String instr = 	Serial.readString();
		// byte inbyte = Serial.read ();
		…………………………………….
		…………………………………….
	}
}

Una volta scoperto che ci sono dati disponibili sul buffer di ricezione, l’applicazione può pensare di prelevarli, senza però perdere troppo tempo, pena l’overflow del buffer. Per questo motivo il polling della Serial.available() conviene che venga sempre fatto alla massima velocità consentita dalla CPU, cioè direttamente dentro il loop senza schedulazioni.

Due funzioni di lettura sono particolarmente utili allo scopo della rimozione dei dati dal buffer di ricezione:

Si noti che le funzioni precedenti, ad eccezione di read() e peek(), eseguono una operazione di parsing, cioè il processo di scorrere i caratteri di un flusso di dati continuo (stream, stringa, ecc.) alla ricerca dell’informazione voluta.

La scrittura sulla seriale non ha particolari restrizioni, può avvenire in qualunque parte del codice (nel loop(), nel setup(), non nelle ISR però) e in qualunque momento e può essere svolta anche in maniera schedulata o arbitrariamente occasionale. L’unico vincolo è che non si scriva troppo spesso un numero eccessivo di dati.

In genere viene eseguita con queste funzioni:

In ogni caso è necessario inizializzare la seriale impostando la sua velocità che deve essere la stessa sia nel dispositivo in TX che in quello in RX. L’impostazione non è automatica e deve essere fatta dal programmatore (assenza di negoziazione automatica). L’inizializzazione in genere si esegue nel setup(). Le velocità possibili vanno da 9600 baud a 115200 baud.

void setup ()
{
	pinMode(LED, OUTPUT);
	Serial.begin(9600);
}  	// end of setup

E’ possibile anche definire una ISR di ricezione customizzata definendo il codice della funzione Serial.serialEvent() per Arduino Uno

void serialEvent(){
	//statements
}

per Arduino Mega ci sono anche altre ISR per altre le porte seriali a disposizione:

void serialEvent1(){
	//statements
}

void serialEvent2(){
	//statements
}

void serialEvent3(){
	//statements
}

Per semplificare la vita agli sviluppatori, alcune ISR sono fornite direttamente dalle librerie di Arduino.

Nell’esempio seguente viene adoperata la ISR serialEvent()che risponde all’evento arrivo di un nuovo carattere nella seriale e che può essere riempita con codice personalizzabile.

Nell’esempio la ISR viene utilizzata per creare una stringa, cioè una parola, unendo i singoli caratteri che man mano arrivano. La stringa è pronta, e quindi il flag viene attivato, quando arriva un carattere di fine linea “\n”.

Quando il loop principale si accorge del flag attivo ristampa in uscita la parola ottenuta tramite l’istruzione Serial.println.

String inputString = "";  // a String to hold incoming data

boolean volatile stringComplete = false;  // whether the string is complete
byte byteRead;
void setup() {
	Serial.begin(9600);
	inputString.reserve(200);
}

void loop(){
	// il polling su una variabile è meno lento di quello su una porta seriale
	if (stringComplete) {
		stringComplete = false; //reset della bandierina (flag)
		Serial.println(inputString);
		// clear the string:
		inputString = "";
	}
}

// ISR (Interrupt Service Routine) che a seguito dell’arrivo di un dato. Crea  // una stringa da una sequenza di caratteri.
void serialEvent(){
	while (Serial.available()){
		// legge il nuovo byte:
		char inChar = (char) Serial.read();
		// lo aggiunge a inputString:
		inputString += inChar;
		// se il carattere in arrivo è un newline,
		// imposta un flag in modo che il main loop
		// possa utilizzarlo per rilevare l’evento di completamento di una stringa:
		if (inChar == '\n') {
			stringComplete = true;
		}
	}
}

Simulazione su Arduino con Tinkercad: https://www.tinkercad.com/embed/lERKq5P0sXm?editbtn=1

Torna all’indice >versione in Python