mikrocontroller.net

Forum: Compiler & IDEs Problem beim Bau eines Thermometers


Autor: Valentin Buck (nitnelav) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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:
//Basic IO
#include <avr/io.h>

//Interrupt handling
#include <avr/interrupt.h>

//Integer types
#include <stdint.h>

int16_t inttemp;//The rounded actual temperature in degree celsius
volatile char links;//Die Anzahl der 10er-Stellen
volatile char rechts;//Die Anzahl der 1er-Stellen
volatile char delay; //Die Pause zwischen zwei Anzeigen


//The overflow-interrupt, 100 bytes
ISR(TIMER1_OVF_vect){
  if (PORTB > 0){
  PORTB = 0;}

  else{
  if(links > 0){
    PORTB = 1;
    links --;
  }  
    else{
      if(rechts > 0){
      PORTB = 2;
      rechts --;
      }
      else{
        if(delay > 0){
        delay --;
        }
        }
    }
  }
}

//Inits, 26 Bytes
void init(){
  //Init ADC
  //Reference @ 1.1 Volts, Temp-Sense-Channel
  ADMUX = (1<<REFS1)| (1<<MUX0) | (1<<MUX1) | (1<<MUX2) | (1<<MUX3);
  ADCSRA = (1<<ADPS1) | (1<<ADEN) | (1<<ADSC) | (1<<ADATE);
  
  //Init PORTS
  DDRB = (1<<PB0) | (1<<PB1);
  
  //Init Timer
  //Normal Mode, Prescaler 512
  TCCR1 = (1<<CS13) | (1<<CS11)|(1<<CS10);
  TIMSK = (1<<TOIE1);
  sei();
}

int main(){
  //init();
  while(1){
    //2282 bytes flash, 264 bytes RAM
    if((links == 0) && (rechts == 0) && (delay == 0)){

      inttemp =((ADCW - 282/*.16*/) * 0.8621)/;
      
      links = inttemp / 10;
      
      rechts = inttemp % 10;//10 bytes flash for one operation?

      delay = 2;
    }
    
  }
  return 0;
}

Die problematische Zeile scheint [c]
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
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

Autor: Valentin Buck (nitnelav) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Valentin Buck schrieb:

> Die problematische Zeile scheint
> 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
>
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

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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#Datent...

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Statt
...  * 8621) / 10000;

würde ich schreiben:
...  * 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:
inttemp =( (int32_t)(ADCW - 282) * 3531) >> 12;

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Edi R. schrieb:

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

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht 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:
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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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)

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Valentin Buck (nitnelav) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
DANKE!!
Es funktioniert jetzt mit dem neuen Code:
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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.