Hallo,
ich hab mal wieder ein Problem mit C.
Ich will ein Thermometer mit einem zweipunktkalibriertem ATtiny25 bauen,
dass ähnlich wie das im elektor vorgestellte µ-Thermometer die
Temperatur als Folge von Lichtblitzen ausgibt.
Mein Problem besteht darin, den Wert, der vom ADC kommt in °C
umzurechnen. Wenn ich dass einfach "normal" da hinschreibe, wird der
Quellcode viel zu groß. Und wenn ich das nach den Regeln in
Festkommaarithmetik mache, dann blinkts nicht. Ich hab es schon mit
konstanten Werten ausprobiert, das Blinken und der ADC sind nicht
schuld.
Mein Problem liegt in der Umwandlung.
Hier mein Code:
1
//Basic IO
2
#include<avr/io.h>
3
4
//Interrupt handling
5
#include<avr/interrupt.h>
6
7
//Integer types
8
#include<stdint.h>
9
10
int16_tinttemp;//The rounded actual temperature in degree celsius
11
volatilecharlinks;//Die Anzahl der 10er-Stellen
12
volatilecharrechts;//Die Anzahl der 1er-Stellen
13
volatilechardelay;//Die Pause zwischen zwei Anzeigen
rechts=inttemp%10;//10 bytes flash for one operation?
68
69
delay=2;
70
}
71
72
}
73
return0;
74
}
75
76
DieproblematischeZeilescheint[c]
77
inttemp=((ADCW-282/*.16*/)*0.8621)/;
zu sein, da diese Zeile mehr als 2000 Bytes flash und 260 bytes RAM
frisst.
Ich hab es auch schon mit
1
inttemp=((ADCW-282/*.16*/)*8621)/10000;
ausprobiert, doch das geht anscheinend auch nicht (Es blinkt nicht).
Die Nachkommastelle der Subtraktion habe ich auskommentiert, da das mir
fast 1000 Bytes flash gebracht hat und die Genauigkeit nicht allzu sehr
darunter leidet.
Weiß jemand, wo mein Fehler liegt?
Ich würde mich freuen, wenn ihr mir helfen könntet.
Mit freundlichen Grüßen,
Valentin Buck
Ich habe jetzt den ersten Fehler gefunden:
Init(); war auskommentiert. Jetzt blinkt er zwar im Festkomma-Modus,
aber er blinkt immer noch "falsch". Wird wohl an der Umwandlung liegen.
mit freundlichen Grüßen,
Valentin Buck
> zu sein, da diese Zeile mehr als 2000 Bytes flash und 260 bytes RAM> frisst.
Klar.
Das benötigt Floating Point Arithmetik. Und die kostet.
Allerdings erstaunen mich die Zahlen schon ein wenig. Hast du den
Optimizer eingeschaltet?
> Ich hab es auch schon mit>
1
inttemp=((ADCW-282/*.16*/)*8621)/10000;
> ausprobiert, doch das geht anscheinend auch nicht (Es blinkt nicht).>
Dann rechnen wir doch mal ein wenig.
Im Hinterkopf immer: sobald ein Zwischenergebnis größer als 32767
auftaucht -> Mööp - mit int wird das nichts.
Der maximale Wert, den du vom ADC bekommen kannst, ist 1023
1023 - 282 macht 741 soweit in Ordnung, da passiert nichts
741 * 8621 macht 6388161 Möööp - über der magichen Grenze für
16 Bit Berechnungen
So gehts also nicht.
Du musst den Compiler zwingen, zumindest die Multiplikation in 32 Bit
durchzuführen. 32 Bit, d.h. das Ergebnis darf nicht größer als 2^31-1,
d.h. 2147483648 sein (ungefähr 2 tausend Millionen). 6388161 (ungefähr 6
Millionen) ist kleiner als diese Grenze d.h. 32 Bit reichen
Der Unterschied ist mit 0,005% weit unterhalb der Messgenauigkeit, aber
statt der Division durch 10000 kommt eine Division durch 4096 ins Spiel,
die man durch eine einfache Verschiebung nach rechts um 12 Stellen
erreicht:
Schreibs aber dann bitte auch als Division.
Da hier negative Ergebnisse möglich sind, macht es hier nämlich einen
Unterschied.
(
Und wenn das ganze unsigned wäre, wäre eine Division trotzdem
angebrachter. Der Compiler ersetzt das dann schon durch Shiften,
wenn es möglich ist
Und genau das ist der springende Punkt: Der Compiler achtet darauf, ob
diese Ersetzung möglich ist, was die meisten der sog. cleveren
Programmierer (nicht wesentlich abwertend gemeint) eben nicht tun
)
Nun, ich habs (mehrfach) ausprobiert: Wenn das Argument ein
vorzeichenbehafteter Typ ist, wird arithmetisch geschoben, d. h. von
links (also in das werthöchste Bit) werden keine "0" geschoben, sondern
der Inhalt bleibt erhalten. Ein negativer Wert bleibt also negativ. Nur
wenn ein unsigned Irgendwas geschoben wird, kommen Nullen von links
rein.
Wieder ein Beispiel, wie gut der Compiler mitdenkt :-)
Aber prinzipiell hast Du schon recht, ich würde auch schreiben:
1
inttemp=((int32_t)(ADCW-282)*3531)/4096;
Zwar nicht, um dem Compiler die Optimierungsmöglichkeit erkennen zu
lassen, sondern damit ich nach 2 Jahren noch weiß, was ich da eigentlich
erreichen wollte.
Edi R. schrieb:> der Inhalt bleibt erhalten. Ein negativer Wert bleibt also negativ.
Dann schieb mal das Bitmuster für -9 um eine Stelle nach rechts. Wenn
dann nicht -4 rauskommt (wie bei einer Division), ist das Ergebnis im
Sinne einer Division wie sie in C verstanden wird, falsch. :-)
(Um es kurz zu machen: Das Schieben entspricht immer noch einer
"Division" aber es unterliegt anderen Rundungsregeln als eine echten
Division)
Hmmm, das stimmt. Beim reinen Verschieben kommt -5 heraus. Das bedeutet
für mich: Wenn man das Einbinden einer Divisionsroutine unbedingt
verhindern will, muss man explizit über eine Verschiebung "dividieren",
wobei man einen zusätzlichen Fehler von +/-1 LSB (eher +0 / -1 LSB)
hinnehmen muss.