Una forma sencilla de meter un delay en una maquina de estados finitos.

EXTRACTO:
El autor propopone una manera sencilla de usar un soft-delay para cambiar los estados de una maquina de estados finitos, sin necesidad de librerias o descargas extras.

Bueno, me encuentro trabajando en dos proyectos que hacen necesario y extensivo el uso de maquinas de estados finitas, Normalmente las escribo como una lista en el Swtich /case y pongo condicionales para ir a la parte del programa que quiero usar, no he tenido mayores problemas.

El problema me vino hoy cuando me di cuenta de que la maquina de estados se efectuaria demasiado rapido (no me mal entiendan, debe ser muy rapida) lo que no debe ser tan rapido es la activación de relevadores mecanicos y cambios en el sistema.

Un ejemplo sencillo:

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  void maquina(); 
}

void maquina()
{
 switch(var): 

case 0: Serial.println("Maquina en estado 0");
var = 1; 
break; 

case 1: Serial.println("Maquina en estado 1");
var = 2; 
break; 

case 2: Serial.println("Maquina en estado 1"); 
var = 0; 
break; 
}

Entonces, digamos que necesitamos que entre el estado 0 y el 1 haya un retardo de 3 segundos,
normalmente meteremos un if(millis() - tiempo < delay), y una vez que expire cambiamos la variable a la siguiente.

Este proceso reuqueriria de muchas lineas de codigo, asi que me he escrito una función que toma el delay para hacer el cambio entre una y otra variable, de manera automatica;

byte var   = 0; 
byte var_2 = 0; 

unsigned long var_time = 0; 
unsigned long var_delay = 3000UL; 

void setup() 
{
  Serial.begin(9600); 
  Serial.println("inciando programa"); 
}

void loop() {
  
  switch (var_2)
  {
     case 0: 
             Serial.println("Maquina en estado 0");    
             var_2 = changueVar(var, 1); 
             //var_time = millis(); 
             break; 
     
     case 1: 
             Serial.println("Maquina en estado 1");    
             var_2 = changueVar(var, 2);
             break; 
    
     case 2: 
             Serial.println("Maquina en estado 2");    
             var_2 = changueVar(var, 3);
             break; 
    
     case 3: 
             Serial.println("Maquina en estado 3");    
             changueVar(var, 1);
             break; 
  }
}



byte changueVar(byte var, byte n_var)
{ 
  static bool first_time = true; 
  
  if(first_time == true)
  {
    //Serial.println("Estamos en el true"); 
    if(n_var != var) var_time = millis();    //Serial.println("tomamos el tiempo"); 
    first_time = false;  
  }
  
  if(millis() - var_time < 3000UL) 
   {
     //Serial.print("Millis     "); Serial.println(millis()); 
     //Serial.print("var time   "); Serial.println(var_time); 
     //Serial.print("diferencia "); Serial.println(millis()-var_time);
     //Serial.println("el timmer no se cumple "); 
     return var;    
   } 
     else
     {
       Serial.println("Se cumplio el timmer"); 
       first_time = true; 
       return n_var; 
     }
}

De esta manera me es mucho mas rapido, es reutilizable y debido a que no requiere librerías, podemos usarlo cuando nos de la gana.

Con un poco mas de trabajo se puede evitar que se "re-imprima" el serial print, o lo que es lo mismo, si ya paso el estado anterior, no hacer nada hasta que expire el timmer, haciendo la espera, sin encender o apagar nada, pero de momento me funciona como esta.

Se los dejo por que me ha costado un poco implementarlo, y les puede ahorrar algo de tiempo.

-Alex.

Bueno aparte de lo expuesto en la parte de arriba, les dejo otra implementacion que es muy util en caso de que realicen Maquinas de estado finito.

Este programa permite "correr" una maquina secundaria hasta su terminación, y despues retomar el hilo en la principal, asi, podemos partir el codigo en bloques manejables y usarlos o re-usarlos cuando y como se nos de la gana.

En una idea mas general, quizás debería hacer una pequeña libreria, para poder implementarlo de manera aun mas sencilla, pero por el momento me funciona genial.

Esta es la salida del serial:
Maq sec Nivel 1
Maq sec Nivel 2
Maq sec Nivel 3
END MAQ SEC
Maq pri Nivel 1
Maq pri Nivel 2
Maq sec Nivel 1
Maq sec Nivel 2
Maq sec Nivel 3
END MAQ SEC
Maq pri Nivel 3

Y este es el bello codigo, sirvanse reutilizarlo como requieran.

byte mp = 0;  //contador del swtich case que se desea controlar
byte ms = 0;  //contador del swtich case principal que se desea controlar 

byte pas_mp = 0; //solo para la deteccion de cambios  
byte pas_ms = 0; //solo para la deteccion de cambios


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


void loop()
{
  
  maquinaPrimaria(mp); 
  
}

//funcion que devuelve un valor boleano usando un valor de tipo byte 
//el byte de la funcion se crea solo como sintaxis, no se usa. 
bool maquinaPrimaria(byte mp_)
{
  //esta es la maquina que corre en primer plano 
  if(mp < 4)// este contador es igual a la cantidad de estados de la maquina +1 
  {         // por ejemplo esta maquina tiene 3 estados, +1 = 4.
    
    switch (mp) //usamos el contador contenido en el nivel global 
    {
      case 0:  
              //suponemos que quremos lanzar la serie de instrucciones de la maquina 
              //secundaria, asi que lo hacemos asi: 
              mp = ifEnd(maquinaSecundaria(ms) , 0, 1); 
              break; 
      
      case 1:  mp = 2; delay(1000); ms = 0; break;                           // re iniciamos la maquina 
      case 2:  mp = 3; delay(1000); mp = ifEnd(maquinaSecundaria(ms) , 2, 3);  break; 
      case 3:  mp = 4; delay(1000); break; 
    }
    
    if(pas_mp != mp) 
    {
        if(mp ==0) Serial.println("Maq pri Nivel 0");
        if(mp ==1) Serial.println("Maq pri Nivel 1"); 
        if(mp ==2) Serial.println("Maq pri Nivel 2"); 
        if(mp ==3) Serial.println("Maq pri Nivel 3");
        pas_mp = mp; 
        return false; 
    }

  }
  else 
  {
    return true; 
  }

}

//esta maquina es la quede debe ejetutarse en segundo plano. 
bool maquinaSecundaria( byte ms_ )
{
  
  if(ms < 4) // este contador es el numero de estados de la maquina +1 
  {
    switch (ms)
      {
        case 0:  ms = 1; delay(1000); break; 
        case 1:  ms = 2; delay(1000); break; 
        case 2:  ms = 3; delay(1000); break; 
        case 3:  ms = 4; delay(1000); break; 
      }
     
      if(pas_ms != ms) 
    {
        if(ms ==0) Serial.println("               Maq sec Nivel 0");
        if(ms ==1) Serial.println("               Maq sec Nivel 1"); 
        if(ms ==2) Serial.println("               Maq sec Nivel 2"); 
        if(ms ==3) Serial.println("               Maq sec Nivel 3");     
        pas_ms = ms; 
    }
      return false;
  }
  else
      {
        Serial.println("               END MAQ SEC"); 
        return true;   // si la maquina se completo satisfactoriamente regresamos true. 
      }
}


// funcion que detecta el estado de la fucnion y de acuerdo a eso cambia el 
//contador de la maquina principal
byte ifEnd (bool ms_, byte s1, byte s2) 
//bool ms_ el estado booleano de la maquina secundaria. 
//s1 estado actual de la maquina principal
//s2 estado si se cumple la maquina secundaria
{
   if (ms_ == false ) {return s1 ; delay(1000);  } 
   if (ms_ == true  ) {return s2 ; delay(1000);  }
}

void startM(byte stat) 
{
//funcion que reinicia la maquina que necesitemos en el momento justo 

}