Genaue Berechnung der PT100/PT1000-Temperatur aus dem Widerstand
TL;DR für ungeduldige Leser
Die Berechnung von PT100/PT1000-Temperaturen leidet unter Genauigkeitsproblemen bei großen Minustemperaturen. UliEngineering implementiert einen auf Polynomfit basierenden Algorithmus, der einen Spitzenfehler von $58.6 \mu{\degree}C$ über den gesamten definierten Temperaturbereich von -200 {\degree}C bis +850\u00a0\u00b0C bietet.
Verwende dieses Code-Snippet (ersetze pt1000_ durch pt100- um PT100-Koeffizienten zu verwenden), um eine genaue Temperatur (in Grad Celsius) z.B. für einen Widerstand von 829.91 \u03a9 eines PT1000\u00a0-Sensors zu berechnen.
from UliEngineering.Physics.RTD import pt1000_temperature
# Die folgenden Aufrufe sind äquivalent und geben -43.2316359463 aus
print(pt1000_temperature("829.91 \u2126"))
print(pt1000_temperature(829.91))Du installierst die Bibliothek (kompatibel mit Python 3.2+) mit
pip install -U UliEngineeringDas Problem
Die Formel zur Berechnung des PT100/PT1000-Widerstands aus der Temperatur ist wohlbekannt (siehe z.B. Thermometrics):
$R_t,=,R_0\cdot(1,+,A\cdot t,+,B\cdot t^2,+,C\cdot (t-100)\cdot t^3)$
wobei $t$ die Temperatur ist, $R_0$ der Widerstand bei null \degreeC ist (d.h. 100 \u03a9 für PT100 und 1000 \u03a9 für PT1000).
Die verbleibenden Parameter A, B und C hängen vom verwendeten Temperaturstandard ab und können vom Sensorhersteller für zusätzliche Genauigkeit gemessen werden. Für den ITU-90-Standard gelten (siehe z.B. code10.info)
$$\begin{array}{lll}A&=&3.9083\cdot10^{-3}\\B\,&=&\,-5.7750\cdot10^{-7}\\C\,&=&\,\begin{cases}-4.1830\cdot 10^{-12}&\text{for}\ t&\lt& 0{\degree}C\\0.0&\text{for}\ t &\geq&\;0\;{\degree}C\end{cases}\end{array}$$Darüber hinaus wird $C$ für Temperaturen > 0°C auf 0 gesetzt, was die Formel vereinfacht zu:
$$R_t\,=\,R_0\cdot(1\,+\,A\cdot t\,+\,B\cdot t^2)$$Es ist offensichtlich, dass bei Kenntnis dieser Informationen der Widerstand bei einer gegebenen Temperatur ohne Fehlerterm berechnet werden kann.
Die Probleme entstehen beim Versuch, die Gleichung im allgemeinen Fall zu lösen.
Die Formel für $t,\geq,0$ ist leicht lösbar (Quelle für die gezeigte Form):
$$t\,=\,\frac{-R_0\cdot\,a\,+\,\sqrt{R_0^2\cdot a^2\,-\,4\cdot R_0\cdot B\cdot (R_0-R_t)}}{2\cdot R_0\cdot b}$$Obwohl die Formel für $t\lt0 {\degree}C$ exakte algebraische Lösungen hat, ist sie so groß, dass sie wahrscheinlich nicht auf deinen Bildschirm passt. Es ist daher als unpraktikabel anzusehen, diese Formel zu implementieren, da man Einfachheit und Geschwindigkeit für eine exakte Lösung opfern würde.
Es ist jedoch inhärent wahr, dass ein gegebener Sensor nur bis zu einer bestimmten Genauigkeit arbeiten kann. Daher ist eine Näherungslösung mit ausreichender Genauigkeit für alle praktischen Anwendungen ausreichend.
Die Lösung: Polynom an die Fehlerfunktion anpassen
Zunächst dachte ich daran, eine iterative Funktion zu implementieren, die eine anfängliche Temperaturschätzung verfeinert. Während dieser Ansatz sicherlich funktionieren würde, da eine exakte Fehlerfunktion verfügbar ist, skaliert er nicht gut und hat keine deterministische Laufzeit.
Lass uns die Fehlerfunktion visualisieren, die ohne Korrekturterm vorhanden ist. Da der $C$-Term für $t\geq0{\degree}C$ auf 0 reduziert wird, interessieren wir uns nur für den Bereich von -200 °C bis 0°C (PT100/PT1000-Sensoren sind in den relevanten Standards unterhalb von -200°C nicht definiert, aber prinzipiell lässt sich diese Methode bis 0°K erweitern).
Die Methode, die ich vorstellen werde — sowie alle Werkzeuge, die zu ihrer Validierung notwendig sind — ist in meiner UliEngineering-Bibliothek im Modul UliEngineering.Physics.RTD implementiert.
Mit matplotlib und UliEngineering ist die Erstellung dieses Plots in nur 12 Codezeilen möglich:
import matplotlib.pyplot as plt
from UliEngineering.Physics.RTD import *
import numpy as np
plt.style.use("ggplot")
plt.gcf().set_size_inches(12, 4)
plt.title("Unkorrigierter PT1000-Fehler")
plt.ylabel("Relativer Fehler [\u00b0C]")
plt.xlabel("Absolute Ist-Temperatur [\u00b0C]")
# 1M Datenpunkte Referenztemperaturen erstellen
temp = np.linspace(-200.0, 0.0, 1000000)
# Abweichung plotten: Referenztemperatur - berechnete Temperatur
x, y, _ = checkCorrectionPolynomialQuality(1000.0, temp, poly=noCorrection)
plt.plot(temp, y)
plt.savefig("PT1000-uncorrected.svg")Wir rufen checkCorrectionPolynomialQuality() mit dem vorgefertigten noCorrection-Polynom auf, das immer null ergibt: In dieser Konfiguration berechnet die Funktion die Widerstände aus unseren Referenztemperaturen und berechnet dann die tatsächlichen Referenztemperaturwerte aus diesen Widerständen zurück. Sie gibt drei Werte zurück: - Ein numpy-Array von Widerständen, entsprechend unseren Referenztemperaturen - Ein numpy-Array der Abweichung von der Referenztemperatur bei einem gegebenen Widerstand / Temperatur, in °C - Ein Skalar für die maximale absolute Abweichung als Qualitätsindikator (d.h. der schlechteste zu erwartende Fehler)
Es ist leicht zu beobachten, dass der Fehler zwar fast 2,5°C bei -200°C erreicht, er aber monoton und gleichmäßig stetig ist.
Unser Ansatz besteht daher darin, ein Polynom an diese Funktion anzupassen, wobei die Abweichung von der Referenztemperatur mithilfe von np.polyfit minimiert wird.
Dieser Algorithmus ist in der Funktion computeCorrectionPolynomial() aus UliEngineering verfügbar. Es wurde experimentell ermittelt, dass ein Polynom 5. Grades deutlich bessere Ergebnisse liefert als Polynome höheren oder niedrigeren Grades. Die Funktion lässt dich jedoch einen benutzerdefinierten Grad angeben, wenn du mit den Parametern experimentieren möchtest.
plt.gcf().clf() # Clear current figure
plt.gcf().set_size_inches(12, 4)
plt.title("Polynomiell korrigierter PT1000-Fehler")
plt.ylabel("Relativer Fehler [°C]")
plt.xlabel("Absolute Ist-Temperatur [°C]")
# Korrekturpolynom berechnen
mypoly = computeCorrectionPolynomial(1000.0)
# Abweichung plotten: Referenztemperatur - berechnete Temperatur
_, y, pp = checkCorrectionPolynomialQuality(1000.0, temp, poly=mypoly)
plt.plot(temp, y)
plt.savefig("PT1000-corrected.svg")
print("Spitze-Spitze-Fehler: {0}".format(pp))Es ist deutlich zu erkennen, dass der verbleibende Fehler nun extrem klein ist: Sein maximaler absoluter Wert über den gesamten definierten Temperaturbereich beträgt nur $58.6,\mu{\degree}C$. Dies gilt als ausreichend für alle praktischen Anwendungen außer der Referenztemperaturmessung in der Metrologie (und selbst dort wenige Zehntel Mikrograd.
Für ein einfaches (und relativ schnell berechenbares) Polynom 5. Grades sind diese Ergebnisse erstaunlich gut. Die aktuelle Git-Version der UliEngineering-Bibliothek implementiert diesen Algorithmus unter Verwendung vorausberechneter Polynome, die automatisch ausgewählt werden, wenn du $R_0=100.0$ oder $R_0=1000.0$ an ptx_temperature() übergibst, welche intern von pt100_temperature() und pt1000_temperature() aufgerufen wird. Für andere $R_0$-Werte musst du das Polynom manuell berechnen und an ptx_temperature() übergeben.
Natürlich können auch NumPy-Arrays und ähnliche Objekte an die Funktionen in UliEngineering übergeben werden.