Comunicazione seriale

Come abbiamo visto nel paragrafo dedicato possiamo utilizzare un cavo USB collegato alla porta seriale del computer per caricare su Arduino i programmi contenti le istruzioni per il suo funzionamento.

Possiamo utilizzare questo canale di comunicazione anche per:

Inviare

I dati sono trasmessi in byte e possono dunque assumere valori compresi tra 0 e 255 espressi in tre diversi formati: integer, floating point o ASCII.

Se inviamo un solo valore per volta possiamo utilizzare tutti e tre i formati mentre se vogliamo trasmettere più valori all'interno di un singolo pacchetto di dati possiamo farlo solo alltraverso il formato ASCII.

Singolo valore (int)

Colleghiamo Arduino e predisponiamo un sistema per testare la comunicazione tra i devices facendo accendere e spegnere un led (attuatore) collegato al pin 3 PWM con una resistenza da 220 Ohm.

image not found

Singolo valore (float)

Se vogliamo scrivere sulla porta seriale una cifra decimale (float) dobbiamo necessariamente convertirla in un numero intero (int) compreso tra 0 e 255 nel software che scrive sulla porta per poi trasformarla nuovamente in valore decimale nel software che legge dalla porta.

Singolo valore (ASCII)

Possiamo ottimizzare la trasmissione di dati attraveso la porta seriale convertendo i valori numerici in caratteri ASCII:

image not found

In Max possiamo realizzare direttamente questa conversione con l'oggetto atoi. Scarica il patch.

image not found

Mentre in SuperCollider dobbiamo effettuare una duplice conversione:

a = 23;         // int
a = a.asString; // int --> string
a = a.ascii;    // string --> ASCII					

In questa conversione ad ogni singolo numero intero compreso tra 0 e 9 ne corrisponde un altro come possiamo leggere nella tabella.

0 = 48

1 = 49

2 = 50

Fino a quando non si supera la cifra singola (da 0 a 9) non c'è alcun problema nella trasmissione dei dati, ma siccome i valori trasmissibili sono compresi tra 0 e 255 avremo numeri composti da due e tre cifre.

12 = 49 50 (1-2)

31 = 51 49 (3-1)

130 = 49 51 48 (1-3-0)

In questi casi per ogni singolo valore numerico ne saranno trasmessi due (per quelli di due cifre) o tre (per quelli di tre cifre) in modo sequenziale (uno dopo l'altro) generando una stringa ininterrotta di numeri:

.. 48 51 57 55 53 54 49 52 51 48 50 53 49..

rendendone di fatto impossibile la riconversione. Verifichiamolo.

Per ovviare a questo inconveniente possiamo aggiungere il valore 13 alla fine di ogni lista che in formato ASCII significa \cr (a capo) separando in questo modo i singoli valori composti da 2 o tre cifre.

12          31         130
1    2 \cr   3   1 \cr   1   3   0  \cr
49  50  13  51  49  13  49  51  48   13

Inoltre dobbiamo sostituire nel codice di Arduino Serial.read() con Serial.parseInt() che legge in maniera corretta questo formato e converte automaticamente il valore in int.

int val;     
void setup()
{
 Serial.begin(9600); 
 pinMode(3,OUTPUT);
}
void loop() {
  if (Serial.available()) {      
             val = Serial.parseInt(); // cerca il primo int 
                                      // separato da 13 
             Serial.print("convertito: ");
             Serial.println(val); 
             analogWrite(3,val);
  };   
}

// DOPO 1000ms CHE NON RICEVE DATA RIPORTA '0' 
// UNA PRIMA SOLUZIONE (C) E' INVIARE CONTINUAMENTE UN DATO
// METTENDO UN METRO IN MAX, SPEGNENDO IL METRO SI RESETTA
// IL SISTEMA

Per ragioni tecniche Serial.parseInt() scrive automaticamente il valore 0 (Timeout) dopo un secondo dall’ultimo dato ricevuto. Se non vogliamo che questo avvenga nello scorrere del flusso di valori abbiamo due possibilità:

Per verificare:

Se invece volessimo utilizzare SuperCollider il codice è il seguente.

s.boot;
SerialPort.devices;
SerialPort.closeAll;
p = SerialPort.new("/dev/cu.usbmodem14201", 9600, crtscts:true);


a = rand(255).postln; // int
a = a.asString;       // int --> string
a = a.ascii;          // string --> ASCII


// Routine che invia continuamente bit alla porta seriale

(
a = 150.asString.ascii ++ 13; // converte, inserisce \cr (13) e crea un Int8Array

r = Routine({
            inf.do({
                    p.putAll(a);  // Scrive sulla porta
                    0.05.wait     // è necessario un downsampling
                    })
             }).reset.play;
)

a = 0.asString.ascii ++ 13;
a = 20.asString.ascii ++ 13;
a = 100.asString.ascii ++ 13;
a = 255.asString.ascii ++ 13;

(
a = rand(255).asString.ascii ++ 13;
a.postln;
)

r.stop;
p.close;

Se apriamo il monitor seriale in Arduino si scollega automaticamente la porta in SuperCollider quindi possiamo monitonarne il funzionamento solo attraverso la prototipazione.

Più valori (ASCII)

image not found

Se volessimo inviare più valori contemporaneamente a uno o più dispositivi attuatori come i led nella configurazione illustrata nell'immagine possiamo impacchettarli in una lista o in un Array nel software che utilizziamo per controllarli (in questo caso Max o SuperCollider).

Se invece vogliamo inviare valori da SuperCollider:

s.boot;
SerialPort.devices;
SerialPort.closeAll;
p = SerialPort.new("/dev/cu.usbmodem14201", 9600, crtscts:true);

// Routine che invia continuamente bit alla porta seriale
(
a = [0,0,0,0,0,0].asString.ascii ++ 13; // Converte, inserisce \cr (13) e crea un Int8Array

r = Routine({
             inf.do({
                     p.putAll(a);  // Scrive sulla porta
                     0.05.wait     // è necessario un downsampling
	                 })
             }).reset.play;
)

r.stop;d.stop;a = [0,0,0,0,0,0].asString.ascii ++ 13; // Stop e reset

p.close;

a = [  0,  0,  0,  0,  0,  0].asString.ascii ++ 13; // Crea un Array con \cr
a = [  0, 20, 40, 60, 80,100].asString.ascii ++ 13;
a = [255,  0,255,  0,255,  0].asString.ascii ++ 13;
a = [  0,255,  0,255,  0,255].asString.ascii ++ 13;
a = [255,180,120, 80, 40,  0].asString.ascii ++ 13;
a = Array.rand(6,0,255).asString.ascii ++ 13;	

Se apriamo il monitor seriale in Arduino si scollega automaticamente la porta in SuperCollider quindi possiamo monitonarne il funzionamento solo attraverso la prototipazione.

Ricevere

Se vogliamo ricevere dati da uno o più sensori analogici possiamo collegarlo a uno dei sei pins dedicati di Arduino.

Questo tipo di sensori in genere ha tre piedini o tre pins.

Dovremo collegre il filo positivo e negativo (rosso e nero) a due di essi per fornire al sensore la corrente necessaria al funzionamento, mentre sul terzo ci sarà una tensione che cambierà dinamicamente al variare del parametro fisico monitorato fornendoci i valori da mappare (debitamente riscalati) su uno o più parametri di un algoritmo di sintesi o di elaborazione del suono in Max o SuperCollider.

Singolo sensore analogico

Nei prossimi esempi utilizzeremo un sensore di prossimità (4-30cm) a raggi infrarossi Sharp che necessita di una tensione di alimentazione compresa tra 4.5V e 5.5V (non dovremo mettere resistenze nel circuito), fornisce un segnale di uscita compreso tra 3.1V (4cm) e 0.3V (30cm) e consuma circa 33mA di corrente.

image not found

Verifichiamo dal datasheet del sensore l'ordine in cui sono posizionati i collegamenti.

Esempio wa wa (Max)

In questo esempio il sensore controlla dinamicamente la frequenza di taglio di un filtro passa basso.

Ricordiamo che l'audio file e il patch devono essere nella stessa cartella.

image not found

Esempio scratch (SuperCollider)

In questo esempio il sensore controlla dinamicamente un puntatore che legge un audio file.

SerialPort.devices;
p = SerialPort("/dev/tty.usbmodem14201", 9600, crtscts:true);       // Connette la
                                                                    // porta Seriale
s.boot;                                                             // Boot Server Audio
b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav"); // Crea un Buffer e
                                                                    // carica un File
// ------------------------------------ Synthdef e Synth
(                                    
SynthDef(\scrat,{arg pos,smooTh;
                 Out.ar(0,
                        BufRd.ar(b.numChannels,
                                 b.bufnum,
                                 VarLag.ar(K2A.ar(pos),smooTh,warp:\lin)
                                 );
                       )
}).add;

{a = Synth(\scrat,[\pos,0,\smooTh,0.2])}.defer(0.3);
)

// ------------------------------------ Legge, converte, riscala e invia i valori

(
c = b.numFrames;    // lunghezza in frames del Buffer
r = Routine({
             var byte, str, val;
             inf.do{|i|
                    if(p.read == 10,
                       {str = "";
                        while({byte = p.read; byte !=13 },
                              {str = str++byte.asAscii}
							  );
                        val = str.asInteger;
                        val = val.linlin(0,1000,0,c); // riscala lineare
                        val.postln;                   // stampa
                        a.set(\pos,val);              // invia al Synth
		              });
                   };
             }).reset.play;
)

r.stop;     // ferma la Routine
s.freeAll;  // distrugge Synth e Buffer dal Server
p.close;    // chiude la porta seriale	

Più sensori analogici