Hallo, ich sitze jetzt ein paar Tage vor einem Problem und kann es nicht erklären. Das Projekt ist ein Drucksensor. Er empfängt über UART einen Befehl zu messen und senden. Im Test einfach mal das Zeichen "s" wie sende Du Ding! Gemessen wird über einen Piezo Sensor (Brücke)mit nachgeschaltetem Instrumentenverstärker (AD623)in den ADC des Atmega. Nu hat der Verstärker einen (geringen) Gleichspannungsoffset der auch noch leicht Temperaturabhängig ist. Mein Codeschnipsel: #define offset_OPV 10 .... ISR(USART_RXC_vector) { cli(); if(USART_receive()=='s') { uint16_t tmp; char Puffer[5] ; //Puffer für itoa tmp=(read_adc(ch_0)); //Druck messen tmp-=offset_OPV; //OPV Offset wegrechnen if(tmp<=0)tmp=0; tmp/=20; //runterteilen = Bar ohne "," itoa(tmp,Puffer,10); USART_transmit_string(Puffer); //ausgeben } sei(); return; } Klappt soweit alles nur kann es vorkommen, dass in der Zeile mit dem OPV Offset mein Messwert z.B. -1 wird. Das wollte ich in der darauffolgenden Zeile mit der if Abfrage abfangen. Leider klappt das nicht. Wenn dieser gewisse Fall eintritt(negatives tmp), erhalte ich als Ausgabe "3276". Könnt Ihr mir sagen, was ich da nicht verstanden habe? Lass ich mal das runterteilen weg (ergibt den Druck direkt in Bar ohne Komma - OPV Verstärkung so eingestellt) krieg ich trotzdem negative Ausgaben wie -1 oder -6 ... Gruss Holger
Hallo Holger, ich kenne die Datentypen exakt, aber ist uint16_t nicht vorzeichenlos? Ergibt dann if(tmp<=0) überhaupt Sinn bzw. was passiert bei tmp-=offset_OPV? Kann es sein, das da nichts Richtiges passieren kann, wenn tmp kleiner als offset_OPV ist? Gruß, Nils
Ich kenne die Datantypen nicht exakt... ...sollte es heißen. Wenn ich also Stuß schreibe, bitte ich um Entschuldigung. Gruß, Nils
Das ist richtig. uint16_t ist "unsigned", daher auch das u am Anfang des Namen. tmp kann also grundsätzlich nicht negativ werden und die Abfrage ist nutzlos. Wenn du "unten rausfällst", kommst du einfach von oben wieder rein. Also komst du bei 9 - 10 z.B. auf 65535. Wenn du das duch 20 teilst, kommen die 3276 raus. Der spätere Aufruf von itoa interpretiert uint16_t so, als sei es vorzeichenbehaftet. Daher bekommst du, wenn du nicht teilst, die negativen Werte. Mach aus tmp einfach einen int, dann müßte es gehen.
Desweiteren ist der Puffer zu knapp bemessen. Sollte der Wert über 9999 betragen, so werden bereits dafür 5 Zeichen verbraucht ... und die abschließende '\0' wird von itoa irgendwo in die Speicherlandschaft geklatscht. Klassischer Buffer Overflow.
Nur als Zusatzinfo: das cli() und sei() in der ISR kannste Dir schenken, das macht die Controller-Hardware automatisch
Das sei ist nicht nur überflüssig, sondern direkt schädlich (interrupt nesting, stack space).
Das sei() steht am Ende der ISR und dürfte deshalb lediglich überflüssig sein... Es sollte zumindest keine großartigen negativen Auswirkungen haben, es sei denn, Dein Stack ist extrem knapp bemessen. Abgesehen davon ist das 'return' am Ende ebenfalls irreführend, da es nur dann Sinn macht, wenn ein Wert zurückgegeben werden soll. ISRs können aber per se keine Werte zurückgeben...
Ahhhh ha...... Danke! Also: cli() und sei() raus! Das "return" ist wohl noch aus meiner Zeit des Assemblers (Rücksprung aus Interruptroutine = reti) auch raus! Und ich dachte gelesen zu haben, dass ein Interrupt auch von einem (höherwertigen) unterbrochen werden kann. Aus uint_16t mache ich int16_t (32768 reichen mir, da der Wert des ADC ja maximal 1023 (10bit) werden kann. Werde es heute Abend gleich ausprobieren - Vielen Dank für die schnelle und kompetente Hilfe!!! Gruss Holger
> Und ich dachte gelesen zu haben, dass ein Interrupt auch > von einem (höherwertigen) unterbrochen werden kann. Von jedem anderen, wenn du es mit sei() freigibst, sonst gar nicht. Die Interruptprioritäten beim AVR entscheiden nur, welcher Interrupt zuerst bearbeitet wird, wenn mehrere gleich- zeitig eintreffen.
Hallo Jörg, bitte entschuldige, wenn ich jetzt noch mal nachfrage... Jetzt hast Du mich völlig durcheinandergebracht. Ich hatte ja mal vorher folgenden Gedanken: Nach der Initialisierung gebe ich UART Empfangsinterrupt und den generellen mit sei() frei. In der main-loop legt sich der Prozessor schlafen (Saft sparen). Interrupts sind jetzt an! Kommt jetzt ein Zeichen -> aufwachen, in Empfangsint springen, Messung machen, ausgeben, Gute Nacht! Damit in der Zeit, wo die Messung gemacht wird sowie umgerechnet und ausgegeben wird, nicht noch ein weiteres Zeichen empfangen werden kann, wollte ich die Interrups verbieten und wenn alles fertig ist die Schaltung wieder scharf schalten. Wo hab ich da falsch gedacht? Gruss Holger
Hallo Ich bin zwar nicht Jörg, aber ich vrsuche trotzdem mal, das verständlich zu erklären (obwohl Jörg das sicher wesentlich besser kann;-) Das 'Verbieten' von Interrupts während einer laufenden ISR geschieht automatisch, da die µC-Hardware beim Einsprung in die ISR automatisch das I-Bit im Statusregister löscht und es erst beim Verlassen der ISR (ebenfalls automatisch) wieder setzt. Du verbietest damit allerdings keine weiteren Interrupts, sondern verhinderst nur deren sofortige Bearbeitung. Das Interrupt-Flag RXC wird nämlich ebenfalls bei Einsprung in die ISR gelöscht und kann theoretisch sofort wieder gesetzt werden, wenn ein neues Zeichen empfangen wird. Dann wird der Interrupt nach dem Verlassen der ISR (was mit dem Setzen des I-Bits verbunden ist) erneut ausgeführt. Den Empfang eines Zeichens während der laufenden ISR kannst Du nur verhindern, indem Du den Empfänger so lange ausschaltest oder indem Du am Ende der ISR das RXC manuell löschst, was dazu führt, dass ein in der Zwischenzeit eingetretenes Ereignis verloren geht. Wenn Dein Kommunikationspartner allerdings weiter sendet, gehen Dir Daten verloren. Da Du, um den µC wieder schlafen zu schicken, sowieso ins Hauptprogramm zurückgehen musst, solltest Du in der ISR nur den Empfangenen Wert in einen (Ring-) Puffer schreiben, ein Flag setzen und den Rest im Hauptprogramm abarbeiten. Dann geht auch kein Ereignis verloren, es sei denn, der 'andere' sendet schneller, als Du verarbeiten kannst. Gerade lange ISRs mit Funktionsaufrufen sind schlechter Programmierstil und führen evtl. zu unerwünschten Nebenwirkungen, z.B. Datenverlust, gerade weil während ihrer Ausführung alles andere blockiert ist. Gruß Johnny
Johhnys Erklärung stimmt fast und trifft in dieser Form für die meisten AVR-Interrupts zu. Die einzige Korrektur ist: bei der UART wird der laufende Interrupt-Status nicht gelöscht, indem man RXC setzt, sondern indem man UDR liest. UDR ist doppelt gepuffert, sofern also bereits ein weiteres Zeichen wartet (das man dann im ersten Aufruf der ISR noch nicht gelesen hat), wird sofort nach dem RETI aus der ISR selbige erneut angeworfen. ,,Vergisst'' man in einer UART-ISR, UDR zu lesen, dann wird diese ISR folglich unendlich oft aufgerufen. Schlafen legt man den Prozessor nach der beendeten Interrupt- Arbeit stets wieder in der main loop, nicht in der ISR selbst.
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.