diff --git a/max31855.py b/max31855.py new file mode 100644 index 0000000..38ac170 --- /dev/null +++ b/max31855.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +import RPi.GPIO as GPIO + +class MAX31855(object): + '''Python driver for [MAX38155 Cold-Junction Compensated Thermocouple-to-Digital Converter](http://www.maximintegrated.com/datasheet/index.mvp/id/7273) + Requires: + - The [GPIO Library](https://code.google.com/p/raspberry-gpio-python/) (Already on most Raspberry Pi OS builds) + - A [Raspberry Pi](http://www.raspberrypi.org/) + + ''' + def __init__(self, cs_pin, clock_pin, data_pin, units = "c", board = GPIO.BCM): + '''Initialize Soft (Bitbang) SPI bus + + Parameters: + - cs_pin: Chip Select (CS) / Slave Select (SS) pin (Any GPIO) + - clock_pin: Clock (SCLK / SCK) pin (Any GPIO) + - data_pin: Data input (SO / MOSI) pin (Any GPIO) + - units: (optional) unit of measurement to return. ("c" (default) | "k" | "f") + - board: (optional) pin numbering method as per RPi.GPIO library (GPIO.BCM (default) | GPIO.BOARD) + + ''' + self.cs_pin = cs_pin + self.clock_pin = clock_pin + self.data_pin = data_pin + self.units = units + self.data = None + self.board = board + + # Initialize needed GPIO + GPIO.setmode(self.board) + GPIO.setup(self.cs_pin, GPIO.OUT) + GPIO.setup(self.clock_pin, GPIO.OUT) + GPIO.setup(self.data_pin, GPIO.IN) + + # Pull chip select high to make chip inactive + GPIO.output(self.cs_pin, GPIO.HIGH) + + def get(self): + '''Reads SPI bus and returns current value of thermocouple.''' + self.read() + self.checkErrors() + return getattr(self, "to_" + self.units)(self.data_to_tc_temperature()) + + def get_rj(self): + '''Reads SPI bus and returns current value of reference junction.''' + self.read() + return getattr(self, "to_" + self.units)(self.data_to_rj_temperature()) + + def read(self): + '''Reads 32 bits of the SPI bus & stores as an integer in self.data.''' + bytesin = 0 + # Select the chip + GPIO.output(self.cs_pin, GPIO.LOW) + # Read in 32 bits + for i in range(32): + GPIO.output(self.clock_pin, GPIO.LOW) + bytesin = bytesin << 1 + if (GPIO.input(self.data_pin)): + bytesin = bytesin | 1 + GPIO.output(self.clock_pin, GPIO.HIGH) + # Unselect the chip + GPIO.output(self.cs_pin, GPIO.HIGH) + # Save data + self.data = bytesin + + def checkErrors(self, data_32 = None): + '''Checks error bits to see if there are any SCV, SCG, or OC faults''' + if data_32 is None: + data_32 = self.data + anyErrors = (data_32 & 0x10000) != 0 # Fault bit, D16 + noConnection = (data_32 & 1) != 0 # OC bit, D0 + shortToGround = (data_32 & 2) != 0 # SCG bit, D1 + shortToVCC = (data_32 & 4) != 0 # SCV bit, D2 + if anyErrors: + if noConnection: + raise MAX31855Error("No Connection") + elif shortToGround: + raise MAX31855Error("Thermocouple short to ground") + elif shortToVCC: + raise MAX31855Error("Thermocouple short to VCC") + else: + # Perhaps another SPI device is trying to send data? + # Did you remember to initialize all other SPI devices? + raise MAX31855Error("Unknown Error") + + def data_to_tc_temperature(self, data_32 = None): + '''Takes an integer and returns a thermocouple temperature in celsius.''' + if data_32 is None: + data_32 = self.data + tc_data = ((data_32 >> 18) & 0x3FFF) + return self.convert_tc_data(tc_data) + + def data_to_rj_temperature(self, data_32 = None): + '''Takes an integer and returns a reference junction temperature in celsius.''' + if data_32 is None: + data_32 = self.data + rj_data = ((data_32 >> 4) & 0xFFF) + return self.convert_rj_data(rj_data) + + def convert_tc_data(self, tc_data): + '''Convert thermocouple data to a useful number (celsius).''' + if tc_data & 0x2000: + # two's compliment + without_resolution = ~tc_data & 0x1FFF + without_resolution += 1 + without_resolution *= -1 + else: + without_resolution = tc_data & 0x1FFF + return without_resolution * 0.25 + + def convert_rj_data(self, rj_data): + '''Convert reference junction data to a useful number (celsius).''' + if rj_data & 0x800: + without_resolution = ~rj_data & 0x7FF + without_resolution += 1 + without_resolution *= -1 + else: + without_resolution = rj_data & 0x7FF + return without_resolution * 0.0625 + + def to_c(self, celsius): + '''Celsius passthrough for generic to_* method.''' + return celsius + + def to_k(self, celsius): + '''Convert celsius to kelvin.''' + return celsius + 273.15 + + def to_f(self, celsius): + '''Convert celsius to fahrenheit.''' + return celsius * 9.0/5.0 + 32 + + def cleanup(self): + '''Selective GPIO cleanup''' + GPIO.setup(self.cs_pin, GPIO.IN) + GPIO.setup(self.clock_pin, GPIO.IN) + +class MAX31855Error(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +if __name__ == "__main__": + + # Multi-chip example + import time + cs_pins = [4, 17, 18, 24] + clock_pin = 23 + data_pin = 22 + units = "f" + thermocouples = [] + for cs_pin in cs_pins: + thermocouples.append(MAX31855(cs_pin, clock_pin, data_pin, units)) + running = True + while(running): + try: + for thermocouple in thermocouples: + rj = thermocouple.get_rj() + try: + tc = thermocouple.get() + except MAX31855Error as e: + tc = "Error: "+ e.value + running = False + print("tc: {} and rj: {}".format(tc, rj)) + time.sleep(1) + except KeyboardInterrupt: + running = False + for thermocouple in thermocouples: + thermocouple.cleanup() diff --git a/oven.py b/oven.py index 386ced5..6d90f63 100644 --- a/oven.py +++ b/oven.py @@ -1,4 +1,5 @@ import threading,time,random,datetime,logging +from max31855 import MAX31855, MAX31855Error log_format = '%(asctime)s %(levelname)s %(name)s: %(message)s' logging.basicConfig(level = logging.INFO, format = log_format) @@ -9,7 +10,7 @@ class Oven (threading.Thread): STATE_RUNNING = "RUNNING" STATE_ABORT = "ABORT" STATE_ERROR = "ERROR" - + def __init__(self): threading.Thread.__init__(self) self.profile = None @@ -18,16 +19,16 @@ class Oven (threading.Thread): self.state = Oven.STATE_IDLE self.temp_sensor = TempSensor(self) self.temp_sensor.start() - + def run_profile(self, profile): self.profile = profile self.state = Oven.STATE_RUNNING self.start_time = datetime.datetime.now() log.info("Starting") - + def abort_run(self): self.state = Oven.STATE_ABORT - + def run(self): while True: if self.state == Oven.STATE_RUNNING: @@ -40,8 +41,8 @@ class Oven (threading.Thread): self.power = 0.0 self.state = Oven.STATE_IDLE time.sleep(1) - - + + def get_state(self): if self.state == Oven.STATE_RUNNING: runtime = (datetime.datetime.now() - self.start_time).total_seconds() @@ -55,23 +56,32 @@ class Oven (threading.Thread): 'totaltime': 300 } return state - + class TempSensor(threading.Thread): def __init__(self,oven): threading.Thread.__init__(self) self.temperature = 0 self.oven = oven + + cs_pin = 27 + clock_pin = 22 + data_pin = 17 + units = "c" + self.thermocouple = MAX31855(cs_pin, clock_pin, data_pin, units) + + def run(self): while True: time_delta = (20.0 - self.temperature)/40 power_delta = 8.0*self.oven.power - - self.temperature += (time_delta+power_delta) + + #self.temperature += (time_delta+power_delta) + self.temperature = self.thermocouple.get() time.sleep(1) - - - + + + if __name__ == "__main__": my_oven = Oven() my_oven.run_profile("abc") - +