Asserts de código en Arduino

Un assert es una comprobación que se realiza en el código para verificar que todo es correcto, si la comprobación no es correcta el programa termina. En el caso de Arduino se llama a la función abort() que deshabilita todas las interrupciones y entra en un bucle infinito. Es una medida de precaución para evitar continuar ejecutando un programa cuando algo va mal y no tiene solución o no se sabe que es lo que falla.

Para usar asserts es necesario incluir la librería assert.h. Un assert se escribe usando la función assert pasando como parámetro una comparación lógica que será verdadera o falsa. Si es verdadera el programa continuará, si es falsa se detendrá.

Veamos un ejemplo:

#include <assert.h>
int a = 5;
int b = 1;
int c;

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

void loop() {
  a--;
  divide();
  delay(1000);
}

void divide(){
  assert(a != 0);
  c = b/a;
  Serial.print(b);
  Serial.print("/");
  Serial.print(a);
  Serial.print(" = ");
  Serial.println(c);
}

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

Puedes echar un vistazo a la versión en vídeo de este entrada:

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

Crear un detector de metales con un magnetómetro o brújula digital.

Vamos a usar un magnetómetro o brújula digital como detector de metales. Para ello vamos a convertir un problema en una ventaja. Cualquier metal magnético cerca del magnetómetro mete ruido en las lecturas, mucho ruido tanto que puede imposibilitar leer los resultados. Usaremos ese ruido para detectar que estamos cerca de un metal magnético o un imán.

En mi caso usaré el magnetómetro del Arduino Nano 33 BLE Sense (ya vimos como leerlo) aunque la ideas sirve cualquier otro magnetómetro, habrá que adaptar el código y los valores empleados.

Realmente es muy sencillo crear un detector de metales. Basta con detectar el ruido y es un ruido tan grande que deja ridículas las lecturas del campo magnético terrestre. Para ello mediremos la intensidad del campo magnético. Lo hacemos elevando al cuadrado el valor de cada eje de magnetómetro, sumándolas y calculando su raíz cuadrada. El del Arduino Nano Sense tiene tres ejes por lo que el calculo lo haremos con el siguiente código.

 IMU.readMagneticField(x, y, z);
 float field = sqrt((x*x) + (y*y) + (z*z))

Ese valor lo podemos comparar con distintos umbrales calculados a base de prueba y error para saber lo cerca que estamos de un metal. En este caso usaremos cuatro niveles, cada uno asociado a un color del LED RGB. Desde «no se detecta nada» a «estas encima de un metal». Lo colores de menos a más son: blanco, verde, amarillo, rojo.

Para evitar verse influido por las condiciones del entorno (por ejemplo que haya metal cerca que hace que siempre se active nuestro detector) puede ser necesario calcular un «offset inicial» cuando se activa el microcontrolador. Este offset es una lectura inicial (la situaremos en la función setup) que se usará para ajustar el valor del campo en el resto de las lecturas. Para decidir si se realiza esta lectura o no se se realiza a través de una constante CALCULATE_OFFSET

if(CALCULATE_OFFSET){
  while(!IMU.magneticFieldAvailable()){    
  }
  IMU.readMagneticField(x, y, z);
  offset = sqrt((x*x) + (y*y) + (z*z));
}

Para corregir el valor del campo respecto de este offset basta con restarlo:

float field = sqrt((x*x) + (y*y) + (z*z))-offset; 

Vamos a juntar todo esto en un ejemplo:

#include <Arduino_LSM9DS1.h>

const bool CALCULATE_OFFSET = true;
float offset = 50; //valor por defecto
float x, y, z;

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

  if (!IMU.begin()) {
    Serial.println("Error al inicializar IMU!");
    while (1);
  }
  if(CALCULATE_OFFSET){
    while(!IMU.magneticFieldAvailable()){    
    }
    IMU.readMagneticField(x, y, z);
    offset = sqrt((x*x) + (y*y) + (z*z));
  }

  // activar todos los pins del led RGB
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);

}

float oldX, oldY, oldZ;

void loop() {

  float difX, difY, difZ;

  if (IMU.magneticFieldAvailable()) {
    IMU.readMagneticField(x, y, z);
    float field = sqrt((x*x) + (y*y) + (z*z))-offset; 
    if(field > 450){
      Serial.print(field);
      Serial.println(" metal");
      //rojo
      digitalWrite(LEDR, LOW);
      digitalWrite(LEDG, HIGH);
      digitalWrite(LEDB, HIGH);
    } else if(field > 150){
      Serial.print(field);
      Serial.println(" metal");
      //amarillo
      digitalWrite(LEDR, LOW);
      digitalWrite(LEDG, LOW);
      digitalWrite(LEDB, HIGH);
    } else if(field > 20){
      Serial.print(field);
      Serial.println(" metal");
      //verde
      digitalWrite(LEDR, HIGH);
      digitalWrite(LEDG, LOW);
      digitalWrite(LEDB, HIGH);
    } else {
      Serial.println(field);
      digitalWrite(LEDR, HIGH);
      digitalWrite(LEDG, HIGH);
      digitalWrite(LEDB, HIGH);
    }    
  }
}

Un ultimo detalle, si usas una placa de Arduino como detector de metales, cuida con los pines. Si entran en contacto con el metal pueden producirse un cortocircuito.

Leer el magnetómetro o brújula digital con Arduino Nano 33 BLE Sense (sensor LSM9DS1)

El modulo LSM9DS1 del Arduino Nano 33 BLE sense incluye un magnetómetro o brújula digital de 3 ejes. X , Y y Z. Su función principal es detectar el campo magnético terrestre en cada uno de los ejes de la placa. El eje X va a lo ancho de la placa, el Z a lo largo y el Y es perpendicular.

El primer paso es incluir la librería

#include <Arduino_LSM9DS1.h>

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

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

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

  if (IMU.magneticFieldAvailable()) {

  }

Para obtener el valor se usa IMU.readMagneticField(x, y, z); Siendo x, y, z tres variables de tipo float donde devolverá los valores del campo magnético en el eje correspondiente:

IMU.readMagneticField(x, y, z);

El campo magnético de mide en microteslas

Veamos todo el código de ejemplo:

#include <Arduino_LSM9DS1.h>

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

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

float x,y,z;

void loop() {
  // comprobar si hay una lectura disponible
  if (IMU.magneticFieldAvailable()) {    
    IMU.readMagneticField(x, y, z);
    Serial.print("x: ");
    Serial.print(x);
    Serial.print(" y: ");
    Serial.print(x);
    Serial.print(" z: ");
    Serial.println(z);
  }
  delay(500);  
}

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í.