/* xsns_39_max31855.ino - MAX31855 thermocouple sensor support for Tasmota Copyright (C) 2021 Markus Past This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef USE_MAX31855 /*********************************************************************************************\ * MAX31855 and MAX6675 - Thermocouple * * SetOption94 0 - MAX31855 * SetOption94 1 - MAX6675 \*********************************************************************************************/ #define XSNS_39 39 const char kMax31855Types[] PROGMEM = "MAX31855|MAX6675"; bool max31855_initialized = false; uint8_t max31855_pins_used = 0; //used as a bit array uint8_t max31855_count = 0; struct MAX31855_ResultStruct { uint8_t ErrorCode; // Error Codes: 0 = No Error / 1 = TC open circuit / 2 = TC short to GND / 4 = TC short to VCC float ProbeTemperature; // Measured temperature of the 'hot' TC junction (probe temp) float ReferenceTemperature; // Measured temperature of the 'cold' TC junction (reference temp) } MAX31855_Result[MAX_MAX31855S]; void MAX31855_Init(void) { if (PinUsed(GPIO_MAX31855CLK) && PinUsed(GPIO_MAX31855DO)) { // Set GPIO modes for SW-SPI pinMode(Pin(GPIO_MAX31855CLK), OUTPUT); pinMode(Pin(GPIO_MAX31855DO), INPUT); // Clock low digitalWrite(Pin(GPIO_MAX31855CLK), LOW); for (uint32_t i = 0; i < MAX_MAX31855S; i++) { if (PinUsed(GPIO_MAX31855CS, i)) { max31855_pins_used |= 1 << i; //set lowest bit max31855_count ++; // Set GPIO modes for SW-SPI pinMode(Pin(GPIO_MAX31855CS, i), OUTPUT); // Chip not selected digitalWrite(Pin(GPIO_MAX31855CS, i), HIGH); max31855_initialized = true; } } } } /* * MAX31855_ShiftIn(uint8_t Length, uint32_t index) * Communicates with MAX31855 via SW-SPI and returns the raw data read from the chip */ int32_t MAX31855_ShiftIn(uint8_t Length, uint32_t index) { int32_t dataIn = 0; digitalWrite(Pin(GPIO_MAX31855CS, index), LOW); // CS = LOW -> Start SPI communication delayMicroseconds(1); // CS fall to output enable = max. 100ns for (uint32_t i = 0; i < Length; i++) { digitalWrite(Pin(GPIO_MAX31855CLK), LOW); delayMicroseconds(1); // CLK pulse width low = min. 100ns / CLK fall to output valid = max. 40ns dataIn <<= 1; if (digitalRead(Pin(GPIO_MAX31855DO))) { dataIn |= 1; } digitalWrite(Pin(GPIO_MAX31855CLK), HIGH); delayMicroseconds(1); // CLK pulse width high = min. 100ns } digitalWrite(Pin(GPIO_MAX31855CS, index), HIGH); // CS = HIGH -> End SPI communication digitalWrite(Pin(GPIO_MAX31855CLK), LOW); return dataIn; } /* * MAX31855_GetProbeTemperature(int32_t RawData) * Decodes and returns the temperature of TCs 'hot' junction from RawData */ float MAX31855_GetProbeTemperature(int32_t RawData) { if (RawData & 0x80000000) { RawData = (RawData >> 18) | 0xFFFFC000; // Negative value - Drop lower 18 bits and extend to negative number } else { RawData >>= 18; // Positiv value - Drop lower 18 bits } float result = (RawData * 0.25f); // MAX31855 LSB resolution is 0.25°C for probe temperature return ConvertTemp(result); // Check if we have to convert to Fahrenheit } /* * MAX31855_GetReferenceTemperature(int32_t RawData) * Decodes and returns the temperature of TCs 'cold' junction from RawData */ float MAX31855_GetReferenceTemperature(int32_t RawData) { if (RawData & 0x8000) { RawData = (RawData >> 4) | 0xFFFFF000; // Negative value - Drop lower 4 bits and extend to negative number } else { RawData = (RawData >> 4) & 0x00000FFF; // Positiv value - Drop lower 4 bits and mask out remaining bits (probe temp, error bit, etc.) } float result = (RawData * 0.0625f); // MAX31855 LSB resolution is 0.0625°C for reference temperature return ConvertTemp(result); // Check if we have to convert to Fahrenheit } /* * MAX31855_GetResult(void) * Acquires the raw data via SPI, checks for MAX31855 errors and fills result structure */ void MAX31855_GetResult(void) { for (uint32_t i = 0; i < MAX_MAX31855S; i++) { if (max31855_pins_used & (1 << i)) { if (Settings->flag4.max6675) { // SetOption94 - Implement simpler MAX6675 protocol instead of MAX31855 int32_t RawData = MAX31855_ShiftIn(16, i); int32_t temp = (RawData >> 3) & ((1 << 12) - 1); /* Occasionally the sensor returns 0xfff, consider it an error */ if (temp == ((1 << 12) - 1)) { return; } MAX31855_Result[i].ErrorCode = 0; MAX31855_Result[i].ReferenceTemperature = NAN; MAX31855_Result[i].ProbeTemperature = ConvertTemp(0.25f * temp); } else { int32_t RawData = MAX31855_ShiftIn(32, i); uint8_t probeerror = RawData & 0x7; MAX31855_Result[i].ErrorCode = probeerror; MAX31855_Result[i].ReferenceTemperature = MAX31855_GetReferenceTemperature(RawData); if (probeerror) { MAX31855_Result[i].ProbeTemperature = NAN; // Return NaN if MAX31855 reports an error } else { MAX31855_Result[i].ProbeTemperature = MAX31855_GetProbeTemperature(RawData); } } } } } void MAX31855_Show(bool Json) { char sensor_name_text[10]; char sensor_name[12]; uint8_t report_once = 0; GetTextIndexed(sensor_name_text, sizeof(sensor_name_text), Settings->flag4.max6675, kMax31855Types); sprintf(sensor_name, "%s",sensor_name_text); for (uint32_t i = 0; i < MAX_MAX31855S; i++) { if (max31855_pins_used & (1 << i)) { if (max31855_count > 1) { sprintf(sensor_name, "%s%c%d",sensor_name_text, IndexSeparator(), i); } if (Json) { ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%*_f,\"" D_JSON_REFERENCETEMPERATURE "\":%*_f,\"" D_JSON_ERROR "\":%d}"), \ sensor_name, Settings->flag2.temperature_resolution, &MAX31855_Result[i].ProbeTemperature, Settings->flag2.temperature_resolution, &MAX31855_Result[i].ReferenceTemperature, MAX31855_Result[i].ErrorCode); if ((0 == TasmotaGlobal.tele_period) && (!report_once)) { #ifdef USE_DOMOTICZ DomoticzFloatSensor(DZ_TEMP, MAX31855_Result[i].ProbeTemperature); #endif // USE_DOMOTICZ #ifdef USE_KNX KnxSensor(KNX_TEMPERATURE, MAX31855_Result[i].ProbeTemperature); #endif // USE_KNX report_once++; } #ifdef USE_WEBSERVER } else { WSContentSend_Temp(sensor_name, MAX31855_Result[i].ProbeTemperature); #endif // USE_WEBSERVER } } } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xsns39(uint32_t function) { bool result = false; if (FUNC_INIT == function) { MAX31855_Init(); } else if (max31855_initialized) { switch (function) { case FUNC_EVERY_SECOND: MAX31855_GetResult(); break; case FUNC_JSON_APPEND: MAX31855_Show(true); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: MAX31855_Show(false); break; #endif // USE_WEBSERVER } } return result; } #endif // USE_MAX31855