12 Stunden Verzögerung mit ATmega16A

Ich versuche, alle 12 Stunden eine LED für 5 Sekunden einzuschalten. Ich habe es versucht:

#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);  
       }
    }
}

aber die LED war jede Woche etwa 2 Stunden lang an.

Sie müssen die Frequenz des Chips kennen, und das Programm muss sich dessen auch bewusst sein. Das F_CPUMakro ist für diesen Zweck.
Schritt eins betrachtet die Taktrate Ihres Chips. Wenn die Verzögerungsroutine davon ausgeht, dass es sich um 8 MHz handelt, und Sie sie mit 1 MHz oder sogar niedriger ausführen, multiplizieren sich Ihre Verzögerungen mit diesem Faktorunterschied
Schauen Sie sich das an . Aber mit einem Timer könnten genauere Verzögerungen erreicht werden.
Wo ist das #define F_CPU?
Wie geschrieben, schaltet Ihr Code die LED alle 43,2 Sekunden für 500 Millisekunden ein.
Ich fand dies tatsächlich eine interessante und zum Nachdenken anregende Frage, und ich denke nicht, dass sie es verdient, herabgestuft zu werden, und sei es nur, weil sie eine detaillierte Antwort von @BenceKaulics erhalten hat

Antworten (3)

Ich hatte das Bedürfnis, eine nicht blockierende Lösung für das Problem zu finden, insbesondere weil wir hier über zwölf Stunden Verzögerung sprechen.

Die Bibliothek util/delay.h und ihre Funktionen _delay_ms() und _delay_us() sind die Software-Verzögerungsfunktionen. Sie sind praktisch in kleinen Programmen und für schnelles Prototyping und Experimentieren. Sie sind einfach und die Genauigkeit könnte in vielen Fällen ausreichen, aber wir haben hier einen Nachteil. Da es sich um Softwareverzögerungen handelt, blockieren sie die CPU. Tatsächlich verwenden wir die CPU als Zähler/Timer, sodass wir in der Zwischenzeit nichts anderes tun können.

Mit Hardware-Timern ist es möglich, im Hintergrund zu zählen und Ereignisse mit Interrupts zu verarbeiten, sodass die CPU frei verwendet werden kann, während der Timer unabhängig läuft.

Der "Nachteil" ist, dass wir etwas mehr Dokumentation durchgehen und etwas mehr Code schreiben müssen, dafür aber eine höhere Genauigkeit und eine freie CPU bekommen .


Richten Sie einen Timer mit Interrupts ein

Ich werde den Timer1 von Atmega16A verwenden , der ein 16-Bit-Zähler ist. Die Taktfrequenz ist F = 8 M H z und ich möchte, dass der Timer alle Sekunden einmal überläuft, und ich werde diese Sekunden zählen, bis 12 Stunden (43200 Sekunden) erreicht sind.

Schritte:

  1. Bestimmen Sie die Taktrate und den maximalen Wert des Timers, um alle Sekunden einen Überlauf zu erhalten.
  2. Stellen Sie die entsprechenden Register ein (Timer-Modus, Uhrenauswahl, Interrupt aktivieren, Wert vergleichen (Maximalwert))
  3. Schreiben Sie unsere Interrupt Service Routine (ISR), um die Sekunden zu zählen und in diesem Fall die LED zu steuern.

Ausführung:

  1. Wir beginnen mit: F S j S T e M C l Ö C k = 8 M H z , wenn wir diese Frequenz ohne Vorskalierung verwenden, hat der Timer in jedem ein Häkchen:

    T T ich M e R = 1 F S j S T e M C l Ö C k = 1 8 M H z = 0,125 μ S

    Leider ist es zu schnell, also werden wir die Taktfrequenz um 256 vorskalieren.

    F T ich M e R = F S j S T e M C l Ö C k 256 = 31250 H z

    T T ich M e R = 1 F T ich M e R = 1 31.250 k H z = 32 μ S

    Für einen 1-Sekunden-Überlauf T ich C k S = 1 S 32 μ S = 31250 Ticks benötigt werden, (In diesem 1 s- Fall gibt die Frequenz diesen Wert eindeutig an.)
    Bei einem 16-Bit-Timer ist der maximal mögliche Wert: 2 16 1 = 65535 , also passt unser Wert, nicht wie mit 8 M H z Wo T ich C k S wäre zu hoch.

  2. Nachdem wir die oben genannten Werte ermittelt haben, ist es an der Zeit, den Timer zu konfigurieren. Wir werden den CTC-Modus (Clear Timer on Compare match) des Timers verwenden, der es ermöglicht, einen benutzerdefinierten Maximalwert festzulegen.

    Im CTC-Modus wird der Zähler auf Null zurückgesetzt, wenn der Zählerwert (TCNT1) mit dem OCR1A (WGM13:0 = 4) übereinstimmt.

    • OCR1A = T ich C k S 1 = 31250 - 1 = 31249, da der Zähler bei 0 beginnt.
    • WGM13:0 = 4, daher muss nur das WGM12- Bit im TCCR1B- Steuerregister gesetzt werden

    Der nächste Schritt besteht darin, den Prescaler durch die Clock-Select-Bits im TCCR1B- Register auf 256 zu setzen. Hinweis: Die Zählung beginnt, sobald die Clock-Quelle ausgewählt ist.

    • CS12:0 = 4, es muss also nur das CS12- Bit gesetzt werden.

    Nächste; Aktivieren Sie den entsprechenden Timer1-Interrupt im TIMSK- Register (Timer/Counter Interrupt Mask Register), der bei Erreichen des Maximalwerts ausgelöst wird.

    Geben Sie hier die Bildbeschreibung ein

    • Wir benötigen das Bit OCIE1A (Timer/Counter1, Output Compare A Match Interrupt Enable), das heißt, wenn es auf 1 geschrieben wird, wird der Timer/Counter1 Output Compare A Match Interrupt aktiviert. ( Im normalen Modus sollte das TOIE1-Bit (Timer/Counter1, Overflow Interrupt Enable) verwendet werden. )
  3. Der letzte Schritt besteht darin, den Code der ISR und die Timer-Initialisierung zu schreiben.

#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
    }
}

Dies ist nur eine Möglichkeit, die ISR zu schreiben, eine andere könnte ein Flag verwenden, das einen Zeitraum von 12 Stunden angibt, und dann die LED in der Hauptfunktion behandeln, wenn dieses Flag gesetzt ist. Für die 5-Sekunden-Verzögerung könnte entweder das _delay_ms(5000)(da es nicht so lang ist) oder ein separates Flag verwendet werden. Die Interrupt-Vektoren (zB: TIMER1_COMPA_vect) sind im Datenblatt aufgeführt.


Testen

Ich habe einen Pin innerhalb des ISR umgeschaltet, um das 1-Sekunden-Timing der Interrupts zu messen, und ich habe die LED alle 30 Sekunden für 5 Sekunden eingeschaltet. Das Ergebnis ist im Bild unten:

Geben Sie hier die Bildbeschreibung ein

Beim 1-Sekunden-Wert konnte ein kleiner Fehler (~0,9 %) beobachtet werden, der durch die Ungenauigkeit des internen Oszillators verursacht wird. Laut Datenblatt ist es normal:

Bei 5 V, 25 °C und einer ausgewählten Oszillatorfrequenz von 1,0, 2,0, 4,0 oder 8,0 MHz ergibt diese Kalibrierung eine Frequenz innerhalb von ± 3 % der Nennfrequenz.


Großartig! Du bist der wahre Lehrer. Vielen Dank

Zuerst müssen Sie die Taktgeschwindigkeitsmaskerade behandeln, die in den Kommentaren besprochen wird. Wenn Sie sicherstellen, dass Ihre Uhren richtig sind, würde ich den Code wahrscheinlich ein wenig umgestalten:

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

Beachten Sie jedoch, dass dieser Ansatz etwas ungenau ist, da Ihre Gesamtperiode länger als 1 Sekunde ist. Die Ausführung des Codes braucht Zeit, sei es sehr kurz im Vergleich zu den 1000 ms Wartezeit.


Ja, ich hasse auch diese 12-Stunden-Sperrschleife.

Uhr ist 8 MHz. Ich habe keine Zeit, es zu testen. Bist du sicher, dass es funktioniert?
30min schneller oder langsamer ist mir egal
"Ich habe keine Zeit es zu testen" ?!?!?!?
@LordDecapitary Ich habe auch keine Zeit, es zu testen, also nimm es oder lass es. Übrigens feste intLitergröße, um lang genug zu sein.
Ich meine, wie kann ich wissen, wie viel Zeit delay_ms (1000) mit 8 MHz ist !!
@LordDecapitary Lies die verdammte Webseite , die in den Kommentaren gepostet wurde.
Ich hasse es, dass dies eine 12-Stunden-Blockierfunktion ist, sollte aber tun, was das OP will, es sei denn, das Mikro muss in der Leerlaufzeit etwas anderes tun.
Mikro macht viel in 5 Sek., das ist nicht das Problem @MattYoung
Warum bekomme ich 50 Sekunden für delay_ms (500) in 1 MHz Takt?
@LordDecapitary Sie erhalten eine Verzögerung von 50 Sekunden, weil Sie anscheinend die delay_ms-Dokumentation NOCH nicht gelesen haben.
@LordDecapitary Bleiben Sie beim Maschinenbau. Peach Engineering ist anscheinend nichts für Sie.
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;