diff --git a/configtool.default.ini b/configtool.default.ini index 5c887ab..97d1bf5 100644 --- a/configtool.default.ini +++ b/configtool.default.ini @@ -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 diff --git a/configtool/addsensordlg.py b/configtool/addsensordlg.py index 1124c6a..cdd5df3 100644 --- a/configtool/addsensordlg.py +++ b/configtool/addsensordlg.py @@ -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 = [""] + 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) diff --git a/configtool/boardpanel.py b/configtool/boardpanel.py index e97a57d..f78c643 100644 --- a/configtool/boardpanel.py +++ b/configtool/boardpanel.py @@ -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) diff --git a/configtool/data.py b/configtool/data.py index c0b7bec..fd03846 100644 --- a/configtool/data.py +++ b/configtool/data.py @@ -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*)?$") diff --git a/configtool/sensorlist.py b/configtool/sensorlist.py index 9f1f72c..7363cc3 100644 --- a/configtool/sensorlist.py +++ b/configtool/sensorlist.py @@ -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]: diff --git a/configtool/sensorpage.py b/configtool/sensorpage.py index dfe6da8..900fe93 100644 --- a/configtool/sensorpage.py +++ b/configtool/sensorpage.py @@ -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() diff --git a/configtool/settings.py b/configtool/settings.py index 09ab8e9..0864e79 100644 --- a/configtool/settings.py +++ b/configtool/settings.py @@ -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: diff --git a/configtool/thermistor.py b/configtool/thermistor.py new file mode 100644 index 0000000..91e3430 --- /dev/null +++ b/configtool/thermistor.py @@ -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 diff --git a/configtool/thermistortablefile.py b/configtool/thermistortablefile.py index 78f3225..81b6afc 100644 --- a/configtool/thermistortablefile.py +++ b/configtool/thermistortablefile.py @@ -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(" },")