Excepciones y control de errores en Arduino

Uno de los puntos débiles de las placas más básicas de Arduino es la gestión de errores, siguen la política de “nunca me equivoco” de tal forma que acciones o código que en otros sistemas lanzarían error no producen ninguno en Arduino.

Un excepción es un mecanismo en el software que “salta” cuando se produce un error y permite que el software ejecute una rutina de gestión de errores que se ocupa de gestionarlo para después recuperar su funcionamiento normal, si es posible. Arduino no permite usar el mecanismo habitual de C++, las instrucciones try y catch. Es posible usarlas en el código sin que den error al compilar pero durante el proceso de compilación se desactiva su funcionamiento. No es algo que se haga de forma arbitraria, simplemente no funcionan en Arduino. Si embargo vamos a tratar de conseguir un mecanismo similar que nos permite una gestión de errores muy básica . Para ello emplearemos dos funciones (setjmp y longjmp) y un tipo de variable (jmp_buf). esta funciones permiten establecer un punto de salto dentro de una función al que se puede ir desde cualquier parte del programa:

  • setjmp – establece el punto donde se va recuperar la ejecución del programa hay que pasarle como parámetro una variable de tipo jmp_buf.
  • jmp_buf – es un tipo de variable que almacena la información necesaria para restaurar la ejecución del código en el punto indicado por setjmp.
  • longjmp – salta al punto establecido por setjmp. Requiere dos parámetros uno de tipo jmp_buf que indica a que setjmp va a saltar y otro que indica el código de error que va devolver setjmp tras el salto. Este código nunca puede ser 0 si se usa 0 devolverá 1.

Ahora veamos como se usa, para entenderlo bien el código hay que saber los siguientes detalles:

  • Es necesario incluir la librería setjmp.h
  • setjmp la primera vez que se llama se usa para fijar el punto de salto y devuelve el valor cero
  • Cuando se llama a longjmp es como si el programa continuara desde donde se llamo a setjmp pero en lugar de devolver 0 devuelve otro valor
  • Para que esto funcione correctamente setjmp tiene que estar dentro de un if o un switch.
  • Para que todo funcione correctamente setjmp ha de fijar el punto de salto dentro del loop, si lleva a otras funciones puede fallar al llegar al final de la función donde ha saltado

En el caso más simple solo tenemos un tipo de errores y por tanto cada vez que se produce un error la forma de gestionarlo es siempre la misma. Para ello basta con usar un if.

#include <setjmp.h>

jmp_buf exception_mng;
int a = 5;
int b = 1;
int c;

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

void loop() { 
  if (setjmp(exception_mng)) { //si se produce error
		Serial.println("EXCEPTION");
  }
  a--;
  divide();
  delay(1000);    
}

void divide(){
  if(a == 0){
    longjmp(exception_mng, 1);
  }
  c = b/a;  
  Serial.print(b);
  Serial.print("/");
  Serial.print(a);
  Serial.print(" = ");
  Serial.println(c);
}

Una versión más avanzada permite gestionar distintos tipos de incidencias según el valor que se le pase a longjmp como segundo parámetro

#include <setjmp.h>

jmp_buf exception_mng;
int a = 5;
int b = 1;
int c;

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

void loop() { 
  switch (setjmp(exception_mng)) {
	case 0: //sin errores
		break;
  case 1: //division por cero
		Serial.println("EXCEPTION DIVISION BY 0");
    break;
  case 2: //divisor negativo
		Serial.println("EXCEPTION DIVISION BY NEGATIVE NUMBER");
    break;
  default: //se ejecuta cuando no se cumple ninguno de los casos anteriores
		Serial.println("GENERIC EXCEPTION");
    break;

  }
  a--;
  divide();
  delay(1000);    
}

void divide(){
  if(a == 0){
    longjmp(exception_mng, 1);
  }
  if(a < 0){
    longjmp(exception_mng, 2);
  }
  c = b/a;  
  Serial.print(b);
  Serial.print("/");
  Serial.print(a);
  Serial.print(" = ");
  Serial.println(c);
}

Este sistema nos dota de un mecanismo básico de gestión de errores aunque nos obliga a nosotros a realizar la comprobación para lanzar la “excepción”.