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.
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 .
Voy a usar el Timer1 de Atmega16A que es un contador de 16 bits. La frecuencia del reloj es y quiero que el temporizador se desborde una vez cada segundo y contaré estos segundos hasta llegar a las 12 horas (43200 segundos).
Pasos:
Ejecución:
Empezamos con:
, si usamos esta frecuencia sin preescala, el temporizador tendrá un tic en cada:
Desafortunadamente, es demasiado rápido, por lo que preescalaremos la frecuencia del reloj en 256.
Para un desbordamiento de 1 segundo
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:
, por lo que nuestro valor encajará, no como con
dónde
sería demasiado alto.
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).
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.
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.
#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.
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:
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.
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.
int
es lo suficientemente largo.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;
Dzarda
F_CPU
macro es para ese propósito.Asmyldof
Bence Kaulics
mate joven
#define F_CPU
?nick johnson
Al Longley