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++){ }}}
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.
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++); }}
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.
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 | }
|
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); }}} }}
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... ;-) ...
Mir geht es hierbei mehr um das Verständnis und nicht um die optimale Lösung eine eigene Uhr zu bauen.
> 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
> 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
}
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. ...
> 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... ;-) ...
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++; } }
> 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" ...
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); }
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.
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.