Descripción: Juguete infantil con forma de caja mágica que se abre y reacciona a los golpes que se den en la parte superior. Los golpes deben ser una secuencia exacta previamente programada. Al acertar la secuencia sale el ratón que «habita» en su interior; y al fallar la secuencia, la tapadera se abre suavemente y se cierra espasmódicamente reproduciendo con el sonido de los golpes la secuencia correcta hasta que es acertada y dicho esquema rítmico es reemplazado por otro distinto.
Componentes:
La caja de madera da un toque muy artesanal, pero no debes preocuparte pues en el apartado de descargas tendrás el STL de la caja completa si lo deseas así que podrás imprimirla tú mismo.
Esquema de conexiones
La fuente será la batería 18650 por lo que para controlarla y cargarla la conectaremos a un TP4056 o similar. Debemos tener en cuenta que la pila ofrece unos 3.7V así que tendremos que añadir un Step up Boost para conseguir unos 5V.
Los dos servos se conectarán directamente a los pines digitales D3 y D5 y se alimentarán de los 5V que salen del Step up Boost y no de la salida de alimentación del Arduino que solo podrá administrarnos 400mA como máximo y es posible que en algún momento necesitemos más corriente para los motores y los leds.
La tira led de 5 voltios es un factor opcional para iluminar el interior de la caja y dar un efecto más misterioso que irá conectado mediante un transistor 2N2222A al pin D11 pues se trata de una salida PWM que nos permitirá controlar la intensidad de la luz. En mi caso la he colocado en la parte oculta del interior de la tapadera superior.
Los dos leds WS2812B irán en el interior de los ojos del «ratón» para dar un efecto más dramático. La razón de usar estos leds es que podemos controlar el color de los mismos aunque se pueden usar también dos leds RGB o prescindir de ellos (con la modificación adecuada del código).
Sensor piezoeléctrico.
Es el componente principal del juguete. Es el encargado de recibir la información de los golpes que se efectúen en la tapa de la caja.
Seguro que has visto alguno en juguetes infantiles. Se trata de un par de chapas metálicas que contienen un cristal en su interior que reacciona generando electricidad al recibir una vibración, y viceversa, posee la facultad de producir vibración al serle aplicada una corriente eléctrica. Por lo cual se trata de un elemento que puede funcionar como sensor de vibraciones o como elemento emisor de sonido (parlante). Así seguro que los has visto como elemento en el interior de las tarjetas navideñas que producen sonidos al abrirlas. Además, se trata de un componente muy barato.
Suelen venderlo con una PCB que contiene un diodo y una resistencia (hay información en internet para construir un sustituto), aunque se puede comprar solo. Si lo has adquirido sin la PCB, no te preocupes, no complica demasiado su incorporación en el proyecto.
En el archivo de descarga de los ficheros STL se incluye una pieza que podrás usar para sujetar el sensor piezoeléctrico, aunque como ves en la imagen superior, también se puede usar cinta adhesiva.
Conectaremos el sensor a la entrada analógica A0 del microcontrolador que nos permitirá evaluar cada uno de los golpes en valores de entre 0 y 1023. En el código asignaremos un valor de threshold para graduar el valor mínimo de la excitabilidad.
Los servomotores
En el prototipo usé un servo de mejores características y mayor tamaño para levantar la tapa pues la caja era de madera y el peso de la misma pensé que podría darme problemas. Así que empleé un HS-311 para tal efecto y garantizar una apertura adecuada. En el modelo definitivo se emplean 2 servos SG90.
El servo «tapa» se conectará en el pin D3 y el servo «ojos» en el D5. Recuerda alimentaros de la fuente para evitar sobrecargar la salida de Arduino.
Una vez colocados es importante calibrarlos modificando las variables del código que aparecen antes del setup (tapa_max, tapa_med …), que deben ajustarse a la altura máxima, mínima e intermedia en grados pues estos valores dependen de cada servo.
Los motores encajan justos y no es imprescindible atornillarlos.
Los ojos
Al ser una orientación estética, no se incluyen en los archivos STL. Puedes pegar algún muñeco, pegatina cartón con un dibujo. En mi caso usé unos ojos que me sobraron de una prueba para una cabeza robótica.
En el interior hay dos leds multicolor que son opcionales y conectados al pin D6.
La caja
La pila encajará en la cavidad asignada abrazada por un perno atornillado.
Se adjuntan las palancas accionadoras de los servos y la pieza para acoplar el sensor piezoeléctrico.
Unas bisagras pegadas en la parte posterior permitirán la apertura de la tapadera.
Esquemas rítmicos
La utilidad didáctica del juguete es complementada con la notación musical que se adjunta, pues representa los toques mágicos que hay programados en origen.
En el código de programación se omite el último toque que corresponde a la última figura blanca.
Tener en cuenta que el primer código es simplemente un golpe.
int secretCode[maximumKnocks] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
El Código
Inicio, librerías y variables
Las librerías usadas son <Servo.h> para manejar los dos motores que llamaremos «tapa» y «ojos» y <Adafruit_NeoPixel.h> para los leds de los ojos que no son necesarios y podrías prescindir de este complemento. Aún así se incluyen en el archivo adjunto.
Antes del setup se calibran los servomotores para determinar la posición mínima, media y máxima. Estos valores varían dependiendo del servo y las peculiaridades de cada uno.
El pinluz=11 hace referencia a una tira led de 5v que he colocado alrededor de la tapadera pero si no quieres usarla puedes prescindir de ella. Añade un efecto mágico a la apertura de la caja. Esta luz será controlada más adelante con las rutinas sistole y diastole que aparecen al final del código.
Los toques estarán en la variable int secretCode[maximumKnocks] que tendrá como máximo 20. El último toque no debe aparecer en la variable y se supone que es una blanco. El primero de los códigos secretos los he dejado a 0, así que habrá que aplicar un sólo toque para abrir la caja.
Setup
Simplemente se inician los servos y se ponen en la posición oculta. A tener en cuenta que la librería Servo.h anula la propiedad de salida PWM de los pines D9 y D10.
Loop
Es muy simple, pues cuenta 5 minutos desde que no se realiza ninguna intervención y realiza la rutina espasmo, que hace que la caja se abra despacio y cierre rápido, para llamar la atención cada determinado tiempo. Esta función se puede suprimir también pues es un complemento para darle mayor atractivo.
Luego, se limita a esperar hasta recibir una interactuación por medio del sensor piezoeléctrico.
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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 |
#include <Servo.h> Servo myservo; #include <Servo.h> #include <Adafruit_NeoPixel.h> #ifdef __AVR__ #include <avr/power.h> // Required for 16 MHz Adafruit Trinket #endif #define PIN 6 #define NUMPIXELS 2 Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); Servo tapa; Servo ojos; int tapa_max = 25; int tapa_med = 50; int tapa_min = 68; //65 int ojos_max = 0; int ojos_med = 132; int ojos_min = 170; int pinluz = 11; const int knockSensor = 0; const int threshold = 50; const int rejectValue = 25; const int averageRejectValue = 15; const int knockFadeTime = 150; const int maximumKnocks = 20; const int knockComplete = 1200; int codigo = 0; //int secretCode[maximumKnocks] = {50, 25, 25, 50, 100, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Initial setup "Shave and a Hair Cut, two bits." //int secretCode[maximumKnocks] = {50, 50, 100, 50, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; int secretCode[maximumKnocks] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; int knockReadings[maximumKnocks]; int knockSensorValue = 0; unsigned long previousMillis = 0; unsigned long currentMillis = millis(); const long interval = 300000; void setup() { Serial.begin (9600); pinMode (pinluz, OUTPUT); pixels.begin(); tapa_abajo(0); delay (500); ojos_abajo(0); delay (500); Serial.println("Program start."); } void loop() { currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; espasmo(); } knockSensorValue = analogRead(knockSensor); if (knockSensorValue >= threshold) { listenToSecretKnock(); } } void espasmo() { tapa_medio(100); delay (50); sistole (4); diastole (50); delay (100); tapa_abajo(0); delay (500); } void listenToSecretKnock() { Serial.println("knock starting"); int i = 0; for (i = 0; i < maximumKnocks; i++) { knockReadings[i] = 0; } int currentKnockNumber = 0; int startTime = millis(); int now; delay(knockFadeTime); do { knockSensorValue = analogRead(knockSensor); if (knockSensorValue >= threshold) { Serial.println("knock."); now = millis(); knockReadings[currentKnockNumber] = now - startTime; currentKnockNumber ++; startTime = now; delay(knockFadeTime); } now = millis(); } while ((now - startTime < knockComplete) && (currentKnockNumber < maximumKnocks)); if (validateKnock() == true) { triggerDoorUnlock(); } else { Serial.println("Secret knock failed."); cancion1(); } } void triggerDoorUnlock() { Serial.println("Door unlocked!"); codigo++; if (codigo >= 4) { codigo = 1; } Serial.print ("Código: "); Serial.println (codigo); tapa_arriba(0); delay (300); pixels.setPixelColor(1, pixels.Color(255, 0, 255)); pixels.setPixelColor(0, pixels.Color(255, 0, 255)); pixels.show(); ojos_arriba(0); sistole (50); iris (13, 255, 0, 255, 60); // nVeces, r, g, b, delay pixels.setPixelColor(1, pixels.Color(255, 0, 255)); pixels.setPixelColor(0, pixels.Color(255, 0, 255)); pixels.show(); ojos_abajo(0); tapa_abajo(0); pixels.setPixelColor(1, pixels.Color(0, 0, 0)); pixels.setPixelColor(0, pixels.Color(0, 0, 0)); pixels.show(); digitalWrite (pinluz, LOW); delay (300); if (codigo == 1) { int aa[maximumKnocks] = {50, 25, 25, 50, 100, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; for (int i = 0; i < maximumKnocks; i++) { secretCode[i] = aa[i]; } validateKnock (); } if (codigo == 2) { int bb[maximumKnocks] = {50, 50, 25, 25, 25, 25, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; for (int x = 0; x < maximumKnocks; x++) { secretCode[x] = bb[x]; } validateKnock (); } if (codigo == 3) { int cc[maximumKnocks] = {50, 100, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; for (int x = 0; x < maximumKnocks; x++) { secretCode[x] = cc[x]; } validateKnock (); } previousMillis = currentMillis; //RELOJ } boolean validateKnock() { int i = 0; int currentKnockCount = 0; int secretKnockCount = 0; int maxKnockInterval = 0; for (i = 0; i < maximumKnocks; i++) { if (knockReadings[i] > 0) { currentKnockCount++; } if (secretCode[i] > 0) { secretKnockCount++; } if (knockReadings[i] > maxKnockInterval) { maxKnockInterval = knockReadings[i]; } } int totaltimeDifferences = 0; int timeDiff = 0; for (i = 0; i < maximumKnocks; i++) { knockReadings[i] = map(knockReadings[i], 0, maxKnockInterval, 0, 100); timeDiff = abs(knockReadings[i] - secretCode[i]); if (timeDiff > rejectValue) { return false; } totaltimeDifferences += timeDiff; } if (totaltimeDifferences / secretKnockCount > averageRejectValue) { return false; } return true; } void cancion1() { int secretKnockCount = 0; for (int i = 0; i < maximumKnocks; i++) { if (secretCode[i] > 0) { secretKnockCount++; } } digitalWrite (pinluz, HIGH); tapa_medio(45); diastole (20); sistole (20); diastole (20); sistole (20); diastole (20); // sistole (20); for (int i = 0; i < secretKnockCount ; i++) { tapa_abajo (0); tapa_medio(0); if (secretCode[i] == 25) { delay (10); } if (secretCode[i] == 50) { delay (250); } if (secretCode[i] == 100) { delay (700); } } tapa_abajo (0); //el último toque. digitalWrite (pinluz, LOW); previousMillis = currentMillis; //RELOJ delay (400); } void iris(int a, int b, int c, int d, int e) { // nVeces, r, g, b, delay for (int i = 0; i < a; i += 1) { pixels.setPixelColor(0, pixels.Color(b, c, d)); pixels.setPixelColor(1, pixels.Color(b, c, d)); pixels.show(); delay (e); pixels.setPixelColor(0, pixels.Color(0, 0, 0)); pixels.setPixelColor(1, pixels.Color(0, 0, 0)); pixels.show(); delay (e); } } void ojos_medio (int pausa) { ojos.attach(5); int pos_ojos = ojos.read(); if (pausa == 0) { ojos.write (ojos_med); delay (250); ojos.detach(); } if (pausa != 0) { if ( (pos_ojos - ojos_med) > 0) { for (int i = pos_ojos; i >= ojos_med; i -= 1) { ojos.write (i); delay (pausa); } } if ( (pos_ojos - ojos_med) <= 0) { for (int i = pos_ojos; i <= ojos_med; i += 1) { ojos.write (i); delay (pausa); } } } ojos.detach(); //parece duplicado en el if del inicio } void ojos_abajo (int pausa) { ojos.attach(5); int pos_ojos = ojos.read(); if (pausa == 0) { ojos.write (ojos_min); delay (250); ojos.detach(); } if (pausa != 0) { if ( (pos_ojos - ojos_min) > 0) { for (int i = pos_ojos; i >= ojos_min; i -= 1) { ojos.write (i); delay (pausa); } } if ( (pos_ojos - ojos_min) <= 0) { for (int i = pos_ojos; i <= ojos_min; i += 1) { ojos.write (i); delay (pausa); } } } ojos.detach(); } void ojos_arriba (int pausa) { ojos.attach(5); int pos_ojos = ojos.read(); if (pausa == 0) { ojos.write (ojos_max); delay (250); ojos.detach(); } if (pausa != 0) { if ( (pos_ojos - ojos_max) > 0) { for (int i = pos_ojos; i >= ojos_max; i -= 1) { ojos.write (i); delay (pausa); } } if ( (pos_ojos - ojos_max) <= 0) { for (int i = pos_ojos; i <= ojos_max; i += 1) { ojos.write (i); delay (pausa); } } } ojos.detach(); } void tapa_abajo(int pausa) { tapa.attach(3); int pos_tapa = tapa.read(); if (pausa == 0) { tapa.write (tapa_min); delay (130); tapa.detach(); } if (pausa != 0) { if ( (pos_tapa - tapa_min) > 0) { for (int i = pos_tapa; i >= tapa_min; i -= 1) { tapa.write (i); delay (pausa); } } if ( (pos_tapa - tapa_min) <= 0) { for (int i = pos_tapa; i <= tapa_min; i += 1) { tapa.write (i); delay (pausa); } } } tapa.detach(); } void tapa_medio(int pausa) { tapa.attach(3); int pos_tapa = tapa.read(); if (pausa == 0) { tapa.write (tapa_med); delay (110); tapa.detach(); } if (pausa != 0) { if ( (pos_tapa - tapa_med) > 0) { for (int i = pos_tapa; i >= tapa_med; i -= 1) { tapa.write (i); delay (pausa); } } if ( (pos_tapa - tapa_med) <= 0) { for (int i = pos_tapa; i <= tapa_med; i += 1) { tapa.write (i); delay (pausa); } } } tapa.detach(); } void tapa_arriba(int pausa) { tapa.attach(3); int pos_tapa = tapa.read(); if (pausa == 0) { tapa.write (tapa_max); delay (250); tapa.detach(); } if (pausa != 0) { if ( (pos_tapa - tapa_max) > 0) { for (int i = pos_tapa; i >= tapa_max; i -= 1) { tapa.write (i); delay (pausa); } } if ( (pos_tapa - tapa_max) <= 0) { for (int i = pos_tapa; i <= tapa_max; i += 1) { tapa.write (i); delay (pausa); } } } tapa.detach(); } void sistole(int intervalo) { for (int i = 0; i <= 255; i += intervalo) { analogWrite (pinluz, i); delay (20); } digitalWrite (pinluz, HIGH); } void diastole(int intervalo) { for (int i = 255; i >= 0; i -= intervalo) { analogWrite (pinluz, i); delay (20); } digitalWrite (pinluz, LOW); } |