"Problema" con millis();

Hola a todos, en un principio dar la enhorabuena por el foro, y en un segundo lugar, queria consultaros un problemita que tengo, el cual no se donde puedo estar metiendo la pata.

He estado buscando, y por mas que busco no encuentro respuesta, asi que al final he abierto este hilo.

Estoy liado con un proyecto, en el que tengo una pantalla oled, 3 botones y un par de diodos leds ws2812b.
Resulta que lo que pretendo hacer es que en la pantalla oled me muestre los distintos modos de iluminacion que pueda hacer con los leds, la libreria que estoy usando para la oled es MicroLCD, despues, para hacer el menu, la libreria MenuSystem, para los botones, Button y para los leds FastLED.

El problema que tengo es que tengo hecha una animacion de los leds, y todo se ejecuta correctamente, la animacion esta hecha con la funcion millis(), donde controlo el tiempo que ha pasado y en base a eso, hago una cosa u otra en la iluminacion, pero en la siguiente funcion que hago, necesito poner un bucle for o un while o do while y no hay manera, cuando cambio de modo con los botones, se bloquea la ejecucion y ya no hay manera de hacer nada, ni se iluminan los leds ni puedo interactuar con los botones para hacer el cambio de pantalla, por lo que no se donde puedo estar cometiendo el fallo.

Resumen: no se si al usar millis() puede dar conflicto con las estructuras de bucles.

Saludos a todos, y cualquier dato que necesiteis, decirmelo.
Muchas gracias.

Si los bucles son muy largos, solo te quedan un par de opciones:

  • Dejar que la función loop se encargue de los cambios de estado. En otras palabras, evitar los for y while. Complicado, verdad?
  • Usar interrupciones y que la verificación de una variable "bandera" sea la que rompa ese bucle.
    Eso es lo que tengo en mente hasta el momento.

Tu problema tal vez sea que tu código no este bien organizado.

Ojalá hubieras puesto tu código para entender mejor de que hablas. Asi es dificil aconsejarte, pero imaginaré situaciones a ver si acierto.

Yo en casos asi creo una máquina de estados.
Siempre un estado es el que esta activo y cuando éste termina llama a otro y otro.

También puedes crear acciones basadas en millis() y activarlas o no como dice Lucario por flags o como yo te sugiero que es con una máquina de estados.

Supongamos que tienes una rutina que controla unos leds.. que parpadee a un tiempo determinado cada uno.. esto debe actuar no importa que otra cosa ocurra.
Pues controlas todo por un flagLED por ejemplo que accionará esta secuencia.
Luego en tu menu accionas otra cosa y si esta escrito debidamente no debería haber problema en que actue pero de ningún modo poner un while que actue como delay(), porque puedes poner un while algo que dure segundos y se acabó el flujo del programa.
Tu segunda rutina debe ser tal que haga lo que pretendes sin monopolizar la ejección del programa, sin detenerlo.

Lucario448:
Si los bucles son muy largos, solo te quedan un par de opciones:

  • Dejar que la función loop se encargue de los cambios de estado. En otras palabras, evitar los for y while. Complicado, verdad?
  • Usar interrupciones y que la verificación de una variable "bandera" sea la que rompa ese bucle.

Eso es lo que tengo en mente hasta el momento.

surbyte:
Tu problema tal vez sea que tu código no este bien organizado.

Ojalá hubieras puesto tu código para entender mejor de que hablas. Asi es dificil aconsejarte, pero imaginaré situaciones a ver si acierto.

Yo en casos asi creo una máquina de estados.
Siempre un estado es el que esta activo y cuando éste termina llama a otro y otro.

También puedes crear acciones basadas en millis() y activarlas o no como dice Lucario por flags o como yo te sugiero que es con una máquina de estados.

Supongamos que tienes una rutina que controla unos leds.. que parpadee a un tiempo determinado cada uno.. esto debe actuar no importa que otra cosa ocurra.
Pues controlas todo por un flagLED por ejemplo que accionará esta secuencia.
Luego en tu menu accionas otra cosa y si esta escrito debidamente no debería haber problema en que actue pero de ningún modo poner un while que actue como delay(), porque puedes poner un while algo que dure segundos y se acabó el flujo del programa.
Tu segunda rutina debe ser tal que haga lo que pretendes sin monopolizar la ejección del programa, sin detenerlo.

Lo primero, muchas gracias a ambos por haberos preocupado por el tema, y lo segundo, os pongo por aqui el codigo, para que le hecheis un vistazo. En un principio, creo que por la estructuracion del codigo no es, a mi me huele a cierta incompatibilidad con los bucles, en el momento en el que se usa la funcion millis(), ¿por que?, no lo se, pero lo cierto es que se me queda la placa bloqueada hasta que le corto corriente y se la vuelvo a dar.

PD: Os pongo el codigo en el siguiente post, por que no me deja la pajina, pasarme de los 9000 caracteres.

#include <Wire.h>
#include <Button.h> //libreria botones 
#include <MenuSystem.h>  //Creador de los menus
#include <MicroLCD.h>   //Libreria control oleds
#include <FastLED.h>   //Libreria control leds

//Variable de configuracion de los leds
#define NUM_LEDS 2
//Declaracion de objetos de tipo leds
CRGB leds[NUM_LEDS];

//Constantes Para los botones 
#define PULLUP true
#define INVERT true
#define DEBOUNCE_MS 20
//#define LONG_PRESS 1000  //(ya no se usa)

Button btArriba(5, PULLUP, INVERT, DEBOUNCE_MS);
Button btSelect(4, PULLUP, INVERT, DEBOUNCE_MS);
Button btAbajo(3, PULLUP, INVERT, DEBOUNCE_MS);

int animacion = 0;  //Esta variable se usara para la seleccion de la ANIMACION.

//VARIABLES PARA CONTROL DE TIEMPO CON millis()
unsigned long previo = 0;     //Controla el tiempo que ha pasado desde el ultimo cambio de leds
unsigned long actual = millis(); //controla el tiempo actual desde que se inicia el sketch
unsigned long intervalo = 100;   //establece el tiempo entre cambios
bool orden = HIGH;   //esta variable controla el orden de los leds


// Opciones de menu
MenuSystem ms;
Menu mm("      ILUMINACION");  //Esta linea es el "titulo" del menu
MenuItem mm_mi1("Modo 1");  //item de menu mm_miX donde X es un numero de menu o submenu
MenuItem mm_mi2("Modo 2");
MenuItem mm_mi3("Modo 3");
Menu mu1("Configuracion");         //Item de menu, que a su vez es un menu en si, osea, que alberga otros items
MenuItem mu1_mi1("Brillo");//Item de menu secundario perteneciente al nivel anterior mu1.
MenuItem mu1_mi2("Atras..");//item de menu mm_miX donde X es un numero de menu o submenu


//Declaracion de funciones de iluminacion.
void blueRed(void);
void Glow(void);
void gloc(void);


//Declaracion de funciones para el manejo del menu.

void on_item1_selected(MenuItem* p_menu_item);
void on_item2_selected(MenuItem* p_menu_item);
void on_item3_selected(MenuItem* p_menu_item);
void on_item4_selected(MenuItem* p_menu_item);
void on_item5_selected(MenuItem* p_menu_item);


void setup()
{
  //Inicializacion de objetos Leds.
  FastLED.addLeds<NEOPIXEL, 6>(leds, NUM_LEDS);
  FastLED.setBrightness(15);

  //Configuracion de los items del menu
  mm.add_item(&mm_mi1, &on_item1_selected);
  mm.add_item(&mm_mi2, &on_item2_selected);
  mm.add_item(&mm_mi3, &on_item5_selected);
  mm.add_menu(&mu1);
  mu1.add_item(&mu1_mi1, &on_item3_selected);
  mu1.add_item(&mu1_mi2, &on_item4_selected);
  ms.set_root_menu(&mm);

//Inicializacion de la pantalla oled
  lcd.begin();
  lcd.clear();

  showMenu(); //Muestra por pantalla el menu
}

void loop()
{
  //Realizo la lectura de las entradas del usuario.
  btSelect.read();
  btArriba.read();
  btAbajo.read();

      if (btArriba.wasPressed()) {
        ms.prev();
        showMenu();
      }
      if (btAbajo.wasPressed()) {
        ms.next();
        showMenu();
      }
      if (btSelect.wasReleased()) {
        ms.select();
        lcd.clear();
        showMenu();
      }
  
  switch (animacion) {
    case 1:
          blueRed();
          break;
    case 2:
          gloc();
          break;
    case 3:
          Glow();
          break;
  } //fin de switch
}

//Funciones de visualizacion y de llamadas desde el menu

void showMenu(){

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.setFontSize(FONT_SIZE_SMALL);
  Menu const* punteroMenu = ms.get_current_menu();

  lcd.println(punteroMenu->get_name());

  MenuComponent const* punteroMenuSel = punteroMenu->get_selected();
  for (int i = 0; i < punteroMenu->get_num_menu_components(); i++)
  {

    MenuComponent const* cp_m_comp = punteroMenu->get_menu_component(i);
    if (punteroMenuSel == cp_m_comp) {
      lcd.print("o ");
    }
    else {
      lcd.print("  ");
    }
    lcd.print(cp_m_comp->get_name());

    lcd.println("");
  }
}


void on_item1_selected(MenuItem* p_menu_item)
{
  //Serial.println("Item1 Selected");
  //bRanCallback = true;
  animacion = 1;
  blueRed();
}

void on_item2_selected(MenuItem* p_menu_item)
{
  //Serial.println("Item2 Selected");
  //bRanCallback = true;
  animacion = 2;
  //Glow();
  gloc();
}

void on_item3_selected(MenuItem* p_menu_item)
{
  //Serial.println("Item3 Selected");
  //bRanCallback = true;
}

void on_item4_selected(MenuItem* p_menu_item)
{
  //Serial.println("Item4 Selected");
  //bRanCallback = false;
  ms.reset();
}

void on_item5_selected(MenuItem* p_menu_item)
{
  //Serial.println("Item4 Selected");
  //bRanCallback = false;
  animacion = 3;
  Glow();
}


//Conjunto de funciones con los efectos de los leds.

void blueRed() //Modo led de parpadeo Rojo y azul.
{
  //NUEVA FUNCION REDISEÑADA CON MILLIS();
  actual = millis();

  //int i=0;
  //do {
    if(actual-previo > intervalo)
    {
      if(orden == true)
      {
        leds[0]= CRGB::Red;
        FastLED.show();
        leds[1] =CRGB::Blue;
        FastLED.show();

        previo = actual;
        orden = false;
      }
      else
      {
        leds[0]= CRGB::Blue;
        FastLED.show();
        leds[1] =CRGB::Red;
        FastLED.show();

        previo = actual;
        orden = true;
      }
    }
  //} while (1);
}

void gloc(){  //Funcion igual a la anterior solo que esta es en blanco y verde el cambio de color.
actual = millis();

//int i=0;
//do {
  if(actual-previo > intervalo)
  {
    if(orden == true)
    {
      leds[0]= CRGB::Green;
      FastLED.show();
      leds[1] =CRGB::White;
      FastLED.show();

      previo = actual;
      orden = false;
    }
    else
    {
      leds[0]= CRGB::White;
      FastLED.show();
      leds[1] =CRGB::Green;
      FastLED.show();

      previo = actual;
      orden = true;
    }
  }
}

void Glow() {            //esta es la funcion que me da problemas
  int i = 0, j = 255;

  for(; i<=255; i++)
  {
    //led nº 0
    leds[0].r = i;
    leds[0].g = i;
    leds[0].b = i;
    //led nº 1
    leds[1].r = i;
    leds[1].g = i;
    leds[1].b = i;
  }
  for(; j>=0; j++)
  {
    //led nº 0
    leds[0].r = j;
    leds[0].g = j;
    leds[0].b = j;
    //led nº 1
    leds[1].r = i;
    leds[1].g = i;
    leds[1].b = i;
  }
}

La función problemática la modifiqué así:

// Nuevas variables globales
int i = 0;
boolean bajando = false;

void Glow() {            //esta es la funcion que me da problemas

  if ((i <= 255) && !bajando)
  {
    //led nº 0
    leds[0].r = i;
    leds[0].g = i;
    leds[0].b = i;
    //led nº 1
    leds[1].r = i;
    leds[1].g = i;
    leds[1].b = i;
    i++;
  } else {
    bajando = true;
    i--;
  }
  if ((i >= 0) && bajando)
  {
    //led nº 0
    // Por qué antes leds[0] = j
    // y no a i?
    leds[0].r = i;
    leds[0].g = i;
    leds[0].b = i;
    //led nº 1
    leds[1].r = i;
    leds[1].g = i;
    leds[1].b = i;
    i--;
  } else {
    bajando = false;
    i++;
  }
}

Ahora sí su frecuencia de actualización puede estar regulada por la típica comparación con millis, y no bloqueará el programa. Porque como había dicho antes, EVITANDO for Y while

Lucario448:
La función problemática la modifiqué así:

// Nuevas variables globales

int i = 0;
boolean bajando = false;

void Glow() {            //esta es la funcion que me da problemas

if ((i <= 255) && !bajando)
  {
    //led nº 0
    leds[0].r = i;
    leds[0].g = i;
    leds[0].b = i;
    //led nº 1
    leds[1].r = i;
    leds[1].g = i;
    leds[1].b = i;
    i++;
  } else {
    bajando = true;
    i--;
  }
  if ((i >= 0) && bajando)
  {
    //led nº 0
    // Por qué antes leds[0] = j
    // y no a i?
    leds[0].r = i;
    leds[0].g = i;
    leds[0].b = i;
    //led nº 1
    leds[1].r = i;
    leds[1].g = i;
    leds[1].b = i;
    i--;
  } else {
    bajando = false;
    i++;
  }
}



Ahora sí su frecuencia de actualización puede estar regulada por la típica comparación con *millis*, y no bloqueará el programa. Porque como había dicho antes, EVITANDO *for* Y *while*

Ahh! claro, en vez de utilizar bucles, los rediseño como funciones ciclicas rellamandolas una y otra vez para que de la sensacion de animacion.

Y una consulta mas, entonces, si existe una incompatibilidad entre la funcion millis() y los bucles no?

Muchas gracias por el truco Lucario488 :wink:

sergi4d:
Ahh! claro, en vez de utilizar bucles, los rediseño como funciones ciclicas rellamandolas una y otra vez para que de la sensacion de animacion.

¡Eso mismo! :smiley:
Sinceramente, no se hubiera ocurrido mejor explicación que esa jaja...

Y una consulta mas, entonces, si existe una incompatibilidad entre la funcion millis() y los bucles no?

En qué sentido lo va a afectar? Si más bien hasta puede funcionar para medir cuánto se tarda en ejecutar algo.

Hasta donde sé, lo que afecta a millis es:

  • Interrupciones (durante ISRs para ser exacto). Paraliza a millis y delay.
  • Después de llamar a la función noInterrupts. Paraliza a millis y delay.
  • Librerías que requieran modificar el "prescaler" del "Timer0". Alterar esto último modifica la velocidad de conteo de millis y delay; además de cambiar la frecuencia del PWM de ciertos pines.

Interrupciones (durante ISRs para ser exacto). Paraliza a millis y delay.

es relativo porque la interrupción del Timer/Counter0 Overflow tiene prioridad 17 de modo que siempre se ejecutará luego de otra interrupción si existiera.
Solo necesita ejecutarse cada 1 mseg.
Hay que ser demasiado torpe para tener el uP ocupado tanto tiempo usando ISRs. O mejor dicho, muy mal diseñadas las ISR que lleven tanto tiempo.
En este caso millis() solo cumple una función de mantener actualizado el sistema asi que que sean 100mseg o algo mas no altera nada y nadie se dará cuenta.
millis() no tiene ningún problema salvo malas programaciones. O sea que el problema somos nosotros al programar mal.

surbyte:
es relativo porque la interrupción del Timer/Counter0 Overflow tiene prioridad 17 de modo que siempre se ejecutará luego de otra interrupción si existiera.
Solo necesita ejecutarse cada 1 mseg.
Hay que ser demasiado torpe para tener el uP ocupado tanto tiempo usando ISRs. O mejor dicho, muy mal diseñadas las ISR que lleven tanto tiempo.

Bueno... ya aprendí algo hoy... :smiley:

millis() no tiene ningún problema salvo malas programaciones. O sea que el problema somos nosotros al programar mal.

Te refieres a lo de las ISRs? O también hay otros factores? (aparte de los que mencioné en el post anterior)

Nada mas que decir, me quito el sombrero ante los dos @Lucario448 y @surbyte muchas gracias a los dos por la ayuda prestada y por las dudas aclaradas.
Espero que este hilo le sirva a alguien que tenga el mismo problema que he tenido yo.

Muchas gracias a los dos de veras.
Saludos! :smiley:

Te refieres a lo de las ISRs? O también hay otros factores? (aparte de los que mencioné en el post anterior)

No. Esta perfecto lo que has enumerado.. solo que hay que tener en cuenta la prioridad.
Tambien hay que recordar que una ISR debe ser y mantenerse CORTA.
Hago lo que quiero rápido.. como bien has dicho.. en otro post creo, que una ISR aumente un contador y devuelva o que active un flag y devuelva, o cosas asi.
Podemos expandirnos tambien sino no servirian para nada.
Pero no usarla por ejemplo para enviar un mensaje por puerto serie como he visto y corregido hace un tiempo.
Cosas asi no!!