diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index b2e29a1c94..a3b44d5739 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -562,6 +562,7 @@ * ================================================================ * Analog Thermocouple Boards * ================================================================ + * -18 : ADS1118 with Thermocouple, e.g., Mightyboard rev G/H * -4 : AD8495 with Thermocouple * -1 : AD595 with Thermocouple * diff --git a/Marlin/src/inc/Conditionals-4-adv.h b/Marlin/src/inc/Conditionals-4-adv.h index 664fd38e40..06e0355f63 100644 --- a/Marlin/src/inc/Conditionals-4-adv.h +++ b/Marlin/src/inc/Conditionals-4-adv.h @@ -483,6 +483,11 @@ #define TEMP_SENSOR_0_IS_AD8495 1 #elif TEMP_SENSOR_0 == -1 #define TEMP_SENSOR_0_IS_AD595 1 +#elif TEMP_SENSOR_0 == -18 + #define HAS_ADS1118 1 + #define TEMP_SENSOR_0_IS_ADS1118 1 + #define TEMP_SENSOR_0_ADS_TMIN 0 + #define TEMP_SENSOR_0_ADS_TMAX 1024 #elif TEMP_SENSOR_0 > 0 #define TEMP_SENSOR_0_IS_THERMISTOR 1 #if TEMP_SENSOR_0 == 1000 @@ -526,6 +531,11 @@ #define TEMP_SENSOR_1_IS_AD8495 1 #elif TEMP_SENSOR_1 == -1 #define TEMP_SENSOR_1_IS_AD595 1 +#elif TEMP_SENSOR_1 == -18 + #define HAS_ADS1118 1 + #define TEMP_SENSOR_1_IS_ADS1118 1 + #define TEMP_SENSOR_1_ADS_TMIN 0 + #define TEMP_SENSOR_1_ADS_TMAX 1024 #elif TEMP_SENSOR_1 > 0 #define TEMP_SENSOR_1_IS_THERMISTOR 1 #if TEMP_SENSOR_1 == 1000 diff --git a/Marlin/src/inc/Warnings.cpp b/Marlin/src/inc/Warnings.cpp index 67a0aa26c7..f350184732 100644 --- a/Marlin/src/inc/Warnings.cpp +++ b/Marlin/src/inc/Warnings.cpp @@ -118,6 +118,10 @@ #warning "Warning! Don't use dummy thermistors (998/999) for final build!" #endif +#if ANY_THERMISTOR_IS(-18) + #warning "ADS1118 support (-18) is in development" +#endif + #if NONE(HAS_RESUME_CONTINUE, HOST_PROMPT_SUPPORT, UNIT_TEST, NO_USER_FEEDBACK_WARNING) #warning "Your Configuration provides no method to acquire user feedback! (Define NO_USER_FEEDBACK_WARNING to suppress this warning.)" #endif diff --git a/Marlin/src/libs/adc/adc_ads1118.cpp b/Marlin/src/libs/adc/adc_ads1118.cpp new file mode 100644 index 0000000000..857f1739c7 --- /dev/null +++ b/Marlin/src/libs/adc/adc_ads1118.cpp @@ -0,0 +1,339 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * 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 . + * + */ + +/** + * adc_ads1118.cpp - library for Texas Instruments ADS1118 - 16-Bit Analog-to-Digital Converter + * based in the sailfish code for ADS1118, ThermocoupleReader, TemperatureTable + * For implementation details, please take a look at the datasheet: + * https://www.ti.com/product/ADS1118 + */ + +#include "../../inc/MarlinConfig.h" + +#if HAS_ADS1118 + +#include "adc_ads1118.h" + +#include "../../HAL/shared/Delay.h" +#include "../../core/macros.h" + +#define ADS1118_CONV_MS 10 +#define ADS1118_CH_MASK 12 + +// Constructor +void ADS1118::init(uint8_t cs, uint8_t mosi, uint8_t miso, uint8_t sck) { + cs_pin = cs; mosi_pin = mosi; miso_pin = miso; sck_pin = sck; + + //#define TEMP_0_CS_PIN 79 // E6 + //#define TEMP_0_SCK_PIN 78 // E2 + //#define TEMP_0_MISO_PIN 80 // E7 + //#define TEMP_0_MOSI_PIN 84 // H2 + + pinMode(cs_pin, OUTPUT); + pinMode(mosi_pin, OUTPUT); + pinMode(sck_pin, OUTPUT); + pinMode(miso_pin, INPUT); + + deselect(); + sckLow(); +} + +// Sets ADS to start a single shot conversion. It will be read async when ready (after ADS1118_CONV_MS), non blocking +void ADS1118::startConversion(const uint8_t pair) { + if (isBusy) return; + + uint16_t config = 0x858B; // 0b 1000 0101 1000 1011 : SS start, single-ended off, gain ±2.048V, single-shot mode, 128SPS, ADC mode, Pullup enable, Write config + switch (pair) { + default: + case 0: config |= (0x0 << ADS1118_CH_MASK); currentchannel = 0; break; // AIN0-AIN1 + case 1: config |= (0x3 << ADS1118_CH_MASK); currentchannel = 1; break; // AIN2-AIN3 + } + + digitalWrite(cs_pin, LOW); + transfer16(config); + digitalWrite(cs_pin, HIGH); + + isBusy = true; + startTime = millis(); +} + +// Determine that a conversion is ready by its elapsed time, if true, reads data and stores in _lastValue +bool ADS1118::ready() { + if (!isBusy) return true; + if (millis() - startTime >= ADS1118_CONV_MS) { + digitalWrite(cs_pin, LOW); + uint16_t raw = transfer16(0); + digitalWrite(cs_pin, HIGH); + lastValue = (int16_t)raw; + isBusy = false; + } + return !isBusy; +} + +int16_t ADS1118::read() { return lastValue; } + +bool ADS1118::busy() { return isBusy; } + +void ADS1118::loop() { ready(); } // updates status and value + +// Sets ADS to start Continuous conversion mode +void ADS1118::startContinuousConversion(const uint8_t channel_pair) { + SERIAL_ECHOLNPGM("ADS1118 Set to start conv"); + if (isBusy) return; + // 0x048B b 0000 0100 1000 1011 : SS start, single-ended off, gain ±2.048V, Continuous conversion mode, 128SPS, ADC mode, Pullup enable, Write config + // 0x049B: read internal temp + uint16_t config = 0x048B; + switch (channel_pair) { + default: + case 0: config |= (0x0 << ADS1118_CH_MASK); currentchannel = 0; break; // AIN0-AIN1 + case 1: config |= (0x3 << ADS1118_CH_MASK); currentchannel = 1; break; // AIN2-AIN3 + } + + digitalWrite(cs_pin, LOW); + transfer16(config); + digitalWrite(cs_pin, HIGH); + + isBusy = true; + startTime = millis(); + SERIAL_ECHOLNPGM("ADS1118 Leaving start conv"); +} + +// Check if ADS has a complete conversion +bool ADS1118::checkDataReady() { + digitalWrite(cs_pin, LOW); + const uint8_t isReady = !digitalRead(miso_pin); // Read MISO, is low when ready + digitalWrite(cs_pin, HIGH); + return isReady; +} + +// Read ADS data +uint16_t ADS1118::readData() { + digitalWrite(cs_pin, LOW); + const uint16_t data = transfer16(0); + digitalWrite(cs_pin, HIGH); + return data; +} + +// Read and write ADS data +uint16_t ADS1118::readWriteData(const uint16_t config) { + digitalWrite(cs_pin, LOW); + const uint16_t data = transfer16(config); + digitalWrite(cs_pin, HIGH); + return data; +} + +// Reads and returns a single channel inmediately with delay (blocking) +int16_t ADS1118::readChannel(const uint8_t channel) { + const uint16_t config = configChannel(channel); + transfer16(config); + + // ADS1118 takes ~8 ms to convert + delay(10); + + const int16_t value = (int16_t)transfer16(config); + return value; +} + +float ADS1118::readVoltage(const uint8_t channel, const float vref) { + const int16_t raw = readChannel(channel); + return (raw / 32768.0f) * vref; // scale 16 bits to voltage +} + +float ADS1118::readInternalTemp() { + const uint16_t config = config_ADC_SS_TEMP; // Config internal temp read 0x8F80; // Config internal temp read & start SS conversion + select(); + transfer16(config); + deselect(); + delay(10); + select(); + int16_t raw = (int16_t)transfer16(config); + deselect(); + return convertInternalTemp(raw); +} + +// Convert raw internal temperature data to °C +float ADS1118::convertInternalTemp(const int16_t data) { + return float(data >> 2) * 0.03125f; // 14 bit left aligned, 0.03125 °C per LSB as datasheet +} + +uint16_t ADS1118::configChannel(const uint8_t channel) { + uint16_t config = 0; + + // Config single-ended inputs PGA ±2.048V, SS mode, 128SPS no pull up + switch (channel) { + case 0: config = 0xC583; break; // 0b 1100 0101 1000 0011 Ch1 & start SS conversion + case 1: config = 0xD583; break; // 0b 1101 0101 1000 0011 Ch2 & start SS conversion + case 2: config = 0xE583; break; // 0b 1110 0101 1000 0011 Ch3 & start SS conversion + case 3: config = 0xF583; break; // 0b 1111 0101 1000 0011 Ch4 & start SS conversion + default: config = 0x8583; break; + } + + return config; +} + +uint32_t ADS1118::transfer32(uint16_t data) { + uint16_t result_prev = transfer16(data); // send config, receive previous result + uint16_t config_echo = transfer16(0x0000); // send dummy, receive echo of configuration + + // Combine both into a single 32-bit value + return ((uint32_t)result_prev << 16) | config_echo; +} + +uint16_t ADS1118::readConfig() { + uint16_t config_echo; + select(); + transfer16(0x0000); // send 16‑bit dummy, ignore reply; configuration is in the following 16‑bit transfer + config_echo = transfer16(0x0000); // send dummy, receive echo of configuration + SERIAL_ECHOPGM("ADS1118 Configuration: "); SERIAL_ECHOLN(config_echo); + deselect(); + return config_echo; +} + +uint16_t ADS1118::transfer16(uint16_t data) { + uint8_t high = transfer8((uint8_t)(data >> 8)); + uint8_t low = transfer8((uint8_t)(data & 0xFF)); + return (uint16_t)(high << 8) | low; +} + +uint8_t ADS1118::transfer8(uint8_t data) { + uint8_t recv = 0; + for (uint8_t i = 0; i < 8; i++) { + // Send MSB first + if (data & 0x80) digitalWrite(mosi_pin, HIGH); else digitalWrite(mosi_pin, LOW); + data <<= 1; + + sckHigh(); + DELAY_NS(100); // small delay for stability + + recv <<= 1; + if (digitalRead(miso_pin)) recv |= 0x01; + + sckLow(); + DELAY_NS(100); + } + return recv; +} + +void ADS1118::select() { digitalWrite(cs_pin, LOW); } +void ADS1118::deselect() { digitalWrite(cs_pin, HIGH); } + +void ADS1118::sckHigh() { digitalWrite(sck_pin, HIGH); } +void ADS1118::sckLow() { digitalWrite(sck_pin, LOW); } + +// Static variable definitions +uint8_t ADS1118::cs_pin = 0; +uint8_t ADS1118::mosi_pin = 0; +uint8_t ADS1118::miso_pin = 0; +uint8_t ADS1118::sck_pin = 0; +unsigned long ADS1118::startTime = 0; +int16_t ADS1118::lastValue = 0; +int16_t ADS1118::config = 0; +uint16_t ADS1118::current_config = 0; +uint16_t ADS1118::previous_config = 0; + +bool ADS1118::isBusy = false; +uint8_t ADS1118::currentchannel = 0; + +// ADS1118, global instance +ADS1118 ads1118; +ThermocoupleK thck_0; +ThermocoupleK thck_1; + +//#if TEMP_SENSOR_0_IS_ADS1118 +// #warning "ThcK 0 is enabled" +// ThermocoupleK thck_0; +//#endif + +//#if TEMP_SENSOR_1_IS_ADS1118 +// #warning "ThcK 1 is enabled" +// ThermocoupleK thck_1; +//#endif + +void ThermocoupleK::init() {} + +// --- Convert ADC reading to °C --- +float ThermocoupleK:: tempReadtoCelsius(const int16_t rawADC) { + //Serial.println((int16_t)pgm_read_word(&ThermocoupleK_Lookup[TEMP_TABLE_SIZE - 1])); + + if (rawADC > (int16_t) pgm_read_word(&table_thermocouple_k[TEMP_TABLE_SIZE - 1].adc)) + return TEMP_MAX_TEMP; + if (rawADC < (int16_t) pgm_read_word(&table_thermocouple_k[0].adc)) + return TEMP_MIN_TEMP; + + // Linear search in the table (from lowest to highest ADC value) + for (uint16_t i = 0; i < TEMP_TABLE_SIZE - 1; i++) { + + int16_t adc1 = pgm_read_word(&table_thermocouple_k[i].adc); + int16_t adc2 = pgm_read_word(&table_thermocouple_k[i + 1].adc); + // Serial.print(i); Serial.print(" "); Serial.print(adc1); Serial.print(" "); Serial.print(adc2); + + if (rawADC >= adc1 && rawADC < adc2) { // in-between tableValue and nextValue + // Approximate temperature via linear interpolation + //float frac = float(rawADC - adc2) / float(adc1 - adc2); + int16_t t1 = pgm_read_word(&table_thermocouple_k[i].temp); + int16_t t2 = pgm_read_word(&table_thermocouple_k[i+1].temp); + // Serial.print(" "); Serial.print(t1); Serial.print(" "); Serial.print(t2); + float tempC = t1 + (float) (rawADC - adc1) * (float(t2 - t1) / float(adc2 - adc1)); // TEMP_TABLE_OFFSET + i + frac; + return tempC; + } + // Serial.println(""); + } + + return TEMP_MIN_TEMP; +} + +float ThermocoupleK:: calcTempCelsius() { + _Tcold = ads1118.convertInternalTemp(_raw_cold); + _Thot = tempReadtoCelsius(_raw_hot); + //SERIAL_ECHOPGM("ADS1118 TCold "); SERIAL_ECHOLN(_Tcold); + //SERIAL_ECHOPGM("ADS1118 THot "); SERIAL_ECHOLN(_Thot); + return _Thot + _Tcold; +} + +void ThermocoupleK::setRawCold(const int16_t raw_cold) { + _raw_cold = raw_cold; +} + +void ThermocoupleK::setRawHot(const int16_t raw_hot) { + _raw_hot = raw_hot; +} +void ThermocoupleK::setTcold(const float tcold) { + _Tcold = tcold; +} + +float ThermocoupleK::getTcold() { + return _Tcold; +} + +void ThermocoupleK::setThot(const float thot) { + _Thot = thot; +} + +float ThermocoupleK::getThot() { + return _Thot; +} + +float ThermocoupleK:: getTempCelsius() { + return _Thot + _Tcold; +} + +#endif // HAS_ADS1118 diff --git a/Marlin/src/libs/adc/adc_ads1118.h b/Marlin/src/libs/adc/adc_ads1118.h new file mode 100644 index 0000000000..d8555db7a0 --- /dev/null +++ b/Marlin/src/libs/adc/adc_ads1118.h @@ -0,0 +1,213 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * 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 . + * + */ +#pragma once + +/** + * Based on Arduino Library for Texas Instruments ADS1118 - 16-Bit Analog-to-Digital Converter with internal Reference and Temperature Sensor + * https://www.ti.com/product/ADS1118 + * https://github.com/ADS1xxx-Series-ADC-Libraries/ADS1118 + */ + +#include "../../inc/MarlinConfigPre.h" + +// ADS config register bits and values: +// Bit 15: Single-shot conversion start +#define ADS_SS_NOP 0x0000 +#define ADS_SS_START 0x8000 +// Bits 14-12 Mux +#define INPUT_CHAN_0_1 0x0000 // *Default* +#define INPUT_CHAN_0_3 0x1000 +#define INPUT_CHAN_1_3 0x2000 +#define INPUT_CHAN_2_3 0x3000 +#define INPUT_CHAN_0_G 0x4000 +#define INPUT_CHAN_1_G 0x5000 +#define INPUT_CHAN_2_G 0x6000 +#define INPUT_CHAN_3_G 0x7000 + +/// Bits 11-9 ADC PGA gain select bits +/// the gain setting sets the voltage range for the ADC. Full Scale Range +/// voltage is the read value at 0x7FFF (the ADC returns a 16bit integer integer value) +/// we use the highest possible gain setting - k-Type thermocouples have a voltage +/// difference of ~12mV at 300C +#define PGA_0_6_14 0x0000 // Gain = 1, Full Scale Voltage is 6.14V +#define PGA_1_4_09 0x0200 // Gain = 1.5, Full Scale Voltage is 4.09V +#define PGA_2_2_04 0x0400 // Gain = 3, Full Scale Voltage is 2.04V *Default* +#define PGA_3_1_02 0x0600 // Gain = 6, Full Scale Voltage is 1.02V +#define PGA_4_0_512 0x0800 // Gain = 12, Full Scale Voltage is 0.512V +#define PGA_5_0_256 0x0A00 // Gain = 24, Full Scale Voltage is 0.256V + +/// Bit 8: operating mode: single sample or continous conversion +#define CONTINUOUS_CONVERSION_MODE 0x0000 // continous conversion +#define SINGLE_SHOT_MODE 0x0100 // single sample + +/// Bit 7-5: Data Rate, Sample Frequency select bits (Hz) +#define SAMPLE_FREQ_860 0x00E0 +#define SAMPLE_FREQ_475 0x00C0 +#define SAMPLE_FREQ_250 0x00A0 +#define SAMPLE_FREQ_128 0x0080 //* default +#define SAMPLE_FREQ_64 0x0060 +#define SAMPLE_FREQ_32 0x0040 +#define SAMPLE_FREQ_16 0x0020 +#define SAMPLE_FREQ_08 0x0000 + +/// Bit 4: ADC mode (thermocouples) vs temperature sensor (on-board cold_junction temp sensor) +#define ADC_MODE 0x0000 +#define TEMP_MODE 0x0010 + +/// Bit 3: Pull up enable +#define PULL_UP_DISABLE 0x0000 +#define PULL_UP_ENABLE 0x0008 + +/// write new data to the config register ( if bits <2:1> are not <01> the config bytes are ignored) +#define ADS_NOP 0x0000 +#define WRITE_CONFIG 0x0002 + +/// Number of read cycles between cold junction temperature reads +/// we don't need to read the cold junction temperature every cycle +/// because we don't expect it to change much +#define TEMP_CHECK_COUNT 120 + +#define THERM_CHANNEL_ONE 0 +#define THERM_CHANNEL_TWO 1 +#define THERM_CHANNEL_HBP 2 +#define THERM_COLD_JUNCTION 3 + +typedef struct { + int16_t adc; + int16_t temp; +} ADC_Lookup; + +const static ADC_Lookup table_thermocouple_k[] PROGMEM = { + { -304, -64}, + { -232, -48}, + { -157, -32}, + { -79, -16}, + { 0, 0}, + { 82, 16}, + { 164, 32}, + { 248, 48}, + { 333, 64}, + { 418, 80}, + { 503, 96}, + { 588, 112}, + { 672, 128}, + { 755, 144}, + { 837, 160}, + { 919, 176}, + { 1001, 192}, + { 1083, 208}, + { 1165, 224}, + { 1248, 240}, + { 1331, 256}, + { 1415, 272}, + { 1499, 288}, + { 1584, 304}, + { 1754, 336}, + { 1840, 352}, + { 1926, 368}, + { 2012, 384}, + { 2099, 400} +}; + +#define TEMP_TABLE_SIZE (sizeof(table_thermocouple_k) / sizeof(table_thermocouple_k[0])) +#define TEMP_MIN_TEMP 0 +#define TEMP_MAX_TEMP 300 +#define TEMP_TABLE_OFFSET 0 // grados Celsius por índice + +class ADS1118 { + public: + static void init(uint8_t cs, uint8_t mosi, uint8_t miso, uint8_t sck); + static void startConversion(const uint8_t pair); // start conversion + static bool ready(); // indicates whether it is already ready to be read + static int16_t read(); // read converted value + static bool busy(); // conversion status + static void loop(); // non‑blocking cycle + + static bool checkDataReady(); + static void startContinuousConversion(const uint8_t channel_pair); + static uint16_t readData(); + static int16_t readChannel(const uint8_t channel); + static uint16_t readConfig (); + static uint16_t readWriteData(const uint16_t config); + + float convertInternalTemp(const int16_t data); + float readInternalTemp(); + + uint16_t config_ADC_SS_CH0 = ADS_SS_START | INPUT_CHAN_0_1| PGA_5_0_256 | SINGLE_SHOT_MODE| SAMPLE_FREQ_128 | ADC_MODE | PULL_UP_ENABLE | WRITE_CONFIG; + uint16_t config_ADC_SS_CH1 = ADS_SS_START | INPUT_CHAN_2_3 | PGA_5_0_256 | SINGLE_SHOT_MODE| SAMPLE_FREQ_128 | ADC_MODE | PULL_UP_ENABLE | WRITE_CONFIG; + uint16_t config_ADC_SS_TEMP = ADS_SS_START | PGA_5_0_256 | SINGLE_SHOT_MODE | SAMPLE_FREQ_128 | TEMP_MODE | PULL_UP_ENABLE | WRITE_CONFIG; + + static uint16_t current_config; + static uint16_t previous_config; + + private: + static void spiTransfer(uint8_t data, uint8_t &resp); + static void writeWord(uint16_t word); + static void readWord(uint16_t &word); + + static uint32_t transfer32 (uint16_t data); + static uint16_t transfer16 (uint16_t data); + static uint8_t transfer8 (uint8_t data); + + + static void select(); + static void deselect(); + + static void sckHigh(); + static void sckLow(); + + static uint8_t cs_pin, mosi_pin, miso_pin, sck_pin; + static unsigned long startTime; + static int16_t lastValue; + static int16_t config; + + static bool isBusy; + static uint8_t currentchannel; + + //uint16_t configForChannel(const uint8_t channel); + static uint16_t configChannel(const uint8_t channel); + float readVoltage(const uint8_t channel, const float vref); +}; + +class ThermocoupleK { + public: + void init(); + float tempReadtoCelsius(int16_t rawADC); + float calcTempCelsius(); + float getTempCelsius(); + float _Tcold, _Thot; + int16_t _raw_cold, _raw_hot; + + void setRawCold(int16_t raw_cold); + void setRawHot(int16_t raw_hot); + + void setTcold(float tcold); + float getTcold(); + + void setThot(float thot); + float getThot(); +}; + +extern ADS1118 ads1118; + +extern ThermocoupleK thck_0; +extern ThermocoupleK thck_1; diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index 00bf83a345..945134fd95 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -207,6 +207,13 @@ #include "stepper.h" #endif + +#define TEMP_SENSOR_IS_ADS(n, M) (ENABLED(TEMP_SENSOR_##n##_IS_ADS##M) || (ENABLED(TEMP_SENSOR_REDUNDANT_IS_ADS##M) && REDUNDANT_TEMP_MATCH(SOURCE, E##n))) + +#if HAS_ADS1118 + #include "../libs/adc/adc_ads1118.h" +#endif + #if ENABLED(FILAMENT_WIDTH_SENSOR) #include "../feature/filwidth.h" #endif @@ -2576,10 +2583,37 @@ void Temperature::task() { } #endif + +#if ANY_THERMISTOR_IS(-18) + + // Conversion for ADS1118 in differential mode (K-type) + // Each LSB bit ≈ 62.5 µV → ~1.5 °C (no calibration). + // Adjustable with GAIN and OFFSET from Configuration_adv.h + + static constexpr celsius_float_t temp_ads1118(const uint8_t e) { + celsius_float_t temp = 0; + switch (e) { + case 0: + temp = thck_0.calcTempCelsius(); + //SERIAL_ECHO("temp ads1118: "); SERIAL_ECHOLN(temp); + break; + case 1: + temp = thck_1.calcTempCelsius(); + break; + default: + temp = -14.0f; // Fallback to error temperature + break; + } + return temp; + } + +#endif // ANY_THERMISTOR_IS(-18) + #if HAS_HOTEND // Derived from RepRap FiveD extruder::getTemperature() // For hot end temperature measurement. celsius_float_t Temperature::analog_to_celsius_hotend(const raw_adc_t raw, const uint8_t e) { + //SERIAL_ECHOLN(e); if (e >= HOTENDS) { SERIAL_ERROR_START(); SERIAL_ECHO(e); @@ -2605,6 +2639,8 @@ void Temperature::task() { return temp_ad595(raw); #elif TEMP_SENSOR_0_IS_AD8495 return temp_ad8495(raw); + #elif TEMP_SENSOR_0_IS_ADS1118 + return temp_ads1118(e); #else break; #endif @@ -2624,6 +2660,8 @@ void Temperature::task() { return temp_ad595(raw); #elif TEMP_SENSOR_1_IS_AD8495 return temp_ad8495(raw); + #elif TEMP_SENSOR_1_IS_ADS1118 + return temp_ads1118(e); #else break; #endif @@ -2875,6 +2913,31 @@ void Temperature::updateTemperaturesFromRawValues() { temp_bed.setraw(read_max_tc_bed()); #endif + // Read ADC ADS1118 + // Note: For ADS1118, we don't call setraw() because read_ads1118() returns int16_t + // (differential measurement can be negative) but raw_adc_t is uint16_t. + // Instead, the ThermocoupleK object (thck_0/thck_1) handles the conversion + // internally, and analog_to_celsius_hotend() will retrieve the computed + // temperature via thck_0.getThot(). This avoids type overflow and keeps + // the conversion logic centralized and ISR-light. + #if TEMP_SENSOR_IS_ADS(0, 1118) + #warning "ADS1118 is selected for hotend 0" + temp_hotend[0].setraw(READ_ADS(0)); + //SERIAL_ECHOPGM("ADS1118 Tcold="); + //SERIAL_ECHO(thck_0.getTcold()); + //SERIAL_ECHOPGM(" Thot="); + //SERIAL_ECHOLN(thck_0.getThot()); + #endif + + #if TEMP_SENSOR_IS_ADS(1, 1118) + #warning "ADS1118 is selected for hotend 1" + temp_hotend[1].setraw(READ_ADS(1)); + //SERIAL_ECHOPGM("ADS1118 Tcold="); + //SERIAL_ECHO(thck_1.getTcold()); + //SERIAL_ECHOPGM(" Thot="); + //SERIAL_ECHOLN(thck_1.getThot()); + #endif + #if HAS_HOTEND HOTEND_LOOP() temp_hotend[e].celsius = analog_to_celsius_hotend(temp_hotend[e].getraw(), e); #endif @@ -2891,7 +2954,8 @@ void Temperature::updateTemperaturesFromRawValues() { TERN_(HAS_POWER_MONITOR, power_monitor.capture_values()); #if HAS_HOTEND - #define _TEMPDIR(N) TEMP_SENSOR_IS_ANY_MAX_TC(N) ? 0 : TEMPDIR(N), + + #define _TEMPDIR(N) (TEMP_SENSOR_IS_ANY_MAX_TC(N) || TEMP_SENSOR_IS_ADS(N,1118)) ? 0 : TEMPDIR(N), static constexpr int8_t temp_dir[HOTENDS] = { REPEAT(HOTENDS, _TEMPDIR) }; HOTEND_LOOP() { @@ -2975,6 +3039,30 @@ void Temperature::init() { TERN_(PROBING_HEATERS_OFF, paused_for_probing = false); + //#define TEMP_0_CS_PIN 79 // E6 + //#define TEMP_0_SCK_PIN 78 // E2 + //#define TEMP_0_MISO_PIN 80 // E7 + //#define TEMP_0_MOSI_PIN 84 // H2 + + #if HAS_ADS1118 + ads1118.init(TEMP_0_CS_PIN, TEMP_0_MOSI_PIN, TEMP_0_MISO_PIN, TEMP_0_SCK_PIN); // Initialize the ADS1118, global instance + ads1118.readConfig(); + #endif + + // ADS TC related macros + #if TEMP_SENSOR_IS_ADS(0, 1118) + #warning "ADS1118 is selected for temp 0" + thck_0.init(); + //SERIAL_ECHOLNPGM("ADS1118 start initial conversion for Tcold..."); + thck_0.setTcold (ads1118.readInternalTemp()); + + ads1118.current_config = ads1118.config_ADC_SS_TEMP; + ads1118.previous_config = ads1118.current_config; + //SERIAL_ECHOPGM("ADS1118 Tcold: "); + //SERIAL_ECHOLN(thck_0.getTcold()); + //ads1118.readConfig(); + #endif + // Init (and disable) SPI thermocouples #if TEMP_SENSOR_IS_ANY_MAX_TC(0) && PIN_EXISTS(TEMP_0_CS) OUT_WRITE(TEMP_0_CS_PIN, HIGH); @@ -3727,7 +3815,7 @@ void Temperature::disable_all_heaters() { return max_tc_temp; } -#endif // HAS_MAX_TC +#endif // TEMP_SENSOR_IS_MAX_TC(0) || TEMP_SENSOR_IS_MAX_TC(1) || TEMP_SENSOR_IS_MAX_TC(2) #if TEMP_SENSOR_IS_MAX_TC(BED) /** @@ -3847,6 +3935,106 @@ void Temperature::disable_all_heaters() { #endif // TEMP_SENSOR_IS_MAX_TC(BED) +#if HAS_ADS1118 + + /** + * @brief Read ADS Thermocouple temperature. + * + * Reads the thermocouple board via HW or SW SPI, using a library (LIB_USR_x) or raw SPI reads. + * Doesn't strictly return a temperature; returns an "ADC Value" (i.e. raw register content). + * Currently only supports channel 0 (single extruder) + * + * @param hindex the hotend we're referencing (different channel in ADS1118) + * @return integer representing the board's buffer, to be converted later if needed + */ + raw_adc_t Temperature::read_ads1118(const uint8_t hindex/*=0*/) { + #define ADS1118_HEAT_INTERVAL 250UL // 250 ms + + //static raw_adc_t ads1118_coldJ_temp_current[2] = { 0, 0 }; + //static raw_adc_t ads1118_hotJ_temp_current[2] = { 0, 0 }; + + static raw_adc_t ads1118_temp_previous[2] = { 0, 0 }; + static uint8_t ads1118_errors[2] = { 0, 0 }; + static millis_t next_ads1118_ms[2] = { 0, 0 }; + + static raw_adc_t ads_val = TEMP_SENSOR_0_ADS_TMAX; + + static uint8_t sampleCount; + + //static millis_t lastmillis; + + const millis_t ms = millis(); + //SERIAL_ECHOPGM("ADS1118 elapsed: "); SERIAL_ECHOLN(ms- lastmillis); + //lastmillis = ms; + if (PENDING(ms, next_ads1118_ms[hindex]) ) // || !ads1118.checkDataReady() + return ads1118_temp_previous[hindex]; // return cached value + + next_ads1118_ms[hindex] = ms + ADS1118_HEAT_INTERVAL; + + // To do: If there are more hotends enabled, cycle through different channels + int16_t raw; + + if (sampleCount < 1) { + ads1118.previous_config = ads1118.current_config; + ads1118.current_config = ads1118.config_ADC_SS_TEMP; + sampleCount++; + } + else if (hindex == 0) { + ads1118.previous_config = ads1118.current_config; + ads1118.current_config = ads1118.config_ADC_SS_CH0; + sampleCount = 0; + } + else if (hindex == 1) { + ads1118.previous_config = ads1118.current_config; + ads1118.current_config = ads1118.config_ADC_SS_CH1; + sampleCount = 0; + } + + raw = (int16_t) ads1118.readWriteData(ads1118.current_config); + + if (ads1118.previous_config == ads1118.config_ADC_SS_TEMP) { + //thck_0.setTcold(ads1118.convertInternalTemp(raw)); + thck_0.setRawCold(raw); + //SERIAL_ECHOPGM("Last read Raw cold: "); SERIAL_ECHOLN(raw); + //SERIAL_ECHOPGM("TCold "); SERIAL_ECHOLN(thck_0.getTcold()); + + } + else if (ads1118.previous_config == ads1118.config_ADC_SS_CH0) { + //ads1118_hotJ_temp_current[hindex] = raw; + //thck_0.setThot(thck_0.tempReadtoCelsius(raw)); + thck_0.setRawHot(raw); + //SERIAL_ECHOPGM("Last read Raw hot: "); SERIAL_ECHOLN(raw); + //SERIAL_ECHOPGM("ADS1118 THot "); SERIAL_ECHOLN(thck_0.getThot()); + } + + //SERIAL_ECHOPGM("ADS1118 State:Read "); SERIAL_ECHOLN(curr_state); SERIAL_ECHOPGM(":"); SERIAL_ECHOLN(raw); + + // Handle read error or disconnection : raw = 0x7FFF or 0x8000 (-32768) + if (raw == 0x7FFF || raw == -32768) { + ads1118_errors[hindex]++; + if (ads1118_errors[hindex] > 3) { + SERIAL_ERROR_START(); + SERIAL_ECHOLNPGM("ADS1118 Fault: Conversion error!"); + ads_val = (raw_adc_t)(TEMP_SENSOR_0_ADS_TMAX << 4); // force error + } + } + else if (raw < 32767){ + ads1118_errors[hindex] = 0; // reset errors if ok + ads_val = (raw_adc_t) (((int16_t)raw) + 32768); // raw shift to unsigned int; + } else { // if we add 32767 to raw it will overflow + ads1118_errors[hindex] = 0; // reset errors if ok + SERIAL_ECHOLNPGM("ADS1118 Warn: Cannot shift adc read from signed to unsigned!"); + ads_val = raw_adc_t(raw); // as is + } + //ads_val = raw_adc_t(3000); // raw; + + ads1118_temp_previous[hindex] = ads_val; // cache value + //SERIAL_ECHOPGM("ADS1118 ads_val: "); SERIAL_ECHOLN(ads_val); + return ads_val; // return the raw value, it will not be used directly for conversion but for errors, (raw values are stored in thermocouple class) + } + +#endif // HAS_ADS1118 + /** * Update raw temperatures * diff --git a/Marlin/src/module/temperature.h b/Marlin/src/module/temperature.h index f2ef4d0f6c..a122dbf2f0 100644 --- a/Marlin/src/module/temperature.h +++ b/Marlin/src/module/temperature.h @@ -1413,6 +1413,12 @@ class Temperature { static raw_adc_t read_max_tc_bed(); #endif + // ADS Thermocouples + #if HAS_ADS1118 + #define READ_ADS(N) read_ads1118(N) + static raw_adc_t read_ads1118(const uint8_t hindex=0); + #endif + #if HAS_AUTO_FAN #if ENABLED(POWER_OFF_WAIT_FOR_COOLDOWN) static bool autofans_on; diff --git a/buildroot/tests/BTT_GTR_V1_0 b/buildroot/tests/BTT_GTR_V1_0 index 1ed65dcc90..0d6321cf6f 100755 --- a/buildroot/tests/BTT_GTR_V1_0 +++ b/buildroot/tests/BTT_GTR_V1_0 @@ -8,7 +8,8 @@ set -e restore_configs opt_set MOTHERBOARD BOARD_BTT_GTR_V1_0 SERIAL_PORT -1 \ - EXTRUDERS 8 TEMP_SENSOR_1 1 TEMP_SENSOR_2 1 TEMP_SENSOR_3 1 TEMP_SENSOR_4 1 TEMP_SENSOR_5 1 TEMP_SENSOR_6 1 TEMP_SENSOR_7 1 + EXTRUDERS 8 TEMP_SENSOR_1 1 TEMP_SENSOR_2 1 TEMP_SENSOR_3 1 TEMP_SENSOR_4 1 TEMP_SENSOR_5 1 TEMP_SENSOR_6 1 TEMP_SENSOR_7 1 \ + TEMP_SENSOR_0 -18 TEMP_0_MOSI_PIN PG15 # Not necessary to enable auto-fan for all extruders to hit problematic code paths opt_set E0_AUTO_FAN_PIN PC10 E1_AUTO_FAN_PIN PC11 E2_AUTO_FAN_PIN PC12 NEOPIXEL_PIN PF13 \ X_DRIVER_TYPE TMC2208 Y_DRIVER_TYPE TMC2130 \ diff --git a/ini/features.ini b/ini/features.ini index 03a9bdbba8..722ad769ee 100644 --- a/ini/features.ini +++ b/ini/features.ini @@ -32,6 +32,7 @@ HAS_MOTOR_CURRENT_I2C = SlowSoftI2CMaster build_src_filter=+ LIB_MAX31855 = GadgetAngel MAX31855=https://github.com/GadgetAngel/Adafruit-MAX31855-V1.0.3-Mod-M/archive/dc9cc10ac2.zip LIB_INTERNAL_MAX31865 = build_src_filter=+ +HAS_ADS1118 = build_src_filter=+ NEOPIXEL_LED = adafruit/Adafruit NeoPixel@~1.12.3 build_src_filter=+ I2C_AMMETER = peterus/INA226Lib@1.1.2 diff --git a/platformio.ini b/platformio.ini index 9f562c0be9..61f2d92503 100644 --- a/platformio.ini +++ b/platformio.ini @@ -85,6 +85,7 @@ default_src_filter = + - - - - ; Library Code + - - - - -