mikrocontroller.net

Forum: Compiler & IDEs ATMEGA168 - Uhr


Autor: Henry (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich bin neu auf dem Gebiet und habe mir daher die Aufgabe gestellt eine 
Uhr zu bauen (mittel 7-Segmentanzeigen). Das ganze funktioniert als 
Brettaufbau an sich auch gut, nur dass die Uhr zu langsam läuft.

Daten:
- 8Mhz interner Osc. (laut Datenblatt und AVR Studio)
- Prescaler auf 1024
- 8Bit-Counter

Das macht also rund 30 Überläufe je Sekunde (256). Eine entsprechende 
Zählvariable sorgt bei erreichten 30 für die Inkrementierung der 
Zeitvariable (sek).

Nun ändert sich die Sekundenanzeige aber nur etwa alle 3-4 sek.
Hat jemand einen Vorschlag?

Das ganze riecht nach geringerem Takt als 8Mhz, ich habe aber nie etwas 
am Takt geändert.


Anbei noch eine Frage: Zählt der Timer auch dann hoch, wenn gerade eine 
ISR (Timer Overflow) abläuft, oder bleibt er währenddessen konst.?

Vielen Dank schonmal...

Autor: Grrrr (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Henry schrieb:
> Hat jemand einen Vorschlag?

Ja. Schaltplan und Code posten.
Glaskugel ist leider gerade in der Abkühlphase.

Autor: Grrrr (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Henry schrieb:
> Zählt der Timer auch dann hoch, wenn gerade eine
> ISR (Timer Overflow) abläuft, oder bleibt er währenddessen konst.?

Ja.

Autor: Einer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wenn du deinen Code zeigst bekommst du mehr Hilfe.

Interner Takt ist eh zu Ungenau.

Autor: Grrrr (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Der interne RC-Oszillator ist eigentlich nicht genau genug für eine Uhr. 
Nimm lieber einen Quartz.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Henry schrieb:

> Brettaufbau an sich auch gut, nur dass die Uhr zu langsam läuft.
>
> Daten:
> - 8Mhz interner Osc. (laut Datenblatt und AVR Studio)

Aus diesem Grund wird deine Uhr auch nie einigermassen genau gehen

> Das macht also rund 30 Überläufe je Sekunde (256). Eine entsprechende

Da ist die nächste Fehlerquelle

> Nun ändert sich die Sekundenanzeige aber nur etwa alle 3-4 sek.

Das ist allerdings zu heftig um es mit den beiden Fehlerquellen zu 
erklären.

> Das ganze riecht nach geringerem Takt als 8Mhz,

allerdings

> ich habe aber nie etwas am Takt geändert.

macht nichts. Trotzdem kontrollieren.

(Endlosschleife und mit einem _delay_ms dazwischen eine LED blinken 
lassen. Optisch kontrollieren ob die delay Zeiten stimmen können. 500ms 
kann man auch optisch kontrollieren. Ob eine Led pro Sekunde 1 mal oder 
3 blinkt, sieht auch ein Blinder)

Autor: Henry (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Erstmal herzlichen Dank für die Antworten. Das die Uhr nicht genau geht 
ist bei den Voraussetzungen ja klar, aber wie schon erwidert können alle 
Abweichungen nicht ein derartigen Fehler erzeugen.

Den Schaltplan müsste ich erst noch "zeichnen". Daher poste ich zunächst 
nur den Code:

Ich gebe zu bedenken, dass ich harter Anfänger bin - der Programmierstil 
ist mit Sicherheit verbesserungswürdig:


#include <avr/io.h>
#include <stdint.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#ifndef F_CPU
#define F_CPU 8000000UL
#endif

volatile uint8_t min=0,hrs=0, i;
uint16_t counter=0;
uint8_t segment_array[4] ={0,0,0,0};


uint8_t debounce_h (volatile uint8_t *reg, uint8_t pin); //PB6
uint8_t debounce_m (volatile uint8_t *reg, uint8_t pin); //PB7
void  integer_split (volatile uint8_t stunden, uint8_t minuten);

int main (void)
{


DDRB= 0xFF;
DDRD= 0xFF; //Transistoren
DDRC &= ~(1<<PC5) | (1<<PC4); // Taster-Eingänge

TCCR0B |= ((1<<CS02)|(1<<CS00));
TIMSK0 |= (1<<TOIE0);
PCICR |= (1<<PCIE1);
PCMSK1 |= (1<<PCINT13) | (1<<PCINT12); // PCINT 5 und 4 aktiviert

sei();

while (1)
{

for (i=0;i<=3; i++)
  { PORTD = 0x00;
    PORTB = 0x00;

   switch (i)
     { case 0:
      PORTD |= (1<<PD0);
      break;

      case 1:
        PORTD |= (1<<PD1);
                  break;

      case 2:
        PORTD |= (1<<PD2);
      break;

      case 3:
        PORTD |= (1<<PD3);
      break;
    }

    switch (segment_array[i])
    { case 0:
      PORTB = 0b11111100;
      break;

      case 1:
            PORTB = 0b01100000;
      break;

      case 2:
      PORTB =  0b11011010;
      break;

      case 3:
      PORTB =  0b11110010;
      break;

      case 4:
      PORTB =  0b01100110;
      break;

      case 5:
      PORTB =  0b10110110;
      break;

      case 6:
      PORTB =  0b10111110;
      break;

      case 7:
      PORTB =  0b11100000;
      break;

      case 8:
      PORTB =  0b11111110;
      break;

      case 9:
      PORTB =  0b11110110;
      break;
       }

  }

}
return 0;
}


uint8_t debounce_h (volatile uint8_t *reg, uint8_t pin)
{cli();
 if ((*reg & (1<<pin))!=0)
  {  _delay_ms(30);
     _delay_ms(30);

  if ((*reg & (1<<pin))!=0)
  { _delay_ms(30);
    _delay_ms(30);
    return 1;
  }

  }
  return 0;
  sei();
}

uint8_t debounce_m (volatile uint8_t *reg, uint8_t pin)
{cli();
 if ((*reg & (1<<pin))!=0)
  {  _delay_ms(30);
     _delay_ms(30);

  if ((*reg & (1<<pin))!=0)
  { _delay_ms(30);
    _delay_ms(30);
    return 1;
  }
  }
  return 0;
  sei();
}

void integer_split (volatile uint8_t stunden, uint8_t minuten)
{  uint8_t k;
   k = stunden - (stunden%10);
   segment_array[0]= k/10;
   segment_array[1]= stunden%10;
   k = minuten - (minuten%10);
   segment_array[2]= k/10;
   segment_array[3]=minuten%10;
}


ISR(TIMER0_OVF_vect)
{counter++;

if (counter > 30)
{  if (min<59)
      { min++;
    }
   else
      { min = 0;
        hrs++;
      }

   if (hrs>23)
     {hrs=0;
     }
   counter=0;
integer_split(hrs, min);
}
}


ISR (PCINT1_vect)
{
   if (debounce_h(&PINC, PC5)==1)
      { hrs++;}
   if (debounce_m(&PINC, PC4)==1)
      { min++;}
   integer_split(hrs, min);
}


Im Code ist auch noch ein Fehler, die Variable "min" soll später mal die 
Minuten darstellen (habe nur vier Anzeigen HH:MM)

Den Vorschlag mit dem Delay werde ich schnellstmöglich mal testen.

Autor: Grrrr (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du darfst natürlich nur alle 60s die Minute inkrementieren.
Du machst es aber alle 30s.

Nimmt man an, das Du die CKDIV8 Fuse nicht zurückgesetzt hat, läuft der 
uC mit 1MHz. Das würde etwa den Faktor 3-4 (im Zusammenhang mit dem 
nicht korrekten 30s wg. 8MHz / 1024) erklären.

Autor: Grrrr (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oops. Da habe ich mich verguckt. Du inkrementierst die Minute jedenfalls 
denke ich alle Sekunde da Du 30 Überläufe pro Sekunde hast.
CKDIV8 würde ich auf jeden Fall angucken.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Funktionalität in integer_split ist schon reichlich viel Tobak für 
eine ISR. Divisionen sind so ziemlich das schlimmste, was du einem AVR 
bei den Grundrechnungsarten antun kannst. Da muss er richtig arbeiten.

Die würde ich in die main Schleife rausziehen. Die ISR setzt nur ein 
Flag, dass die min (welche eigentlich Sekunden sind) nicht mehr stimmen 
und die Hauptschleife zerlegt sich dann die Zahlen so wie sie das 
braucht.

Autor: Henry (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi!

Danke zunächst für die Antworten. Werde das Bit und den Takt nochmal 
überprüfen (mittels LED etc.).

Bezüglich der ISR habe ich noch eine Frage:

Ursprünglich wurde die Funktion "integer_split" in der main-schleife 
ausgeführt. Da hatte ich allerdings das Problem, dass die 
Segmentanzeigen nicht richtig funktionierten (Multiplex). Heißt, während 
der Ausführung der integer_split blieb die letzte Anzeige an. Die letzte 
Anzeige ist länger an als die anderen, so dass es fürs Auge aussieht als 
ginge nur die letzte.
Vielleicht sollte ich während der Abarbeitung des integer_split kein 
Signal auf die Anzeigen schicken....

Die überfüllte ISR braucht also u.U. derart lange, dass ich damit den 
eigentlich folgenden Overflow Interrupt verpasse?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Henry schrieb:

> ausgeführt. Da hatte ich allerdings das Problem, dass die
> Segmentanzeigen nicht richtig funktionierten (Multiplex).

Na ja.
Der ganze Multiplex ist .... unkonventionell aufgebaut :-)

Normalerweise verlegt man diesen Multiplex in eine Timer-ISR

Allerdings ist die eine, die du hast, mit 30 Aufrufen in der Sekunde ein 
wenig zu schmalbrüstig für einen 7-Segment Multiplex.

Du hast 2 Möglichkeiten
* für das Multiplexing einen eigenen Timer samt ISR abstellen.
* Die Aufrufrate deiner jetzigen ISR erhöhen und dort den 7-Seg
  Multiplex mitmachen lassen

Welche Variante du auch immer wählst:
Bei einem Aufruf wird nur auf die jeweils nächste Stelle der Anzeige 
weitergeschaltet und NICHT alle 4 Anzeigen in einem Rutsch 
durchgeschaltet. Dei jeweils nächste Anzeige kommt erst beim nächsten 
ISR Aufruf drann. Auf diese Art leuchten alle Anzeigen immer gleich lang 
(minus dem bischen Zeitversatz, der in der ISR für das Zeit hochzählen 
vergeht. Das ist aber zu kurz, als dass du es sehen wirst)

> Vielleicht sollte ich während der Abarbeitung des integer_split kein
> Signal auf die Anzeigen schicken....

Du solltest den Update der Anzeige von allen anderen Dingen entkoppeln, 
indem du ihn in eine ISR verlagerst. Dann brauchst du dir um die Anzeige 
keine Sorgen mehr machen. Du beschreibst einfach nur deine Variablen und 
die ISR sorgt dafür, dass dieser Inhalt auch zur Anzeige kommt.

> Die überfüllte ISR braucht also u.U. derart lange, dass ich damit den
> eigentlich folgenden Overflow Interrupt verpasse?

Ich habs nicht nachgerechnet. Aber mein Bauch sagt: könnte sein. Du hast 
ziemlich viele Divisionen in dieser split Funktion.

Edit: Jetzt seh ichs erst. Ich wusste doch, dass mich da etwas stört. Du 
zählst nicht 30 ISR Aufrufe ab, sondern 31

ISR(TIMER0_OVF_vect)
{counter++;

if (counter > 30)


Probiers mit einer kleineren Zahl und deinen Fingern aus.

ISR(TIMER0_OVF_vect)
{counter++;

if (counter > 4)

Der Counter hat beim Betreten der ISR die Werte:
0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4

Von einer 0 zur nächsten 0 sind das aber 5 Zählvorgänge und nicht 4

Schreib deine ISR nicht so kryptisch
ISR(TIMER0_OVF_vect)
{
  counter++;

  if (counter == 30)
  {
    counter = 0;
    sec++;
    if( sec == 60 )
    {
      sec = 0;
      min++;
      if( min == 60 )
      {
        min = 0;
        hrs++;
        if( hrs == 24 )
          hrs = 0;
      }
    }
  }
}

So funktionieren alle Zählstufen gleich und es ist leichter sich von der 
korekten Funktion zu überzeugen.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.