Configtool: add Steinhart-Hart algorithm and thermistor presets.

The Steinhart-Hart algorithm allows more precise thermistor
tables, but also requires more parameters, which aren't
available for all thermistors. Accordingly, add support for both,
the traditional logic using the beta parameter as well as the new
one.

This also adds thermistor-presets, so users can simply choose
from a pulldown-menu to set their thermistor.

Also, identical thermistors get merged into one table, saving
binary size.

Last not least, a few bugs in this area got fixed.

Usually, all these things go into separate commits, but they were
contributed all in one and separating them is a bit error-prone
for little gain.

This should address issue #130, #134 and #135.
This commit is contained in:
jbernardis 2015-05-06 19:37:51 -04:00 committed by Markus Hitter
parent 3d2c307c56
commit 610e1d169b
9 changed files with 679 additions and 180 deletions

View File

@ -46,3 +46,23 @@ r1 = 0
numtemps = 25
maxadc = 1023
minadc = 0
# Define thermistor presets. These can either be defined to use the
# beta argorithm by specifying 4 parameters:
# R0, beta, Rp, Vadc
#
# or to use the Steinhart-Hart algorithm by specifying 7 parameters:
# Rp, T0, R0, T1, R1, T2, R2
#
# TODO: this should be moved into a file which isn't overridden by a local
# file (here: configtool.ini). Similar to config/protectedfiles.py.
[thermistors]
EPCOS 100K (B5754061104) = 100000, 4066, 4700, 5.0
EPCOS 100K (B57560G1104F) = 100000, 4092, 4700, 5.0
EPCOS 100K (B57560G104F) = 100000, 4036, 4700, 5.0
RRRF 100K = 100000, 3960, 4700, 5.0
RRRF 10K = 10000, 3964, 1600, 5.0
RS 10K = 10000, 3480, 1600, 5.0
Honeywell 100K = 100000, 3974, 4700, 5.0
ATC Semitec 104GT-2 = 100000, 4267, 4700, 5.0

View File

@ -3,11 +3,19 @@ import wx
from configtool.data import (pinNames, BSIZESMALL, sensorTypes, offsetTcLabel,
offsetChLabel, reInteger, reFloat)
MODE_NONTHERM = 0
MODE_THERMISTOR = 1
METHOD_BETA = 0
METHOD_SH = 1
MethodLabels = ["Beta", "Steinhart-Hart"]
labelWidth = 160
class AddSensorDlg(wx.Dialog):
def __init__(self, parent, names, pins, font, name = "", stype = "",
pin = "", r0 = "", beta = "", r2 = "", vadc = "",
modify = False):
pin = "", params = [], modify = False):
if modify:
title = "Modify temperature sensor"
else:
@ -19,14 +27,28 @@ class AddSensorDlg(wx.Dialog):
self.names = names
self.choices = pins
self.modify = modify
self.presets = parent.thermistorpresets
labelWidth = 160
if len(params) == 0:
self.currentMethod = METHOD_BETA
self.currentMode = MODE_NONTHERM
else:
self.currentMode = MODE_THERMISTOR
if len(params) == 4:
self.currentMethod = METHOD_BETA
else:
self.currentMethod = METHOD_SH
self.nameValid = False
self.R0Valid = False
self.betaValid = False
self.R2Valid = False
self.vadcValid = False
self.param0Valid = False
self.param1Valid = False
self.param2Valid = False
self.param3Valid = False
self.param4Valid = False
self.param5Valid = False
self.param6Valid = False
sizer = wx.BoxSizer(wx.VERTICAL)
hsz = wx.BoxSizer(wx.HORIZONTAL)
hsz.AddSpacer((10, 10))
@ -75,6 +97,7 @@ class AddSensorDlg(wx.Dialog):
if not found:
ch.SetSelection(0)
stStart = sl[0]
self.chType = ch
lsz.Add(ch)
@ -103,76 +126,161 @@ class AddSensorDlg(wx.Dialog):
csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL)
st = wx.StaticText(self, wx.ID_ANY, "R0:", size = (labelWidth, -1),
st = wx.StaticText(self, wx.ID_ANY, "", size = (labelWidth, -1),
style = wx.ALIGN_RIGHT)
st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label0 = st
self.tcR0 = wx.TextCtrl(self, wx.ID_ANY, r0)
self.tcR0.SetFont(font)
self.tcR0.Bind(wx.EVT_TEXT, self.onR0Entry)
lsz.Add(self.tcR0)
self.tcR0.SetToolTipString("Nominal resistance of the thermistor. "
"Typically 10000 ( = 10k) or 100000 ( = 100k).")
csz.Add(lsz)
csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL)
st = wx.StaticText(self, wx.ID_ANY, "Beta:", size = (labelWidth, -1),
style = wx.ALIGN_RIGHT)
st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.tcBeta = wx.TextCtrl(self, wx.ID_ANY, beta)
self.tcBeta.SetFont(font)
self.tcBeta.Bind(wx.EVT_TEXT, self.onBetaEntry)
lsz.Add(self.tcBeta)
self.tcBeta.SetToolTipString("Thermistor beta value. Can be found in the "
"datasheet or measured like described in http"
"://reprap.org/wiki/MeasuringThermistorBeta")
vals = params + ["", "", "", "", "", "", ""]
self.param0 = wx.TextCtrl(self, wx.ID_ANY, vals[0])
self.param0.SetFont(font)
self.param0.Bind(wx.EVT_TEXT, self.onParam0Entry)
lsz.Add(self.param0)
csz.Add(lsz)
csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL)
st = wx.StaticText(self, wx.ID_ANY, "R2:", size = (labelWidth, -1),
st = wx.StaticText(self, wx.ID_ANY, "", size = (labelWidth, -1),
style = wx.ALIGN_RIGHT)
st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label1 = st
self.tcR2 = wx.TextCtrl(self, wx.ID_ANY, r2)
self.tcR2.SetFont(font)
self.tcR2.Bind(wx.EVT_TEXT, self.onR2Entry)
lsz.Add(self.tcR2)
self.tcR2.SetToolTipString("Resistance value of the secondary resistor. "
"This is not a property of the thermistor, but "
"one of the board. Typical values are 4700 "
"( = 4k7 ohms) or 1000 ( = 1k ohms).\n\n"
"As these resistors are typically +-5%, "
"measuring the actually used one can increase "
"accuracy substantially.")
self.param1 = wx.TextCtrl(self, wx.ID_ANY, vals[1])
self.param1.SetFont(font)
self.param1.Bind(wx.EVT_TEXT, self.onParam1Entry)
lsz.Add(self.param1)
csz.Add(lsz)
csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL)
st = wx.StaticText(self, wx.ID_ANY, "Vadc:", size = (labelWidth, -1),
st = wx.StaticText(self, wx.ID_ANY, "", size = (labelWidth, -1),
style = wx.ALIGN_RIGHT)
st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label2 = st
self.tcVadc = wx.TextCtrl(self, wx.ID_ANY, vadc)
self.tcVadc.SetFont(font)
self.tcVadc.Bind(wx.EVT_TEXT, self.onVadcEntry)
lsz.Add(self.tcVadc)
self.tcVadc.SetToolTipString("Comparison voltage used by the controller. "
"Usually the same as the controller's supply "
"voltage, 3.3 or 5.0 (volts).")
self.param2 = wx.TextCtrl(self, wx.ID_ANY, vals[2])
self.param2.SetFont(font)
self.param2.Bind(wx.EVT_TEXT, self.onParam2Entry)
lsz.Add(self.param2)
csz.Add(lsz)
csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL)
st = wx.StaticText(self, wx.ID_ANY, "", size = (labelWidth, -1),
style = wx.ALIGN_RIGHT)
st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label3 = st
self.param3 = wx.TextCtrl(self, wx.ID_ANY, vals[3])
self.param3.SetFont(font)
self.param3.Bind(wx.EVT_TEXT, self.onParam3Entry)
lsz.Add(self.param3)
csz.Add(lsz)
csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL)
st = wx.StaticText(self, wx.ID_ANY, "", size = (labelWidth, -1),
style = wx.ALIGN_RIGHT)
st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label4 = st
self.param4 = wx.TextCtrl(self, wx.ID_ANY, vals[4])
self.param4.SetFont(font)
self.param4.Bind(wx.EVT_TEXT, self.onParam4Entry)
lsz.Add(self.param4)
csz.Add(lsz)
csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL)
st = wx.StaticText(self, wx.ID_ANY, "", size = (labelWidth, -1),
style = wx.ALIGN_RIGHT)
st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label5 = st
self.param5 = wx.TextCtrl(self, wx.ID_ANY, vals[5])
self.param5.SetFont(font)
self.param5.Bind(wx.EVT_TEXT, self.onParam5Entry)
lsz.Add(self.param5)
csz.Add(lsz)
csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL)
st = wx.StaticText(self, wx.ID_ANY, "", size = (labelWidth, -1),
style = wx.ALIGN_RIGHT)
st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label6 = st
self.param6 = wx.TextCtrl(self, wx.ID_ANY, vals[6])
self.param6.SetFont(font)
self.param6.Bind(wx.EVT_TEXT, self.onParam6Entry)
lsz.Add(self.param6)
csz.Add(lsz)
csz.AddSpacer((10, 10))
csz.AddSpacer((10, 10))
hsz.Add(csz)
hsz.AddSpacer((10, 10))
csz = wx.BoxSizer(wx.VERTICAL)
csz.AddSpacer((30, 45))
lsz = wx.BoxSizer(wx.HORIZONTAL)
st = wx.StaticText(self, wx.ID_ANY, "Presets:",
size = (70, -1), style = wx.ALIGN_RIGHT)
st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel)
choices = ["<none>"] + sorted(self.presets.keys())
ch = wx.Choice(self, wx.ID_ANY, choices = choices)
ch.SetFont(font)
ch.Enable(False)
ch.SetSelection(0)
self.chPresets = ch
ch.Bind(wx.EVT_CHOICE, self.onPresetChoice)
lsz.Add(ch)
csz.Add(lsz)
csz.AddSpacer((10, 50))
b = wx.StaticBox(self, wx.ID_ANY, "Temp Table Algorithm")
b.SetFont(font)
sbox = wx.StaticBoxSizer(b, wx.VERTICAL)
sbox.AddSpacer((5, 5))
style = wx.RB_GROUP
self.rbMethod = []
for k in MethodLabels:
rb = wx.RadioButton(self, wx.ID_ANY, k, style = style)
rb.SetFont(font)
self.Bind(wx.EVT_RADIOBUTTON, self.onMethodSelect, rb)
self.rbMethod.append(rb)
style = 0
sbox.Add(rb, 1, wx.LEFT + wx.RIGHT, 16)
sbox.AddSpacer((5, 5))
self.rbMethod[self.currentMethod].SetValue(True);
csz.Add(sbox)
hsz.Add(csz)
hsz.AddSpacer((10, 10))
sizer.Add(hsz)
bsz = wx.BoxSizer(wx.HORIZONTAL)
self.bSave = wx.Button(self, wx.ID_ANY, "Save", size = BSIZESMALL)
@ -188,13 +296,10 @@ class AddSensorDlg(wx.Dialog):
self.bCancel.Bind(wx.EVT_BUTTON, self.onCancel)
bsz.Add(self.bCancel)
csz.Add(bsz, flag = wx.ALIGN_CENTER_HORIZONTAL)
csz.AddSpacer((10, 10))
sizer.Add(bsz, flag = wx.ALIGN_CENTER_HORIZONTAL)
sizer.AddSpacer((10, 10))
hsz.Add(csz)
hsz.AddSpacer((10, 10))
self.SetSizer(hsz)
self.SetSizer(sizer)
self.Fit()
self.selectSensorType(stStart)
@ -222,9 +327,20 @@ class AddSensorDlg(wx.Dialog):
tc.SetBackgroundColour("pink")
tc.Refresh()
def onMethodSelect(self, evt):
rb = evt.GetEventObject()
lbl = rb.GetLabel()
for i in range(len(MethodLabels)):
if lbl == MethodLabels[i]:
self.currentMethod = i
self.setDialogMode()
self.validateFields()
return
def checkDlgValidity(self):
if (self.nameValid and self.R0Valid and self.betaValid and
self.R2Valid and self.vadcValid):
if (self.nameValid and self.param0Valid and self.param1Valid and
self.param2Valid and self.param3Valid and self.param4Valid and
self.param5Valid and self.param6Valid):
self.bSave.Enable(True)
else:
self.bSave.Enable(False)
@ -242,7 +358,7 @@ class AddSensorDlg(wx.Dialog):
valid = True
else:
valid = False
if valid:
tc.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
else:
@ -250,7 +366,7 @@ class AddSensorDlg(wx.Dialog):
tc.Refresh()
return valid
def onTextCtrlFloat(self, tc, rqd):
w = tc.GetValue().strip()
if w == "":
@ -264,7 +380,7 @@ class AddSensorDlg(wx.Dialog):
valid = True
else:
valid = False
if valid:
tc.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
else:
@ -272,64 +388,256 @@ class AddSensorDlg(wx.Dialog):
tc.Refresh()
return valid
def onR0Entry(self, evt):
stype = self.chType.GetString(self.chType.GetSelection())
if stype in ['Thermistor']:
rqd = True
def onParam0Entry(self, evt):
if self.currentMode == MODE_THERMISTOR:
self.param0Valid = self.onTextCtrlInteger(self.param0, True)
else:
rqd = False
self.R0Valid = self.onTextCtrlInteger(self.tcR0, rqd)
self.param0Valid = True
self.checkDlgValidity()
if evt is not None:
evt.Skip()
def onBetaEntry(self, evt):
stype = self.chType.GetString(self.chType.GetSelection())
if stype in ['Thermistor']:
rqd = True
def onParam1Entry(self, evt):
if self.currentMode == MODE_THERMISTOR:
self.param1Valid = self.onTextCtrlInteger(self.param1, True)
else:
rqd = False
self.betaValid = self.onTextCtrlInteger(self.tcBeta, rqd)
self.param1Valid = True
self.checkDlgValidity()
if evt is not None:
evt.Skip()
def onR2Entry(self, evt):
stype = self.chType.GetString(self.chType.GetSelection())
if stype in ['Thermistor']:
rqd = True
def onParam2Entry(self, evt):
if self.currentMode == MODE_THERMISTOR:
self.param2Valid = self.onTextCtrlInteger(self.param2, True)
else:
rqd = False
self.R2Valid = self.onTextCtrlInteger(self.tcR2, rqd)
self.param2Valid = True
self.checkDlgValidity()
if evt is not None:
evt.Skip()
def onVadcEntry(self, evt):
stype = self.chType.GetString(self.chType.GetSelection())
if stype in ['Thermistor']:
rqd = True
def onParam3Entry(self, evt):
if self.currentMode == MODE_THERMISTOR:
if self.currentMethod == METHOD_BETA:
self.param3Valid = self.onTextCtrlFloat(self.param3, True)
else:
self.param3Valid = self.onTextCtrlInteger(self.param3, True)
else:
rqd = False
self.vadcValid = self.onTextCtrlFloat(self.tcVadc, rqd)
self.param3Valid = True
self.checkDlgValidity()
if evt is not None:
evt.Skip()
def onParam4Entry(self, evt):
if self.currentMode == MODE_THERMISTOR:
if self.currentMethod == METHOD_BETA:
self.param4Valid = True
else:
self.param4Valid = self.onTextCtrlInteger(self.param4, True)
else:
self.param4Valid = True
self.checkDlgValidity()
if evt is not None:
evt.Skip()
def onParam5Entry(self, evt):
if self.currentMode == MODE_THERMISTOR:
if self.currentMethod == METHOD_BETA:
self.param5Valid = True
else:
self.param5Valid = self.onTextCtrlInteger(self.param5, True)
else:
self.param5Valid = True
self.checkDlgValidity()
if evt is not None:
evt.Skip()
def onParam6Entry(self, evt):
if self.currentMode == MODE_THERMISTOR:
if self.currentMethod == METHOD_BETA:
self.param6Valid = True
else:
self.param6Valid = self.onTextCtrlInteger(self.param6, True)
else:
self.param6Valid = True
self.checkDlgValidity()
if evt is not None:
evt.Skip()
def selectSensorType(self, lbl):
if lbl == 'Thermistor':
flag = True
if lbl == "Thermistor":
self.currentMode = MODE_THERMISTOR
else:
flag = False
self.currentMode = MODE_NONTHERM
self.setDialogMode()
self.tcR0.Enable(flag);
self.tcBeta.Enable(flag);
self.tcR2.Enable(flag);
self.tcVadc.Enable(flag);
def setDialogMode(self):
if self.currentMode == MODE_THERMISTOR:
if self.currentMethod == METHOD_BETA:
self.param0.SetToolTipString("Nominal resistance of the thermistor. "
"Typically 10000 ( = 10k) or 100000 "
"( = 100k).")
self.label0.SetLabel("R0:")
self.param1.SetToolTipString("Thermistor beta value. Can be found in "
"the datasheet or measured like described "
"in http://reprap.org/wiki/"
"MeasuringThermistorBeta")
self.label1.SetLabel("Beta:")
self.param2.SetToolTipString("Resistance value of the secondary "
"resistor. This is not a property of the "
"thermistor, but one of the board. "
"Typical values are 4700 ( = 4k7 ohms) "
"or 1000 ( = 1k ohms).")
self.label2.SetLabel("R2:")
self.param3.SetToolTipString("Comparison voltage used by the "
"controller. Usually the same as the "
"controller's supply voltage, 3.3 or 5.0 "
"(volts).")
self.label3.SetLabel("Vadc:")
self.label4.SetLabel("")
self.param4.SetToolTip(None)
self.param4.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param4.Refresh()
self.label5.SetLabel("")
self.param5.SetToolTip(None)
self.param5.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param5.Refresh()
self.label6.SetLabel("")
self.param6.SetToolTip(None)
self.param6.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param6.Refresh()
self.param4.Enable(False)
self.param5.Enable(False)
self.param6.Enable(False)
else:
self.param0.SetToolTipString("Reference resistance value.")
self.label0.SetLabel("Rp:")
self.param1.SetToolTipString("First data point, temperature at which "
"resistance is equal to R0.")
self.label1.SetLabel("T0:")
self.param2.SetToolTipString("Resistance when temperature is T0.")
self.label2.SetLabel("R0:")
self.param3.SetToolTipString("Second data point, temperature at which "
"resistance is equal to R1.")
self.label3.SetLabel("T1:")
self.param4.SetToolTipString("Resistance when temperature is T1.")
self.label4.SetLabel("R1:")
self.param5.SetToolTipString("Third data point, temperature at which "
"resistance is equal to R2.")
self.label5.SetLabel("T2:")
self.param6.SetToolTipString("Resistance when temperature is T2.")
self.label6.SetLabel("R2:")
self.param4.Enable(True)
self.param5.Enable(True)
self.param6.Enable(True)
self.label0.SetSize((labelWidth, -1))
self.label0.SetWindowStyle(wx.ALIGN_RIGHT)
self.label1.SetSize((labelWidth, -1))
self.label1.SetWindowStyle(wx.ALIGN_RIGHT)
self.label2.SetSize((labelWidth, -1))
self.label2.SetWindowStyle(wx.ALIGN_RIGHT)
self.label3.SetSize((labelWidth, -1))
self.label3.SetWindowStyle(wx.ALIGN_RIGHT)
self.label4.SetSize((labelWidth, -1))
self.label4.SetWindowStyle(wx.ALIGN_RIGHT)
self.label5.SetSize((labelWidth, -1))
self.label5.SetWindowStyle(wx.ALIGN_RIGHT)
self.label6.SetSize((labelWidth, -1))
self.label6.SetWindowStyle(wx.ALIGN_RIGHT)
self.param0.Enable(True);
self.param1.Enable(True);
self.param2.Enable(True);
self.param3.Enable(True);
self.chPresets.Enable(True);
for rb in self.rbMethod:
rb.Enable(True)
else:
self.param0.SetToolTip(None)
self.label0.SetLabel("")
self.param0.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param0.Refresh()
self.param1.SetToolTip(None)
self.label1.SetLabel("")
self.param1.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param1.Refresh()
self.param2.SetToolTip(None)
self.label2.SetLabel("")
self.param2.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param2.Refresh()
self.param3.SetToolTip(None)
self.label3.SetLabel("")
self.param3.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param3.Refresh()
self.param4.SetToolTip(None)
self.label4.SetLabel("")
self.param4.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param4.Refresh()
self.param5.SetToolTip(None)
self.label5.SetLabel("")
self.param5.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param5.Refresh()
self.param6.SetToolTip(None)
self.label6.SetLabel("")
self.param6.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.param6.Refresh()
self.param0.Enable(False);
self.param1.Enable(False);
self.param2.Enable(False);
self.param3.Enable(False);
self.param4.Enable(False);
self.param5.Enable(False);
self.param6.Enable(False);
self.chPresets.Enable(False);
for rb in self.rbMethod:
rb.Enable(False)
def onChoice(self, evt):
pass
def onPresetChoice(self, evt):
ch = evt.GetEventObject()
s = ch.GetSelection()
label = ch.GetString(s)
if s != 0:
self.param0.SetValue(self.presets[label][0])
self.param1.SetValue(self.presets[label][1])
self.param2.SetValue(self.presets[label][2])
self.param3.SetValue(self.presets[label][3])
if len(self.presets[label]) == 7:
self.param4.SetValue(self.presets[label][4])
self.param5.SetValue(self.presets[label][5])
self.param6.SetValue(self.presets[label][5])
self.currentMethod = METHOD_SH
else:
self.currentMethod = METHOD_BETA
self.rbMethod[self.currentMethod].SetValue(True)
self.setDialogMode()
self.validateFields()
evt.Skip()
def onSensorType(self, evt):
ch = evt.GetEventObject()
s = ch.GetSelection()
@ -341,21 +649,34 @@ class AddSensorDlg(wx.Dialog):
def validateFields(self):
self.validateName(self.tcName)
self.onR0Entry(None)
self.onBetaEntry(None)
self.onR2Entry(None)
self.onVadcEntry(None)
self.onParam0Entry(None)
self.onParam1Entry(None)
self.onParam2Entry(None)
self.onParam3Entry(None)
self.onParam4Entry(None)
self.onParam5Entry(None)
self.onParam6Entry(None)
def getValues(self):
nm = self.tcName.GetValue()
pin = self.choices[self.chPin.GetSelection()]
stype = self.chType.GetString(self.chType.GetSelection())
if stype in ['Thermistor']:
addtl = [self.tcR0.GetValue().strip(), self.tcBeta.GetValue().strip(),
self.tcR2.GetValue().strip(), self.tcVadc.GetValue().strip()]
if self.currentMode == MODE_THERMISTOR:
if self.currentMethod == METHOD_BETA:
addtl = [str(self.param0.GetValue().strip()),
str(self.param1.GetValue().strip()),
str(self.param2.GetValue().strip()),
str(self.param3.GetValue().strip())]
else:
addtl = [str(self.param0.GetValue().strip()),
str(self.param1.GetValue().strip()),
str(self.param2.GetValue().strip()),
str(self.param3.GetValue().strip()),
str(self.param4.GetValue().strip()),
str(self.param5.GetValue().strip()),
str(self.param6.GetValue().strip())]
else:
addtl = "NONE"
addtl = None
return (nm, sensorTypes[stype], pin, addtl)

View File

@ -11,7 +11,8 @@ from configtool.data import (defineValueFormat,
reCandProcessors, reCandCPUClocks, reFloatAttr,
reDefine, reDefineBL, reDefQS, reDefQSm,
reDefQSm2, reDefBool, reDefBoolBL, reDefHT,
reDefTS, reDefTT, reSensor, reHeater, reTempTable)
reDefTS, reDefTT, reSensor, reHeater,
reTempTable4, reTempTable7)
from configtool.pinoutspage import PinoutsPage
from configtool.sensorpage import SensorsPage
from configtool.heaterspage import HeatersPage
@ -66,7 +67,8 @@ class BoardPanel(wx.Panel):
self.pageValid.append(True)
self.pgSensors = SensorsPage(self, self.nb, len(self.pages),
self.settings.font)
self.settings.font,
self.settings.thermistorpresets)
text = "Temperature Sensors"
self.nb.AddPage(self.pgSensors, text)
self.pages.append(self.pgSensors)
@ -356,6 +358,8 @@ class BoardPanel(wx.Panel):
tn = self.sensors[k][0].upper()
if tn in tempTables.keys():
self.sensors[k][3] = tempTables[tn]
else:
self.sensors[k][3] = None
if os.path.basename(fn) in protectedFiles:
self.parent.enableSaveBoard(False, True)
@ -395,11 +399,16 @@ class BoardPanel(wx.Panel):
return None
def parseTempTable(self, s):
m = reTempTable.search(s)
m = reTempTable4.search(s)
if m:
t = m.groups()
if len(t) == 4:
return list(t)
m = reTempTable7.search(s)
if m:
t = m.groups()
if len(t) == 7:
return list(t)
return None
def onSaveConfig(self, evt):
@ -482,14 +491,13 @@ class BoardPanel(wx.Panel):
ttString = "\n// r0 beta r2 vadc\n"
for s in self.sensors:
sstr = "%-10s%-15s%-7s" % ((s[0] + ","), (s[1] + ","), (s[2] + ","))
if s[3] != "NONE":
if s[3] is None:
sstr += "0"
else:
tt = s[3]
sstr += "THERMISTOR_%s" % s[0].upper()
ttString += "//TEMP_TABLE %-8s (%-8s%-6s%-6s%s)\n" % \
(s[0].upper(), (tt[0] + ","), (tt[1] + ","),
(tt[2] + ","), tt[3])
else:
sstr += s[3]
ttString += "//TEMP_TABLE %-8s (%s)\n" % \
(s[0].upper(), ", ".join(tt))
fp.write("DEFINE_TEMP_SENSOR(%s)\n" % sstr)
fp.write(ttString)
skipToSensorEnd = True
@ -607,7 +615,9 @@ class BoardPanel(wx.Panel):
dlg.Destroy()
fp.close()
return self.generateTempTables()
def generateTempTables(self):
if not generateTempTables(self.sensors, self.settings):
dlg = wx.MessageDialog(self, "Error writing to file thermistortable.h.",
"File error", wx.OK + wx.ICON_ERROR)

View File

@ -57,7 +57,8 @@ reHelpTextEnd = re.compile("^\s*\*/")
reSensor = re.compile(".*\\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*\\)")
reHeater = re.compile(".*\\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*\\)")
reTempTable = re.compile(".*\\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d*.?\d*)\s*\\)")
reTempTable4 = re.compile(".*\\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d*.?\d*)\s*\\)")
reTempTable7 = re.compile(".*\\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\\)")
reInteger = re.compile("^\d+U?L?$")
reFloat = re.compile("^\d+(\.\d*)?$")

View File

@ -85,7 +85,10 @@ class SensorList(wx.ListCtrl):
elif len(s) == 3:
return ""
else:
return s[3]
if s[3] is None:
return ""
else:
return "[%s]" % (", ".join(s[3]))
def OnGetItemAttr(self, item):
if not self.valid[item]:

View File

@ -7,11 +7,12 @@ from addsensordlg import AddSensorDlg
class SensorsPage(wx.Panel, Page):
def __init__(self, parent, nb, idPg, font):
def __init__(self, parent, nb, idPg, font, thermistorpresets):
wx.Panel.__init__(self, nb, wx.ID_ANY)
Page.__init__(self, font)
self.parent = parent
self.font = font
self.thermistorpresets = thermistorpresets
self.id = idPg
self.sensorTypeKeys = {'TT_MAX6675': 'TEMP_MAX6675',
@ -101,10 +102,14 @@ class SensorsPage(wx.Panel, Page):
nm.append(s[0])
s = self.sensors[self.selection]
if s[3] is None:
params = []
else:
params = s[3]
dlg = AddSensorDlg(self, nm, self.validPins, self.font,
name = s[0], stype = s[1], pin = s[2],
r0 = s[3][0], beta = s[3][1], r2 = s[3][2],
vadc = s[3][3], modify = True)
params = params, modify = True)
rc = dlg.ShowModal()
if rc == wx.ID_OK:
tt = dlg.getValues()

View File

@ -29,6 +29,8 @@ class Settings:
self.maxAdc = 1023
self.minAdc = 1
self.thermistorpresets = {}
self.cfg = ConfigParser.ConfigParser()
self.cfg.optionxform = str
@ -70,6 +72,16 @@ class Settings:
else:
print "Missing %s section - assuming defaults." % self.section
section = "thermistors"
if self.cfg.has_section(section):
for opt, value in self.cfg.items(section):
value = value.strip().replace(" ", "")
vl = value.split(",")
if len(vl) != 4 and len(vl) != 7:
print "Invalid entry for thermistor %s." % opt
else:
self.thermistorpresets[opt] = vl
def saveSettings(self):
self.section = "configtool"
try:
@ -90,6 +102,15 @@ class Settings:
self.cfg.set(self.section, "minadc", str(self.minAdc))
self.cfg.set(self.section, "uploadspeed", str(self.uploadspeed))
section = "thermistors"
try:
self.cfg.add_section(section)
except ConfigParser.DuplicateSectionError:
pass
for t in self.thermistorpresets.keys():
self.cfg.set(section, t, ", ".join(self.thermistorpresets[t]))
try:
cfp = open(self.inifile, 'wb')
except:

88
configtool/thermistor.py Normal file
View File

@ -0,0 +1,88 @@
from math import *
import sys
class SHThermistor:
def __init__(self, rp, t0, r0, t1, r1, t2, r2):
self.rp = rp
self.paramsOK = True
try:
T0 = t0 + 273.15; T1 = t1 + 273.15; T2 = t2 + 273.15
a0 = log(r0); a1 = log(r1); a2 = log(r2)
z = a0 - a1
y = a0 - a2
x = 1 / T0 - 1 / T1
w = 1 / T0 - 1 / T2
v = a0 ** 3 - a1 ** 3
u = a0 ** 3 - a2 ** 3
self.C = (x - z * w / y) / (v - z * u / y)
self.B = (x - self.C * v) / z
self.A = 1 / T0 - self.C * a0 ** 3 - self.B * a0
except:
self.paramsOK = False
def setting(self, t):
if not self.paramsOK:
return None, None
try:
T = t + 273.15
y = (self.A - 1/T) / self.C
x = ((self.B / (3 * self.C)) ** 3 + (y ** 2) / 4) ** 0.5
r = exp((x - y / 2) ** (1.0/3) - (x + y / 2) ** (1.0/3))
return self.adc(r), r
except:
return None, None
def adc(self, r):
return 1023.0 * r / (r + self.rp)
class BetaThermistor:
def __init__(self, r0, t0, beta, r1, r2, vadc):
self.paramsOK = True
try:
self.r0 = r0
self.t0 = t0 + 273.15
self.beta = beta
self.vadc = vadc
self.k = r0 * exp(-beta / self.t0)
if r1 > 0:
self.vs = r1 * self.vadc / (r1 + r2)
self.rs = r1 * r2 / (r1 + r2)
else:
self.vs = self.vadc
self.rs = r2
except:
self.paramsOK = False
def temp(self, adc):
v = adc * self.vadc / 1024
if (self.vs - v):
r = self.rs * v / (self.vs - v)
else:
r = self.r0 * 10
try:
return (self.beta / log(r / self.k)) - 273.15
except:
print "// error for ADC = {adc}, {v}, {r}".format(adc = adc, v = v, r = r)
return None
def resistance(self, t):
return self.r0 * exp(self.beta * (1 / (t + 273.15) - 1 / self.t0))
def setting(self, t):
if not self.paramsOK:
return None, None
try:
r = self.r0 * exp(self.beta * (1 / (t + 273.15) - 1 / self.t0))
v = self.vs * r / (self.rs + r)
return round(v / self.vadc * 1024), r
except:
return None, None

View File

@ -1,6 +1,6 @@
import os
from createTemperatureLookup import Thermistor
from thermistor import SHThermistor, BetaThermistor
class ThermistorTableFile:
@ -18,21 +18,30 @@ class ThermistorTableFile:
def output(self, text):
self.fp.write(text + "\n")
def paramsEqual(p1, p2):
for i in range(len(p1)):
if p1[i] != p2[i]:
return False
return True
def generateTempTables(sensors, settings):
ofp = ThermistorTableFile(settings.folder)
if ofp.error:
return False
mult = 4
minadc = int(settings.minAdc)
N = int(settings.numTemps)
# Make a list of single-item dicts to keep the order.
tl = []
for sensor in sensors:
if sensor[3] != "NONE":
tl.append({sensor[0].upper(): sensor[3]})
if sensor[3] is not None:
found = False
for t in tl:
if paramsEqual(t[0], sensor[3]):
t[1].append(sensor[0].upper())
found = True
if not found:
tl.append((sensor[3], [sensor[0].upper()]))
ofp.output("");
ofp.output("/**");
@ -48,77 +57,98 @@ def generateTempTables(sensors, settings):
ofp.output("");
for i in range(len(tl)):
ofp.output("#define THERMISTOR_%s %d" % (tl[i].keys()[0], i))
for n in tl[i][1]:
ofp.output("#define THERMISTOR_%s %d" % (n, i))
ofp.output("");
if len(tl) == 0 or N == 0:
ofp.close();
return True
step = int((300.0 / (N-1) + 1))
idx = range(step*(N-1), -step, -step)
ofp.output("const uint16_t PROGMEM temptable[NUMTABLES][NUMTEMPS][2] = {")
tcount = 0
for tn in tl:
tcount += 1
t = tn.values()[0]
r0 = t[0]
beta = t[1]
r2 = t[2]
vadc = t[3]
ofp.output(" // %s temp table parameters:" % tn.keys()[0])
ofp.output((" // R0 = %s, T0 = %s, R1 = %s, R2 = %s, beta = %s, "
"maxadc = %s") % (r0, settings.t0, settings.r1, r2,
beta, settings.maxAdc))
ofp.output(" {")
thm = Thermistor(int(r0),
int(settings.t0),
int(beta),
int(settings.r1),
int(r2),
float(vadc),
float(vadc))
maxadc = int(settings.maxAdc)
zadc = int(thm.setting(0))
if zadc < maxadc:
maxadc = zadc
increment = float(maxadc - minadc) / float(N - 1);
ct = 1.0
adcs = []
for i in range(N):
adcs.append(int(ct))
ct += increment
if ct > maxadc:
ct = maxadc
counter = 0
for adc in adcs:
counter = counter + 1
degC = thm.temp(adc)
resistance = thm.resistance(thm.temp(adc))
vTherm = adc * thm.vadc / 1024
ptherm = vTherm * vTherm / resistance
if adc > 1:
resolution = thm.temp(adc - 1) - thm.temp(adc)
else:
resolution = thm.temp(adc) - thm.temp(adc + 1)
if counter == len(adcs):
sep = " "
else:
sep = ","
val = int(thm.temp(adc) * mult)
if val < 0:
val = 0
ostr = (" {%4s, %5s}%s // %6.2f C, %6.0f ohms, %0.3f V,"
" %0.2f C/count, %0.2f mW") % (adc, val,
sep, degC, resistance, vTherm, resolution, ptherm * 1000)
ofp.output(ostr)
if tcount == len(tl):
ofp.output(" }")
finalTable = tcount == len(tl)
if len(tn[0]) == 4:
BetaTable(ofp, tn[0], tn[1], idx, settings, finalTable)
elif len(tn[0]) == 7:
SteinhartHartTable(ofp, tn[0], tn[1], idx, settings, finalTable)
else:
ofp.output(" },")
pass
ofp.output("};")
ofp.close()
return True
def BetaTable(ofp, params, names, idx, settings, finalTable):
r0 = params[0]
beta = params[1]
r2 = params[2]
vadc = float(params[3])
ofp.output(" // %s temp table using Beta algorithm with parameters:" %
(", ".join(names)))
ofp.output((" // R0 = %s, T0 = %s, R1 = %s, R2 = %s, beta = %s, "
"maxadc = %s") % (r0, settings.t0, settings.r1, r2,
beta, settings.maxAdc))
ofp.output(" {")
thrm = BetaThermistor(int(r0), int(settings.t0), int(beta), int(settings.r1),
int(r2), vadc)
for t in idx:
a, r = thrm.setting(t)
if a is None:
ofp.output("// ERROR CALCULATING THERMISTOR VALUES AT TEMPERATURE %d" % t)
continue
vTherm = a * vadc / 1024
ptherm = vTherm * vTherm / r
if t <= 0:
c = " "
else:
c = ","
ostr = (" {%4s, %5s}%s // %4d C, %6.0f ohms, %0.3f V,"
" %0.2f mW") % (int(round(a)), t*4, c, t, r,
vTherm, ptherm * 1000)
ofp.output(ostr)
if finalTable:
ofp.output(" }")
else:
ofp.output(" },")
def SteinhartHartTable(ofp, params, names, idx, settings, finalTable):
ofp.output((" // %s temp table using Steinhart-Hart algorithm with "
"parameters:") % (", ".join(names)))
ofp.output((" // Rp = %s, T0 = %s, R0 = %s, T1 = %s, R1 = %s, "
"T2 = %s, R2 = %s") %
(params[0], params[1], params[2], params[3], params[4], params[5],
params[6]))
ofp.output(" {")
thrm = SHThermistor(int(params[0]), int(params[1]), int(params[2]),
int(params[3]), int(params[4]), int(params[5]),
int(params[6]))
for t in idx:
a, r = thrm.setting(t)
if a is None:
ofp.output("// ERROR CALCULATING THERMISTOR VALUES AT TEMPERATURE %d" % t)
continue
if t <= 0:
c = " "
else:
c = ","
ofp.output(" {%4d, %5d}%s // %4d C, %6.0f ohms, %7.2f adc" %
(int(round(a)), t*4, c, t, int(round(r)), a))
if finalTable:
ofp.output(" }")
else:
ofp.output(" },")