Forum: Compiler & IDEs itoa Problem (evtl.)


von Holger G. (holli1195)


Lesenswert?

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

von Nils (Gast)


Lesenswert?

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

von Nils (Gast)


Lesenswert?

Ich kenne die Datantypen nicht exakt...

...sollte es heißen. Wenn ich also Stuß schreibe, bitte ich um
Entschuldigung.

Gruß, Nils

von Rolf Magnus (Gast)


Lesenswert?

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.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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.

von johnny.m (Gast)


Lesenswert?

Nur als Zusatzinfo: das cli() und sei() in der ISR kannste Dir schenken,
das macht die Controller-Hardware automatisch

von A.K. (Gast)


Lesenswert?

Das sei ist nicht nur überflüssig, sondern direkt schädlich (interrupt
nesting, stack space).

von johnny.m (Gast)


Lesenswert?

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...

von Holger G. (holli1195)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> 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.

von Holger G. (holli1195)


Lesenswert?

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

von johnny.m (Gast)


Lesenswert?

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

von Holger G. (holli1195)


Lesenswert?

Kapiert!

Danke!

Gruss Holger

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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
Noch kein Account? Hier anmelden.