Sensor de proximidad con Arduino Nano 33 BLE Sense (sensor APDS9960)

Además de leer colores, gestos y luz ambiental, el sensor APDS9960es capaz de leer la proximidad de un objecto al mismo.

El primer paso es incluir la librería

#include <Arduino_APDS9960.h>

Inicializar el sensor a través de la instrucción APDS.bgin() que devolverá true si todo va bien o false si hay algún error durante la inicialización.

  if (!APDS.begin()) {
    Serial.println("Error al inicializar el sensor");
  }

Para saber si hay alguna lectura disponible se pude usar la función APDS.proximityAvailable()

  if (APDS.proximityAvailable()) {

  }

Para obtener el valor se usa APDS.readProximity()

int proximity = APDS.readProximity();

La lectura devuelve un int cuyo valor puede ser positivo entre 0 y 255 (aunque en mis pruebas no he logrado que pase de 252) que indica la distancia (a menor valor más cerca) o -1 que indica una lectura errónea del sensor. La lectura no viene en ninguna unidad de medida y habrá que convertirla realizando pruebas en una situación ambiental similar a la que se vaya a usar. De todas formas este valor hay que tomarlo más como una referencia que como un valor exacto. Por ejemplo los valores que pasan e 250 pueden interpretarse como («no hay nada cerca»), el valor 0 no indica «sobre el sensor» se alcanza con el objeto a unos 3 o 4 centímetros del mismo.

Veamos todo el código junto:

#include <Arduino_APDS9960.h>

void setup() {
  Serial.begin(9600);
  while (!Serial);

  if (!APDS.begin()) {
    Serial.println("Error al inicializar el sensor");
  }
}

void loop() {
  // comprobar si hay una lectura disponible
  if (APDS.proximityAvailable()) {
    int proximity = APDS.readProximity();
    Serial.println(proximity);
  }
  delay(500);  
}

Leer gestos con Arduino Nano 33 BLE Sense (sensor APDS9960)

Arduino Nano 33 BLE Sense incluye un sensor de gestos. Su funcionamiento es muy sencillo, tiene cuatro detectores infrarrojos con un led emisor. Cada uno está colocado en una de la esquinas del sensor. Cuando pasas la mano por encima de cada uno de los detectores se reflejan los infrarrojos en la piel y detectan el gesto. Viendo en qué orden los sensores detectan la mano sabe en qué dirección ocurre el gesto.

Este sistema tiene varios problemas:

  • Distancia de funcionamiento, el gesto tiene que realizarse cerca del sensor.
  • En según qué entornos puede dar lecturas falsas.
  • Puede tener problemas para detectar gestos con las manos sucias o con guantes.

Hablamos de manos pero realmente lee cualquier elemento que refleje suficiente luz infrarroja.

El sensor lee cuatro tipos gestos: arriba, abajo, izquierda, derecha.

Cada gesto tiene asociada una constante: GESTURE_UP, GESTURE_DOWN, GESTURE_LEFT, GESTURE_RIGHT.

También existe una constate para indicar que no se ha entendido el gesto: GESTURE_NONE.

Tal y como está colocado el sensor para leer correctamente los gestos el conector USB debe de quedar en la parte «inferior».

Lo primero es incluir la librería del sensor, que creara un objeto APDS:

#include <Arduino_APDS9960.h>

Después hay que ver iniciar el sensor con APDS.begin()

  if (!APDS.begin()) {
    Serial.println("Error al iniciar el sensor");
  }

Una vez el sensor está listo podemos comprobar si ha leído algún gesto con la instrucción APDS.gestureAvailable() que devolverá true cuando hay captado algún gesto. El gesto correspondiente lo podemos obtener con APDS.readGesture().

Veamos un código, adaptado de los ejemplos, para leer gestos:

#include <Arduino_APDS9960.h>

void setup() {
  Serial.begin(9600);
  while (!Serial);

  if (!APDS.begin()) {
    Serial.println("Error al iniciar el sensor");
  }
}

void loop() {
  //comprobar si se ha detectado algún gesto
  if (APDS.gestureAvailable()) {
    int gesture = APDS.readGesture();
    switch (gesture) {
      case GESTURE_UP:
        Serial.println("UP - arriba");
        break;

      case GESTURE_DOWN:
        Serial.println("DOWN - abajo");
        break;

      case GESTURE_LEFT:
        Serial.println("LEFT - izquierda");
        break;

      case GESTURE_RIGHT:
        Serial.println("RIGHT - derecha");
        break;

      default:        
        break;
    }
  }
}

Podemos ajustar la sensibilidad del sensor a los gestos con la instrucción APDS.setGestureSensitivity(sensitivity) siendo sensitivity un valor entre 0 y 100. Por defecto su valor es de 80. A más alto sea su valor más sensibilidad tendrá a los gestos, lo que significa que captará los mejor los gestos pero a cambio será más probable que lea gestos «erroneos».

Leer colores con Arduino Nano 33 BLE Sense (sensor APDS9960)

Arduino Nano 33 BLE Sense incorpora el sensor APDS9960 que permite leer el color de lo que «tiene delante». Para ellos devuelve el color en formato RGB, esto es que devuelve tres valores con los componentes rojo (red), verde (green) y azul (blue) del color. Aunque el valor de estos tres componentes se devuelva en una variable integer de 16 bits realmente solo se usan 12 bits (0…4095). En realidad el mayor valor que he logrado leer del sensor es 4097 que para eso necesita usar 13 bits pero se puede considerar el 4095 como máximo para simplificar.

Para tener más información del sensor se puede consultar su datasheet aquí.

Lo primero es incluir la librería correspondiente. Se encuentra en el gestor de librerías de Arduino por lo que solo hay que buscarlo para instalarlo:

#include <Arduino_APDS9960.h>

Al incluirla se puede acceder al objeto APDS:

  • Se usa APDS.begin(); para inicializar la lectura de datos. Devuelve true si todo ha ido bien y false si ha ocurrido algún error.
  • Es necesario declarar tres variables de tipo int donde se almacenaran los valores leídos int r, g, b;
  • Para leer el color se usa APDS.readColor(r, g, b); a la que es necesario pasar los tres integer declarados antes.

Veamos como queda el código inspirado en uno de los ejemplos

#include <Arduino_APDS9960.h>

void setup() {
  Serial.begin(9600);
  while (!Serial);

  //comprueba si elsensor se ha iniciado correctamente
  if (!APDS.begin()) {
    Serial.println("Error initializing APDS-9960 sensor.");
  }
}

void loop() {
  // comprueba si hay un color disponible
  while (! APDS.colorAvailable()) {
    delay(5);
  }
  int r, g, b;
  
  // lee el color
  APDS.readColor(r, g, b);

  Serial.print("r = ");
  Serial.println(r);
  Serial.print("g = ");
  Serial.println(g);
  Serial.print("b = ");
  Serial.println(b);
  Serial.println();

  delay(2000);
}

Un detalle importante a tener en cuenta es que el formato RGB que devuelve el sensor tiene 12 bits por componente (color de 36 bits). El color RGB más habitual tiene 8 bits por componente (color de 24 bits) por lo que es probable que la mayoría de los algoritmos y librerías que se encuentren usen ese formato y sea necesario adaptar los valores.

Relato: Nuestro peor enemigo

Ocurrió en una de esas salas oscuras, que sólo tienen una luz colgando del techo. En esas salas siempre se hablan de cosas secretas. Debajo de la luz una mesa circular, sentados alrededor de ella, en el lugar donde la luz se funde con las sombras, doce seres, puede que humanos, estaban sentados observando al resto de las sombras, nerviosos.

Una sombra se levanta, carraspea, duda, mira hacia una sombría audiencia silenciosa.

– Toda crisis es una oportunidad – Un silencio sombrío es la única respuesta que obtiene. Prosigue. – Los datos son claros. No tenemos alternativa. A consecuencia de la guerra todos los planetas habitables a varias decenas de años luz están a punto de dejar de serlo. La armas biológicas, químicas y nucleares han destruido prácticamente todas nuestras colonias y las pocas que no han destruido las han dañado de forma irreparable

Un murmullo de voces, las sombras se muestran agitadas. Una pregunta:

– ¿Y nuestros enemigos?

– Los informes dicen que se enfrentan a una situación similar. Hemos destruido sus colonias y se enfrentan a su propia extinción

Murmullos de aprobación mientras la cara del orador se pone seria

– ¡Caballeros! La situación de nuestros enemigos no afecta a la nuestra. No tenemos ningún planeta habitable ni terraformable en decenas de años luz. Esto podría ser el fin de nuestra especie.

Silencio dramático

– Nuestros científicos e ingenieros más cualificados han estado trabajando en una solución, el programa arca. Tres enormes naves con capacidad para 10000 personas cada una. Partirán en busca de nuevos planetas en un viaje que durará cientos de años. A los nuevos planeta llegarán los tataranietos de los actuales tripulantes. Esto nos da la oportunidad de crear una nueva sociedad, de enseñarles los valores correctos, de enseñarles a no cometer nuestros mismos errores.

Un voz se alza desde las sombras:

– Una duda ¿Es posible que nuestros enemigos estén pensando hacer lo mismo?

– Es probable.

– ¿Y qué pasaría si ellos llevan armas?

Las sombras cuchichean nerviosas

– ¡No podemos descartar esa opción!

– ¡No salvamos a la humanidad para ser sus esclavos!

– Podríamos reducir el número de pasajeros y subir armas a las naves

– Y dar formación militar a todos los niños.

– Exacto, prepararles por si les atacan

– ¿Y por qué esperar? ¡Podríamos dar el primer golpe!

– ¡Cierto! Podríamos llenar la naves de armas y soldados y así cogerlos por sorpresa

– ¡Serán nuestros esclavos!

En otra sala mal iluminada, muy lejos de ahí, otras sombras, puede que humanos, quizás no, tenían la misma conversación.

Leer temperatura y humedad con Arduino Nano 33 BLE Sense (sensor HTS221)

Arduni Nano 33 BLE Sense incluye un sensor (HTS221) para medir la temperatura y la humedad. Su uso es muy sencillo.

Los sensores tienen las siguientes características:

SensorRango de medidaError
Temperatura-40 ºC hasta 120 ºC± 0.5 °C entre los 15 ºC y 40 °C
Humedad0% hasta 100%± 3.5% entre el 20% y el 80%

 Para ver más información del sensor se puede recurrir a su datasheet.

Para leer los datos lo primero es incluir la librería necesaria para leer los datos del sensor. 

#include <Arduino_HTS221.h>

La librería está disponible desde el gestor de librerías del IDE de Arduino, tan solo hay que buscarla por el nombre «Arduino_HTS221» e instalarla.

Una vez instalada su uso es muy sencillo:

  • Se usa HTS.begin() para inicializar la lectura de datos. Devuelve true si todo ha ido bien y false si ha ocurrido algún error.
  • Para leer la temperatura se usa HTS.readTemperature() que devuelve un float con la temperatura en grados Celsius.
  • Para leer la humedad se usa HTS.readHumidity() que devuelve un float con la humedad en porcentaje.
  • Si se desea finalizar el uso del sensor y liberar recursos se llama a HTS.end()

¡Ya esta!. No hay más que hacer.

Veamos uno de los ejemplos que viene con la librería:

#include <Arduino_HTS221.h>

void setup() {
  Serial.begin(9600);
  while (!Serial);

  if (!HTS.begin()) {
    Serial.println("Failed to initialize sensor!");
    while (1);
  }
}

void loop() {
  // read all the sensor values
  float temperature = HTS.readTemperature();
  float humidity    = HTS.readHumidity();

  // print each of the sensor values
  Serial.print("Temperature = ");
  Serial.print(temperature);
  Serial.println(" °C");

  Serial.print("Humidity    = ");
  Serial.print(humidity);
  Serial.println(" %");

  // print an empty line
  Serial.println();

  // wait 1 second to print again
  delay(1000);
}

Por último, si en lugar de necesitar la temperatura en grados Celsius la necesitas en grados Farenheit, se puede hacer con la siguiente instruccion:

HTS.readTemperature(FAHRENHEIT );

Ejemplo de fragmentación de memoria con Arduino

En dispositivos con poca memoria RAM, como es el caso de Arduino, cuando se asigna memoria de forma dinámica puede surgir el problema de la fragmentación. Se produce cuando la memoria libre esta fragmentada en trozos tan pequeños que no se puede reservar suficiente memoria contigua libre aunque la suma total de memoria libre es mayor que la que se trata de reservar.

Veamos un ejemplo de como se produce esta fragmentación, para ello empezaremos reservando un bloque de 1000 bytes de memoria (memory1) para limitar la memoria disponible. Luego reservamos un bloque de memoria de 300 bytes (memory2) otro de un byte (memory3) y otro de 300 (memory4) de nuevo. La función de ese bloque de un único byte es evitar que al liberar los otros dos bloques estos puedan unirse en un mismo bloque. Liberamos los dos bloques de 300 bytes por lo que ahora debería haber libre un mínimo de 600 bytes libres, sin embargo si tratamos de reservar 500 bytes (memory5) y no puedo (el puntero tiene valor 0). Hay memoria libre suficiente pero esta divida en bloques más pequeños del que necesito. Con solo un byte estratégicamente colocado hemos causado un problema de fragmentación. Los bloques más pequeños (memory6) pueden reservar memoria sin problemas.

byte* memory1;
byte* memory2;
byte* memory3;
byte* memory4;
byte* memory5;
byte* memory6;

void setup() {  
  Serial.begin(9600);
  while (!Serial) {
    ; // esperamos a que el puerto este inicializado 
  }

  memory1 = (byte*) calloc (1000, sizeof(byte)); //reservamos 1000
  memory2 = (byte*) calloc (300, sizeof(byte)); //reservamos 300
  memory3 = (byte*) calloc (1, sizeof(byte)); //reservamos 1
  memory4 = (byte*) calloc (300, sizeof(byte)); //reservamos 300
  free(memory2); // liberamos 300
  free(memory4); //liberamos 300 
  memory5 = (byte*) calloc (500, sizeof(byte)); //reservamos 500
  memory6 = (byte*) calloc (300, sizeof(byte)); //reservarmos 300

  Serial.println((long)memory1);
  Serial.println((long)memory2);
  Serial.println((long)memory3);
  Serial.println((long)memory4);
  Serial.println((long)memory5);
  Serial.println((long)memory6);
}
 
void loop() {

}

Veamos un ejemplo de salida:

466
1468
1770
1774
0
1468

Se puede ver que memory5 no puede reservar memoria mientras que a memory6 se le asigna el primer bloque libre que coincide con el memory2 que acaba de ser liberado.

Este ejemplo esta pensado para una placa Arduino UNO en caso de usarlo en otra placa habrá que ajustar el tamaño del primer bloque.

Este texto mejorado y ampliado forma parte de mi libro sobre como mejorar tus programas en Arduino. Puedes echarle un vistazo aquí.

Puedes ver el ejemplo en el siguiente vídeo:

Haz click para ver el vídeo en mi canal de Youtube

Arduino, leer puerto serie usando serialEvent

SerialEvent es una función que se llama automáticamente cuando hay datos esperando a ser leídos en el puerto serie. Se invoca tras cada iteración de la función loop, por lo que cualquier delay o bloqueo en la función loop retrasa su invocación. Es una forma sencilla de integrar la gestión de los datos por puerto serie nuestro código.

void setup() {
  Serial.begin(9600);
}

void loop() {

}

void serialEvent() {
  //mientras tenga datos que leer
  while (Serial.available()) {
	//leer datos
  }	
}

Para las placas que tienen más de un puerto serie existe una función serialEvent distinta para cada uno:

  • Serial – serialEvent
  • Serial1- serialEvent1
  • Serial2 – serialEvent2
  • Serial3 – serialEvent3

Esta función no esta disponible para todos los puerto serie de todas las placas, por ejemplo no esta disponible para las placas basadas en microcontroladores SAMD, para Arduino Due o las placas Leonardo, Micro, o Yún que no funciona serialEvent (serialEvent1 si que funciona).

Este texto mejorado y ampliado forma parte de mi libro sobre como mejorar tus programas en Arduino. Puedes echarle un vistazo aquí.

Trabajar con datos tipados de un dispositivo desde una página web

Cada vez hay más integración entre la web y la domática, «internet of things» o «web of things» son ejemplos de ello. Uno de los problemas que te puedes encontrar es que a nivel de programación son dos mundos muy dispares. En los dispositivos electrónicos reina C/C++ y similares, mientras que en la web el más usado es JavaScript. Aunque ambos lenguajes no trabajan directamente uno con otro lo hacen a través de API a veces surge el problema tratar los datos y es que mientras que C tiene un tipado de datos muy específico el de JS es menos concreto. ¿Qué podemos hacer cuando necesitamos un tipado de datos muy concreto que nos permita incluso trabajar a nivel de bits?. Simplemente usar datos tipados en JS.

Datos tipados en JavaScript

Pese a su fama JS tiene más tipos de datos que los conocidos String, boolean, number.

En este caso vamos a centrarnos en los siguientes tipos que podemos usar para imitar los tipos habituales de C/C++. Podemos elegir el tamaño en bits, si es con signo o sin signo incluso la forma en que se comporta cuando hay un desbordamiento. La única pega, solo funciona con arrays lo que nos supone una incomodidad a la hora de trabajar con un único elemento (tendremos que usar un array de tamaño 1).

TipoRangoTamaño (bytes)DescripciónTipo en C/C++
Int8Array -128…12718-bit con signoint8_t
Uint8Array 0…25518-bit sin signouint8_t
Uint8ClampedArray 0…25518-bit sin signo (clamped)uint8_t
Int16Array -32768…32767216-bit entero con signoint16_t
Uint16Array 0…65535216-bit entero sin signouint16_t
Int32Array -2147483648… 2147483647432-bit entero con signoint32_t
Uint32Array 0…4294967295432-bit entero sin signouint32_t
Float32Array 1.2E-38…3.4E38432-bit punto flotantefloat
Float64Array 5E-324…1.8E308864-bit punto flotantedouble
BigInt64Array -2^63…2^63 – 1864-bit entero con signoint64_t
BigUint64Array 0…2^64 – 1864-bit entero sin signouint64_t

Lo valores en punto flotante siguen el estándar IEEE.

Veamos algunos ejemplos de como usarlos y alguna propiedades interesantes (BYTES_PER_ELEMENT, byte que ocupa cada elemento; byteLength, longitud total del array en bytes):

var int8 = new Int8Array(3);
int8[0] = 42;
console.log(int8[0]); //42
console.log(int8.length); //3
console.log(int8.BYTES_PER_ELEMENT); //1
console.log(int8.byteLength); //3 = length * BYTES_PER_ELEMENT

var int16 = new Int16Array(3);
int16[0] = 42;
console.log(int16[0]); //42
console.log(int16.length); //3
console.log(int16.BYTES_PER_ELEMENT); //2
console.log(int16.byteLength); //6 = length * BYTES_PER_ELEMENT

Un dato clamped (acotado) es un dato que cuando se produce un desbordamiento de su valor por arriba o por abajo el dato toma su mayor y menor valor. O más simplemente explicado si intentas asignarle un valor mayor de 255 o menor que 0 se le asignará el valor 255 y 0 respectivamente. Lo datos que no son de tipo clamped simplemente ignoran los bits del desbordamiento. Podemos verlo mejor con un ejemplo:

var uInt8Clamped = new Uint8ClampedArray(1);
var uInt8 = new Uint8Array(1);

uInt8Clamped[0] = 256;
uInt8[0] = 256;
console.log(uInt8Clamped[0]); //255 
console.log(uInt8[0]); //0

uInt8Clamped[0] = 265;
uInt8[0] = 265;
console.log(uInt8Clamped[0]); //255 
console.log(uInt8[0]); //9

ArrayBuffer y Dataview

Aún hay un truco más que permite JS para trabajar con estos datos. Crear un ArrayBuffer que permite reservar un «espacio de memoria» indicando el tamaño del mismo en bytes. Desde un ArrayBuffer no puedes ni leer ni escribir esta memoria, para ello tienes que asignarlo a un array tipado como los uqe hemos visto antes. Puedes asignar el mismo ArrayBuffer a varios arrays tipados que lo compartirán:

var buffer = new ArrayBuffer(2);
var view8 = new Uint8Array(buffer);
var view16 = new Uint16Array(buffer);
view16[0] = 258;
console.log(view16[0]);// 258 -> 00000001 00000010
console.log(view8[0]); // 2 -> 00000010
console.log(view8[1]); // 1 -> 00000001

En el ejemplo se ve como un ArrayBuffer de 2 bytes se puede leer desde un array de 1 elemento de 16 bits o un array de un array de 2 elementos de 8 bits y como los valores se solapan.

Hay otra forma de hacerlo, con un DataView. Las ventajas de los DataView son dos:

  • Permiten escribir y leer en el ArrayBuffer con cualquier tipo de datos usando el métodoset y get correspondiente getUint8(), setUint8(), getInt8(), setInt8(), getUint16(), setUint16(), …
  • El orden en que se recuperan los bytes es más intuitivo

El ejemplo anterior con DataView

const buffer = new ArrayBuffer(2);
var view = new DataView(buffer, 0);
view.setUint16(0, 258); // (max unsigned 16-bit integer)
console.log(view.getUint16(0));// 258 -> 00000001 00000010
console.log(view.getUint8(0)); // 1 -> 00000001
console.log(view.getUint8(1)); // 2 -> 00000010

Gamificación en el diseño del software

La gamificación consiste en aplicar mecánicas del juego a otros entornos para lograr un refuerzo de los comportamientos deseados del usuario. Es útil conocer sus mecanismos para poder aplicarlos al diseño de software al que puede aportar varios beneficios:

  • Aumenta el engagement.
  • Reduce el estrés del usuario.
  • Motiva el uso del software.
  • Reduce el esfuerzo de aprender a usarlo.
  • Motiva que los usuarios compartan su experiencia y avances entre sus conocidos ayudando a la difusión.

Tipos de jugadores:

Segun Richard Bartle:

  • Achievers (10%): tienen como objetivo resolver retos con éxito y conseguir una recompensa por ello.
  • Explorers (10%): quieren descubrir y aprender cualquier cosa nueva o desconocida del sistema.
  • Socializers (80%): sienten atracción por los aspectos sociales por encima de la misma estrategia del juego.
  • Killers (1%): buscan competir con otros jugadores

Jugadores VS. Mundo: algunos usuarios (Socializers y Killers) que buscan relacionarse, sea del modo que sea, con otros usuarios, mientras que otros (Explorers y Achievers) prefieren dinámicas que les permitan relacionarse con el mundo del sistema.

Interacción VS. Acción: algunos usuarios (Killers y Achievers) quieren actuar directamente sobre algún elemento, ya sea otro usuario o el propio sistema, mientras que otros (Socializers y Explorers) prefieren dinámicas de interacción mutua.

Mecánicas de juego:

Las mecánicas de juego son aquellas reglas que consiguen que la actividad se asimile a un juego o a una actividad lúdica, pues consiguen la participación y el compromiso por parte de los usuarios a través de una sucesión de retos y barreras que han de superar.  Existen muchas mecánicas de juego distintas, pero cabe destacar:

  • Recolección: se usa la afición de coleccionar de los usuarios y la posibilidad de presumir ante nuestras amistades de estas colecciones.
  • Puntos: trata de incentivar al usuario mediante un sistema de puntos con el que conseguir algo, como prestigio o premios.
  • Comparativas y clasificaciones: someten a los usuarios a un sistema de clasificación que tiene en cuenta su implicación en la actividad. De esta manera se explota el espíritu competitivo de los usuarios.
  • Niveles: con este sistema se premia la implicación del usuario en la actividad otorgándole un nivel o descripción con el que distinguirse del resto, y que anima a los usuarios nuevos a igualarlos.
  • Realimentación o feedback: cuando el sistema responde a las actividades del usuario, éste valora que el trabajo que ha hecho tiene una implicación relevante.
  • Reconocimiento social: Se da a conocer públicamente que logros o acciones ha realizado el usuario

Prácticas para aumentar el engagement:

  • Adaptación hedónica: premiar cada vez que se entra de alguna forma. Se crea la sensación de que es necesario entrar a diario. Sentimiento de beneficio si se entra de pérdida si no se entra.
  • Generar tareas incompletas (Efecto Zeigarnik): el usuario tiene tendencia a recordar las tareas no terminadas y sentir la necesidad de darles fin. La forma es estructurar la gamificación por “capítulos” que indiquen, u obliguen, cuando parar pero que dejen tareas pendientes.
  • Sensación de control: el usuario debe de tener la sensación de que sus acciones son las que causan todo.
  • Captar la atención rápidamente: un usuario prueba entre entre 90 segundos y 3 minutos antes de decidir si sigue. Hay que engancharle pronto y darle alguna recompensa por hacerlo bien lo antes posible. No perder tiempo, no dejarle “explorar” ir directos a engancharle.
  • Interactividad: el usuario debe de estar realizando acciones, no es un ente pasivo.
  • Perseguir al usuario: notificaciones, emails, insistir sin resultar pesado.
  • Refuerzo positivo: hacer que el usuario sienta que es realmente bueno en su labor. Alabar su desempeño. 
  • Interacciones personalizadas: usar el nombre del usuario, un trato cercano y el humor aumenta el éxito de las interacciones.
  • Crear competición: compararse con los demás incentiva a avanzar para superarles.

Que hace a una gamificación buena:

  • No ha de ser intrusiva, se tiene que poder usar el software sin participar en el juego.
  • Orientada a unir al equipo más que a la competición.
  • Permite socializar (el 80% de los jugadores son sociales).
  • No es cuestión de hacer que el trabajo sea un juego es cuestión de dar un extra para incentivar.
  • Buscar el refuerzo positivo y premiar. Evitar el castigo y las penalizaciones.
  • Hay tareas o momento mentalmente desagradables, la gamificación puede hacer el trance más fácil.
  • Hay que tratar de integrarla dentro de “una historia” que motive al usuario a seguir a delante.
  • Tener objetivos personalizados.
  • Permitir compararse con los demás promoviendo la sana competencia.
  • Ser útil, no ha de ser simplemente dar puntos o premios, los objetivos propuestos deben de tener un fin más allá de simplemente “enganchar” al usuario.

Transformar un valor de una escala a otra en Arduino. Map

Hay veces que tenemos un valor expresado en una escala y queremos pasarla a otra. Por ejemplo uno de los casos más habituales es el calcular un porcentaje. Que pasamos un número expresado en una escala a otra que va de 0 a 100. En Arduino para pasar un valor de un rango o escala [in_min, in_max] a otro [out_min, out_max] podemos usar la función map.

long map(long x, long in_min, long in_max, long out_min, long out_max)

La función map toma 5 parámetros:

  • x: Valor a transformar
  • in_min: Valor más bajo de la escala origen
  • in_max: Valor más alto de la escala origen
  • out_min: Valor más bajo de la escala destino
  • out_max: Valor más alto de la escala destino.

La función tiene la siguiente implementación interna:

long map(long x, long in_min, long in_max, long out_min, long out_max) { 
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 
}

Os habréis dado cuenta de que trabajo con long ¿Qué pasa si queremos otro tipo de datos?. Muy fácil, nos hacemos nosotros mismos la función:

float floatMap(float x, float in_min, float in_max, float out_min, float out_max) { 
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 
}


double doubleMap(double x, double in_min, double in_max, double out_min, double out_max) { 
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 
}

Optimizar la función map

Ya que nos hemos puesto a crear nuestras funciones veamos como podemos optimizar los cálculos, si siempre tenemos que usar la función map con los mismos rango de números podemos crear nuestra versión personalizarla para que se ejecute más rápido. Es tan sencillo como sustituir los valores min y max de cada rango y simplificar. Veamoslo con un ejemplo, vamos a crearnos nuestro propio map que transforme de la escala de 0 a 1023 a otra escala de 0 a 255.

in: 0..1023
out: 0..255
int_min = 0
int_max = 1023
out_min = 0
out_max = 255

Reemplazamos:

long customMap(long x) { 
    return ((x - 0) * (255 - 0) / (1023 - 0)) + 0; 
}

Simplificamos donde podamos:

long customMap(long x) { 
    return (x * 255) / 1023; 
}

Ahora tenemos nuestra función personalizada que realiza solo dos operaciones. Como no es necesario pesarle los rangos usa solo un parámetro.