Cómo NO usar delay


#1

El Altair usa un pequeño micro-controlador sin Sistema Operativo, lo cuál significa que sólo puede ejecutar un programa a la vez, esto no significa que no pueda hacer muchas cosas simultáneamente.

Muchas veces vamos a querer que el Altair haga las cosas más despacio para que sean perceptibles a nosotros (por ejemplo, el parpadear de un LED), sin embargo la característica principal del Altair; su conectividad con la plataforma de Aquila nos limita un poco en ese aspecto, ¡él necesita avisar todo el tiempo lo que está pasando! y el delay lo detiene, eso haría que el sistema no funcione como debería.
De hecho, esto es útil inclusive si no inscribes nada de Aquila.
Por eso, hoy te voy a enseñar cómo NO usar delay

Si quieres saber más sobre cómo implementar la plataforma Aquila, te dejo el link de ese turorial. -> Link

#El problema

¿Por qué NO al delay? porque a pesar de que es lo primero que aprendiste al comenzar a programar con Arduino/Altair y es muy sencillo, resulta que congela el procesador por completo y no hará más cosas hasta pasado el delay.
(¿Has intentado tragar saliva y respirar al mismo tiempo? Es un claro ejemplo de cómo funciona delay. Claro que si lo logras podrías broncoaspirar; y no me hago responsable de lo que te pase :slight_smile: )

¿Recuerdas el código de Blink?

 /*
int led = 13;
 
void setup()
{                
  pinMode(led, OUTPUT);     
}
 
void loop() {
  digitalWrite(led, HIGH);
  delay(1000);
  digitalWrite(led, LOW);
  delay(1000);
}

¡El simple ejemplo de Blink se la pasa casi todo el tiempo en la función delay, así que no se puede hacer nada más! (o apenas y podría)

También, quizá conozcas el ejemplo de sweep (usando un servo motor)

#include <Servo.h> 
 
int led = 13;
 
Servo myservo;
 
int pos = 0;
 
void setup() 
{ 
  pinMode(led, OUTPUT);     
  myservo.attach(9);
} 
 
void loop() 
{ 
  digitalWrite(led, HIGH);
  delay(1000);
  digitalWrite(led, LOW);
  delay(1000);
  
  for(pos = 0; pos <= 180; pos += 1)
  {
    myservo.write(pos);
    delay(15);
  } 
  for(pos = 180; pos>=0; pos-=1)
  {                                
    myservo.write(pos);
    delay(15);
  } 
} 

Si intentas mezclar ambos códigos, de tal manera de que el LED parpadee cada segundo y que también el servo se mueva, notarás que va alternar entre las dos tareas.

#La solución

Te presento a Millis (darle click, te llevará a la página de referencia de Arduino; te recomiendo te des una vuelta)

//No necesitas equipo adicional para poder echar a andar este codigo, el pin 15 ya
//tiene integrado un LED en el ALTAIR
const int ledPin = 15; //Un bello LED color azul brillara en este ejemplo


int ledState = HIGH; //Inicialmente LED apagado
long previousMillis = 0; //Variable que se usara para saber la ultima vez que se ejecuto el if
//Estas dos variables son de tipo long porque los valores que recibe son en milisegundos
//se llenaria demasiado rapido si solo usaramos un int
long interval = 1000; //El intervalo que determina cada cuanto se entra al if
void setup() {
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}
void loop()
{
//Aqui va el codigo que se loopea, es decir, que se repite hasta el infinito
//En si lo que hace es
//La funcin millis automaticamente comienza a contar milisegundos desde que arranca el codigo
//Ese valor es asignado a la variable currentMillis

//Hace una resta: si desde la ultima vez que se reviso millis ya paso mas de 1 segundo..
//entonces la ultima vez que se excedio un segundo se actualiza y se cambia a 
// "La ultima vez que ocurrio eso fue AHORA" y con eso se cambia el estado del LED

  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > interval) //Actualmente llevamos 950ms y la ultima vez fue a los 0ms, eso es mayor a 1000? No, entonces no pasa nada..
  {
    previousMillis = currentMillis; //"La ultima vez fue AHORA"

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    digitalWrite(ledPin, ledState);
  }
}

Quizá lo primero que notes es que es un código demasiado complicado para hacer algo tan sencillo. Sí, es más complejo, pero mucho más eficiente. Y si quieres que tu Altair no se congele a la hora de hacer acciones, es mejor que sigas leyendo.

//No se requiere nada adicional para poder ejecutar este codigo en tu Altair :)

int ledPin1 = 14; // LED VERDE
int ledState1 = HIGH; // Inicialmente esta apagado
unsigned long previousMillis1 = 0; //Sera el que almacene en que ms fue la ultima vez que se encendio o apago
long OnTime1 = 250; //Tiempo que el led VERDE permanecera encendido
long OffTime1 = 750; // Tiempo que el led VERDE permanecera apagado

int ledPin2 = 15; // LED AZUL
int ledState2 = HIGH; // Inicialmente esta apagado
unsigned long previousMillis2 = 0; //Sera el que diga en que ms fue la ultima vez que se encendio o apago
long OnTime2 = 330; //Tiempo que el led AZUL permanecera encendido
long OffTime2 = 400; //Tiempo que el led AZUL permanecera apagado

void setup()
{
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
}
void loop()
{
  //Revisa si es momento de apagar el LED
  unsigned long currentMillis = millis();
  if((ledState1 == LOW) && (currentMillis - previousMillis1 >= OnTime1)) //Si el LED VERDE esta prendido y la ultima vez que se encendio fue hace mas de 250 ms entonces vamos a apagarlo!
  {
    ledState1 = HIGH; // Cambia el valor de la variable para luego escribirla
    previousMillis1 = currentMillis; // "La ultima vez fue AHORA"
    digitalWrite(ledPin1, ledState1); // Hace efectivo el cambio en el LED
  }
  else if ((ledState1 == HIGH) && (currentMillis - previousMillis1 >= OffTime1))
  {
    ledState1 = LOW;
    previousMillis1 = currentMillis;
    digitalWrite(ledPin1, ledState1);
  }
  
  
  if((ledState2 == HIGH) && (currentMillis - previousMillis2 >= OnTime2))
  {
/    ledState2 = LOW;
    previousMillis2 = currentMillis;
    digitalWrite(ledPin2, ledState2);
  }
  else if ((ledState2 == LOW) && (currentMillis - previousMillis2 >= OffTime2)) //Si el LED AZUL esta apagado y la ultima vez que se apago fue hace mas de 400 ms entonces vamos a prenderlo!
  {
    ledState2 = HIGH; // Cambia el valor de la variable para luego escribirla
    previousMillis2 = currentMillis; // "La ultima vez fue AHORA"
    digitalWrite(ledPin2, ledState2); // Hace efectivo el cambio en el LED
  }
}

Magnifico! Acabas de hacer tu primer código que es multitarea! Se ve magnífico, ¿no? Si hubieras utilizado delay esto no hubieras sido posible, o hubiera sido demasiado complicado de lograr (y por ende, ¡hubieras tenido que teclear muchísimo!)
Ahora te preguntarás, "y si quiero añadir más LEDs eso implicaría hacer mucho mucho código más, ¿no?"
Para nada, ahí ya nos estamos metiendo con la POO (ya te estás volviendo todo un Máster en programación; si es que no sabías)

#POO

POO (Programación Orientada a Objetos)
Si no estás familiarizado con ello, te recomiendo te des una vuelta por google. Aunque, básicamente habla sobre encapsular información y reutilizar código para hacerte un programador feliz.

Intentaré explicar lo mejor posible éste código, no obstante deberás buscar información respecto a la estructura y funcionamiento de las clases y en general de la POO


//Una clase no es necesariamente una funcion que se ejecute en cuanto se topa en el
//codigo, de hecho no se puede utilizar de manera directa 
//se tienen que usar "clones" para poder acceder a las variables

//Para este ejemplo no requieres nada mas que tu Altair
class Flasher
{
  //Dentro de una clase van variables y funciones. Asi como un constructor y un destructor
  //En una clase no se pueden inicializar las variables de manera directa, solo se les da nombre y tipo
  //Es ahi donde entra el constructor. Mas adelante veras como funciona.
  int ledPin; // el numero del PIN que se usara
  long OnTime; // milisegundos del tiempo encendido
  long OffTime; // millisegundos del tiempo apagado
  
  int ledState; //Se usara para escribir con digitalWrite en ledPin
  unsigned long previousMillis; // Variable que registra en que ms fue la ultima vez que hubo un cambio
  
  
  public: //public, private y protected, eso es parte de la estructura de una clase. Te lo dejo de tarea.
    Flasher(int pin, long on, long off) //Esto es un constructor (es lo que define la plantilla que se usara 
    //a la hora de "clonar" la clase para poder emplearla). Tambien nos dice que va a necesitar 3 parametros cuando se usen sus "clones".
    {
      ledPin = pin; //Y aqui es donde podemos inicilizar los valores predeterminados que tendran los clones de la clase
      pinMode(ledPin, OUTPUT);
      OnTime = on;
      OffTime = off;
      ledState = LOW;
      previousMillis = 0;
    }
    
    void Update() //Sin problemas, dentro de la clase se puede crear una funcion
    {
      // Checa si ya es momento de cambiar el estado del led
      unsigned long currentMillis = millis();
      if((ledState == LOW) && (currentMillis - previousMillis >= OnTime)) //Si el led esta encendido y han pasado N milisegundos desde la ultima vez que se encendio, entonces
      {
        ledState = HIGH; // .. entonces cambia la variable
        previousMillis = currentMillis; // "La ultima vez que se hizo el cambio fue AHORA!"
        digitalWrite(ledPin, ledState); // Actualiza el estado del LED respecto a LEDSTATE
      }
      else if ((ledState == HIGH) && (currentMillis - previousMillis >= OffTime)) //Si el led esta apagado y han pasado N milisegundos desde la ultima vez que se apago, entonces
      {
      ledState = LOW;  // .. entonces cambia la variable
      previousMillis = currentMillis; // "La ultima vez que se hizo el cambio fue AHORA!"
      digitalWrite(ledPin, ledState); // Actualiza el estado del LED respecto a LEDSTATE
      }
    }
};
//Ok ya tienes toda tu clase bien hecha y lista para comenzar a clonar, no olvides que segun la tecleaste, deberas darle 3 parametros.

//Esta es la forma en que luce un clon de la clase, es tal como creas una variable, salvo que esta es de tipo *nombre_de_clase*

Flasher led1(14, 100, 400); // Led 1 es un Flasher; usara el pin 14, estara prendido 100 ms y apagado por 400 ms (LED VERDE)
Flasher led2(15, 350, 350); // Led 2 es un Flasher; usara el pin 15, estara prendido 350 ms y apagado por 350 ms (LED AZUL)



void setup()
{
 //Woah, te preguntabas donde estaba la funcion setup. Aunque este vacia, es necesaria para poder compilar y ejecutar el codigo, asi que deberas teclearla de igual forma. 
}
void loop() //Ahora, el codigo que se ejecuta infinitas veces..
{
  led1.Update(); //Recuerdas que creaste un clon de la clase flasher? Bueno, ahora tienes acceso a las variables de la clase flasher que se llama led1
  led2.Update(); //La nomeclatura de esto es parte de las clases de POO, revisa en internet como funciona esto
}

Te habrás dado cuenta que no se tuvo que duplicar el código como en el ejemplo anterior para hacer el segundo LED
Y quizá te estés dando cuenta de que si quisieras añadir uno, dos o X LEDs más, solo tendrías que añadir 2 lineas de código extra por LED!

Intenta añadirle un LED 13 al código y ve cuan fácil fue! (LED ROJO)

Ya has aprendido muchísimo ahorita, es buen momento para que te pongas en práctica e intentes mezclarlo con otros ejemplos, como el del servo motor. O alguna otra cosa. Recuerda que esto se hace principalmente para permitir al Altair que se puede comunicar con el HUB de manera continua.

¡No olvides comentar todas tus dudas!


Recursos para Aquila (Documentación, código, etc.)
[Complemento] Acciones y Eventos
#2

Hola, probe el codigo unicamente con el led azul (encendido y apagado) con el servidor sin embargo queria saber si hay alguna forma de que el led parpadeé automaticamente indefinidas veces con apretar una vez un boton “on” en el servidor y no repetidas veces para que este se prenda y apague.


#3

@Victor_Dlgado, puedes probar con la biblioteca SimpleTimer (incluida en las bibliotecas para el Altair), puedes encontrar documentación de como usarla aquí: http://playground.arduino.cc/Code/SimpleTimer

Lo que podrías hacer es crear un timer que cambie el estado del led cada n millisegundos, y activarlo o desactivarlo (función enable() o disable() del timer) cuando aprietas el botón en el servidor.


#4

Buenas, me encanto el codigo de POO, para no usar delay, a mi me fue util para los rele que tengo conectado a arduino… me costo encontrar un codigo q no use delay y que a su vez maneje dos tiempos distintos (me refiero al de encendido y apagado) ya que la mayoria de los codigos que hay en internet dan el mismo tiempo de encendido que de apagado… y en mi caso no era util… felicidades buen post…


#5

Hola, tengo unas dudas respecto a la parte del delay del servo.

¿Como podria hacer que un servo se mueva a la izquierda X tiempo y a la derecha Y tiempo? y ¿Como sustituiria el “delay(15)” para que el servo pueda leer los datos?

saludos


#6

Tendrías que manejar una variable con la posición actual del servo e ir checando tiempos hasta que sea hora de moverlo un grado más, en ese momento llamas myservo.write(pos) y actualizas la variable de posición. también podrías manejar banderas de dirección y cuando quieres que pare.


#7

Hola Luis, excelente post nos has enseñado mucho sobre maneras de optimizar el código, te agradecería si me puedes solucionar una duda, he hecho un dimmer cuando pulso un botón el de encender luz totalmente he puesto un bucle for que me vaya aumentando el valor de una variable que controla el nivel de intensidad, he hecho esto porque quiero que el encendido sea suave no de un solo golpe (esta misma idea aplico al apagar el dimmer soft-on , soft-off), este bucle no esta dentro de void loop ya que en void loop leo el botón si es presionado y si lo presiona llamo a una función fuera de void loop la función es la siguiente (usando delay):

CODIGO EN LOOP:
if(est_on_1 == LOW)
{
//LLAMADA A FUNCION QUE ENCIENDE LUZ TOTALMENTE
EnciendeLuz();
}

void EnciendeLuz()
{
if(dim>0)
{
for(int y = dim; y >= 0; y = y-pas)
{
dim=y;
aux_dim = dim;
delay(40);
//Serial.println(dim);
}
}
}

El problema es que mientras estoy dentro de ese bucle for no puedo realizar otras acciones con mi arduino pues obviamente estoy atascado en ese bucle por los delay, por ejemplo si ese mismo micro hago que contgrole unos cuatros focos más, si alguien presionó encender un foco y dentro del período de tiempo que esta en ese for otro botón que controla otro foco fue presionado no le va a responder el arduino, y si a esto le empeoro que quiero controlar 8 focos al mismo tiempo puedes imaginarte las molestias.

He intentado usar el Millis pero no logro conseguir que funcione, el código que he puesto es el siguiente, 100% seguro que estoy mal es la primera vez que trato de usar Millis y creo que aun no lo entiendo correctamente:

PRUEBA 1:
long IntervaloEnciende = 40; //ESTO ESTA DECLARADO AL INICIO DEL CODIGO 40 MILISEGUNDOS

void ActualizaMillis()
{
currentMillis = millis();
}

//FUNCION ENCIENDE LUZ TOTALMENTE
void EnciendeLuz()
{
if(dim>0)
{
for(int y = dim; y >= 0; y = y-pas)
{
//USAR MILIS EN LUGAR DE DELAY
if(currentMillis - previousMillis > IntervaloEnciende)
{
previousMillis = currentMillis;
dim=y;
aux_dim = dim;
Serial.println(dim);
}
ActualizaMillis();
}
}
}

PRUEBA 2:
//FUNCION ENCIENDE LUZ TOTALMENTE
void EnciendeLuz()
{
if(dim>0)
{
for(int y = dim; y >= 0; y = y-pas)
{
//USAR MILIS EN LUGAR DE DELAY
if(currentMillis - previousMillis > IntervaloEnciende) //EL VALOR DE currentMillis lo cargue en el void loop
{
previousMillis = currentMillis;
dim=y;
aux_dim = dim;
Serial.println(dim);
}
}
}
}

PRUEBA 3:

//FUNCION ENCIENDE LUZ TOTALMENTE
void EnciendeLuz()
{
//currentMillis = millis();
if(dim>0)
{
for(int y = dim; y >= 0; y = y-pas)
{

      //USAR MILIS EN LUGAR DE DELAY
      if(currentMillis - previousMillis > IntervaloEnciende) //EL VALOR DE currentMillis lo cargue en el void loop
      {
        //previousMillis = currentMillis; 
        dim=y;
        aux_dim = dim;               
        Serial.println(dim);
      }     
    }
  } 

}

En la última opción si comento previousMillis = currentMillis; dentro del if logro variar el valor de dim pero sin embargo no hay un tiempo entre cada cambio dentro del for y el incremento es demasiado rápido e inpercentible en el foco

Se que he puesto demasiado escrito mil disculpas por eso es mi primera vez en el foro no se si este bien esto, pero te agradezco mucho si pudieses ayudarme con esta duda.

Saludos
Andres


#8

gracias me sirvio de mucho pude logarar en un cornometro se encienda un led a un determinado tiempo