C ATmega8: Taktung falsch

svenna80

Mitglied
31. Juli 2009
39
0
6
Sprachen
Schönen Guten Abend,

mich beschäftigt ein Problem, was ich nicht alleine lösen kann. Ich befasse mich gerade mit den Timern auf dem ATmega8.
Folgende Soft- und Hardware benutze ich:

AVR-Studio Version 4.16.628
PonyProg Version 2.07c Beta
Atmel Evaluations-Board Version 2.0.1 mit einem ATmega8 16PU. Mit einem externen Quarz (4000MHz) getaktet. Quarz ist an PIN XTAL1 und XTAL2 angeschlossen. Die Fuses sind entsprechend gesetzt.

Ich habe ein Programm geschrieben, um zu gucken,ob die Hardware richtig funktioniert. Mein Programm funktioniert auch. Es wird mit "delay_ms" eine LED zum Blinken gebracht. Die Lampe blinkt in der richtigen Taktung. Hier ist es:

Code:
#define F_CPU 4000000UL
#include <avr/io.h>
#include <util/delay.h>


int main (void) 



{


DDRD |= (1<<DDD5);                      //setzt PD5 auf Ausgang. Hier ist LED2 angeschlossen.

while(1)
    {


PORTD |= (1<<PD5);       //schaltet Pin PD5 für 4sek an                 
_delay_ms(4000);

PORTD &=~ (1<<PD5);   //schaltet Pin PD5 wieder für 2sek aus
_delay_ms(2000);
}

    
return 0;                 
}

Wenn das Programm funktioniert, schließe ich mal aus, dass die Fehlerursache des gleich folgenden Problems nicht an der Harware liegt?
Ich hab ein zweites Programm geschrieben/abgeguckt, wo ich den Timer1 ausprobiere im CTC-Modus. Das Programm funktioniert aber leider nicht richtig! Auf dem Evaluations-Board sind zwei LEDs. Die eine mach ich zu Testzwecken einfach nur an. Nachdem die erste LED an ist, soll die zweite LED dann nach einer bestimmten Zeit auch leuchten. Die zweite LED geht dann auch irgendwann an, aber die Zeit passt gar nicht (es dauert vieeel zu lang). Und ich frage mich, woran das wohl liegen mag?:confused:
Hier das zweite Programm:
Code:
//Vortest Uhr

#define F_CPU 4000000UL  
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>



int counter; //Zaehlvariable
int millisec;  
int sec;
int min;
int std;
volatile uint8_t test = 0;








ISR(TIMER1_COMPA_vect)  //Die Interruptroutine. Kein ";" dahinter sonst Fehlermeldung beim Compilieren! 
                       // TIMER1_COMPA_vect ist der Interruptvektor.

{

test++;                 //zaehlt Variable um 1 hoch.
if(test==1000000)          //nach 2sek sollte die zweite LED anfangen zu leuchten;macht sie aber nicht;es braucht viel länger als 2sek!
{          
PORTD |= (1<<PD6);
}

}




int main (void) 

{


  // Timer 1 konfigurieren, Timer 1 kann CTC
  TCCR1A = (1<<WGM12); // CTC Modus mit OCR1A
  TCCR1B = (1<<CS11); // Prescaler 8    
  // ((4000000/8)/1000) = 500; um bis auf 500 hochzuzählen wird 1ms benötigt
  OCR1A = 500-1;  //warum -1 ???
 
  // Compare Interrupt erlauben
  TIMSK |= (1<<OCIE1A);          
  
  
  
 sei();        //einschalten der Interrupts. Setzt das Global Interrupt Enable Bit im Status Register. 

while(1)
    {
//Ausgabe

DDRD |= (1<<PD5) | (1<<PD6);   
PORTD |= (1<<PD5);                    //zum Testen





    }

return 0;                 
}

Hab ich einen Denkfehler im Programm? Oder kann es an einer Einstellung in AVR-Studio bzw. PonyProg liegen? Aber dann würde das 1. Programm wohl auch nicht richtig funktionieren?

Für euer Mühe danke ich euch!

Grüße Sven
 
Hallo Sven,

ich habe im Moment leider nicht so die Zeit, deshalb nur mal ganz kurz (falls dir es nicht ausreichend weiterhilft und kein anderer User zwischenzeitlich helfen kann, schaue ich morgen nocheinmal genauer)

Du hast zwei Fehler im zweiten Programm. Die TimerISR soll sicherlich alle ms oder in größeren Intevallen auftreten. Du zählst in der ISR bis 1000000, erst dann setzt du den Portpin. Die ISR müsste alle 2us aufgerufen werden, um 2 Sekunden zu erreichen.

Der zweite Fehler: Die Variable "test" ist uint8_t, du müsstest hier dann schon uint32_t verwenden.

Schau dir mal unseren AVR Timer Calculator an, der hilft dir bei der Berechnung des Comparewertes.

Gruß,
Dirk
 
Danke Dirk, für die schnelle Antwort!

Die Variable habe ich jetzt entsprechend geändert.

Die restliche Antwort verstehe ich nicht ganz genau.:(
Das Programm habe ich mir auch schon angeguckt.
Ich erkläre mal kurz, wie ich es jetzt verstehe: Wenn der OCR1A bei 499 ist, dann ist 1s vergangen und der Interrupt wird ausgelöst. Der AVR-Timer Calculator sagt mir das auch:
Wenn ich im Calculator für die Frequenz 4000000Hz und für Interrupt-Time 1000ms(=1sek) eingebe, dann gibt mir das Programm bei einem Prescaler von 8 auch den Wert 499 aus.
Das bedeutet, dass jede Sekunde, der Interrupt ausgelöst wird und auch jede Sekunde meine Variable "test" in der Interruptroutine einen hochgezählt wird, oder nicht?
Wenn ich jetzt die LED nach zwei Sekunden zum Leuchten bringen will, dann müsste ich in der Interruptroutine statt einer 1000000 eine 2 eingeben.

Das klappt aber auch nicht. Jetzt geht die LED ziemlich schnell an. Ich schätze mal weniger als 1 Sekunde.
Hab ich noch einen Denkfehler?
 
Guten Morgen Sven!

Schau dir nochmal die Berechnung mit dem AVR-Timer-Calculator an. Den Wert für die Interruptzeit mußt du in Mikrosekunden angeben, nicht in Millisekunden.

Bei einem Prescaler von 8 und einem OCR1A Wert von 499 erhältst du also alle 1ms einen Compare-Interrupt, nicht alle 1s.

Bei 1ms müsstest du also für 2s bis 2.000 zählen oder.....


Du stellst die Interruptzeit auf 1s:
(Prescaler von 8 reicht nicht aus)
zB: Prescaler 256, OCR1A=15624

(Das Programm berücksichtigt nicht, ob ein Prescaler in einem bestimmten Mikrocontroller vorhanden ist. Nicht jeder Mikrocontroller hat alle angegebenen Prescaler. Hier mal in das Datenblatt schauen, ob 256 vorhanden ist, ich denke aber mal schon)



Noch ein Hinweis zur Initialisierung des Timers:

Mit TCCR1B = (1<<CS11); stellst du den Prescaler ein, ab hier läuft der Timer! Du solltest zuvor den Timer selber TCNT1 auf 0x0000 initialisieren und auch den Comparewert OCR1A festlegen. Erst ganz zum Schluss den Timer durch Setzen eines Prescalerbits im Register TCCR1B starten. Wenn du im Laufenden Programm, also nicht direkt nach Reset den Timer initialisierst, ist es noch sinnvoll, das Interrupt-Anforderungsflag sicherheitshalber zu löschen, bevor du den entsprechenden Interrupt freigibst. Das machst du durch Setzen des Bits OCIE1A im Register TIMSK.

Dirk
 
Leider bin ich gerade nicht zu Hause und kann das Programm nicht testen. Aber ich habe es mal umgeschrieben:

Code:
//Vortest Uhr

#define F_CPU 4000000UL  
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>



int counter; //Zaehlvariable
int millisec;  
int sec;
int min;
int std;
volatile uint32_t test = 0;








ISR(TIMER1_COMPA_vect)  //Die Interruptroutine. Kein ";" dahinter sonst Fehlermeldung beim Compilieren! 
                       // TIMER1_COMPA_vect ist der Interruptvektor.

{

test++;                 //zaehlt Variable um 1 hoch, jede ms 1mal
if(test==2000)          //nach 2sek sollte die zweite LED anfangen zu leuchten
{          
PORTD |= (1<<PD6);
}

}




int main (void) 

{
sei();        //einschalten der Interrupts. Setzt das Global Interrupt Enable Bit im Status Register. 

// Compare Interrupt erlauben
  TIMSK |= (1<<OCIE1A);          
  
TCNT1=0; //TCNT1 auf 0x0000 initialisieren, wozu?
  // Timer 1 konfigurieren, Timer 1 kann CTC
  TCCR1A = (1<<WGM12); // CTC Modus mit OCR1A
  OCR1A = 499; 


TCCR1B = (1<<CS11); // Prescaler 8    
  // ((4000000/8)/1000) = 500; um bis auf 499 hochzuzählen wird 1ms benötigt; warum -1 ???;ab hier startet der Timer
 
  
  
  
  
 

while(1)
    {
//Ausgabe

DDRD |= (1<<PD5) | (1<<PD6);   
PORTD |= (1<<PD5);                    //zum Testen





    }

return 0;                 
}

So ganz habe ich noch nicht verstanden, wozu diese Zeile gut ist?: TCNT1=0; //TCNT1 auf 0x0000 initialisieren

Ich probier das Programm morgen mal aus, dann bin ich wieder zu Hause.

Danke und Gruß

Sven
 
TCNT1 ist das Zählregister des Timers/counters 1 (strenggenommen sind es zwei Register, weil das ein 16bit-Timer ist). Da 0 das initial value dieser Register ist (nach jdem Reset) und der Timer steht (Prescaler=0), sollte dieser Schritt hier ähnlich unnötig sein, wie zB das initialisieren des Stackpointers bei einigen anderen Controllern (Mega88 usw). Zu der Frage mit den 500 aus Deinem Code: der Timer startet mit 0.
(Wenn das ein erstes Testprogramm (bezüglich des (Hardware-) Timers sein soll, warum läßt Du die LED dann nicht erstmal im Sekundentakt toggeln?)
P.S.: üblicherweise erlaubt man die Interrupts global (SEI) erst nach der kompletten Initialisierung, also direkt vor dem Hauptprogramm.
 
Danke LotadaC für deine Antwort. Jetzt weiß ich endlich, weswegen man einen abziehen muss.

Ich weiß nicht, wodran es noch liegen kann. Wenn ich test=2000 eintrage, dann muss ich über 4 Min warten bis die LED 2 leuchtet und nicht wie angenommen 2 Sek. Wenn ich 40 eintrage, sind es 5 Sek. Irgendetwas läuft da nicht richtig. :(

Code:
//Vortest Uhr

//#define F_CPU 4000000UL  
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>



int counter; //Zaehlvariable
int millisec;  
int sec;
int min;
int std;
volatile uint32_t test = 0;








ISR(TIMER1_COMPA_vect)  //Die Interruptroutine. Kein ";" dahinter sonst Fehlermeldung beim Compilieren! 
                       // TIMER1_COMPA_vect ist der Interruptvektor.

{

test++;                 //zaehlt Variable um 1 hoch, jede ms 1mal


}




int main (void) 

{
 

// Compare Interrupt erlauben
  TIMSK |= (1<<OCIE1A);          
  
//TCNT1=0; //TCNT1 auf 0x0000 initialisieren, wozu?
  // Timer 1 konfigurieren, Timer 1 kann CTC
  TCCR1A = (1<<WGM12); // CTC Modus mit OCR1A
  OCR1A = 499; 


TCCR1B = (1<<CS11); // Prescaler 8    
  // ((4000000/8)/1000) = 500; um bis auf 499 hochzuzählen wird 1ms benötigt;ab hier startet der Timer
 
  
  
sei();        //einschalten der Interrupts. Setzt das Global Interrupt Enable Bit im Status Register.  
  


while(1)
    {
//Ausgabe

DDRD |= (1<<PD5) | (1<<PD6);   
PORTD |= (1<<PD5);                    //zum Testen

if(test==40)          // Led geht nach ca. 5sek an. Bei 2000 würde sie nach ca. 4 Minuten angehen.
{          
PORTD |= (1<<PD6);
}



    }

return 0;                 
}
 
So scheint es zu gehen: Die LED2 leuchtet nach 2Sek. Allerdings ist das nicht zu friedendstellend. Ich kann OCR1A ja nicht beliebig groß machen. Wenn ich mit dem Calculator den OCR1A-Wert für 5Sek berechnen will, dann wir der Wert immer größer und es gibt Error us. Wie kann ich jetzt zum Beispiel bis auf eine Stunde hochzählen?
Hier mal das Prog:

Code:
//Vortest Uhr

//#define F_CPU 4000000UL  
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>



int counter; //Zaehlvariable
int millisec;  
int sec;
int min;
int std;
volatile uint32_t test = 0;








ISR(TIMER1_COMPA_vect)  //Die Interruptroutine. Kein ";" dahinter sonst Fehlermeldung beim Compilieren! 
                       // TIMER1_COMPA_vect ist der Interruptvektor.

{

PORTD |= (1<<PD6);


}




int main (void) 

{
 

// Compare Interrupt erlauben
  TIMSK |= (1<<OCIE1A);          
  
//TCNT1=0; //TCNT1 auf 0x0000 initialisieren, wozu?
  // Timer 1 konfigurieren, Timer 1 kann CTC
  TCCR1A = (1<<WGM12); // CTC Modus mit OCR1A
  OCR1A = 31249; mit AVR Timer Calculator ermittelt


TCCR1B = (1<<CS12); // Prescaler 256    
  
 
  
  
sei();        //einschalten der Interrupts. Setzt das Global Interrupt Enable Bit im Status Register.  
  


while(1)
    {
//Ausgabe

DDRD |= (1<<PD5) | (1<<PD6);   
PORTD |= (1<<PD5);                    //zum Testen





    }

return 0;                 
}
 
Jetzt geht es. Ich lass die zweite LED nach 8 Sek. leuchten (OCR1A=62499 entspricht 4 Sek, Prescaler 256). Die Zeit passt, die LED leuchtet nach 8 Sek! :D
ABER wenn ich den OCR1A = 15624 (entspricht 1 Sek, Prescaler 256) mache und hoffe, dass die LED nach 2 Sek aufleuchtet stimmt die Zeit wieder nicht!:mad: Wodran kann denn das liegen????

Das Prog:

Code:
//Vortest Uhr

//#define F_CPU 4000000UL  
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>



int counter; //Zaehlvariable
int millisec;  
int sec;
int min;
int std;
volatile uint32_t test = 0;








ISR(TIMER1_COMPA_vect)  //Die Interruptroutine. Kein ";" dahinter sonst Fehlermeldung beim Compilieren! 
                       // TIMER1_COMPA_vect ist der Interruptvektor.

{

test = test + 1;


}




int main (void) 

{
 

// Compare Interrupt erlauben
  TIMSK |= (1<<OCIE1A);          
  
//TCNT1=0; //TCNT1 auf 0x0000 initialisieren, wozu?
  // Timer 1 konfigurieren, Timer 1 kann CTC
  TCCR1A = (1<<WGM12); // CTC Modus mit OCR1A
  OCR1A = 62499; //mit AVR Timer Calculator ermittelt, entspricht 4 Sek


TCCR1B = (1<<CS12); // Prescaler 256    
  
 
  
  
sei();        //einschalten der Interrupts. Setzt das Global Interrupt Enable Bit im Status Register.  
  


while(1)
    {
//Ausgabe

DDRD |= (1<<PD5) | (1<<PD6);   
PORTD |= (1<<PD5);                    //zum Testen

if(test==2)          //nach 8sek leuchtet die zweite LED. 
{          
PORTD |= (1<<PD6);
}



    }

return 0;                 
}
 
Ich hab jetzt mal den Prescaler gewechselt. Prescaler ist jetzt 64. Dadurch wird der OCR1A größer. Nämlich auf 62499.
Jetzt funktioniert der Timer so wie er soll/die Zeiten passen! Ich bekomme jetzt alle 1 Sek einen Interrupt! :cool:
Irgendwie scheint der ATmega8 nicht mit kleineren Werten im OCR1A umgehen zu können. Komisch oder?

So, bin dann mal im Bett. Gute Nacht!
 
Haste mal versucht, das im Simulator nachzuvollziehen? Ansonsten könnte man das auch mal als Assembler-Programm testen.
P.S.: Du lässt in jedem Durchlauf der Hauptprogramm-Schleife Led1 anschalten ( und beide LEDs auf Ausgang setzen), das reicht vor der Schleife.
 
Hallo Sven,

deine Initialisierung ist nicht richtig. Das Bit WGM12 für den CTC Mode mit dem Register OCR1A befindet sich nicht in TCCR1A, sondern in TCCR1B!

Die Initialisierung wäre so richtig:

Code:
TCNT1 = 0; // optional, wenn Timer nach Reset noch nicht genutzt wurde ist es nicht notwendig
OCR1A = 15624; (ergibt bei fosc=4MHz, Prescaler=256 eine Interruptzeit von 1s)
TIMSK |= (1<<OCIE1A);
TCCR1B = (1<<WGM12) | (1<<CS12); // ab hier läuft der Timer! (Prescaler 256)

sei();

Im Moment läuft der Timer bei dir ganz normal von 0x0000 bis 0xFFFF, danach läuft er über. Es ist hier egal, welchen Wert du für OCR1A festlegst, es kommt in selben Zeitabständen zu einem CompareInterrupt.

Noch ein paar Hinweise:
(1) Ungünstig finde ich die Variable "test", zumal sie uint32_t ist. Hier könntest du sogar schon folgendes machen: Wenn du in der while(1) Schleife des Hauptprogramms ein Sekundensignal benötigst, wäre es sogar möglich, das Interruptanforderungsflag des CompareInterrupts zu prüfen. Dann brauchst du nichtmal die ISR.

(2) Die Initialisierung der Portfunktionen sollten vor die while(1) Schleife.

(3) Wenn du Variablen größer als uint8_t im Hauptprogramm behandelst, die auch in der ISR verwendet (geändert) werden, musst du dafür sorgen, dass dir im Hauptprogramm an den entsprechenden Stellen die ISR die Variable nicht verändert.

Gruß,
Dirk
 
Ups...
Aber trotzdem ist das nur die halbe Erklärung. Ihn interessiert ja (bisher) nur das erste CompareMatch. Das sollte auch im normalMode stimmen. Was macht das falsch gesetzte Bit (mag mir jetzt nicht das DB aufs Tablet laden)?
Die IRQ-Unterdrückung beim Rechnen mit mehrbytigen Zahlen wird nicht durch den Compiler erledigt? (und bei Bascom? - sry, ich programmiere sonst möglichst in Assembler)
 
Hallo LotadaC!
Ups...
Aber trotzdem ist das nur die halbe Erklärung. Ihn interessiert ja (bisher) nur das erste CompareMatch. Das sollte auch im normalMode stimmen.

Nein, natürlich ist das die Erklärung. Er zählt ja innerhalb der ISR eine Variable und er prüft innerhalb der while(1) Schleife auf Variable == 2. Das ist nicht der erste CompareMatch!

Der eigentliche Fehler ist, dass der Timer Timer garnicht im CTC Mode mit OCR1A läuft.

Was macht das falsch gesetzte Bit (mag mir jetzt nicht das DB aufs Tablet laden)?

Das Bit FOC1A wird gesetzt ... hier passiert aber im aktuellen Programm nichts.

Die IRQ-Unterdrückung beim Rechnen mit mehrbytigen Zahlen wird nicht durch den Compiler erledigt? (und bei Bascom? - sry, ich programmiere sonst möglichst in Assembler)
Beim GCC muss man sich selber drum kümmern,bei Bascom weiss ich nicht, denke aber mal, dass es dort Bascom macht.

Dirk
 
Hallo! :hello:

Ich habe die Initilisierung jetzt vor die while-Schleife gesetzt (2). Brauch ja nicht jedes Mal neu initialisiert werden.

Das Register war falsch. Nicht TCCR1A, sondern TCCR1B. Ich habe es berichtigt. Danke!
Hier der berichtigte Code:

Code:
//Vortest Uhr

//#define F_CPU 4000000UL  
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>



int counter; //Zaehlvariable
int millisec;  
int sec;
int min;
int std;
volatile uint32_t test = 0;








ISR(TIMER1_COMPA_vect)  //Die Interruptroutine. Kein ";" dahinter sonst Fehlermeldung beim Compilieren! 
                       // TIMER1_COMPA_vect ist der Interruptvektor.

{

test = test + 1;


}




int main (void) 

{
 

// Compare Interrupt erlauben
  TIMSK |= (1<<OCIE1A);          
  
//TCNT1=0; //TCNT1 auf 0x0000 initialisieren
  // Timer 1 konfigurieren, Timer 1 kann CTC
  TCCR1B = (1<<WGM12); // CTC Modus mit OCR1A
  OCR1A = 62499; //mit AVR Timer Calculator ermittelt, entspricht 1 Sek


TCCR1B = (1<<CS10) | (1<<CS11); // Prescaler 64    
  
 
  
  
sei();        //einschalten der Interrupts. Setzt das Global Interrupt Enable Bit im Status Register.  
  

DDRD |= (1<<PD5) | (1<<PD6);   
PORTD |= (1<<PD5);                    //zum Testen

while(1)
    {
//Ausgabe



if(test==10)          //nach 10sek leuchtet die zweite LED. 
{          
PORTD |= (1<<PD6);
}



    }

return 0;                 
}

Ich habe das Programm weiterentwickelt. Gebe jetzt die Uhrzeit auf ein LCD-Display aus. Allerdings läuft die Zeit nicht ganz richtig. Ich habe ein Abweichung (zu langsam) von ca. 3 Sekunden/Minute. :hmpf:
Ich habe gelesen, dass ein Quarz auch nicht ganz genau ist. Allerdings belaufen sich die Ungenauigkeiten ohne Softwareberichtigung in ms/Tag oder so ähnlich.
Da ich kein Ozilloskop habe und die Zeiten nur mit einer Stopuhr gemessen habe. Kann ich nicht ganz genau sagen, ob der Fehler auch in dem Programm oben auftritt (Vortest Uhr). Zählt ja immer nur bis zehn.
Aber auf dem Display zählt es höher, sodass Zeitfehler jetzt eher auffallen.
Bevor ich euch jetzt mit dem anderen Programm (Uhr) belaste, wollte ich die anderen beiden möglichen Fehlerquellen (1),(3) erst selber bearbeiten/eleminieren:

Noch ein paar Hinweise:
(1) Ungünstig finde ich die Variable "test", zumal sie uint32_t ist. Hier könntest du sogar schon folgendes machen: Wenn du in der while(1) Schleife des Hauptprogramms ein Sekundensignal benötigst, wäre es sogar möglich, das Interruptanforderungsflag des CompareInterrupts zu prüfen. Dann brauchst du nichtmal die ISR.

(2) Die Initialisierung der Portfunktionen sollten vor die while(1) Schleife.

(3) Wenn du Variablen größer als uint8_t im Hauptprogramm behandelst, die auch in der ISR verwendet (geändert) werden, musst du dafür sorgen, dass dir im Hauptprogramm an den entsprechenden Stellen die ISR die Variable nicht verändert.

Ich würde gerne bei (3) anfangen: Wie kann ich denn dafür sorgen, dass die ISR die Variable nicht im Hauptprogramm verändert?
Warum passiert das nur mit Variablen, die größer als uint8_t sind??
Was ist eine IRQ-Unterdrückung? Hat das damit was zu tun?

Zu (2):
Wie überprüfe ich das Interruptanforderungsflag vom Compare Interrupt? Warum sind uint32_t Variablen ungünstig?

Beste Grüße
Sven
 
zu3.: uint8 sind ein-byte-Variablen, die der Prozessor mit einem Takt manipuliert (8bit ein byte - 0 bis 254). Größere Zahlen brauchen dann mehrere bytes, die dann in mehreren aufeinanderfolgenden Takten manipuliert werden müssen. Wenn zufällig zwischen diesen Takten eine ISR reinhaut, steht ein der zusammengesetzten Zahl was Falsches, klar? In Assembler kann man das Globale InterrutEnableFlag problemlos setzen und löschen, kA obs dafür in BASCOM einen einfachen Befehl gibt - versuchs mal mit "disable interrupts".
2tens hat zwar nix mit dem Zitat zu tun, aber: TIFR ist das "TimerInterruptFlagRegister", in dem unter anderen auch die OC-Flags der Timer zu finden sind. Die werden gesetzt, sobald der OC-Match stattfindet. ist der entsprechende Interrupt freigegeben (TIMSK), und Interrrupts global (SREG), wird der entsprechende Interrupt-Vektor angesprungen.
Cave: Execution der ISR löscht das InterruptFlag automatisch. Wenn Du das Flag nur pollst, bleibt das Flag gesetzt - dann mußt Du es selbst hinterher löschen.
 
Hallo zusammen!

zu3.: uint8 sind ein-byte-Variablen, die der Prozessor mit einem Takt manipuliert (8bit ein byte - 0 bis 254).

@LotadaC: wahrscheinlich ein Schreibfehler wegen der frühen Morgenstunden ;), der Zahlenbereich ist 0..255.

@Sven:
Schau dir am besten einmal das AVR GCC Tutorial bei mikrocontroller.net an, dort ist erklärt, auf was man achten muss, wenn man Variablen in einer ISR und im Hauptprogramm nutzt. Im Prinzip musst du dafür sorgen, dass wenn du im Hauptprogramm auf die Variable zugreifst, die ISR nichts ändern darf. Das erreichst du zum Beispiel, indem du vor dem Zugriff die Interrupts global abschaltest cli(); und danach wieder freigibst sei();.

uint32_t ist soweit ungünstig, wenn du hiermit nur bis 2 zählst, wie in deinem Code in einem deiner Beiträge. Du hast es nun so realisiert, dass du die Sekunden in der ISR erhöhst und im Hauptprogramm auswertest. Du kannst es aber auch so machen, dass du in der ISR lediglich ein Byte (oder Bit) als Signal (Sekundensignal) verwendest, dieses im Hauptprogramm auswertest (pollst) und im Hauptprogramm die Sekunden zählst. Irgendwo hatte ich das glaube schoneinmal geschrieben.

Gruß,
Dirk
 
ja (*gähn Kaffeeschlürf*) - und ja, hier gehts um C, nicht um Bascom. Noch ein Hinweis: es gibt diverse Assemblertutorials, die einem die Abläufe innerhalb der Controller und insbesondere der Hardware deutlich näher bringen (auch wenn man dann in'ner Hochsprache programmiert). Desweiteren ist natürlich ein Blick in das Datenblatt des verwendeten Controllers immer empfehlenswert.
 
So, habe jetzt eure Tips einmal umgesetzt. Danke schon mal bis hierher!

Habe die Varibalen mit volatile deklariert und nur 1 Byte Variable benutzt. Wo das nicht ging, habe ich mit cli() und sei() gearbeitet.
Die Timer-Konfiguartion ist so geblieben. Bis auf die Änderung des Registernamens, der ja falsch war.
Leider habe ich immer noch das Problem, dass die Uhr 3Sekunden/Min zu langsam läuft. :(
Am Ende dieses Beitrages habe ich ein Foto von meiner Uhr hinzugefügt. :cool:

Ich möchte das Programm kurz erkären:
Mit der Deklaration der Varibalen habe ich gleich Werte vergeben. Die Werte entsprechen der Uhrzeit (einstellen der Uhrzeit). Es wäre, nach dem das Programm auf dem ATemga geladen ist, also 21:05:00
Ich habe die Uhrzeit in Zehnerstunden, Einerstunden, Zehnerminuten,Einerminuten, Zehnersekunden und Einersekunden aufgeteilt. Wenn es genau 21Uhr (21:00:00) wäre , dann wären die Zehnerstunden auf 2 und die Einerstunden auf 1 usw.
Die Variable zaehler brauche ich am Ende des Programmes um zu wissen, wann der Tag zu ende ist, damit die Uhr auf 00:00:00 springt.


Code:
// 
// Anpassungen im makefile:
//    ATMega8 => MCU=atmega8 im makefile einstellen
//    lcd-routines.c in SRC = ... Zeile anhängen 
// 
#include <avr/io.h>
#include <stdlib.h>
#include "lcd-routines.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <math.h>

//Variablen, die sowohl in Interrupt-Routinen (ISR = Interrupt Service Routine(s)) 
//als auch vom übrigen Programmcode geschrieben oder gelesen werden, müssen mit 
//einem volatile deklariert werden. Damit wird dem Compiler mitgeteilt, dass der 
//Inhalt der Variablen vor jedem Lesezugriff aus dem Speicher gelesen und nach 
//jedem Schreibzugriff in den Speicher geschrieben wird. Ansonsten könnte der 
//Compiler den Code so optimieren, dass der Wert der Variablen nur in 
//Prozessorregistern zwischengespeichert wird, die nichts von der Änderung woanders 
//mitbekommen.

volatile uint8_t einersek = 0;
volatile uint8_t zehnersek = 0;
volatile uint8_t einermin = 5;
volatile uint8_t zehnermin = 0;
volatile uint8_t einerstd = 1;
volatile uint8_t zehnerstd = 2;
volatile uint32_t zaehler  = 75900;
volatile uint32_t tmpCnt = 0;


ISR(TIMER1_COMPA_vect)  //Die Interruptroutine. Kein ";" dahinter sonst Fehlermeldung beim Compilieren! 
                       // TIMER1_COMPA_vect ist der Interruptvektor.

{

einersek++;
zaehler++;



}






 
int main(void)
{
   


// Compare Interrupt erlauben
  TIMSK |= (1<<OCIE1A);          
  
//TCNT1=0; //TCNT1 auf 0x0000 initialisieren
  // Timer 1 konfigurieren, Timer 1 kann CTC
  TCCR1B = (1<<WGM12); // CTC Modus mit OCR1A
  OCR1A = 62499; //mit AVR Timer Calculator ermittelt, entspricht 1 Sek


TCCR1B = (1<<CS10) | (1<<CS11); // Prescaler 64    
  
 



  
sei();        //einschalten der Interrupts. Setzt das Global Interrupt Enable Bit im Status Register. 
  
  
  
  
  lcd_init();

char Buffer[20]; // in diesem {} lokal
 
  
 

    
  //Zeile 1 des Displays   
lcd_setcursor(0,1);
lcd_string("Zeit");

//Zeile 1 des Displays 
lcd_setcursor(5,2);
lcd_data(':');


 
 while(1)
    {
  //Ausgabe

     
lcd_setcursor(0,1);    //Zeile 1 des Displays
lcd_string("Zeit");

lcd_setcursor(5,2);    //Zeile 2 des Displays
lcd_data(':');

lcd_setcursor(2,2);    //Zeile 2 des Displays
lcd_data(':');


if(einersek<=9)  //Ausgabe auf Display        
{         

lcd_setcursor(7,2);
itoa(einersek,Buffer,10); lcd_string(Buffer);

}


if(einersek==10)          
{         

einersek = 0;
zehnersek++;
lcd_clear();                 //Zeile behebt den Fehler,dass eine Null zuviel angezeigt wird nach der einersek (sporadisch)

}



if(zehnersek<=5)   //Ausgabe auf Display      
{         

lcd_setcursor(6,2);
itoa(zehnersek,Buffer,10); lcd_string(Buffer);

}

if(zehnersek==6)          
{         

zehnersek = 0;
einermin++;

}


if(einermin<=9)  //Ausgabe auf Display        
{         

lcd_setcursor(4,2);
itoa(einermin,Buffer,10); lcd_string(Buffer);

}


if(einermin==10)          
{         

einermin = 0;
zehnermin++;

}

if(zehnermin<=5)    //Ausgabe auf Display      
{         

lcd_setcursor(3,2);
itoa(zehnermin,Buffer,10); lcd_string(Buffer);

}

if(zehnermin==6)          
{         

zehnermin = 0;
einerstd++;

}

if(einerstd<=9)       //Ausgabe auf Display   
{         

lcd_setcursor(1,2);
itoa(einerstd,Buffer,10); lcd_string(Buffer);

}


if(einerstd==10)          
{         

einerstd = 0;
zehnerstd++;

}

if(zehnerstd<=2)     //Ausgabe auf Display     
{         

lcd_setcursor(0,2);
itoa(zehnerstd,Buffer,10); lcd_string(Buffer);

}
//Bei Variablen größer ein Byte, auf die in Interrupt-Routinen und im 
//Hauptprogramm zugegriffen wird, muss darauf geachtet werden, dass 
//die Zugriffe auf die einzelnen Bytes außerhalb der ISR nicht durch 
//einen Interrupt unterbrochen werden. (Allgemeinplatz: AVRs sind 8-bit 
//Controller)

cli();
tmpCnt = zaehler;
sei();
if(tmpCnt==86400)  //entspricht 24h          
{         

zehnerstd = 0;
einerstd = 0;

}




}
return 0;                 
}

Uhr.jpg

Was kann den Fehler noch verursachen? Vielleicht der Compiler?

Mit Grüßen

Sven
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)