di Andrea Valle
Pubblichiamo la seconda parte dell’articolo di Andrea Valle “Come costruire un serraturofono a borracce programmabile e interattivo. Guida teorico-pratica”. Per la prima parte cliccare qui; per la seconda parte cliccare qui]
- Lo strato fisico-computazionale
Si tratta ora di controllare le serrature. Come si vede in Figura 1
Fig. 1
ognuna di esse prevede un polo positivo e uno negativo (i cavi rosso e nero). I cavi sono raccolti in una basetta, in cui i positivi sono ponticellati e alimentati da una corrente a 12V (cavo fucsia). La soluzione più semplice per l’alimentazione consiste nell’usare un alimentatore da computer fisso: basso costo, molti watt, diversi voltaggi disponibili (tra cui 12V). Come si nota, in ogni serratura il circuito è perciò interrotto e il polo negativo è invece connesso a una elettronica di controllo (i cavi azzurri), che è rappresentata in Figura 2, dove sono raccordate tutte le serrature (12, come si evince dalla numerazione).
Fig. 2
Il circuito di controllo è mutuato (paro paro) da quello proposto da O’Sullivan e Igoe in Figura 3.
Fig. 3
Le componenti sono tre: resistenza (1KOhm), transistor (TIP 120) e diodo (snubber diode). Alla M si sostituisca una S per serratura (il cavo connesso al cosiddetto “collettore” del transistor). Il transistor è il centro del processo e fa variare la corrente che arriva alla serratura attraverso il segnale che arriva dal microcontrollore alla sua cosiddetta “base”, mentre il cosiddetto “emettitore” è collegato a massa. La resistenza semplicemente abbatte opportunamente il segnale di provenienza da Arduino in modo utile per la base del transistor, mentre il diodo protegge dalla corrente di ritorno (indotta) dal motore/serratura. Dal punto di vista logico, il circuito delle serrature si compone così di un polo positivo e uno negativo che viene controllato da una sorta di variatore di corrente. Arduino è in grado di generare segnali PWM (Pulse Width Modulation): ovvero, segnali che alternano tra un massimo (fisso) e zero con un duty cycle variabile (cioè il rapporto tra le durate di massimo e 0). Se si considera un certo intervallo di tempo, la quantità di corrente totale varierà in funzione del duty cycle, anche se il valore di picco è sempre costante. Il circuito, anche se digitale in termini di valori prodotti, può perciò essere usato per controllare un comportamento “continuo” in un motore. Nel nostro caso, la situazione è ancora più semplice, poiché si tratta di attivare discretamente, a mo’ di interruttore, il solenoide: o passa corrente (picco), e il circuito è chiuso, o non passa (zero), ed è aperto. In Figura 4 si vedono i due blocchi da 6 circuiti (quelli di Figura 3) montati sfasati su una basetta.
Il fascio di cavi colorati porta a Arduino. Su quest’ultimo la connessione è piuttosto semplice: infatti i cavi colorati sono semplicemente connessi con i pin di uscita di Arduino, ognuno dotato di un identificativo numerico. Anche Arduino può essere alimentato dallo stesso alimentatore da PC (e via 12V).
Si tratta a questo punto di programmare Arduino in modo tale che:
– sulla base di un messaggio ricevuto dal calcolatore
– invii i segnali al pin selezionato (chiudendo o aprendo il circuito)
Il codice di base che uso, con un certo insieme di variazioni sul tema, è il seguente (che deriva da un esempio che mi ha spedito anni fa Fredrik Olofsson, grazie redFrik). Esso assume che Arduino sia collegato alla porta seriale (USB) del calcolatore, attraverso la quale sono inviati in tempo reale messaggi (dal software di controllo, vedi dopo) di 4 byte, i quali specificano dove (pin) e come (valore) Arduino deve generare un certo segnale. Commento il codice al suo interno, attraverso la doppia barra //. Il codice viene compilato e caricato su Arduino attraverso l’ambiente di programmazione dedicato.
// protocollo di comunicazione: 253 mm nn 255con
// ogni messaggio deve includere 4 byte
// primo e ultimo sono fissi (253, 255) = inizio e fine messaggio, per sicurezza
// dunque, primo e ultimo byte sono ridondanti, ma evitano messaggi malformati
// mm rappresenta il pin
// nn il valore (escursione 8 bit: 0-255), per noi sempre o 255 o 0
// 1. inizializzazione. Una volta che il programma è caricato su Arduino
// e Arduino riceve alimentazione, esegue il programma.
// Si parte con l’esecuzione impostando le variabili…
byte index= 0;
byte value = 0;
byte port = 1;
// …Arduno esegue poi la funzione setup una sola volta
void setup() {
// indica a Arduino di ascoltare la porta seriale (USB)
// 115200 è il tasso di trasferimento e deve essere lo stesso
// sul calcolatore che invia messaggi
Serial.begin(115200);
// imposta i pin come output (possono funzionare come input)
// qui i pin sono i 15 PWM di un Arduino Mega 2560
// Non tutti i pin supportano PWM: nel nostro caso non sarebbe necessario,
// basterebbe un pin digitale, ma è utile per i motori DC
// i numeri sono gli identificativi dei pin indirizzabili da fuori
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
pinMode(44, OUTPUT);
pinMode(45, OUTPUT);
pinMode(46, OUTPUT);
}
// 2. la funzione loop viene eseguita costantemente
// in loop, appunto, finché Arduino è alimentato
void loop() {
while(Serial.available()) { // se la porta seriale è disponibile
byte val= Serial.read(); // leggi un byte
if((index == 0) && (val == 253)) { // se il primo byte è 253 = buono
index = 1; // aumenta index = vai avanti
} else if((index == 1)) {
// ora l’indice è 1, quindi seleziona il pin indicato da mm
port = val;
index = 2; // aumenta index = vai avanti
} else if((index == 2)) {
// ora l’indice è 2, quindi imposta il valore come indicato da nn
value = val;
index = 3; // aumenta index = vai avanti
} else if((index == 3) && (val == 255)) { // se il quarto byte è 255,
// allora fine messaggio:
// genera un certo segnale con valore nn sul pin mm
analogWrite(port, value);
index= 0; // azzerra il contatore
} else { // se invece il primo byte non è 253, riparti da capo
index= 0;
}
}
delay(1); // 1 millisecondo di attesa e si riparte con loop
}
In sostanza, grazie al programma, una volta alimentato e collegato alla porta USB, Arduino si mette ad ascoltare quello che arriva. Se i messaggi sono della forma [253, mm, nn, 255], allora viene eseguito il codice, che risulta in un certo segnale elettrico in uscita dal pin selezionato. Questo raggiunge il transistor e ne determina il comportamento. Nel nostro caso, il controllo per il transistor può semplicemente avere valore 255 o 0, cioè chiudi il circuito (serratura alimentata e solenoide ritratto) o mantienilo interrotto (serratura a riposo e solenoide prossimo alla borraccia). Sono possibili molte variazioni sul tema di questo codice. Ad esempio, nell’implementazione attuale il serraturofono non utilizza la porta seriale, ma una connessione via ethernet che richiede una scheda di rete aggiuntiva per Arduino: il codice cambia un po’, perché si deve leggere un messaggio sotto forma di stringa, ma la logica è la stessa. Come si diceva, si può notare come Arduino non sia trattato tanto come un piccolo calcolatore, quanto come un convertitore digitale-analogico. Il controllo di alto livello spetta infatti al software sul calcolatore: i miei 25 lettori potranno agevolmente presagire che si tratti di SuperCollider (SC)
Lo strato computazionale
A questo punto, il terzo strato è quello strettamente delegato ai processi computazionali di alto livello, cioè propriamente musicali.
Il punto fondamentale della programmazione è quello di permettere a sua volta strati di astrazione (abstraction layers). Ad esempio, ricordarsi che i 15 pin del codice precedente sono indirizzati da 2 a 15 più 44, 45 e 46 vuol dire situarsi a livello macchina. Scomodo per pensare musicalmente. Meglio sarebbe pensare a una sequenza 0-14. Meglio ancora, nel caso del serraturofono potrebbe essere utile a livello musicale indirizzare i pin attraverso i simboli delle note o degli interi midi relativi alle altezze in questione. Se poi si considerano le serrature, il singolo evento percussivo (ogni “nota”) si compone in realtà di tre istruzioni: sposta il battente contro il risonatore, attendi una certa durata, riporta il battente a riposo, cioè due messaggi per Arduino relativi allo stesso pin (255 e 0), inframezzati da una istruzione di attesa. Ma musicalmente è un singolo evento e così andrebbe pensato.
Per iniziare, il seguente codice SC accede alla porta seriale e chiede i nomi dei dispositivi connessi:
SerialPort.devices ;
Individuato il nome utile, la prossima espressione invece assegna alla variabile ~ard un oggetto SerialPort specificamente dedicato al suo controllo:
~ard = SerialPort(“/dev/tty.usbmodem1421”, 115200) ;
Gli argomenti (tra parentesi) passati a SerialPort rappresentano il nome del dispositivo per il sistema operativo (che varia, di qui la necessità di ispezionare presso il sistema) e il tasso di comunicazione (che deve essere lo stesso del programma Arduino).
Queste due espressioni:
~ard.putAll(Int8[253, 2, 255, 255]) ;
~ard.putAll(Int8[253, 2, 0, 255]) ;
sono il mattone di base del controllo: permettono di impostare sul pin 2 rispettivamente i valori 255 (segnale al picco, serratura ritratta) e 0 (nessun segnale, serratura a riposo). Si noti che ogni messaggio è costituito di 4 byte (in un array di elementi a 8 bit), il primo e l’ultimo sono di controllo, come descritto in precedenza. A questo punto, lo strato computazionale di base è pronto: le espressioni si possono scrivere e valutare in tempo reale. Si scrive sul computer, il serraturofono a borracce risponde. Si noti che non c’è bisogno di librerie esterne: si tratta infatti semplicemente di inviare byte sulla porta seriale in modo comprensibile a Arduino.
Supponiamo che le 12 serrature, associate alla scala cromatica, siano legate alla seguente serie di pin per questioni di organizzazione del circuito elettronico: [2,3,44,5,6,7,8,9,46,10,11,12,13]. Come si diceva, è una sequenza che non ha relazione con il dato musicale, e per di più non è in progressione. Poiché le borracce sono intonate a partire dal mi bemolle = 75 midi, si può allora definire una semplice funzione che associ a ognuna delle 12 note midi un certo pin, come la seguente:
~map = {|note| [2,3,44,5,6,7,8,9,46,10,11,12,13][note-75] } ;
A questo punto si può ragionare in termini di nota midi, perché l’espressione seguente convertirà in automatico quest’ultima nel pin relativo
~map.(77) ; // restituisce 44, il pin del fa, ovvero terza borraccia
Si può così descrivere musicalmente, con un ulteriore strato di astrazione, l’idea di generazione di un evento, cioè una “nota”, attraverso questa funzione:
~ev = {|note = 1, dur = 0.1|
{
~ard.putAll(Int8[253, ~map.(note), 255, 255]);
dur.wait;
~ard.putAll(Int8[253, ~map.(note), 0, 255]);
}.fork
};
Il blocco interno tra parentesi graffe che termina con fork genera una routine, cioè un processo nel tempo. È composto di 3 espressioni, secondo il modello già descritto sopra: la prima e la terza concernono la comunicazione sulla porta seriale e sono già state illustrate, la seconda (dur.wait) richiede di sospendere l’esecuzione (attendere) per una durata dur in secondi. Durante dur, Arduino continuerà a generare corrente pari a 255 sul pin selezionato. Dopo, aprirà il circuito (battente a riposo con 0).
Una volta definita, la funzione si usa semplicemente così:
~ev.(77, 0.05) ;
Ovvero, “genera un evento all’altezza midi 77 della durata di 50 millisecondi”. È abbastanza intuitivo che anche il tempo potrebbe essere agevolmente convertito da cronometrico a metronomico ed espresso in termini di frazioni. Strato su strato, siamo alle note, e diventa così semplice implementare processi tradizionalmente compositivi. Ad esempio, si supponga di avere una certa predilezione per il serialismo hardcore, versione integrale. La funzione seguente restituisce un processo (una routine, chiamata ~wiener) che genera una serie dodecafonica e associa a ogni altezza una e una sola durata da una serie di 12 durate diverse. L’unico argomento di controllo della funzione è dur, che rappresenta il moltiplicatore dell’unità di durata.
I commenti sono nel codice:
~makeWiener = {|dur = 0.1| // moltiplicatore di durata
// sequenza da 1 a 12, permutata a caso
var series = Array.series(12).scramble ;
// un’altra, moltiplicata per dur, per le durate
var times = Array.series(12).scramble*dur ;
// la routine costruenda va avanti all’infinito
var wiener = Routine{
inf.do{|i|
// attraverso il contatore, che procede da 0 1 a 11
// e poi (via modulo %) riprende da 0, si accede all’altezza…
~ev.(series[i%12]+75, dur) ; // (il solito evento)
// e alla durata associata, che dtermina quando generare il nuovo evento
times[i%12].wait ;
}
} ;
wiener // ciò che la funzione restituisce = la routine
} ;
Si usa così:
~myWiener = makeWiener.(0.1) ; // ora la routine esiste
~myWiener.play ; // ora viene messa in funzione
Ed ecco che viene generata una bella serie che accoppia altezze e durate, per poi essere eseguita all’infinito.
La si interrompe così:
~myWiener.pause ;
A questo livello,se si vuole,ci si dimentica di serrature, borracce, cavi, nastro telato, transistor, pin, porta seriale, si posano cacciavite e saldatore e ci si concentra su eventi, in questo caso molto tradizionalmente, su altezze e durate. E la dinamica? In teoria, dovrebbe essere costante, poiché non c’è modo di controllare la forza con cui il battente percuote la borraccia. Senonché, a pasticciare con gli eventi, si scoprono cose interessanti. La durata dell’evento non è ortogonale alla dinamica, al contrario ne è correlata. Entro certi intervalli di durata, a una maggiore durata corrisponde maggiore dinamica. Questo perché il solenoide della serratura ha tempo a sufficienza per realizzare per intero la sua corsa. In sostanza, la cosa è in qualche modo analoga all’idea del parametro velocity nel protocollo MIDI: è usato per indicare la dinamica, ma il suo nome propriamente indica un altro parametro fisico di riferimento, la velocità del martello che batte sulla corda di un pianoforte.
Concludo con un video esemplificativo. Si vede il serraturofono a borracce alle prese con un processo algoritmico di prova, che implementa una drum machine generativa e che può essere controllato interattivamente. Il video mostra le componenti descritte, la comunicazione in tempo reale via SC, e la possibilità di utilizzare interfacce grafiche. Dunque, il controllo in tempo reale avviene via codice, ma sono altresì possibili ulteriori forme di controllo.
(Beh, a dirla tutta, prima di scrivere questo contributo il progetto mi era sembrato più semplice).
Lascia una risposta