Comportamiento no esperado Delay()

Buenos días,

Estoy motorizando una montura ecuatorial con un motor paso a paso Nema 17 con una arduino nano y un driver drv8825.

La velocidad de giro es mi máximo quebradero de cabeza, pues tiene que dar una vuelta cada 14 minutos y 24 segundos exactamente.

Utilizo la librería stepper.h

En primer lugar siempre he tenido un problema al configurar el motor (he probado con dos distintos). Son motores de 200 pasos estándares y para que den una vuelta completa necesito decir que son de 800 pasos. De esa manera consigo que dé una vuelta cada minuto. Además he conseguido "engañar" al sketch diciéndole que es de 80 pasos y así da 1 vuelta cada 10 minutos. No tengo puenteado ningún M por lo tanto debería estar a full step (de hecho tiene mucha fuerza, he probado a puentear y hace pasos más cortos según debería esperarse)

Lo que hice es poner en bucle la siguiente instrucción (enciendo un led teóricamente cada pulso):

     stepper.setSpeed(1);
     stepper.step(1);
     digitalWrite(6,HIGH);
     delayMicroseconds(50);
     digitalWrite(6,LOW);

De esta manera, misteriosamente da un paso potente cada 4 pulsos del led.

Como necesito aún menor velocidad calculé que si provoco un retardo de 330 ms conseguía el tiempo exacto de 14 min y 24 seg. y añadí

delay(330);

mido tiempos y no consigo atrasar nada, exactamente como si no estuviera el delay. Tan solo poniendo más de 1000 consigo que de golpe se atrase lo esperado.

He probado a encender el pin 13 durante esos 330 y lo enciende, pero da la impresión de que lo hace a la vez que el siguiente bucle, es como si lanzase el encendido del led a la vez que comienza el siguiente ciclo... He probado también a hacer delay(1) con un contador while de 330 pasos (para ver si así se "entretenía" a hacer la tarea...)

En fin, que por más vueltas que le doy no llego a una explicación, y tampoco a un método para logar la velocidad que busco...

¿Alguna ayuda? ¿qué estoy haciendo mal?

Muchas gracias por anticipado.

Buenos dias

Para empezar te diría que deberías abandonar el delay para un motor ecuatorial. El motivo es que nunca podrás ajustar correctamente los tiempos, porque dependerá del tiempo de ejecución del codigo . debes utilizar milis().
Sobre tu código en particular, te diría que pusieras tu código completo.

Saludos

Gracias por la respuesta. Aquí pongo el código completo. Utilizo un botón para activar y parar el motor. No es muy preciso pero hasta ahora me servía. Tiene una rutina para monitorizar el tiempo que tarda en cada ciclo. Es un poco cutre pero si fuera a velocidad 0,1rpm el funcionamiento es correcto (no perfecto pero correcto)

/*
Rutina Arduino para motor bipolar Nema 17 en Montura ecuatorial EQ1
controlado por botón pulsador y DRV8825. 800 pasos full step. Divido 10 para 0,1 rpm
Montura EQ1 corona 100 dientes, 1 vuelta en 24 horas. 24x60x60=86400 seg. 86400/100=14,4 min. 14 min y 24 seg
*/

#include <Stepper.h> //incluye la librería stepper
#define STEPS 80 //número de pasos
Stepper stepper(STEPS, 8, 9); //crea stepper 8=DIR 9=STEP

//Variables estáticas
 int boton=10 ; //pin 10 entrada botón
 int tiempo=20 ; //tiempo para dar margen de retirada dedo de botón (bouncing)
 int normal=80 ; //pasos a velocidad normal

//Variables dinámicas
 int valorBoton=0 ; //da el estado del botón LOW (0) HIGH (1). De inicio es LOW
 int valorBotonAnt=0 ; //almacena estado botón anterior
 int estado=0 ; //indica si toca ON u OFF

void setup() 
{
 pinMode(boton,INPUT); //pin 10 como entrada
 pinMode(6,OUTPUT); //pin 6 como led
 Serial.begin(9600);
}

void loop() 
{
 valorBoton = digitalRead(boton); //lee valor del botón y lo guarda como valorboton
 if(valorBoton==HIGH && valorBotonAnt==LOW) //detecta si se ha pulsado el botón
 {
   estado = 1 - estado; //se graba como cambio de estado
   delay(20); //elimina efecto bouncing
 }  
 valorBotonAnt=valorBoton ; //guarda el valor del pulsador como anterior

 if(estado==1)
 {
 unsigned long time0; //variable inicio tarea
 unsigned long time1; //variable final tarea
 unsigned long tiempo; //variable tiempo tarea
 time0 = millis(); //inicio tarea

 for(int i=0; i<normal; i++)
 {
 valorBoton = digitalRead(boton); //nuevas lecturas botón (normal)
 if(valorBoton==HIGH && valorBotonAnt==LOW)
 {
   estado = 1 - estado;
   delay(20);
 }
 valorBotonAnt=valorBoton ;
 if(estado==1)
   {
     stepper.setSpeed(1);
     stepper.step(1);
     digitalWrite(6,HIGH);
     delayMicroseconds(50);
     digitalWrite(6,LOW);
     delay(330);
   }
   else
   {
     i==normal;
   }
 }

 time1 = millis(); //final tarea
 tiempo = (time1-time0); //cálculo tiempo tarea
 Serial.println(tiempo); //monitoriza el tiempo empleado en dar una vuelta completa
 
 }
 else
 {
   //no se mueve

 }
}

@fotoflipao los códigos no se postean simplemente pegándolos. Usa tags para códigos que son todos los íconos que estan arriba de los emoticons, en particular el de código es este </>.
Lee las normas del foro y editalos, el código parcial de tu primer post y el completo del segundo.

surbyte:
@fotoflipao los códigos no se postean simplemente pegándolos. Usa tags para códigos que son todos los íconos que estan arriba de los emoticons, en particular el de código es este </>.
Lee las normas del foro y editalos, el código parcial de tu primer post y el completo del segundo.

Perdón. Lo he modificado, espero haberlo hecho bien. Gracias por el aviso.

Finalmente he hecho una modificación eliminando la librería stepper.h que me respeta los delays. Simplemente encendiendo y apagando el pin conectado a step del driver, me había complicado en exceso. Ahora el problema: consigo que dé una vuelta en los 864 segundos exactos (no hay apenas código que retrase el bucle). Eso sí, como el motor requiere cierta fuerza tengo que hacerlo a full step y tengo el delay de 4320 ms que me impide parar el motor con un simple toque de botón (tengo que darle justo entre delay y delay, lo cual es casi imposible) Ahora el botón sólo me sirve para ponerlo en marcha. Este es el código completo.

/*
Rutina Arduino para motor bipolar Nema 17 en Montura ecuatorial EQ1 controlado por botón pulsador y DRV8825. 
Montura EQ1 corona 100 dientes, 1 vuelta en 24 horas. 24x60x60=86400 seg. 86400/100=864seg. 14,4 min. (14 min y 24 seg)
200 pasos full step (4320ms/step). 400 pasos a 1/2 (2160ms/step). 800 pasos a 1/4 (1080ms/step)
*/

//Variables estáticas
  int boton=10 ; //pin 10 entrada botón
  int tiempo=20 ; //tiempo para dar margen de retirada dedo de botón (bouncing)
  int pasos=200 ; //pasos por vuelta (200 full, 400 1/2, 800 1/4)
  int espera=4320 ; //tiempo de espera tras paso (4320 full, 2160 1/2, 1080 1/4)

//Variables dinámicas
  int valorBoton=0 ; //da el estado del botón LOW (0) HIGH (1). De inicio es LOW
  int valorBotonAnt=0 ; //almacena estado botón anterior
  int estado=0 ; //indica si toca ON u OFF

void setup() 
{
  pinMode(8,OUTPUT); //pin 8=DIR
  pinMode(9,OUTPUT); //pin 9=STEP
  digitalWrite(8,LOW); //partida pin 8 apagado (giro agujas reloj)
  digitalWrite(9,LOW); //partida pin 9 apagado
  pinMode(boton,INPUT); //pin 10 como entrada
  pinMode(6,OUTPUT); //pin 6 como led
  Serial.begin(9600);
}

void loop() 
{
  valorBoton = digitalRead(boton); //lee valor del botón y lo guarda como valorboton
  if(valorBoton==HIGH && valorBotonAnt==LOW) //detecta si se ha pulsado el botón
  {
    estado = 1 - estado; //se graba como cambio de estado
    delay(20); //elimina efecto bouncing
  }  
  valorBotonAnt=valorBoton ; //guarda el valor del pulsador como anterior

  if(estado==1)
  {
  unsigned long time0; //variable inicio tarea
  unsigned long time1; //variable final tarea
  unsigned long tiempo; //variable tiempo tarea
  time0 = millis(); //inicio tarea

  for(int i=0; i<pasos; i++)
  {
  valorBoton = digitalRead(boton); //nuevas lecturas botón (normal)
  if(valorBoton==HIGH && valorBotonAnt==LOW)
  {
    estado = 1 - estado;
    delay(20);
  }
  valorBotonAnt=valorBoton ;
  if(estado==1)
    {
      digitalWrite(9,HIGH); //da un step
      delayMicroseconds(1);
      digitalWrite(9,LOW); //para el step
      delay(espera); //tiempo de espera
      digitalWrite(6,HIGH); //enciende led 6
      delayMicroseconds(50);
      digitalWrite(6,LOW); //apaga led 6
    }
    else
    {
      i==pasos;
    }
  }

  time1 = millis(); //final tarea
  tiempo = (time1-time0); //cálculo tiempo tarea
  Serial.println(tiempo); //monitoriza el tiempo empleado en dar una vuelta completa
  
  }
  else
  {
    //no se mueve

  }
}

Se me ocurre poner una interrupción, pero no veo claro cómo debo ponerlo y qué tiene que hacer. He investigado un poco sustituir delay por millis pero no veo qué me puede solucionar en este caso...

¿Alguna sugerencia?

Gracias.

Una interrupción es buena idea, pero mejor aún es que olvides el delay, casi como si no existiera. La diferencia básica entre el delay y millis es esta:

  • usando millis() tú decides cuantos mili segundos tiene que esperar para hacer algo, mientras tanto, se pueden seguir ejecutando cosas.
  • usando delay() también decides cuánto tiempo debe esperar. Pero en este caso dejas muerta la placa. Pase lo que pase no se va a enterar ni puede ejecutar código alguno, por lo que si pulsas un botón durante un Delay solo lo sabrás tú o quien te mire pulsarlo por qué la placa no

De ahí que es mejor usar siempre millis en vez delays.

Y te lo digo yo que soy 100% novato y ya casi me dan repelus los delays jjjjj

Bueno, he solucionado el tema de los delays usando millis()
Ahora me estoy peleando con las interrupciones. Consigo ponerlo en marcha pero sigo sin poderlo parar, algo falla en la interrupción. Llevo dos días dándole vueltas. Os pego el código completo a ver si me echáis un cable de dónde falla el asunto. Debe estar en el valor "estado" pero no consigo darle a la tecla del origen del problema...

/*
Rutina Arduino para motor bipolar Nema 17 en Montura ecuatorial EQ1 controlado por botón pulsador y DRV8825. 
Montura EQ1 corona 100 dientes, 1 vuelta en 24 horas. 24x60x60=86400 seg. 86400/100=864seg. 14,4 min. (14 min y 24 seg)
200 pasos full step (4320ms/step). 400 pasos a 1/2 (2160ms/step). 800 pasos a 1/4 (1080ms/step)
*/

//Variables estáticas
  const int boton=2; //pin 2 led (interrupt)
  int DIR=8; //pin 8 DIR
  int STEP=9; //pin 9 STEP
  int led=6; //pin 6 led
  int bouncing=20 ; //tiempo para dar margen de retirada dedo de botón (bouncing)
  int pasos=200/4 ; //pasos por vuelta (200 full, 400 1/2, 800 1/4). Dividimos por 4 para medidas cada cuarto de vuelta.
  int espera=4320 ; //tiempo de espera tras paso (4320 full, 2160 1/2, 1080 1/4)

//Variables dinámicas
  int valorBoton=0 ; //da el estado del botón LOW (0) HIGH (1). De inicio es LOW
  int valorBotonAnt=0 ; //almacena estado botón anterior
  volatile byte estado=0 ; //indica si toca ON u OFF

void setup() 
{
  pinMode(DIR,OUTPUT); //pin 8=DIR como salida
  pinMode(STEP,OUTPUT); //pin 9=STEP como salida
  digitalWrite(DIR,LOW); //partida pin 8 DIR apagado (giro agujas reloj)
  digitalWrite(STEP,LOW); //partida pin 9 STEP apagado
  pinMode(led,OUTPUT); //pin 6 led como salida
  pinMode(boton,INPUT); //pin 2 boton de entrada
  attachInterrupt(0, interrupt, RISING); //pin interrupción 0 (2 en arduino nano) ante cambios en estado
  Serial.begin(9600);
}

void loop() 
{
  if(estado==1)
  {
  unsigned long time0; //variable inicio tarea
  unsigned long time1; //variable final tarea
  unsigned long tiempo; //variable tiempo tarea
  time0 = millis(); //inicio tarea
  
  for(int i=0; i<pasos; i++)
  {
    if(estado==1)
    {
    digitalWrite(led,HIGH); //enciende led 6
    delayMicroseconds(50);
    digitalWrite(led,LOW); //apaga led 6
    digitalWrite(STEP,HIGH); //da un step
    delayMicroseconds(50); //tiempo de pulso
    unsigned long inicioMillis; //variable inicio tiempo espera
    inicioMillis = millis(); //inicia tiempo espera
    while((unsigned long)(millis() - inicioMillis) < espera)
      {
      digitalWrite(STEP,LOW); //para el step
      }
    }
  }
  time1 = millis(); //final tarea
  tiempo = (time1-time0); //cálculo tiempo tarea
  Serial.println(tiempo); //monitoriza el tiempo empleado en dar una vuelta completa
  }
}

void interrupt()
{
  valorBoton = digitalRead(boton); //lee valor del botón y lo guarda como valorboton
  if(valorBoton==HIGH && valorBotonAnt==LOW) //detecta si se ha pulsado el botón
  {
    estado = 1 - estado; //se graba como cambio de estado
    delay(bouncing); //elimina efecto bouncing
  }
}

Varios errores en tu rutina de interrupción
Te lo acabo de modificar
Poner estado == 1 y preguntar solo por estado es lo mismo.

const int boton=2; //pin 2 led (interrupt)
int DIR         = 8; //pin 8 DIR
int STEP        = 9; //pin 9 STEP
int led         = 6; //pin 6 led
int bouncing    = 20 ; //tiempo para dar margen de retirada dedo de botón (bouncing)
int pasos       = 200/4; //pasos por vuelta (200 full, 400 1/2, 800 1/4). Dividimos por 4 para medidas cada cuarto de vuelta.
int espera      = 4320; //tiempo de espera tras paso (4320 full, 2160 1/2, 1080 1/4)

//Variables dinámicas

volatile byte estado=0 ; //indica si toca ON u OFF

void setup() {

  pinMode(DIR,OUTPUT); //pin 8=DIR como salida
  pinMode(STEP,OUTPUT); //pin 9=STEP como salida
  digitalWrite(DIR,LOW); //partida pin 8 DIR apagado (giro agujas reloj)
  digitalWrite(STEP,LOW); //partida pin 9 STEP apagado
  pinMode(led,OUTPUT); //pin 6 led como salida
  pinMode(boton,INPUT); //pin 2 boton de entrada
  attachInterrupt(0, interrupt, RISING); //pin interrupción 0 (2 en arduino nano) ante cambios en estado
  Serial.begin(9600);
}

void loop()  {
  
  if (estado)   {
      unsigned long time0; //variable inicio tarea
      unsigned long time1; //variable final tarea
      unsigned long tiempo; //variable tiempo tarea
      time0 = millis(); //inicio tarea
      
      for (int i=0; i<pasos; i++)  {
          if (estado)  {
              digitalWrite(led,HIGH); //enciende led 6
              delayMicroseconds(50);
              digitalWrite(led,LOW); //apaga led 6
              digitalWrite(STEP,HIGH); //da un step
              delayMicroseconds(50); //tiempo de pulso

              unsigned long inicioMillis= millis(); //inicia tiempo espera //variable inicio tiempo espera
              // porque dos pasos si puedes usar uno.
              while ((unsigned long)(millis() - inicioMillis) < espera)   {
                    digitalWrite(STEP,LOW); //para el step
              }
          }
      }
      time1 = millis(); //final tarea
      tiempo = (time1-time0); //cálculo tiempo tarea
      Serial.println(tiempo); //monitoriza el tiempo empleado en dar una vuelta completa
  }
}

void interrupt() {
  static int valorBoton    = 0; //da el estado del botón LOW (0) HIGH (1). De inicio es LOW
  static int valorBotonAnt = 0; //almacena estado botón anterior

  valorBoton = digitalRead(boton);              //lee valor del botón y lo guarda como valorboton
  if (valorBoton==HIGH && valorBotonAnt==LOW) {  //detecta si se ha pulsado el botón
    // este delay en la ISR no esta bien!!!!!! 
    delay(bouncing); //elimina efecto bouncing
    if (valorBoton) // si realmente luego del bouncing esta en ON ahi eliminas el rebote
        estado = !estado; //se graba como cambio de estado
  }
  valorBotonAnt = valorBoton;
}

Bueno, muchas gracias por las correcciones, son muy valiosas para seguir aprendiendo.

Con el código de la interrupción sigue sin funcionar, arranca con una pulsación pero no para con otra nueva pulsación. He probado un código mucho más sencillo, el siguiente y parece que casi siempre funciona (es posible que aquí sí que exista algo de bouncing, que no sé cómo evitar):

void interrupt() {

        estado = !estado; //se graba como cambio de estado

}

Lo anterior te lo di para que probaras, en realidad a mi no me gusta poner delay de 20 mseg en una interrupción, es un contrasentido. Ya lo expresé en el código.
Tu código esta mal hecho para mi gusto.
Yo lo haría asi

//Variables estáticas

const int boton=2; //pin 2 led (interrupt)
int DIR         = 8; //pin 8 DIR
int STEP        = 9; //pin 9 STEP
int led         = 6; //pin 6 led
int bouncing    = 20 ; //tiempo para dar margen de retirada dedo de botón (bouncing)
int pasos       = 200/4; //pasos por vuelta (200 full, 400 1/2, 800 1/4). Dividimos por 4 para medidas cada cuarto de vuelta.
int espera      = 4320; //tiempo de espera tras paso (4320 full, 2160 1/2, 1080 1/4)

//Variables dinámicas
int valorBoton    = 0; //da el estado del botón LOW (0) HIGH (1). De inicio es LOW
int valorBotonAnt = 0; //almacena estado botón anterior
byte estado       = 0 ; //indica si toca ON u OFF
bool BotonEstado  = false;

unsigned long time0; //variable inicio tarea
unsigned long time1; //variable final tarea
unsigned long tiempo; //variable tiempo tarea
unsigned int contadorPasos;

void setup() {

  pinMode(DIR,OUTPUT); //pin 8=DIR como salida
  pinMode(STEP,OUTPUT); //pin 9=STEP como salida
  digitalWrite(DIR,LOW); //partida pin 8 DIR apagado (giro agujas reloj)
  digitalWrite(STEP,LOW); //partida pin 9 STEP apagado
  pinMode(led,OUTPUT); //pin 6 led como salida
  pinMode(boton,INPUT); //pin 2 boton de entrada
  attachInterrupt(0, interrupt, RISING); //pin interrupción 0 (2 en arduino nano) ante cambios en estado
  Serial.begin(9600);
}

void loop()  {

  valorBoton = digitalRead(boton);              //lee valor del botón y lo guarda como valorboton
  if (valorBoton && !valorBotonAnt) {  //detecta si se ha pulsado el botón
    
    delay(bouncing); //elimina efecto bouncing
    if (valorBoton) // si realmente luego del bouncing esta en ON ahi eliminas el rebote
        BotonEstado = !BotonEstado; // cambio estado 0 a 1 y a 0
  }
  valorBotonAnt = valorBoton;
  
  if (BotonEstado) 
      estado = 0;  // comienzo la maquina de estados
  else 
      estado = 5;  // no hago nada

  switch (estado) {
      case 0: time0 = millis(); //inicio tarea
              contadorPasos = 0;
              estado = 1;
              break;

      case 1: digitalWrite(led,HIGH);           // enciende led 6
              delayMicroseconds(50);
              digitalWrite(led,LOW);            // apaga led 6
              digitalWrite(STEP,HIGH);          // da un step
              delayMicroseconds(50);            // tiempo de pulso

              unsigned long inicioMillis = millis(); //inicia tiempo espera //variable inicio tiempo espera
              estado = 2;              
              break;
      case 2: if (millis() - inicioMillis > espera) {
                  estado = 3;
              }
              else
                  digitalWrite(STEP,LOW); //para el step
              break;
      case 3: if (++contadorPasos>=pasos)
                  estado = 4;

      case 4: time1   = millis(); //final tarea
              tiempo  = (time1-time0); //cálculo tiempo tarea
              Serial.println(tiempo); //monitoriza el tiempo empleado en dar una vuelta completa
              estado = 0;
              break;
      default:
              break;
  }
}

Espero que funcione

De nuevo una lección de maestro.

Estoy empezando a comprender muchas instrucciones que no conocía gracias a los consejos de este foro (no hace tanto que estaba con el "hola mundo").
Una duda, ¿debo eliminar la interrupción? Por lo que veo parece que eliminas la interrupción y reconstruyes mediante pasos sucesivos con la instrucción switch. Brillante, al menos para mí.

Podré probarlo durante el fin de semana, ya comentaré cómo va. También subiré las fotos del invento, pueden venir bien para alguien.

Gracias de nuevo, @surbyte !!

Esto es el concepto de máquina de estados.. tu pasas de un estado a otro siempre conocido.
Se debe cumplir algo para avanzar o saltar. Puedes hacer lo que gustes.. saltar de 0 a 7 o a 2 o consecutivamente pero cada paso esta bien definido.

Ahora si lo miras son pasos rápidos... y siempre me devuelven al loop, y chequeo el pulsador o muestro algo en el display, o leo sensores. Se ve la idea?
Se que lo has entendido y espero que lo que hice funcione.. como lo que tu esperas.
Pruebalo y me cuentas.

Yo tardé mucho en ver esto. Explorando el foro en inglés comencé a ver la idea y me dije.. SI esta es la manera correcta tal como yo habia estudiado en mis años de ingeniería cuando un problema lógico debe desarmarse en estados conocidos y crear una tabla de estados. Imagino esto usando integrados sin inteligencia... pues funciona igual, jajaja o acaso no lo ves todos los dias con un semáforo?

Es pensar de otro modo, el resultado es excelente. Ya verás cuando le agregues mas complejidad a la idea.

Esto es el concepto de máquina de estados.. tu pasas de un estado a otro siempre conocido.
Se debe cumplir algo para avanzar o saltar. Puedes hacer lo que gustes.. saltar de 0 a 7 o a 2 o consecutivamente pero cada paso esta bien definido.

Ahora si lo miras son pasos rápidos... y siempre me devuelven al loop, y chequeo el pulsador o muestro algo en el display, o leo sensores. Se ve la idea?
Se que lo has entendido y espero que lo que hice funcione.. como lo que tu esperas.
Pruebalo y me cuentas.

Yo tardé mucho en ver esto. Explorando el foro en inglés comencé a ver la idea y me dije.. SI esta es la manera correcta tal como yo habia estudiado en mis años de ingeniería cuando un problema lógico debe desarmarse en estados conocidos y crear una tabla de estados. Imagino esto usando integrados sin inteligencia... pues funciona igual, jajaja o acaso no lo ves todos los dias con un semáforo?

Es pensar de otro modo, el resultado es excelente. Ya verás cuando le agregues mas complejidad a la idea.

Qué es y cómo funciona una máquina de estados

Esta escrito por @cgriell que es alguien que quiere aportar al foro sin conocer arduino. Dice que ya pasó su tiempo para Arduino (yo no estoy de acuerdo), pero ha explicado de modo brillante como funciona.
Ahi tienes la fundamentación teórica de esta manera de programar.
No dejes de leerlo. Verás como todo se aclara.

surbyte:
Esto es el concepto de máquina de estados.. tu pasas de un estado a otro siempre conocido.

¡Muchas gracias Surbyte por toda la información! Tengo ahí "chicha" para estudiar, como dices es un concepto que una vez asimilado dará mucho juego.

En cuanto al código he definido inicioMillis antes del switch para que no dé error y he quitado el attachInterrupt pero ahora no responde al pulsar el botón, ni arranque ni parada. Daré vueltas a ver si comprendo el switch y consigo detectar por qué falla.