Post your best Lua script examples here

User avatar
By athompson673
#88708 I made a thing...

max31865 Lua NodeMCU library - https://datasheets.maximintegrated.com/ ... X31865.pdf

A simplified interface for PT-RTD thermometers using the adafruit max31865 module. (most other max31865 modules are identical)

- depends on spi and bit libraries -

PT-RTD thermometers have become an extremely cheap source of highly accurate temperature sensing. The MAX31865 doesn't do all the work for you however, and temperature conversion and calibration are left up to the user. This library seeks to make it extremely fast to hook up the module and get right to measuring temperature while at the same time being capable of fairly high accuracy.

Simple Usage:
Code: Select all> max31865 = require('MAX31865')
> thermometer = max31865.setup(4) --spi CS on pin 4
> print(thermometer:temp()) --read temperature in C
25.514875945444



Configuration:
Several configuration items are accessable via the config method.
  • run_stop: A value of max31865.RUN will turn on the bias voltage to the RTD, and begin sampling the ADC in continuous mode. A value of max31865.STOP will turn off the burden voltage and sampling to conserve power. Readings should be taken after power is on for a little while to allow adc to stabalize, and sensor self-heating to level out (see datasheet).
  • wires: max31865.TWO_WIRE, max31865.THREE_WIRE, max31865.FOUR_WIRE. Are you using a 2-wire 3-wire or 4-wire RTD? remember to set the solder pads accordingly as well.
  • filter: An included filter intended to reject noise from mains power at either 50Hz or 60Hz. Choose according to your local mains power specification: max31865.FILTER_50, max31865.FILTER_60

Code: Select all> thermometer:config(max31865.RUN, max31865.THREE_WIRE, max31865.FILTER_60)
> -- ^this is the default config when thermometer:setup() is called   



Calibration:
By default the library assumes the burden resistor on your module is perfectly 430 ohms, and your temperature probe is perfectly 100 ohms at 0C. A single
point calibration with the freezing point of distilled water can get you quite a lot closer accuracy.

Code: Select all> print(thermometer:adc()) --stick thermometer in ice water to calibrate 0C
7635
> thermometer.adc_zero = 7635



Advanced Calibration:
A single point calibration is not perfect, and the most advanced systems fully characterize the temperature probe with three constants used in the Callendar-Van Dusen equation: A, B, and C. If you pay a lot of money, you can sometimes purchase pre-characterized probes that may come with these values. Newton's method is then used to calculate this equation, and the number of approximations may be adjusted as desired. This is a fairly heavy equation, so keep in mind processing time and the ever present watchdog timer. In my testing, 5 iterations seemed plenty to get an accurate value. The Zero point depends on the probe as well as the burden resistor, and will still need to be calibrated.

Code: Select all> max31865._A = 3.908300e-3
> max31865._B = -5.77500e-7
> max31865._C = -4.18301e-12
> max31865._root_find_iterations = 5
> print(thermometer:adc()) --stick thermometer in ice water to calibrate 0C
7635
> thermometer.adc_zero = 7635



Advanced usage:
The intent of using a library is to simplify one's life, so some of the advanced modes (fault detection) are not explicitly set up with this library. They can still be accessed without too much trouble using the `_spi_transaction` method. See the datasheet (link at top of page) for more information.

Code: Select all> =encoder.toHex(thermometer:_spi_transaction('\3', 2)) --read fault Hi register
> thermometer:_spi_transaction('\128\208', 0) --write 0xd0 to config register



MAX31865.lua
Code: Select all--MAX31865.lua

if spi == nil or bit == nil then
    error("max31865 Lua requires the spi and bit libraries")
end

spi.setup(1, spi.MASTER, spi.CPOL_HIGH, spi.CPHA_HIGH, 8, 40)

local max31865 = {}

max31865.RUN = 1
max31865.STOP = 0
max31865.FOUR_WIRE = 0
max31865.THREE_WIRE = 1
max31865.TWO_WIRE = 0
max31865.FILTER_50 = 1
max31865.FILTER_60 = 0
max31865._A = 3.908300e-3
max31865._B = -5.77500e-7
max31865._C = -4.18301e-12
max31865._root_find_iterations = 5


local _CVD_root_finder = function(r0, R)
--The Callendar Van Dusen Equation for Platinum Based RTD Thermometers
--Newton's method root finder
    local T = 0
    local a, b, c = max31865._A, max31865._B , max31865._C
    local i = max31865._root_find_iterations
    for _ = 1,i do
        if T < 0 then
            local r = r0 * (1 + a*T + b*T*T + c*(T - 100)*T*T*T) - R
            local slope = r0 * (a + 2*b*T + 4*c*(T - 75)*T*T)
            T = T - r/slope
        else
            local r = r0 * (1 + a*T + b*T*T) - R
            local slope = r0 * (a + 2*b*T)
            T = T - r/slope
        end
    end
    return T
end

max31865.setup = function(CS, config)
    local t = {}
   
    --chip select gpio setup
    t._CS = CS
    gpio.mode(t._CS, gpio.OUTPUT)
    gpio.write(t._CS, gpio.HIGH)
   
    t._spi_transaction = function(self, write_data, read_bytes)
        spi.set_mosi(1, write_data)
        gpio.write(self._CS, 0)
        spi.transaction( 1, 0, 0, 0, 0, #write_data * 8, 0, read_bytes * 8)
        gpio.write(self._CS, 1)
        return spi.get_miso(1, read_bytes)
    end
   
    t._config = config or 0xd0  --0b11010000 default
    -- see: https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
    t.config = function(self, run_stop, wires, filter)
        if run_stop ~= nil then
            if run_stop == 1 then
                self._config = bit.set(self._config, 7)
                self._config = bit.set(self._config, 6)
            elseif run_stop == 0 then
                self._config = bit.clear(self._config, 7)
                self._config = bit.clear(self._config, 6)
            end
        end
        if wires ~= nil then
            if wires == 1 then
                self._config = bit.set(self._config, 4)
            elseif wires == 0 then
                self._config = bit.clear(self._config, 4)
            end
        end
        if filter ~= nil then
            if filter == 1 then
                self._config = bit.set(self._config, 0)
            elseif filter == 0 then
                self._config = bit.clear(self._config, 0)
            end
        end
        self:_spi_transaction('\128' .. string.char(self._config), 0)
    end
   
    t.adc = function(self)
        local raw = self:_spi_transaction('\1',2)
        return bit.rshift(string.byte(raw,1) * 256 + string.byte(raw, 2), 1)
    end
   
    t.resistance = function(self, Rref)
        Rref = Rref or 430
        return (self:adc() *  Rref) / 32768
    end
   
    --default zero point for ideal 100ohm PRT and ideal 430ohm burden resistor: 7620.5
    t.adc_zero = 7620.5
   
    t.temp = function(self)
        return _CVD_root_finder(self.adc_zero, self:adc())
    end
   
    t:config()
    return t
end


return max31865