Arduino 101: Construye un Vestido LED con Reconocimiento de Patrones usando Intel Curie
Componentes y suministros
| Tira NeoPixel | × | 1 | Puede sustituirse por LEDs RGB individuales. |
| Arduino 101 | × | 1 | |
| Pulsador SparkFun 12mm | × | 1 | |
| Resistencia 10k ohm | × | 1 | |
| Cables jumper (genéricos) | × | 1 | Pueden necesitarse cables largos para guiar los LEDs desde el hombro a la mano. |
| Batería 9V (genérica) | × | 1 | |
| Conector 9V a Jack Barril | × | 1 | |
| LEDs RGB Neopixel Adafruit Flora - Pack de 4 | × | 1 | Pueden usarse para reemplazar la tira Neopixel. |
| Hilo Conductivo Cosible | × | 1 | Opción para reemplazar algunos cables. |
Herramientas y máquinas necesarias
- Pistola de pegamento caliente
- Máquina de coser
- Soldador
Aplicaciones y servicios online
- Arduino IDE
Sobre este proyecto
Introducción
Soy ingeniera y artista, y disfruto de proyectos que combinan ciencia y arte. He estado creando diseños de vestidos con microcontroladores integrados para que la funcionalidad de componentes electrónicos pueda realzar el tema del diseño. Para este proyecto, quiero utilizar el motor de coincidencia de patrones (PME) y el acelerómetro dentro del Intel Curie en Arduino 101. Curie está diseñado para aplicaciones PME ya que tiene capacidad de red neuronal, pero no hay muchos ejemplos de PME. Espero que este ejemplo te inspire a aprovechar la capacidad PME de Curie.
El vestido usa una página de mi novela gráfica como tela. Una científica y su robot miran a través de un telescopio. A medida que la portadora dibuja un patrón en el aire, un conjunto de LEDs dispuestos en forma de constelaciones brillarán en el cielo nocturno sobre la tela.
Instrucciones
Nota: después de publicar este proyecto, renové la parte del circuito electrónico. Un método mejorado para fijar los LEDs a la tela y hacerlos duraderos se publica abajo después del paso 3.
Actualización: usar este vestido en varios eventos de demostración de todo el día proporcionó una estimación de la duración de la batería. Para alimentar el vestido, la batería de 9V necesita cambiarse aproximadamente cada 3 horas. La batería no se agota, pero cae por debajo de 9V, volviéndose ineficiente para la coincidencia de patrones. Puedes guardar la batería para otros usos.
Paso 1
Usé una tira NeoPixel de Adafruit, la corté en trozos y los dispuse en formas de constelaciones. Siéntete libre de usar otros tipos de LEDs como LEDs RGB individuales. Pégalos o cóselos a la tela base.
Paso 2
Coloca una tela de interfaz encima y puedes delinear las constelaciones. Este paso es opcional, pero encontré útil tener múltiples capas de tela para reforzar la estructura. En realidad cosí otra tela gruesa en la parte posterior de la tela base. Así que tres capas en total como base, encapsulando los LEDs.
Paso 3
Suolda los LEDs. Si usas LEDs cosibles individuales, también puedes usar hilo conductor para conectarlos. De cualquier forma, requiere mucho trabajo manual y paciencia. Como tengo cuatro constelaciones (Orion, Osa Mayor, Cygnus y Casiopea), las separé en cuatro trazados. Cada uno se conectará a un pin diferente del Arduino 101.
Pasos mejorados
La tela que usé para los pasos anteriores era demasiado suave, lo que facilita doblar los LEDs y cables, rompiendo las uniones de soldadura. Repetí todo el proceso con el siguiente método mejorado.
Posicionar los LEDs
Usé una tira NeoPixel de Adafruit, la corté en trozos y los dispuse en formas de constelaciones. Siéntete libre de usar otros tipos de LEDs como LEDs RGB individuales. Pégalos o cóselos a la tela base.
El fieltro es un buen tipo de tela gruesa que da estructura y funciona bien con pegamento caliente. Dispón los LEDs en sus posiciones respectivas para que cuando se enciendan, sus posiciones reflejen estrellas en constelaciones.
Planificar los LEDs y cables
Pega las tiras de LED al fieltro en algunos puntos. No pongas pegamento debajo de toda la tira porque necesitas cierto margen para deslizar tubos termorretráctiles, como se muestra en la figura. Corta cables trenzados a las longitudes adecuadas y colócalos en sus posiciones relativas en la misma pieza de fieltro. Recomiendo no pegar los cables con pegamento caliente aún. Mi figura aquí es un error en ese sentido. Es mejor mantener los cables en sus posiciones con cinta temporal para que al soldar los cables a los LEDs, las posiciones de los cables puedan ajustarse.
Preparar todas las piezas para coser
La figura muestra que preparé cuatro constelaciones (Orion, Osa Mayor, Cygnus y Casiopea) en cuatro piezas separadas. Después de soldar y poner tubo termorretráctil alrededor de las uniones soldadas, puedes pegar todo firmemente al fieltro.
¡Probar!
¡Prueba tu circuito antes de continuar! Puedes simplemente hacer un test de tira NeoPixel para cada trazado.
Okay, puse el nivel como "Fácil" ya que científicamente no es difícil una vez entiendes el código, pero requiere mucho trabajo estabilizando los cables en la tela.
Asegúrate de que tu Arduino IDE sea la versión más reciente y tenga la librería Curie PME. Recomiendo usar el Editor Web de Arduino. Descarga la librería aquí.
Hacer el vestido
En paralelo (figurativamente), haz el vestido. Después de probar el circuito, cose las telas base con los LEDs en el interior del vestido. Los LEDs brillarán a través de los gráficos.
Como puedes ver, el Arduino 101 está en mi mano. Hice una carcasa impresa en 3D para el Arduino 101 y la batería. Hay cables largos conectando los LEDs y la placa, que están ocultos en la manga.
El código a continuación te dará información sobre cómo se programa la placa. Después de flashear el código, entrena primero las neuronas para que aprendan qué patrones existen. Mira este video en ~0:30.
Para más fotos y otros diseños de tech-fashion/pinturas en tela, visita mi web :)
Código
- PME_4LED_new.ino
- PME_LED
PME_4LED_new.ino Arduino
Este es un script actualizado del anterior. Almacena los datos de entrenamiento. Actualmente, está configurado con un paso de inicialización. La primera serie de LEDs muestra Rojo cuando hay alimentación. Manteniendo el Arduino 101 plano con el puerto USB apuntando en dirección horizontal, mientras se presiona el botón, permite usar el entrenamiento previo. Estos LEDs cambian a Verde y luego Azul al soltar el botón, indicando que el sistema está listo. Si el Arduino 101 se sostiene con el puerto USB apuntando en dirección vertical, mientras se presiona el botón, se borran los datos de entrenamiento previos. El sistema puede reentrenarse al soltar el botón.
/*
* Este ejemplo demuestra el uso del motor de coincidencia de patrones (CuriePME)
* para clasificar flujos de datos del acelerómetro de CurieIMU.
*
* Primero, el sketch te pedirá que dibujes algunas letras en el aire
* (imagina que escribes en una pizarra invisible, usando tu placa como bolígrafo),
* y los datos IMU de estos movimientos se usan como datos de entrenamiento para el PME.
* Una vez finalizado el entrenamiento, puedes seguir dibujando letras y el PME
* intentará adivinar qué letra estás dibujando.
*
* Este ejemplo requiere un botón conectado al pin digital 4
* https://www.arduino.cc/en/Tutorial/Button
*
* NOTA: Para mejores resultados, dibuja letras grandes, al menos 30-60 cm de alto.
*
* Copyright (c) 2016 Intel Corporation. Todos los derechos reservados.
* Ver aviso de licencia al final del archivo.
*/
#include "CurieIMU.h"
#include "CuriePME.h"
#include
#include
#include
#define PINM 6 // ¿En qué pin están conectados los NeoPixels?
#define PINC 3
#define PINS 9
#define PINO 5
Adafruit_NeoPixel stripM = Adafruit_NeoPixel(10, PINM, NEO_GRB + NEO_KHZ800); // La tira tiene 15 píxeles. Puedes cambiar esto.
Adafruit_NeoPixel stripS = Adafruit_NeoPixel(10, PINS, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel stripC = Adafruit_NeoPixel(10, PINC, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel stripO = Adafruit_NeoPixel(10, PINO, NEO_GRB + NEO_KHZ800);
int tr = 0; // Variables para "color-target" y "color-current" para suavizado...
int tg = 0;
int tb = 0;
int r = 0;
int g = 0;
int b = 0;
int rawX = 0; // Para valores del acelerómetro de Curie
int rawY = 0;
//int rawZ = 0;
float angle = 0.0;
/* Controla cuántas veces debe dibujarse una letra durante el entrenamiento.
* Cualquier valor mayor a 4, y puede que no tengas suficientes neuronas para
* las 26 letras del alfabeto. Menor a 4 significa menos trabajo para entrenar
* una letra, pero el PME puede tener más dificultad clasificándola. */
const unsigned int trainingReps = 4;
/* Aumenta esto a 'A-Z' si quieres -- solo toma más tiempo entrenar */
const unsigned char trainingStart = 'A';
const unsigned char trainingEnd = 'D';
/* Pin de entrada para señalar cuando se dibuja una letra - necesitarás
* asegurar que un botón está conectado a este pin */
const unsigned int buttonPin = 4;
/* Tasa de muestreo del acelerómetro */
const unsigned int sampleRateHZ = 200;
/* Nº de bytes que una neurona puede contener */
const unsigned int vectorNumBytes = 128;
/* Nº de muestras procesadas (1 muestra == accel x, y, z)
* que caben dentro de una neurona */
const unsigned int samplesPerVector = (vectorNumBytes / 3);
/* Este valor convierte caracteres ASCII A-Z
* en valores decimales 1-26, y viceversa. */
const unsigned int upperStart = 0x40;
const unsigned int sensorBufSize = 2048;
const int IMULow = -32768;
const int IMUHigh = 32767;
const char *filename = "NeurDataDress.dat";
void setup() {
Serial.begin(9600);
//while(!Serial);
pinMode(buttonPin, INPUT);
/* Iniciar el IMU (Unidad de Medición Inercial) */
CurieIMU.begin();
/* Iniciar el PME (Motor de Coincidencia de Patrones) */
CuriePME.begin();
CurieIMU.setAccelerometerRate(sampleRateHZ);
CurieIMU.setAccelerometerRange(2);
/* Iniciar chip SPI Flash */
if (!SerialFlash.begin(ONBOARD_FLASH_SPI_PORT, ONBOARD_FLASH_CS_PIN)) {
Serial.println("No se puede acceder al chip SPI Flash");
}
stripM.begin(); // inicializar tira neopixel
stripS.begin();
stripC.begin();
stripO.begin();
stripM.show(); // Inicializar todos los píxeles a 'off'
stripS.show();
stripC.show();
stripO.show();
solidM(stripM.Color(255, 0, 0), 50); // Rojo para listo para entrada
}
/* Esta función lee el archivo guardado por el ejemplo anterior
* El archivo contiene todos los datos aprendidos, guardados anteriormente.
* Una vez restaurada la red, puede clasificar patrones de nuevo
* sin tener que reentrenarse.*/
void restoreNetworkKnowledge ( void ) {
SerialFlashFile file;
int32_t fileNeuronCount = 0;
Intel_PMT::neuronData neuronData;
// Abrir el archivo y escribir datos de prueba
file = SerialFlash.open(filename);
CuriePME.beginRestoreMode();
if (file) {
// iterar sobre la red y guardar los datos
while(1) {
Serial.print("Leyendo Neurona: ");
uint16_t neuronFields[4];
file.read( (void*) neuronFields, 8);
file.read( (void*) neuronData.vector, 128 );
neuronData.context = neuronFields[0] ;
neuronData.influence = neuronFields[1] ;
neuronData.minInfluence = neuronFields[2] ;
neuronData.category = neuronFields[3];
if (neuronFields[0] == 0 || neuronFields[0] > 127) break;
fileNeuronCount++;
// esta parte solo imprime cada neurona mientras se restaura,
// para que puedas ver qué está pasando.
Serial.print(fileNeuronCount);
Serial.print("\n");
Serial.print( neuronFields[0] );
Serial.print( "\t");
Serial.print( neuronFields[1] );
Serial.print( "\t");
Serial.print( neuronFields[2] );
Serial.print( "\t");
Serial.print( neuronFields[3] );
Serial.print( "\t");
Serial.print( neuronData.vector[0] );
Serial.print( "\t");
Serial.print( neuronData.vector[1] );
Serial.print( "\t");
Serial.print( neuronData.vector[2] );
Serial.print( "\n");
CuriePME.iterateNeuronsToRestore( neuronData );
}
}
CuriePME.endRestoreMode();
Serial.print("Conjunto de Conocimiento Restaurado. \n");
}
boolean longPress=false;
int startTime=0;
int lastOrientation = - 1; // orientación previa (para comparación)
int lastReading = -1;
boolean lastPress=false;
void loop () {
int orientation = - 1; // orientación de la placa
String orientationString; // string para descripción de orientación
// Orientaciones de la placa:
// 0: plana, procesador hacia arriba
// 1: plana, procesador hacia abajo
// 2: horizontal, pines analógicos abajo
// 3: horizontal, pines analógicos arriba
// 4: vertical, conector USB arriba
// 5: vertical, conector USB abajo
// leer acelerómetro:
int x = CurieIMU.readAccelerometer(X_AXIS);
int y = CurieIMU.readAccelerometer(Y_AXIS);
int z = CurieIMU.readAccelerometer(Z_AXIS);
// calcular valores absolutos, para determinar el mayor
int absX = abs(x);
int absY = abs(y);
int absZ = abs(z);
if ( (absZ > absX) && (absZ > absY)) {
// basar orientación en Z
if (z > 0) {
orientationString = "arriba";
orientation = 0;
} else {
orientationString = "abajo";
orientation = 1;
}
} else if ( (absY > absX) && (absY > absZ)) {
// basar orientación en Y
if (y > 0) {
orientationString = "pines digitales arriba";
orientation = 2;
} else {
orientationString = "pines analógicos arriba";
orientation = 3;
}
} else {
// basar orientación en X
if (x < 0) {
orientationString = "conector arriba";
orientation = 4;
} else {
orientationString = "conector abajo";
orientation = 5;
}
}
// si la orientación cambió, imprimir descripción:
if (orientation != lastOrientation) {
Serial.println(orientationString);
lastOrientation = orientation;
}
// si la orientación cambió, imprimir descripción:
if (orientation != lastOrientation) {
lastOrientation = orientation;
}
int reading = digitalRead(buttonPin);
if (lastReading != reading) {
Serial.print("buttonPin=");
Serial.println(reading);
lastReading = reading;
}
if (reading == HIGH) {
if (startTime == 0) {
startTime=millis();
} else if ((millis() - startTime) >2000) {
longPress=true;
if (!lastPress) {
Serial.println("longPress");
// verde para pulsación larga
solidM(stripM.Color(0, 255, 0), 50);// Verde
lastPress=true;
}
}
}
if ( reading == LOW && longPress ) {
blackout(5);
Serial.print("orientation=");
Serial.print(orientation);
Serial.print(" SerialFlash.exists(filename)=");
Serial.println(SerialFlash.exists(filename));
if (orientation!=4 && SerialFlash.exists(filename)) {
restoreNetworkKnowledge();
Serial.print("Entrenamiento restaurado.");
} else {
trainLetters();
blackout(5);
Serial.print("Entrenamiento completado.");
}
Serial.println(" Ahora, dibuja algunas letras (recuerda ");
Serial.println("mantener el botón) y ve si el PME puede clasificarlas.");
solidM(stripM.Color(0, 0, 255), 500); // Azul para listo para coincidencia
blackout(5); // apagar para coincidencia
while (true) {
match();
}
}
}
void solidM(uint32_t c, uint8_t wait) {
for (uint16_t i=0; i < stripM.numPixels(); i++) {
stripM.setPixelColor(i, c); // apagar cada píxel
}
stripM.show();
delay(wait);
}
void blackout(uint8_t wait) {
for (uint16_t i=0; i < stripM.numPixels(); i++) {
stripM.setPixelColor(i, 0); // apagar cada píxel
}
for (uint16_t i=0; i < stripS.numPixels(); i++) {
stripS.setPixelColor(i, 0);
}
for (uint16_t i=0; i < stripC.numPixels(); i++) {
stripC.setPixelColor(i, 0);
}
for (uint16_t i=0; i < stripO.numPixels(); i++) {
stripO.setPixelColor(i, 0);
}
stripM.show();
stripS.show();
stripC.show();
stripO.show();
delay(wait);
}
void match() {
byte vector[vectorNumBytes];
unsigned int category;
char letter;
char pattern;
/* Registrar datos IMU mientras se mantiene el botón,
* y convertirlos a un vector adecuado */
readVectorFromIMU(vector);
/* Usar el PME para clasificar el vector, es decir, devolver una categoría
* de 1-26, representando una letra de A-Z */
category = CuriePME.classify(vector, vectorNumBytes);
if (category == CuriePME.noMatch) {
Serial.println("No reconozco esa -- inténtalo de nuevo.");
theaterChaseM(stripM.Color(127, 127, 127), 50);
theaterChaseS(stripS.Color(127, 127, 127), 50);
theaterChaseC(stripC.Color(127, 127, 127), 50);
theaterChaseO(stripO.Color(127, 127, 127), 50);// Blanco
stripM.show();
stripS.show();
stripC.show();
stripO.show();
blackout(1);
} else {
letter = category + upperStart;
pattern = letter;
if ( pattern == 'A' ) {
// color rojo
colorWipeM(stripM.Color(0, 255, 0), 50); // Verde
theaterChaseM(stripM.Color(127, 127, 127), 50); // Blanco
stripM.show();
} else if ( pattern == 'B') {
colorWipeS(stripS.Color(255, 0, 0), 50); // Rojo
theaterChaseS(stripS.Color(127, 127, 127), 50); // Blanco
stripS.show();
} else if ( pattern == 'C') {
colorWipeC(stripC.Color(0, 0, 255), 50); // Azul
theaterChaseC(stripC.Color(127, 127, 127), 50); // Blanco
stripC.show();
} else if ( pattern == 'D') {
colorWipeO(stripO.Color(255, 0, 255), 50); // Magenta
theaterChaseO(stripO.Color(127, 127, 127), 50); // Blanco
stripO.show();
}
Serial.println(letter);
}
}
/* Filtro simple de "media móvil", elimina ruido bajo y otras
* pequeñas anomalías, con el efecto de suavizar el flujo de datos. */
byte getAverageSample(byte samples[], unsigned int num, unsigned int pos, unsigned int step) {
unsigned int ret;
unsigned int size = step * 2;
if (pos < (step * 3) || pos > (num * 3) - (step * 3)) {
ret = samples[pos];
} else {
ret = 0;
pos -= (step * 3);
for (unsigned int i = 0; i < size; ++i) {
ret += samples[pos - (3 * i)];
}
ret /= size;
}
return (byte)ret;
}
/* Necesitamos comprimir el flujo de datos crudos del acelerómetro en 128 bytes,
* para que quepa en una neurona, preservando tanto del patrón original como sea posible.
* Asumiendo que típicamente habrá 1-2 segundos de datos del acelerómetro a 200Hz,
* ¡necesitaremos descartar más del 90% para cumplir ese objetivo!
*
* Esto se hace de 2 formas:
*
* 1. Cada muestra consiste en 3 valores signed de 16-bit (uno cada para X, Y y Z).
* Mapear cada valor de 16 bit a un rango de 0-255 y empaquetarlo en un byte,
* reduciendo el tamaño de muestra a la mitad.
*
* 2. Submuestreo. Si muestreamos a 200Hz y el botón se mantiene 1.2
* segundos, tendremos ~240 muestras. Como sabemos que cada muestra,
* una vez comprimida, ocupará 3 de los 128 bytes de nuestra neurona
* (ver #1), sabemos que solo caben 42 de esas 240 muestras en una
* neurona (128 / 3 = 42.666). Así que si tomamos (por ejemplo) cada 5ª
* muestra hasta tener 42, deberíamos cubrir la mayor parte de la ventana
* de muestreo y tener cierta semejanza del patrón original. */
void undersample(byte samples[], int numSamples, byte vector[]) {
unsigned int vi = 0;
unsigned int si = 0;
unsigned int step = numSamples / samplesPerVector;
unsigned int remainder = numSamples - (step * samplesPerVector);
/* Centrar ventana de muestra */
samples += (remainder / 2) * 3;
for (unsigned int i = 0; i < samplesPerVector; ++i) {
for (unsigned int j = 0; j < 3; ++j) {
vector[vi + j] = getAverageSample(samples, numSamples, si + j, step);
}
si += (step * 3);
vi += 3;
}
}
void readVectorFromIMU(byte vector[]) {
byte accel[sensorBufSize];
int raw[3];
unsigned int samples = 0;
unsigned int i = 0;
/* Esperar hasta que se presione el botón */
while (digitalRead(buttonPin) == LOW);
/* Mientras se mantiene el botón... */
while (digitalRead(buttonPin) == HIGH) {
if (CurieIMU.dataReady()) {
CurieIMU.readAccelerometer(raw[0], raw[1], raw[2]);
/* Mapear valores crudos a 0-255 */
accel[i] = (byte) map(raw[0], IMULow, IMUHigh, 0, 255);
accel[i + 1] = (byte) map(raw[1], IMULow, IMUHigh, 0, 255);
accel[i + 2] = (byte) map(raw[2], IMULow, IMUHigh, 0, 255);
i += 3;
++samples;
/* Si no hay suficiente espacio en los buffers
* para la siguiente lectura, terminamos */
if (i + 3 > sensorBufSize) {
break;
}
}
}
undersample(accel, samples, vector);
}
void trainLetter(char letter, unsigned int repeat) {
unsigned int i = 0;
while (i < repeat) {
byte vector[vectorNumBytes];
if ( letter == 'A' ) {
colorWipeM(stripM.Color(0, 255, 0), 50); // Verde
theaterChaseM(stripM.Color(127, 127, 127), 50); // Blanco
stripM.show();
} else if ( letter == 'B') {
colorWipeS(stripS.Color(255, 0, 0), 50); // Rojo
theaterChaseS(stripS.Color(127, 127, 127), 50); // Blanco
stripS.show();
} else if ( letter == 'C') {
colorWipeC(stripC.Color(0, 0, 255), 50); // Azul
theaterChaseC(stripC.Color(127, 127, 127), 50); // Blanco
stripC.show();
} else if ( letter == 'D') {
colorWipeO(stripO.Color(255, 0, 255), 50); // Magenta
theaterChaseO(stripO.Color(127, 127, 127), 50); // Blanco
stripO.show();
}
if (i) Serial.println("Y otra vez...");
readVectorFromIMU(vector);
CuriePME.learn(vector, vectorNumBytes, letter - upperStart);
Serial.println("¡Lo pillé!");
delay(1000);
++i;
}
}
bool create_if_not_exists (const char *filename, uint32_t fileSize) {
if (!SerialFlash.exists(filename)) {
Serial.println("Creando archivo " + String(filename));
return SerialFlash.createErasable(filename, fileSize);
}
Serial.println("Archivo " + String(filename) + " ya existe");
return true;
}
void saveNetworkKnowledge ( void ) {
// const char *filename = "NeurData.dat";
SerialFlashFile file;
Intel_PMT::neuronData neuronData;
uint32_t fileSize = 128 * sizeof(neuronData);
Serial.print( "Tamaño de archivo a guardar = ");
Serial.print( fileSize );
Serial.print("\n");
create_if_not_exists( filename, fileSize );
// Abrir el archivo y escribir datos de prueba
file = SerialFlash.open(filename);
file.erase();
CuriePME.beginSaveMode();
if (file) {
// iterar sobre la red y guardar los datos
while( uint16_t nCount = CuriePME.iterateNeuronsToSave(neuronData)) {
if( nCount == 0x7FFF) break;
Serial.print("Guardando Neurona: ");
Serial.print(nCount);
Serial.print("\n");
uint16_t neuronFields[4];
neuronFields[0] = neuronData.context;
neuronFields[1] = neuronData.influence;
neuronFields[2] = neuronData.minInfluence;
neuronFields[3] = neuronData.category;
file.write( (void*) neuronFields, 8);
file.write( (void*) neuronData.vector, 128 );
}
}
CuriePME.endSaveMode();
Serial.print("Conjunto de Conocimiento Guardado. \n");
}
void trainLetters() {
for (char i = trainingStart; i <= trainingEnd; ++i) {
Serial.print("Mantén el botón y dibuja la letra '");
Serial.print(String(i) + "' en el aire. Suelta el botón tan pronto ");
Serial.println("como termines.");
trainLetter(i, trainingReps);
Serial.println("OK, terminado con esta letra.");
delay(2000);
}
// Iniciar chip SPI Flash
if (!SerialFlash.begin(ONBOARD_FLASH_SPI_PORT, ONBOARD_FLASH_CS_PIN)) {
Serial.println("No se puede acceder al chip SPI Flash");
}
saveNetworkKnowledge();
}
/* Funciones especiales de luces del ejemplo Adafruit Strandtest
* Introduce un valor de 0 a 255 para obtener un valor de color. Usado para efecto arcoíris arriba.
* Luces Theater Chase del código de ejemplo Adafruit strandtest. Toma el valor RGB actual
* y hace un efecto "theatre chase" con él. */
/*Theatre-style crawling lights.*/
void theaterChasePIN(uint8_t p,uint32_t c, uint8_t wait) {
Adafruit_NeoPixel strip = Adafruit_NeoPixel(10, p, NEO_GRB + NEO_KHZ800);
for (int j=0; j<10; j++) { // hacer 10 ciclos de persecución
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
strip.setPixelColor(i+q, c); // encender cada tercer píxel
}
strip.show();
delay(wait);
for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
strip.setPixelColor(i+q, 0); // apagar cada tercer píxel
}
}
}
}
/* Rellenar los puntos uno tras otro con un color*/
void colorWipePIN(uint8_t p, uint32_t c, uint8_t wait) {
Adafruit_NeoPixel strip = Adafruit_NeoPixel(10, p, NEO_GRB + NEO_KHZ800);
for(uint16_t i=0; i
PME_LED Arduino
Este usa el Curie PME para controlar LEDs. Básicamente es una combinación del código Draw in the Air PME y el código de ejemplo Adafruit NeoPixel. Este código no es exactamente el que usé (que es un poco complejo) ya que dudo que hagas el mismo arreglo de constelaciones. Más bien, aquí hay un código genérico que puedes modificar para personalizar tus necesidades, por ejemplo, puedes cambiar cómo distribuir los pines para diferentes tiras de LEDs. Puede que lo actualice con un código mejorado más tarde.
/*
* Este ejemplo demuestra el uso del motor de coincidencia de patrones (CuriePME)
* para clasificar flujos de datos del acelerómetro de CurieIMU. El código es una modificación
* del ejemplo Draw in the Air:
* https://github.com/01org/Intel-Pattern-Matching-Technology
*
* Primero, el sketch te pedirá que dibujes patrones en el aire
* (imagina que escribes en una pizarra invisible, usando tu placa como bolígrafo),
* y los datos IMU de estos movimientos se usan como datos de entrenamiento para el PME.
* Una vez finalizado el entrenamiento, puedes seguir dibujando letras y el PME
* intentará adivinar qué letra estás dibujando.
*
* Este ejemplo requiere un botón conectado al pin digital 4
* https://www.arduino.cc/en/Tutorial/Button
*
* NOTA: Para mejores resultados, dibuja letras grandes, al menos 30-60 cm de alto.
*
* Copyright (c) 2016 Intel Corporation. Todos los derechos reservados.
* Ver aviso de licencia al final del archivo.
*/
#include "CurieIMU.h"
#include "CuriePME.h"
#include
#define PIN 6 // ¿En qué pin están conectados los NeoPixels?
Adafruit_NeoPixel strip = Adafruit_NeoPixel(54, PIN, NEO_GRB + NEO_KHZ800); // La tira tiene 15 píxeles. Cambia esto.
int tr = 0; // Variables para "color-target" y "color-current" para suavizado...
int tg = 0;
int tb = 0;
int r = 0;
int g = 0;
int b = 0;
int rawX = 0; // Para valores del acelerómetro de Curie
int rawY = 0;
//int rawZ = 0;
float angle = 0.0;
/* Controla cuántas veces debe dibujarse una letra durante el entrenamiento.
* Cualquier valor mayor a 4, y puede que no tengas suficientes neuronas para
* las 26 letras del alfabeto. Menor a 4 significa menos trabajo para entrenar
* una letra, pero el PME puede tener más dificultad clasificándola. */
const unsigned int trainingReps = 4;
/* Aumenta esto a 'A-Z' si quieres -- solo toma más tiempo entrenar */
const unsigned char trainingStart = 'A';
const unsigned char trainingEnd = 'D';
/* Pin de entrada para señalar cuando se dibuja una letra - necesitarás
* asegurar que un botón está conectado a este pin */
const unsigned int buttonPin = 4;
/* Tasa de muestreo del acelerómetro */
const unsigned int sampleRateHZ = 200;
/* Nº de bytes que una neurona puede contener */
const unsigned int vectorNumBytes = 128;
/* Nº de muestras procesadas (1 muestra == accel x, y, z)
* que caben dentro de una neurona */
const unsigned int samplesPerVector = (vectorNumBytes / 3);
/* Este valor convierte caracteres ASCII A-Z
* en valores decimales 1-26, y viceversa. */
const unsigned int upperStart = 0x40;
const unsigned int sensorBufSize = 2048;
const int IMULow = -32768;
const int IMUHigh = 32767;
void setup() {
Serial.begin(9600);
// while(!Serial);
pinMode(buttonPin, INPUT);
/* Iniciar el IMU (Unidad de Medición Inercial) */
CurieIMU.begin();
/* Iniciar el PME (Motor de Coincidencia de Patrones) */
CuriePME.begin();
CurieIMU.setAccelerometerRate(sampleRateHZ);
CurieIMU.setAccelerometerRange(2);
trainLetters();
//Serial.println("Entrenamiento completado. Ahora, dibuja algunas letras (recuerda ");
// Serial.println("mantener el botón) y ve si el PME puede clasificarlas.");
strip.begin(); // inicializar tira neopixel
strip.show(); // Inicializar todos los píxeles a 'off'
}
void loop () {
/// estas funciones están escritas al final del sketch.
Serial.println("Entrenamiento completado. Ahora, dibuja algunas letras (recuerda ");
Serial.println("mantener el botón) y ve si el PME puede clasificarlas.");
byte vector[vectorNumBytes];
unsigned int category;
char letter;
char pattern;
/* Registrar datos IMU mientras se mantiene el botón,
* y convertirlos a un vector adecuado */
readVectorFromIMU(vector);
/* Usar el PME para clasificar el vector, es decir, devolver una categoría
* de 1-26, representando una letra de A-Z */
category = CuriePME.classify(vector, vectorNumBytes);
if (category == CuriePME.noMatch) {
Serial.println("No reconozco esa -- inténtalo de nuevo.");
theaterChase(strip.Color(127, 127, 127), 50); // Blanco
strip.show();
} else {
letter = category + upperStart;
pattern = letter;
if ( pattern == 'A' ) {
// rojo
colorWipe(strip.Color(0, 255, 0), 50); // Verde
theaterChase(strip.Color(127, 127, 127), 50); // Blanco
strip.show();
} else if ( pattern == 'B') {
colorWipe(strip.Color(255, 0, 0), 50); // Rojo
theaterChase(strip.Color(127, 127, 127), 50); // Blanco
strip.show();
} else if ( pattern == 'C') {
colorWipe(strip.Color(0, 0, 255), 50); // Azul
theaterChase(strip.Color(127, 127, 127), 50); // Blanco
strip.show();
} else if ( pattern == 'D') {
colorWipe(strip.Color(255, 0, 255), 50); // Magenta
theaterChase(strip.Color(127, 127, 127), 50); // Blanco
strip.show();
}
Serial.println(letter);
}
}
/* Filtro simple de "media móvil", elimina ruido bajo y otras
* pequeñas anomalías, con el efecto de suavizar el flujo de datos. */
byte getAverageSample(byte samples[], unsigned int num, unsigned int pos, unsigned int step) {
unsigned int ret;
unsigned int size = step * 2;
if (pos < (step * 3) || pos > (num * 3) - (step * 3)) {
ret = samples[pos];
} else {
ret = 0;
pos -= (step * 3);
for (unsigned int i = 0; i < size; ++i) {
ret += samples[pos - (3 * i)];
}
ret /= size;
}
return (byte)ret;
}
/* Necesitamos comprimir el flujo de datos crudos del acelerómetro en 128 bytes,
* para que quepa en una neurona, preservando tanto del patrón original como sea posible.
* Asumiendo que típicamente habrá 1-2 segundos de datos del acelerómetro a 200Hz,
* ¡necesitaremos descartar más del 90% para cumplir ese objetivo!
*
* Esto se hace de 2 formas:
*
* 1. Cada muestra consiste en 3 valores signed de 16-bit (uno cada para X, Y y Z).
* Mapear cada valor de 16 bit a un rango de 0-255 y empaquetarlo en un byte,
* reduciendo el tamaño de muestra a la mitad.
*
* 2. Submuestreo. Si muestreamos a 200Hz y el botón se mantiene 1.2
* segundos, tendremos ~240 muestras. Como sabemos que cada muestra,
* una vez comprimida, ocupará 3 de los 128 bytes de nuestra neurona
* (ver #1), sabemos que solo caben 42 de esas 240 muestras en una
* neurona (128 / 3 = 42.666). Así que si tomamos (por ejemplo) cada 5ª
* muestra hasta tener 42, deberíamos cubrir la mayor parte de la ventana
* de muestreo y tener cierta semejanza del patrón original. */
void undersample(byte samples[], int numSamples, byte vector[]) {
unsigned int vi = 0;
unsigned int si = 0;
unsigned int step = numSamples / samplesPerVector;
unsigned int remainder = numSamples - (step * samplesPerVector);
/* Centrar ventana de muestra */
samples += (remainder / 2) * 3;
for (unsigned int i = 0; i < samplesPerVector; ++i) {
for (unsigned int j = 0; j < 3; ++j) {
vector[vi + j] = getAverageSample(samples, numSamples, si + j, step);
}
si += (step * 3);
vi += 3;
}
}
void readVectorFromIMU(byte vector[]) {
byte accel[sensorBufSize];
int raw[3];
unsigned int samples = 0;
unsigned int i = 0;
/* Esperar hasta que se presione el botón */
while (digitalRead(buttonPin) == LOW);
/* Mientras se mantiene el botón... */
while (digitalRead(buttonPin) == HIGH) {
if (CurieIMU.dataReady()) {
CurieIMU.readAccelerometer(raw[0], raw[1], raw[2]);
/* Mapear valores crudos a 0-255 */
accel[i] = (byte) map(raw[0], IMULow, IMUHigh, 0, 255);
accel[i + 1] = (byte) map(raw[1], IMULow, IMUHigh, 0, 255);
accel[i + 2] = (byte) map(raw[2], IMULow, IMUHigh, 0, 255);
i += 3;
++samples;
/* Si no hay suficiente espacio en los buffers
* para la siguiente lectura, terminamos */
if (i + 3 > sensorBufSize) {
break;
}
}
}
undersample(accel, samples, vector);
}
void trainLetter(char letter, unsigned int repeat) {
unsigned int i = 0;
while (i < repeat) {
byte vector[vectorNumBytes];
if (i) Serial.println("Y otra vez...");
readVectorFromIMU(vector);
CuriePME.learn(vector, vectorNumBytes, letter - upperStart);
Serial.println("¡Lo pillé!");
delay(1000);
++i;
}
}
void trainLetters() {
for (char i = trainingStart; i <= trainingEnd; ++i) {
Serial.print("Mantén el botón y dibuja la letra '");
Serial.print(String(i) + "' en el aire. Suelta el botón tan pronto ");
Serial.println("como termines.");
trainLetter(i, trainingReps);
Serial.println("OK, terminado con esta letra.");
delay(2000);
}
}
/////////////////Funciones especiales de luces del ejemplo Adafruit Strandtest
// ¡Arcoíris! Nota- esta función bloquea nuevas entradas de posición hasta que termina.
void rainbowCycle(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256*5; j++) { // 5 ciclos de todos los colores en la rueda
for(i=0; i< strip.numPixels(); i++) {
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
}
strip.show();
delay(wait);
}
}
// Introduce un valor de 0 a 255 para obtener un valor de color. Usado para efecto arcoíris arriba.
// Los colores son una transición r - g - b - vuelta a r.
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if(WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if(WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
//////Luces Theater Chase del código de ejemplo Adafruit strandtest. Toma el valor RGB actual
// y hace un efecto "theatre chase" con él.
//Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
for (int j=0; j<10; j++) { // hacer 10 ciclos de persecución
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
strip.setPixelColor(i+q, c); // encender cada tercer píxel
}
strip.show();
delay(wait);
for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
strip.setPixelColor(i+q, 0); // apagar cada tercer píxel
}
}
}
}
// Rellenar los puntos uno tras otro con un color
void colorWipe(uint32_t c, uint8_t wait) {
for(uint16_t i=0; i
Esquemáticos
Simplemente conectar los LEDs a los pines del Arduino 101 y un botón al pin 4 (como se describe en el código: https://www.arduino.cc/en/Tutorial/Button). El diagrama de circuito es similar a este Fritzing de Adafruit: https://learn.adafruit.com/adafruit-neopixel-uberguide/basic-connections
Conecté la batería de 9V directamente al jack barril.
Proceso de manufactura
- La Evolución y Producción de Patrones de Costura Industriales
- Proyecto Sensor DHT11 Temperatura y Humedad con Indicadores LED y Altavoz Piezo
- Construye un Spybot controlado por Bluetooth con Arduino
- FlickMote: Mando IR por gestos para Smart Home
- TV‑B‑Gone DIY con Arduino: Mando universal para apagar cualquier TV
- Construye un Reloj Maestro LED Personalizado con Alarma – Visible desde 12 Metros
- Convierte tu Teclado MIDI en Tutor de Piano Inteligente con Arduino, LEDs y MuseScore
- Controla LEDs con Alexa mediante Raspberry Pi – Guía paso a paso fácil
- Find Me: Localizador Inteligente de Objetos con Arduino y Bluetooth
- Soluciones de Alimentación Optimizadas para Proyectos Arduino
- Elimine cables puente: Cómo BreadShield conecta Arduino directamente a protoboards