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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
# 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
KELVIN_TO_CELCIUS = -273.15
SAMPLE_TIME = 0.001
SAMPLE_COUNT = 8
REPORT_TIME = 0.300
RANGE_CHECK_COUNT = 4
# Analog voltage to temperature converter for thermistors
class Thermistor:
def __init__(self, pullup):
self.pullup = pullup
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_CELCIUS)
inv_t2 = 1. / (t2 - KELVIN_TO_CELCIUS)
inv_t3 = 1. / (t3 - KELVIN_TO_CELCIUS)
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_CELCIUS)
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)
inv_t = self.c1 + self.c2 * ln_r + self.c3 * ln_r**3
return 1.0/inv_t + KELVIN_TO_CELCIUS
def calc_adc(self, temp):
# Calculate adc reading from a temperature
if temp <= KELVIN_TO_CELCIUS:
return 1.
inv_t = 1. / (temp - KELVIN_TO_CELCIUS)
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)
return r / (self.pullup + r)
# Interface between ADC and heater temperature callbacks
class PrinterADCtoTemperature:
def __init__(self, config, adc_convert):
self.adc_convert = adc_convert
ppins = config.get_printer().lookup_object('pins')
self.mcu_adc = ppins.setup_pin('adc', config.get('sensor_pin'))
self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback)
def setup_callback(self, temperature_callback):
self.temperature_callback = temperature_callback
def get_report_time_delta(self):
return REPORT_TIME
def adc_callback(self, read_time, read_value):
temp = self.adc_convert.calc_temp(read_value)
self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp)
def setup_minmax(self, min_temp, max_temp):
adc_range = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]]
self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT,
minval=min(adc_range), maxval=max(adc_range),
range_check_count=RANGE_CHECK_COUNT)
# Create an ADC converter with a thermistor
def PrinterThermistor(config, params):
pullup = config.getfloat('pullup_resistor', 4700., above=0.)
thermistor = Thermistor(pullup)
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 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_CELCIUS)
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_CELCIUS)
r2 = config.getfloat("resistance2", minval=0.)
t3 = config.getfloat("temperature3", minval=KELVIN_TO_CELCIUS)
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 },
"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
pheater = config.get_printer().lookup_object("heater")
for sensor_type, params in Sensors.items():
func = (lambda config, params=params: PrinterThermistor(config, params))
pheater.add_sensor(sensor_type, func)
def load_config_prefix(config):
thermistor = CustomThermistor(config)
pheater = config.get_printer().lookup_object("heater")
pheater.add_sensor(thermistor.name, thermistor.create)
|