NTC Erfassung ohne Fließkomma-Arithmetik

#4130409
Lesenswert?

Die Code-Zeilen mit double-Arithmetik kosten ganze 1500bytes.
Hat jemand eine Idee zu einer codesparenden und trotzdem genauen 
Alternative?

double adc2Cel(long adcval){ // 10bit ADC > °C    1.5kByte - ganz schön 
viel
#define R25_ntc 10000        // NTC Nennwiderstand
#define B_ntc  3988          // NTC-Empfindlichkeit
#define Tn_ntc  298          // Bezugstemperatur
#define R  10000L            // Gegenwiderstand
    double s=0;
    long int rntc,tntc;
  if (adcval<10) adcval=10;                    // sinnvolle Grenzwerte
  if (adcval>1010) adcval=1010;                // -47 bis  182 °C
  rntc = ((long)adcval*R)/(1024-adcval);       // Rntc
  s = B_ntc + log((double)rntc/R25_ntc)*Tn_ntc;
  tntc= (long)1000*B_ntc*Tn_ntc/(long)s;
  s= (tntc-273150)/1000;
  return s;                                    // °C
}

Dank euch!

Bernd
#4130436
Lesenswert?

Klarer Fall für eine Wertetabelle.

Einfach in Excel (oder LibreOffice) die erwarteten ADC Werte für 
ganzzahlige Temperaturwerte ausrechnen und als Array (im Flash) 
speichern.

Der Code muss nur die Position in der Tabelle finden, für die Tabelle[x] 
<= adc_wert <= Tabelle[x+1] gilt, und dann linear interpolieren für 
einen recht genauen Temperaturwert.

Die Tabelle bräuchte allerdings für 230 Temperaturwerte 460 Byte 
Speicherplatz. Kannst Du den Bereich weiter einschränken? Können 
Temperaturen >100°C überhaupt auftreten?
Gast #4130463
Lesenswert?

Wie wäre es mit Festkommaarithmetik? Ich vermute du verwendest einen 
8bit Controller (AVR z.B.) und die float-Lib sowie die float/double 
Rechnerrei frisst schon alleine ziemlich speicher.

Also du verrechnest alle Variablen welche eine Kommastelle haben mit 
einem festen Faktor. Je nach dem wie groß die Auflösung sein soll: 10, 
100, 1000. Datentyp muss dann aber am besten int32 oder int64 sein.

Dann solltest du die Rechnung zerlegen um zu schauen ob du irgendwo eine 
integer-Überlauf bekommst. Wenn ja muss du die Berechnung in 
Teilschritte erledigen.
#4131537
Lesenswert?

Dank des Beitrages: Beitrag "Algorithmus für Logarithmus-Bererechnung?"
bin ich zu einer Lösung gekommen, die auf meinem Tiny 1.5kbyte spart.
Die feste Skalierung in ln_m() ist natürlich ein Kompromiss. Im 
mittleren Temperaturbereich ist aber eine Auflösung von 0.1 °C gut 
erreichbar.

long adc2_mC(long adcval){    // 10bit ADC > T in milli°C Einheiten
#define B_ntc    3988L        // NTC-Empfindlichkeit in K
#define Tn_ntc    298L        // 25°C Bezugstemperatur in K
#define Rn_ntc  10000L        // 10k Widerstand bei Bezugstemperatur
#define Tzero  273150L        // mK
#define R       10000L        // 10k Gegenwiderstand der Halbbruecke
long rntc,tntc,ne;
    if (adcval<10) adcval=10;      // 194.1°C
    if (adcval>1010) adcval=1010;   // -47.3°C
    rntc = ((long)adcval*R)/(1024-adcval);          // Rntc 1/1000 genau
    ne = B_ntc + (ln_m((rntc*1000)/Rn_ntc)*Tn_ntc)/1000; // Nenner in K
    tntc= (1000*B_ntc*Tn_ntc)/ne;   // mK= mK*K/K
    return (tntc-Tzero);      // mC
}

long ln_m(long x){// In/Output als milli-Zahlen von 
10(=0.01)..100000(=100)
#define SCALE 9    // Erweitern mit 2^SCALE und später SCALE abziehen
long lval;    // (Logarithmengesetz)
uint32_t rval;
   lval=(x*(1<<SCALE))/1000;  //
   if (lval>0xFFFF) lval=0xFFFF; if (lval<1) lval=1;// Begrenzen
   rval=fixlog2((uint16_t)lval);  // Aufruf mit Typ-Anpassung
   lval=(rval >> 16)*1000000 + ((rval & 0xffff) * 15625 >> 10) - 
SCALE*1000000;// ppm genau -9...7
   lval=((lval/100)*6931)/100000; // Umrechnung log2->ln -6.238 bis 
4.852
   return lval ;      // Ausgabe als milli-Zahl
}

uint32_t fixlog2(uint16_t x) { // Input 0...64k   Output: Fixkomma 16.16
  uint32_t q;                          // 2.30
  uint16_t y = 0;                      // 0.16
  uint8_t intpart = 15, i;             // 8.0

  while(!(x & 0x8000)) {x <<= 1;intpart--;} // die Ganzen ermitteln

  for(i=0; i<16; i++) {
    q = (uint32_t)x * x;               // x: 1.15, q: 2:30
    y <<= 1;
    if(q & (uint32_t)1 << 31)          // q >= 2.0?
      y |= 1;                          // 1-Bit -> y, q *= 2, q: 1.31
    else
      q <<= 1;                         // 0-Bit -> y, q: 1.31
    x = q >> 16;                       // x = q
  }
  return (uint32_t)intpart << 16 | y;  // return: 16.16
}  // Quelle: Beitrag "Algorithmus für Logarithmus-Bererechnung?"
Gast #4131684
Lesenswert?

Bernd K. schrieb:
> bin ich zu einer Lösung gekommen, die auf meinem Tiny 1.5kbyte spart.

Immerhin wissen wir jetzt, daß es sich um einen AVR handelt. Da aber 
auch schon kleine ATtiny mit 8 kB ROM verfügbar sind, sehe ich keinen 
Sinn darin, den notwendigen Code einsparen zu wollen.

Vergleicht man die gezeigten Quelltexte, geht bei der nun favorisierten 
Lösung die Les- und Wartbarkeit völlig in die Hose. Welche Fehler eine 
Programmänderung mit sich bringt, ist nicht abzusehen.
Für mich ist das eine schlechte Lösung!
Gast #4131885
Lesenswert?

m.n. schrieb:
> Na und? Reicht der Speicher nicht?
> Falls Dir sechs Stellen Auflösung reichen, rechne doch mit 'float' ;-)

Du Scherzbold!
Der TO fummelt mit nem AVR herum und dort gibt es sowieso kein double, 
sondern double wird einfach auf float heruntergebrochen.

Mein erster Tip wäre, einen simplen PIC16Fxxx zu nehmen, denn für diese 
µC hatte ich hier schon mal ne Gleitkomma-Lib gepostet.

Mein zweiter Tip wäre, sich mal die Steinhart-Hart-Formel aus dem Netz 
zu ziehen und zu benutzen. 1,5K für ne GK-Lib ist m.E. zwar relativ viel 
(wohl schlampig implementiert), aber allemal besser als irgendwelche 
Integer- Fummel-Varianten, die vor lauter internen Überläufen mehr 
schlecht als recht funktionieren.

Hatte da nicht mal jemand vor einiger Zeit geschrieben, daß man 
Gleitkomma bei µC seiner Ansicht nach überhaupt nicht braucht???

W.S.
#4132177
Lesenswert?

Erst sollte man mal das Problem spezifizieren und nicht gleich die 
Loesung vorwegnehmen. Also, man moechte einen NTC im Bereich von -47 bis 
182 °C betreiben, glaub ich gleich mal nicht. Und dann noch 0.1 Grad 
anzeigen wollen. Aha. Mit einem 10bit ADC. Sonst noch was ?

Woher kommen die -47 Grad ? Von einem Peltier ? Da muss man gut sein. 
Einen 2 stoeckigen und sehr gute Isolation. Nicht trivial.
Die 180 Grad ? Ein Epoyk NTC ist nur bis 150 oder so spezifiziert.
Mit 10 bit? Nie. NIE ! Schreib mal eine Simulation der Werte, und der 
Aufloesung auf dem PC. Aus dem Bauch heraus sag ich mal, die 10 bit 
reichen nicht mal fuer 1 Grad Aufloesung. Die Genauigkeit lassen wir mal 
weg.
Gast #4132185
Lesenswert?

W.S. schrieb:
> m.n. schrieb:
>> Na und? Reicht der Speicher nicht?
>> Falls Dir sechs Stellen Auflösung reichen, rechne doch mit 'float' ;-)
>
> Du Scherzbold!
> Der TO fummelt mit nem AVR herum und dort gibt es sowieso kein double,
> sondern double wird einfach auf float heruntergebrochen.

Wie ich sehe, hast Du den Scherz aber nicht verstanden ;-)
Mit einem AVR kann man durchaus 'double' mit 64 Bit rechnen und die Lib 
dafür ist etwas größer als die für 32 Bit 'float'.

Für mich bleibt es unsinnig, die Codegröße ohne Not 'optimieren' zu 
wollen. Auch die Ersatzlösungen brauchen Speicherplatz und haben hohes 
Fehlerpotential.
Jetzt fehlt noch das Totschlagargument, float wäre viel zu langsam. 
Vorweg: das ist genau so unsinning wie falsch!
#4132252
Lesenswert?

Ich rechne auch NTC, allerdings nur 3 mal pro Sekunde.
Ich rechne die Linearisierung mit float, 32bit, aus
folgenden Gruenden:
- Ein approximierendes Polynom muesste fuer die gewuenschte
  Aufloesung und Genauigkeit 6.Ordnung sein. Der
  Linearisierungspunkt waere dann aber ohne Float nicht
  mehr frei waehlbar.
- Ich verwende einen 24bit Wandler
- ich moechte besser als 5mK Aufloesung, zum Regeln
- der NTC ist spezifiziert als 1%
- die absolute Genauigkeit ist vielleicht 2 Grad, begrenzt
  durch den NTC

Alle nachfolgenden Berechnungen gehen dann mit 32 bit
Integern.
Moderator (Firma: Titel) Persönliche Seite #4132296
Lesenswert?

Bernd K. schrieb:
> Hat jemand eine Idee zu einer codesparenden und trotzdem genauen
> Alternative?
Ich würde eine Lookuptabelle und lineare Interpolation vorschlagen.

Definiere "genau".
Bernd K. schrieb:
> if (adcval<10) adcval=10;      // 194.1°C
> if (adcval>1010) adcval=1010;   // -47.3°C
Vergiss diese Kommastellen ganz schnell. Vermutlich hat nämlich dein NTC 
schon 5% Fehler...
Gast #4132317
Lesenswert?

Lothar Miller schrieb:
> Vergiss diese Kommastellen ganz schnell. Vermutlich hat nämlich dein NTC
> schon 5% Fehler...

Wobei das nur der einfachere Teil des Problems ist (daß man mit einem 
anderen Sensor verbessern kann). Wo die Temperatur gemessen wird ist ein 
viel größeres Problem. Selbst innerhalb eines festen Körpers sind 
Temperaturgradienten von mehreren Grad, die dann auch noch je nach 
Wetterlage unterschiedlich sind, nichts ungewöhnliches. Statt 
aufwändiger Rechnungen kann man auch die Nachkommastellen aus 
/dev/random lesen.

MfG Klaus
#4132358
Lesenswert?

Vielen Dank für die Hinweise!
Das schöne am NTC ist ja seine Nichtlinearität.
Im konkreten Einsatz als Drainback-Steuerung einer Thermischen 
Solaranlage ist das sehr praktisch. Außerdem werden dort üblicherweise 
nur PT100 oder NTCs verbaut. Die Anforderung bestand aus zwei Punkten:
1. genaue Messungen um die 0°C (Frostschutzfunktion) und
2. über den ganzen real auftretenden T-Bereich von sagen wir mal -30 bis 
+150°C überhaupt Meßergebnisse zu bekommen und keinen Over-/Underflow.

LUTs sind sicher ein pragmatischer Ansatz. Der Weg über den 
Festpunkt-Logarithmus funktioniert aber auch.

OK, Profis nehmen einfach einen größeren Prozessor und haben die 
Probleme nicht. Hab mich aber auf den ATtiny85 mit Funk-Interface HM-TRP 
eingeschossen. Ich finde es immer wieder beeindruckend, was man aus dem 
Kistchen rausholen kann. Dank dem Bootloader von Peter Dannegger fürs 
Update via Klinkenbuchse und Debugging via Soft-UART (beides 1-Wire), 
kommt man trotz der knappen Resourcen schnell zum Erfolg. Man darf nur 
nicht auf die Idee kommen, dicke Libs einzubinden.

8 Pins sind wenig, aber es reicht mit ein Paar Kniffen für die 
Ansteuerung/Auslesung von:
1x Ventil
1x Pumpe
1x NTC
1x Messung von Pumpen oder Ventilstrom
3 Tasten
2 LEDs
1x RS232 (1-Wire)
1x Servomotor mit Stromüberwachung
... und der Resetpin ist immer noch aktiv :-)

Wen es interessiert, bitte melden.
Gast #4132369
Lesenswert?

Jetzt Nicht schrieb:
> - Ein approximierendes Polynom muesste fuer die gewuenschte
>   Aufloesung und Genauigkeit 6.Ordnung sein.

Eh?

- Der Fehler der Approximination nimmt bei höheren Graden zu
- Die Auflösung ist unabhängig vom Grad
- Üblicherweise erhöht man die Anzahl der Stützstellen bei konstanten 
Grad (Siehe Splines, Teillinearisierung, etc)

>   Der
>   Linearisierungspunkt waere dann aber ohne Float nicht
>   mehr frei waehlbar.

Quatsch. Natürlich ist er genauso frei wählbar. Float stellt einen 
Teilbereich der reellen Zahlen (also p/q mit p und q aus den Ganzen 
Zahlen) dar. Den geforderten Bereich -50 bis +200 lässt sich 
hervorragend mit < 0.01er Granularität darstellen. Bei konstantem q 
bleibt reine Integer Arithmetik. Bei gleicher Speichergröße, lässt sich 
hier sogar eine kleinere Granularität erreichen und damit die Stütz- und 
Interpolationsstellen freier wählen.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren