Hallo, wenn ich meinen AVR in den sleep-mode schicke und ihn per ext. Interrupt aufwecken will (LOW-Pegel) habe ich folgendes Problem. Die ISR wird ausgeführt, und wenn die Taste nach ausführung der ISR immernoch gedrückt ist, wird die ISR nochmal ausgeführt (bis ich die Taste loslasse) -> wie kann ich dies unterbinden, denn die ISR soll nur einmal ausgeführt werden. Warum kann ich keine Funktionsaufrufe in meine ISR reintun, wie z.B. senden über uart-> wird nur Müll gesendet Danke schon mal für eure Hilfe.
Abhängig von der Einstellung koennen externe Interrupts so lang "triggert", wie die "Ausloesebedingung" erfuellt ist. Abhilfe: Interrupt in der ISR deaktivieren und im Hauptprogramm erst dann wieder aktiveren, wenn "Taste losgelassen", danach "sleep".
>Warum kann ich keine Funktionsaufrufe in meine ISR reintun, wie z.B. >senden über uart-> wird nur Müll gesendet Das würde ich auch gerne mal wissen. Habe das nämlich auch gerade festgestellt.
Man kann sehr wohl Unterfunktionen in Interrupts aufrufen, man muß bloß ungefähr wissen, wie lange diese Funktionen dauern. UART-Funktionen gehören leider zu denen, die sehr sehr lange dauern. Wenn Du also viele tausende Zyklen im Interrupt verbrätst, verhungert Dir derweil Dein Mainprogramm und sämtliche anderen Interrupts. Du darfst aber keine Funktionen aufrufen, die selber Interrupts verwenden. Das gibt dann einen Deadlock (einer blockiert den anderen und wartet dann auf den anderen), da ja in einem Interrupthandler alle anderen Interrupts gesperrt sind. Es gibt da zwar Tricks, aber die sollte nur der anwenden, der wirklich genau weiß, was er tut. Weitere gerne benutzte Performancekiller in Interrupts sind: LCD-Ausgaben, Delay-Schleifen >100µs, float-Rechnungen und printf(). Wobei natürlich gilt, Ausnahmen bestätigen die Regel. Peter
Hallo Peter, danke für Deine Antwort (natürlich auch an die anderen). Wie kann ich es erreichen, dass mein AVR (tiny2313) die ISR nur einmal ausführt?
Ich habe auch eine Frage zu den Unterfunktionen in den Interruptroutinen. Wie kann ich meinem µC dann beibringen, per Uart etwas zu senden, wenn der Interrupt ausgelöst wurde. Z.B. wenn ich einen Timer verwende und dadurch das Overflow Interrupt ausgelöst wird und ich dann etwas per Uart verschicken möchte. Ich möchte quasi eine Zeit hochzählen und jede Millisekunde diese Zeit per Uart verschicken. Kann man im Main-Programm irgendwie sagen, dass: Wenn Interrupt ausgelöst, schicke die Zeit per Uart. Also in C-Syntax: if (Signal Overflow(oder sonstwas)) { uart_puts(blabla) } Wisst ihr wie ich es meine? Wäre für jede Hilfe dankbar...denke da jetzt schon ne ganze Weile drüber nach und habe noch keine richtige Lösung gefunden.
> Ich möchte quasi eine Zeit hochzählen und jede Millisekunde > diese Zeit per Uart verschicken. Sicher kannst Du das. Deine main() ist dann im Grunde nichts anderes als eine Endlosschleife. Dort benutzt Du dann ein Flag (globale Variable). Ist diese gesetzt worden, dann verschickst Du die Zeit per UART und setzt das Flag zurueck. Die Timer-Interrupt Funktion hat dann die Aufgabe, in bestimmten Zeiteinheiten genau dieses Flag zu setzen.
> Ich möchte quasi eine Zeit hochzählen und jede Millisekunde diese > Zeit per Uart verschicken. Dann sollte deine UART aber recht schnell sein. Faustregel: bei 9600 Bd braucht ein Zeichen bereits ca. 1 ms.
Schonmal danke für eure Antworten. Hab das jetzt auch hinbekommen, d.h. ich kann senden wann immer ich möchte(je nach Timereinstellung). Nun habe ich eine andere Frage. Wie Jörg ja schon anmerkte, wird es wohl schwierig jede ms die Zeit zu übermitteln (hat auch nichts funktioniert ;)). Jetzt habe ich folgendes probiert: Ich lasse mein Uart nur jede Sekunde die Zeit übermitteln, das funktioniert auch soweit, nur das es ein Delay gibt(Jede Minute fehlen ca. 2 Sekunden). Ich benutze die fdevopen und printf Funktionen um dies zu realisieren und denke mir, dass diese Funktionen zu viel Performance schlucken bzw. einfach nicht schnell genug sind. Liege ich mit meiner Vermutung richtig? Wenn ja, was könnte ich denn sonst machen um den Timestamp per Uart zu versenden?
Da du die Zeit in der ISR hochzählst, sollte es egal sein, wie lange das Ausgeben der Zeit anschließend dauert, solange du kein `roll over' hast, also die Ausgabe nicht summa summarum langsamer als das Weiterschalten ist.
Hmm...aber woran liegt es dann? Und wie kann ich genau feststellen, dass meine Ausgabe nicht langsamer als das weiterschalten ist? Ich habe den Timer jetzt 2 Stunden laufen lassen und er geht jetzt ca. 5 Minuten nach. Gibt es ne gute einfache Möglichkeit um zu überprüfen, ob der Timer seinen Job richtig macht? Ich lasse im Moment nebenher noch eine LED im Takt des Timers (also 1 s) blinken und da hat sich nach 2 Stunden noch nichts sichtbares bemerkbar gemacht...
> Und wie kann ich genau feststellen, dass meine Ausgabe nicht > langsamer als das weiterschalten ist? Also wenn du sie nur noch einmal pro Sekunde machst, ist sie wohl eher nicht zu langsam. > Ich habe den Timer jetzt 2 Stunden laufen lassen und er geht jetzt > ca. 5 Minuten nach. Wie taktest du den Timer denn?
Ich take ihn folgendermaßen: Da ich F_CPU 4 MHz habe, teile ich den Takt durch 8. Daraus folgt eine Taktfrequenz von 500KHz für den Timer. Also ein Takt entspricht nun 0,000002 Sekunden. Um ihn also auf 1 ms einzustellen, muss ich den Timer bis 500 zählen lassen. um dies zu erreichen lasse ich ihn jeweils 2 mal bis 250 zählen. Dann ist doch meine 1ms korrekt oder? Hier mein C-Code dazu: void Timerinit(void) { TIMSK=0x01; //Timer/Counter Interrupt Mask TCNT0=0x05; //255-Registerinhalt:=250 TCCR0=0x02; //Timer/Counter Control Register auf CK/8 sei(); //All Interrupt enable } Das ist im Moment meine Interruptroutine für die eine Sekunde: SIGNAL(SIG_OVERFLOW0)/* signal handler for tcnt0 overflow interrupt */ { if (T==2000)//Wenn der timer 2 mal durchgelaufen ist,ist 1ms vergangen { timestamp++; if ( !(PINC & (1<<PINC5)) ) { PORTC=0xFF; } else { PORTC=0x00; } T=0; } else { T++; } } Hilft dir das weiter um mir bei meinem problem zu helfen? kann es sein, daß der Takt des µC nicht ganz stimmt(Ich nutze den internen).
Ich sehe gerade, daß bei if(T==2000) ein kleiner Fehler im Kommentar ist, da müsste jetzt stehen: Wenn er 2000 mal durchgelaufen ist, ist eine Sekunde vergangen ;)
Für einen genauen Zeitgeber nimmt man keinen Overflow- Interrupt, sondern lässt den Kanal im CTC-Modus (clear timer on compare match) laufen und nimmt den compare match Interrupt. Falls der Timer 0 in deinem AVR noch kein CTC kann, solltest du besser über einen anderen Kanal nachdenken.
Ich meinte eher, wo die 4MHz herkommen. Falls das der interne RC-Oszillator ist, brauchst du dich über so eine Ungenauigkeit nämlich gar nicht wundern.
Mir fällt aber grad noch was auf.
1 | if (T==2000)//Wenn der timer 2 mal durchgelaufen ist,ist 1ms vergangen |
2 | {
|
3 | timestamp++; |
4 | //...
|
5 | T=0; |
6 | }
|
7 | else
|
8 | {
|
9 | T++; |
10 | }
|
Der Code erhöht timestamp erst nach 2001 Timerdurchläufen. Mach es mal so rum:
1 | T++; |
2 | if (T==2000) |
3 | {
|
4 | timestamp++; |
5 | //...
|
6 | T=0; |
7 | }
|
OK danke für eure Hilfe, ich habe nun veruscht den CTC-Mode zu verwenden und bin (wie sicherlich klar war) auf einige Probleme gestoßen. Als erstes weiß ich nicht genau, ob ich den CTC-Mode richtig initialisiert habe und wollte euch deswegen fragen, ob mein Code dazu richtig ist. void Timerinit(void) { TIMSK=0x02; //Timer/Counter Interrupt Mask OCR0 =0xFA; //Output Compare Register = 250 TCCR0=0xA; //Timer/Counter Control Register auf CK/8 und CTC-Mode sei(); //All Interrupt enable } Nun noch eine Frage: Meine LED blinkt jetzt wieder im Sekunden-Takt(dekne ich zumindest) aber jetzt sagt mein UART nichts mehr. Wieso? Ich habe doch nur den Timermoder verändert, also wieso mag das Uart jetzt nicht mehr? Nächste Frage: Wie heißt die Interruptroutine für den CTC-Mode? Etwa so: SIGNAL(SIG_OUTPUT_COMPARE0) ??? Mich wundert halt, das die LED blinkt aber der Uart nicht mehr senden will :(...dieser CTC-Mode ist am Anfang doch ganz schön schwer zu begreifen. Habe da noch einige Fragen zu aber vielleicht klären mich ja die nächsten Beiträge von euch auf :)...schonmal vielen Danke. Gruß Marian
Ok jetzt bin ich ganz verwirrt!!! Ich hatte den Vorschlag von Rolf angenommen und mein T++ vor die if-Schleife gelegt(War beim Nachrechen auch ganz logisch). Jetzt hab ich das T++ wieder an die vorherige Stelle getan und siehe da: Der Uart sendet wieder...und anscheinend auch ein schöner 1 Sekundentakt. Also liebe Profis, woran liegt das??? Wieso will Uart nicht senden, wenn ich meine Variable vor der Schleife hochzähle? Gruß der verwirrte Marian
Weil sich aus dem was du bisher erzaehlt hast kein Zusammenhang zwischen der UART und deinem Timerinterrupt ergibt. Du musst also noch irgendwas anderes veraendert haben.
Nein ich habe nichts verändert außer dem Timer-Modus und die ISR. Wie schon geschrieben nur das T++ vor die if-schleife gelegt und sonst nur den CTC-Modus aktiviert. Deswegen bin ich ja auch so verwirrt :)
CTC-Modus ist sowas von trivial, irgendwas musst du komplett vergeigt haben. Aber sorry, die Schnipsel sind mir bisschen zu zusammenhanglos, als dass ich persönlich zu viel Zeit da rein investieren möchte. Schreib' mal die von dir gewünschte Funktionalität auf, und es wird sich sicher jemand finden, der dir dafür ein Programmgerippe entwirft. Das geht m. M. n. schneller.
Wieso sorry? Musst du doch auch nicht. Du hast mir doch eh schon so oft geholfen aber ich dachte halt, das meine Problemstellung genügt, damit ihr Profis damit was anfangen könnt. Also nochmal das was ich möchte: Grunddaten: Atmega32 L mit 4 MHz(intern) und WinAVR Programmiere über ISP (aber das ist auch nicht so wichtig). Ich wollte halt zum Timerverständniss mal versuchen, einen Timer zu schreiben, der mir jede Sekunde die aktuelle Zeit(seit Programmstart) per Uart verschickt. Diese Zeit soll in 8 Stellen Hex ausgegeben werden(und optional noch in dezimal). Dazu habe ich (mittlerweile) den CTC-Modus verwendet(wurde mir ja empfohlen). Das die "UHR" nachgeht, ist mir jetzt aufgrund des Frequenz/Temperaturverhaltens auch klar geworden. Also habe ich eigentlich nur noch folgendes Problem. Ich denke, das sich mein Programm (fast) selbsterklärt, weswegen ich es angehangen habe. Nun nochmal die Frage, die mich so verwirrt hat. Wenn ich in der ISR das T++ for die If-Schleife schreibe, will mein Uart nicht mehr senden! Habe es mehrfach probiert.
> Habe es mehrfach probiert. Ist halt kein Windows. :-)) Der Fehler ist nicht zufällig, sondern systematisch. > Wenn ich in der ISR das T++ for die If-Schleife schreibe, will mein > Uart nicht mehr senden! Ja klar. Jetzt, nachdem ich da nochmal draufgeguckt habe, wird mir auch klar, warum. Deine UART sendet ja nur, wenn im Hauptprogramm die Variable T mit dem Wert 2000 angetroffen wird. Wenn du das T++ in der ISR vorziehst, setzt die ISR aber beim Erreichen von 2000 sofort die Variable wieder auf 0 zurück, das Hauptprogramm sieht also nie mehr eine 2000. Hier noch wahllose Kommentare: > typedef unsigned long int uint32_t; Dafür bitte #include <stdint.h> nehmen. > TIMSK=0x02; //Timer/Counter Interrupt Mask > TCCR0=0xA; //Timer/Counter Control Register auf CK/8 und CTC-Mode Das schreibt man besser symbolisch: TIMSK = (1 << OCIE0); TCCR0 = (1 << WGM01) | (1 << CS01); > OCR0 =0xFA; //Output Compare Register = 250 Warum denn in Hex=? OCR0 = 250; oder besser gleich: #define F_CLOCK 2000 OCR0 = F_CPU / (8ul * F_CLOCK) (Das F_CLOCK kannst du dann für all die anderen magischen Zahlen 2000 wiederverwenden.) > PORTC=0x00; Überflüssig, ist default. > /*Set Frame format :8data, 1stop bit */ > UCSRC = (1<<URSEL) | (3<<UCSZ0) ; Auch überflüssig, 8N1 ist ebenfalls default (hat ja auch Sinn). > void uart_puts (char *s) Benutzt du gar nicht, du nimmst ja stdio. > if ( !(PINC & (1<<PINC5)) ) // Pinkontrolle ob sie auf "1" oder "0" ist Hat nicht recht Sinn. Du fragst den Eingangspegel an einem Pin ab, das als Ausgang geschaltet ist. Da kannst du gleich PORTC abfragen, es sei denn, du erwartest, dass draußen jemand einen Kurzschluss anbringt. ;-) Einfacher ist allerdings PORTC ^= 0xFF; >if (T==2000) >{ > fdevopen(uart_putc, NULL,0); > printf("S%08lx %li\n\r",timestamp,timestamp); Bitte nur einmal fdevopen() am Anfang von main()...
Ersteinmal Danke Jörg! Ich hätte nicht gedacht, daß in diesem kleinen Programm schon soviele "Fehler" enthalten sind. Danke das du mich darauf hinweist! Aber ich habe mal wieder (hoffe ich nerve dich nicht allzusehr) einige Fragen dazu: Das mit dem T++ und Uart ist mir gestern auch noch klar geworden (genauso wie du es mir erklärt hast). Nun noch ein paar Anmerkungen (Fragen) von mir ;). > typedef unsigned long int uint32_t; Das kann ich einfach weglassen, wenn ich die stdint.h einbinde? D.h. er erkennt dann automatisch das uint32_t als 32 bit Variable? Ok die neue Schreibweise, um die einzelne Bits zu setzen, nehme ich mir jetzt auch an. Warum Hex? Tja keine Ahnung, hab ich am Anfang mal gesehen und dachte halt, man macht es so...wusste nich das ich auch einfach den Dezimalwert ins Register schreiben kann. Das mit dem Default, hm....mir wurde gesagt, wenn man etwas nutzen will, sollte man es so initialisieren wie man es möchte, damit keine Missverständinsse oder Fehler auftreten. Aber OK, werde es mir aneignen :). Nun zu den beiden größten Probleme: Laut Tutorial ist das doch die einzige Möglichkeit zu überprüfen, was für einen Pegel ein PIN hat. Wie kann ich denn sonst herausbekommen, ob mein PIN (wo die LED dran ist) auf "1" oder "0" ist. Was meinst du mit: einfacher ist allerdings PORTC ^=0xFF; ? Könntest du das bitte erläutern? Und deinen letzten Satz verstehe ich auch nicht ganz. Meinst du, daß ich fdevopen einfach vor die Schleife ziehen soll, damit es nicht jedesmal mit ausgeführt wird? Kannst du mir vielleicht auch sagen, warum das besser ist? So long, Gruß Marian
> Ersteinmal Danke Jörg! Ich hätte nicht gedacht, daß in diesem > kleinen Programm schon soviele "Fehler" enthalten sind. Naja, Kleinigkeiten. > Das kann ich einfach weglassen, wenn ich die stdint.h einbinde? > D.h. er erkennt dann automatisch das uint32_t als 32 bit Variable? Genau dafür ist stdint.h da. Ist ein mit C99 neu hinzugekommener Header, weil man erkannt hat, dass die Programmierer sehr oft (vor allem bei hardwarenaher Programmierung) Datentypen benötigen, deren Bitbreite sie exakt benennen möchten. Indem man die Bereitstellung dieser Typen der Implementierung (als Compiler bzw. Library) überlässt, werden die Applikationen in dieser Hinsicht komplett portabel. > Tja keine Ahnung, hab ich am Anfang mal gesehen und dachte halt, man > macht es so...wusste nich das ich auch einfach den Dezimalwert ins > Register schreiben kann. 0xFA ist doch einfach eine Zahl, genau wie 250 (oder mit dem gepatchten Compiler 0b11111010). Man nimmt zweckmäßigerweise die jeweils ausdrucksstärkste Zahl. Für eine Zeitkonstante wie hier ist das sicher dezimal, da unser Gehirn sich daran gewöhnt hat, so zu denken. Um alle Bits eines Ports im DDR-Register auf Ausgang zu schalten, ist sicher 0xFF (oder 0b11111111) die bessere Darstellung. > Das mit dem Default, hm....mir wurde gesagt, wenn man etwas nutzen > will, sollte man es so initialisieren wie man es möchte, damit keine > Missverständinsse oder Fehler auftreten. Ist Geschmackssache. Da man bei Controllern immer begrenzte Ressourcen hat, sehe ich persönlich keinen Sinn drin, einen ordentlich dokumentierten default state nicht auch zu benutzen. Man kann und darf sich ja z. B. auch drauf verlassen, dass in C alle statischen und globalen Variablen mit 0 initialisiert worden sind. Sie nochmal separat im Programmablauf auszunullen ist daher auch nur Ressourcenverschwendung. > Laut Tutorial ist das doch die einzige Möglichkeit zu überprüfen, > was für einen Pegel ein PIN hat. Wie kann ich denn sonst > herausbekommen, ob mein PIN (wo die LED dran ist) auf "1" oder "0" > ist. Du hast zwei Register für den Port. PORTx spiegelt den gegenwärtigen Zustand des Port-Ausgabe-Registers, PINx liest direkt von den Pins. Normalerweise sollten beide natürlich die gleichen Werte haben für auf Ausgabe geschaltete Pins. Wenn du jetzt auf ein Portpin logisch 1 anlegst, aber extern jemand sehr viel Strom zieht (durch einen Kurzschluss, aber z. B. auch, indem er eine LED direkt an den Port anklemmt und damit den Pegel so weit runterzieht, dass er nicht mehr sicher eingangsseitig noch 1 ergeben würde), dann unterscheiden sich die Werte. Daher liest man, um den gegenwärtigen Zustand eines Ausgangs zu erfahren, normalerweise das PORTx-Register zurück, nicht das PINx-Register. > Was meinst du mit: einfacher ist allerdings PORTC ^=0xFF; ? Könntest > du das bitte erläutern? Die XOR-Verknüpfung mit 1 ist ein `toggle', d. h. eine XOR-Verknüpfung eines kompletten 8-Bit-Wertes mit 0xFF bewirkt ein Umschalten aller Bits in den jeweils anderen Zustand. Genau das passiert hier. Falls du mit der C-typischen ,,Kurzschlussschreibweise'' noch nicht vertraut bist, verdeutliche dir, was genau die ausgeschriebene Anweisung PORTC = PORTC ^ 0xFF; macht. > Und deinen letzten Satz verstehe ich auch nicht ganz. Meinst du, daß > ich fdevopen einfach vor die Schleife ziehen soll, damit es nicht > jedesmal mit ausgeführt wird? Kannst du mir vielleicht auch sagen, > warum das besser ist? Weil man einen stream nur einmal öffnet und danach bis zum Schließen benutzen kann. Effektiv öffnest du jedesmal einen neuen stream, benutzt den dann aber nicht (da du den Rückkehrwert von fdevopen() ignorierst). Intern wird das so lange gut gehen, bis malloc() keinen Speicher mehr verfügbar hat, danach gibt die Funktion nur noch NULL zurück.
Danke für die sehr ausführliche Erklärung :). Das Thema ist jetzt für mich beendet. Nun mal eine neue Frage :)...man kann ja nie auslernen. Kann man in einem Programm zwei unterschiedliche Uarts nutzen? Das heißt, kann ich einmal mit 9600 Baud senden und dann nochmal später mit 115200 Baud? Nochmal zu Erklärung: Uart_init1 (mit 9600 Baud); uart_init2 (mit 115200 Baud); void main() { if (Fall A) { Uart_init1(); uart_puts("Hallo mit 9600 Baud"); } if (Fall B) { Uart_init2(); uart_puts("Hallo mit 115200 Baud"); } } Wisst ihr wie ich es meine? Wäre mal wieder dankbar für eine Antwort :) Gruß Marian
Na klar, warum nicht? Aber mach besser einen neuen Thread auf für ein neues Thema.
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.