Entendiendo el Theremín y un ejemplo básico#
En la clase de hoy hemos creado nuestro primer theremín. El theremín es un instrumento que cambia el sonido que se reproduce dependiendo de la luz.
La manera en la que vamos a lograr esto es haciendo uso de un fotorresistor (LDR), ya usado anteriormente en otros proyectos para medir la luz. Este lee un valor entre 0 y 1023, ya que está conectado a un pin analógico.
Luego, dependiendo de ese valor vamos a usar una pieza nueva llamada zumbador (buzzer), que es básicamente el reproductor de sonido. Existen dos tipos:
- Activo: solo puede estar encendido (5V) o apagado (0V), ya que internamente genera su propia señal.
- Pasivo: nos permite enviarle una frecuencia (Hz) para que suene de diferentes maneras.
Para poder mandarle esa frecuencia vamos a hacer uso de una función que ya viene preinstalada, una función built‑in llamada tone(). A esta función le pasamos dos argumentos: el pin del zumbador, que tenemos definido al principio del programa, y la frecuencia a la que queremos que suene.
OJO: es importante saber que el tono no para hasta que:
- desconectemos la placa,
- cambiemos el firmware,
- o utilicemos la función
noTone() con el pin del zumbador.
El código es el siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| #define FOTO_PIN A0
#define ZUMBADOR 9
const int MINIMO_ORIGEN = 500;
const int MAXIMO_ORIGEN = 1000;
int lectura_foto = 0;
int frecuencia = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
delay(1);
lectura_foto = analogRead(FOTO_PIN);
frecuencia = map(lectura_foto, MINIMO_ORIGEN, MAXIMO_ORIGEN, 50, 6000);
tone(ZUMBADOR, frecuencia);
Serial.println("Lectura fotorresistor: " + String(lectura_foto));
}
|
Comprobaciones de seguridad básicas#
- Revisar que el zumbador pasivo está conectado sin necesidad de respetar polaridad.
- Evitar usar
tone() en los pines 3 o 11 si se están utilizando funciones PWM, ya que comparten el Timer 2. - Asegurarse de que el LDR no se conecta directamente entre 5V y GND sin una resistencia fija, para evitar riesgos eléctricos.
Justificación del rango 50–6000 Hz#
Utilizamos el rango de 50 Hz a 6000 Hz porque:
- El oído humano es capaz de percibir frecuencias entre 20 Hz y 20 kHz, pero en la práctica la mayoría de personas no oye por encima de 14 kHz.
- El zumbador pasivo empleado en esta práctica ofrece su mejor rendimiento y claridad dentro del rango 50 Hz a 6 kHz, por lo que es el intervalo más adecuado para que el theremín suene correctamente.
Theremín con potenciómetro#
Ahora que ya hemos visto cómo funciona el theremín básico, vamos a crear una versión mejorada que incluye un potenciómetro. ¿Para qué sirve? Pues nos permite cambiar la frecuencia que le mandamos al zumbador según la posición del potenciómetro: podemos generar tonos más graves, más agudos o incluso apagar el zumbador, algo muy útil porque a veces puede resultar bastante molesto.
En esencia, hacemos lo mismo que en el proyecto anterior: seguimos leyendo el valor del fotorresistor (LDR) para generar el tono, pero mapeamos a rangos de frecuencia diferentes dependiendo del valor del potenciómetro.
El código es el siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| #define FOTO_PIN A0
#define POTEN_PIN A1
#define ZUMBADOR 9
const int MINIMO_ORIGEN = 500;
const int MAXIMO_ORIGEN = 1000;
int lectura_foto = 0;
int lectura_poten = 0;
int frecuencia = 0;
String nivel = "Apagado";
void setup() {
Serial.begin(9600);
}
void loop() {
delay(1);
lectura_foto = analogRead(FOTO_PIN);
lectura_poten = analogRead(POTEN_PIN);
Serial.println("Lectura fotorresistor: " + String(lectura_foto) + " " +
"Lectura potenciómetro: " + String(lectura_poten) + " " +
"Nivel de frecuencia: " + nivel);
if (lectura_poten >= 0 && lectura_poten < 100) {
noTone(ZUMBADOR);
nivel = "Apagado";
} else if (lectura_poten >= 100 && lectura_poten < 300) {
// Frecuencia muy grave
frecuencia = map(lectura_foto, MINIMO_ORIGEN, MAXIMO_ORIGEN, 100, 400);
nivel = "Muy Grave";
tone(ZUMBADOR, frecuencia);
} else if (lectura_poten >= 300 && lectura_poten < 600) {
// Frecuencia grave
frecuencia = map(lectura_foto, MINIMO_ORIGEN, MAXIMO_ORIGEN, 400, 800);
nivel = "Grave";
tone(ZUMBADOR, frecuencia);
} else if (lectura_poten >= 600 && lectura_poten < 900) {
// Frecuencia poco aguda
frecuencia = map(lectura_foto, MINIMO_ORIGEN, MAXIMO_ORIGEN, 800, 2000);
nivel = "Agudo";
tone(ZUMBADOR, frecuencia);
} else if (lectura_poten >= 900 && lectura_poten < 1024) {
// Frecuencia muy aguda
frecuencia = map(lectura_foto, MINIMO_ORIGEN, MAXIMO_ORIGEN, 2000, 6000);
nivel = "Muy Agudo";
tone(ZUMBADOR, frecuencia);
}
}
|
Theremín con potenciómetro y alarma#
Vamos con el último proyecto del día, una versión modificada del theremín con potenciómetro, a la que añadimos una alarma SOS en código Morse, activada mediante un botón.
La funcionalidad principal es la misma que en el proyecto anterior: el potenciómetro controla diferentes rangos de frecuencia del zumbador dependiendo de su posición. Pero ahora, si pulsamos el botón, el theremín deja de funcionar temporalmente para reproducir la secuencia SOS:
- S → … (tres puntos)
- O → — (tres rayas)
- S → … (tres puntos)
La alarma suena solo mientras mantenemos pulsado el botón. En cuanto lo soltamos, el theremín vuelve a funcionar con normalidad. Para organizar mejor el código, añadimos dos funciones auxiliares: punto() y raya().
El código es el siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
| #define FOTO_PIN A0
#define POTEN_PIN A1
#define ZUMBADOR 9
#define BTN_PIN 7
const int MINIMO_ORIGEN = 500;
const int MAXIMO_ORIGEN = 1000;
int lectura_foto = 0;
int lectura_poten = 0;
int frecuencia = 0;
String nivel = "Apagado";
void setup() {
Serial.begin(9600);
pinMode(BTN_PIN, INPUT_PULLUP);
}
void loop() {
delay(1);
lectura_foto = analogRead(FOTO_PIN);
lectura_poten = analogRead(POTEN_PIN);
int estado = digitalRead(BTN_PIN);
while (estado == LOW) {
// Alarma SOS en Morse
// S → ...
punto(); punto(); punto();
delay(300);
// O → ---
raya(); raya(); raya();
delay(300);
// S → ...
punto(); punto(); punto();
delay(1000);
estado = digitalRead(BTN_PIN);
}
Serial.println("Lectura fotorresistor: " + String(lectura_foto) + " " +
"Lectura potenciómetro: " + String(lectura_poten) + " " +
"Nivel de frecuencia: " + nivel);
if (lectura_poten >= 0 && lectura_poten < 100) {
noTone(ZUMBADOR);
nivel = "Apagado";
} else if (lectura_poten >= 100 && lectura_poten < 300) {
// Frecuencia muy grave
frecuencia = map(lectura_foto, MINIMO_ORIGEN, MAXIMO_ORIGEN, 100, 400);
nivel = "Muy Grave";
tone(ZUMBADOR, frecuencia);
} else if (lectura_poten >= 300 && lectura_poten < 600) {
// Frecuencia grave
frecuencia = map(lectura_foto, MINIMO_ORIGEN, MAXIMO_ORIGEN, 400, 800);
nivel = "Grave";
tone(ZUMBADOR, frecuencia);
} else if (lectura_poten >= 600 && lectura_poten < 900) {
// Frecuencia aguda
frecuencia = map(lectura_foto, MINIMO_ORIGEN, MAXIMO_ORIGEN, 800, 2000);
nivel = "Agudo";
tone(ZUMBADOR, frecuencia);
} else if (lectura_poten >= 900 && lectura_poten < 1024) {
// Frecuencia muy aguda
frecuencia = map(lectura_foto, MINIMO_ORIGEN, MAXIMO_ORIGEN, 2000, 6000);
nivel = "Muy Agudo";
tone(ZUMBADOR, frecuencia);
}
}
void punto() {
tone(ZUMBADOR, 1000);
delay(200);
noTone(ZUMBADOR);
delay(200);
}
void raya() {
tone(ZUMBADOR, 1000);
delay(600);
noTone(ZUMBADOR);
delay(200);
}
|