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
unsignedintadc_wert=0;// wird durch Funktion zyklisch aktualisiert
11
...
12
13
intmain(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!
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.
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)
@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. ;-)
@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:
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%)
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!
@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
> 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
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.
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=(unsignedchar)(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).
@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
André Wippich wrote:
>> Karl heinz Buchegger wrote (sinngemäß):>
1
>OCR2=(unsignedchar)(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.
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.
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.