Yo mismo escribí un programa para implementar la función millis() y delay() sin la biblioteca arduino. Incluí una variable de conteo que cuenta cada segundo y envía su valor cada segundo a través del puerto serie. Lo que encontré es que su valor se aleja del valor real en casi 2 segundos cada 3 minutos. ¿Hay algo mal con mi código? ¿O es Serial.print() el culpable que puede retrasar esa rutina? ¿Cuánto tiempo tarda en ejecutarse Serial.print()?
Aquí está el código:
Editar: edité el código de esta manera, pero el conteo en el arduino aún se retrasa alrededor de 4 segundos después de 4 minutos. Tiene un retraso de 13 segundos después de 10 minutos, es decir, cuenta solo 587 segundos después de los 600 segundos reales.
Edición 2: Aquí está mi código actualizado. Todavía hay retraso en el tiempo. Tengo un retraso de alrededor de 6 segundos en 5 minutos.
#include "Arduino.h"
#include <avr/io.h>
#include <avr/interrupt.h>
void toggle_led(void);
unsigned long volatile millis_count = 0;
volatile char state = 1;
unsigned long volatile current_count = 0;
unsigned int volatile count = 0;
ISR(TIMER0_COMPA_vect) { //Timer interrupt ISR
millis_count++;
if (millis_count - current_count == 1000) {
current_count = millis_count;
toggle_led();
Serial.println(count++);
}
}
int main(void) {
init();
TCCR0B = 0b11; //Timer settings for interrupt at every millisecond
OCR0A = 249;
TIMSK0 |= 0b10;
sei();
Serial.begin(9600);
DDRB |= 1<<5;
for (;;) {
}
}
void toggle_led(void) {
PORTB ^= (1<<5);
}
Su demora será la duración de la función newdelay() más el tiempo que lleva enviar los datos en serie.
Tienes:
Cada uno de esos pasos lleva tiempo.
Para obtener un tiempo exacto de 1000 ms, puede activar el envío en serie desde una interrupción de 1 segundo, o puede examinar el conteo de milisegundos dentro de su bucle y enviar los datos en serie cuando los milisegundos pasan por 1000:
unsigned long lastmil;
lastmil == newmillis();
for(;;)
{
if(((newmillis()%1000) == 0) && (lastmil != newmillis()))
{
lastmil = newmillis();
Serial.println(count++);
toggle_led();
}
}
(Otra forma de evitar que se reproduzca varias veces durante un solo milisegundo sería agregar un breve retraso dentro del if (...) para garantizar que la rutina tarde al menos 1 ms)
La función Serial.println() tomará una cantidad variable de tiempo dependiendo de:
A 9600 baudios estás enviando 9600 símbolos por segundo. Con formato "8N1" que son 10 símbolos por byte. Entonces, para una cadena de 3 dígitos, más el retorno de carro y el avance de línea, serán 10 símbolos × 5 caracteres, que son 50 símbolos.
9600 baudios son 0,0001041670,000104167 s por símbolo (o 104 µS por símbolo), por lo que 50 símbolos serán 0,00520835 s (o 5,20835 ms).
Ese es el tiempo de transmisión real. Luego, también debe agregarle el tiempo necesario para formatear los datos y llamar a las rutinas en serie. Todos estos toman una cantidad de tiempo no especificada. Para averiguarlo, necesitará conocer el código ensamblador en el que se compila la rutina, luego encontrar el número de ciclos de reloj que toma cada instrucción y sumarlos.
Majenko ha explicado en detalle por qué estás viendo la deriva del tiempo. La pregunta ahora debería ser cómo hacerlo bien. Ya se ha encontrado con una de las varias razones por las que las esperas ocupadas son una mala manera de medir el tiempo a largo plazo.
No conozco el hardware AVR en el que se basa el arduino, pero estoy seguro de que tiene temporizadores. Debe haber una manera de configurar una interrupción periódica completamente en hardware utilizando uno de estos temporizadores. Una interrupción de 1 kHz (período de 1 ms) suele ser una buena compensación entre la resolución de tiempo, no usar demasiado la CPU y un valor lo suficientemente pequeño que el hardware puede hacer de forma nativa. El hardware llamará regularmente a esta rutina de interrupción 1000 veces por segundo, independientemente de cualquier otra cosa que esté haciendo su rutina principal. Puede contar fácilmente múltiples tics de 1 ms para hacer tics con períodos más largos, como 1 segundo. Dado que 1 segundo es bastante lento, la rutina de interrupción puede establecer una bandera cada segundo que la rutina de primer plano verifica en su ciclo de eventos principal y luego se reinicia.
Dependiendo de qué más esté sucediendo, es posible que no use una rutina de interrupción, sino que el hardware solo establezca una bandera cada tic del reloj. En ese caso, probablemente desee hacer que el reloj marque más tiempo, como cada 10 ms. El código de primer plano maneja eso en el bucle de eventos principal y lo divide en el tic de 1 que realmente desea.
Continuando con la respuesta de Olin... así es como se implementa la función Arduino millis()... Hay una variable de contador global volátil de 32 bits llamada timer0_millis que es mantenida/actualizada por TIMER0_OVF_vect ISR, y la función millis en sí misma detiene las interrupciones brevemente para leer ese valor en una variable local y devolverlo. Puede revisar la implementación en el archivo "wiring.c" en la biblioteca central de Arduino para ver cómo lo hicieron realmente.
Además de las respuestas mencionadas, existe otra posibilidad de deriva temporal. Los cristales utilizados para cronometrar el arduino también tienen un rango de tolerancia bastante amplio, por lo que el cristal puede inducir una deriva. Si planea usar esto durante un largo período de tiempo y debe ser preciso en sus mediciones de tiempo, buscaría un chip de reloj externo, por ejemplo, el DS1307, que es fácil de conectar al arduino. Estos chips generalmente usan un cristal de 32khz de "alta precisión" para mantener el tiempo (el mismo cristal que se usa en los relojes de pared comunes).
bdutta74
bdutta74
Majenko
0xakhil