Forum: Compiler & IDEs Vorberechnungen durch Präprozessor möglich?


von André W. (sefiroth)


Lesenswert?

Hallo,

ist es möglich sich durch den Präprozessor (oder etwas anderes) Werte 
vor der eigentlichen Kompilierung berechnen zu lassen?

Ich habe z.B. das Problem, dass ich einen AVR mit ADC-Wandler habe, der 
bei Überschreiten eines Grenzwertes (Überlastungswarnung bei zu hohem 
Strom durch Shunt-Widerstand) Meldung machen soll. Dazu muss ich den 
Maximalwert des Stroms natürlich in einen ADC Wert umrechnen, was 
eigentlich nur mit float Operationen möglich ist. Ich will aber nicht 
mit Kanonen auf Spatzen schießen, wenn ich im Endeffekt nur einen 
Charakter oder Integer Wert brauche...

Hier mal ein Code-Beispiel, wie ich es mir ungefähr gedacht habe:
1
#define R_Shunt  1 // in Ohm
2
#define I_max    1.75 // in A
3
#define U_max    (R_Shunt * I_max)
4
#define U_ref    5 // in V
5
#define ADC_max  ( (U_max / U_ref) * 1023)
6
7
#define UEBERSTROM() (adc_wert > ADC_max)
8
9
// globals
10
unsigned int adc_wert = 0; // wird durch Funktion zyklisch aktualisiert
11
...
12
13
int main (void)
14
{
15
   ...
16
17
   if ( UEBERSTROM() )
18
   {
19
      // Strom aus
20
   }
21
22
   ...
23
}

Nur wird hier die Berechnung des ADC-Wertes einfach nur im Code 
eingesetzt und ist dann aufgrund der Integerrechnung entsprechend 
ungenau (2/5 müsste bei der Integerrechnung glaube ich Null ergeben). 
Ähnliche Probleme treten auf, wenn der Widerstand z.B. nur 0.1 Ohm hat 
(auch = 0). Mittels Float ließe es sich ja berechnen, aber so oder so 
finde ich es schlecht den Wert im Programm berechnen lassen zu müssen, 
da er ja fix ist.

Schön wäre es, wenn der PC vorm/beim Compilieren seine Rechenpower 
einsetzen könnte, um mir den ADC-Wert schön mit zig Nachkommastellen zu 
berechnen und das Ergebnis dann als char/int im Code einzutragen.

Wenn es da ne Lösung gibt, wäre ich sehr dankbar, denn sonst müsste ich 
jedesmal, wenn ich die Stromschwelle anders festlegen möchte, auch alle 
Folgewerte neu berechnen.

Vielen Dank schonmal!

von Benedikt K. (benedikt)


Lesenswert?

Sobald du Kommazahlen verwendest, wird float verwendet. Vermutlich auch 
beim Vergleich (ich bin mir aber nicht ganz sicher).
Mach einfach folgendes:

#define UEBERSTROM() (adc_wert > (unsigned int)ADC_max)

Jetzt sollte hier im Assemblercode nur eine Zahl stehen, und von den 
Fließkomma Berechnungen sieht der AVR nix.

von Wolfram (Gast)


Lesenswert?

Wenn der Optimierer an ist sollte es vorberechnet werden.
(U_max / U_ref) * 1023)
gewöhn dir so etwas ab.
((U_max *1023)/ U_ref))
macht das gleiche hat aber kein Problem bei < 1 im Integerbereich (aber 
Überlauf könnte auftreten)
Die Werte die du angibst werden als (int) gerechnet, wenn du einen 
größeren Bereich brauchst (auch für Zwischenergebnisse), sind sie 
entsprechend zu kennzeichen
Bsp:
3600UL  (UL für unsigned long)

von Falk (Gast)


Lesenswert?

@André Wippich

>ist es möglich sich durch den Präprozessor (oder etwas anderes) Werte
>vor der eigentlichen Kompilierung berechnen zu lassen?

Sicher.

>Hier mal ein Code-Beispiel, wie ich es mir ungefähr gedacht habe:

>#define R_Shunt  1 // in Ohm

Fast richtig. Du willst hier sicher ne Foat Variable, wegen der 
Genauigkeit

define R_Shunt  1.0 // in Ohm

>#define I_max    1.75 // in A
>#define U_max    (R_Shunt * I_max)

>#define U_ref    5 // in V

Dito.

#define U_ref    5.0 // in V

#define ADC_max  ( (U_max / U_ref) * 1023)

Fast richtig. Probier mal

#define ADC_max  ( (U_max * 1023 / U_ref)

>#define UEBERSTROM() (adc_wert > ADC_max)


>Nur wird hier die Berechnung des ADC-Wertes einfach nur im Code
>eingesetzt und ist dann aufgrund der Integerrechnung entsprechend
>ungenau (2/5 müsste bei der Integerrechnung glaube ich Null ergeben).
>Ähnliche Probleme treten auf, wenn der Widerstand z.B. nur 0.1 Ohm hat

Du musst

a) alles möglichst in eine Formel schreiben
b) erst alle Multiplikationen machen, dann alle Divisionen

>Schön wäre es, wenn der PC vorm/beim Compilieren seine Rechenpower
>einsetzen könnte, um mir den ADC-Wert schön mit zig Nachkommastellen zu
>berechnen und das Ergebnis dann als char/int im Code einzutragen.

Kann er.

MFG
Falk

Disclaimer: Ich bin nicht so der C-Profi. Wahrscheinlich ist meine 
Erklärung nicht so ganz korrekt. Aber das Prinzip sollte stimmen. ;-)

von yalu (Gast)


Lesenswert?

@André Wippich

> ist es möglich sich durch den Präprozessor (oder etwas anderes) Werte
> vor der eigentlichen Kompilierung berechnen zu lassen?

Der Präprozessor ersetzt bei der Makroexpansion nur Texte, aber der
Compiler berechnet konstante Teilausdrücke, sofern sie nur die
Basisoperationen und keine Funktionsaufrufe enthalten. Beim GCC ist
deses Feature schon in der niedrigsten Optimierungsstufe aktiv.
Deswegen sollte dein Code eigentlich das gewünschte tun.

Allerdings wird beim Vergleich in UEBERSTROM adc_wert in double
umgewandelt, da auch ADC_max ein double-Ausdruck ist. Schneller und
kürzer wird der erzeugte Code, wenn ADC_max wie adc_wert ein unsigned
int ist.

> Nur wird hier die Berechnung des ADC-Wertes einfach nur im Code
> eingesetzt und ist dann aufgrund der Integerrechnung entsprechend
> ungenau (2/5 müsste bei der Integerrechnung glaube ich Null
> ergeben). Ähnliche Probleme treten auf, wenn der Widerstand z.B. nur
> 0.1 Ohm hat (auch = 0).

Ich sehe hier keine Integerberechnung. In jedem Teilausdruck der in
deinem Code enthaltenen Formeln ist mindestens ein Operand double, so
dass sämtliche Berechnungen (vom Compiler) in double ausgeführt
werden. Um das deutliche zu machen würde ich, wie bereits von Falk
angemerkt, alle Konstanten als double-Wert angeben.

Wolfram schrieb:
> (U_max / U_ref) * 1023)
> gewöhn dir so etwas ab.
> ((U_max *1023)/ U_ref))

Die Auswertereihenfolge von Multiplikationen/Divisionen ist bei
double-Berechnungen egal (bei int-Berechnungen aber nicht).

Die 1023 würde ich durch 1024 ersetzen, da ein A/D-Schritt 1/1024 der
Referenzspannung ist.

Die obigen Anmerkungen führen zu folgendem, leicht abgeändertem Code:
1
#define R_Shunt  1.00 // in Ohm
2
#define I_max    1.75 // in A
3
#define U_max    (R_Shunt * I_max)
4
#define U_ref    5.00 // in V
5
#define ADC_max  ((unsigned int)((U_max / U_ref) * 1024))
6
7
#define UEBERSTROM() (adc_wert > ADC_max)
8
9
// globals
10
unsigned int adc_wert = 0; // wird durch Funktion zyklisch aktualisiert
11
12
int main (void)
13
{
14
15
   if ( UEBERSTROM() )
16
   {
17
      // Strom aus
18
   }
19
}
Für die Zeile
1
   if ( UEBERSTROM() )
wird dabei der gleiche Code generiert wie für
1
   if ( adc_wert >= 359 )
Außer dem int-Vergleich wird also nichts zur Laufzeit berechnet.

von André W. (sefiroth)


Lesenswert?

Ok, ich muss zugeben, dass ich da noch ein paar Schwierigkeiten habe 
reinzufinden in die Typkonversionen und Vorberechnungen... Ich hätte 
hier folgendes akutes Beispiel an dem ich grad sitze und wo das Problem 
wieder auftaucht. Es wäre nett, wenn Ihr mal drüber schaut, ob die 
Berechnungen dann korrekt durchgeführt werden.
1
// PWM Werte / Kennlinie (CAN -> PWM Ausgabe)
2
#define CAN_MAX    200    // Botschaftsinhalt, der 100% Anforderung entspricht
3
4
#define PWM_MAX    0.90  // Anforderung (1.00 = 100%) für maximale Leistung (bei CAN = 100%)
5
#define PWM_MIN    0.00  // Anforderung (1.00 = 100%) für minimale Leistung (bei CAN = 0%) 
6
7
#define TMR_MAX    PWM_MAX * 255
8
#define TMR_MIN    PWM_MIN * 255
9
              
10
#define PWM_Fak    ( (TMR_MAX - TMR_MIN) / CAN_MAX )
11
12
...
13
14
void Aktualisiere_PWM(unsigned char PWM_Anf)
15
{
16
  
17
  // PWM-Anforderung (vom CAN) prüfen (200dez = 100%)
18
  if (PWM_Anf <= 200)
19
  {
20
    // Wegen SteuerBox-Anforderung PWM invertieren:
21
    PWM_Anf = 200 - PWM_Anf;
22
    
23
    // PWM Ausgangssignal gemäß Kennlinie generieren
24
    // 0%   CAN -> PWM_MIN
25
    // 100% CAN -> PWM_MAX
26
    
27
    OCR2 = (unsigned char) ( (PWM_Anf * PWM_Fak) + TMR_MIN );
28
29
  }
30
  else
31
  {
32
    // Limitierung auf 100%
33
    OCR2 = 0xFF;
34
  }
35
}

Damit die Berechnungen stimmen, müsste PWM_Fakt gleich 1.1475 sein. Und 
bei PWM_Anf = 200 müsste OCR2 = 229 oder 230 sein.

Wird damit gerechnet bzw kommt das heraus? Danke nochmal für die Hilfe!

von Falk (Gast)


Lesenswert?

@André Wippich

>hier folgendes akutes Beispiel an dem ich grad sitze und wo das Problem
>wieder auftaucht. Es wäre nett, wenn Ihr mal drüber schaut, ob die
>Berechnungen dann korrekt durchgeführt werden.

So wie ich es sehe müsste es stimmen. Was sagt der Debugger/Simulator?

MFG
Falk

von Karl H. (kbuchegg)


Lesenswert?

> Damit die Berechnungen stimmen, müsste PWM_Fakt gleich 1.1475 sein. Und
> bei PWM_Anf = 200 müsste OCR2 = 229 oder 230 sein.
>

Am einfachsten ist es, wenn du selbst mal Compiler spielst:

Aus
   (PWM_Anf * PWM_Fak) + TMR_MIN

macht der Präprozessor durch Textersetzung:
(Für PWM_Fak einsetzen)
1
  (PWM_Anf * ( (TMR_MAX - TMR_MIN) / CAN_MAX )) + TMR_MIN

danach machst du für TMR_MAX und TMR_MIN die Textersetzungen
1
  (PWM_Anf * ( (PWM_MAX * 255 - PWM_MIN * 255) / CAN_MAX )) + PWM_MIN * 255

und zu guter letzt macht der Präprozesor noch Ersetzungen für
PWM_MAX, PWM_MIN und CAN_MAX
1
  (PWM_Anf * ( (0.90 * 255 - 0.00 * 255) / 200 )) + 0.00 * 255

damit hat der Präprozessor alle Textersetzungen abgeschlossen.
Iregendwann kommt dann der eigentliche Compiler und
übersetzt diesen arithmetischen Ausdruck. Dabei kannst du mal
davon ausgehen, dass er alle Arithmetik, die nur mit
Konstanten arbeitet, selbst ausrechnet und nur das Ergebnis
einsetzt.(*) Auch können wir davon ausgehen, dass der Compiler
so schlau ist, dass eine Addition von 0.0 den Wert nicht
ändert:
1
  (PWM_Anf * ( (0.90 * 255 - 0.00 * 255) / 200 )) + 0.00 * 255
2
3
  (PWM_Anf * ( (229.5 - 0.00 ) / 200 )) + 0.00
4
5
  (PWM_Anf * ( (229.5) / 200 )) + 0.00
6
7
  (PWM_Anf * ( (229.5) / 200 ))
8
9
  (PWM_Anf * ( 1.1475 )

(*) Wobei der Compiler natürlich auch die C-Regeln berücksichtigt:
d.h. Ist in einem binären arithmetischen Ausdruck einer der beiden
Operanden ein float oder double, dann wird der andere Operand
ebenfalls zu einem double umgebaut und die ganze Operation im
Zahlraum double durchgeführt sodass das Ergebnis wiederrum ein
double ist.

von André W. (sefiroth)


Lesenswert?

Falk wrote:
> So wie ich es sehe müsste es stimmen. Was sagt der Debugger/Simulator?

Der verursacht einen Runtime-Error und schmiert immer ab, wenn ich ihn 
starten will :-)


Karl heinz Buchegger wrote (sinngemäß):
1
OCR2 = (unsigned char) (PWM_Anf * ( 1.1475 ) )

Soweit bin ich auch schon gekommen :-) Diese Berechnung muss ja dann der 
Mikrocontroller zur Laufzeit durchführen und ich befürchte dass er 
1.1475 dann zu 1 abrundet. Oder rechnet er dann automatisch float? 
Werden die notwendigen Bibliotheken dann vom Compiler eingebunden?

Wenn ich 0.3 * 100 ("float" * "double") rechne, ergibt das ja float. Was 
passiert wenn ich 200/230 rechne ("double" / "double")? Wird es auch zu 
float umgesetzt?

Wie unterscheiden sich double und float nochmal voneinander?Ich hab es 
bisher immer so verstanden: Float ist Fließkommarechnung - klar. Und 
double ist das Gleiche wie int (keine Kommarechnung), nur mit der 
doppelten Bytezahl an Speicherplatz (also bis zu +0,5 * 2^32 ... -0,5 * 
2°32). Stimmt das so?

Sorry die etwas dümmlicheren Fragen, aber ich will das endlich mal 
komplett raffen :-) Proggen kann ich, nur fehlen mir ein paar 
wesentliche Grundlagen, die die Arbeit einfacher machen (lang, lang 
ist's her g).

von Falk (Gast)


Lesenswert?

@André Wippich

>Der verursacht einen Runtime-Error und schmiert immer ab, wenn ich ihn
>starten will :-)

Uups, das liegt aber eher am Debugger als an deinem Code ;-)

>Mikrocontroller zur Laufzeit durchführen und ich befürchte dass er
>1.1475 dann zu 1 abrundet. Oder rechnet er dann automatisch float?

Das wird er wohl tun.

>Werden die notwendigen Bibliotheken dann vom Compiler eingebunden?

Muss man glaub ich einstllen?

>passiert wenn ich 200/230 rechne ("double" / "double")? Wird es auch zu
>float umgesetzt?

Auf dem AVR ist Double=float.
Normalerweise ist Double ein 64 Bit Floating point, während Float ein 32 
Bit Floating Point Format is.

>Wie unterscheiden sich double und float nochmal voneinander?Ich hab es

Auf dem AVR gar nicht. Siehe oben.

>bisher immer so verstanden: Float ist Fließkommarechnung - klar. Und
>double ist das Gleiche wie int (keine Kommarechnung), nur mit der

NEIN! Sie oben.

>Sorry die etwas dümmlicheren Fragen, aber ich will das endlich mal
>komplett raffen :-) Proggen kann ich, nur fehlen mir ein paar
>wesentliche Grundlagen, die die Arbeit einfacher machen (lang, lang
>ist's her g).

???
Dieser Satz ist ein Oxymoron.

http://de.wikipedia.org/wiki/Oxymoron

;-)
Falk

von Karl H. (kbuchegg)


Lesenswert?

André Wippich wrote:
>
> Karl heinz Buchegger wrote (sinngemäß):
>
1
> OCR2 = (unsigned char) (PWM_Anf * ( 1.1475 ) )
2
>
>
> Soweit bin ich auch schon gekommen :-) Diese Berechnung muss ja dann der
> Mikrocontroller zur Laufzeit durchführen und ich befürchte dass er
> 1.1475 dann zu 1 abrundet.

Warum soll er das tun.
Die C Regeln sagen eindeutig: Wenn einer der beiden Operanden
ein float oder double ist, dann wird diese Berechnung in
double ausgeführt und der andere Operand wird im Datentyp
an double angeglichen. Ist dabei der andere Datentyp ein int
oder long, so wird dieser Datentyp zuvor auf double gewandelt.

> Werden die notwendigen Bibliotheken dann vom Compiler eingebunden?

Zumindest für die arithmetischen Basisfunktionen sollte beim
gcc nichts notwendig sein. Ein Angeben der Floating Point
Library schadet aber sicherlich nichts.

>
> Wenn ich 0.3 * 100 ("float" * "double") rechne, ergibt das ja float. Was
> passiert wenn ich 200/230 rechne ("double" / "double")?

200/230  ist keine double Berechnung.
200 ist ein int
230 ist ein int
Ergo wird die Division als Ganzzahldivision ausgeführt.

Aber:
200.0  wäre eine Gleitkommazahl. Die Berechnung
200.0 / 230
würde als Gleitkommadivision ausgeführt werden.

Die Abstufung ist:
   Gleitkomma (float und double)
   Ganzzahltypen (long, int)

wobei Gleitkomma 'höher' steht als Ganzzahl. Solange in einer
Berechnung nur Ganzzahltypen vorkommen, wird die Operation
als Ganzzahloperation durchgeführt. Ist mindestens einer der
beiden ein Gleitkommaoperand, wird dauch der andere zu einem
Gleitkommaoperanden 'upgegradet' und die Operation wird
als Gleitkommaoperation durchgeführt.

Eigentlich ganz einfach.

> Wird es auch zu
> float umgesetzt?

Nein.
Zu double. Die Standardtypen in C sind int und double.
Wenn irgendwas zu einem Gleitkommatypen 'upgegradet' werden
muss, dann wird es zu einem double.

Auf einem AVR mit gcc gibt es keinen Unterschied zwischen
float und double. Beides sind Gleitkomma Datenformate mit
jeweils 4 Byte pro Zahl.

>
> Wie unterscheiden sich double und float nochmal voneinander?

Auf einem AVR mit gcc: gar nicht
Ansonsten:
Üblich ist: float wird mit 4 Bytes dargestellt, double mit 8.
Das ergibt bei einem float so um die 5 bis 6 signifikante
Ziffern, während es bei einem double schon so um die 15 sind.

Achtung: signifikante Ziffern! Nicht Nachkommastellen!
Die Zahl 1367.8 als float benutzt bereits 5 signifikante
Ziffern: 4 Stück davon vor dem Komma und 1 nach dem Komma.
Alle weiteren Kommastellen sind daher Unsinn und
bestenfalls noch näherungsweise korrekt. Vor allem wenn das
float-Ergebnis 1367.8942 aus einer Berechnung stammt. Alles
nach der Hunderstelstelle ist absoluter Unsinn.

> Ich hab es
> bisher immer so verstanden: Float ist Fließkommarechnung - klar. Und
> double ist das Gleiche wie int (keine Kommarechnung), nur mit der
> doppelten Bytezahl an Speicherplatz (also bis zu +0,5 * 2^32 ... -0,5 *
> 2°32). Stimmt das so?

Nein. Das ist Unsinn. double ist ein Fliesskommatyp.

Übliche Datentypen sind zb:

Gleitkomma:  float (meist 4 Bytes, auf dem AVR-gcc: 4 Bytes)
             double (meist 8 Bytes, auf einem AVR-gcc: 4 Bytes)

Ganzzahl:    signed char    (1 Byte, mit Vorzeichen, -128..127 )
             unsigned char  (1 Byte, ohne Vorzeichen,  0..255)
             int            (2 Byte, mit Vorzeichen, -32768..32767)
             unsigned int   (2 Byte, ohne Vorzeichen, 0..65536)
             long           (4 Byte, mit Vorzeichen, -2^31..(2^31)-1)
             unsigned long  (4 Byte, ohne Vorzeichen, 0..(2^32)-1

>
> Sorry die etwas dümmlicheren Fragen, aber ich will das endlich mal
> komplett raffen :-) Proggen kann ich, nur fehlen mir ein paar
> wesentliche Grundlagen, die die Arbeit einfacher machen (lang, lang
> ist's her g).

Dann wäre eventuell ein Blick in die Literatur angebracht.

von André W. (sefiroth)


Lesenswert?

Danke für die Erläuterungen! Das mit double hab ich echt nicht gewusst, 
da es mir bisher immer (oder zumindest überwiegend) anders erklärt 
wurde... Schon krass, dass ich erst hier im Forum aufgeklärt werde ^_^

Zum Oxymoron: Nun ja, das "scheinbar widersprüchlich" sollte man dabei 
nicht übersehen... Ich habe schon viele Programme für AVRs geschrieben 
und die liefen auch 1A. Allerdings benötigten die meistens keine 
Fließkommarechnung. Und wenn doch nur bei der Berechnung von Konstanten, 
was ich dann einfach per Taschenrechner gelöst und per #define 
eingetragen habe (Im Programm genügte eigentlich immer 
Ganzzahlarithmetik). Um CAN-Botschaften zu versenden, Ports zu setzen, 
PWM zu erzeugen u.s.w. benötigt man ja nicht zwangsläufig 
Fließkommarechnung. Und da zwischen der eher dürftigen Vorlesung zu dem 
Thema (C / C++; zudem für PC) und meinen Anfängen in der 
Mikrocontrollerprogrammierung einige Zeit liegt (in der ich HTML/PHP/SQL 
geschrieben habe), ist halt das grundsätzlichste zum Thema Datentypen 
mit der Zeit verloren gegangen. Bei nem Ressourcenmonster wie nem PC 
muss man sich ja auch eher selten um Typenkonvertation Gedanken machen 
;-)

Aber Karl-Heinz hat auf jeden Fall recht. Ich werd mich mal nach 
Literaur zum Thema AVR-GCC umsehen und die Basics mal wieder pauken.

von Karl H. (kbuchegg)


Lesenswert?

André Wippich wrote:
> Danke für die Erläuterungen! Das mit double hab ich echt nicht gewusst,
> da es mir bisher immer (oder zumindest überwiegend) anders erklärt
> wurde... Schon krass, dass ich erst hier im Forum aufgeklärt werde ^_^
>
> Zum Oxymoron: Nun ja, das "scheinbar widersprüchlich" sollte man dabei
> nicht übersehen... Ich habe schon viele Programme für AVRs geschrieben
> und die liefen auch 1A. Allerdings benötigten die meistens keine
> Fließkommarechnung.

Und das ist auch gut so.
Besonders mit dem AVR-gcc mit seinen 4 Byte float/double
ist sehr schnell Ende der Fahnenstange. 5 bis 6 signifikante
Stellen sind nicht viel und bei komplizierteren arithmetischen
Ausdrücken schrumpft das sehr schnell zu 4 bis 5 signifikante
Stellen. Mit verheerenden Auswirkungen auf die Rechenergebnisse.

> Aber Karl-Heinz hat auf jeden Fall recht. Ich werd mich mal nach
> Literaur zum Thema AVR-GCC umsehen und die Basics mal wieder pauken.

Ich hab eigentlich weniger AVR-GCC sondern mehr 'C ganz allgemein'
gemeint.


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.