Forum: Compiler & IDEs Problem beim Bau eines Thermometers


von Valentin B. (nitnelav) Benutzerseite


Lesenswert?

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_t inttemp;//The rounded actual temperature in degree celsius
11
volatile char links;//Die Anzahl der 10er-Stellen
12
volatile char rechts;//Die Anzahl der 1er-Stellen
13
volatile char delay; //Die Pause zwischen zwei Anzeigen
14
15
16
//The overflow-interrupt, 100 bytes
17
ISR(TIMER1_OVF_vect){
18
  if (PORTB > 0){
19
  PORTB = 0;}
20
21
  else{
22
  if(links > 0){
23
    PORTB = 1;
24
    links --;
25
  }  
26
    else{
27
      if(rechts > 0){
28
      PORTB = 2;
29
      rechts --;
30
      }
31
      else{
32
        if(delay > 0){
33
        delay --;
34
        }
35
        }
36
    }
37
  }
38
}
39
40
//Inits, 26 Bytes
41
void init(){
42
  //Init ADC
43
  //Reference @ 1.1 Volts, Temp-Sense-Channel
44
  ADMUX = (1<<REFS1)| (1<<MUX0) | (1<<MUX1) | (1<<MUX2) | (1<<MUX3);
45
  ADCSRA = (1<<ADPS1) | (1<<ADEN) | (1<<ADSC) | (1<<ADATE);
46
  
47
  //Init PORTS
48
  DDRB = (1<<PB0) | (1<<PB1);
49
  
50
  //Init Timer
51
  //Normal Mode, Prescaler 512
52
  TCCR1 = (1<<CS13) | (1<<CS11)|(1<<CS10);
53
  TIMSK = (1<<TOIE1);
54
  sei();
55
}
56
57
int main(){
58
  //init();
59
  while(1){
60
    //2282 bytes flash, 264 bytes RAM
61
    if((links == 0) && (rechts == 0) && (delay == 0)){
62
63
      inttemp =((ADCW - 282/*.16*/) * 0.8621)/;
64
      
65
      links = inttemp / 10;
66
      
67
      rechts = inttemp % 10;//10 bytes flash for one operation?
68
69
      delay = 2;
70
    }
71
    
72
  }
73
  return 0;
74
}
75
76
Die problematische Zeile scheint [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

von Valentin B. (nitnelav) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

Valentin Buck schrieb:

> Die problematische Zeile scheint
1
> inttemp =((ADCW - 282/*.16*/) * 0.8621)/;
> 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

1
  inttemp =( (int32_t)(ADCW - 282) * 8621) / 10000;

von Karl H. (kbuchegg)


Lesenswert?

Bei Berechnungen immer ein Auge auf die Datentypen haben und auch auf 
die Grenzwerte, die nicht überschritten werden dürfen. Auch nicht bei 
Zwischenergebnissen!

http://www.mikrocontroller.net/articles/FAQ#Datentypen_in_Operationen

von Edi R. (edi_r)


Lesenswert?

Statt
1
...  * 8621) / 10000;

würde ich schreiben:
1
...  * 3531) / 4096;

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:
1
inttemp =( (int32_t)(ADCW - 282) * 3531) >> 12;

von Karl H. (kbuchegg)


Lesenswert?

Edi R. schrieb:

>
1
inttemp =( (int32_t)(ADCW - 282) * 3531) >> 12;

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
)

von Edi R. (edi_r)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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)

von Edi R. (edi_r)


Lesenswert?

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.

von Valentin B. (nitnelav) Benutzerseite


Lesenswert?

DANKE!!
Es funktioniert jetzt mit dem neuen Code:
1
inttemp =( (int32_t)(ADCW - 282) * 3531) / 4096;
Es blinkt sehr schön die Werte raus.
Danke an alle, die mit geholfen haben.
Mit freundlichen Grüßen,
Valentin Buck

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.