12 horas de retraso con ATmega16A

Estoy tratando de encender un LED durante 5 segundos cada 12 horas. Lo intenté:

#include <mega16.h>
#include <delay.h>

void main(void)
{
    DDRC.0 = 1;
    PORTC.0 = 0;

    while(1)
    {      
       unsigned long i;
       unsigned long j;
       PORTC.0=1;

       for(i=1; i<432; i++)
       {   
          delay_ms(100);  
       }

       PORTC.0 = 0;

       for(j=1; j<100; j++)
       {
           delay_ms(50);  
       }
    }
}

pero el LED estaba encendido durante aproximadamente 2 horas cada semana.

Necesita saber la frecuencia del chip, y el programa también debe ser consciente de ello. La F_CPUmacro es para ese propósito.
El primer paso es mirar la velocidad del reloj de su chip. Si la rutina de retraso asume que es de 8 MHz y la ejecuta a 1 MHz o incluso menos, sus retrasos se multiplicarán por la diferencia de ese factor.
Echa un vistazo a esto . Pero con un temporizador se pueden lograr retrasos más precisos.
¿Dónde está el #define F_CPU?
Tal como está escrito, su código encenderá el LED durante 500 milisegundos cada 43,2 segundos.
De hecho, encontré esta pregunta interesante y que invita a la reflexión, y no creo que merezca ser rechazada, aunque solo sea por haber obtenido una respuesta detallada de @BenceKaulics.

Respuestas (3)

Sentí la necesidad de dar una solución sin bloqueo al problema, especialmente porque aquí estamos hablando de doce horas de retraso.

La biblioteca util/delay.h y sus funciones _delay_ms() y _delay_us() son las funciones de retraso del software. Son convenientes en programas pequeños y para prototipos y experimentación rápidos. Son simples y la precisión podría ser suficiente en muchos casos, pero aquí tenemos una desventaja. Como son retrasos de software, bloquean la CPU. En realidad, usamos la CPU como contador/temporizador, por lo que no podemos hacer nada más mientras tanto.

Con los temporizadores de hardware, es posible contar en segundo plano y manejar eventos con interrupciones, por lo que la CPU será de uso libre mientras el temporizador se ejecuta de forma independiente.

La "desventaja" es que tenemos que pasar por un poco más de documentación y tenemos que escribir un poco más de código, pero a cambio obtendremos una mayor precisión y una CPU libre .


Configurar un temporizador con interrupciones

Voy a usar el Timer1 de Atmega16A que es un contador de 16 bits. La frecuencia del reloj es F = 8 METRO H z y quiero que el temporizador se desborde una vez cada segundo y contaré estos segundos hasta llegar a las 12 horas (43200 segundos).

Pasos:

  1. Determine la velocidad del reloj y el valor máximo del temporizador para obtener un desbordamiento cada segundo.
  2. Configure los registros correspondientes (modo de temporizador, selección de reloj, habilitar interrupción, valor de comparación (valor máximo))
  3. Escriba nuestra Rutina de Servicio de Interrupción (ISR) para contar los segundos y en este caso para controlar el LED.

Ejecución:

  1. Empezamos con: F S y s t mi metro C yo o C k = 8 METRO H z , si usamos esta frecuencia sin preescala, el temporizador tendrá un tic en cada:

    T t i metro mi r = 1 F S y s t mi metro C yo o C k = 1 8 METRO H z = 0.125 m s

    Desafortunadamente, es demasiado rápido, por lo que preescalaremos la frecuencia del reloj en 256.

    F t i metro mi r = F S y s t mi metro C yo o C k 256 = 31250 H z

    T t i metro mi r = 1 F t i metro mi r = 1 31.250 k H z = 32 m s

    Para un desbordamiento de 1 segundo t i C k s = 1 s 32 m s = 31250 se necesitan marcas (en este caso de 1 s , la frecuencia claramente da este valor).
    Con un temporizador de 16 bits, el valor máximo posible es: 2 dieciséis 1 = 65535 , por lo que nuestro valor encajará, no como con 8 METRO H z dónde t i C k s sería demasiado alto.

  2. Como hemos determinado los valores mencionados anteriormente, es hora de configurar el temporizador. Usaremos el modo CTC (Clear Timer on Compare Match) del temporizador, que permite establecer un valor máximo personalizado.

    En el modo CTC, el contador se pone a cero cuando el valor del contador (TCNT1) coincide con el OCR1A (WGM13:0 = 4).

    • OCR1A = t i C k s 1 = 31250 - 1 = 31249, ya que el contador parte de 0.
    • WGM13:0 = 4, por lo tanto, solo se debe configurar el bit WGM12 en el registro de control TCCR1B

    El siguiente paso es establecer el preescalador en 256 mediante los bits de selección de reloj en el registro TCCR1B . Nota: el conteo comenzará tan pronto como se seleccione la fuente de reloj.

    • CS12:0 = 4, por lo tanto, solo se debe configurar el bit CS12 .

    Próximo; habilitar la interrupción del Timer1 correspondiente en el registro TIMSK (Timer/Counter Interrupt Mask Register), que se activará al alcanzar el valor máximo.

    ingrese la descripción de la imagen aquí

    • Necesitamos el bit OCIE1A (Timer/Counter1, Output Compare A Match Interrupt Enable), que es cuando se escribe en 1, Timer/Counter1 Output Compare A Match Interrupt está habilitada. ( En modo normal, se debe usar el bit TOIE1 (Temporizador/Contador1, Habilitación de interrupción por desbordamiento) )
  3. El último paso es escribir el código del ISR y la inicialización del temporizador.

#include <avr/io.h>
#include <avr/interrupt.h>

#define LED     PC0

// global variable to count the seconds
volatile uint16_t sec_cnt = 0; 

// Timer1 initializtion
void init_timer1() 
{
    // Timer Mode 4: Clear Timer on Compare match (CTC)
    TCCR1B |= (1<<WGM12); 
    // Initialize Timer staring value
    TCNT1 = 0;
    // Set Compare value for 1s overflow
    OCR1A = 31249;
    // Enable Timer Compare A Match interrupt
    TIMSK |= (1<<OCIE1A);
    // Start Timer & Clock Select: Prescale I/O clock by 256
    TCCR1B |= (1<<CS12);
}

// Timer1 output compare match A interrupt rutine
ISR(TIMER1_COMPA_vect)
{   
    if(sec_cnt < 5)
    {
        PORTC |= (1<<LED);  // turn on LED in first 5 sec
    }
    else
    {
        PORTC &= ~(1<<LED);
    }

    sec_cnt++;

    if(sec_cnt == 43200)    // 12*60*60 = 43200
    {
        sec_cnt = 0;        // restart counting
    }
}

void main(void)
{
    // init Timer1
    init_timer1();
    // enable global interrupts
    sei();                  

    while(1)
    {
         // Let the ISR handle the LED ...
         // and do other stuff
    }
}

Esta es solo una forma de escribir el ISR, otra podría ser usando una bandera que indique un período de 12 horas, luego maneje el LED en la función principal cuando esta bandera esté configurada. Para el retraso de 5 segundos, _delay_ms(5000)se puede usar la bandera (ya que no es tan larga) o una bandera separada. Los vectores de interrupción (p. ej.: TIMER1_COMPA_vect) se enumeran en la hoja de datos.


Pruebas

Estaba alternando un pin dentro del ISR para medir el tiempo de 1 segundo de las interrupciones y encendí el LED durante 5 segundos cada 30 segundos. El resultado está en la siguiente imagen:

ingrese la descripción de la imagen aquí

Se pudo observar un pequeño error (~0.9%) en el valor de 1 segundo causado por la imprecisión del oscilador interno. Es normal, según la hoja de datos:

A 5 V, 25 °C y 1,0, 2,0, 4,0 ó 8,0 MHz de frecuencia de oscilador seleccionada, esta calibración da una frecuencia dentro de ± 3% de la frecuencia nominal.


¡Impresionante! Eres el verdadero maestro. Muchas gracias.

Primero, debe manejar la mascarada de la velocidad del reloj que se analiza en los comentarios. Cuando se asegure de que sus relojes estén bien, probablemente refactorizaría un poco el código:

int32_t second;
for (second = 0; second < 60LL*60*12; ++second) {
    if (second < 5) {
        PORTC.0 = 1;
    } else {
        PORTC.0 = 0;
    }
    delay_ms(1000);
}

Pero tenga en cuenta que este enfoque será un poco inexacto, porque su período total es más largo que 1 segundo. La ejecución del código lleva tiempo, aunque sea muy corto en comparación con la espera de 1000 ms.


Sí, yo también odio este ciclo de bloqueo de 12 horas.

el reloj es de 8 mhz. No tengo tiempo para probarlo, ¿estás seguro de que funcionará?
No me importa 30 minutos más rápido o más lento
"No tengo tiempo para probarlo" ?!?!?!?
@LordDecapitary Tampoco tengo tiempo para probarlo, así que tómalo o déjalo. Por cierto, el tamaño literal fijo intes lo suficientemente largo.
¡Quiero decir, cómo podría saber cuánto tiempo es delay_ms (1000) con 8 mhz!
@LordDecapitary Lea la maldita página web publicada en los comentarios.
Odio que esta sea una función de bloqueo de 12 horas, pero debería hacer lo que quiere el OP, a menos que el micro necesite hacer algo más en el tiempo de inactividad.
micro hace mucho en 5 segundos. Ese no es el problema @MattYoung
¿Por qué obtengo 50 segundos para delay_ms (500) en un reloj de 1 mhz?
@LordDecapitary Obtiene un retraso de 50 segundos porque aparentemente TODAVÍA no ha leído la documentación de delay_ms.
@LordDecapitary Quédate con la ingeniería mecánica. La ingeniería de melocotón aparentemente no es para ti.
unsigned int seconds,mins,hours;

for(hours=0;hours<12;hours++)
{
    for(mins=0;mins<60;mins++)
    {
       for(seconds=0;seconds<60;seconds++)
       {
          delay_ms(1000);
       }
    }
}

led = 1;
delay_ms(5000);
led = 0;