import os from thermistor import SHThermistor, BetaThermistor class ThermistorTableFile: def __init__(self, folder): self.error = False fn = os.path.join(folder, "thermistortable.h") try: self.fp = open(fn, 'wb') except: self.error = True def close(self): self.fp.close() 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 N = int(settings.numTemps) tl = [] for sensor in sensors: 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("/**"); ofp.output(" This file was autogenerated when saving a board with"); ofp.output(" Teacup's Configtool. You can edit it, but the next board"); ofp.output(" save operation in Configtool will overwrite it without"); ofp.output(" asking."); ofp.output("*/"); ofp.output(""); ofp.output("#define NUMTABLES %d" % len(tl)) ofp.output("#define NUMTEMPS %d" % N) ofp.output(""); for i in range(len(tl)): 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 ofp.output("const uint16_t PROGMEM temptable[NUMTABLES][NUMTEMPS][3] = {") tcount = 0 for tn in tl: tcount += 1 finalTable = tcount == len(tl) if len(tn[0]) == 4: BetaTable(ofp, tn[0], tn[1], settings, finalTable) elif len(tn[0]) == 7: SteinhartHartTable(ofp, tn[0], tn[1], settings, finalTable) else: pass ofp.output("};") ofp.close() return True def BetaTable(ofp, params, names, 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) hiadc = thrm.setting(0)[0] N = int(settings.numTemps) samples = optimizeTempTable(thrm, N, hiadc) prev = samples[0] for i in samples: t = thrm.temp(i) if t is None: ofp.output("// ERROR CALCULATING THERMISTOR VALUES AT ADC %d" % i) continue v = thrm.adcInv(i) r = thrm.resistance(t) vTherm = i * vadc / 1024 ptherm = vTherm * vTherm / r if i == max(samples): c = " " else: c = "," delta = (t - thrm.temp(prev)) / (prev - i) if i != prev else 0 ostr = (" {%4s, %5s, %5s}%s // %4d C, %6.0f ohms, %0.3f V," " %0.2f mW, m = %6.3f") % (i, int(t * 4), int(delta * 4 * 256), c, int(t), int(round(r)), vTherm, ptherm * 1000, delta) ofp.output(ostr) prev = i if finalTable: ofp.output(" }") else: ofp.output(" },") def SteinhartHartTable(ofp, params, names, 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]), float(params[1]), int(params[2]), float(params[3]), int(params[4]), float(params[5]), int(params[6])) hiadc = thrm.setting(0)[0] N = int(settings.numTemps) samples = optimizeTempTable(thrm, N, hiadc) prev = samples[0] for i in samples: t = thrm.temp(i) if t is None: ofp.output("// ERROR CALCULATING THERMISTOR VALUES AT ADC %d" % i) continue r = int(thrm.adcInv(i)) if i == max(samples): c = " " else: c = "," delta = (t - thrm.temp(prev)) / (prev - i) if i != prev else 0 ofp.output(" {%4d, %5d, %5d}%s // %4d C, %6d ohms, m = %6.3f" % (i, int(t * 4), int(delta * 4 * 256), c, int(t), int(round(r)), delta)) prev = i if finalTable: ofp.output(" }") else: ofp.output(" },") def optimizeTempTable(thrm, length, hiadc): # This is a variation of the Ramer-Douglas-Peucker algorithm, see # https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm # # It works like this: # # - Calculate all (1024) ideal values. # - Keep only the ones in the interesting range (0..500C). # - Insert the two extremes into our sample list. # - Calculate the linear approximation of the remaining values. # - Insert the correct value for the "most-wrong" estimation into our # sample list. # - Repeat until "N" values are chosen as requested. # Calculate actual temps for all ADC values. actual = dict([(x, thrm.temp(1.0 * x)) for x in range(1, int(hiadc + 1))]) # Limit ADC range to 0C to 500C. MIN_TEMP = 0 MAX_TEMP = 500 actual = dict([(adc, actual[adc]) for adc in actual if actual[adc] <= MAX_TEMP and actual[adc] >= MIN_TEMP]) # Build a lookup table starting with the extremes. A = min(actual) B = max(actual) lookup = dict([(x, actual[x]) for x in [A, B]]) error = dict({}) while len(lookup) < length: error.update(dict([(x, abs(actual[x] - LinearTableEstimate(lookup, x))) for x in range(A + 1, B)])) # Correct the most-wrong lookup value. next = max(error, key = error.get) lookup[next] = actual[next] # Prepare to update the error range. A = before(lookup, next) B = after(lookup, next) return sorted(lookup) def after(lookup, value): return min([x for x in lookup.keys() if x > value]) def before(lookup, value): return max([x for x in lookup.keys() if x < value]) def LinearTableEstimate(lookup, value): if value in lookup: return lookup[value] # Estimate result with linear estimation algorithm. x0 = before(lookup, value) x1 = after(lookup, value) y0 = lookup[x0] y1 = lookup[x1] return ((value - x0) * y1 + (x1 - value) * y0) / (x1 - x0)