Mahlzeit allerseits.
Folgendes: Ich programmiere gerade einen STM32F334. Hier habe ich eine
Funktion, die ein Integer (uint16_t), dieses kommt aus dem AD-Wandler,
in ein float32_t umwandeln soll.
Dahinter verbirgt sich eine Ausgangsspannung, und ich will das Ergebnis
des ADCs wieder auf die korrekte Spannung umrechnen.
So sehen die entsprechenden Funktionen aus:
An und für sich funktioniert das Ganze. Wenn ich mit dem Debugger
anhalte, dann hat die Variable outputVoltage sehr brauchbare Werte, wie
mir mein Multimeter hier anzeigt. Auch wenn verschiedene Spannungen
anliegen...bis hierher funktioniert das wunderbar.
Jetzt will ich die Funktion acquireScaledOutputVoltage() in einem
Timer-Interrupt aufrufen, und hier passiert etwas was ich partout nicht
verstehe:
Wenn ich mit dem Debugger am If-Statement anhalte, steht in der Variable
outputVoltage exakt der Wert, den der AD-Wandler liefert. Also z.B. 440
(NICHT 440.0 oder so, sondern 440 wie Integer normalerweise angezeigt
werden) anstelle von 59,x oder so. In der Spalte 'Type' steht sie
allerdings auch als volatile float32_t, und nicht als Integer.
Wenn eine andere Ausgangsspannung anliegt, dann ändert sich der Wert
entsprechend, es liegt aber recht zuverlässig immer das Ergebnis der
AD-Wandlung in der Variable.
Ich habe es auch mit anderen Namen probiert, ich bin mir recht sicher
keine globale Variable "outputVoltage" o.ä. zu haben, Strg+Klick auf die
Funktion führt auch dahin wo ich es erwarte, genauso wie die
Variablennamen...clean+build hab ich auch schon öfter durch, es hilft
alles nix.
Hat irgendjemand eine Idee, was da passiert? Und warum?
Wie schon gesagt, handelt es sich hier um einen STM32F334, programmiert
wird mit der CubeIDE. Firmeneigene Bibliothek, nix mit HAL oder LL.
Wenn du die FPU in ISRs benutzt, musst du das Sichern der FPU-Register
aktivieren, über die FPCCR.LSPEN imd FPCCR.ASPEN bits.
Siehe
https://static.docs.arm.com/dai0298/a/DAI0298A_cortex_m4f_lazy_stacking_and_context_switching.pdf#page=8
Aber das Problem ist einfach nur dass der Debugger die
Floating-Point-Zahl falsch formatiert? Ist das so wichtig, wenn das
Programm an sich funktioniert? Debugger zeigen manchmal komische Sachen
an, da braucht man nicht viel drauf zu geben.
Welches Floating-Point-ABI benutzt du? Könnte sein dass der Debugger die
Variable anders anzeigt, solange sie noch in CPU-Registern statt
FPU-Registern ist, wenn man das Float-ABI "softfp" oder "soft" nutzt.
Aber auch das sollte die Funktion des Programms nicht beeinträchtigen.
Niklas G. schrieb:> Aber das Problem ist einfach nur dass der Debugger die> Floating-Point-Zahl falsch formatiert? Ist das so wichtig, wenn das> Programm an sich funktioniert? Debugger zeigen manchmal komische Sachen> an, da braucht man nicht viel drauf zu geben.
Ich glaube nicht daß es am Debugger liegt. Wenn ich z.B. etwas unter 40V
am Ausgang habe (die von der acquireOutputVoltage-Funktion auch korrekt
errechnet werden) und die Begrenzung auf const float32_t 50.0f
einstelle, dann springt er in die If-Schleife weil in der
Vergleichsvariable eben 290 oder sowas steht - und nicht 38,x.
Ich habe die beiden Bits, die du genannt hast, mal am Ende der
Konfiguration noch gesetzt, aber das Problem besteht immer noch.
Ich glaub ich mach jetzt erstmal Feierabend. Morgen gehts weiter.
Wühlhase schrieb:> dann springt er in die If-Schleife weil in der> Vergleichsvariable eben 290 oder sowas steht - und nicht 38,x.
Ach das Problem ist dass statt des umgerechneten Werts der
Original-ADC-Wert genommen wird?
Ein reproduzierbares Minimalbeispiel wäre hilfreich, und die Disassembly
von acquireScaledOutputVoltage und TIM3_IRQHandler.
wird outputRawVoltage über den FSMC gelesen? Ist outputRawVoltage dann
auch volatile deklariert? Oder Compiler Option -O0 (keine Optimierung)
oder Debug Build ausprobiert?
Arduino Fanboy D. schrieb:> Wühlhase schrieb:>> If-Schleife>> http://if-schleife.de/
Da hast du natürlich Recht, das war Unsinn. Mea Culpa.
Niklas G. schrieb:> Ach das Problem ist dass statt des umgerechneten Werts der> Original-ADC-Wert genommen wird?
Genau so ist es. Oder, anders gesagt: Das Programmverhalten ist
äquivalent zu diesem Code:
Und das ist definitiv nicht, was ich oben formuliert habe.
Mir ist absolut schleierhaft, wie das raw da so durchrutschen kann. Vor
allem wird es ja auch als (u)int behandelt, zumindest in der
Debuggeransicht. In der Detailansicht sieht es auch wie ein Integer aus
- nicht wie ein float. Obwohl 'outputVoltage' am Ende einen korrekt
berechneten float-Wert hat.
Ich schaue mal ob ich morgen etwas Reproduzierbares zusammengeschustert
bekomme.
Johannes S. schrieb:> wird outputRawVoltage über den FSMC gelesen? Ist outputRawVoltage dann> auch volatile deklariert? Oder Compiler Option -O0 (keine Optimierung)> oder Debug Build ausprobiert?
*outputRawVoltage ist ein Pointer, der auf ein Element eines
uint16-Arrays zeigt. Dieses Array wird vom DMA mit ADC-Werten gefüllt.
In der DMA-ISR wird noch eine gleitende Mittelwertbildung durchgeführt.
Dieses Array ist nicht volatile und die volatile-Deklarationen hab ich
erst später eingefügt, um den Fehler zu beheben bzw. um mit dem Debugger
etwas sehen zu können. Sonst hat er gelegentlich mal das ein oder andere
wegoptimiert.
So, hier ist erstmal das Assemblat:
Die Funktion acquireScaledOutputVoltage() (die Funktion, mit der ich den
Rawwert hole, hat der Compiler hier integriert wenn ich das richtig
verstehe):
1
95 float32_t acquireScaledOutputVoltage(){
2
acquireScaledOutputVoltage:
3
0800155c: push {lr}
4
0800155e: sub sp, #12
5
96 volatile uint16_t raw = acquireRawOutputVoltage();
Wühlhase schrieb:> Muß die FPU> eigentlich noch separat irgendwo aktiviert werden?
Ja, über das CPACR:
https://www.mikrocontroller.net/articles/ARM_GCC#FPU_der_Cortex-M4F_nutzen
Allerdings hast du das anscheinend schon gemacht, denn sonst wäre dein
Programm bei der ersten FPU-Instruktion abgestürzt.
Wühlhase schrieb:> (die Funktion, mit der ich den> Rawwert hole, hat der Compiler hier integriert wenn ich das richtig> verstehe):
Nö. Die Funktionsaufrufe sind eindeutig vorhanden.
Wühlhase schrieb:> 0800157a: vdiv.f32 s0, s15, s14Wühlhase schrieb:> 08002f34: vmov s15, r0
Da passt was nicht zusammen. Du hast die "acquireScaledOutputVoltage"
mit "-mfloat-abi=hard" und die "TIM3_IRQHandler" mit
"-mfloat-abi=softfp" kompiliert. Erstere gibt ihren Rückgabewert im
FPU-Register s0 zurück, aber zweitere nimmt an es würde sich im
CPU-Register r0 befinden. Das kann nicht funktionieren. Also Fehler im
makefile.
Es ist ein kurioser Zufall, dass der Rückgabewert von
acquireRawOutputVoltage durch das r0 "durchrutscht" (weil
acquireScaledOutputVoltage das r0 nicht wieder überschreibt) und dann im
"TIM3_IRQHandler" als skalierter Wert angenommen wird...
Edit: Das "vcvt.f32.s32" macht mich stutzig. Könnte es ein dass du eine
Deklaration der Form
1
int32_tacquireScaledOutputVoltage()
vor der "TIM3_IRQHandler" hast, während die Definition von
"acquireScaledOutputVoltage" aber "float32_t" hat? Also die Grundregel
missachtet, dass alle (nicht-static) Funktionen in Headern deklariert
werden müssen, und diese Header vor der Definition in der jeweiligen .c
/ .cpp Datei #includiert werden müssen?
Wühlhase schrieb:> void TIM3_IRQHandler(void){> volatile float32_t outputVoltage = acquireScaledOutputVoltage();
Nur ein Tipp: Es ist kontraproduktiv, lokale Variablen in einer IRQ als
volatile zu definieren. Die können nicht unvorhergesehen "von außen"
geändert werden.
Frank M. schrieb:> Wühlhase schrieb:>> void TIM3_IRQHandler(void){>> volatile float32_t outputVoltage = acquireScaledOutputVoltage();>> Nur ein Tipp: Es ist kontraproduktiv, lokale Variablen in einer IRQ als> volatile zu definieren. Die können nicht unvorhergesehen "von außen"> geändert werden.
Ja, das war auch mal nicht-volatile, aber dann optimiert mir der
Compiler das weg, da in der if-Abfrage noch nicht viel passiert.
Niklas G. schrieb:> Wühlhase schrieb:>> (die Funktion, mit der ich den>> Rawwert hole, hat der Compiler hier integriert wenn ich das richtig>> verstehe):>> Nö. Die Funktionsaufrufe sind eindeutig vorhanden.
Bist du dir da sicher? Die acquireRawOutputVoltage-Funktion steht
nämlich in Zeile 51, aber die hab ich dort im Assemblat nicht gefunden.
Es würde auch durchaus Sinn machen, da ich diese Funktion zumindest
jetzt nirgendwo sonst aufrufe.
Niklas G. schrieb:> Da passt was nicht zusammen. Du hast die "acquireScaledOutputVoltage"> mit "-mfloat-abi=hard" und die "TIM3_IRQHandler" mit> "-mfloat-abi=softfp" kompiliert. Erstere gibt ihren Rückgabewert im> FPU-Register s0 zurück, aber zweitere nimmt an es würde sich im> CPU-Register r0 befinden. Das kann nicht funktionieren. Also Fehler im> makefile.
Hm...im makefile habe ich definitiv nicht rumgefummelt, aber dann werde
ich mal versuchen, die Werte einheitlich auf 'hard' zu setzen.
Niklas G. schrieb:> Edit: Das "vcvt.f32.s32" macht mich stutzig. Könnte es ein dass du eine> Deklaration der Form>
1
int32_tacquireScaledOutputVoltage()
> vor der "TIM3_IRQHandler" hast, während die Definition von> "acquireScaledOutputVoltage" aber "float32_t" hat? Also die Grundregel> missachtet, dass alle (nicht-static) Funktionen in Headern deklariert> werden müssen, und diese Header vor der Definition in der jeweiligen .c> / .cpp Datei #includiert werden müssen?
Hm...nein, eigentlich nicht. Strg-Klick führt schon zu der Funktion, die
ein float zurückliefert und ich wüßte nicht, daß ich den Wert nochmal
als Integer irgendwo angepasst hätte. Und die Funktionen sind alle brav
in einer Headerdatei eingetragen.
Ich habe zwar etwas Fremdcode von ST im Projekt drin, den werde ich mal
durchsuchen, aber eine Funktion dieses Namens habe ich darin noch nicht
gefunden. Und wenn es eine solche Funktion dort gäbe, dann müßte die in
einer Headerdatei aufgeführt sein.
Edit:
Ich habe gerade nochmal nachgesehen - ich habe tatsächlich vergessen,
die Funktion acquireScaledOutputVoltage() im Headerfile einzutragen.
Das habe ich jetzt nachgeholt und baue das Projekt nochmal neu (dauert
etwas), mal sehen ob es das war.
Wühlhase schrieb:> Bist du dir da sicher? Die acquireRawOutputVoltage-Funktion steht> nämlich in Zeile 51, aber die hab ich dort im Assemblat nicht gefunden.
Genauer hinschauen :D Da steht "bl 0x8001550 <acquireRawOutputVoltage>".
Sie ist also im Kompilat an Adresse 0x8001550 und wird aufgerufen.
Wühlhase schrieb:> Hm...nein, eigentlich nicht. Strg-Klick führt schon zu der Funktion, die> ein float zurückliefert
Das heißt gar nichts. Zwischen dem, was IDE meint wie der Code
funktioniert und dem, was der Compiler sieht und daraus macht kann eine
meilenweiter Unterschied sein.
Wühlhase schrieb:> Ich habe gerade nochmal nachgesehen - ich habe tatsächlich vergessen,> die Funktion acquireScaledOutputVoltage() im Headerfile einzutragen.
Wie konntest du die dann überhaupt aufrufen? Aus Sicht der ISR existiert
dann keine Funktion dieses Namens. Oder hast du etwa die Warnung
"warning: implicit declaration of function 'acquireScaledOutputVoltage'"
ignoriert und der Compiler hat per Default den Rückgabetyp "int"
angenommen?
Frank M. schrieb:> Wühlhase schrieb:>> void TIM3_IRQHandler(void){>> volatile float32_t outputVoltage = acquireScaledOutputVoltage();>> Nur ein Tipp: Es ist kontraproduktiv, lokale Variablen in einer IRQ als> volatile zu definieren. Die können nicht unvorhergesehen "von außen"> geändert werden.
Eine Frage hätte ich hierzu nochmal:
volatile sagt ja dem Compiler, daß er eine Variable keiner Optimierung
unterziehen soll.
Dementsprechend entsteht halt höchstens etwas größerer Code, wenn ich
volatile an dieser Stelle verwende.
Daraus habe ich jetzt geschlossen, daß ich allerhöchstens etwas mehr
Speicher verbrate (und vielleicht etwas mehr Rechenzeit, wenn so eine
Variable überflüssigerweise mit aufden Stack muß), sonst aber keine
weiteren Nachteile habe.
Ist das soweit richtig, oder gibt es da noch weitere Nachteile/Probleme?
(Und wie gesagt: Ursprünglich war die Variable auch nicht volatile, aber
was der Compiler wegoptimiert kann ich nicht mehr im Debugger sehen...)
Wühlhase schrieb:> Ist das soweit richtig, oder gibt es da noch weitere Nachteile/Probleme?
Das ist richtig, wobei der Speicher/Rechenzeit-Nachteil auch gar nicht
so klein sein kann, wenn es sich z.B. um ein Zwischenergebnis handelt,
was der Compiler sonst komplett überspringen könnte. Mindestens mal wird
die Variable sinnlos auf den Stack geschrieben, obwohl sie allein in den
Registern hätte bleiben können. Das ist bei größeren Prozessoren mit
tiefer Pipeline und Branch Prediction von größerer Bedeutung.
Irre...es funktioniert jetzt.
Ich kann mir das wie und warum noch nicht erklären, aber auf jeden Fall
tut mein Code jetzt, was er soll - nachdem ich das Headerfile
überarbeitet und die Funktion nachgetragen habe.
Vielen Dank für eure Hilfe. :)
Wühlhase schrieb:> Ich kann mir das wie und warum noch nicht erklären
Ohne Deklaration wusste der Compiler beim Kompilieren des
TIM3_IRQHandler nicht, was die Funktion acquireScaledOutputVoltage
zurückgibt. Daher hat er die Warnung "warning: implicit declaration of
function 'acquireScaledOutputVoltage'" ausgegeben und einfach "int"
angenommen. Aufgrund des ABIs (AAPCS) hat er angenommen, der
Rückgabewert vom Typ int würde sich im CPU-Register r0 befinden.
Tatsächlich gibt die Funktion aber einen float-Wert zurück, welcher sich
im FPU-Register s0 befindet, und bei Rückkehr von
acquireScaledOutputVoltage befindet sich in r0 rein zufällig der
nicht-skalierte Integer-Rückgabewert von acquireRawOutputVoltage. Diesen
hat die TIM3_IRQHandler dann in einen float umgewandelt (denn es ist
ja ein int), nach outputVoltage geschrieben, und das mit
ABSOLUTE_MAXIMUM_OUTPUTVOLTAGE verglichen. Der eigentliche Rückgabewert
von acquireScaledOutputVoltage wurde komplett ignoriert.
Der Fehler bestand also darin, die Compiler-Warnung zu ignorieren und
keine Deklaration der Funktion anzugeben. Am Besten mit
"-Werror=implicit-function-declaration" kompilieren, oder mit einem
nicht-antiken Sprachstandard (C99, C11 oder C18 statt C89/Ansi-C).
Implizite Funktionsdeklarationen sollte man grundsätzlich nicht nutzen,
deswegen gibt's die auch in aktuellen C-Versionen und auch in C++ nicht
mehr. Auch geholfen hätte "-Wconversion", denn dann würde angekreidet
dass der Rückgabetyp (angeblich) "int" ist und jetzt nach float
konvertiert wird; das sollte die Alarmglocken läuten lassen.
Wühlhase schrieb:> Irre...es funktioniert jetzt.
Wegen der fehlenden externen Deklaration? Wie oben schon erwähnt: Du
hättest dann zumindest eine Warnung vom Compiler bekommen: "implicit
declaration...".
Man sollte grundsätzlich jede Warnung ernstnehmen. Deshalb benutze ich
auch prinzipiell "-Werror", damit die Compilation auch nach jeder
Warnung sofort abgebrochen wird.