[SOLUCIONADO] millis() para varios motores servo y stepper

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:

//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.

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".

//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();
    }   
}

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

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.

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.

1 Like

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.....

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.

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?

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í?

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:

//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();
    }   
}
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.

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

//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();
   }

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.

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():

//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);
}

apacher:
... 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():

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():

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().

Vamos ahora a poner las instrucciones al estilo millis() y máquinas de estados sólo para el funicular:

1 Si tenemos anotado que el funicular está ascendiendo
    y detectamos que ha llegado arriba:
        1.1 detenemos el funicular,
        1.2 anotamos en qué instante estamos,
        1.3 anotamos que el funicular está detenido en la parte superior.
2 Si tenemos anotado que el funicular está detenido en la parte superior
    y entre el instante que tenemos anotado y el instante en que estamos
    ya han transcurrido 20 segundos o más:
        2.1 ponemos en marcha el funicular en sentido descendente,
        2.2 anotamos que el funicular está descendiendo.
3 Si tenemos anotado que el funicular está descendiendo
    y detectamos que ha llegado abajo:
        3.1 detenemos el funicular,
        3.2 anotamos en qué instante estamos,
        3.3 anotamos que el funicular está detenido en la parte inferior.
4 Si tenemos anotado que el funicular está detenido en la parte inferior
    y entre el instante que tenemos anotado y el instante en que estamos
    ya han transcurrido 20 segundos o más:
        4.1 ponemos en marcha el funicular en sentido ascendente,
        4.2 anotamos que el funicular está ascendiendo.

Esta vez la lista es poco más larga, enrevesada y compleja. Pero nuestro humano hará instrucción por instrucción sin apenas detenerse y “perder tiempo” en cada una de ellas. Y rápidamente llegará a la última instrucción, para volver a empezar con la primera instrucción. Así, una tras otra, hasta el infinito y más allá.

Un par de pequeños detalles. Antes de empezar, le diremos a la persona que ha de tener donde anotar dos cosas: qué es lo que está haciendo el funicular y un instante. Que inicialmente anote que el funicular está detenido en la parte superior y que anote que el instante actual. No importa si el funicular está arriba, abajo o en medio. Pasado 20 segundos desde que empiece con las instrucciones, el humano pondrá a bajar el funicular hasta que detecte que está abajo. La “máquina de estados” es la anotación de qué está haciendo del estado del funicular.

Ahora, si añadimos el ascensor, al estilo millis() y máquinas de estados, las instrucciones serían:

1 Si tenemos anotado que el funicular está ascendiendo
    y detectamos que ha llegado arriba:
        1.1 detenemos el funicular,
        1.2 anotamos en qué instante estamos,
        1.3 anotamos que el funicular está detenido en la parte superior.
2 Si tenemos anotado que el funicular está detenido en la parte superior
    y entre el instante que tenemos anotado y el instante en que estamos
    ya han transcurrido 20 segundos o más:
        2.1 ponemos en marcha el funicular en sentido descendente,
        2.2 anotamos que el funicular está descendiendo.
3 Si tenemos anotado que el funicular está descendiendo
    y detectamos que ha llegado abajo:
        3.1 detenemos el funicular,
        3.2 anotamos en qué instante estamos,
        3.3 anotamos que el funicular está detenido en la parte inferior.
4 Si tenemos anotado que el funicular está detenido en la parte inferior
    y entre el instante que tenemos anotado y el instante en que estamos
    ya han transcurrido 20 segundos o más:
        4.1 ponemos en marcha el funicular en sentido ascendente,
        4.2 anotamos que el funicular está ascendiendo.
5 Si tenemos anotado que el ascensor está ascendiendo
    y detectamos que ha llegado arriba:
        5.1 detenemos el ascensor,
        5.2 anotamos en qué instante estamos,
        5.3 anotamos que el ascensor está detenido en la parte superior.
6 Si tenemos anotado que el ascensor está detenido en la parte superior
    y entre el instante que tenemos anotado y el instante en que estamos
    ya han transcurrido 15 segundos o más:
        6.1 ponemos en marcha el ascensor en sentido descendente,
        6.2 anotamos que el ascensor está descendiendo.
7 Si tenemos anotado que el ascensor está descendiendo
    y detectamos que ha llegado abajo:
        7.1 detenemos el ascensor,
        7.2 anotamos en qué instante estamos,
        7.3 anotamos que el ascensor está detenido en la parte inferior.
8 Si tenemos anotado que el ascensor está detenido en la parte inferior
    y entre el instante que tenemos anotado y el instante en que estamos
    ya han transcurrido 15 segundos o más:
        8.1 ponemos en marcha el ascensor en sentido ascendente,
        8.2 anotamos que el ascensor está ascendiendo.

Igual que antes, un par de pequeños detalles. Antes de empezar, le diremos a la persona que ha de tener donde anotar otras dos cosas: qué es lo que está haciendo el ascensor y un instante. Por no complicar las instrucciones, cuando decimos de anotar el estado y el instante, se ha anotar el estado del funicular en un lado y el del ascensor en otro, al igual que los instantes. Vamos, que necesitamos dos variables para el funicular y otras dos para el ascensor, y no mezclar unas con otras. Igualmente, inicialmente ha de anotar que el ascensor está detenido en la parte superior y que anote que el instante actual para el ascensor. No importa si el ascensor está arriba, abajo o en medio. Pasado 15 segundos desde que empiece con las instrucciones, el humano pondrá a bajar el ascensor hasta que detecte que está abajo. La “máquina de estados” es la anotación de qué está haciendo del estado del ascensor.

Ahora el humano no se detendrá con los brazos cruzados, ni se quedará “atascado” un rato atendiendo una misma instrucción. Porque todas las instrucciones se realizan rápidamente una detrás de otra una y otra vez.

Se trata de aplicar este concepto a la hora de programar el Arduino. Y si se implementa el control de los servos, luces, pitidos y demás; con esta misma filosofía; se podría controlar todo con un Arduino. Un poco más complicado que con delay(), sí, pero conseguimos hacer varias cosas a la vez.

No es fácil, lo sé, pero no es imposible.

1 Like

GRACIAS! "IgnoranteAbsoluto" por la claridad de tu explicación y por el tiempo que has dedicado a la misma.

Voy a analizar si está a mi alcance, bien dices que "No es fácil, lo sé, pero no es imposible.", dedicaré "horas de silla" para tratar de conseguirlo (horas que no faltan en tiempos de pandemia).

Un cordial saludo, extensivo a jose.

Aquí te dejo una propuesta rápida basada en tu código. He de reconocer que para el sonido he usado una clase que tenía por hecha por ahí, para poder poner el sonido sin más. Precisamente esa es la ventaja de usar millis() y clases: copia, pegas y un par de cambios.

Yo me olvidadaría de entrada la clase Musica, y me fijaría en el resto del código para entender cómo funciona la idea.

#include <AccelStepper.h>

//Define los pines del Motor: #define da nombre a una constante
#define MOTOR_PIN_1  8      // IN1 on the ULN2003 driver
#define MOTOR_PIN_2  9      // IN2 on the ULN2003 driver
#define MOTOR_PIN_3  10    // IN3 on the ULN2003 driver
#define MOTOR_PIN_4  11    // IN4 on the ULN2003 driver

//Pines final de carrera
const uint8_t PIN_FIN_MONTANIA = A0;
const uint8_t PIN_FIN_PARED = A2;
const uint8_t VALOR_FIN_DETECTADO = LOW;   // Es el valor que tiene que tene el pin cuando se detecta el fin de carrera

AccelStepper stepper = AccelStepper(4, MOTOR_PIN_1, MOTOR_PIN_3, MOTOR_PIN_2, MOTOR_PIN_4);

//Luces
const uint8_t PIN_LUZ_VERDE_PARED = 3;     //LEDs verdes de la pared
const uint8_t PIN_LUZ_VERDE_MONTANIA = 4;  //LEDs verdes de la montaña
const uint8_t PIN_LUZ_ROJA_PARED = 5;      //LEDs rojos de la pared
const uint8_t PIN_LUZ_ROJA_MONTANIA = 6;   //LEDs rojos de la montaña Resist 200

//Sonidos
const uint8_t PIN_SPEAKER = A4;  //Parlante en A4

const unsigned long TIEMPO_ESPERA = 5000UL;
const int VELOCIDAD = 550;

enum estado_t {
    ESTADO_INICIAL,
    ESTADO_SUBIENDO,
    ESTADO_ARRIBA,
    ESTADO_BAJANDO,
    ESTADO_ABAJO
} estado = ESTADO_INICIAL;

void luces_Andando(){
    digitalWrite(PIN_LUZ_ROJA_PARED, HIGH);          //enciende rojos al andar la góndola
    digitalWrite(PIN_LUZ_VERDE_PARED, LOW);         //apaga verdes de la pared al andar la góndola
    digitalWrite(PIN_LUZ_ROJA_MONTANIA, HIGH);     //enciende rojos al andar la góndola
    digitalWrite(PIN_LUZ_VERDE_MONTANIA, LOW);    //apaga verdes al andar la góndola
}

void luces_Detenida_Montania(){
    digitalWrite(PIN_LUZ_ROJA_PARED, HIGH);          //enciende rojos de la pared
    digitalWrite(PIN_LUZ_VERDE_PARED, LOW);         //apaga verdes de la pared
    digitalWrite(PIN_LUZ_ROJA_MONTANIA, LOW);      //apaga rojos en la montaña
    digitalWrite(PIN_LUZ_VERDE_MONTANIA, HIGH);   //enciande verdes de la montaña
}

void luces_Detenida_Pared(){
    digitalWrite(PIN_LUZ_ROJA_PARED, LOW);          //apaga rojos de la pared
    digitalWrite(PIN_LUZ_ROJA_MONTANIA, HIGH);    //enciende rojos en la montaña
    digitalWrite(PIN_LUZ_VERDE_PARED, HIGH);       //enciende verdes de la pared
    digitalWrite(PIN_LUZ_VERDE_MONTANIA, LOW);   //apaga verdes de la montaña
}

void setup(){
    pinMode(PIN_FIN_MONTANIA, INPUT_PULLUP);                // resistencia PULL UP, abierta recibe 5V       
    pinMode(PIN_FIN_PARED, INPUT_PULLUP);               
    pinMode(PIN_SPEAKER, 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
}

unsigned long instanteAnterior = TIEMPO_ESPERA; // Así lo forzamos para que no espere la primera vez

// Notas de la melodía:
int melodia[] = {   // La nota 0 es silencio
  0, 150, 0, 2000
};

// Duración de las notas: 4 = quarter note, 8 = eighth note, etc.:
int duracionNotas[] = {
  8, 2, 1, 2
  , 0   // Tiene que haber siempre un cero al final
};

class Musica {
    public:
        Musica(int pin, int *melodia, int *duracionNotas)
            : pin(pin)
            , melodia(melodia)
            , duracionNotas(duracionNotas)
            , estado(ESTADO_FIN)
            , thisNote(0)
            , t(0UL)
            , tiempo(0)
            {};

        void start() {
            this->estado = ESTADO_INICIO;
        }

        void stop() {
            this->estado = ESTADO_FIN;
            noTone(this->pin);
            //pinMode(this->pin, OUTPUT);
            //digitalWrite(this->pin, HIGH);
        }

        void loop() {
            if (this->estado == ESTADO_FIN) {
                return;
            }
            if (this->estado == ESTADO_INICIO) {
                this->thisNote = -1;
                this->tiempo = 0;
                this->t = millis();
                this->estado = ESTADO_SILENCIO;
            }
            if (this->estado == ESTADO_SILENCIO) {
                if ((millis() - this->t) < this->tiempo) {
                    return;
                }
                this->thisNote++;
                if (!this->duracionNotas[this->thisNote]) {
                    this->estado = ESTADO_FIN;
                    pinMode(this->pin, OUTPUT);
                    digitalWrite(this->pin, HIGH);
                    return;
                }
                this->tiempo = 1000 / this->duracionNotas[this->thisNote];
                this->t = millis();
                this->estado = ESTADO_NOTA;
                if (this->melodia[this->thisNote]) {
                    tone(this->pin, this->melodia[this->thisNote]);
                }
            }
            if (this->estado == ESTADO_NOTA) {
                if ((millis() - this->t) < this->tiempo) {
                    return;
                }
                noTone(this->pin);
                this->tiempo = this->duracionNotas[this->thisNote] * 1.30;
                this->t = millis();
                this->estado = ESTADO_SILENCIO;
            }
        }

    private:
        int pin;
        int *melodia;
        int *duracionNotas;
        enum estado_t {
            ESTADO_INICIO,
            ESTADO_NOTA,
            ESTADO_SILENCIO,
            ESTADO_FIN
        };
        estado_t estado;
        int thisNote;
        unsigned long t;
        unsigned int tiempo;
};

Musica musica (PIN_SPEAKER, melodia, duracionNotas);

void loop() { 
    unsigned long instanteActual = millis();

    musica.loop();

    switch (estado) {
        case ESTADO_INICIAL : // Si acaba de encenderse el Arduino
            if (digitalRead(PIN_FIN_PARED) == VALOR_FIN_DETECTADO) {
                // Si se encuentra abajo, decimos que está abajo
                luces_Detenida_Pared();
                estado = ESTADO_ABAJO;
            }
            else {
                // Si no, decimos que está arriba
                luces_Andando();
                estado = ESTADO_ARRIBA;
            }
            break;
        case ESTADO_SUBIENDO :
            if (digitalRead(PIN_FIN_MONTANIA) == VALOR_FIN_DETECTADO) {
                stepper.stop();
                luces_Detenida_Montania();
                instanteAnterior = instanteActual;
                musica.start();
                estado = ESTADO_ARRIBA;
            }
            else {
                stepper.runSpeed();  //Run
            }
            break;
        case ESTADO_ARRIBA :
            if ((instanteActual - instanteAnterior) >= TIEMPO_ESPERA) {
                luces_Andando();
                stepper.setSpeed(-VELOCIDAD); // Ojo con el signo menos para invertir el giro
                estado = ESTADO_BAJANDO;
            }
            break;
        case ESTADO_BAJANDO :
            if (digitalRead(PIN_FIN_PARED) == VALOR_FIN_DETECTADO) {
                stepper.stop();
                luces_Detenida_Pared();
                instanteAnterior = instanteActual;
                musica.start();
                estado = ESTADO_ABAJO;
            }
            else {
                stepper.runSpeed();  //Run
            }
            break;
        case ESTADO_ABAJO :
            if ((instanteActual - instanteAnterior) >= TIEMPO_ESPERA) {
                luces_Andando();
                stepper.setSpeed(VELOCIDAD);
                estado = ESTADO_SUBIENDO;
            }
            break;

    }
}

Nota: se puede observar que no hay ni un solo delay(), for o while en todo el código.

GRACIAS! por tu generosidad IgnoranteAbsoluto.
Me reescribiste el sketch del funicular! Hace quince días que estaba dando vueltas con millis() sin poder avanzar, por lo que decidí pedir ayuda, y me encontré con un SamaritanoAbsoluto...
Voy a estudiar la lógica de la programación que hiciste y los principios y codificación de la máquina de estado. Tal vez con un Arduino Mega 2560 pueda integrar funicular, ascensor y desvíos.
Un cordial saludo.

IgnoranteAbsoluto: estuve estudiando principios y codificación de la máquina de estados y luego pude entender en cómo funciona la idea de la reprogramación que me enviaste del funicular.

Utilicé otro motor y el driver L298N por lo que cambié la secuencia a AccelStepper stepper = AccelStepper(4, MOTOR_PIN_1, MOTOR_PIN_3, MOTOR_PIN_2, MOTOR_PIN_4); y funcionó sin inconvenientes.

Por el momento pienso continuar en forma sencilla, agregando sólo un cambio/desvío de vía con un servo.

Muchas gracias nuevamente por tu ayuda.

Armando