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 numtemps = 25
maxadc = 1023 maxadc = 1023
minadc = 0 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, from configtool.data import (pinNames, BSIZESMALL, sensorTypes, offsetTcLabel,
offsetChLabel, reInteger, reFloat) 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): class AddSensorDlg(wx.Dialog):
def __init__(self, parent, names, pins, font, name = "", stype = "", def __init__(self, parent, names, pins, font, name = "", stype = "",
pin = "", r0 = "", beta = "", r2 = "", vadc = "", pin = "", params = [], modify = False):
modify = False):
if modify: if modify:
title = "Modify temperature sensor" title = "Modify temperature sensor"
else: else:
@ -19,14 +27,28 @@ class AddSensorDlg(wx.Dialog):
self.names = names self.names = names
self.choices = pins self.choices = pins
self.modify = modify 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.nameValid = False
self.R0Valid = False self.param0Valid = False
self.betaValid = False self.param1Valid = False
self.R2Valid = False self.param2Valid = False
self.vadcValid = False self.param3Valid = False
self.param4Valid = False
self.param5Valid = False
self.param6Valid = False
sizer = wx.BoxSizer(wx.VERTICAL)
hsz = wx.BoxSizer(wx.HORIZONTAL) hsz = wx.BoxSizer(wx.HORIZONTAL)
hsz.AddSpacer((10, 10)) hsz.AddSpacer((10, 10))
@ -75,6 +97,7 @@ class AddSensorDlg(wx.Dialog):
if not found: if not found:
ch.SetSelection(0) ch.SetSelection(0)
stStart = sl[0] stStart = sl[0]
self.chType = ch self.chType = ch
lsz.Add(ch) lsz.Add(ch)
@ -103,76 +126,161 @@ class AddSensorDlg(wx.Dialog):
csz.AddSpacer((10, 10)) csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL) 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) style = wx.ALIGN_RIGHT)
st.SetFont(font) st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel) lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label0 = st
self.tcR0 = wx.TextCtrl(self, wx.ID_ANY, r0) vals = params + ["", "", "", "", "", "", ""]
self.tcR0.SetFont(font) self.param0 = wx.TextCtrl(self, wx.ID_ANY, vals[0])
self.tcR0.Bind(wx.EVT_TEXT, self.onR0Entry) self.param0.SetFont(font)
lsz.Add(self.tcR0) self.param0.Bind(wx.EVT_TEXT, self.onParam0Entry)
self.tcR0.SetToolTipString("Nominal resistance of the thermistor. " lsz.Add(self.param0)
"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")
csz.Add(lsz) csz.Add(lsz)
csz.AddSpacer((10, 10)) csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL) 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) style = wx.ALIGN_RIGHT)
st.SetFont(font) st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel) lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label1 = st
self.tcR2 = wx.TextCtrl(self, wx.ID_ANY, r2) self.param1 = wx.TextCtrl(self, wx.ID_ANY, vals[1])
self.tcR2.SetFont(font) self.param1.SetFont(font)
self.tcR2.Bind(wx.EVT_TEXT, self.onR2Entry) self.param1.Bind(wx.EVT_TEXT, self.onParam1Entry)
lsz.Add(self.tcR2) lsz.Add(self.param1)
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.")
csz.Add(lsz) csz.Add(lsz)
csz.AddSpacer((10, 10)) csz.AddSpacer((10, 10))
lsz = wx.BoxSizer(wx.HORIZONTAL) 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) style = wx.ALIGN_RIGHT)
st.SetFont(font) st.SetFont(font)
lsz.Add(st, 1, wx.TOP, offsetTcLabel) lsz.Add(st, 1, wx.TOP, offsetTcLabel)
self.label2 = st
self.tcVadc = wx.TextCtrl(self, wx.ID_ANY, vadc) self.param2 = wx.TextCtrl(self, wx.ID_ANY, vals[2])
self.tcVadc.SetFont(font) self.param2.SetFont(font)
self.tcVadc.Bind(wx.EVT_TEXT, self.onVadcEntry) self.param2.Bind(wx.EVT_TEXT, self.onParam2Entry)
lsz.Add(self.tcVadc) lsz.Add(self.param2)
self.tcVadc.SetToolTipString("Comparison voltage used by the controller. "
"Usually the same as the controller's supply "
"voltage, 3.3 or 5.0 (volts).")
csz.Add(lsz) csz.Add(lsz)
csz.AddSpacer((10, 10)) 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) bsz = wx.BoxSizer(wx.HORIZONTAL)
self.bSave = wx.Button(self, wx.ID_ANY, "Save", size = BSIZESMALL) 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) self.bCancel.Bind(wx.EVT_BUTTON, self.onCancel)
bsz.Add(self.bCancel) bsz.Add(self.bCancel)
csz.Add(bsz, flag = wx.ALIGN_CENTER_HORIZONTAL) sizer.Add(bsz, flag = wx.ALIGN_CENTER_HORIZONTAL)
csz.AddSpacer((10, 10)) sizer.AddSpacer((10, 10))
hsz.Add(csz) self.SetSizer(sizer)
hsz.AddSpacer((10, 10))
self.SetSizer(hsz)
self.Fit() self.Fit()
self.selectSensorType(stStart) self.selectSensorType(stStart)
@ -222,9 +327,20 @@ class AddSensorDlg(wx.Dialog):
tc.SetBackgroundColour("pink") tc.SetBackgroundColour("pink")
tc.Refresh() 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): def checkDlgValidity(self):
if (self.nameValid and self.R0Valid and self.betaValid and if (self.nameValid and self.param0Valid and self.param1Valid and
self.R2Valid and self.vadcValid): self.param2Valid and self.param3Valid and self.param4Valid and
self.param5Valid and self.param6Valid):
self.bSave.Enable(True) self.bSave.Enable(True)
else: else:
self.bSave.Enable(False) self.bSave.Enable(False)
@ -242,7 +358,7 @@ class AddSensorDlg(wx.Dialog):
valid = True valid = True
else: else:
valid = False valid = False
if valid: if valid:
tc.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) tc.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
else: else:
@ -250,7 +366,7 @@ class AddSensorDlg(wx.Dialog):
tc.Refresh() tc.Refresh()
return valid return valid
def onTextCtrlFloat(self, tc, rqd): def onTextCtrlFloat(self, tc, rqd):
w = tc.GetValue().strip() w = tc.GetValue().strip()
if w == "": if w == "":
@ -264,7 +380,7 @@ class AddSensorDlg(wx.Dialog):
valid = True valid = True
else: else:
valid = False valid = False
if valid: if valid:
tc.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) tc.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
else: else:
@ -272,64 +388,256 @@ class AddSensorDlg(wx.Dialog):
tc.Refresh() tc.Refresh()
return valid return valid
def onR0Entry(self, evt): def onParam0Entry(self, evt):
stype = self.chType.GetString(self.chType.GetSelection()) if self.currentMode == MODE_THERMISTOR:
if stype in ['Thermistor']: self.param0Valid = self.onTextCtrlInteger(self.param0, True)
rqd = True
else: else:
rqd = False self.param0Valid = True
self.R0Valid = self.onTextCtrlInteger(self.tcR0, rqd)
self.checkDlgValidity() self.checkDlgValidity()
if evt is not None: if evt is not None:
evt.Skip() evt.Skip()
def onBetaEntry(self, evt): def onParam1Entry(self, evt):
stype = self.chType.GetString(self.chType.GetSelection()) if self.currentMode == MODE_THERMISTOR:
if stype in ['Thermistor']: self.param1Valid = self.onTextCtrlInteger(self.param1, True)
rqd = True
else: else:
rqd = False self.param1Valid = True
self.betaValid = self.onTextCtrlInteger(self.tcBeta, rqd)
self.checkDlgValidity() self.checkDlgValidity()
if evt is not None: if evt is not None:
evt.Skip() evt.Skip()
def onR2Entry(self, evt): def onParam2Entry(self, evt):
stype = self.chType.GetString(self.chType.GetSelection()) if self.currentMode == MODE_THERMISTOR:
if stype in ['Thermistor']: self.param2Valid = self.onTextCtrlInteger(self.param2, True)
rqd = True
else: else:
rqd = False self.param2Valid = True
self.R2Valid = self.onTextCtrlInteger(self.tcR2, rqd)
self.checkDlgValidity() self.checkDlgValidity()
if evt is not None: if evt is not None:
evt.Skip() evt.Skip()
def onVadcEntry(self, evt): def onParam3Entry(self, evt):
stype = self.chType.GetString(self.chType.GetSelection()) if self.currentMode == MODE_THERMISTOR:
if stype in ['Thermistor']: if self.currentMethod == METHOD_BETA:
rqd = True self.param3Valid = self.onTextCtrlFloat(self.param3, True)
else:
self.param3Valid = self.onTextCtrlInteger(self.param3, True)
else: else:
rqd = False self.param3Valid = True
self.vadcValid = self.onTextCtrlFloat(self.tcVadc, rqd)
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() self.checkDlgValidity()
if evt is not None: if evt is not None:
evt.Skip() evt.Skip()
def selectSensorType(self, lbl): def selectSensorType(self, lbl):
if lbl == 'Thermistor': if lbl == "Thermistor":
flag = True self.currentMode = MODE_THERMISTOR
else: else:
flag = False self.currentMode = MODE_NONTHERM
self.setDialogMode()
self.tcR0.Enable(flag); def setDialogMode(self):
self.tcBeta.Enable(flag); if self.currentMode == MODE_THERMISTOR:
self.tcR2.Enable(flag); if self.currentMethod == METHOD_BETA:
self.tcVadc.Enable(flag); 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): def onChoice(self, evt):
pass 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): def onSensorType(self, evt):
ch = evt.GetEventObject() ch = evt.GetEventObject()
s = ch.GetSelection() s = ch.GetSelection()
@ -341,21 +649,34 @@ class AddSensorDlg(wx.Dialog):
def validateFields(self): def validateFields(self):
self.validateName(self.tcName) self.validateName(self.tcName)
self.onR0Entry(None) self.onParam0Entry(None)
self.onBetaEntry(None) self.onParam1Entry(None)
self.onR2Entry(None) self.onParam2Entry(None)
self.onVadcEntry(None) self.onParam3Entry(None)
self.onParam4Entry(None)
self.onParam5Entry(None)
self.onParam6Entry(None)
def getValues(self): def getValues(self):
nm = self.tcName.GetValue() nm = self.tcName.GetValue()
pin = self.choices[self.chPin.GetSelection()] pin = self.choices[self.chPin.GetSelection()]
stype = self.chType.GetString(self.chType.GetSelection()) stype = self.chType.GetString(self.chType.GetSelection())
if self.currentMode == MODE_THERMISTOR:
if stype in ['Thermistor']: if self.currentMethod == METHOD_BETA:
addtl = [self.tcR0.GetValue().strip(), self.tcBeta.GetValue().strip(), addtl = [str(self.param0.GetValue().strip()),
self.tcR2.GetValue().strip(), self.tcVadc.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: else:
addtl = "NONE" addtl = None
return (nm, sensorTypes[stype], pin, addtl) return (nm, sensorTypes[stype], pin, addtl)

View File

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

View File

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

View File

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

View File

@ -29,6 +29,8 @@ class Settings:
self.maxAdc = 1023 self.maxAdc = 1023
self.minAdc = 1 self.minAdc = 1
self.thermistorpresets = {}
self.cfg = ConfigParser.ConfigParser() self.cfg = ConfigParser.ConfigParser()
self.cfg.optionxform = str self.cfg.optionxform = str
@ -70,6 +72,16 @@ class Settings:
else: else:
print "Missing %s section - assuming defaults." % self.section 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): def saveSettings(self):
self.section = "configtool" self.section = "configtool"
try: try:
@ -90,6 +102,15 @@ class Settings:
self.cfg.set(self.section, "minadc", str(self.minAdc)) self.cfg.set(self.section, "minadc", str(self.minAdc))
self.cfg.set(self.section, "uploadspeed", str(self.uploadspeed)) 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: try:
cfp = open(self.inifile, 'wb') cfp = open(self.inifile, 'wb')
except: 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 import os
from createTemperatureLookup import Thermistor from thermistor import SHThermistor, BetaThermistor
class ThermistorTableFile: class ThermistorTableFile:
@ -18,21 +18,30 @@ class ThermistorTableFile:
def output(self, text): def output(self, text):
self.fp.write(text + "\n") 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): def generateTempTables(sensors, settings):
ofp = ThermistorTableFile(settings.folder) ofp = ThermistorTableFile(settings.folder)
if ofp.error: if ofp.error:
return False return False
mult = 4
minadc = int(settings.minAdc)
N = int(settings.numTemps) N = int(settings.numTemps)
# Make a list of single-item dicts to keep the order.
tl = [] tl = []
for sensor in sensors: for sensor in sensors:
if sensor[3] != "NONE": if sensor[3] is not None:
tl.append({sensor[0].upper(): sensor[3]}) 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("");
ofp.output("/**"); ofp.output("/**");
@ -48,77 +57,98 @@ def generateTempTables(sensors, settings):
ofp.output(""); ofp.output("");
for i in range(len(tl)): 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(""); ofp.output("");
if len(tl) == 0 or N == 0: if len(tl) == 0 or N == 0:
ofp.close(); ofp.close();
return True 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] = {") ofp.output("const uint16_t PROGMEM temptable[NUMTABLES][NUMTEMPS][2] = {")
tcount = 0 tcount = 0
for tn in tl: for tn in tl:
tcount += 1 tcount += 1
t = tn.values()[0] finalTable = tcount == len(tl)
r0 = t[0] if len(tn[0]) == 4:
beta = t[1] BetaTable(ofp, tn[0], tn[1], idx, settings, finalTable)
r2 = t[2] elif len(tn[0]) == 7:
vadc = t[3] SteinhartHartTable(ofp, tn[0], tn[1], idx, settings, finalTable)
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(" }")
else: else:
ofp.output(" },") pass
ofp.output("};") ofp.output("};")
ofp.close() ofp.close()
return True 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(" },")