Cómo crear un cronometro cuenta regresiva?

Hola, buenas noches...

Estoy armando mi proyecto, y una parte de él requiere un contador de tiempo en regresión. He buscado info por la web y he encontrado proyectos de contadores de tiempo de diversos diseños que utilizan LCDs o de diseño de 7 segmentos, pero ninguno se asemeja a mi necesidad. También he buscado por mi cuenta, pero no he podido entender ni menos hacerme una idea de como hacerlo funcionar(he podido avanzar en otras partes de mi proyecto). Básicamente lo que necesito es que cualquiera de los pulsadores tipo SHK_N° inicie el conteo en regresivo (en 40 segundos) al ser pulsados...

int SHK_1=2; //Sensor delantero Izquierdo
int SHK_2=3; //Sensor delantero derecho
int SHK_3=4; //Sensor central Izquierdo
int SHK_4=5; //Sensor central derecho
int SHK_5=6; //Sensor trasero Izquierdo
int SHK_6=7; //Sensor trasero Izquierdo

void setup() {
pinMode(SHK_1,INPUT_PULLUP);
pinMode(SHK_2,INPUT_PULLUP);
pinMode(SHK_3,INPUT_PULLUP);
pinMode(SHK_4,INPUT_PULLUP);
pinMode(SHK_5,INPUT_PULLUP);
pinMode(SHK_6,INPUT_PULLUP);
pinMode(CANCEL,INPUT_PULUP);

Y que SÓLO el botón CANCEL pueda detener el contador.... Podrían ayudarme, gracias...

P.D: Más que darme un código, me podrían explicar su funcionamiento, para aprender, eso sería genial, gracias.

Pero según tu necesidad por donde deseas mostrar esos datos? LCD? Display 7 seg? Monitor serial?

La idea sería que uses millis()

Cuando un pulsador sea pulsado tomas los millisIniciales, a esos le sumas en otra variable el tiempo de tu contador 40seg(40.000 millis)y la diferencia es "la cuenta atrás"

Ahora bien, como ha dicho Carlos, como lo vas a mostrar???

Una idea básica:

// Variables globales:
int tiempo = -1;
unsigned long tAnterior;

void loop() {
  if (!digitalRead(SHK_X)) { // ! es para invertir la lectura del botón. En configuración pull-up, presionar un botón lleva al pin al estado LOW; o sea, false
    tiempo = 40; // segundos
    tAnterior = millis(); // Momento en que se inicia la cuenta
  }

  if (tiempo >= 0) { // Conviene que la variable "tiempo" sea con signo
    unsigned long tActual = millis();
    if (tActual - tAnterior >= 1000) { // Descuento cada segundo
      mostrarTiempo();
      tAnterior = tActual; // Momento en que se inicia un nuevo segundo
      tiempo--;
    }
  }

  if (!digitalRead(CANCEL)) // Cancelar la cuenta, poniendo el timer en cero.
    tiempo = 0;
}

Primero una observación que nunca entiendo que beneficio tiene

unsigned long tActual = millis();
    if (tActual - tAnterior >= 1000) { // Descuento cada segundo
      mostrarTiempo();
      tAnterior = tActual; // Momento en que se inicia un nuevo segundo
      tiempo--;
    }

para qué crear una variable tActual si puedo prescindir de ella de este modo!!

    if (millis() - tAnterior >= 1000) { // Descuento cada segundo
      mostrarTiempo();
      tAnterior = millis(); // Momento en que se inicia un nuevo segundo
      tiempo--;
    }

Iba a dar una alternativa diferente pero releyendo la consulta veo que el interesado no necesita precisión en milisegundos en su contador regresivo así que me guardo la idea por el momento.

surbyte: Primero una observación que nunca entiendo que beneficio tiene

unsigned long tActual = millis();
    if (tActual - tAnterior >= 1000) { // Descuento cada segundo
      mostrarTiempo();
      tAnterior = tActual; // Momento en que se inicia un nuevo segundo
      tiempo--;
    }

No lo sé, pensaba que era más rápido acceder a una variable que a una función repetidas veces ::)

surbyte: Iba a dar una alternativa diferente pero releyendo la consulta veo que el interesado no necesita precisión en milisegundos en su contador regresivo así que me guardo la idea por el momento.

Creo que estamos pensando la misma cosa, pero también me lo guardaré...

No siempre el código fuente más resumido da resultado a un código más rápido o más corto, surbyte. Este es un caso típico; y la opción de Lucario448 es más efectiva pues, como él dijo, evita volver a llamar a la función millis por segunda vez. El hecho de no utilizar una variable para almacenar millis no significa que el compilador no deba guardar en una variable intermedia la salida de millis. La única diferencia es que dicha variable intermedia se destruye inmediatamente a su utilización, mientras que si tú le has dado nombre, la mantiene hasta salir de ámbito. En este caso viene bien, puesto que puedes reutilizarla para asignarla a tAnterior en lugar de hacer otra llamada a millis. No obstante, también está la "alternativa diferente".

carlosjq10: Pero según tu necesidad por donde deseas mostrar esos datos? LCD? Display 7 seg? Monitor serial?

Hola, buenas noches...

Ayer cuando escribía este hilo pensaba en lo mismo, en realidad quería crear un contador en retroceso (de 40 a 0 seg), pero ví que se me acabaron los pines disponibles de la Arduino Uno, así que como solución a ese problema me compré una Arduino Mega y añadí a la compra un LCD (DFR0009 LCD), así que con el problema de los pines y del contador resuelto, puedo empezar a trabajar sin problemas.

Mira, lo que necesito es lo siguiente (disculpa si suena un poco exigente :O ), estoy ensamblando mi código para desarrollar mi proyecto, la condición de inicio del contador en reversa es que cualquiera de los pulsadores tipo SHK_ N° sea pulsado y que sea cancelado con algún botón de la pantalla del LCD (Estoy trabajando en descubrir el funcionamiento de este aparato, no quiero ni deseo que me pongan los códigos en bandeja)

//                 Ingenieria en Telecomunicaciones, conectividad y redes     

//                            Proyecto Smart Life Protector
//                                  Cristopher López
//                                    Inacap 2016

//Declaración de Leds
int Led_1=30;  //Led delantero Izquierdo
int Led_2=31;  //Led delantero Derecho
int Led_3=32; //Led central Izquierdo
int Led_4=33; //Led central Derecho
int Led_5=34; //Led trasero Izquierdo
int Led_6=35; //Led trasero Derecho

//Combinatorios

int Led_7=36;  //SHK_1 + SHK_2
int Led_8=37;  //SHK_2 + SHK_4
int Led_9=38;  //SHK_4 + SHK_6
int Led_10=39; //SHK_6 + SHK_5
int Led_11=40; //SHK_5 + SHK_3
int Led_12=41; //SHK_3 + SHK_1

//Declaración de sensores de impacto

int SHK_1=42; //Sensor delantero Izquierdo
int SHK_2=43; //Sensor delantero derecho
int SHK_3=44; //Sensor central Izquierdo
int SHK_4=45; //Sensor central derecho
int SHK_5=46; //Sensor trasero Izquierdo
int SHK_6=47; //Sensor trasero Izquierdo

int val;

void setup() {
  //Leds como salidas
pinMode(Led_1,OUTPUT);
pinMode(Led_2,OUTPUT);
pinMode(Led_3,OUTPUT);
pinMode(Led_4,OUTPUT);
pinMode(Led_5,OUTPUT);
pinMode(Led_6,OUTPUT);
pinMode(Led_7,OUTPUT);
pinMode(Led_8,OUTPUT);
pinMode(Led_9,OUTPUT);
pinMode(Led_10,OUTPUT);
pinMode(Led_11,OUTPUT);
pinMode(Led_12,OUTPUT);
//Sensores como entrada
pinMode(SHK_1,INPUT_PULLUP);
pinMode(SHK_2,INPUT_PULLUP);
pinMode(SHK_3,INPUT_PULLUP);
pinMode(SHK_4,INPUT_PULLUP);
pinMode(SHK_5,INPUT_PULLUP);
pinMode(SHK_6,INPUT_PULLUP);
}
void loop() {
  
      //Led directos
  if (digitalRead(SHK_1) == HIGH) { //Led 1
         digitalWrite(Led_1, HIGH);
           }else{
            digitalWrite(Led_1, LOW);
            }                       //Cierre Led 1
  if (digitalRead(SHK_2)==HIGH){     //Led 2
          digitalWrite(Led_2,HIGH);
           }else{
            digitalWrite(Led_2,LOW);
            }                       //Cierre Led 2
  if (digitalRead(SHK_3)==HIGH){     //Led 3
          digitalWrite(Led_3,HIGH);
           }else{
            digitalWrite(Led_3,LOW);
           }                       //Cierre Led 3
   if (digitalRead(SHK_4)==HIGH){   //Led 4
           digitalWrite(Led_4,HIGH);
           }else{
            digitalWrite(Led_4,LOW);
           }                       //Cierre Led 4
   if (digitalRead(SHK_5)==HIGH){   //Led 5
           digitalWrite(Led_5,HIGH);
           }else{
            digitalWrite(Led_5,LOW);
           }                      //Cierre Led 5
   if (digitalRead(SHK_6)==HIGH){   //Led 6
           digitalWrite(Led_6,HIGH);
           }else{
            digitalWrite(Led_6,LOW);
           }                       //Cierre Led 6
//**************************************************************
       //Combinatorios    
    if(digitalRead(SHK_1)&& digitalRead(SHK_2)==HIGH){
        digitalWrite(Led_7,HIGH);
         }else{
          digitalWrite(Led_7,LOW);
         }
    if(digitalRead(SHK_2)&& digitalRead(SHK_4)==HIGH){
         digitalWrite(Led_8,HIGH);     
         }else{
          digitalWrite(Led_8,LOW);
         }
    if(digitalRead(SHK_4)&& digitalRead(SHK_6)==HIGH){
         digitalWrite(Led_9,HIGH);
         }else{
          digitalWrite(Led_9,LOW);
         }
    if(digitalRead(SHK_6)&& digitalRead(SHK_5)==HIGH){
         digitalWrite(Led_10,HIGH);
         }else{
          digitalWrite(Led_10,LOW);
         }
    if(digitalRead(SHK_3)&& digitalRead(SHK_5)==HIGH){
         digitalWrite(Led_11,HIGH);
         }else{
          digitalWrite(Led_11,LOW);
         }
    if(digitalRead(SHK_1)&& digitalRead(SHK_3)==HIGH){
         digitalWrite(Led_12,HIGH);
         }else{
          digitalWrite(Led_12,LOW);
         }
//***************************************************************          
} //Cierre loop

MaestroLopez:
Mira, lo que necesito es lo siguiente (disculpa si suena un poco exigente :open_mouth: ), estoy ensamblando mi código para desarrollar mi proyecto, la condición de inicio del contador en reversa es que cualquiera de los pulsadores tipo SHK_ N° sea pulsado y que sea cancelado con algún botón de la pantalla del LCD

Suena sencillo: cualquier SHK arranca la cuenta, mas no la reiniciará hasta que sea cancelada o terminada.

Si vas a introducir esa característica al programa, ve creando el código bajo el fundamento que acabo de dar.

MaestroLopez:
(Estoy trabajando en descubrir el funcionamiento de este aparato, no quiero ni deseo que me pongan los códigos en bandeja)

Confieso que soy el culpable de eso último :o :sweat_smile:

Pues si quieres aprender por cuenta propia, entonces comienza por aprender para qué sirve millis y cómo se usa ; luego hablamos…

Espera un segundo… si por “este aparato” te refieres a la pantalla LCD, entonces soy inocente :slight_smile:

Lucario448: Suena sencillo: cualquier SHK arranca la cuenta, mas no la reiniciará hasta que sea cancelada o terminada.

Si vas a introducir esa característica al programa, ve creando el código bajo el fundamento que acabo de dar.

Confieso que soy el culpable de eso último :o :sweat_smile:

Pues si quieres aprender por cuenta propia, entonces comienza por aprender para qué sirve millis y cómo se usa ; luego hablamos...

Espera un segundo... si por "este aparato" te refieres a la pantalla LCD, entonces soy inocente :)

Si, me refiero al LCD, no tenía previsto adquirir uno, pero como fui a comprar la Mega, decidí comprarla XD .... y sobre la función milis, dame unos días para poder aprender algo sobre eso y también sobre el LCD, intentaré crear un programa decente y hablamos, gracias por la ayuda (Y)

MaestroLopez: .... y sobre la función milis, dame unos días para poder aprender algo sobre eso [...] intentaré crear un programa decente y hablamos

Amén hermano, amén.

Así entenderás lo que hice en post #3 ;)

Así entenderás lo que hice en post #3 ;)

Hola, ha pasado casi una semana desde el último post, bueno, como dije, aprendí a usar el LCD e incluso reestructuré mi código (me enseñó un amigo que es programador) para que se apaguen los dos led y prenda el tercero, además de que limpié el código ya que tenía los procesos repetidos, solo falta realizar una pequeña modificación el el código (no podía ser perfecto), pero le que me preocupa ahora (A parte de la SIM808, maldición, no he podido hacerla funcionar) es el uso del contador, he estudiado el comando "milis" y he intentado descifrar tu código, pero de verdad que es muy complicado (créeme, he escrito el código en papel he intenté descifrarlo, pero soy muy cabeza dura, hasta un profesor pudo entenderlo y yo no)... Encontré por ahí un contador que utiliza delay, pero me parece muy mediocre utilizar delay (no lo sé, me estoy jugando el termino de mi carrera con esto y simplemente no puedo hacer las cosas tan fáciles para mi), en fin, te escribiré mis preguntas....

1)

 tAnterior = millis(); // Momento en que se inicia la cuenta

Le metí mano al código y tal como lo describiste si sólo coloco millis(); el programa comienza a contar sin parar, por lo que puedo deducir que al activarse el pulsador, lo primero que se acciona a nivel de programa es esta línea.

2)

if (!digitalRead(SHK_X))

¿Qué quiere decir este signo "!"?....estudiando millis me encontré con que significaba "desigualdad", pero no le veo la utilidad y no puedo entender que función cumple.

3)

if (tiempo >= 0) { // Conviene que la variable "tiempo" sea con signo
    unsigned long tActual = millis();
    if (tActual - tAnterior >= 1000) { // Descuento cada segundo
      mostrarTiempo();
      tAnterior = tActual; // Momento en que se inicia un nuevo segundo
      tiempo--;
    }
  }

  if (!digitalRead(CANCEL)) // Cancelar la cuenta, poniendo el timer en cero.
    tiempo = 0;
}

Aquí viene la ensalada de dudas en mi cabeza..

3.1) lo escribiré con mis palabras, para ser más claro en mi idea... Si el "tiempo" es igual o mayor que 0

 unsigned long tActual = millis();

¿que significa esa línea?, no puedo entenderla

3.2)

 if (tActual - tAnterior >= 1000) { // Descuento cada segundo

esta otra línea tampoco puedo comprenderla, dirá algo así como: tiempo que corre hacia adelante - (la Arduino reconoce este símbolo como una resta?) no sé que valor toma este tAnterior igual o menor que 1000 mili segundos.

3.3)mostrarTiempo(); ¿cómo la Arduino sabe que es "mostrarTiempo?...según lo que he aprendido en estas semanas hay que declarar todo en las variables globales y esto apareció de la nada :o

3.4)

tAnterior = tActual; // Momento en que se inicia un nuevo segundo
tiempo--;

¿De que forma se inicia un nuevo tiempo?, perdón por la pregunta, pero he intentado imaginarme y desarrollar de una forma lógica esto, pero es realmente dificil... y supongo que el "tiempo--"" añade un nuevo segundo al cumplirse el ciclo, o me equivoco?...y de esta en lo correcto, no tiene dificultad con el codigo inicial de esta pregunta (3.4)...

Sé que son muchas las preguntas, pero más que copiar y pegar prefiero entender y modificar, eso, gracias por tu tiempo (Y).

MaestroLopez: 1)

 tAnterior = millis(); // Momento en que se inicia la cuenta

Le metí mano al código y tal como lo describiste si sólo coloco millis(); el programa comienza a contar sin parar

Es para tener un punto de partida. Correcto, millis comienza a contar apenas las interrupciones sean habilitadas (algo que ocurre incluso antes del setup; o mejor dicho, a los 20 milisegundos justo después de encender); y también correcto: nunca se detiene. millis es una cuenta (en milisegundos) del tiempo que el Arduino lleva funcionando, se desborda (vuelve a cero) en aprox. 54 días de funcionamiento continuo.

MaestroLopez: 2)

if (!digitalRead(SHK_X))

¿Qué quiere decir este signo "!"?....estudiando millis me encontré con que significaba "desigualdad", pero no le veo la utilidad y no puedo entender que función cumple.

"Desigualdad", a qué suena en otras palabras? "NO es igual que", también dicho "negación de 'es igual que'".

Ese es el punto: significa negación. En lógica booleana, qué significa la negación? Valor de verdad opuesto (!true == false && !false == true).

En la mayoría de lenguajes de programación, '!' significa invertir valor booleano de una variable (o de un bit).

Ahora aquí respondo otra pregunta que de seguro te surgió ahora mismo: por qué eso ahí? Pues primero lee el comentario que dejé en esa línea, hay una parte que dice:

En configuración pull-up, presionar un botón lleva al pin al estado LOW; o sea, false

Configuraste las entradas con INPUT_PULLUP, lo que quiere decir que tales pines naturalmente se mantienen en estado HIGH (aún sin conectar a nada). Pues lógicamente para que sense un pulso, hay que llevarlo al estado contrario (LOW); esto se logra conectando el botón entre pin y tierra.

Si no colocase ese '!' al lado del digitalRead, entonces la condición del if se cumpliría sólo si NO se presiona ese botón. Esa era la idea? Si la respuesta es negativa, entonces que la condición SEA LA OPUESTA (con !).

Se entiende?

MaestroLopez: 3)

if (tiempo >= 0) { // Conviene que la variable "tiempo" sea con signo
    unsigned long tActual = millis();
    if (tActual - tAnterior >= 1000) { // Descuento cada segundo
      mostrarTiempo();
      tAnterior = tActual; // Momento en que se inicia un nuevo segundo
      tiempo--;
    }
  }

 if (!digitalRead(CANCEL)) // Cancelar la cuenta, poniendo el timer en cero.    tiempo = 0; }




Aquí viene la ensalada de dudas en mi cabeza..

3.1) lo escribiré con mis palabras, para ser más claro en mi idea... **Si el "tiempo" es igual o mayor que 0**

tiempo = 0, es para que en pantalla se logre imprimir el "segundo cero". No sé si lo notaste en un comentario, había dicho que convenía que fuera con signo, porque así ya con -1 la pantalla deja de actualizarse. Es válido mostrar cero segundos, pero -1? Como que no...

MaestroLopez: unsigned long tActual = millis();

¿que significa esa línea?, no puedo entenderla

Por cuestiones de rendimiento (discusión entre surbyte, noter y yo), millis se llama sólo al principio del loop; pero tampoco el valor puede quedarse estático, así que se actualiza cada vez que el ciclo loop se repita.

MaestroLopez: 3.2)

 if (tActual - tAnterior >= 1000) { // Descuento cada segundo

esta otra línea tampoco puedo comprenderla, dirá algo así como: tiempo que corre hacia adelante - (la Arduino reconoce este símbolo como una resta?) no sé que valor toma este tAnterior igual o menor que 1000 mili segundos.

if (la diferencia entre el tiempo actual y el punto de partida >= un segundo)

La resta es para calcular la diferencia que hay entre dos números. Matemática básica. 1 segundo = 1000 milisegundos. Las operaciones matemáticas tienen prioridad sobre las comparaciones booleanas. Implícitamente se entiende así:

if ((tActual - tAnterior) >= 1000)

MaestroLopez: 3.3)

mostrarTiempo();

¿cómo la Arduino sabe que es "mostrarTiempo?...según lo que he aprendido en estas semanas hay que declarar todo en las variables globales y esto apareció de la nada :o

Acaso también querías que te dijera cómo manejar tu pantalla?

mostrarTiempo es una función hipotética. Aquí es donde debes ejecutar el conjunto de instrucciones necesarias para mostrar, en pantalla, el tiempo restante. Usa la variable tiempo para tal efecto.

MaestroLopez: 3.4)

tAnterior = tActual; // Momento en que se inicia un nuevo segundo
tiempo--;

¿De que forma se inicia un nuevo tiempo?, perdón por la pregunta, pero he intentado imaginarme y desarrollar de una forma lógica esto, pero es realmente dificil... y supongo que el "tiempo--"" añade un nuevo segundo al cumplirse el ciclo, o me equivoco?

  • Si la condición en la pregunta 3.2 se cumple, entonces es necesario desplazar el "punto de partida" al milisegundo más actual posible; esto para poder seguir llevando rastro de cuándo pasa otro segundo. Aquí sí no te culpo de que no hayas entendido, el comentario no fue muy explicativo.
  • millis va para adelante, pero tu cronómetro va para atrás; así que supuse que la implementación más simple de hacer, es la de una variable que va decreciendo en el paso de cada segundo (o cualquier otro intervalo).