en Hardware, Tutoriales

Arduino ¿Usar delays o evitarlos?

Últimamente he visto mucha confusión sobre lo que hace realmente la función delay(ms)[1] de arduino, y he recibido muchas preguntas sobre si debe usarse o no en ciertas ocasiones. En este tema intentaré despejar todas estas dudas.

¿Debo usar la función delay en mi código?

La respuesta a esta pregunta es: depende. Esta función esta definida en la librería estándar de arduino y lo que hace es bloquear el bucle principal de ejecución del programa. A mayores esta función devuelve el control al planificador de tareas, pero si no estar usando el planificador o no sabes lo que es puedes obviar esta parte.

Para verlo más claro vamos a analizar la definición de la función que se encuentra en el archivo: hardware/arduino/sam/cores/arduino/wiring.c [2]

void delay( uint32_t ms )
{
    if (ms == 0)
        return;
    uint32_t start = GetTickCount();
    do {
        yield();
    } while (GetTickCount() - start < ms);
}

GetTickCount() devuelve los milisegundos que han pasado desde que el microcontrolador se ha encendido.

  • Lo primero que hace es comprobar si el parámetro es 0 y salir de la función pero lo que nos interesa es la siguiente parte.
  • Asigna los milisegundos que han pasado a la variable start
  • Comienza a hacer un bucle mientras no se haya cumplido el tiempo que le hemos dicho y lo único que hace es ejecutar la variable yield() una y otra vez.

La función yield llama al planificador del sistema, pero como hemos dicho más arriba, si no estamos usando el planificador esta función no hace nada, por lo tanto la función delay en este caso es un bucle que itera constantemente hasta que haya pasado el tiempo especificado.

¿Entonces, ahorra energía el arduino al usar delay?

La respuesta es: NO, como hemos visto la función delay actúa como un bucle infinito, por lo tanto el consumo de CPU es el máximo mientras se está ejecutando un delay, por lo tanto no agregues delays a tu código si no son necesarios ya que esto no supone que se caliente menos o que ahorre energía, y por lo tanto no son en ningún caso obligatorios.

Para demostrar esto he hecho la siguiente prueba:
Cargamos 2 códigos en el arduino, el primero con un delay y el segundo con un bucle que no hace nada, y comparamos los consumos.

void setup() {}

void loop() {
  delay(99999);
}

Comprobamos la intensidad a la entrada de Arduino y el resultado es de 36.21mA a 5V y a 23ºC

consumo-arduino-delay

void setup() {}

void loop() {
  while(1);
}

Ahora hacemos la misma operación con el bucle infinito y obtenermos: 34.27mA a 5V y a 23ºC

consumo-arduino-bucle-infinito

Como vemos el consumo no solo no ha aumentado, si no que ¡ha disminuido! Esto se debe a que el delay ejecuta una llamada a la función yield por cada iteración, esto provoca que se reserve espacio en la pila y este acceso a memoria aumenta el consumo.

¿Entonces, cuando se debe usar delay y cuando no?

Pongamos un ejemplo: Queremos que al pulsar un botón se encienda un led durante 10 segundos, y luego se apague. Lo primero que se nos podría ocurrir es lo siguiente.

void setup() {
  pinMode(10, INPUT_PULLUP); // boton
}

void loop() {
  if (digitalRead(10) == LOW) // boton pulsado
  {
    digitalWrite(13, HIGH); // encender led
    delay(10000); // esperamos 10s
    digitalWrite(13, LOW); // apagamos led
  }
}

Este ejemplo funciona correctamente, pero ¿que pasa si queremos añadir un segundo botón que haga lo mismo con otro led?

void setup() {
  pinMode(10, INPUT_PULLUP); // boton 1
  pinMode(11, INPUT_PULLUP); // boton 2
}

void loop() {
  if (digitalRead(10) == LOW) // boton 1 pulsado
  {
    digitalWrite(13, HIGH); //encender led 1
    delay(10000); // esperamos 10s
    digitalWrite(13, LOW); // apagamos el led 1
  }
  
  if (digitalRead(11) == LOW) // boton 2 pulsado
  {
    digitalWrite(12, HIGH); //encender led 2
    delay(10000); // esperamos 10s
    digitalWrite(12, LOW); // apagamos led 2
  }
}

Esto ya no funcionaría puesto que al pulsar el primer botón el código se quedaría esperando 10 segundos en el delay y durante ese tiempo no va a hacernos caso si pulsamos el botón 2.

Para solucionar esto podemos utilizar la función millis(), esta función nos devuelve el número de milisegundos que han pasado desde que se encendió el arduino, de esta forma podemos comprobar periódicamente si ya ha pasado el tiempo suficiente para apagar el led.

void setup() {
  pinMode(10, INPUT_PULLUP); // boton 1
  pinMode(11, INPUT_PULLUP); // boton 2
}

unsigned long timerLed1 = 0;
unsigned long timerLed2 = 0;

void loop() {
  if (digitalRead(10) == LOW) // boton 1 pulsado
  {
    digitalWrite(13, HIGH); //encender led 1
    timerLed1 = millis(); // asignamos los ms actuales al timer1
  }
  
  if (millis() - timerLed1 > 10000) // comprobamos si han pasado mas de 10000 ms
  {
    digitalWrite(13, LOW); // si ya han pasado 10s apagamos el led1
  }
  
  if (digitalRead(11) == LOW) // boton 2 pulsado
  {
    digitalWrite(12, HIGH); //encender led 2
    timerLed2 = millis(); // asignamos los ms actuales al timer2
  }
  
  if (millis() - timerLed2 > 10000) // comprobamos si han pasado mas de 10000 ms
  {
    digitalWrite(12, LOW); // si ya han pasado 10s apagamos el led2
  }
}

Ahora si que funciona correctamente, al no usar delays el bucle principal no se interrumpe y por lo tanto sigue respondiendo al resto de botones y otras cosas que tengamos en nuestro programa. Fijate que para los timers he usado el tipo de dato unsigned long, es importante usar este tipo de dato ya que la función millis() devuelve valores muy grandes y no caben en una variable de tipo int, por lo tanto puede producir resultados inesperados.

¿Entonces, como hago para que el arduino ahorre energía?

Como hemos visto, los delays no son una solución si lo que queremos es reducir el consumo de nuestro arduino, para ello deben usarse los modos de ahorro de energía del microcontrolador. La idea consiste en dormir al arduino durante un tiempo hasta que necesitamos realizar otra acción.

El arduino tiene 6 modos de ahorro de energía:

  • SLEEP_MODE_IDLE
  • SLEEP_MODE_ADC
  • SLEEP_MODE_PWR_SAVE
  • SLEEP_MODE_EXT_STANDBY
  • SLEEP_MODE_STANDBY
  • SLEEP_MODE_PWR_DOWN

De arriba a abajo cada uno ahorra mas energía, pero cuanto más dormido más difícil es despertarlo. Mientras el arduino esta en un modo de ahorro de energía no ejecuta ningún código, y por lo tanto la única forma de despertarlo es a través de lo que llamamos interrupciones.

Las interrupciones ocurren cuando se produce un evento que puede ser que una entrada digital cambie de estado o pase un tiempo determinado. De esta forma podemos asignar una interrupción a la entrada digital 1 y posteriormente decirle al arduino que se ponga en modo SLEEP_MODE_PWR_DOWN. De esta forma no consumirá prácticamente nada, hasta que la entrada digital 1 cambie de estado, en ese momento se despertará y continuará la ejecución de código.

Por desgracia el manejo de interrupciones requiere de una entrada entera para poder explicarlas detalladamente. Pero si estáis interesados os recomiendo leer aquí: http://playground.arduino.cc/Learning/arduinoSleepCode

Cabe añadir que un microprocesador como el de arduino, ejecuta millones de operaciones en un solo segundo, por lo tanto, aun que parezca despreciable, hacer dormir al procesador 1 segundo puede suponer un ahorro de un porcentaje de energía importante.

Conclusión

La función delay es una manera rápida y sencilla que nos permite hacer esperar al arduino un tiempo determinado, sin embargo tiene los inconvenientes de que bloquea el bucle principal del programa y además no ahorra energía. Usa esta función siempre que esto no sea un problema en tu proyecto, de lo contrario debes buscar una alternativa de las que hemos comentado en este artículo.

Creative Commons License
Todohacker by 4m1g0 is licensed under a Creative Commons.

Escribe un comentario

Comentario

12 Comentarios

  1. si quiero despertar el micro-controlador de arduino uno cada hora, cual es el modo de ahorro de energía que usted me recomienda?.
    Espero pronta respuesta…

    • Hola,

      Los modos de ahorro de energía disponibles son:
      SLEEP_MODE_IDLE
      SLEEP_MODE_ADC
      SLEEP_MODE_PWR_SAVE
      SLEEP_MODE_EXT_STANDBY
      SLEEP_MODE_STANDBY
      SLEEP_MODE_PWR_DOWN

      Por orden de arriba a abajo, cada uno ahorra más pero es más dificil desperar. Los dos últimos solo «escuchan» interrupciones externas como un botón u otro dispositivo, así que no son adecuados para lo que tu quieres. El modo mas ahorrativo que nos permite despertar pasado un tiempo es SLEEP_MODE_EXT_STANDBY.

      Para ello vamos a usar la librería de arduino MSTimer2: http://playground.arduino.cc/Main/MsTimer2 (descomprimir y copiar la carpeta en «libraries» dentro de la instalación de arduino)

      Lo que hacemos es asignar una interrupción cada hora que «despierte» al arduino, y luego nos vamos a dormir.
      Hay que destacar que la librería MSTimer2 usa el timer2 del arduino, por lo tanto es incompatible con otras librerías que usen el mismo timer, y con las salidas PWM 11 y 3 que también lo usan.

      El código quedaría similar a esto. No he podido probarlo puesto que no tengo ahora mismo un arduino disponible, si hay algún problema comentármelo, intentaré probarlo cuando pueda.

      #include
      #include

      void despierta() {
      // esta funcion en nuestro caso no tiene codigo,
      // simplemente sirve para despertar el micro y continuar
      // despues del punto donde se ha dormido
      }

      void setup() {
      MsTimer2::set(3600000UL, despierta); // 1h
      MsTimer2::start();
      }

      void loop() {
      // Vamos a dormir
      set_sleep_mode(SLEEP_MODE_EXT_STANDBY);
      sleep_enable();
      sleep_mode();
      // aqui estamos durmiendo,
      //esperando a que salte la interrupcion del MSTimer

      sleep_disable(); // desperamos
      // aqui va el codigo que debe ejecutarse cada hora.
      Serial.println("ha pasado 1 hora");
      }

      • Buenas tardes gracias por tu información me sirvió bastante, probé el código y me he dado cuenta que se ejecuta continuamente la función podrías explicarme por favor como hago despertar el arduino directamente por la interrupción es que en este momento el código corre de seguido donde debería ejecutarse cada hora, la verdad he buscado en Internet pero no he encontrado un ejemplo claro de este tipo de Sleep de antemano gracias

  2. muy bueno el tutorial pero tengo un problemilla necesito que el led este encendido mas tiempo por ejemplo 20 minutos que habria que cambiarle

  3. mil y mil a la n gracias, el desarrollo de este ejemplo a pesar de que trata sobre como ahorrar consumo de corriente es brillante respecto al uso de la función millis() y por fin me hizo comprender como funciona, de paso me evitó usar una interrupción en el proyecto que estoy desarrollando. Saludos

  4. Hey que tal me preguntaba si con arduino podria controlar un motor sus ruedas un tiempo definido y que se apague en el tiempo que desee al mismo tiempo q se accione un gatillo de pistola