C ATmega8: Taktung falsch

Hallo Sven!
Was kann den Fehler noch verursachen? Vielleicht der Compiler?

Ich glaube nicht, dass der Compiler den Fehler verursacht. _delay_ms o.ä. die irgendwo auftreten könnten Probleme machen,
wenn diese zu viel oder zu lange sind. delay.h benötigt F_CPU die Systemfrequenz. Hast du diese in den Projekteinstellungen
angegeben? Vielleicht gehen bei dir manchmal Sekunden "verloren" wenn
Code:
if(einersek==10)
{
  einersek = 0; 
  // ...
}
nicht oft genug aufgerufen wird. Ubrigens solltest du hier auf "größer gleich" prüfen.


Du hast innerhalb der while(1) Schleife sehr viele Display-Ausgaben, teilweise auch permanent, was nicht notwendig ist und
Taktzyklen "verbraucht". Der Display-Bus ist auch ständig aktiv. Du musst eigentlich immer nur dann etwas am Display ausgeben,
wenn sich die Sekunde ändert.

Ich würde das Programm etwas anders strukturieren, vom Prinzip in etwa so ...


Code:
ISR(TIMER1_COMPA_vect)           
{

  sekundensignal = 1; // setzen, dem Hauptprogramm melden

}

// ...

while(1)
{

[COLOR=#0000ff]wenn sekundensignal == 1 dann
{

  - sekundensignal löschen (=0)

  - sekunde inkrementieren
  wenn Sekunde größer gleich 10 dann
  {
    - sekunde = 0
    - zehnersekunde inkrementieren
    wenn zehnersekunde größer gleich 6 dann
    {
      zehnersekunde = 0
      
      ... usw.

    }
  }

  - lcd locate
  - string buffer füllen
  - string ausgeben
}[/COLOR]

// sonstiges machen

}

Es muss gewährleistet sein, dass die if-Abfrage (blau) mindestens einmal die Sekunden ausgerufen wird, sonste gehen Sekunden verloren. Falls du das nicht gewährleisten kannst,
dann zählst du die "Uhr" komplett innerhalb der ISR und bei jeder Sekunde (Sekundensignal) gibst du die Uhrzeit auf dem Display aus.

Umschalten von 23:59:59 auf 00:00:00 kannst du ganz innerhalb der if-Abfrage prüfen, nachdem du einerstunden inkrementierst:
wenn einerstunden == 4 und zehnerstunden == 2 dann
- alles auf 0

Du kannst natürlich bei deiner Vorgehensweise bleiben, du solltest als erstes mal dafür sorgen, dass nur dann eine Displayausgabe gemacht wird, wenn es wirklich notwendig ist.
(siehe hierzu mein Beispiel).

Grüße,
Dirk
 
Hallo Dirk!

Ich habe deine Vorschlage umgesetzt. Leider ohne Erfolg. Habe das Programm jetzt ein wenig kleiner gemacht. Zu Testzwecken, bis der Fehler behoben ist, lasse ich einfach Sekunden hochzählen im Display. Leider habe ich immer noch den Fehler mit den 3Sek/Min drin.
Ich habe mal das OCR1A = 20000 gesetzt, dann sollte die Sekunde ja eigentlich um einiges schnell hochzählen,oder?
Passiert ist aber nichts! Sie haben gleich schnell bzw. langsam gezählt.
Ich habe mal versucht den Prescaler auf 1 zu setzen (Den Wert in OCR1A natürlich auch angepasst), und in der if-Abfrage den Wert von 1 auf 1000 gesetzt. Da wurde dann gar nichts mehr gezählt.
Ich nehme mal stark an, dass nicht das Programm schuld ist, sonder der Timer nicht richtig funktioniert?
Können falsch gesetzte Fuse-Bits die Ursache sein?


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 a = 0;
volatile uint8_t seksignal = 0;
volatile uint8_t einersek = 0;
volatile uint8_t zehnersek = 0;
volatile uint8_t einermin = 0;
volatile uint8_t zehnermin = 0;
volatile uint8_t einerstd = 0;
volatile uint8_t zehnerstd = 0;
//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.

{

seksignal = 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 = 20000; // 62499 mit AVR Timer Calculator ermittelt, entspricht 1 Sek. Zu Testzwecken auf 20000 eingestellt.


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
 
  

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



 while(1)
    {
  //Ausgabe

     
if(seksignal == 1)       
{         


seksignal = 0;
einersek++;
a = 1; 

}




if(einersek>=10)          
{         

einersek = 0;
zehnersek++;


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

lcd_setcursor(6,2);
itoa(zehnersek,Buffer,10); lcd_string(Buffer);
a = 0;
}




if(a == 1)          
{         


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

}



}
return 0;                 
}

Fuses.JPG
 
Hallo Sven,

schau dir mal deine Timerinitialisierung genauer an:

Code:
   // 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 = 20000; // 62499 mit AVR Timer Calculator ermittelt, entspricht 1 Sek. Zu Testzwecken auf 20000 eingestellt.

  [COLOR=#800000]TCCR1B = (1<<CS10) | (1<<CS11); // Prescaler 64   [/COLOR]

Rot: überschreibt TCCR1B, das Flag WGM12 ist dann 0, was du zuvor setzt, also der Timer arbeitet nicht mehr im CTC Modus!

Ändere das erst mal und probiere erneut.

Gruß,
Dirk
 
Hallo!

Der letzte Tip, war der entscheidende (natürlich sind die anderen auch wichtig)!
Jetzt wird genauso schnell gezählt, wie meine Stoppuhr es auch tut. Und auch die Zählgeschwindigkeit ändert sich, wenn ich den Wert im OCR1A ändere. :party:
So funktioniert es:

Code:
/ Compare Interrupt erlauben
  TIMSK |= (1<<OCIE1A);          
  
TCNT1=0; //TCNT1 auf 0x0000 initialisieren
  
  OCR1A = 62499; //mit AVR Timer Calculator ermittelt, entspricht 1 Sek

// Timer 1 konfigurieren, Timer 1 kann CTC
TCCR1B = (1<<CS10) | (1<<CS11)| (1<<WGM12); // Prescaler 64; CTC Modus mit OCR1A

Ich hätte nicht gedacht, dass das Register wieder überschrieben wird.
D.h, wenn man das gleiche Register beschreibt, sollte man, so wie oben gemacht, es in einer Zeile machen und nicht auf mehre Zeilen aufteilen.
Jetzt kann ich mich wieder an das Uhrenprogramm heranwagen. Dankeschön!
Mal gucken, ob das dann gleich funktioniert oder was mich da erwartet...
 
Sollte aber eigentlich klar sein. TCCR1B = blablub ist schließlich eine Zuweisung. Ergo wird der Inhalt des Registers ersetzt. Um einzelne Bits zu setzen muß das Regitster geladen werden, mit der Maske ver-OR-t werden, und wieder zurückgeschrieben werden. (ReadModifyWrite). Dabei ggf Interrupts unterdrücken. In den Registern bis 1Fhex lassen sich die bits einzeln setzen/löschen. Aber dann nicht mehrere gleichzeitig.
 
Hier die fertige Uhr:
:dance3:

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 a = 0;
volatile uint8_t seksignal = 0;
volatile uint8_t einersek = 0;
volatile uint8_t zehnersek = 0;
volatile uint8_t einermin = 5;
volatile uint8_t zehnermin = 4;
volatile uint8_t einerstd = 9;
volatile uint8_t zehnerstd = 1;



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

{

seksignal = 1;

}






 
int main(void)
{
   


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


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

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

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(':');
 
 
lcd_setcursor(7,2);
itoa(einersek,Buffer,10); lcd_string(Buffer);

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

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

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

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



 while(1)
    {
  //Ausgabe

     
if(seksignal == 1)         
{         

seksignal = 0;
einersek++;
a = 1; 

}




if(einersek>=10)          
{         

einersek = 0;
zehnersek++;


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

lcd_setcursor(6,2);
itoa(zehnersek,Buffer,10); lcd_string(Buffer);
a = 0;
}

if(a == 1)          
{         


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

}


if(zehnersek>=6)          
{         

zehnersek = 0;
einermin++;

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

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

lcd_setcursor(5,2);    //nur wichtig, wenn einermin auf 10 geht.
lcd_data(':');

}


if(einermin>=10)          
{         

einermin = 0;
zehnermin++;

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

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





if(zehnermin>=6)          
{         

zehnermin = 0;
einerstd++;

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

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

lcd_setcursor(2,2);    //nur wichtig, wenn einerstd auf 10 geht.
lcd_data(':');
}


if(einerstd>=10)          
{         

einerstd = 0;
zehnerstd++;

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

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

}

if(zehnerstd>=2 && einerstd>=4)     //Ausgabe auf Display     
{         

einerstd = 0;
zehnerstd = 0;

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

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

}


}
return 0;                 
}
 

Ü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)