Hallo, bin µC Neuling. Ich möchte, dass beim Überlauf des Timer 0 etwas bestimmtes passiert. Hier der riesengroße bisherige Code: main () // Hauptprogramm, startet bei Power ON und Reset { TCCR1A=(1<<CS00)|(1<<CS00); // Timer 0: Clock/1024 TIMSK=(1<<TOIE0) // Timer 0: Interrupt bei Überlauf while(1) { } } Im GCC Tutorial steht zu TOIE0: "Das Global Enable Interrupt Flag muss selbstverständlich auch gesetzt sein. " Ist der gesetzt? Welches Register ist das? Und die mainfrage: Wie schreibe ich jetzt eine Funktion was beim Überlauf passieren soll? Vielen Dank.
Hallo Thomas, schau mal hier rein, da wird Dir geholfen. http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Die_Timer.2FCounter_des_AVR Gruß Frank
Hi, da das nicht direkt in deinem Post steht treffe ich folgende Annahmen: - du verwendest den WinAVR Comiler - du nutzt einen Atmel AVR µC Das globale Interrupt Flag I befindet sich im Statusregister (SREG). Das kannst du setzen mit sei() (alle Int. erlauben) und löschen mit cli() (alle Int. sperren). Die Interruptfunktion dann wie folgt:
1 | ISR(TIMER0_OVF_vect){ |
2 | dein Code; |
3 | }
|
Zu deinem Code:
1 | main () // Hauptprogramm, startet bei Power ON und Reset |
2 | {
|
3 | TCCR1A=(1<<CS00)|(1<<CS00); // Timer 0: Clock/1024 |
4 | TIMSK=(1<<TOIE0) // Timer 0: Interrupt bei Überlauf |
5 | while(1) |
6 | {
|
7 | |
8 | }
|
9 | }
|
Du beschreibst wenn ich das richtig sehe das Controlregister A für Timer 1 willst aber den Timer 0 verwenden. Solltest also TCCR0 verwenden. Und wenn du den Timer 1 verwenden willst stehen die Clock Select Bits (CS02 ..CS00) nicht im TCCR1A sondern im TCCR1B (zumindest beim ATmega8). Dazu kommt das du zweimal das selbe CS bit setzt, ansich nicht schlimm nur dein Vorteiler ist dann nicht so eingestellt wie du möglicherweise vorhattest. Gruß Robert @Frank: Ich glaube da war er schon, findet nur vor lauter Informationen nicht das richtige :)
> Ich glaube da war er schon ... Ich glaube nicht, die Infos stehen da ab Kapitel 18 und speziell ab 18.5 http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmieren_mit_Interrupts
Ich steuere mit dem ATMega8 eine 4-fach 7 Segmentanzeige an die multigeplext wird. Die globale Variable "number" wird auf der Anzeige ausgegeben, das funktioniert soweit. Jetzt möchte ich noch den 16 Bit Timer dazunehmen. Er soll number jede Sekunde um 1 erhöhen. Komischerweise kennt der Compiler OCR1H und OCR1L nicht. Fehlermeldung: main.c:69: error: 'OCR1H' undeclared (first use in this function) main.c:69: error: (Each undeclared identifier is reported only once main.c:69: error: for each function it appears in.) main.c:70: error: 'OCR1L' undeclared (first use in this function) Woran liegt das? Hier der Code: #include <avr/io.h> #include <avr/interrupt.h> //---------------------------------------------------------------------- void showDigit(unsigned int digit); //---------------------------------------------------------------------- unsigned int number=1234; // number wird ständig angezeit unsigned int digitNo[4]; // Die einzelnen Ziffern von number //---------------------------------------------------------------------- ISR(TIMER0_OVF_vect){ // Multiplexing: Ansteuern der nächsten 7 Segment Anzeite if((PINB&0b00000001)==0b00000001) { PORTD=0b01111111; // Ausschalten der 7 Segment Anzeige PORTB=0b00000010; // Aktivieren der nächsten Ziffer showDigit(digitNo[1]); // Anschalten der 7 Segment Anzeige } else if((PINB&0b00000010)==0b00000010) { PORTD=0b01111111; PORTB=0b00000100; showDigit(digitNo[2]); } else if((PINB&0b00000100)==0b00000100) { PORTD=0b01111111; PORTB=0b00001000; showDigit(digitNo[3]); } else { PORTD=0b01111111; PORTB=0b00000001; showDigit(digitNo[0]); } } ISR(TIMER1_OVF_vect){ // 1 Sekunde ist vergangen number++; /* digits berechnen */ digitNo[0]=number%10; digitNo[1]=(number/10)%10; digitNo[2]=(number/100)%10; digitNo[3]=(number/1000); } int main (void) // Hauptprogramm, startet bei Power ON und Reset { DDRB=0b00001111; // Ziffern 0 bis 3 DDRD=0b01111111; // 7 Segmente /* digits berechnen */ digitNo[0]=number%10; digitNo[1]=(number/10)%10; digitNo[2]=(number/100)%10; digitNo[3]=(number/1000); sei(); // Interrupts aktivieren TCCR0=(1<<CS01); // Timer 0: Clock/8 TIMSK=(1<<TOIE0); // Timer 0: Interrupt bei Überlauf TCCR1B=(1<<CS11)|(1<<CS10)|(1<<CTC1); // Timer 1: Clock/64, Löschen von TCNT1L/H bei erreichen von OCR1L/H OCR1H=15625/256; // <= FEHLER!!!!!!!!!!! OCR1L=15625%256; // <= FEHLER!!!!!!!!!!! while(1) { } return 0; } //---------------------------------------------------------------------- void showDigit(unsigned int digit) { switch(digit) { case 0: { PORTD=0b01000000; break; } case 1: { PORTD=0b01111001; break; } case 2: { PORTD=0b00100100; break; } case 3: { PORTD=0b00110000; break; } case 4: { PORTD=0b00011001; break; } case 5: { PORTD=0b00010010; break; } case 6: { PORTD=0b00000010; break; } case 7: { PORTD=0b01111000; break; } case 8: { PORTD=0b00000000; break; } case 9: { PORTD=0b00010000; break; } } } Schonmal vielen dank für Hilfe!
Klar kennt der Compiler die nicht. Schau bitte ins Datenblatt. Da steht, wie die Register heißen! Außerdem kann man so langen Code besser als Anhang posten.
Leider will der 16 Bit Timer immernoch nicht. Hier ist der Quellcode zum testen des Timers. Jede Sekunde soll B0 von 0 auf 1 bzw 1 auf 0 spring. #include <avr/io.h> #include <avr/interrupt.h> ISR(TIMER1_COMPA_vect){ // 1 Sekunde ist vergangen TCNT1L=0; TCNT1H=0; if((PINB&0)==0) PORTB=0b00000001; else PORTB=0b00000000; } int main (void) // Hauptprogramm, startet bei Power ON und Reset { DDRB=0b00001111; // Ziffern 0 bis 3 DDRD=0b01111111; // 7 Segmente PORTD=0b00001111; TCCR1B=(1<<CS11)|(1<<CS10); // Timer 1: Clock/64 OCR1AH=15625/256; // OCR1A = 15625 OCR1AL=15625%256; TIFR=(1<<OCF1A); // Output Compare Flag Timer1 A aktiviert while(1) { } return 0; }
Stimmt. Funktioniert aber leider trotzdem noch nicht. Ein kleiner Fehler im Interrupt Aufruf: if((PINB&0)==1) PORTB=0b00000001; muss es heißen. Hat damit aber nichts zu tun da er da nochnichtmal reingesprungen ist.
Thomas D wrote: > > Ich check mal ob ichs check... ein Blick ins Datenblatt oder ins Tut würde helfen...
So, nächstes Problem: Beide Timer laufen lassen. Timer 1 soll beim overflow resetten und Timer1 nach 1 Sekunde zu number einen hinzufügen. Die main sieht so aus: int main (void) // Hauptprogramm, startet bei Power ON und Reset { DDRB=0b00001111; // Ziffern 0 bis 3 DDRD=0b01111111; // 7 Segmente /* digits berechnen */ digitNo[0]=number%10; digitNo[1]=(number/10)%10; digitNo[2]=(number/100)%10; digitNo[3]=(number/1000); sei(); // Interrupts aktivieren TCCR0=(1<<CS01); // Timer 0: Clock/8 TIMSK=(1<<TOIE0); // Timer 0: Interrupt bei Überlauf TCCR1B=(1<<CS11)|(1<<CS10); // Timer 1: Clock/64, Löschen von TCNT1L/H bei erreichen von OCR1L/H OCR1AH=15625/256; OCR1AL=15625%256; TIMSK=(1<<OCIE1A); while(1) { } return 0; } Es leuchtet nichts. Hab das Programm leicht umgeschrieben, sodass jede Sekunde etwas leuchten soll und es geht. In den TIMER1_COMPA_vect Interrupt spring er also rein. Das gleiche mit dem TIMER0_OVF_vect, da springt er nicht rein. Sobald ich die Zeile TIMSK=(1<<OCIE1A); rausnehme springt er auch in den TIMER0_OVF_vect. Wie kann das sein? Der Interrupt 1 funktioniert immer, Interrupt 0 nur wenn TIMSK=(1<<OCIE1A); nicht dabei ist.
In Deinem letzten Code (von 16:52) ist noch die Zugriffsreihenfolge bei TCNT1H/L falsch! Bitte lies im Tutorial den Abschnitt über 16-Bit-Register. Dein C-Compiler kann Dir die Arbeit der Einzelzugriffe nämlich abnehmen (das gilt auch für OCR1AH/L). Die Reihenfolge ist bei den Registern essentiell wichtig! Und einen Timer in einem Compare-Interrupt Handler zurückzusetzen ist unsinnig. Dafür gibt es die CTC-Betriebsart. Auch dazu gibt es im Tutorial bei der Timer-Beschreibung einen Abschnitt.
Thomas D wrote: > Wie kann das sein? Der Interrupt 1 funktioniert immer, Interrupt 0 nur > wenn TIMSK=(1<<OCIE1A); nicht dabei ist. Ganz einfach: Weil Du beim Aktivieren des Compare-Interrupt den anderen wieder löschst... Der Artikel zum Thema Bitmanipulation ist in dem Zusammenhang auch empfehlenswert.
Thomas D wrote:
> if((PINB&0)==1) PORTB=0b00000001; muss es heißen.
BTW: Die Bedingung wird nie wahr! Eine VerUNDung mit Null liefert
immer Null (false) zurück.
Jop, es funktioniert jetzt endlich! ;) Vielen Dank! Hab den CTC angemacht und aus dem TIMSK=(1<<OCIE1A); ein TIMSK|=(1<<OCIE1A); gemacht. Über die Reihenfolge der Register habe ich so schnell nichts gefunden aber es funktioniert auch mit der alten Reihenfolge. Vielen Dank nochmal. :)
"> if((PINB&0)==1) PORTB=0b00000001; muss es heißen. BTW: Die Bedingung wird nie wahr! Eine VerUNDung mit Null liefert immer Null (false) zurück." Stimmt. if((PINB&1)==1) PORTB=0b00000000; else PORTB=0b00000001; sollte es heißen.
Thomas D wrote: > Über die Reihenfolge der Register habe ich so schnell nichts gefunden > aber es funktioniert auch mit der alten Reihenfolge. http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#16-Bit_Register_.28ADC.2C_ICR1.2C_OCR1.2C_TCNT1.2C_UBRR.29 Dass es mit der "alten" Reihenfolge in diesem Fall vielleicht klappt, ist purer Zufall! > Stimmt. if((PINB&1)==1) PORTB=0b00000000; else PORTB=0b00000001; sollte > es heißen. Auch das ist so nicht richtig. Wenn Du den Portpin umschalten willst, dann solltest Du nicht PINB einlesen, sondern PORTB. PINB nur dann, wenn der Pin als Eingang konfiguriert ist und der von außen vorgegebene Zustand eingelesen werden soll. Außerdem lässt sich der ganze if-else-Kram da oben als eine einzige Anweisung schreiben:
1 | PORTB ^= 1; |
oder der besseren Lesbarkeit halber
1 | PORTB ^= PB1; |
BTW: Auch Du solltest die Formatierungsmöglichkeit für C-Code hier im Forum nutzen!
Alles klar. Danke für die Hilfe. Lesen: Erst Low, dann High Schreiben: High, dann Low PIND==... einlesen der Eingänge PORTD==... einlesen der Ausgänge PORTD=... schreiben der Ausgänge / (de)aktivieren der Pullup Widerstände der Eingänge PIND=... gibts nicht? ... trotzdem hatte es ja komischerweise funktioniert. Frage: ^= ist die invertierung der Bits? Dann würde das hier ja nicht ganz stimmen: PORTB ^= 1; Ne, kann nicht sein. Was ist ^= ? Sehe es zum ersten mal.
Zum 3ten und diesmal mit Vorschau.
1 | int main(void) { |
2 | return 0; |
3 | }
|
Okay danke.
Thomas D wrote: > PIND=... gibts nicht? Bei neueren AVRs (z.B. ATMega48/88/168) gibt es auch das. Das toggelt dann den Pin.
Toggeln heißt invertieren...? Wenn ich dich richtig verstehe invertiert es die Ausgangspins? Also DDRD=0b00001111; PORTD=0b00001111; PIND=0b00001111; <- setzt in diesem Fall D0...D3 auf 0?
Schau doch einfach mal ins Datenblatt, da sind die I/O-Register sehr genau beschrieben. ...
Jo, habs gefunden. PINx sind auch bei ATmega48/88/168 nur Leseregister.
Thomas D wrote: > PINx sind auch bei ATmega48/88/168 nur Leseregister. Hatte mich vertan. Die Mega48/... waren mit die letzten AVRs, die das noch nicht hatten...
So die nächste Aufgabe ist den Reset auszuschalten, sodass ich den PC6 benutzen kann. Im Datenblatthabe ich gefunden: "If the RSTDISBL Fuse is programmed, PC6 is used as an I/O pin." Nur leider weiß ich nicht in welchem Register ich den RSTDISBL programmieren kann. Auf Seite 62 Tabelle 26 findet sich dann: "Overriding Signals for Alternate Functions in PC6..PC4" Unter der Spalte Reset gibt es die Möglichkeit "RSTDISBL", "0" oder "1" einzustellen. Da RSTDISBL invertierte Logik ist würde ich sagen muss da eine "0" rein. Unter PC4 und PC5 muss es denke ich auch auf "0" somit ist "Signal Name" = "PVOV" Jetzt weiß ich leider nicht wie ich einprogrammiere, dass diese Einstellung übernommen werden soll.
Thomas D wrote: > Nur leider weiß ich nicht in welchem Register ich den RSTDISBL > programmieren kann. RSTDISBL ist ein Fusebit und das muss beim Programmieren des Controllers separat gesetzt werden. Das geht nicht durch das Anwenderprogramm. Und wenn der Reset abgeschaltet ist, ist der AVR nicht mehr per ISP programmierbar, nur noch über HV-Programmierung mit einem speziellen Programmer (z.B. STK500). Siehe AVR Fuses!
Übrigens wärst Du nicht der Erste, der sich durch unüberlegtes Spielen an den Fuses ausgesperrt hat, bevor er das erste sinnvolle Programm für (und in) seinen AVR geschrieben hat. Dieses Forum ist voller solcher Hilferufe... Wenn Du den AVR (und seine Architektur) wirklich kennen lernen und verstehen willst, dann mache Deine ersten Schritte in Assembler, denn das ist real, da gilt (nur) das Datenblatt und der Instruktionssatz. Wenn Du das halbwegs verstanden hast, fällt es Dir in einer Hochsprache bedeutend leichter. ...
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.