Go Down

Topic: [SOLUCIONADO] millis() para varios motores servo y stepper (Read 1 time) previous topic - next topic

apacher

Buenos días.
Estoy tratando de utilizar millis() en un sketch que mueve un motor paso a paso alimentado con 12V, utilizando el driver L298N y la biblioteca AccelStepper en un Arduino UNO. Tengo como objetivo realizar simultáneamente otras tareas y es la primera vez que empleo millis().

Pido ayuda pues no he conseguido que el motor funcione (aunque funciona en un sketch sin emplear millis() con dos fines de carrera que actúan para invertir la marcha).

Este es el código utilizado:

Code: [Select]

//millis() con L298N y AccelStepper
#include <AccelStepper.h>
AccelStepper stepper = AccelStepper(4, 8, 9, 10, 11);
int interval=500;
unsigned long previousMillis=0;
unsigned long currentMillis = 0;

void setup(){               
  stepper.setMaxSpeed(1000);
}
 
void loop(){ 
    if ((unsigned long)(currentMillis - previousMillis) >= interval) {
        stepper.setSpeed(250);
        stepper.runSpeed();
        previousMillis = millis();
    }   
}


He leído diferentes posts del forum y no consigo detectar el error.

Agradecido de antemano por la ayuda que por favor me puedan enviar, envío un cordial saludo desde Paraná, Argentina.

IgnoranteAbsoluto

La condición del if nunca va a ser cierta. Las variables previousMillis y currentMillis siempre valdrán cero. Y cero menos cero es no es mayor o igual que interval, que vale 500.

Falta asignar el valor de millis() a currentMillis al principio del loop(). Justo antes del if.

Otro detalle: recomiendo declarar interval de tipo unsigned long. Si no se hace así, nunca podrá tener un valor superior a 65535, con lo que "hará cosara raras" si se quieren intervalos de más de 65.535 segundos. También recomiendo poner el sufijo UL a los números que representan milosegundos, para que el compilador los interprete como unsigned long y no los "trunque".


Code: [Select]

//millis() con L298N y AccelStepper
#include <AccelStepper.h>
AccelStepper stepper = AccelStepper(4, 8, 9, 10, 11);
unsigned long interval=500UL;  // El sufijo UL le indica al compilador que el número es un unsigned long
unsigned long previousMillis=0;
unsigned long currentMillis = 0;

void setup(){               
  stepper.setMaxSpeed(1000);
}
 
void loop(){
    currentMillis = millis();
    if ((unsigned long)(currentMillis - previousMillis) >= interval) {
        stepper.setSpeed(250);
        stepper.runSpeed();
        previousMillis = millis();
    }   
}

apacher

Muchas gracias por la rápida respuesta, el motor funcionó. Un cordial saludo.

apacher

Me surgió una nueva pregunta: por favor, ¿cómo lograr que el motor gire en forma continua? Pues interpreto que da un paso por cada giro del loop.

IgnoranteAbsoluto

La idea de "programar con millis() es que la función loop() se ha de ejecutar lo más rápido posible para que una vez termine vuleva a empezar.

Cada vez que se ejecute el loop() "comprobamos la situación de las cosas". Por ejemplo: supongo que lo que quieres es que esté girando el motor en un sentido y transcurrido el timpo que gire en el sentido contrario. Bien, pues en alguna parte del loop() simplemente has de mirar si ha transcurrido el tiempo marcado. Si no es así, no haces nada más respecto al motor, el "programa sigue" haciendo otras cosas lo más rápido posible, para que termine el loop() y vuelva a empezar a ver, entre otras cosas, si ha transcurrido el tiempo indicado. Si, por el contrario, ha transcurrido el tiempo indicado, pues cambias el sentido de giro del motor, y recuerdas cuando lo has hecho: previousMillis = millis();. Una vez hecho esto, el "programa sigue". Hagas lo que hagas, le "programa ha de continuar" con las cosas que ha de controlar.

Lo que necesitas, en todo caso, es el mecanismo para saber si ha transcurrido el tiempo, y algo que te indique en qué sentido está girando para saber cual es el nuevo sentido que ha de tener llegado el momento.

Cuando la cosa se complica, queremos que haga más cosas, ya es cuando hacemos uso de las máquinas de estado. Una "máquina de estado" no es más que una variable cuyo valor nos indica en qué situacion estamos de entre todas las posibles situaciones que controlamos. En el ejemplo, es la variable que nos indica en qué sentido está girando el motor. Pero podría indicar también si el motor está parado o girando es un sentido o en otro. O si está girando en un sentido porque se lo hemos ordenado nosotros o porque ha "tropezado" y se ha de "recolocar". O si la parada es hasta que pulsemos un botón o que ha de parar sólo por un tiempo determinado. En definitiva, es "el estado" en que se encuentra la secuencia de acciones que queremos que se realizen para una determinada cosa. Para cada controlr de una secuenca de acciones podríamos tener una maquia de estado.

¿Quieres que el motor se ponga en marcha cuando pulsas un botón y que gire un tiempo en un sentido, luego en otro y que luego se pare? Pues tienes tres estados posibles. Parado esperando a que se pulse un botón, girando en el primer sentido y girando en el segundo sentido. Lo que te ayuda a controlar el tiempo es millis(), lo que te ayuda a controlar qué es lo que ha de estar haciendo es "la máquina de esdado".

Mira los tutoriales de millis() y máquinas de estados, si no los ha visto ya el la sección Documentación de este foro.

_jose_

runSpeed tiene que ser llamado como minimo tantas veces como requiera la velocidad a la que tiene que moverse el motor,o sea si el loop no se ejecuta con suficiente frecuencia ,el motor no alcanzara la velocidad esperada,salvo que esa velocidad sea muy baja.Si metes runSpeed dentro de un bloque de codigo que solo se ejecuta cada medio segundo,tu motor avanzara un paso cada medio segundo.....

IgnoranteAbsoluto

Lo que ha dicho _jose_ tiene toda la razón. Y la verdad es que no me había fijado. Creo que dentro del if Sólo deberías de tener "el cambio de estado". Por que stepper.runSpeed(); se debería de ejecutar contínuamente tan rápido como sea posible. Es por ello que el loop() no debe de "entretenerse" más que lo inprescindible.

stepper.runSpeed(); "internamente" controla el tiempo transcurrido y "avanza" cuando ha pasado el tiempo necesario, no antes. Pero si mucho después si tardas mucho entre llamada y llamada. Por eso lo debe de ir fuera del if, y ejecutarse cada vez que se ejecute el loop(). Bastaría con ponerlo al principio del loop().

De todas formas, es muy difícil decir si un programa está bien o no si no se sabe qué es lo que se quiere que haga el programa. Digamos que todo programa que compile y que no "corrompa la memoria", técnicamente, está bien hecho. Otra cosas es que sirva para lo que se pensó.

Vamos, que ayudaría que sepamoas que se supone que ha de hacer el programa.

apacher

Muchas gracias "IgnoranteAbsoluto", estudiaré las máquinas de estado.
Muchas gracias "_jose_", interpreto por lo que dices que debería colocar runSpeed dentro de un while o for, ¿es así?
Trabajo en una maqueta de trenes de escala HO, en la que un funicular funciona con un sketch con fines de carrera en cada estación que actúan para invertir la marcha una vez que son activados, con un delay los detengo unos segundos, y repito lo mismo en un ascensor. Asimismo empleo servos para los cambios de vías (desvíos) con sketches que funcionan con botones. Y pienso que empleando millis() tal vez pudiera incluir varias de estas actividades en un solo Arduino de manera de economizarlos, ¿es posible?

apacher

Obtuve que el motor girara en forma continua generando una función fuera del loop y disminuyendo a 10 el valor del intervalo. ¿Es correcto hacerlo así?

IgnoranteAbsoluto

Ya puedes decir misa, que si no muestras el código, no sabremos decirte si lo has hecho como es debido.


Si pones el runSpeed() dentro de un while o un for el Arduino estará "perdiendo el tiempo" con esos bucles. Te valdría con ponerlo como primera sentencia del loop() para que cada vez que se ejecuet el loop() se ejecute tambíen el runSpeed().

Es por ello que cuanto menos se entretenga el programa "haciendo cosas" más veces por segundo se llamará a la función runSpeed().

No hará lo que tú quieres, pero este programa debería de hacer que el motor se moviera mucho más rápido que antes:

Code: [Select]
//millis() con L298N y AccelStepper
#include <AccelStepper.h>
AccelStepper stepper = AccelStepper(4, 8, 9, 10, 11);
unsigned long interval=500UL;  // El sufijo UL le indica al compilador que el número es un unsigned long
unsigned long previousMillis=0;
unsigned long currentMillis = 0;

void setup(){              
  stepper.setMaxSpeed(1000);
}
 
void loop(){
    stepper.runSpeed();
    currentMillis = millis();
    if ((unsigned long)(currentMillis - previousMillis) >= interval) {
        stepper.setSpeed(250);
        previousMillis = millis();
    }  
}



_jose_

Code: [Select]

void loop(){
  stepper.runSpeed();
  //resto de codigo
}


Lo que puedas hacer o no ,ademas de mantener la velocidad constante del motor dependera de lo que tarde en ejecutarse lo que puse como resto de codigo.

apacher

Esta es la modificación que hice y el motor funcionó en forma continua:
Code: [Select]


//millis() con L298N y AccelStepper
#include <AccelStepper.h>
AccelStepper stepper = AccelStepper(4, 8, 9, 10, 11);
unsigned long interval=10UL;  // El sufijo UL le indica al compilador que el número es un unsigned long
                                            // disminuyendo el intervalo el giro fue continuo
unsigned long previousMillis=0;
unsigned long currentMillis = 0;

void setup(){               
  stepper.setMaxSpeed(1000);
}
 
void loop(){
    currentMillis = millis();
    if ((unsigned long)(currentMillis - previousMillis) >= interval) {
        corre();
        previousMillis = millis();
    }   
}

void corre(){
    stepper.setSpeed(950);
    stepper.runSpeed();
   }

IgnoranteAbsoluto

Corre de forma contínua porque se está llamando a stepper.runSpeed(); cada 10 milisegundos. Pero esa no es la forma adecuada de hacerlo. A stepper.runSpeed(); el mayor número de veces por segundo. Porque ya stepper.runSpeed(); se encarga de dar un paso en función de si ya ha pasado el tiempo que debe de pasar entre paso y paso según la velocidad que le hemos dicho que ha de tener. Nosotros no hemos de controlar ese tiempo.

Tendría sentido que nosotros controlásemos el tiempo de cada paso si no tuvieramos la librería AccelStepper. Pero la librería ya se encarga de eso. Los millis() los queremos para controlar el tiempo que queremos que esté haciendo otras cosas. Por ejemplo: para que esté 20 segundos sin moverse.

apacher

Muchas gracias, tomaré en cuenta las indicaciones de ambos. En el rápido intercambio tal vez se pasó el objetivo del desarrollo, que aquí lo reenvío:

"Trabajo en una maqueta de trenes de escala HO, en la que un funicular funciona con un sketch con fines de carrera en cada estación que actúan para invertir la marcha una vez que son activados, con un delay los detengo unos segundos, y repito lo mismo en un ascensor.

Asimismo empleo servos para los cambios de vías (desvíos) con sketches que funcionan con botones.
Y pienso que empleando millis() tal vez pudiera incluir varias de estas actividades en un solo Arduino de manera de economizarlos, ¿es posible?"

Este es el código actual del funicular, en el que pretendo eliminar los delays utilizando millis():

Code: [Select]

//Configuración de funicular con limit switches, luces y sonido. 5/4/2020
//Motor paso a paso 28BYJ-48 + AccelStepper - 2 LEDs (R y V)
//Fin de carrera conectado NO y PULL UP, al oprimir, apaga A1 y detiene el motor

#include <AccelStepper.h>

//Define los pines del Motor: #define da nombre a una constante
#define motorPin1  8      // IN1 on the ULN2003 driver
#define motorPin2  9      // IN2 on the ULN2003 driver
#define motorPin3  10    // IN3 on the ULN2003 driver
#define motorPin4  11    // IN4 on the ULN2003 driver

AccelStepper stepper = AccelStepper(4, motorPin1, motorPin3, motorPin2, motorPin4);

//Luces
  int VerdesPared = 3;       //LEDs verdes de la pared
  int VerdesMontania = 4;  //LEDs verdes de la montaña
  int RojosPared = 5;         //LEDs rojos de la pared
  int RojosMontania = 6;    //antes A3; LEDs rojos de la montaña Resist 200

//Sonidos
  int Speaker = A7;           //Parlante en A1
  int freqmin = 2000;        //frecuencia más baja a emitir
  int freqmax = 4000;       //frecuencia más alta a emitir

void setup(){
  pinMode(A0, INPUT_PULLUP);                // resistencia PULL UP, abierta recibe 5V       
  pinMode(A1,OUTPUT);               
  pinMode(A2, INPUT_PULLUP);               
  pinMode(A3,OUTPUT);   
  pinMode(A4,OUTPUT);           

  stepper.setMaxSpeed(1000);// =< 1000 //Configura los máximos pasos (steps) p/seg
  stepper.setAcceleration(200);                //Configura la máxima aceleración en pasos p/seg^2
}
 
   void loop(){ 
    ida();
    luces_Detenida_Montania();
    sonido();
    regreso();
    luces_Detenida_Pared();
    sonido();
}

void regreso(){
   delay(2000);
   while(digitalRead(A2) == HIGH){   //si fin de carrera 2
   stepper.setSpeed(-750);                //invierte el giro
   stepper.runSpeed();                      //Run
   luces_Andando();
  }
    stepper.stop();               //detiene el motor
}

void ida(){
   delay(2000);
   while(digitalRead(A0) == HIGH){
   stepper.setSpeed(750);
   stepper.runSpeed();
   luces_Andando();
  }
    stepper.stop();
}

void luces_Andando(){
 digitalWrite(RojosPared, HIGH);          //enciende rojos al andar la góndola
 digitalWrite(VerdesPared, LOW);         //apaga verdes de la pared al andar la góndola
 digitalWrite(RojosMontania, HIGH);     //enciende rojos al andar la góndola
 digitalWrite(VerdesMontania, LOW);    //apaga verdes al andar la góndola
}

void luces_Detenida_Montania(){
 digitalWrite(RojosPared, HIGH);          //enciende rojos de la pared
 digitalWrite(VerdesPared, LOW);         //apaga verdes de la pared
 digitalWrite(RojosMontania, LOW);      //apaga rojos en la montaña
 digitalWrite(VerdesMontania, HIGH);   //enciande verdes de la montaña
 delay(2000);
}

void luces_Detenida_Pared(){
 digitalWrite(RojosPared, LOW);          //apaga rojos de la pared
 digitalWrite(RojosMontania, HIGH);    //enciende rojos en la montaña
 digitalWrite(VerdesPared, HIGH);       //enciende verdes de la pared
 digitalWrite(VerdesMontania, LOW);   //apaga verdes de la montaña
 delay(2000);
}

void sonido(){       
 tone(A4,150);                                  //tono
   delay(500);                                   //duración
 noTone(A4);
   delay(1000);
 tone(A4,2000);
   delay(500);
 noTone(A4);
   delay(500);
}

IgnoranteAbsoluto

... un funicular funciona con un sketch con fines de carrera en cada estación que actúan para invertir la marcha una vez que son activados, con un delay los detengo unos segundos, y repito lo mismo en un ascensor. Asimismo empleo servos para los cambios de vías (desvíos) con sketches que funcionan con botones. Y pienso que empleando millis() tal vez pudiera incluir varias de estas actividades en un solo Arduino de manera de economizarlos, ¿es posible?
En principio creo que sí es posible hacerlo con un único Arduino. Habría que ver de cuantos botones, servos, motores y lucecitas se quieren poner en total, para ver cuantos pines se necesitan y buscar el Arduino que se adapte a ello.

Pero para hacerse una idea entre la diferencia de programar con delay() y programa con millis(), voy a poner dos ejemplos de pseudocódigo a modo de "lista de instrucciones para un humano".

Instrucciones al estilo delay():

Code: [Select]
1 Poner en marcha el funicular en sentido ascendente.
2 Si no detectamos que el funicular ha llegado arriba, pasamos a la instrucción 2.
3 Detenemos el funicular.
4 Esperamos 20 segundos.
5 Ponemos en marcha el funicular en sentido descendente.
6 Si no detectamos que el funicular ha llegado abaja, pasamos a la instrucción 6.
7 Detenemos el funicular.
8 Esperamos 20 segundos.



Le damos las instrucciones a un humano y le decimos que empiece por la primera, que las las siga al pie de la letra una por una. Que no pase a la siguiente instrucción hasta que termine con la que está, que no queremos que haga dos instrucciones a la vez porque se nos puede liar y si llega al final, que sobre la marcha vuelva a empezar por la primera. Así hasta el infinito y más allá.

Nuestro humano, cuando llegue a la instrucción 2, es probable que se quede un buen rato sin hacer otra cosa que no sea la instrucción 2. Porque esa instrucción le dice que no haga otra cosa que vigilar si ha llegado el funicular. Y cuando llegue a la instrucción 4, estará 20 segundos sin hacer nada, de brazos cruzados, esperando a que transcurran los 20 segundos. Lo mismo ocurrirá en los pasos 6 y 8. Y una vez que transcurran los 20 segundos del paso 8, volverá a la primera instrucción, y volverá a poner en marcha el funicular para que suba…

Vale. Tenemos las instrucciones para que nuestro humano controle el funicular. Ahora vamos a añadir instrucciones par que controle el ascensor. Al estilo delay():

Code: [Select]
1 Poner en marcha el funicular en sentido ascendente.
2 Si no detectamos que el funicular ha llegado arriba, pasamos a la instrucción 2.
3 Detenemos el funicular.
4 Esperamos 20 segundos.
5 Ponemos en marcha el funicular en sentido descendente.
6 Si no detectamos que el funicular ha llegado abaja, pasamos a la instrucción 6.
7 Detenemos el funicular.
8 Esperamos 20 segundos.
9 Poner en marcha el ascensor en sentido ascendente.
10 Si no detectamos que ha llegado arriba, pasamos a la instrucción 2.
11 Detenemos el funicular.
12 Esperamos 15 segundos.
13 Ponemos en marcha el funicular en sentido descendente.
14 Si no detectamos que ha llegado abaja, pasamos a la instrucción 6.
15 Detenemos el funicular.
16 Esperamos 15 segundos.



Ahora, ya logramos que tanto el funicular como el ascensor suban, bajen y esperen. Tal vez no como nos gustaría, pero lo hace. ¿Por qué no como nos gustaría? Pues porque queremos que los dos suban y bajen independientemente de si el otro se está moviendo o no. Mientra que tal como hemos hecho la lista de instrucciones, mientras uno sube y baja, el otro espera abajo quietecito. Ese es el problema del delay().

Go Up