Il Theremin con il microcontrollore Possiamo costruire uno strumento simile al Theremin utilizzando un dispositivi programmabile come il microcontrollore e un sensore di distanza (ultrasuoni). A secondo della distanza a cui si trova la mano rispetto al sensore faremo generare note con frequenza diversa. Il sensore a ultrasuoni utilizzato viene gestito con un protocollo standard di comunicazione che è detto I2C (di cui vengono allegate le caratteristiche). La basetta con il sensore ad ultrasuoni è quella di figura. L indirizzo a cui risponde il sensore è E0, facilmente modificabile per permettere il collegamento su un'unica linea di trasmissione di più ultrasuoni dello stesso tipo. I sensore restituisce un dato a 16 bit che rappresenta la distanza in cm dall ostacolo. La distanza minima che riesce a misurare è di circa 16 cm, la massima di 6 m. Occorre mettere esternamente sul bus le resistenze di pull-up, il cui valore è attorno a 1,8 Kohm. Il programma Theremin1 è il più semplice per gestire il theremin: è quello riportato di seguito Nella parte di dichiarazione delle variabili vengono defini i bit a cui sono collegati i dati e l altoparlante Bit 2 clock Bit 3 data Bit 9 altoparlante I 4 define successive servono per fare in modo di poter utilizzare I bit del bus sia in input (lettura) che in output, sono comandi usti dal software di gestione dell I2C.
La distanza rilevata dagli ultrasuoni viene memorizzata nella componente 0 e 1 del vettore valore[]. Il byte meno significativo è in valore[1], quello meno significativo in valore[0]. Per ottenere il dato letto nella variabile dist occorre sommare i due byte attribuendo peso 0 e peso 255 con l istruzione seguente dist=valore[0]*255+valore[1]; Nel programma di setup vengono inizializzati gli ingressi uscite e settata la linea seriale con dato e ck a livello alto. Nel programma che si ripete nel loop vengono inizializzate una serie di variabili che permettono di modificare facilmente l intervallo di frequenze utilizzate e la distanza massima gestita. fmax ed fmin rappresentano l intervallo di frequenze che si vogliono generare dmax e dmin rappresentano la distanza massima e minima per cui il sensore sente l ostacolo. Se c è un ostacolo a una distanza superiore a quella massima non viene emesso nessun suono, se c è un ostacolo a una distanza inferiore a quella minima nel programma viene emessa sempre la nota a frequenza minima, anche se è meglio non usare questo intervallo perché il sensore non funziona correttamente (vedi data-sheet) dist è la distanza a cui viene rilevato l ostacolo
nota è la frequenza della nota che viene generata. E direttamente proporzionale alla distanza dell ostacolo, per cui sarà acuta se ci allontaniamo, grave se ci avviciniamo. Viene calcolata con la seguente relazione nota=((dist-dmin)*(fmax-fmin))/dmax+fmin; - in questo modo se la distanza è quella minima viene emessa una frequenza pari alla fmin - il valore di dmax deve essere scelto tenendo conto del fatto che la risoluzione del sensore è di 1 cm, quindi occorre fare in modo che a uno spostamento di 1 cm corrisponda una variazione di nota. Nel programma in esame si vogliono generare le note di 4 ottave, cioè 12*4=48 note, quindi la distanza massima deve essere 48 cm a cui va aggiunta la distanza minima, quindi diventa 63 cm. Queste inizializzazioni non ci permettono di usare per esempio solo 12 note (una sola ottava) perché in questo caso il theremin dovrebbe lavorare su un distanza massima di 12 +15=27 cm, piuttosto piccola. Il software relativo a theremin 2 permette di fare questa operazione (con altri limiti). dmax=12*nottave+dmin La nota viene generata solo se la distanza rilevata è compresa fra la massima e la minima, se è maggiore della massima distanza gestita il suono viene spento tramite l istruzione notone(suono), se è minore viene generata sempre la nota relativa alla fmin (quella più bassa). void loop() int nota,fmax=1976,fmin=130,dmax=63,dmin=15; //dmax è il numero di note nell'intervallo considerato (12 per ottava)per il numero di ottave considerato+dmin //es se le note vanno da 130 a 1976 sono 4 ottave x12=48+dmin che è 15 quindi 63 //lettura ultrasuoni la distanza è in valore[1] num=2; lettura_ultrasuoni(0xe0,valore); dist=valore[0]*255+valore[1]; //controllo distanza per determinare la nota if((dist<dmax) && (dist>dmin)) //scarto le distanze lette maggiori di dmax e minori di dmin nota=((dist-dmin)*(fmax-fmin))/dmax+fmin; else if(dist<=dmin) nota=fmin; else notone(suono); //visualizzazione su linea seriale /*Serial.print("distanza in cm "); Serial.print(dist); Serial.print(" freq nota "); Serial.println(nota);*/ //delay(10); La funzione lettura_ultrasuoni legge il valore che restituisce il sensore a ultrasuoni. Occorre passarle nella variabile addrhard l indirizzo hardware del dispositivo I2C, ritorna nel vettore valore[] la distanza letta.
Richiama le procedure scritte di seguito che servono per inviare sul bus I2C i segnali corretti per la comunicazione. In particolare invia prima il segnale di start, poi l indirizzo del dispositivo con cui vuole comunicare indicando con l ottavo bit che vuole scrivere sullo slave, poi l indirizzo software e il comando per indicare che si aspetta una lettura della distanza, infine legge con un ciclo ripetuto 2 volte i due dati di 8 bit. Tutte le routine seguenti sono quelle che permettono di implementare la comunicazione I2C, non le esamineremo nel dettaglio, possono essere considerate come una libreria che viene utilizzata. //tutte le routine seguenti sono uguali per la gestione I2C di tutti i dispositivi int read_infrared (int addrhard) int dato; i2c_start(); // send start sequence i2c_tx(addrhard+1); // indirizzo di lettura con bit 0 alto dato=i2c_rx(0); // dato i2c_stop(); // send stop sequence return(dato); void write_infrared(int addrhard,int addrsof) i2c_start(); // send start sequence i2c_tx(addrhard); // indirizzo e write i2c_tx(addrsof); // dato i2c_stop(); void i2c_dly(void) delaymicroseconds(40); //20 microsec void i2c_start(void) // i2c start bit sequence // i dati sono inizializzato a zero SDA0; void i2c_stop(void) SDA0; // i2c stop bit sequence
unsigned char i2c_rx(char ack) char x, d=0; for(x=0; x<8; x++) d <<= 1; do while(digitalread(scl_in)==0); // wait for any SCL clock stretching if(digitalread(sda_in)) d = 1; if(ack) SDA0; else // send (N)ACK bit return d; void i2c_tx(unsigned char d) char x; for(x=8; x; x--) if(d&0x80) else SDA0; d <<= 1; Il programma Theremin2, riportato di seguito, è identico per la parte riguardante la gestione degli ultrasuoni (che non è stata riscritta) ma genera solo 12 note in funzione della distanza. Viene impostata solo la fmin, la fmax sarà funzione del numero di note che si vogliono generare, contenute nella variabile notemax. Posso quindi scegliere da dmax che voglio (senza i vincoli del programma precedente), l intervallo dmax-dmin sarà diviso in un numero di intervalli pari al numero di note che si vogliono ottenere. Se per es sono 12 sarà diviso in 12 intervallo, quindi la nota cambia ogni volta che ci spostiamo di una quantità fissa dipendente dalla distanza massima e minima impostate. Nel caso del programma dove dmax=60 cm e dmin=20 con 12 note da generare, cm il passo di variabilità è (60-20)/12=3,4 cm. Ogni volta che mi sposto di 3,4 cm cambia la nota. Per calcolare la frequenza della nota da generare determino prima in che intervallo si trova, cioè un numero compreso fra 0 e 11 memorizzato nella variabile nnota, poi sapendo che le note di un ottava
costituiscono una progressione geometrica con passo 1.0595 determino la nota moltiplicando la fmin per la potenza di 1.0595 con nnota, cioè frequenza=fmin*1.0595 nnota Di seguito le istruzioni che fanno questi conti: nnota=((dmax-dist)*notemax)/(dmax-dmin); //il numero nota va da 0 a 11 (0 la più bassa corrisponde alla distanza massima) le posizioni rispetto alla fmin nota=(pow(1.0595,nnota))*fmin; //ogni nota è la più bassa moltiplicato la cost 1.0595 elevata al numero di posizioni di cui ci si deve spostare nella scala /**************************************************************************************** classe 4IT a.s. 2011-2012 Theremin 2 programma di simulazione del Theremin che genera solo 12 note in funzione della distanza per lettura degli ultrasuoni tipo SFR02 (singolo I2C) la distanza arriva fino al valore di dmax (in cm) al di sotto di dmin emette sempre la fmin *****************************************************************************************/ #define clock 2 //il bit 2 è il clock dell'i2c #define data 3 //il bit 3 è il dato #define suono 9 //il bit 9 è l'uscita per il suono #define SCL1 (pinmode(clock,input),digitalwrite(clock,high))//comando per collegare la resistenza di pull_up #define SCL0 (pinmode(clock,output),digitalwrite(clock,low)) #define SDA1 (pinmode(data,input),digitalwrite(data,high))//comando per collegare la resistenza di pull_up #define SDA0 (pinmode(data,output),digitalwrite(data,low)) #define SCL_IN clock #define SDA_IN data int valore[20],i,num; long dist;//distanza letta con gli ultrasuoni void setup() //ck e dato per definzione del protocollo devono partire alti digitalwrite(clock,low); digitalwrite(data,low); Serial.begin(9600); pinmode(suono,output); void loop() int nota,fmin=262,dmax=60,dmin=20;//fmax dipende dal valore scelto per fmin fmax=494 con questa fmin //per cambiare intervallo di frequenza basta modificare fmin int nnota,notemax=12; //per generare più note di un'0ttava basta cambiare notemax //divido l'intervallo della distanza in 12 parti, ognuna corrisponde a una nota dell'ottava scelta //se la fmin è (come in questo caso) 262 arriverò fino a fmax che è 494 (ottava fondamentale9 //lettura ultrasuoni la distanza è in valore[1] e valore[0] num=2; lettura_ultrasuoni(0xe0,valore); dist=valore[0]*255+valore[1]; nnota=((dmax-dist)*notemax)/(dmax-dmin); //il numero nota va da 0 a 11 (0 la più bassa corrisponde alla distanza massima) le posizioni rispetto alla fmin //controllo distanza per determinare la nota if((dist<=dmax) && (dist>=dmin)) //scarto le distanze lette maggiori di dmax e minori di dmin nota=(pow(1.0595,nnota))*fmin; //ogni nota è la più bassa moltiplicato la cost 1.0595 elevata al numero di posizioni di cui ci si deve spostare nella scala
else notone(suono); //a distanze superiori di dmax e inferiori a dmin non suona //visualizzazione su linea seriale //quando lo uso queste visualizzazioni le metto come commento Serial.print("distanza in cm "); Serial.print(dist); Serial.print(" numero nota "); Serial.print(nnota); Serial.print(" freq nota "); Serial.println(nota); //delay(10); //inserisco un ritardo se voglio mantenere la nota fissa per un certo tempo