Forum: Compiler & IDEs ATMEGA168 - Uhr


von Henry (Gast)


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...

von Grrrr (Gast)


Lesenswert?

Henry schrieb:
> Hat jemand einen Vorschlag?

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

von Grrrr (Gast)


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.

von Einer (Gast)


Lesenswert?

Wenn du deinen Code zeigst bekommst du mehr Hilfe.

Interner Takt ist eh zu Ungenau.

von Grrrr (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


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)

von Henry (Gast)


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.

von Grrrr (Gast)


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.

von Grrrr (Gast)


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.

von Karl H. (kbuchegg)


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.

von Henry (Gast)


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?

von Karl H. (kbuchegg)


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
1
ISR(TIMER0_OVF_vect)
2
{
3
  counter++;
4
5
  if (counter == 30)
6
  {
7
    counter = 0;
8
    sec++;
9
    if( sec == 60 )
10
    {
11
      sec = 0;
12
      min++;
13
      if( min == 60 )
14
      {
15
        min = 0;
16
        hrs++;
17
        if( hrs == 24 )
18
          hrs = 0;
19
      }
20
    }
21
  }
22
}

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.