aboutsummaryrefslogtreecommitdiffstats
path: root/klippy/extras/thermistor.py
blob: 475c132646b548792dab46ec0070b6920805d8de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# Temperature measurements with thermistors
#
# Copyright (C) 2016-2019  Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
from . import adc_temperature

KELVIN_TO_CELSIUS = -273.15

# Analog voltage to temperature converter for thermistors
class Thermistor:
    def __init__(self, pullup, inline_resistor):
        self.pullup = pullup
        self.inline_resistor = inline_resistor
        self.c1 = self.c2 = self.c3 = 0.
    def setup_coefficients(self, t1, r1, t2, r2, t3, r3, name=""):
        # Calculate Steinhart-Hart coefficents from temp measurements.
        # Arrange samples as 3 linear equations and solve for c1, c2, and c3.
        inv_t1 = 1. / (t1 - KELVIN_TO_CELSIUS)
        inv_t2 = 1. / (t2 - KELVIN_TO_CELSIUS)
        inv_t3 = 1. / (t3 - KELVIN_TO_CELSIUS)
        ln_r1 = math.log(r1)
        ln_r2 = math.log(r2)
        ln_r3 = math.log(r3)
        ln3_r1, ln3_r2, ln3_r3 = ln_r1**3, ln_r2**3, ln_r3**3

        inv_t12, inv_t13 = inv_t1 - inv_t2, inv_t1 - inv_t3
        ln_r12, ln_r13 = ln_r1 - ln_r2, ln_r1 - ln_r3
        ln3_r12, ln3_r13 = ln3_r1 - ln3_r2, ln3_r1 - ln3_r3

        self.c3 = ((inv_t12 - inv_t13 * ln_r12 / ln_r13)
                   / (ln3_r12 - ln3_r13 * ln_r12 / ln_r13))
        if self.c3 <= 0.:
            beta = ln_r13 / inv_t13
            logging.warn("Using thermistor beta %.3f in heater %s", beta, name)
            self.setup_coefficients_beta(t1, r1, beta)
            return
        self.c2 = (inv_t12 - self.c3 * ln3_r12) / ln_r12
        self.c1 = inv_t1 - self.c2 * ln_r1 - self.c3 * ln3_r1
    def setup_coefficients_beta(self, t1, r1, beta):
        # Calculate equivalent Steinhart-Hart coefficents from beta
        inv_t1 = 1. / (t1 - KELVIN_TO_CELSIUS)
        ln_r1 = math.log(r1)
        self.c3 = 0.
        self.c2 = 1. / beta
        self.c1 = inv_t1 - self.c2 * ln_r1
    def calc_temp(self, adc):
        # Calculate temperature from adc
        adc = max(.00001, min(.99999, adc))
        r = self.pullup * adc / (1.0 - adc)
        ln_r = math.log(r - self.inline_resistor)
        inv_t = self.c1 + self.c2 * ln_r + self.c3 * ln_r**3
        return 1.0/inv_t + KELVIN_TO_CELSIUS
    def calc_adc(self, temp):
        # Calculate adc reading from a temperature
        if temp <= KELVIN_TO_CELSIUS:
            return 1.
        inv_t = 1. / (temp - KELVIN_TO_CELSIUS)
        if self.c3:
            # Solve for ln_r using Cardano's formula
            y = (self.c1 - inv_t) / (2. * self.c3)
            x = math.sqrt((self.c2 / (3. * self.c3))**3 + y**2)
            ln_r = math.pow(x - y, 1./3.) - math.pow(x + y, 1./3.)
        else:
            ln_r = (inv_t - self.c1) / self.c2
        r = math.exp(ln_r) + self.inline_resistor
        return r / (self.pullup + r)

# Create an ADC converter with a thermistor
def PrinterThermistor(config, params):
    pullup = config.getfloat('pullup_resistor', 4700., above=0.)
    inline_resistor = config.getfloat('inline_resistor', 0., minval=0.)
    thermistor = Thermistor(pullup, inline_resistor)
    if 'beta' in params:
        thermistor.setup_coefficients_beta(
            params['t1'], params['r1'], params['beta'])
    else:
        thermistor.setup_coefficients(
            params['t1'], params['r1'], params['t2'], params['r2'],
            params['t3'], params['r3'], name=config.get_name())
    return adc_temperature.PrinterADCtoTemperature(config, thermistor)

# Custom defined thermistors from the config file
class CustomThermistor:
    def __init__(self, config):
        self.name = " ".join(config.get_name().split()[1:])
        t1 = config.getfloat("temperature1", minval=KELVIN_TO_CELSIUS)
        r1 = config.getfloat("resistance1", minval=0.)
        beta = config.getfloat("beta", None, above=0.)
        if beta is not None:
            self.params = {'t1': t1, 'r1': r1, 'beta': beta}
            return
        t2 = config.getfloat("temperature2", minval=KELVIN_TO_CELSIUS)
        r2 = config.getfloat("resistance2", minval=0.)
        t3 = config.getfloat("temperature3", minval=KELVIN_TO_CELSIUS)
        r3 = config.getfloat("resistance3", minval=0.)
        (t1, r1), (t2, r2), (t3, r3) = sorted([(t1, r1), (t2, r2), (t3, r3)])
        self.params = {'t1': t1, 'r1': r1, 't2': t2, 'r2': r2,
                       't3': t3, 'r3': r3}
    def create(self, config):
        return PrinterThermistor(config, self.params)

# Default sensors
Sensors = {
    "EPCOS 100K B57560G104F": {
        't1': 25., 'r1': 100000., 't2': 150., 'r2': 1641.9,
        't3': 250., 'r3': 226.15 },
    "ATC Semitec 104GT-2": {
        't1': 20., 'r1': 126800., 't2': 150., 'r2': 1360.,
        't3': 300., 'r3': 80.65 },
    "SliceEngineering 450": {
        't1': 25., 'r1': 500000., 't2': 200., 'r2': 3734.,
        't3': 400., 'r3': 240. },
    "TDK NTCG104LH104JT1": {
        't1': 25., 'r1': 100000., 't2': 50., 'r2': 31230.,
        't3': 125., 'r3': 2066. },
    "NTC 100K beta 3950": { 't1': 25., 'r1': 100000., 'beta': 3950. },
    "Honeywell 100K 135-104LAG-J01": { 't1': 25., 'r1': 100000., 'beta': 3974.},
    "NTC 100K MGB18-104F39050L32": { 't1': 25., 'r1': 100000., 'beta': 4100. },
}

def load_config(config):
    # Register default thermistor types
    pheaters = config.get_printer().load_object(config, "heaters")
    for sensor_type, params in Sensors.items():
        func = (lambda config, params=params: PrinterThermistor(config, params))
        pheaters.add_sensor_factory(sensor_type, func)

def load_config_prefix(config):
    thermistor = CustomThermistor(config)
    pheaters = config.get_printer().load_object(config, "heaters")
    pheaters.add_sensor_factory(thermistor.name, thermistor.create)