Go Down

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

IgnoranteAbsoluto

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

Code: [Select]
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:

Code: [Select]
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.


apacher

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

IgnoranteAbsoluto

#17
Apr 29, 2020, 12:46 am Last Edit: Apr 29, 2020, 12:49 am by IgnoranteAbsoluto
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.

Code: [Select]
#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.


apacher

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.

apacher

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
Code: [Select]
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

apacher

Disculpas por la errata.
Debe decir:
Code: [Select]
AccelStepper stepper = AccelStepper(4, MOTOR_PIN_1, MOTOR_PIN_2, MOTOR_PIN_3, MOTOR_PIN_4);
Lo siento.
Armando

apacher

Buenos días.
Implementé el movimiento del primer servo utilizando millis() tal como me sugirió y orientó Ignorante_Absoluto. Antes de combinar este sketch con el del funicular solicito por favor que me orienten acerca de cómo disminuir la velocidad del movimiento del servo sin usar delay(), for o while. Este es el código con el que muevo un microservo SG90.
Code: [Select]

//Desvío simple con millis() 4/5/2020
//Servo con dos botones y LEDs. Resistencias Pull Up.

#include <Servo.h>

Servo servoUno;                              //motor en PIN ~3

//Botones
const uint8_t PIN_DESVIO_UNO = 4;            // Botón para desvío, mueve a izquierda
const uint8_t PIN_RECTA_UNO = 5;             // Botón para vía recta, mueve a derecha (0°)
const uint8_t VALOR_BOTON_DETECTADO = LOW;   //Valor que deberá tener el pin cuando se detecta el botón

//Para arranque
int gradosUno = 0;                           // Inicializa en la posición 0° vía recta

//LEDs
const uint8_t VERDE_RECTA_UNO = 6;           // Led de vía recta
const uint8_t AZUL_DESVIO_UNO = 7;           // LED de desvío

enum estado_t {
    ESTADO_INICIAL_UNO,                       
    ESTADO_DESVIANDO_UNO,
    ESTADO_DESVIO_UNO,
    ESTADO_RECTIFICANDO_UNO,
    ESTADO_RECTA_UNO,
}
estado = ESTADO_INICIAL_UNO;
const unsigned long TIEMPO_ESPERA_DESVIO = 500UL;
const unsigned long TIEMPO_ESPERA_UNO = 500UL;
unsigned long instanteAnterior = TIEMPO_ESPERA_UNO;

void setup() {
  pinMode(PIN_DESVIO_UNO, INPUT_PULLUP);     // resistencia PULL UP       
  pinMode(PIN_RECTA_UNO, INPUT_PULLUP);
  unsigned long instanteAnterior = TIEMPO_ESPERA_UNO;
  pinMode(PIN_RECTA_UNO, INPUT);             //Configura pines de entrada para botones
  pinMode(PIN_DESVIO_UNO, INPUT);
  pinMode(VERDE_RECTA_UNO, OUTPUT);          //Configura pines de salida para LEDs
  pinMode(AZUL_DESVIO_UNO, OUTPUT);
  servoUno.attach(3,750,1800);               //Configura el Servo, 750 ms = 0° y 1800 ms = 180°
  servoUno.write(gradosUno);                 //Al arrancar mueve el motor hasta 0°, vía recta
  digitalWrite(VERDE_RECTA_UNO, HIGH);       //Enciende LED verde, vía recta
}

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

  switch (estado) {
        case ESTADO_INICIAL_UNO:             // Encendido Arduino, vía recta 0°.
            estado = ESTADO_RECTA_UNO; 
         
        case ESTADO_DESVIANDO_UNO:           //Apretado el botón DESVIO_UNO
          if (digitalRead(PIN_RECTA_UNO) == VALOR_BOTON_DETECTADO) {
            digitalWrite (AZUL_DESVIO_UNO,HIGH);
            digitalWrite (VERDE_RECTA_UNO,LOW);
            instanteAnterior = instanteActual;
            servoUno.write(45);              //Mueve a izquierda
            estado = ESTADO_DESVIO_UNO;
            }
          else {
            estado = ESTADO_DESVIO_UNO;
            }
          break;
           
        case ESTADO_DESVIO_UNO:
          if ((instanteActual - instanteAnterior) >= TIEMPO_ESPERA_UNO) {
            estado = ESTADO_RECTIFICANDO_UNO;
            }
          break;

        case ESTADO_RECTIFICANDO_UNO:       //Apretado el botón RECTA_UNO
          if (digitalRead(PIN_DESVIO_UNO) == VALOR_BOTON_DETECTADO) {
            digitalWrite (VERDE_RECTA_UNO,HIGH);
            digitalWrite (AZUL_DESVIO_UNO,LOW);
            instanteAnterior = instanteActual;
            estado = ESTADO_RECTA_UNO;
            servoUno.write(0);              //Mueve a 0°
            }
          else {
            estado = ESTADO_RECTA_UNO;
            }
        break;
       
        case ESTADO_RECTA_UNO:
          if ((instanteActual - instanteAnterior) >= TIEMPO_ESPERA_UNO) {
            estado = ESTADO_DESVIANDO_UNO;
          }
        break;
    }
  }

Serán bienvenidos correcciones/agregados/cambios que haya que hacer en el resto del sketch. Desde ya muchas gracias.
Armando

IgnoranteAbsoluto

¿Qué significa "disminuir la velocidad del movimiento del servo"? ¿Significa que el servo se mueva de una posición a otra más lento de lo que lo hace "normalmente"? Si es así, ¿por qué? ¿Por estética (para que luzca más "bonito"), o por cuestiones mecánicas?

En principio se podría controlar que el servo se moviese de un punto a otro más despacio, salvo al inicializarse el Arduino porque el problema que tiene el servo es que se queda en la última posición en que se puso. Al reiniciar el Arduino (bien por reset o por apagado y encendido), no se sabe en qué posición está. Con lo que al empezar el programa se suele poner en una posición prefijada. Si no estaba en esa posición, se posicionará a "su velocidad normal".

"El truco" para que se mueva lentamente es ir posicionando el servo en grados intermedios poco a poco, a lo largo del tiempo, hasta que esté en la posición final deseada.

Viendo el código, a simple vista, no entiendo bien lo que se pretende hacer. Vendría bien una descripción de cómo se quiere que funcione y lo que ha de hacer. Lo que más me despista es el "tiempo de espera". ¿Esperar qué?
 

apacher

Muchas gracias por tu pronta respuesta.

El servo mueve un cambio de vías en un tren con escala HO (1:87), el objetivo de hacer el movimiento más lento es para simular los cambios de vía en la realidad. De la manera que funciona ahora el movimiento (de alrededor de treinta grados) es, a mi entender, demasiado rápido.

Cuando empleaba delay(), previamente a que me indicaras los pasos a seguir para trabajar con millis(), utilizaba un for:
Code: [Select]

for (int i = 50; i > 0; i--){  //Para mover a baja velocidad
        servoUno.write(i);     //Mueve a derecha grado a grado
        delay(25);
        }


La posición prefijada es la vía recta, sin habilitar el desvío, por lo que alineo el eje a cero grados y lo adjudico a vía recta, salvo que en algún sector el default sea con desvío, con lo que colocaré en ese caso el eje del servo correspondiente en alrededor de treinta grados.

Como el objetivo avanzado es integrar en el sketch otros dos cambios de vía, el funicular y un ascensor (en un Arduino Mega) debo evitar interrupciones. ¿Cómo debo programar con millis() el avance en grados intermedios?

El "tiempo de espera" seguramente es un despiste mío ;-) Si lo debo quitar, ¿cómo queda el if?

Un cordial saludo.

Armando

IgnoranteAbsoluto

Disculpa la tardanza en contestar. El trabajo me ha tenido prisionero. Sobre lo que tenías hecho del control de la aguja, he hecho unas cuantas modificaciones para que funcionara. En los comentarios del código explico algunos detalles.

Code: [Select]
//Desvío simple con millis() 4/5/2020
//Servo con dos botones y LEDs. Resistencias Pull Up.

#include <Servo.h>

Servo servoUno;                              //motor en PIN ~3

//Botones
const uint8_t PIN_DESVIO_UNO = 4;            // Botón para desvío, mueve a izquierda
const uint8_t PIN_RECTA_UNO = 5;             // Botón para vía recta, mueve a derecha (0°)
const uint8_t VALOR_BOTON_DETECTADO = LOW;   //Valor que deberá tener el pin cuando se detecta el botón

//Para arranque
int gradosUno = 0;                           // Inicializa en la posición 0° vía recta

//LEDs
const uint8_t VERDE_RECTA_UNO = 6;           // Led de vía recta
const uint8_t AZUL_DESVIO_UNO = 7;           // LED de desvío

enum estado_t {             // Estos son los estados de cualquier aguja, no importa qué aguja (no hace falta el _UNO)
    ESTADO_INICIAL,         // Para cuando se encienda el Arduino se posicione recta
    ESTADO_DESVIANDO,       // Cuando está cambiando de recta a desvío
    ESTADO_DESVIO,          // Cuando está en modo desvío
    ESTADO_RECTIFICANDO,    // Cuando está cambiando de desvío a recta
    ESTADO_RECTA,           // Cuando está en recta
} estado = ESTADO_INICIAL;  // Inicialmente tenemos que posicionarla recta, sí o sí

const unsigned long TIEMPO_DURACION_CAMBIO = 1500UL; // Este es el tiempo total que queremos que tarde la aguja en cambiar de posición
unsigned long instanteAnterior = 0;                 // En principio lo inicializamos a cero, sólo se usa cuando se está moviendo la aguja
unsigned long tiempoUnGrado = 0;                    // Tiempo que ha de pasar entre grado y grado de movimiento de la aguja
int anguloActual = 0;               // Ángulo en que se encuentra actualmente la aguja. Inicialmente lo pondremos a cero
int anguloDestino = 0;              // Ángulo en que queremoa que se posicione la aguja. Inicialmente lo pondremos a cero

void setup() {
    pinMode(PIN_DESVIO_UNO, INPUT_PULLUP);     // resistencia PULL UP
    pinMode(PIN_RECTA_UNO, INPUT_PULLUP);
    pinMode(VERDE_RECTA_UNO, OUTPUT);          //Configura pines de salida para LEDs
    pinMode(AZUL_DESVIO_UNO, OUTPUT);
    servoUno.attach(3,750,1800);               //Configura el Servo, 750 ms = 0° y 1800 ms = 180°
    servoUno.write(gradosUno);                 //Al arrancar mueve el motor hasta 0°, vía recta
}

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

    // La variable estado nos indica en qué estado se encuentra la máquina de estados.
    // Tenemos que controlar qué ha de hacerse mientras se está en ese "estado".
    // Según el estado y las "condiciones" se mantiene en el estado en que está o cambia de estado.
    switch (estado) {
case ESTADO_INICIAL:    // Encendido Arduino, posicionamos la vía recta 0°
            servoUno.write(0);                      // Mueve a 0°
            // Indicamos vía recta
            digitalWrite (AZUL_DESVIO_UNO, LOW);    // Apagar el LED de desvío
            digitalWrite(VERDE_RECTA_UNO, HIGH);    // Enciende LED verde, vía recta
    estado = ESTADO_RECTA;                  // Cambiamos de estado
            break; // Si no ponemos este break, el programa continúa ejecutando las siguientes líneas

case ESTADO_RECTA:      // La vía está recta y permanecerá en este estado hasta que se pulse el botón de desvío
    if (digitalRead(PIN_DESVIO_UNO) == VALOR_BOTON_DETECTADO) { // Si se pulsa el botón de desvío
                // Indicamos desvío
digitalWrite (VERDE_RECTA_UNO, LOW);
digitalWrite (AZUL_DESVIO_UNO, HIGH);
                anguloDestino = 45;                     // Ángulo en que queremoa que se posicione la aguja
                tiempoUnGrado = TIEMPO_DURACION_CAMBIO / abs(anguloDestino - anguloActual); // Tiempo de "salto" entre grado y grado de movimento
                anguloActual++;                         // Incrementamos un grado el ángulo actual
servoUno.write(anguloActual);           // Posisionamos el servo en el nuevo ángulo
instanteAnterior = instanteActual;      // Guardamos el instante en que se ha movido el servo
estado = ESTADO_DESVIANDO;              // Cambiamos de estado
    }
    break;

case ESTADO_DESVIANDO:  // Se está moviendo la aguja para desvío
            if ((instanteActual - instanteAnterior) >= tiempoUnGrado) { // Si ya ha pasado el tiempo que hay que esperar entre cada moviento de un grado
                anguloActual++;                         // Incrementamos un grado el ángulo actual
servoUno.write(anguloActual);           // Posisionamos el servo en el nuevo ángulo
instanteAnterior = instanteActual;      // Guardamos el instante en que se ha movido el servo
                if (anguloActual == anguloDestino) {    // Si ya la aguja está en su "destino", cambiamos de estado
                    estado = ESTADO_DESVIO;             // Cambiamos de estado
                }
            }
            break;

case ESTADO_DESVIO:     // La vía está desvíada y permanecerá en este estado hasta que se pulse el botón de recta
    if (digitalRead(PIN_RECTA_UNO) == VALOR_BOTON_DETECTADO) {  // Si se pulsa el botón de reta
                // Indicamos vía recta
digitalWrite (VERDE_RECTA_UNO, HIGH);
digitalWrite (AZUL_DESVIO_UNO, LOW);
                anguloDestino = 0;                      // Ángulo en que queremoa que se posicione la aguja
                tiempoUnGrado = TIEMPO_DURACION_CAMBIO / abs(anguloDestino - anguloActual); // Tiempo de "salto" entre grado y grado de movimento
                anguloActual--;                         // Decrementamos un grado el ángulo actual
servoUno.write(anguloActual);           // Posisionamos el servo en el nuevo ángulo
instanteAnterior = instanteActual;      // Guardamos el instante en que se ha movido el servo
estado = ESTADO_RECTIFICANDO;           // Cambiamos de estado
    }
    break;

case ESTADO_RECTIFICANDO:   // Se está moviendo la aguja para vía recta
            if ((instanteActual - instanteAnterior) >= tiempoUnGrado) { // Si ya ha pasado el tiempo que hay que esperar entre cada moviento de un grado
                anguloActual--;                         // Decrementamos un grado el ángulo actual
servoUno.write(anguloActual);           // Posisionamos el servo en el nuevo ángulo
instanteAnterior = instanteActual;      // Guardamos el instante en que se ha movido el servo
                if (anguloActual == anguloDestino) {    // Si ya la aguja está en su "destino", cambiamos de estado
                    estado = ESTADO_RECTA;              // Cambiamos de estado
                }
            }
            break;
    }
}



Se puede cambiar la velocidad del movimiento de la aguja aumentando o disminuyendo el valor de TIEMPO_DURACION_CAMBIO.

Es una solución rápida, porque me imagino que querrás controlar más de una aguja con el mismo Arduino. Para ello lo más "cómodo" es convertir este código en una clase y trabajar con tantos objetos como agujas se quiera controlar. En cuanto tenga algo de tiempo procuraré hacer la clase.

Un par de dudas que me surgen de cómo sería deseable que funcione.
- ¿Qué luz debería estar encendida, mientras se está moviendo la aguja? Ninguna, las dos, parpadeando, la de "destino", la de "origen".
- ¿Qué se ha de hacer si se pulsa "el otro botón" durante el movimiento de la aguja? Por ejemplo: si pulsamos el botón de desvío; la aguja se mueve a la posición de desvío. Si antes de llegar a la posición de desvío se pulsa el botón de vía recta, ¿no hacemos caso hasta que se termine de mover la aguja o inmediatamente movemos la aguja en sentido contrario para ponerla en vía recta?

Nota: no sé si se debería de crear un nuevo post o continuar con este, tal vez con el nombre cambiado. Quizás un moderador podría aconsejar sobre ello.

IgnoranteAbsoluto

Aquí tienes la "adaptación rápida" del código convertido en la clase Aguja. Esto nos permite definir más agujas rápidamente. Como ejemplo, he puesto que controle dos agujas.

Code: [Select]
//Desvío simple con millis() 4/5/2020
//Servo con dos botones y LEDs. Resistencias Pull Up.

#include <Servo.h>

const uint8_t PIN_SERVO_UNO = 3;    // motor en PIN ~3
const uint8_t PIN_SERVO_DOS = 10;   // motor en PIN ~10

//Botones
const uint8_t PIN_DESVIO_UNO = 4;            // Botón para desvío, mueve a izquierda
const uint8_t PIN_RECTA_UNO = 5;             // Botón para vía recta, mueve a derecha (0°)
const uint8_t PIN_DESVIO_DOS = 8;            // Botón para desvío, mueve a izquierda
const uint8_t PIN_RECTA_DOS = 9;             // Botón para vía recta, mueve a derecha (0°)
const uint8_t VALOR_BOTON_DETECTADO = LOW;   //Valor que deberá tener el pin cuando se detecta el botón

//LEDs
const uint8_t VERDE_RECTA_UNO = 6;           // Led de vía recta
const uint8_t AZUL_DESVIO_UNO = 7;           // LED de desvío
const uint8_t VERDE_RECTA_DOS = 11;           // Led de vía recta
const uint8_t AZUL_DESVIO_DOS = 12;           // LED de desvío

const unsigned long TIEMPO_DURACION_CAMBIO = 1500UL; // Este es el tiempo total que queremos que tarde la aguja en cambiar de posición


// Prototipado de la clase Aguja
class Aguja {
    public:     // Las variables y funciones declaradas como públicas son accesibles por todos
        // Constructor
        Aguja(Servo &servo, unsigned long duracionCambio, uint8_t pinRecta, uint8_t pinDesvio, uint8_t pinLuzVerde, uint8_t pinLuzAzul);
        void loop();    // Lo que normalmente pondríamos en el loop() principal. Se ha de ejecutar contínuamente
    private:    // Las variables y funciones declaradas como privada no son accesibles fuera de la clase
        Servo &servo;                   // Referencia al servo
        unsigned long duracionCambio;   // Este es el tiempo total que queremos que tarde la aguja en cambiar de posición
        uint8_t pinRecta;               // Botón para vía recta, mueve a derecha (0°)
        uint8_t pinDesvio;              // Botón para desvío, mueve a izquierda
        uint8_t pinLuzVerde;
        uint8_t pinLuzAzul;
        int anguloActual;               // Ángulo en que se encuentra actualmente la aguja. Inicialmente lo pondremos a cero
        int anguloDestino;              // Ángulo en que queremoa que se posicione la aguja. Inicialmente lo pondremos a cero
        unsigned long instanteAnterior; // En principio lo inicializamos a cero, sólo se usa cuando se está moviendo la aguja
        unsigned long tiempoUnGrado;    // Tiempo que ha de pasar entre grado y grado de movimiento de la aguja
        enum estado_t {             // Estos son los estados de cualquier aguja, no importa qué aguja (no hace falta el _UNO)
            ESTADO_INICIAL,         // Para cuando se encienda el Arduino se posicione recta
            ESTADO_DESVIANDO,       // Cuando está cambiando de recta a desvío
            ESTADO_DESVIO,          // Cuando está en modo desvío
            ESTADO_RECTIFICANDO,    // Cuando está cambiando de desvío a recta
            ESTADO_RECTA,           // Cuando está en recta
        } estado;                   // Inicialmente tenemos que posicionarla recta, sí o sí
};

// Implementación de las funciones de la clase Aguja
Aguja::Aguja(Servo &servo, unsigned long duracionCambio, uint8_t pinRecta, uint8_t pinDesvio, uint8_t pinLuzVerde, uint8_t pinLuzAzul)
    : servo(servo),
    duracionCambio(duracionCambio),
    pinRecta(pinRecta),
    pinDesvio(pinDesvio),
    pinLuzVerde(pinLuzVerde),
    pinLuzAzul(pinLuzAzul),
    anguloActual(0),
    anguloDestino(0),
    instanteAnterior(0),
    tiempoUnGrado(0),
    estado(ESTADO_INICIAL)
{
    pinMode(pinRecta, INPUT_PULLUP);    // resistencia PULL UP
    pinMode(pinDesvio, INPUT_PULLUP);
    pinMode(pinLuzVerde, OUTPUT);       //Configura pines de salida para LEDs
    pinMode(pinLuzAzul, OUTPUT);
}

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

    // La variable estado nos indica en qué estado se encuentra la máquina de estados.
    // Tenemos que controlar qué ha de hacerse mientras se está en ese "estado".
    // Según el estado y las "condiciones" se mantiene en el estado en que está o cambia de estado.
    switch (estado) {
case ESTADO_INICIAL:    // Encendido Arduino, posicionamos la vía recta 0°
            servo.write(0);                     // Mueve a 0°
            // Indicamos vía recta
            digitalWrite (pinLuzAzul, LOW);     // Apagar el LED de desvío
            digitalWrite(pinLuzVerde, HIGH);    // Enciende LED verde, vía recta
    estado = ESTADO_RECTA;              // Cambiamos de estado
            break; // Si no ponemos este break, el programa continúa ejecutando las siguientes líneas

case ESTADO_RECTA:      // La vía está recta y permanecerá en este estado hasta que se pulse el botón de desvío
    if (digitalRead(pinDesvio) == VALOR_BOTON_DETECTADO) {  // Si se pulsa el botón de desvío
                // Indicamos desvío
digitalWrite (pinLuzVerde, LOW);
digitalWrite (pinLuzAzul, HIGH);
                anguloDestino = 45;                     // Ángulo en que queremoa que se posicione la aguja
                tiempoUnGrado = duracionCambio / abs(anguloDestino - anguloActual); // Tiempo de "salto" entre grado y grado de movimento
                anguloActual++;                         // Incrementamos un grado el ángulo actual
servo.write(anguloActual);              // Posisionamos el servo en el nuevo ángulo
instanteAnterior = instanteActual;      // Guardamos el instante en que se ha movido el servo
estado = ESTADO_DESVIANDO;              // Cambiamos de estado
    }
    break;

case ESTADO_DESVIANDO:  // Se está moviendo la aguja para desvío
            if ((instanteActual - instanteAnterior) >= tiempoUnGrado) { // Si ya ha pasado el tiempo que hay que esperar entre cada moviento de un grado
                anguloActual++;                         // Incrementamos un grado el ángulo actual
servo.write(anguloActual);              // Posisionamos el servo en el nuevo ángulo
instanteAnterior = instanteActual;      // Guardamos el instante en que se ha movido el servo
                if (anguloActual == anguloDestino) {    // Si ya la aguja está en su "destino", cambiamos de estado
                    estado = ESTADO_DESVIO;             // Cambiamos de estado
                }
            }
            break;

case ESTADO_DESVIO:     // La vía está desvíada y permanecerá en este estado hasta que se pulse el botón de recta
    if (digitalRead(pinRecta) == VALOR_BOTON_DETECTADO) {   // Si se pulsa el botón de reta
                // Indicamos vía recta
digitalWrite (pinLuzVerde, HIGH);
digitalWrite (pinLuzAzul, LOW);
                anguloDestino = 0;                      // Ángulo en que queremoa que se posicione la aguja
                tiempoUnGrado = duracionCambio / abs(anguloDestino - anguloActual); // Tiempo de "salto" entre grado y grado de movimento
                anguloActual--;                         // Decrementamos un grado el ángulo actual
servo.write(anguloActual);              // Posisionamos el servo en el nuevo ángulo
instanteAnterior = instanteActual;      // Guardamos el instante en que se ha movido el servo
estado = ESTADO_RECTIFICANDO;           // Cambiamos de estado
    }
    break;

case ESTADO_RECTIFICANDO:   // Se está moviendo la aguja para vía recta
            if ((instanteActual - instanteAnterior) >= tiempoUnGrado) { // Si ya ha pasado el tiempo que hay que esperar entre cada moviento de un grado
                anguloActual--;                         // Decrementamos un grado el ángulo actual
servo.write(anguloActual);              // Posisionamos el servo en el nuevo ángulo
instanteAnterior = instanteActual;      // Guardamos el instante en que se ha movido el servo
                if (anguloActual == anguloDestino) {    // Si ya la aguja está en su "destino", cambiamos de estado
                    estado = ESTADO_RECTA;              // Cambiamos de estado
                }
            }
            break;
    }
}


Servo servoUno;
Servo servoDos;
Aguja agujaUno(servoUno, TIEMPO_DURACION_CAMBIO, PIN_RECTA_UNO, PIN_DESVIO_UNO, VERDE_RECTA_UNO, AZUL_DESVIO_UNO);
Aguja agujaDos(servoDos, TIEMPO_DURACION_CAMBIO, PIN_RECTA_DOS, PIN_DESVIO_DOS, VERDE_RECTA_DOS, AZUL_DESVIO_DOS);

void setup() {
    servoUno.attach(PIN_SERVO_UNO, 750, 1800);    //Configura el Servo, 750 ms = 0° y 1800 ms = 180°
    servoDos.attach(PIN_SERVO_DOS, 750, 1800);    //Configura el Servo, 750 ms = 0° y 1800 ms = 180°
}

void loop() {
    agujaUno.loop();  // Llamamos al loop() de Aguja
    agujaDos.loop();  // Llamamos al loop() de Aguja
}


apacher

Muchas gracias por tu nuevo mensaje! No te preocupes por favor por la demora en responder, tus aportes y soluciones bien valen una cuarentena!

 Estaba trabajando con dos desvíos (el proyecto es manipular cuatro) y no sabía cómo hacerlo sin utilizar "clase", que no alcanzaba a comprender su uso. Voy a estudiar para entender y aplicar lo que me enviaste.

Respondo ahora a tus preguntas:

"- ¿Qué luz debería estar encendida, mientras se está moviendo la aguja? Ninguna, las dos, parpadeando, la de "destino", la de "origen"."
Si fuera posible, apagada la de "destino" y parpadeando la de "origen".

"- ¿Qué se ha de hacer si se pulsa "el otro botón" durante el movimiento de la aguja? Por ejemplo: si pulsamos el botón de desvío; la aguja se mueve a la posición de desvío. Si antes de llegar a la posición de desvío se pulsa el botón de vía recta, ¿no hacemos caso hasta que se termine de mover la aguja o inmediatamente movemos la aguja en sentido contrario para ponerla en vía recta?"
Sería adecuado no hacer caso, ¿te parece?

"no sé si se debería de crear un nuevo post o continuar con este, tal vez con el nombre cambiado. Quizás un moderador podría aconsejar sobre ello."
Podría editar el nombre y escribir algo así como: 'millis() para varios motores  servo y stepper'. ¿Quedaría explicitado el amplio contenido del post?

Un cordial saludo.

Armando

IgnoranteAbsoluto

El título propuesto me gusta.

Un par de preguntas más.

¿Tienes pensado poner algo más a parte de los dos motores paso a paso y los cuatro sevos? Con todo lo que conlleva de LED y pulsadores.

¿Lo piensas controlar todo con un Arduino MEGA o con dos Arduino UNO (uno para los paso a paso y otro para los servos o un motor paso a paso y dos servos cada uno)?

¿Para el cambio de ajuga te valdría con hacerlo con un único pulsador por aguja en lugar de dos? Con uno solo, lo pulsas y cambia la aguja a la "otra posición".

apacher

Muchas gracias, ya cambié el título del post por: "millis() para varios motores  servo y stepper".

Respodo a tus preguntas:

"¿Tienes pensado poner algo más a parte de los dos motores paso a paso y los cuatro sevos? Con todo lo que conlleva de LED y pulsadores."
Será sólo para los dos PAPs y los cuatro servos.

"¿Lo piensas controlar todo con un Arduino MEGA o con dos Arduino UNO (uno para los paso a paso y otro para los servos o un motor paso a paso y dos servos cada uno)?"
Pienso controlarlo con un Arduino Mega, ¿te parece posible?

"¿Para el cambio de ajuga te valdría con hacerlo con un único pulsador por aguja en lugar de dos? Con uno solo, lo pulsas y cambia la aguja a la "otra posición"."
Sería ideal hacerlo con un único pulsador para cada aguja, siendo así replicaría las luces de las vías en el tablero indicando "Recta" o "Desvío". Pero no sé cómo hacerlo y temo cargarte con otro trabajo.

Un cordial saludo

Armando

apacher

No tenía conocimiento de la POO-OOP por lo que no entendía la lógica de la construcción de clases con variables y funciones de los objetos. Por ello te dije que no sabía cómo hacer para un único pulsador para cada aguja. Me puse a estudiar el tema, felizmente pude seguir paso a paso tu programa y conclui que la rutina de cambios de estado de un pulsador es lo mismo esté o no el objeto dentro de una clase.
Con tus indicaciones me siento como que he ascendido a una división superior (me hiciste ganar un campeonato!).
Muchas gracias nuevamente.
Un cordial saludo
Armando

Go Up