Forum: Mikrocontroller und Digitale Elektronik for schleife als Timer


von Joachim (Gast)


Lesenswert?

Hallo,

ich hätte da mal eine Verständnisfrage:

wenn ich meinen Controller mit 4 MHz getaktet habe, könnte ich doch 
normalerweise eine Funktion "sekunde" aufstellen, in der ich eine for 
Schleife einfach hochzählen lasse, und bei einem beliebigen Wert 
abbreche.

Nachdem ein ADD Befehl einen Takt benötigt, müsste ich dann mal schnell 
bis 4 Millionen hochzählen lassen, um eine Sekunde zu generieren.

Ich habe das ausprobiert und gegen meinen Erwartungen muss ich 
allerdings bis 28,5 Millionen hochzählen lassen. Nachdem ich da mit C 
mache, hätte ich erwartet, dass ich eher mehr als 4 Millionen Takte für 
eine Sekunde brauche, da die for Schleife da siche auch was "raubt".

Wäre echt dankbar für antworten.

void sekunde()
{
uint16_t j;
uint16_t i;
for (j=0;j<500;j++){

for (i=0;i<57000;i++){
}}}

von Karl H. (kbuchegg)


Lesenswert?

Was mich eher wundert: Dass du da überhaupt einen
Zeitverzug messen kannst :-)

Wahrscheinlich hast du den Optimizer nicht eingeschaltet.
Der optimiert dir die komplette Schleifenstruktur weg,
da sie ja offensichtlich keinen Nutzen hat ausser Zeit zu
verbraten. Und Compiler sind darauf gedrillt möglichst
Rechenzeit einzusparen.

Aber im Grundsatz ist dein Gedankengang schon richtig.

Um rauszufinden warum du bis 28.5 Millionen zählen
musst, müsste man sich jetzt mal das Assembler Listing
des vom Compiler generierten Codes ansehen. Wahrscheinlich
hat da irgendeine Teiloptimierung zugeschlagen.

von Joachim (Gast)


Lesenswert?

Sowas in der Art habe ich mir gedacht. Wenn ich z.B den Code ein wenig 
abändere, dann überspringt er mir gleich die 2. for Schleife:

void sekunde()
{
uint16_t j;
uint16_t i;
for (j=0;j<500;j++){

for (i=0;i<57000;i++);

}}

von Dirk (Gast)


Lesenswert?

Sowie Karl Heinz meinte wird sicherlich eine Teiloptimierung 
vorgenommen. Deine beiden Variablen solltest du mit einem volatile 
versehen um eine Optimierung auszuschliessen.

Ich wuerde die Makrofunktion delay_ms benutzen z.B. so:

#include delay.h

void waits(u8 waittime)
{
  volatile u8 waittime=0;
  for(waittime=0;waitime<1000;waittime++){
   delay_ms(1);
  }
}


Die Funktion macht nix anderes als die Rechenzeit zuverschwenden.



von Dirk (Gast)


Lesenswert?

Hm, die Uhrzeit macht einen Betriebsblind eher so:

1
#include delay.h
2
3
void waits(u8 waittime)
4
{
5
  volatile u8 i=0;
6
  for(i=0;i<waittime;i++){
7
   delay_ms(1);
8
  }
9
}



von Joachim (Gast)


Lesenswert?

Ok, danke...so funktioniert die Uhr ganz gut, eine Frage hätte ich 
allerdings noch bezüglich der Anzeige:

Immer wenn die Zahlen für std,min oder sec nur einzahlig sind, dann 
stellt das Display mir keine 0 dar, obwohl ich bei sprintf den Parameter 
"%2d" reingeschtieben habe.

Hier mal der ganze code:

#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <lcd.h>
#include <lcd.c>
#include <util/delay.h>

uint8_t a=23;
uint8_t b=19;
uint8_t c=50;
int sec=0;
int min;
int hr;
char s[4];
char m[4];
char h[4];


void waits(uint16_t waittime)
{
  volatile uint16_t i=0;
  for(i=0;i<waittime;i++){
   _delay_ms(1);
  }
}


int main(void)
{
lcd_init(LCD_DISP_ON);


while (1){

for (hr=a;hr<24;hr++){
for (min=b;min<60;min++){
for (sec=c;sec<60;sec++)
{
a=0;b=0;c=0;
waits(1000);
sprintf(h,"%2d",hr);sprintf(m,"%2d",min);sprintf(s,"%2d",sec);
lcd_home();
lcd_puts(h);lcd_puts(":");lcd_puts(m);lcd_puts(":");lcd_puts(s);

}}}
}}

von Hannes L. (hannes)


Lesenswert?

Es ist schon eine Schande, dass die Controller keine internen Timer 
haben, die nur darauf warten würden, Interrupts auslösen zu dürfen, in 
denen man taktgenau die Zeit hochzählen könnte...

;-)

...

von Joachim (Gast)


Lesenswert?

Mir geht es hierbei mehr um das Verständnis und nicht um die optimale 
Lösung eine eigene Uhr zu bauen.

von Karl H. (kbuchegg)


Lesenswert?

> obwohl ich bei sprintf den Parameter "%2d"

%2d heist nur, dass du die Zahl in einem Feld der
Breite 2 dargestellt haben willst. Wenn du noch
führende Nullen haben willst, musst du die anfordern

  %02d

von Karl H. (kbuchegg)


Lesenswert?

> sprintf(h,"%2d",hr);sprintf(m,"%2d",min);sprintf(s,"%2d",sec);

Warum so kompliziert mit 3 mal sprintf.
Einer tuts auch:

  sprintf( buffer, "%02d:%02d:%02d", hr, min, sec );
  lcd_home();
  lcd_puts( buffer );


(buffer natürlich entsprechend gross dimensioniern)

Die Sache mit dem Timer ignoriere ich mal und nehme
das einfach mal als Übungsaufgabe:

Deine Uhr hat einen Nachteil:
Ist es mit diesem Code nur schwer möglich, sie von aussen
zu stellen.
Kannst du das Ganze auch so machen, dass zu einer
jetzigen Uhrzeit (ausgedrückt in 3 Variablen: Stunden, Minuten,
Sekunden) genau 1 Sekunde addiert wird und sich dabei Stunden
und Minuten richtig verhalten?

Anstatt 3 ineinandergeschachtelter Schleifen hast du nur mehr eine

  while( 1 ) {
    1 Sekunde zu Stunden/Minuten/Sekunden addieren
    Uhrzeit ausgeben
    1 Sekunde warten
  }



von Hannes L. (hannes)


Lesenswert?

Joachim wrote:
> Mir geht es hierbei mehr um das Verständnis und nicht um die optimale
> Lösung eine eigene Uhr zu bauen.

Das ist gut.

Dann fang mal damit an, zu verstehen, dass Dein restlicher Code, 
besonders die LCD-Ausgabe auch Rechenzeit braucht, also viele Takte 
dauert. Diese musst Du ermitteln und von Deiner Warteschleife abziehen. 
Und bei jeder Programmänderung müsstest Du das wieder neu durchrechnen.

Da halte ich es für einfacher, zu verstehen, dass ein Timer (evtl. mit 
Vorteiler) die Takte bis zum nächsten "Termin" im Hintergrund zählen 
kann, während sich das Hauptprogramm um andere Dinge wie LCD kümmern 
kann.

"Verstehen" kannst Du den Mikrocontroller (den kleinen Mikrocontroller, 
der ohne Betriebssystem läuft) sowiso nur, wenn Du Dich auf sie 
ASM-Ebene herab lässt. Denn der Controller kann kein C, auch kein BASIC, 
er kann nur Maschinencode, der 1:1 in ASM ausgedrückt und beschrieben 
werden kann.

Um nicht wieder darauf festgenagelt zu werden, diese Aussage gilt nicht 
für PCs oder Hochleistungscontroller mit Betriebssystemen.

...

von Hannes L. (hannes)


Lesenswert?

> Die Sache mit dem Timer ignoriere ich mal und nehme
> das einfach mal als Übungsaufgabe:

Sorry, ich hatte nicht mitbekommen, dass es hier um des Verständnis von 
elementarem C geht, ich nahm fälschlicherweise an, es ginge um das 
Verständnis eines 8-Bit-Mikrocontrollers und vermutete AVR.

Daher müsste ich jetzt meinen vorherigen Beitrag zurücknehmen und das 
Gegenteil behaupten. Mir ist aber eigentlich nicht danach... ;-)

...

von Joachim (Gast)


Lesenswert?

Ok, ich verstehe. Jetzt kann ich über externe Interrupts (Taster) die 
Uhrzeit vorgeben. Mit 2 Tastern wäre das nicht schwer. Da müsste ich nur 
jeweils den Minuten und Stunden den Befehl ++ verpassen. Irgend einen 
Vorschlag, wie ich mit nur einem Taster auskommen könnte ?

#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <lcd.h>
#include <lcd.c>
#include <util/delay.h>

int sec;
int min=46;
int hr=0;
char buffer[16];

void waits(uint16_t waittime)
{
  volatile uint16_t i=0;
  for(i=0;i<waittime;i++){
   _delay_ms(1);
  }
}

int main(void)
{
lcd_init(LCD_DISP_ON);

while (1){
if(sec==60) {sec=0;min++;}
if(min==60) {min=0;hr++;}
if(hr==24) {hr=0;}
sprintf(buffer,"%02d:%02d:%02d",hr,min,sec);
lcd_home();
lcd_puts(buffer);
waits(1000);
sec++;
}
}

von Hannes L. (hannes)


Lesenswert?

> Jetzt kann ich über externe Interrupts (Taster) die
> Uhrzeit vorgeben.

Es gibt gute Gründe, Taster nicht mit externem Interrupt abzufragen. Mit 
einem Timer-Interrupt (der nebenbei noch andere Dinge erledigen kann) 
geht das besser, da mechanische Taster noch entprellt werden müssen.

Beitrag "Tasten entprellen - Bulletproof"

...

von Joachim (Gast)


Lesenswert?

Hallo,

ich beschäftige mich jetzt mit den Interrupts. Im Tutorial habe ich 
gelesen, dass jeder Interrupt eine Routine besitzt, die dann 
nacheinander abgearbeitet werden. Wo finde ich diese Routine beim AVR 
mega8 ?

Bei folgendem Programmcode habe ich das Problem, dass sich die Uhr nach 
einigen Minuten aufhängt. Woran könnte das liegen ? Der Code ist 
einigermaßen dokumentiert, allerdings sicher nicht schwer verständlich.

#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <lcd.h>
#include <lcd.c>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <entprell.c>

int sec;
int min=11;
int hr=23;
char buffer[16];


ISR (TIMER0_OVF_vect){    //Einstellmöglichkeit der  Minuten über PD2
                                 //PD2 wird 15 mal pro sec abgefragt
if(debounce(&PIND, PD2)){
min++;

if(sec>59) {sec=0;min++;}
if(min>59) {min=0;hr++;}
if(hr>23) {hr=0;}
sprintf(buffer,"%02d:%02d:%02d",hr,min,sec);
lcd_home();
lcd_puts(buffer); }
sei();
}


ISR(TIMER1_COMPA_vect)  //hier wird der Zahlenwert der abgearbeitet
{

TCNT1=0;                   //Compare Register löschen
if(sec>59) {sec=0;min++;}
if(min>59) {min=0;hr++;}
if(hr>23) {hr=0;}
sprintf(buffer,"%02d:%02d:%02d",hr,min,sec);
lcd_home();
lcd_puts(buffer);
sec++;

}

int main(void)    //Initialisierung der Timer0(clk/1024)
                  //und Timer1(clk/256)

{
DDRD=0x00;

TCCR1B|=(1<<CS12);TIMSK|=(1<<OCIE1A);
TCCR0|=(1<<CS00)|(1<<CS02);
TIMSK|=(1<<TOIE0);
lcd_init(LCD_DISP_ON);
OCR1A=15625;                    //Compare, damit Timer 1 im Sekundentakt
sei();                     //aufgerufen wird

while(1);
}

von Karl H. (kbuchegg)


Lesenswert?

Warum die Uhr konkret hängt, kann ich im Moment nicht sagen.
Ich denke aber es hängt damit zusammen, dass die eine
Grundregel bei der Arbeit mit Interrupts ausser acht
gelassen hast:
Interrupt Routinen sollen kurz sein! D.h. du sollst in
einer Interrupt Routine nur das notwendigste machen. Nicht
mehr. Grundsätzlich möchte man so schnell wie möglich aus
dem Interrupt wieder raus. Während ein Interrupt abgearbeitet
wird, sind alle weiteren Interrupts gesperrt. Wenn da also
in der Zwischenzeit andere Interrupts auflaufen, kann es sein
dass einer verloren geht. Und das möchte man auf keinen Fall.

Was ist in deinen Interrupt Routinen absolut nicht notwendig?
Da ist ganz sicher das Ausgeben auf LCD. Das muss nicht im
Interrupt passieren.

Die Grundstruktur, die sich bewährt hat, sieht so aus:
Im Hauptprogramm main() läuft die Hauptschleife. In der
Hauptschleife werden die Aktionen angeordnet und mit
einer zusätzlichen Variable, einem 'Flag' (engl. für Flagge,
Fahne) wird ausgewählt, ob der Programmteil betreten werden
soll oder nicht.
Die Interrupt Funktion braucht dann nur noch das Flag setzen,
um die Hauptschleife dazu zu veranlassen, diesen Programmteil
auszuführen.

Das würde zb. so aussehen:


volatile uint8_t UpdateDisplay;
volatile uint8_t sec;
volatile uint8_t min;
volatile uint8_t hr;

ISR(TIMER1_COMPA_vect)  //hier wird der Zahlenwert der abgearbeitet
{
  TCNT1 = 0;                   //Compare Register löschen

  sec++;

  if( sec > 59 ) {
    sec = 0;
    min++;
  }

  if( min > 59 ) {
    min = 0;
    hr++;
  }

  if( hr > 23 ) {
    hr = 0;
  }

  //
  // von der Hauptschleife eine Neuausgabe der Variablen auf
  // das Display anfordern. Dazu einfach das Flag UpdateDisplay
  // auf 1 setzen. Wenn die Hauptschleife im nächsten Durchlauf
  // dann das Flag abfragt, erkennt sie die 1. führt die
  // Ausgabe aus und setzt das Flag wieder auf 0 zurück.
  //
  UpdateDisplay = 1;
}

int main()
{
  char buffer[20];

  ...

  UpdateDisplay = 0;

  while( 1 ) {
    //
    // wurde von einer ISR ein Update für das Display
    // angefordert?
    if( UpdateDisplay == 1 ) {

      // Ja. Wurde es. Also machen wir das mal
      sprintf( buffer, "%02d:%02d:%02d", hr, min, sec );
      lcd_home();
      lcd_puts( buffer );

      // Auftrag ausgeführt. Das Flag wieder zurücksetzen
      UpdateDisplay = 0;
    }
  }
}

Achte darauf, wie ich die Variable UpdateDisplay einsetze.

Noch was.
Deine Programme werden jetzt schon immer umfangreicher.
Umso wichtiger ist es, dass du anfängst einen leserlichen
Stil zu entwickeln. Alles in einer Wurscht herunterzuschreiben
ist kein Stil. Einrückungen und die Verwendung von Leerzeichen
können den Unterschied zwischen einem unleserlichem und einem
gut lesbarem, wartbarem Programm ausmachen. Ich sage nicht,
dass mein Stil der einzig selig machende ist, aber schau
dir trotzdem an, wo ich Leerzeichen setzte, wie ich einrücke.
Die Regel: Eine Zeile, eine Anweisung hat sich in der Praxis
als recht brauchbar herausgestellt.

> Im Tutorial habe ich gelesen, dass jeder Interrupt eine Routine
> besitzt, die dann nacheinander abgearbeitet werden. Wo finde ich
> diese Routine beim AVR mega8 ?

Das hast du misverstanden. Du bist es der diese Routine schreibt.
Wenn du wissen willst welche Interrupts und damit welche
Interrupt Handler möglich sind und unter welchen Umständen sie
aufgerufen werden, dann lautet die Antwort wie so oft: Im Datenblatt
des Prozessors steht das alles drinnen.


In deinem Pgm sind noch ein paar andere Dinge über die man
reden müsste. Ich belasse es im Moment aber dabei.

Baue jetzt erst mal die andere ISR soweit um, dass sie ebenfalls
einen Update über das Flag von der Hauptschleife anfordert.
Mal sehen, ob sich damit dein Problem soweit lösen läst.

von Joachim (Gast)


Lesenswert?

Erst mal danke für deine Bemühungen !

> Im Tutorial habe ich gelesen, dass jeder Interrupt eine Routine
> besitzt, die dann nacheinander abgearbeitet werden. Wo finde ich
> diese Routine beim AVR mega8 ?

Ich habe das falsch ausgedückt:

Statt Routine habe ich Priorität gemeint, also Rangordnung.

Ganz einfach. Es gibt keine.
Wenn mehrere Interrupts wirklich gleichzeitig auftreten
gilt die Reihenfolge in der Interrupt Vektor Tabelle.

von Joachim (Gast)


Lesenswert?

So...habe jetzt mal versucht, nicht alles nur blind zu übernehmen (auch 
wenns vielleicht so aussieht). Bis jetzt läuft die Uhr und hängt sich 
nicht auf.

#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <lcd.h>
#include <lcd.c>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <entprell.c>

volatile uint8_t UpdateDisplay;
volatile uint8_t sec;
volatile uint8_t min;
volatile uint8_t hr;

void init(void)
{
  DDRD=0x00;

  TCCR1B|=(1<<CS12);TIMSK|=(1<<OCIE1A);
  TCCR0|=(1<<CS00)|(1<<CS02);TIMSK|=(1<<TOIE0);

  lcd_init(LCD_DISP_ON);

  OCR1A=15625;

}

ISR (TIMER0_OVF_vect)
{

if(debounce(&PIND, PD2)){

  min++;

  UpdateDisplay = 1;

  }
}

ISR (TIMER1_COMPA_vect)
{
  TCNT1 = 0;                   //Compare Register löschen

  sec++;

  if( sec > 59 ) {
    sec = 0;
    min++;
  }

  if( min > 59 ) {
    min = 0;
    hr++;
  }

  if( hr > 23 ) {
    hr = 0;
  }

  UpdateDisplay = 1;

}


int main(void)
{
  char buffer[16];

  init();

  sei();

  while(1){

    if (UpdateDisplay==1){

      sprintf(buffer,"%02d:%02d:%02d",hr,min,sec);
      lcd_home();
      lcd_puts(buffer);

      UpdateDisplay = 0;

    }
  }

}

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.