Forum: Mikrocontroller und Digitale Elektronik STM32 - Funktion liefert merkwürdiges Ergebnis zurück


von Wühlhase (Gast)


Lesenswert?

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:
1
uint16_t acquireRawOutputVoltage(){
2
  return *outputRawVoltage;
3
}
4
5
//...
6
7
float32_t acquireScaledOutputVoltage(){
8
  uint16_t raw = acquireRawOutputVoltage();
9
  float32_t outputVoltageScale = (float32_t)raw / 4095.0f;
10
  float32_t outputVoltage = outputVoltageScale * 550.0f;
11
  return outputVoltage;
12
}

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:
1
void TIM3_IRQHandler(void){
2
  volatile float32_t outputVoltage = acquireScaledOutputVoltage();
3
4
  if(outputVoltage > ABSOLUTE_MAXIMUM_OUTPUTVOLTAGE){
5
    //...
6
  }/
7
  TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
8
  NVIC_ClearPendingIRQ(TIM3_IRQn);
9
}

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
von Wühlhase (Gast)


Lesenswert?

Danke, das probiere ich mal aus.

von Wühlhase (Gast)


Lesenswert?

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.

von Einer K. (Gast)


Lesenswert?


von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

wird outputRawVoltage über den FSMC gelesen? Ist outputRawVoltage dann 
auch volatile deklariert? Oder Compiler Option -O0 (keine Optimierung) 
oder Debug Build ausprobiert?

von Wühlhase (Gast)


Lesenswert?

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:
1
//float23_t acquireScaledOutputVoltage(){
2
uint16_t acquireScaledOutputVoltage(){
3
  uint16_t raw = acquireRawOutputVoltage();
4
  return raw;
5
  //float32_t outputVoltageScale = (float32_t)raw / 4095.0f;
6
  //float32_t outputVoltage = outputVoltageScale * 550.0f;
7
  //return outputVoltage;
8
}
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.

von Wühlhase (Gast)


Lesenswert?

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();
6
08001560:   bl      0x8001550 <acquireRawOutputVoltage>
7
08001564:   strh.w  r0, [sp, #6]
8
 97         float32_t outputVoltageScale = (float32_t)raw / 4095.0f;
9
08001568:   ldrh.w  r3, [sp, #6]
10
0800156c:   uxth    r3, r3
11
0800156e:   vmov    s15, r3
12
08001572:   vcvt.f32.u32    s15, s15
13
08001576:   vldr    s14, [pc, #20]  ; 0x800158c <acquireScaledOutputVoltage+48>
14
0800157a:   vdiv.f32        s0, s15, s14
15
100       }

Und hier die Timer-ISR-Funktion:
1
143       void TIM3_IRQHandler(void){
2
          TIM3_IRQHandler:
3
08002f2c:   push    {lr}
4
08002f2e:   sub     sp, #12
5
144         volatile float32_t outputVoltage = acquireScaledOutputVoltage();
6
08002f30:   bl      0x800155c <acquireScaledOutputVoltage>
7
08002f34:   vmov    s15, r0
8
08002f38:   vcvt.f32.s32    s15, s15
9
08002f3c:   vstr    s15, [sp, #4]
10
146         if(outputVoltage > ABSOLUTE_MAXIMUM_OUTPUTVOLTAGE){
11
08002f40:   vldr    s14, [sp, #4]
12
08002f44:   ldr     r3, [pc, #44]   ; (0x8002f74 <TIM3_IRQHandler+72>)
13
08002f46:   vldr    s15, [r3]
14
08002f4a:   vcmpe.f32       s14, s15
15
08002f4e:   vmrs    APSR_nzcv, fpscr
16
08002f52:   bgt.n   0x8002f6c <TIM3_IRQHandler+64>
17
162         TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
18
08002f54:   movs    r1, #1
19
08002f56:   ldr     r0, [pc, #32]   ; (0x8002f78 <TIM3_IRQHandler+76>)
20
08002f58:   bl      0x800139e <TIM_ClearITPendingBit>
21
1549        NVIC->ICPR[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); /* Clear pending interrupt */
22
08002f5c:   ldr     r3, [pc, #28]   ; (0x8002f7c <TIM3_IRQHandler+80>)
23
08002f5e:   mov.w   r2, #536870912  ; 0x20000000
24
08002f62:   str.w   r2, [r3, #384]  ; 0x180
25
164       }

Was mir gestern nach Feierabend noch eingefallen ist: Muß die FPU 
eigentlich noch separat irgendwo aktiviert werden?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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, s14

Wü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_t acquireScaledOutputVoltage()
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?

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von Wühlhase (Gast)


Lesenswert?

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_t acquireScaledOutputVoltage()
> 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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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?

von Wühlhase (Gast)


Lesenswert?

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

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Wühlhase (Gast)


Lesenswert?

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

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

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.