├── __init__.py ├── README.md ├── LICENSE └── max31865.py /__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MAX31865 2 | Raspberry Pi Python interface for the MAX31865. The MAX31865 is an easy-to-use resistance-to-digital converter optimized for platinum resistance temperature detectors (RTDs). 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stephen P. Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /max31865.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #The MIT License (MIT) 3 | # 4 | #Copyright (c) 2015 Stephen P. Smith 5 | # 6 | #Permission is hereby granted, free of charge, to any person obtaining a copy 7 | #of this software and associated documentation files (the "Software"), to deal 8 | #in the Software without restriction, including without limitation the rights 9 | #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | #copies of the Software, and to permit persons to whom the Software is 11 | #furnished to do so, subject to the following conditions: 12 | # 13 | #The above copyright notice and this permission notice shall be included in all 14 | #copies or substantial portions of the Software. 15 | # 16 | #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | #SOFTWARE. 23 | 24 | import time, math 25 | import RPi.GPIO as GPIO 26 | #import numpy 27 | 28 | class max31865(object): 29 | """Reading Temperature from the MAX31865 with GPIO using 30 | the Raspberry Pi. Any pins can be used. 31 | Numpy can be used to completely solve the Callendar-Van Dusen equation 32 | but it slows the temp reading down. I commented it out in the code. 33 | Both the quadratic formula using Callendar-Van Dusen equation (ignoring the 34 | 3rd and 4th degree parts of the polynomial) and the straight line approx. 35 | temperature is calculated with the quadratic formula one being the most accurate. 36 | """ 37 | def __init__(self, csPin = 8, misoPin = 9, mosiPin = 10, clkPin = 11): 38 | self.csPin = csPin 39 | self.misoPin = misoPin 40 | self.mosiPin = mosiPin 41 | self.clkPin = clkPin 42 | self.setupGPIO() 43 | 44 | def setupGPIO(self): 45 | GPIO.setwarnings(False) 46 | GPIO.setmode(GPIO.BCM) 47 | GPIO.setup(self.csPin, GPIO.OUT) 48 | GPIO.setup(self.misoPin, GPIO.IN) 49 | GPIO.setup(self.mosiPin, GPIO.OUT) 50 | GPIO.setup(self.clkPin, GPIO.OUT) 51 | 52 | GPIO.output(self.csPin, GPIO.HIGH) 53 | GPIO.output(self.clkPin, GPIO.LOW) 54 | GPIO.output(self.mosiPin, GPIO.LOW) 55 | 56 | def readTemp(self): 57 | # 58 | # b10000000 = 0x80 59 | # 0x8x to specify 'write register value' 60 | # 0xx0 to specify 'configuration register' 61 | # 62 | # 0b10110010 = 0xB2 63 | # Config Register 64 | # --------------- 65 | # bit 7: Vbias -> 1 (ON) 66 | # bit 6: Conversion Mode -> 0 (MANUAL) 67 | # bit5: 1-shot ->1 (ON) 68 | # bit4: 3-wire select -> 1 (3 wire config) 69 | # bits 3-2: fault detection cycle -> 0 (none) 70 | # bit 1: fault status clear -> 1 (clear any fault) 71 | # bit 0: 50/60 Hz filter select -> 0 (60Hz) 72 | # 73 | # 0b11010010 or 0xD2 for continuous auto conversion 74 | # at 60Hz (faster conversion) 75 | # 76 | 77 | #one shot 78 | self.writeRegister(0, 0xB2) 79 | 80 | # conversion time is less than 100ms 81 | time.sleep(.1) #give it 100ms for conversion 82 | 83 | # read all registers 84 | out = self.readRegisters(0,8) 85 | 86 | conf_reg = out[0] 87 | print "config register byte: %x" % conf_reg 88 | 89 | [rtd_msb, rtd_lsb] = [out[1], out[2]] 90 | rtd_ADC_Code = (( rtd_msb << 8 ) | rtd_lsb ) >> 1 91 | 92 | temp_C = self.calcPT100Temp(rtd_ADC_Code) 93 | 94 | [hft_msb, hft_lsb] = [out[3], out[4]] 95 | hft = (( hft_msb << 8 ) | hft_lsb ) >> 1 96 | print "high fault threshold: %d" % hft 97 | 98 | [lft_msb, lft_lsb] = [out[5], out[6]] 99 | lft = (( lft_msb << 8 ) | lft_lsb ) >> 1 100 | print "low fault threshold: %d" % lft 101 | 102 | status = out[7] 103 | # 104 | # 10 Mohm resistor is on breakout board to help 105 | # detect cable faults 106 | # bit 7: RTD High Threshold / cable fault open 107 | # bit 6: RTD Low Threshold / cable fault short 108 | # bit 5: REFIN- > 0.85 x VBias -> must be requested 109 | # bit 4: REFIN- < 0.85 x VBias (FORCE- open) -> must be requested 110 | # bit 3: RTDIN- < 0.85 x VBias (FORCE- open) -> must be requested 111 | # bit 2: Overvoltage / undervoltage fault 112 | # bits 1,0 don't care 113 | #print "Status byte: %x" % status 114 | 115 | if (status & 0x80): 116 | raise FaultError("High threshold limit (Cable fault/open)") 117 | if (status & 0x40): 118 | raise FaultError("Low threshold limit (Cable fault/short)") 119 | if (status & 0x04): 120 | raise FaultError("Overvoltage or Undervoltage Error") 121 | 122 | def writeRegister(self, regNum, dataByte): 123 | GPIO.output(self.csPin, GPIO.LOW) 124 | 125 | # 0x8x to specify 'write register value' 126 | addressByte = 0x80 | regNum; 127 | 128 | # first byte is address byte 129 | self.sendByte(addressByte) 130 | # the rest are data bytes 131 | self.sendByte(dataByte) 132 | 133 | GPIO.output(self.csPin, GPIO.HIGH) 134 | 135 | def readRegisters(self, regNumStart, numRegisters): 136 | out = [] 137 | GPIO.output(self.csPin, GPIO.LOW) 138 | 139 | # 0x to specify 'read register value' 140 | self.sendByte(regNumStart) 141 | 142 | for byte in range(numRegisters): 143 | data = self.recvByte() 144 | out.append(data) 145 | 146 | GPIO.output(self.csPin, GPIO.HIGH) 147 | return out 148 | 149 | def sendByte(self,byte): 150 | for bit in range(8): 151 | GPIO.output(self.clkPin, GPIO.HIGH) 152 | if (byte & 0x80): 153 | GPIO.output(self.mosiPin, GPIO.HIGH) 154 | else: 155 | GPIO.output(self.mosiPin, GPIO.LOW) 156 | byte <<= 1 157 | GPIO.output(self.clkPin, GPIO.LOW) 158 | 159 | def recvByte(self): 160 | byte = 0x00 161 | for bit in range(8): 162 | GPIO.output(self.clkPin, GPIO.HIGH) 163 | byte <<= 1 164 | if GPIO.input(self.misoPin): 165 | byte |= 0x1 166 | GPIO.output(self.clkPin, GPIO.LOW) 167 | return byte 168 | 169 | def calcPT100Temp(self, RTD_ADC_Code): 170 | R_REF = 400.0 # Reference Resistor 171 | Res0 = 100.0; # Resistance at 0 degC for 400ohm R_Ref 172 | a = .00390830 173 | b = -.000000577500 174 | # c = -4.18301e-12 # for -200 <= T <= 0 (degC) 175 | c = -0.00000000000418301 176 | # c = 0 # for 0 <= T <= 850 (degC) 177 | print "RTD ADC Code: %d" % RTD_ADC_Code 178 | Res_RTD = (RTD_ADC_Code * R_REF) / 32768.0 # PT100 Resistance 179 | print "PT100 Resistance: %f ohms" % Res_RTD 180 | # 181 | # Callendar-Van Dusen equation 182 | # Res_RTD = Res0 * (1 + a*T + b*T**2 + c*(T-100)*T**3) 183 | # Res_RTD = Res0 + a*Res0*T + b*Res0*T**2 # c = 0 184 | # (c*Res0)T**4 - (c*Res0)*100*T**3 185 | # + (b*Res0)*T**2 + (a*Res0)*T + (Res0 - Res_RTD) = 0 186 | # 187 | # quadratic formula: 188 | # for 0 <= T <= 850 (degC) 189 | temp_C = -(a*Res0) + math.sqrt(a*a*Res0*Res0 - 4*(b*Res0)*(Res0 - Res_RTD)) 190 | temp_C = temp_C / (2*(b*Res0)) 191 | temp_C_line = (RTD_ADC_Code/32.0) - 256.0 192 | # removing numpy.roots will greatly speed things up 193 | #temp_C_numpy = numpy.roots([c*Res0, -c*Res0*100, b*Res0, a*Res0, (Res0 - Res_RTD)]) 194 | #temp_C_numpy = abs(temp_C_numpy[-1]) 195 | print "Straight Line Approx. Temp: %f degC" % temp_C_line 196 | print "Callendar-Van Dusen Temp (degC > 0): %f degC" % temp_C 197 | #print "Solving Full Callendar-Van Dusen using numpy: %f" % temp_C_numpy 198 | if (temp_C < 0): #use straight line approximation if less than 0 199 | # Can also use python lib numpy to solve cubic 200 | # Should never get here in this application 201 | temp_C = (RTD_ADC_Code/32) - 256 202 | return temp_C 203 | 204 | class FaultError(Exception): 205 | pass 206 | 207 | if __name__ == "__main__": 208 | 209 | import max31865 210 | csPin = 8 211 | misoPin = 9 212 | mosiPin = 10 213 | clkPin = 11 214 | max = max31865.max31865(csPin,misoPin,mosiPin,clkPin) 215 | tempC = max.readTemp() 216 | GPIO.cleanup() 217 | --------------------------------------------------------------------------------