[RESUELTO]Arduino Mega se niega a hacer un cálculo de tiempo

Hola, tengo un Arduino Mega con un reloj RTC DS1307
El reloj funciona bien (entre otras cosas dibuja un reloj digital en un display TFT)

Tengo que hacer unos cálculos de tiempo con una variable declarada como "unsigned long HoraComienzo" que tiene que almacenar los segundos transcurridos desde las 00:00:00 hs hasta la hora en que se calcula:

RTC.getTime();
HoraComienzo = RTC.second + (RTC.minute * 60) + (RTC.hour * 3600);

Con ese valor luego el programa calcula unos intervalos para aumentar o disminuir la intensidad de un grupo de leds.
Pero no funciona, si en lugar de pedirle que haga ese cálculo le pongo el valor, por ejemplo de las 10 de la mañana (36000 segundos) de la siguiente forma funciona todo el resto de cálculos:

RTC.getTime;
HoraComienzo = 360000;

He probado colocando operaciones como "180000 + 180000" ó "360 * 1000" y también funcionan.

¿Que estoy haciendo mal?

Hola, no uso esa libreria pero estaba viendo que por ejemplo lo que tu has escrito RTC.minute creo que debiera ser RTC.minute(), etc.
Supongo que ademas habras hecho algunos Serial.print para cada unidad de tiempo para ver que es lo que sale antes de aplicar la formula, bueno

Saludos.

Añade el sufijo UL al 60 y al 3600

RTC.getTime();
HoraComienzo = RTC.second + (RTC.minute * 60UL) + (RTC.hour * 3600UL);

hypernovat:
Hola, no uso esa libreria pero estaba viendo que por ejemplo lo que tu has escrito RTC.minute creo que debiera ser RTC.minute(), etc.
Supongo que ademas habras hecho algunos Serial.print para cada unidad de tiempo para ver que es lo que sale antes de aplicar la formula, bueno

Saludos.

Pues no vá con los paréntesis (lo he probado, he probado TODO, jaja)

En relación a mostrar por puerto serie el resultado del contenido de la variable, pues como la información sale por un display he intentado, pero probablemente como son cifras grandes no los puedo ver. Veré de configurar la salida serie para ver el contenido de la variable.

IgnoranteAbsoluto:
Añade el sufijo UL al 60 y al 3600

RTC.getTime();

HoraComienzo = RTC.second + (RTC.minute * 60UL) + (RTC.hour * 3600UL);

No estoy en casa, en unas horas lo probaré, pero me podrías explicar el motivo?
O dónde leer el tema? (para aprender, si?)

Con las ayudas que te han dado y un poco de tu parte lo resuelves.
Te pones a probar (trastear como dicen Uds) con un código que haga solo eso, pones el monitor serie y comienzas a ver cosas parciales y debes ser correctas.

RTC.getTime();
HoraComienzo = RTC.second(); // lo imprimes con Serial print
Serial.print("Segundos =");
Serial.println(HoraComienzo);
Horacomienzo+= RTC.minute() * 60UL;
Serial.print("+ Minutos =");
Serial.println(HoraComienzo);
Horacomienzo+= RTC.hour() * 3600UL;
Serial.print("+ Horas =");
Serial.println(HoraComienzo);

Ahi veras si hay o no error.

Bienvenido de las vacaciones, jeje...
Esta tarde (son las 13:23 ahora) me pongo a ello.
En principio probaré con lo de agregar UL (unsigned long, puede ir en minúsculas también según lo que he leído por ahí googleando) y lo mismo se soluciona directamente. De lo contrario haré lo que me recomiendan con el puerto serie.

Si se soluciona "a la primera", me daré cuenta enseguida porque lo utilizo para dividir un período de tiempo en "trozos iguales" y variar la intensidad de un circuito de leds en cada segmento utilizando millis.

El programa, en 3 horas (de 10 a 13 hs), debe aumentar la intensidad luminosa de cero a 255 y lo de transformar la diferencia entre esas horas en segundos (y luego en 255 trocitos) es por prever un corte de corriente durante ese período, que reiniciará todo el programa y de esa forma llevará los leds de cero a 255 en trocitos más pequeños, pero con el mismo efecto. Perdón por el lío si es que lo he creado, jaja.

Un saludo.

De nuevo, lo que me gustaría que aprendas Alberto y esto si es importante es a tener herramientas para buscar la solución por tu cuenta.
La mejor herramienta siempre es el Monitor Serie y con su ayuda verás los resultados parciales.
Asi comprendes que esta pasando.. y cuando algo no funciona, piensa en si las cosas son como las ves o como las interpreta el compilador y luego ejecuta el Arduino, por supuesto por nuestra falla al indicarlo correctamente.

Todo reside en eso, comprender donde se esta parado y cuando visualizas el error (que nos pasa a todos), lo resuelves sea investigando o volviendo a leer algo que creías de un modo y es de otro (casi siempre este es el problema).

Pues si, la diferencia entre el cálculo agregando UL es muuuuuuuy diferente que sin UL, pongo una captura de pantalla con el IDE y a la derecha el monitor serie:

Como se puede ver, son las 10 y pico de la mañana, con el agregado de UL salen las cifras correctas, sin el UL las cifras son enormes, con lo cual al dividir esas cifras de 430 millones de segundos, el programa no podía funcionar porque los "delay con millis" eran intervalos tan grandes que el programa ni se movía esperando que pasaran esos tiempos tan largos, jaja

¿Algún sesudo compañero me podría explicar el motivo de esas diferencias?

Lo dejamos a IgnoranteAbsoluto que es sencillamente espectacular en sus explicaciones.

Siento no haber explicado antes el porqué. Pero es que no he tenido tiempo para hacerlo hasta ahora. Supuse que sería preferible resolver el problema cuanto antes para no quedar atascado y ganar tiempo, y preferí dar la solución sin explicaciones, dejando las explicaciones para ahora.

Hay que saber que el compilador de C++ asume ciertas cosas cuando no se le indica nada explícitamente y hace muchas conversiones de tipo sin que se lo digamos y siguiendo ciertas reglas.

Una de las cosas que asume "por su cuenta" es que si le asignamos un valor literal (léase un número normal y corriente) a una variable de tipo entero largo con signo (long) o sin signo (unsignde long) él asume que ese número es un entero largo. Pero si lo que hacemos es operar con un número con un tipo de datos inferior a un long (como puede ser int, byte, char, etc) él interpreta que ese valor numérico es de tipo int y opera con los demás "tipos inferiores" como si también fueran de tipo int, haciendo una conversión automática a partir de ese "tipo inferior", dando como resultado de la operación un valor de tipo int. A no ser que el valor literal no quepa en un int, ya que en tal caso asume que ese literal es de un tipo "superior" para que no se desborde, convirtiendo el resto de operadores a ese tipo antes de operar con ellos. Ese "tipo superior" suele ser un long.

Así que cuando asignas diréctamente a HoraComienzo el valor 36000, o el valor 10 * 3600, le estás asignado un "literal" sin especificar el tipo de dato que es, pero el "entiende" que le quieres asignar un unsigned long, ya que como sólo se está "trabajando" con una variable de ese tipo, no tiene sentido que el literal sea de otro tipo.

Pero cuando a HoraComienzo le asignas el valor del resultado de una operación entre, por ejemplo, una variable de tipo byte y un literal (número) al que no le has especificado un tipo, el compilador asume que ese literal es de tipo int, "promociona" la variable de tipo byte a tipo int, realiza la operación y obtiene como resultado un tipo int. Este resultado es el que finalmente asigna a la variable de tipo long (o unsigned long como es el caso que nos trae entre manos.

Vale, tal vez llegado a este punto todo parezca un mal trabalenguas. Mejor verlo con el ejemplo de pasar 10 horas a segundos. Tenemos una variable de tipo byte que nos da para guardar un valor entre 0 y 255, con lo que nos da más que de sobra para guardar la hora del día (sólo la hora). Y para saber cuantos segundos son lo multiplicamos por el literal 3600 (sin más). Tal como he comentado, el compilador considerará ambos como de tipo int y el resultado será de ese mismo tipo. Bien, en principio el resultado es 36000...

¡Un momento! ¡Alto! Aquí hay algo que no me cuadra. ¿Un int con el valor 36000? Pero si estamos tratando con un Arduino de 16 bits, por lo que una variable de tipo int se de 16 bits por lo que los valores que puede guardar han de estar entre -32768 y 32767 (sí, lo he buscado en Google), con lo que "no cabe" el valor 36000 ¿Qué pasa entonces? ¿Explota? No, simplemente "se desborda" y, en este caso, pasa a ser un número negativo, el -29536. ¿Porqué? Mejor dejar la explicación para otro día, o buscar en Google; pero es así. Y si no me creen lo pueden "ver" con el siguiente programa.

void setup() {
    Serial.begin(9600);
    Serial.print(F("32766 + 0 = ")); Serial.println(32766 + 0);
    Serial.print(F("32766 + 1 = ")); Serial.println(32766 + 1);
    Serial.print(F("32766 + 2 = ")); Serial.println(32766 + 2);
    Serial.print(F("32766 + 3 = ")); Serial.println(32766 + 3);
    Serial.print(F("10 * 3600 = ")); Serial.println(10 * 3600);
    Serial.print(F("20 * 3600 = ")); Serial.println(20 * 3600);
    Serial.print(F("    36000 = ")); Serial.println(36000);
}

void loop() {
}

Bueno, si ya tenemos claro que el resultado se nos puede desbordar y dar un valor negativo (e incluso volver a ser positivo como en el caso de 24 * 3600) ahora solo queda el paso de asignar ese valor de tipo int a nuestra variable de tipo unsigned long. Y aquí viene algo más desconcertante aún. Se está convirtiendo un valor negativo de un entero con signo a un entero largo sin signo. Para esto las reglas del "complemento a 2" especifica que si se pasa de una variable con signo a una variable de mayor tamaño, se "estira" el bit de signo para que siga teniendo el mismo valor, en "complemento a 2", en la nueva variable de mayor tamaño, esto también es mejor buscarlo en Google, pero para mostrarlo con un ejemplo sencillo basta con decir que un char que valga 126 en binario sería 01111110 y si lo pasamos a un int su valor binario sería 0000000001111110, que seria 126 en base 10. Mientras que si el char vale -127 en binario sería 10000001 y al pasarlo a un int su valor binario es 1111111110000001, que sería -127 en base 10. Y ahora la otra sorpresa: 1111111110000001 es -127 si se trata de un int, pero si se trata de un unsigned int su valor es 65409. Recordemos que un unsigned int puede tener un valor entre 0 y 65535 (este valor sí que me lo sé sin mirar Google).

Pues básicamente es eso lo que está pasando para que salga un número tan disparatado. Añadir al ejemplo del principio la línea:

    Serial.print(F("(unsigned long)(int)36000 = ")); Serial.println((unsigned long)(int)36000);

Se está forzando que tome el valor como un entero con signo, lo convierta a un entero largo sin signo y lo muestre. Saldrá el número 4294937760. ¿Se les parece a algún número visto antes?

Entiendo que todo esto resulte un galimatías, pero no es fácil de explicar ni de entender esto de los desbordamientos, bit de signo, conversiones de tipos y demás cosas. Pero ya se puede tener una idea de por dónde va la cosa.

Para evitar todo esto basta con que se fuerce a operar con tipos unsigned long y esto se logra con que uno o varios de los operandos sean de ese tipo. Esto hace que el compilador convierta los "tipos pequeños" a ese "tipo mayor" antes de operar con ellos y no se desborde. Y una forma fácil es aprovechar que los estamos multiplicando por un valor literal y que ese valor sea del "tipo grande" y eso se lo indicamos explícitamente con el sufijo UL si queremos que sea unsigned long o con el sufijo L si lo que queremos que sea es de tipo long. En el ejemplo anterior nos vale con especificar que uno de los literales es de tipo long para que el resultado de las operaciones sean de ese tipo y no se desborde.

void setup() {
    Serial.begin(9600);
    Serial.print(F(" 32766 + 0L = ")); Serial.println(32766 + 0L);
    Serial.print(F(" 32766 + 1L = ")); Serial.println(32766 + 1L);
    Serial.print(F(" 32766 + 2L = ")); Serial.println(32766 + 2L);
    Serial.print(F(" 32766 + 3L = ")); Serial.println(32766 + 3L);
    Serial.print(F(" 10L * 3600 = ")); Serial.println(10L * 3600);
    Serial.print(F(" 20L * 3600 = ")); Serial.println(20L * 3600);
    Serial.print(F("     36000L = ")); Serial.println(36000L);
}

void loop() {
}

Así que mi consejo es que pongan el sufijo siempre que proceda que esté, por ejemplo al hace la asignación:

HoraComienzo = 360000UL;

Sé que la explicación muy confusa y aún habría que explicar muchos detalles más, pero con esto espero que tengan ya una idea de por dónde viene este tipo de problemas y les ayude a solucionarlos.

P.D.: surbyte, creo que el problema es demasiado complicado de descubrir por alguien que no tenga ciertos conocimientos de las interioridades del C++, así que por esta vez veo justificado que albertoG1 consultase sin experimentar mucho :wink:

No lo discuto pero... no estas de acuerdo @IgnoranteAbsoluto que usando el recurso de Monitor Serie hubiera encontrado que algo andaba mal?

Luego tal vez hubiera dado con el desbordamiento y con la asunción del compilador de trabajar con enteros int.

Es el camino para resolver sus propios fallos.

Estoy de acuerdo contigo, surbyte. Pero, por lo que comenta albertoG1, en su montaje no tenía inicialmente configurada la salida serie para poder depurar. Eso sí que es un desacierto. Yo, siempre que sea posible, reservo los pines del puerto serie para poder mostrar datos y mensajes que me permitan depurar el programa en cualquier momento. Una vez finalizado y verificado que funciona, comento o quito todo mensaje de depuración y verifico que "la cosa" continúa funcionando correctamente. No sería la primera vez que al quitar algo que he puesto para depurar deje de funcionar "el invento" por no haberme dado cuenta de que en medio de las líneas de depuración tenia alguna operación.

Ante todo, como siempre, gracias por los comentarios "desaznantes"

En relación al tema del puerto serie, no por justificarme, sino por explicarme, hasta ahora siempre lo utilizo, pero es la primera vez que programo con una TFT, y llegué hasta mostrar el estado de todos los mecanismos en el display, con lo cual me dije ¿para qué quiero que escriba contínuamente en el puerto serie?... Error!!!

Pero bueno, gracias por el extenso comentario de IgnoranteAbsoluto, no es que haya pillado todo, pero entiendo que hay que decirle al procesador que trate los datos como unsigned long ante el riesgo de desbordamiento. Espero algún día entender con qué operación obtiene esas cifras de trescientos y pico millones de segundos, jeje.

Pues he logrado asignarles valores correctos a las variables de tiempo, pero al pasar esa variable al if de los "delays con millis" no conserva el valor calculado.

El objetivo es el de dividir un intervalo de tiempo en 255 fracciones iguales, de manera de llevar un circuito de leds de cero a 255 en un tiempo variable.
A la 1 del mediodía (13 hs. que son 46800 segundos desde las cero horas) tienen que estar los leds a máxima potencia.
De manera habitual comienzan a encenderse a las 10 de la mañana, o sea que en 3 horas tienen que pasar de cero a máximo, en principio sencillo, dividir las 3 horas en 255 partes, llevarlo a milisegundos para el condicional de los millis() y todo perfecto.
Pero el problema es que si se reinicia el Arduino (corte de luz o lo que sea) durante esas 3 horas el efecto desde cero a máximo tendrá que ser realizado en menos tiempo (de lo contrario a las 13 horas los leds pasarán de un valor no máximo a máximo "de golpe", o alternativamente alargar el proceso más allá de las 13 horas (noooo!).
Por eso en caso de reiniciar el Arduino más tarde de las 10 de la mañana, calcular el tiempo que queda hasta las 13 horas y dividirlo en 255 intervalos para utilizarlos con los millis().

Repito, a la hora de pasar el valor del intervalo a los millis() no lo mantiene.
Pongo un sketch de la función que se repite de manera contínua desde el menú que la invoca:

void EfectoAmanecer()
{     
    const long Intervalo;     //Le he puesto "const" en un intento que mantenga el valor (Uffff!!!)

    if (contador)                 //Entra una sola vez
    { 
      MillisAnteriores = millis(); 
 //Calcula el tiempo en milisegundos desde el inicio hasta las 13 hs y lo divide por la intensidad máxima (255) de los leds:
      const long Intervalo = (46800000UL-((RTC.second + (RTC.minute * 60UL) + (RTC.hour * 3600UL))*1000UL)) / IntensidadLedsMax;  
      contador = false;                         //Entra una sola vez
    }
    if (Hora >= 10 && Hora < 13)  //A partir de las 10 de la mañana hasta las 12:59:59
    {  
      if ((MillisActuales - MillisAnteriores) >= Intervalo);//ESTE VALOR NO LO MANTIENE, APARECE CERO!!
       { MillisAnteriores = MillisActuales;
         if (EstadoLeds <= IntensidadLedsMax)  //Cuando llegue a la intensidad máxima no vuelve a entrar
          {
            EstadoLeds++;
            analogWrite(CircuitoLeds, EstadoLeds);
          }
     }
}

Los datos los muestro por puerto serie, el cálculo del intervalo ((46800000UL-((RTC.second + (RTC.minute * 60UL) + (RTC.hour * 3600UL))*1000UL)) / IntensidadLedsMax; ) que lo pongo dentro de un condicional que se cumple una sola vez para que mantenga ese valor hasta el final, es correcto, pero a la hora de colocar la variable "const long Intervalo" en el condicional de los millis dá CERO, y claro, los leds pasan de cero a máximo en un par de segundos.

Lo mismo es alguna burrada de las mías, y me estoy equivocando en alguna cosa básica, jaja

Creo, albertoG1, que lo mejor sería que dieras este por solucionado esta consulta e iniciaras una nueva exponiendo tu "nuevo" problema, que aunque esté relacionado no es el mismo. Y ya de paso, si es posible, pongas el código de lo que tienes hecho o, al menos, las partes del código que afectan a "la consulta", incluyendo la definición de variables. Aveces el problema está en la definición de variables y la gente no acostumbra a ponerlas.

Ok, lo mismo comienzo un nuevo mensaje, aunque creo que surbyte no estaría muy de acuerdo.
Pero algo tendré que hacer, no entiendo porqué al pasar el valor de la variable al condicional de los millis() cambia el contenido, en el monitor serie aparece "0" pero sospecho que ese no es el valor que contiene sino que será un valor muy alto (porque el programa no entra en los condicionales de los delay con millis ni "a palos" y por eso no entran en el condicional.

Me acabo de dar cuenta de que has definido dos veces la variable con el nombre Intervalo. Una vez al pricipio de la función y la segunda vez dentro del if (contador), donde calculas el valor que quieres. Eso significa que tienes dos variables distintas con el mismo nombre y diferentes valores. La primera no la inicializas y luego la usas para verificar si ha transcurrido el tiempo (la que parece valer siempre cero). Y otra que se crea dentro del if (contador) y te "tapa" la primera. Es esa segunda variable a la que le asignas el valor deseado y después no se usa porque "desaparece" al terminar el if (contador), que es su ámbito.

Primero, antes que nada, quita el const de la definición de la primera variable. El const es para indicar que no se trata de una "verdadera variable", sino de una "constante" a la que no se quiere que se cambie nunca el valor asignado al crearse, y este no es el caso ya que queremos podes cambiar su valor después de su creación.

Después quita el const long que le has puesto a Intervalo al asignarle el valor que quieres, dentro del if (contador). Esto es lo que te está creando esa segunda variable que nunca llegas a usar. Porque "desaparece" al terminar el bloque del if (contador), en el que se había definido.

Siento no habreme dado cuenta antes. Aunque insisto en que es una consulta diferente a la que ha iniciado este post, por muy relacinadas que estén. :wink:

Hola, pues era eso: lo que comentas en la última explicación, pequeña mezcolanza de variables estaba haciendo.
Además otro motivo que no pasaba el valor de la variable y aparecía como cero porque eran dos funciones, una que llama a otra y le pasa el valor de la variable con el mismo nombre en ambas funciones, o sea que era una variable local para cada función.
La declaré como pública y ahora funciona todo como la seda, UAUUUUUU!!!! :grinning: :grinning:
Incluso el efecto contrario, a la noche cuando los leds tienen que hacer el efecto contrario, es decir desde las 19 a las 22 hs disminuir de 255 a cero.

Pues para el que le pueda servir, las tres cosas que solucionaron mi problema:

1.- utilizar el puerto serie para visualizar valores que hacen que no puncione un programa como pretendemos.

2.- decirle al procesador cuando debe tratar cifras como long agregando al final UL, L o LL de manera de no desbordar el contenido de la variable o que el resultado sea un número enorme (eso todavía no lo pillo).

3.- tener en cuenta el ámbito de las variables, o utilizar nombres diferentes para no confundirse.

Pondré el título como resuelto.
Gracias a surbyte y a IgnoranteAbsoluto (he copiado la explicación extensa que has puesto en las dos respuestas más arriba para no perderla y releerlas)