Посоветуйте датчик температуры

Ошибка, о которой говорю - заключается в том, что с одного (или с нескольких) датчика поступает некорректное значение на микроконтроллер. Некорректное значение - может быть как “вылетевшее” (большая ступенька) так и постоянное значение (замирание на одном уровне), продолжающее поступать в течение долгого времени.

Останов - происходит программно, как реакция на неадекватные данные с датчика температуры. Останов - имеется в виду отключение всех исполнительных механизмов.

МК - не зависает, не перезагружается. Продолжает транслировать некорректные значения с датчика (датчиков).

Отрабатывает - согласно кода. Проблема - что приходят некорректные значения температуры.

Рандомно - нельзя. Существует вероятность потерять оборудование (разморозка конденсатора - разрыв льдом пластинчатого теплообменника и выход фреона). Контроль - по значениям температуры.

Во времена расцвета “самоплясных самоваров” была такая проблема, что несколько датчиков 18в20 + LCD I2C никогда не могли сосуществовать вместе. Даже 100 часов “прогона” не выдерживали. Лично у меня НЕ получилось победить эту проблему на ардуине.

Проблема решилась гораздо проще: на гите было “подсмотрено” несколько библиотек для работы с I2C и 18в20 и они были полностью переписаны на “голом” ASMe. У 1-wire очень жесткие ТАЙМИНГИ и если в процессе обмена (к примеру) прилетит какой-нить millis() = хана. После этого было “замучено” несколько СОТЕН датчиков с али и все было ровно. Специально написанный софт выдерживал месячный прогон с “0” ошибок. Посему мой вердикт именно такой: - для таких “поделок” нужно еще и код писать “правильный” уметь.

NMi, спасибо. Признаться, мало что понял из сообщения - но основная мысль, что дисплей с I2C (мой случай, как понимаю) с датчиками DS18B20, по 1-wire, без глубокого передела библиотеки (не в моих силах) - заставить работать без ошибок не получится…

Но тогда - какой может быть совет для моего случая? (дисплей - нужен, значения температур (корректные) - нужны)

Опрашивать не чаще раз в секунду и игнорировать(правильно обрабатывать) сигналы отвала/ещё чего-то датчика. Это в общем случае :face_without_mouth:

Все получится. На самоплясных форумах КУЧА готового кода есть. Я этим занимался более 15-ти лет назад!

18в20 это классная штука - если оригинал!!! Но где его купить? ПоЭтому делалось все очень просто - датчики калибровались в нужных диапазонах обычным высокоточным градусником. И все!

Я делал проще: = никогда не ждал енти 760мс и тд. Таймер на 0.25с. Даем команду на измерение температуры. Ничего не ждем. Занимаемся выводом на LCD и прочей математикой. Прилетает 4 тика = 1 сек. Опрашиваем датчик(и). Ждем 1 тик = 0.25с и снова команда измерения. Примерно как-то так.

Я не знаю, как корректно это называется… Но: я посылаю запрос на подготовку датчиков к выдаче информации. Проходит 2 сек. Посылаю адресно запрос на получение значения. Проходит ещё 8 сек. и снова посылаю запрос на подготовку датчиков к выдаче информации. Такой цикл. Как понимаю - опрашиваю не чаще раз в секунду.

Это - форумы про самогонные аппараты?

Не очень понимаю, что написано…

Но есть свои наводки на мысли. Ранее (сейчас действует) сделал автоматику системы отопления - работает несколько лет, также получаю значение температуры с 4-ёх датчиков, с периодичностью 10 сек. Проблем там нет (прилетают, бывает, некорректные значения - но они единичные и успешно отфильтровываются программой). Так вот - основное отличие в том, что там всё сделано на delay.

В этом случае - делать на delay не стал в первую очередь из-за того, что принимаю одновременно сигналы с импульсных выходов. Ну и во вторую очередь - неоднократно встречал, что правильнее делать - без delay/

Никто не мешает написать такую функцию, при которой будет срабатывать все указанное. Притом, срабатывать не когда повезет, а в заранее запланированное время. Что для отладки всяко полезнее.

Так Вы на живой установке отлаживаете?
Так нельзя - нужно на макете, который имитирует все возможные состояния.

У меня не укладывается в голове (не придумывается, как можно написать функцию, запускающую и останавливающую компрессор именно тогда, когда значения температур в определённых точках достигнут определённых значений, не имея этих самых значений). Если бы я смог написать функцию управления агрегатом, позволяющую агрегату работать - но при этом не иметь значений температур (не зависеть от значений температур), то - мне вообще бы не нужны были эти датчики температуры.

Но я подумаю, о чём идёт речь “написать функцию работы агрегата без температур”. Может что-то пойму со временем. О чём речь

На живой. Иначе - у меня точно не хватит терпения. А так, на живой - “щёкотно”, и вроде, интересно было. Не знаю, хватит ли терпения довести до конца. До надёжно работающего состояния. Или поломаю раньше.

Я думал - тут мне однозначно что-то кардинально посоветуют - типа, перейти на NTC датчики. Но в то же время - есть у меня подозрение, что и работу уже прилепленных DS18B20 - возможно, можно как-то организовать. Условия, конечно, в наружном блоке экстремальные. Там - то льдом обрастёт, то водой зальёт (наверное), то горяченное. Я без гильзы защитной их использовал (прикрепил). Но старался термоусадкой с клеевой подложкой - максимально закрыть ножки. Может в этом быть проблема? Или же - они и в такой обстановке должны работать?

Сегодня - увеличил допустимый скачёк температуры на том датчике, что прошлой ночью выдал ошибку (может, он перескочил прошлой ночью предыдущую ступеньку в 30 град. С за 10 сек. Поставил ему теперь 40 град. С за 10 сек. максимум. Понаблюдаю, что произойдёт к утру. Пока что - пашет)

если совсем идей не будет…

(ds.read() | ds.read() << 8) с последующим temp * 10 >> 4 крайне уязвим к “мусору” на линии. Если датчик временно превратился в “глюк” или из-за помехи был прочитан неверный байт, вы получаете а что вы получите тогда ?

а если кодом… то гдее библиотека #include <DallasTemperature.h> или ее просто так написали, и она точно не нужна ?
если убрали CRC, то может надо вернуть и показать код ?

или все таки не стоит пытаться кодом решить проблему ?)))

посоветовать на вашей плате под нагрузкой, в ручном режиме включить 4 реле наверное бредово… но может прояснит ситуацию)))
но а вообще надо собирать все и проверять лично)))

Встречал эти датчики на довольно “ответственных” местах, вроде норм работают.

Проследите в коде, чтобы работа с шиной 1-Wire по времени была отделена от силовых переключений на достаточное время(десяток-другой миллисекунд)

Попробуйте полностью запрещать все прерывания, когда идёт работа с шиной 1 - Wire.
В самой библиотеке прерывания запрещаются, но не сплошь, а время от времени.

если я не ошибаюсь, то

(ds.read() | ds.read() << 8)

встречалось практически во всех вариантах получения температуры с датчиков DS18B20 при использовании библиотеки “OneWire”

А что, вот это вот

temp * 10 >> 4

повышает вероятность поступления некорректного значения? А какие есть ещё варианты. Мне хочется - получить значение температуры в виде uint16_t, без плавающей запятой, но содержащее значение до десятых градуса (типа 156 - это 15,6 градуса).

Я подумал - раз код работает без ссылки на эту библиотеку - ну, значит, она и не нужна… достаточно “OneWire”. Где-то встречал на форуме обсуждения - и там высказались, что “OneWire” лучше чем “DallasTemperature”. Я так понял - можно обойтись каким-то одним из этих двух вариантов

Код который у меня работал и содержал проверку CRC:

Спойлер
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;

#include <OneWire.h>
OneWire  ds(21);  // выбор пина ардуино, на котором висит шина 1-wire

#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0
#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin

// Assign human-readable names to some common 16-bit color values:
#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
#define WHITEGREY  0xC638
#define DARKGREY  0x7BEF
#define DARKGREEN 0x0C80


#define pinVoda_tick        18 //счетчик воды
boolean stateCheck_voda;

#define pinElectr_tick        19 //счетчик электричества
boolean stateCheck_electr;


boolean n = 0;//для получения температуры
//адреса для датчиков температуры
//0xDB00000EB43A8C28, {0x28, 0x8C, 0x3A, 0xB4, 0xE, 0x0, 0x0, 0xDB} //1 (оригинал)
//0x3A00000076A76F28, {0x28, 0x6F, 0xA7, 0x76, 0x0, 0x0, 0x0, 0x3A} //2
//0xAB00000075289528, {0x28, 0x95, 0x28, 0x75, 0x0, 0x0, 0x0, 0xAB} //3
//0x2900000072CDFF28, {0x28, 0xFF, 0xCD, 0x72, 0x0, 0x0, 0x0, 0x29} //4
//0xF9000000739C4F28, {0x28, 0x4F, 0x9C, 0x73, 0x0, 0x0, 0x0, 0xF9} //5
//0xAC00000072FB2C28, {0x28, 0x2C, 0xFB, 0x72, 0x0, 0x0, 0x0, 0xAC} //6
//0x2D00000072D1FC28, {0x28, 0xFC, 0xD1, 0x72, 0x0, 0x0, 0x0, 0x2D} //7
//0x760000007697D528, {0x28, 0xD5, 0x97, 0x76, 0x0, 0x0, 0x0, 0x76} //8
//0x5F00000072E5EA28, {0x28, 0xEA, 0xE5, 0x72, 0x0, 0x0, 0x0, 0x5F} //9
//0x58000000768CEF28, {0x28, 0xEF, 0x8C, 0x76, 0x0, 0x0, 0x0, 0x58} //10
//0xC400000077899C28, {0x28, 0x9C, 0x89, 0x77, 0x0, 0x0, 0x0, 0xC4} //11


//массивы для графиков
uint8_t tIsparitel[320];
uint8_t tOverheat[320];
uint8_t tKompressor[320];
uint8_t tWaterIn[320];
uint8_t tWaterOut[320];
uint8_t tDefrost[320];
uint8_t heatPower[320];
uint8_t gWater[320];
uint8_t ElectrPow[320];
uint8_t COP[320];


//для отображения точных значений с датчиков
int16_t tOverheatReal;
int32_t gWaterReal;
int32_t heatPowerReal;
int32_t ElectrPowReal;
int32_t COPreal;


int32_t gWaterRealPrew;//для фиксирования предыдущего показания для отображения при сбоях
boolean FlagWater = false;//флаг для фиксирования сбоев показаний водомера

//для подсчёта тепла и электричества
int32_t countHeatAndElectr = 0;
int32_t countHeat = 0;
int32_t countElectr = 0;

uint32_t myTimer1; //для таймера цикла
boolean Flag1 = true;
uint8_t countGetGrafic = 34;


uint8_t tKompressorCounter = 0;//для подсчёта количества корректных замеров перед отключением и включением вентилятора
int16_t trend_fan[20]; //массив для отображения графика работы вентилятора
int16_t trend_kompressor[20]; //массив для отображения графика работы компрессора
int16_t trend_pump[20]; //массив для отображения графика работы насоса
int16_t trend_4way_valve[20]; //массив для отображения графика работы четырёхходового клапана

//для водомера
uint32_t tmr;
boolean gWaterFlag = false;   //флаг для водомера при прерывании
uint32_t WaterStartTimer; //засечка времени старта отсчёта времени для водомера
uint32_t WaterCountTimer; //сколько времени прошло между срабатываниями

//для электросчётчика
uint32_t tmr2;
boolean ElectrFlag = false;   //флаг для электросчётчика при прерывании
uint32_t ElectrStartTimer; //засечка времени старта отсчёта времени для электросчётчика
uint32_t ElectrCountTimer; //сколько времени прошло между срабатываниями
uint32_t HeatOptionPauseStartTime;//для отсчёта времени и прокрутки насоса в режиме паузы прогрева

//для контроля режимов
uint32_t StartHeatTime;//для фиксации времени начала режима работы "нагрев"
uint32_t StopTime;//для фиксации времени останова
boolean StartHeatFlag = false;//флаг для активации и окончания перехода в режим "нагрев"
boolean StopFlag = false;//флаг для перехода в режим "останов"
boolean HeatOption = false;//флаг о том что в работе режим "нагрев"
boolean HeatOptionPause = false;//режим приостанова нагрева
uint8_t HeatOptionCout = 0; //счётчик для смены режима на приостанов нагрева
uint8_t HeatOptionPauseCout = 0; //счётчик для смены режима окончания приостанова нагрева
uint8_t defrostCount = 0; //счётчик для перехода в режим Defrost
boolean DefrostOption = false; //флаг о начале работы режима Defrost
uint32_t DefrostTime;//для фиксации времени начала режима работы Defrost


// ниже перечисление температур
typedef enum Temper_enum
{
  KompressorC,            //0
  WaterInC,               //1
  WaterOutC,              //2
  temp3C,                 //3
  DefrostC,               //4
  IsparitelC,             //5

  size_array_Temp     //6 size

} Temper_ENUM;

// ниже сами переменные температур, изначально ставим 0*С
int16_t  Temper[size_array_Temp] =
{
  0,     //KompressorC   0
  0,     //WaterInC      1
  0,     //WaterOutC     2
  0,     //temp3C        3
  0,     //DefrostC      4
  0      //IsparitelC    5
};
// например, чтобы использовать переменную температуры на улице, пишем так: Temper[UlicaC] =

// ниже соответствие адресов датчиков определённым температурам
byte ADDR_DS18B20 [size_array_Temp][8] =
{
  {0x28, 0xD5, 0x97, 0x76, 0x0, 0x0, 0x0, 0x76}, //KompressorC   0
  {0x28, 0x4F, 0x9C, 0x73, 0x0, 0x0, 0x0, 0xF9}, //WaterInC      1
  {0x28, 0xFC, 0xD1, 0x72, 0x0, 0x0, 0x0, 0x2D}, //WaterOutC     2
  {0x28, 0x95, 0x28, 0x75, 0x0, 0x0, 0x0, 0xAB}, //temp3C        3
  {0x28, 0xEF, 0x8C, 0x76, 0x0, 0x0, 0x0, 0x58}, //DefrostC      4
  {0x28, 0x8C, 0x3A, 0xB4, 0xE, 0x0, 0x0, 0xDB}  //IsparitelC    5
};


//расход воды без прерывания
void voda_tick() {
  //Serial.println("proverka");
  boolean current_status = digitalRead(pinVoda_tick);
  if (stateCheck_voda and !current_status) {
    tmr = millis(); //зафиксировали время срабатывания прерывания
    gWaterFlag = true;
    stateCheck_voda = current_status;
  }
  if (!stateCheck_voda and current_status)stateCheck_voda = current_status;
  if (current_status)gWaterFlag = false;
  if (!current_status and millis() - tmr >= 1000 and gWaterFlag == true)
  {
    WaterCountTimer = millis() - WaterStartTimer;
    WaterStartTimer = millis();
    gWaterFlag = false;
  }
}

//электрическая мощность без прерывания
void electr_tick() {
  //Serial.println("proverka");
  boolean current_status = digitalRead(pinElectr_tick);
  if (stateCheck_electr and !current_status) {
    tmr2 = millis(); //зафиксировали время срабатывания прерывания
    ElectrFlag = true;
    stateCheck_electr = current_status;
  }
  if (!stateCheck_electr and current_status)stateCheck_electr = current_status;
  if (current_status)ElectrFlag = false;
  if (!current_status and millis() - tmr2 >= 50 and ElectrFlag == true)
  {
    ElectrCountTimer = millis() - ElectrStartTimer;
    ElectrStartTimer = millis();
    ElectrFlag = false;
  }
}


void getDataInt100(int16_t a)//вывод значений на дисплей у которых делится на 100
{
  //Serial.println("proverka");
  if (a >= -9 and a < 0)
  {
    tft.print("-0.0");
    tft.print(abs(a));
  }
  else if (a<9 and a >= 0)
  {
    tft.print("0.0");
    tft.print(a);
  }
  else if (a <= -10 and a > -100)
  {
    tft.print("-0.");
    tft.print(abs(a % 100));
  }
  else
  {
    tft.print(a / 100);
    tft.print(".");
    if (abs(a % 100) < 10)
      tft.print("0");
    tft.print(abs(a % 100));
  }
}


void getDataInt10(int16_t a)//вывод значений на дисплей у которых делится на 10
{
  //Serial.println("proverka");
  if (a >= -9 and a < 0)
  {
    tft.print("-0.");
    tft.print(abs(a));
  }
  else if (a<9 and a >= 0)
  {
    tft.print("0.");
    tft.print(a);
  }
  else
  {
    tft.print(a / 10);
    tft.print(".");
    tft.print(abs(a % 10));
  }
}


int16_t getDataForUintMassiv(int16_t a)//границы для последнего значения в массиве для графиков, типа uint8_t
{
  //Serial.println("proverka");
  int16_t LastInMassive;//ввожу временную переменную для вычисления последнего значения массива для графиков
  if (a < -400)
    LastInMassive = 0;
  else if (a >= -400 and a < 500)
    LastInMassive = round ((a + 400) / 5);
  else if (a >= 500 and a < 1250)
    LastInMassive = round (180 + (a - 500) / 10);
  else if (a >= 1250)
    LastInMassive = 255;
  return LastInMassive;
}


int32_t getDataForUintMassiv32(int32_t a)//границы для последнего значения в массиве для графиков, типа uint8_t
{
  //Serial.println("proverka");
  int32_t LastInMassive;//ввожу временную переменную для вычисления последнего значения массива для графиков
  if (a < -400)
    LastInMassive = 0;
  else if (a >= -400 and a < 0)
    LastInMassive = round ((a + 400) / 10);
  else if (a >= 0 and a < 430)
    LastInMassive = round (40 + a / 2);
  else if (a >= 430)
    LastInMassive = 255;
  return LastInMassive;
}


void getGraphics(uint16_t c, uint16_t d, uint8_t g[320]) //процедура построения графиков
{
  //Serial.print("proverka");
  int16_t oldX = 0;
  int16_t oldY = g[0];
  for (int16_t x = 1; x < 320; x++)
  {
    int16_t nxt_x = x;
    tft.drawLine(oldX, (c - oldY), nxt_x, (c - g[x]), d);
    oldY = g[x];
    oldX = nxt_x;
  }
}


void getMainDisplay()//вычерчивание основного дисплея
{
  Serial.println("proverka");
  tft.fillScreen(BLACK);

  //сетка по времени:
  for (uint8_t i = 0; i < 13; i++)
  {
    tft.drawFastVLine((9 + 24 * i), 20, 190, DARKGREY);
    tft.drawFastVLine((9 + 24 * i), 230, 210, DARKGREY);
  }

  //сетка по шкале температур:
  for (uint8_t i = 0; i < 11; i++)
  {
    if (i == 4 or i == 5 or i == 6 or i == 7 or i == 9 or i == 10)
      tft.drawFastHLine(0, (5 + 20 * i), 320, DARKGREEN);
    else if (i == 1 or i == 2 or i == 3 or i == 8)
      tft.drawFastHLine(0, (5 + 20 * i), 320, BLUE);
  }

  for (uint8_t i = 0; i < 11; i++)
    tft.drawFastHLine(0, (235 + 20 * i), 320, BLUE);


  //подписи по вертикали на графиках
  tft.setCursor(0, 22); // Устанавливаем курсор (X = , Y = )
  tft.setTextSize(1); // Указываем размер символов в строке от 1 до 3
  tft.setTextColor(WHITE, BLACK); // Указываем цвет текста
  tft.print("90");

  tft.setCursor(0, 42); // Устанавливаем курсор (X = , Y = )
  tft.print("70");

  tft.setCursor(0, 62); // Устанавливаем курсор (X = , Y = )
  tft.print("50");

  tft.setCursor(0, 82); // Устанавливаем курсор (X = , Y = )
  tft.print("40");

  tft.setCursor(0, 102); // Устанавливаем курсор (X = , Y = )
  tft.print("30");

  tft.setCursor(0, 122); // Устанавливаем курсор (X = , Y = )
  tft.print("20");

  tft.setCursor(0, 142); // Устанавливаем курсор (X = , Y = )
  tft.print("10");

  tft.setCursor(0, 162); // Устанавливаем курсор (X = , Y = )
  tft.print("0");

  tft.setCursor(0, 182); // Устанавливаем курсор (X = , Y = )
  tft.print("-10");

  tft.setCursor(0, 202); // Устанавливаем курсор (X = , Y = )
  tft.print("-20");


  tft.setCursor(0, 232); // Устанавливаем курсор (X = , Y = )
  tft.print("4");

  tft.setCursor(0, 252); // Устанавливаем курсор (X = , Y = )
  tft.print("3.6");

  tft.setCursor(0, 272); // Устанавливаем курсор (X = , Y = )
  tft.print("3.2");

  tft.setCursor(0, 292); // Устанавливаем курсор (X = , Y = )
  tft.print("2.8");

  tft.setCursor(0, 312); // Устанавливаем курсор (X = , Y = )
  tft.print("2.4");

  tft.setCursor(0, 332); // Устанавливаем курсор (X = , Y = )
  tft.print("2");

  tft.setCursor(0, 352); // Устанавливаем курсор (X = , Y = )
  tft.print("1.6");

  tft.setCursor(0, 372); // Устанавливаем курсор (X = , Y = )
  tft.print("1.2");

  tft.setCursor(0, 392); // Устанавливаем курсор (X = , Y = )
  tft.print("0.8");

  tft.setCursor(0, 412); // Устанавливаем курсор (X = , Y = )
  tft.print("0.4");

  tft.setCursor(0, 432); // Устанавливаем курсор (X = , Y = )
  tft.print("0");

  //надписи назначения измерений датчиков
  tft.setCursor(0, 0); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(RED); // Указываем цвет текста
  tft.print("Kompressor:");

  tft.setCursor(10, 10); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(MAGENTA); // Указываем цвет текста
  tft.print("Overheat:");

  tft.setCursor(125, 10); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(CYAN); // Указываем цвет текста
  tft.print("Defrost:");

  tft.setCursor(220, 10); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(RED); // Указываем цвет текста
  tft.print("Isparitel:");

  tft.setCursor(120, 0); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(YELLOW); // Указываем цвет текста
  tft.print("Water in:");

  tft.setCursor(220, 0); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(GREEN); // Указываем цвет текста
  tft.print("Water out:");

  tft.setCursor(5, 210); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(RED); // Указываем цвет текста
  tft.print("Heat power:");

  tft.setCursor(152, 210); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(YELLOW); // Указываем цвет текста
  tft.print("COP:");

  tft.setCursor(218, 210); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(GREEN); // Указываем цвет текста
  tft.print("Electr pow:");

  tft.setCursor(22, 220); // Устанавливаем курсор (X = , Y = )
  tft.setTextColor(MAGENTA); // Указываем цвет текста
  tft.print("G water:");
}



void setup()
{ // put your setup code here, to run once:
  //Serial.println("proverka");
  Serial.begin(9600);

  tft.reset();
  tft.begin(38022); //указание модели экрана
  tft.invertDisplay(true); //инверсия цветов - т.к. по умолчанию инвертирует цвета
  //tft.setRotation(1); //поворот экрана на 90 градусов

  //для водомера
  pinMode(18, INPUT_PULLUP);// подключили импульсный выход на D18 и +5
  stateCheck_voda = digitalRead(pinVoda_tick);

  //для электросчётчика
  pinMode(19, INPUT_PULLUP);// подключили импульсный выход на D19 и +5
  stateCheck_electr = digitalRead(pinElectr_tick);

  //присвоение массивам значений, чтобы на графике сначала были нули
  for (int i = 0; i < 320; i++) {
    tIsparitel[i] = 80;
    tOverheat[i] = 80;
    tKompressor[i] = 80;
    tWaterIn[i] = 80;
    tWaterOut[i] = 80;
    tDefrost[i] = 80;
    heatPower[i] = 40;
    gWater[i] = 40;
    ElectrPow[i] = 40;
    COP[i] = 40;
  }

  //для управления реле
  pinMode(48, OUTPUT);//пин реле вентилятора
  digitalWrite(48, HIGH);//управление вентилятором

  pinMode(49, OUTPUT);//пин реле четырёхходового клапана
  digitalWrite(49, HIGH);//управление четырёхходовым клапаном

  pinMode(50, OUTPUT);//пин реле компрессора
  digitalWrite(50, HIGH);//управление компрессором

  pinMode(51, OUTPUT);//пин реле насоса
  digitalWrite(51, HIGH);//управление насосом

  //инициирую режим "нагрев" при старте микроконтроллера
  StartHeatTime = millis();//фиксация времени начала перехода в режим "нагрев"
  StartHeatFlag = true;//поднят флаг о том, что начался переход в режим "нагрев"


  delay(1000);  //задержка 1 сек
  myTimer1=millis();
}


void loop()// put your main code here, to run repeatedly:
{
  voda_tick();//переход на процедуру по подсчёту расхода воды
  electr_tick();//переход на процедуру по подсчёту электрической мощности


  //код управления режимами
  if (StartHeatFlag == true and  millis() - StartHeatTime >= 20000 and Temper[WaterInC] >= 50)//если начат переход в режим "нагрев" и при этом прошло время после старта перехода и температура воды перед ППТО более 5 град.
    digitalWrite(51, LOW);//включить насос

  if (StartHeatFlag == true and  millis() - StartHeatTime >= 30000 and Temper[KompressorC] <= 850)//если начат переход в режим "нагрев" и при этом прошло время после старта перехода и температура компрес. менее 85 град.
    digitalWrite(48, LOW);//включить вентилятор

  if (StartHeatFlag == true and  millis() - StartHeatTime >= 40000)//если начат переход в режим "нагрев" и при этом прошло время после старта перехода
    digitalWrite(49, LOW);//переключить четырёхходовой клапан в режим "нагрев"

  if (StartHeatFlag == true and  millis() - StartHeatTime >= 45000 and digitalRead(51) == LOW and digitalRead(49) == LOW)//если начат переход в режим "нагрев" и при этом прошло время после старта перехода и включён насос и четырёхходовой клапан в режим "нагрев"
    digitalWrite(50, LOW);//включить компрессор


  if (StartHeatFlag == true and digitalRead(51) == LOW and digitalRead(48) == LOW and digitalRead(50) == LOW and digitalRead(49) == LOW)//если начат переход в режим "нагрев" и все требуемые для этого устройства включены
  {
    StartHeatFlag = false;//окончен переход в режим "нагрев"
    HeatOption = true;//действует режим "нагрев"
  }

  if (HeatOption == true and (digitalRead(49) == HIGH or Temper[DefrostC] <= 50 ))
  {
    digitalWrite(50, HIGH);//остановить компрессор
    HeatOption = false;//отключить режим "обогрев"
    StopFlag = true;//переход в режим останов
    StopTime = millis();
  }

  if (StopFlag == true)
  {
    digitalWrite(50, HIGH);//остановить компрессор
    digitalWrite(48, HIGH);//остановить вентилятор
    if (millis() - StopTime >= 120000)
      digitalWrite(49, HIGH);//перевести четырёхходовой клапан в штатное положение (режим "охлаждение")
  }

  if (DefrostOption == true)
  {
    digitalWrite(51, LOW);//включить насос
    digitalWrite(50, HIGH);//остановить компрессор
    digitalWrite(48, HIGH);//остановить вентилятор
    if (millis() - DefrostTime >= 120000)
      digitalWrite(49, HIGH);//перевести четырёхходовой клапан в штатное положение (режим "охлаждение")
    if (millis() - DefrostTime >= 1200000 and Temper[IsparitelC] >= 20)
    {
      DefrostOption = false;
      StartHeatTime = millis();
      StartHeatFlag = true;
    }
  }






  //первый шаг выполнения по таймеру
  if (millis() - myTimer1 >= 2000 and Flag1 == true  and countGetGrafic < 36)
  {
    //****************************измерение температуры***************************************
    // флаг работы: запрос температуры или её чтение
    n = !n;
    if (n) {
      ds.reset();  // сброс шины
      ds.write(0xCC);//обращение ко всем датчикам
      ds.write(0x44);// начать преобразование (без паразитного питания)
    }
    else   {
      for (uint8_t i = 0; i < size_array_Temp; i++) { // цикл фор перебирает все датчики t
        int16_t Temper_ = 200;
        uint8_t buff[9];

        ds.reset();
        ds.select(ADDR_DS18B20[i]);    //выбор адреса DS18B20
        ds.write(0xBE); // Read Scratchpad (чтение регистров)
        for (uint8_t j = 0; j < 9; j++) buff[j] = ds.read(); //читаем все 9 байт от датчика
        ds.reset();
        if (OneWire::crc8(buff, 8) == buff[8]) { // если CRC верна
          Temper_ =  buff[0] | (buff[1] << 8); // читаем температуру из первых двух байт (остальные были нужны для проверки CRC)
          Temper_ = (Temper_ * 10) >> 4; //целое в десятых *C (214=>21,4*C)
          if  (Temper_ < 1500 && Temper_ > -500 && Temper_ != 850 && Temper_ != -1270) // ещё раз перестраховываемся от дерьмовых значений
          {
            Temper[i] =  (int16_t) Temper_;// всё, тут уже пишем температуру в переменную
          }
        }
      }
    }
    //****************************измерение температуры***************************************


    //вычисляю перегрев
    tOverheatReal = (Temper[temp3C] - Temper[IsparitelC]);

    //расчёт расхода воды
    if (millis() - WaterStartTimer > 300000 or digitalRead(51) == HIGH) //если в течение 5 мин не пришёл сигнал с водомера либо выключен насос
    {
      gWaterReal = 0;
      //Serial.println(millis()-WaterStartTimer);
      FlagWater = false;//сброшен флаг подсветки значения
    }
    else if (3600000 / WaterCountTimer > 200)//если пришло нереально большое значение
    {
      gWaterReal = gWaterRealPrew;
      FlagWater = true;//поднят флаг для подсветки значения расхода
    }
    else
    {
      //Serial.println("vhod");
      gWaterReal = (3600000 / WaterCountTimer); //итоговое значение в м3/ч умноженные на 100
      gWaterRealPrew = gWaterReal;
      FlagWater = false;//сброшен флаг подсветки значения
      //Serial.println(FlagWater);
    }


    //расчёт электрической мощности
    if (millis() - ElectrStartTimer > 60000) //если нет данных с счётчика в течение 1 мин.
      ElectrPowReal = 0;
    else
      ElectrPowReal = 72000 / ElectrCountTimer; //значение в кВт умноженные на 100
    //Serial.println(ElectrCountTimer);
    //Serial.println(ElectrPowReal);


    heatPowerReal = ((int32_t)Temper[WaterOutC] - (int32_t)Temper[WaterInC]) * gWaterReal * 1163 / 10000; //тепловая мощность на теплообменнике в кВт умноженная на 100
    //Serial.println(tWaterOutReal);
    //Serial.println(tWaterInReal);
    //Serial.println(gWaterReal);
    //Serial.println(heatPowerReal);


    if (ElectrPowReal < 10)
      COPreal = 0;
    else
      COPreal = 100 * heatPowerReal / ElectrPowReal; //вычисление COP, умноженное на 100
    //Serial.println(heatPowerReal);
    //Serial.println(ElectrPowReal);
    //Serial.println(COPreal);


    //подсчёт средней тепловой мощности и электроэнергии
    countHeat = countHeat + heatPowerReal;
    countElectr = countElectr + ElectrPowReal;
    countHeatAndElectr++;


    //останов вентилятора при перегреве компрессора
    if (HeatOption == true and Temper[KompressorC] >= 850 and tOverheatReal >= 300 and digitalRead(48) == LOW)//если высокая температура на выходе из компрессора и перегрев с запасом и вентилятор включён
    {
      tKompressorCounter++;
      if (tKompressorCounter >= 5)//если сработал счётчик
      {
        digitalWrite(48, HIGH);//отключить вентилятор
        tKompressorCounter = 0;
      }
    }
    else if (HeatOption == true and (Temper[KompressorC] <= 750 or tOverheatReal <= 200) and digitalRead(48) == HIGH)//если остыл компрессор или перегрев мал и вентилятор выключен
    {
      tKompressorCounter++;
      if (tKompressorCounter >= 3)//если сработал счётчик
      {
        digitalWrite(48, LOW);//включить вентилятор
        tKompressorCounter = 0;
      }
    }
    else
      tKompressorCounter = 0;


    //смена режимов
    if (HeatOption == true and Temper[WaterInC] >= 360)//если температура воды перед теплообменником достигла 36 град.
    {
      HeatOptionCout++;
      if (HeatOptionCout >= 10)//если устоялся режим
      {
        HeatOption = false;
        HeatOptionPause = true;
        HeatOptionCout = 0;
        HeatOptionPauseStartTime = millis();
        digitalWrite(50, HIGH);//остановить компрессор
        digitalWrite(48, HIGH);//остановить вентилятор
      }
    }
    else
      HeatOptionCout = 0;

    if (HeatOptionPause == true and digitalRead(49) == LOW and millis() - HeatOptionPauseStartTime >= 120000) //если началась пауза обогрева и 4х ход. клапан включён и вышло время
      digitalWrite(49, HIGH);//перевести четырёхходовой клапан в штатное положение (режим "охлаждение")

    if (HeatOptionPause == true and digitalRead(51) == HIGH and millis() - HeatOptionPauseStartTime >= 960000) //если прошло время 15 мин. после остановки насоса
    {
      digitalWrite(51, LOW);//включить насос
      HeatOptionPauseStartTime = millis();
    }

    if (HeatOptionPause == true and digitalRead(51) == LOW and millis() - HeatOptionPauseStartTime >= 60000) //если прошло время 1 мин. после включения насоса
    {
      digitalWrite(51, HIGH);//выключить насос
      HeatOptionPauseStartTime = millis();
    }

    if (HeatOptionPause == true and Temper[WaterInC] <= 280)//если в режиме паузы обогрева температура воды перед теплообменником низкая
    {
      HeatOptionPauseCout++;
      if (HeatOptionPauseCout >= 10)//если устоялся режим
      {
        HeatOptionPause = false;
        HeatOptionPauseCout = 0;
        StartHeatTime = millis();//фиксация времени начала перехода в режим "нагрев"
        StartHeatFlag = true;//поднят флаг о том, что начался переход в режим "нагрев"
      }
    }
    else
      HeatOptionPauseCout = 0;


    //режим разморозки
    if (HeatOption == true and Temper[IsparitelC] < -100)
      defrostCount++;
    else
      defrostCount = 0;

    if (HeatOption == true and Temper[IsparitelC] < -100 and defrostCount >= 10)
    {
      HeatOption = false;
      DefrostOption = true;
      DefrostTime = millis();
    }



    Flag1 = false;
  }


  //второй шаг выполнения по таймеру
  if (millis() - myTimer1 >= 5000)
  {
    //вычерчивание чёрных прямоугольников для закрашивания предыдущих значений с датчиков
    tft.fillRect(74, 0, 30, 20, BLACK);
    tft.fillRect(181, 0, 30, 20, BLACK);
    tft.fillRect(289, 0, 30, 20, BLACK);

    tft.fillRect(74, 210, 30, 20, BLACK);
    tft.fillRect(181, 210, 30, 10, BLACK);
    tft.fillRect(289, 210, 30, 10, BLACK);
    tft.fillRect(152, 220, 60, 10, BLACK);
    tft.fillRect(100, 470, 30, 8, BLACK);
    tft.fillRect(200, 470, 30, 8, BLACK);


    //отображение точных значений с датчиков
    tft.setCursor(74, 0); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(RED); // Указываем цвет текста
    getDataInt10(Temper[KompressorC]);

    tft.setCursor(181, 0); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(YELLOW); // Указываем цвет текста
    getDataInt10(Temper[WaterInC]);

    tft.setCursor(289, 0); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(GREEN); // Указываем цвет текста
    getDataInt10(Temper[WaterOutC]);

    tft.setCursor(74, 10); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(MAGENTA); // Указываем цвет текста
    getDataInt10(tOverheatReal);

    tft.setCursor(181, 10); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(CYAN); // Указываем цвет текста
    getDataInt10(Temper[DefrostC]);

    tft.setCursor(289, 10); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(RED); // Указываем цвет текста
    getDataInt10(Temper[IsparitelC]);

    tft.setCursor(74, 210); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(RED); // Указываем цвет текста
    getDataInt100(heatPowerReal);

    tft.setCursor(181, 210); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(YELLOW); // Указываем цвет текста
    getDataInt100(COPreal);

    tft.setCursor(289, 210); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(GREEN); // Указываем цвет текста
    getDataInt100(ElectrPowReal);

    tft.setCursor(74, 220); // Устанавливаем курсор (X = , Y = )
    if (FlagWater == true)
      tft.setTextColor(CYAN, MAGENTA); // Указываем цвет текста
    else
      tft.setTextColor(MAGENTA); // Указываем цвет текста
    getDataInt100(gWaterReal);

    tft.setCursor(100, 470); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(RED); // Указываем цвет текста
    getDataInt100(countHeat / countHeatAndElectr);

    tft.setCursor(200, 470); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(GREEN); // Указываем цвет текста
    getDataInt100(countElectr / countHeatAndElectr);

    tft.setCursor(152, 220); // Устанавливаем курсор (X = , Y = )
    tft.setTextColor(RED); // Указываем цвет текста
    if (StopFlag == true)//если режим "останов" активен
      tft.print("Stop");
    else if (StartHeatFlag == true)
      tft.print("Start heat");
    else if (HeatOption == true)
      tft.print("Heat option");
    else if (HeatOptionPause == true)
      tft.print("Pause heat");
    else if (DefrostOption == true)
      tft.print("Defrost");
      

    //надписи для линий "включено_выключено"
    tft.setCursor(0, 440); // Устанавливаем курсор (X = , Y = )
    if (digitalRead(50) == LOW)
      tft.setTextColor(RED); // Указываем цвет текста
    else
      tft.setTextColor(BLUE); // Указываем цвет текста
    tft.print("Kompressor");

    tft.setCursor(0, 450); // Устанавливаем курсор (X = , Y = )
    if (digitalRead(51) == LOW)
      tft.setTextColor(RED); // Указываем цвет текста
    else
      tft.setTextColor(BLUE); // Указываем цвет текста
    tft.print("Pump");

    tft.setCursor(0, 460); // Устанавливаем курсор (X = , Y = )
    if (digitalRead(49) == LOW)
      tft.setTextColor(RED); // Указываем цвет текста
    else
      tft.setTextColor(BLUE); // Указываем цвет текста
    tft.print("4 way valve");

    tft.setCursor(0, 470); // Устанавливаем курсор (X = , Y = )
    if (digitalRead(48) == LOW)
      tft.setTextColor(RED); // Указываем цвет текста
    else
      tft.setTextColor(BLUE); // Указываем цвет текста
    tft.print("Fan");

    countGetGrafic++;//для подсчёта времени между вывода графика
    myTimer1 = millis();
    Flag1 = true;
  }



  //первый шаг выполнения по таймеру (график периодически)
  if (millis() - myTimer1 >= 2000 and Flag1 == true and countGetGrafic >= 36)
  {
    //перенос значений в массиве
    for (uint16_t x = 0; x < 319; x++)
    {
      tIsparitel[x] = tIsparitel[x + 1];
      tOverheat[x] = tOverheat[x + 1];
      tKompressor[x] = tKompressor[x + 1];
      tWaterIn[x] = tWaterIn[x + 1];
      tWaterOut[x] = tWaterOut[x + 1];
      gWater[x] = gWater[x + 1];
      heatPower[x] = heatPower[x + 1];
      ElectrPow[x] = ElectrPow[x + 1];
      COP[x] = COP[x + 1];
      tDefrost[x] = tDefrost[x + 1];
    }

    tKompressor[319] = (uint8_t)getDataForUintMassiv(Temper[KompressorC]);
    tWaterIn[319] = (uint8_t)getDataForUintMassiv(Temper[WaterInC]);
    tWaterOut[319] = (uint8_t)getDataForUintMassiv(Temper[WaterOutC]);
    tOverheat[319] = (uint8_t)getDataForUintMassiv(tOverheatReal);
    tDefrost[319] = (uint8_t)getDataForUintMassiv(Temper[DefrostC]);
    tIsparitel[319] = (uint8_t)getDataForUintMassiv(Temper[IsparitelC]);
    heatPower[319] = (uint8_t)getDataForUintMassiv32(heatPowerReal);
    COP[319] = (uint8_t)getDataForUintMassiv32(COPreal);
    ElectrPow[319] = (uint8_t)getDataForUintMassiv32(ElectrPowReal);
    gWater[319] = (uint8_t)getDataForUintMassiv32(gWaterReal);


    //формирование массивов для графиков включения оборудования
    for (uint8_t i; i < 20; i++)
    {
      trend_fan[i] = trend_fan[i] << 1;
      bitWrite(trend_fan[i], 0, bitRead(trend_fan[i + 1], 15));
      trend_kompressor[i] = trend_kompressor[i] << 1;
      bitWrite(trend_kompressor[i], 0, bitRead(trend_kompressor[i + 1], 15));
      trend_pump[i] = trend_pump[i] << 1;
      bitWrite(trend_pump[i], 0, bitRead(trend_pump[i + 1], 15));
      trend_4way_valve[i] = trend_4way_valve[i] << 1;
      bitWrite(trend_4way_valve[i], 0, bitRead(trend_4way_valve[i + 1], 15));
    }
    bitWrite(trend_fan[19], 0, !digitalRead(48));
    bitWrite(trend_kompressor[19], 0, !digitalRead(50));
    bitWrite(trend_pump[19], 0, !digitalRead(51));
    bitWrite(trend_4way_valve[19], 0, !digitalRead(49));


    getMainDisplay();//прорисовка всего экрана


    getGraphics(245, RED, tKompressor);
    getGraphics(245, YELLOW, tWaterIn);
    getGraphics(245, GREEN, tWaterOut);
    getGraphics(245, MAGENTA, tOverheat);
    getGraphics(245, CYAN, tDefrost);
    getGraphics(245, RED, tIsparitel);
    getGraphics(475, RED, heatPower);
    getGraphics(475, YELLOW, COP);
    getGraphics(475, GREEN, ElectrPow);
    getGraphics(475, MAGENTA, gWater);

    for (uint8_t i = 0; i < 20; i++)
    {
      for (uint8_t j = 0; j < 16; j++)
      {
        if (bitRead(trend_fan[i], j) == 1)
          tft.drawPixel ((i * 16 + (15 - j)), 479, YELLOW);
        if (bitRead(trend_kompressor[i], j) == 1)
          tft.drawPixel ((i * 16 + (15 - j)), 449, YELLOW);
        if (bitRead(trend_pump[i], j) == 1)
          tft.drawPixel ((i * 16 + (15 - j)), 459, YELLOW);
        if (bitRead(trend_4way_valve[i], j) == 1)
          tft.drawPixel ((i * 16 + (15 - j)), 469, YELLOW);
      }
    }

    countGetGrafic = 0; //для подсчёта времени между выводами графика
    Flag1 = false;
  }
}

Действительно, была проблема, когда использовал прерывания interrupt при получении импульса с водомера и электросчётчика. Ошибок по температуре - было гораздо больше. В итоге - встретил на форуме, попробовал, получилось. Оказалось - можно без прерываний, в loop без проблем успевать зафиксировать импульс. Отказался от прерываний. Ошибок по температурам стало гораздо меньше.

Срабатывание реле и чтение температур - разнёс во времени. Тоже раньше с этой проблемой сталкивался - если одновременно - жди ошибок с датчиков.

int16_t

не разобрался, как можно редактировать отправленное сообщение

проблема в том что при чтении мусора непонятно что будет…

#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;

#include <OneWire.h>
OneWire ds(21);  // выбор пина ардуино, на котором висит шина 1-wire

#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4

// Цвета
#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
#define WHITEGREY  0xC638
#define DARKGREY  0x7BEF
#define DARKGREEN 0x0C80

#define pinVoda_tick        18
boolean stateCheck_voda;

#define pinElectr_tick       19
boolean stateCheck_electr;

uint8_t relay;

// Адреса датчиков
byte ADDR_DS18B20 [6][8] = {
  {0x28, 0xD5, 0x97, 0x76, 0x0, 0x0, 0x0, 0x76}, // KompressorC   0
  {0x28, 0x4F, 0x9C, 0x73, 0x0, 0x0, 0x0, 0xF9}, // WaterInC      1
  {0x28, 0xFC, 0xD1, 0x72, 0x0, 0x0, 0x0, 0x2D}, // WaterOutC     2
  {0x28, 0x95, 0x28, 0x75, 0x0, 0x0, 0x0, 0xAB}, // temp3C        3
  {0x28, 0xEF, 0x8C, 0x76, 0x0, 0x0, 0x0, 0x58}, // DefrostC      4
  {0x28, 0x8C, 0x3A, 0xB4, 0xE, 0x0, 0x0, 0xDB}  // IsparitelC    5
};

// Массивы для графиков
uint8_t tIsparitel[320];
uint8_t tOverheat[320];
uint8_t tKompressor[320];
uint8_t tWaterIn[320];
uint8_t tWaterOut[320];
uint8_t tDefrost[320];
uint8_t heatPower[320];
uint8_t gWater[320];
uint8_t ElectrPow[320];
uint8_t COP[320];

// Точные значения
int16_t tOverheatReal;
int32_t gWaterReal;
int32_t heatPowerReal;
int32_t ElectrPowReal;
int32_t COPreal;

int32_t gWaterRealPrew;
boolean FlagWater = false;
boolean FirstImpulsWater = false;
boolean SecondImpulsWater = false;

// Средние значения
int32_t countHeatAndElectr = 0;
int32_t countHeat = 0;
int32_t countElectr = 0;

// Флаги цикла
boolean Flag1 = true;
boolean Flag2 = true;
boolean Flag3 = true;
boolean Flag4 = true;
uint8_t countGetGrafic = 9;
uint32_t Timer;

// Графики реле
int16_t trend_fan[20];
int16_t trend_kompressor[20];
int16_t trend_pump[20];
int16_t trend_4way_valve[20];

// Водомер
uint32_t tmr;
boolean gWaterFlag = false;
uint32_t WaterStartTimer;
uint32_t WaterCountTimer;

// Электросчётчик
uint32_t tmr2;
boolean ElectrFlag = false;
uint32_t ElectrStartTimer;
uint32_t ElectrCountTimer;

// Режимы
boolean StartHeatFlag;
boolean StopFlag;
boolean HeatOptionFlag;
boolean HeatOptionPauseFlag;
boolean DefrostOptionFlag;

uint8_t tKompressorCounter = 0;
uint8_t HeatOptionCout = 0;
uint8_t HeatOptionPauseCout = 0;
uint8_t defrostCount = 0;
uint8_t StopMaxTemp = 0;
uint8_t StopMaxElectr = 0;
uint8_t StopMinTempHeater = 0;
uint8_t StopNo4xvalve = 0;
uint8_t StopNoElectric = 0;

uint8_t StopKind = 0;
uint8_t MistakeTemp[6] = {};
boolean getMistakeFlag = false;

uint32_t HeatOptionPauseStartTime;
uint32_t StartHeatTime;
uint32_t StopTime;
uint32_t DefrostTime;

// Переменные температур
int16_t Temper[6] = {0, 0, 0, 0, 0, 0};
int16_t TemperPrev[6];

// ============== CRC8 ДЛЯ DS18B20 ==============
uint8_t dallas_crc8(const uint8_t *data, uint8_t len) {
  uint8_t crc = 0;
  for (uint8_t i = 0; i < len; i++) {
    uint8_t inbyte = data[i];
    for (uint8_t j = 0; j < 8; j++) {
      uint8_t mix = (crc ^ inbyte) & 0x01;
      crc >>= 1;
      if (mix) crc ^= 0x8C;
      inbyte >>= 1;
    }
  }
  return crc;
}

// ============== НОВАЯ ФУНКЦИЯ ЧТЕНИЯ С ПРОВЕРКОЙ CRC ==============
// Возвращает температуру в десятых долях (например, 156 = 15.6°C)
// Возвращает 0x7FFF при ошибке CRC
int16_t readDS18B20(byte *addr) {
  uint8_t data[9];
  
  ds.reset();
  ds.select(addr);
  ds.write(0xBE); // Читать scratchpad
  
  for (uint8_t i = 0; i < 9; i++) {
    data[i] = ds.read();
  }
  
  // Проверка CRC
  if (dallas_crc8(data, 8) != data[8]) {
    return 0x7FFF; // Ошибка
  }
  
  int16_t raw = (data[1] << 8) | data[0];
  return (raw * 10) >> 4; // Десятые доли
}

// ============== СБРОС ШИНЫ (запуск конвертации) ==============
void resetTemp() {
  ds.reset();
  ds.write(0xCC); // Ко всем датчикам
  ds.write(0x44); // Конвертация
}

// ============== ПОЛУЧЕНИЕ ТЕМПЕРАТУР ==============
void gettingTemp() {
  for (uint8_t i = 0; i < 6; i++) {
    int16_t temp = readDS18B20(ADDR_DS18B20[i]);
    
    if (temp != 0x7FFF) {
      // Данные достоверны
      Temper[i] = temp;
      TemperPrev[i] = temp;
      if (MistakeTemp[i] <= 6) MistakeTemp[i] = 0;
    } else {
      // Ошибка CRC
      Temper[i] = TemperPrev[i];
      if (StopFlag == false) {
        MistakeTemp[i]++;
        if (MistakeTemp[i] > 6) {
          StopFlag = true;
          StopTime = millis();
          StopKind = 6;
        }
      }
    }
  }
}

// ============== ПРОВЕРКА ГРАНИЦ ТЕМПЕРАТУР ==============
void tempMistake(uint8_t a, int16_t b, int16_t c, int16_t d) {
  // Эта функция теперь только для физических проверок,
  // так как CRC уже отсеяла помехи.
  // Но оставляем на всякий случай.
  if (MistakeTemp[a] > 0) return; // Уже есть ошибка CRC
  
  if (Temper[a] >= b && Temper[a] <= c && abs(Temper[a] - TemperPrev[a]) <= d) {
    // Норма
  } else {
    Temper[a] = TemperPrev[a];
    if (StopFlag == false) MistakeTemp[a]++;
    if (MistakeTemp[a] > 6 && StopFlag == false) {
      StopFlag = true;
      StopTime = millis();
      StopKind = 6;
    }
  }
}

// ============== ВОДОМЕР ==============
void voda_tick() {
  boolean current_status = digitalRead(pinVoda_tick);
  if (stateCheck_voda && !current_status) {
    tmr = millis();
    gWaterFlag = true;
    stateCheck_voda = current_status;
  }
  if (!stateCheck_voda && current_status) stateCheck_voda = current_status;
  if (current_status) gWaterFlag = false;
  if (!current_status && millis() - tmr >= 1000 && gWaterFlag == true) {
    WaterCountTimer = millis() - WaterStartTimer;
    WaterStartTimer = millis();
    gWaterFlag = false;
    if (FirstImpulsWater == true) SecondImpulsWater = true;
    FirstImpulsWater = true;
  }
}

// ============== ЭЛЕКТРОСЧЁТЧИК ==============
void electr_tick() {
  boolean current_status = digitalRead(pinElectr_tick);
  if (stateCheck_electr && !current_status) {
    tmr2 = millis();
    ElectrFlag = true;
    stateCheck_electr = current_status;
  }
  if (!stateCheck_electr && current_status) stateCheck_electr = current_status;
  if (current_status) ElectrFlag = false;
  if (!current_status && millis() - tmr2 >= 50 && ElectrFlag == true) {
    ElectrCountTimer = millis() - ElectrStartTimer;
    ElectrStartTimer = millis();
    ElectrFlag = false;
  }
}

// ============== ВЫВОД ЗНАЧЕНИЙ НА ДИСПЛЕЙ ==============
void getDataInt100(int16_t a) {
  if (a >= -9 && a < 0) {
    tft.print("-0.0");
    tft.print(abs(a));
  } else if (a < 9 && a >= 0) {
    tft.print("0.0");
    tft.print(a);
  } else if (a <= -10 && a > -100) {
    tft.print("-0.");
    tft.print(abs(a % 100));
  } else {
    tft.print(a / 100);
    tft.print(".");
    if (abs(a % 100) < 10) tft.print("0");
    tft.print(abs(a % 100));
  }
}

void getDataInt10(int16_t a) {
  if (a >= -9 && a < 0) {
    tft.print("-0.");
    tft.print(abs(a));
  } else if (a < 9 && a >= 0) {
    tft.print("0.");
    tft.print(a);
  } else {
    tft.print(a / 10);
    tft.print(".");
    tft.print(abs(a % 10));
  }
}

int16_t getDataForUintMassiv(int16_t a) {
  int16_t LastInMassive;
  if (a < -400) LastInMassive = 0;
  else if (a >= -400 && a < 500) LastInMassive = round((a + 400) / 5);
  else if (a >= 500 && a < 1250) LastInMassive = round(180 + (a - 500) / 10);
  else LastInMassive = 255;
  return LastInMassive;
}

int32_t getDataForUintMassiv32(int32_t a) {
  int32_t LastInMassive;
  if (a < -400) LastInMassive = 0;
  else if (a >= -400 && a < 0) LastInMassive = round((a + 400) / 10);
  else if (a >= 0 && a <= 160) LastInMassive = round(40 + a);
  else if (a > 160 && a < 710) LastInMassive = round(200 + (a - 160) / 10);
  else LastInMassive = 255;
  return LastInMassive;
}

void getGraphics(uint16_t c, uint16_t d, uint8_t g[320]) {
  int16_t oldX = 0;
  int16_t oldY = g[0];
  for (int16_t x = 1; x < 320; x++) {
    int16_t nxt_x = x;
    tft.drawLine(oldX, (c - oldY), nxt_x, (c - g[x]), d);
    oldY = g[x];
    oldX = nxt_x;
  }
}

// ============== ОТРИСОВКА ДИСПЛЕЯ ==============
void getMainDisplay() {
  tft.fillScreen(BLACK);

  // Сетка по времени
  for (uint8_t i = 1; i < 8; i++) {
    tft.drawFastVLine((40 * i), 20, 190, DARKGREY);
    tft.drawFastVLine((40 * i), 230, 210, DARKGREY);
  }

  // Сетка по температуре
  for (uint8_t i = 0; i < 11; i++) {
    if (i == 4 || i == 5 || i == 6 || i == 7 || i == 9 || i == 10)
      tft.drawFastHLine(0, (5 + 20 * i), 320, DARKGREEN);
    else if (i == 1 || i == 2 || i == 3 || i == 8)
      tft.drawFastHLine(0, (5 + 20 * i), 320, BLUE);
  }
  for (uint8_t i = 0; i < 5; i++)
    tft.drawFastHLine(0, (231 + 10 * i), 320, BLUE);
  tft.drawFastHLine(0, 275, 320, BLUE);
  for (uint8_t i = 0; i < 8; i++)
    tft.drawFastHLine(0, (295 + 20 * i), 320, DARKGREEN);

  // Подписи
  tft.setTextSize(1);
  
  tft.setCursor(0, 22); tft.setTextColor(WHITE, BLACK); tft.print("90");
  tft.setCursor(0, 42); tft.print("70");
  tft.setCursor(0, 62); tft.print("50");
  tft.setCursor(0, 82); tft.print("40");
  tft.setCursor(0, 102); tft.print("30");
  tft.setCursor(0, 122); tft.print("20");
  tft.setCursor(0, 142); tft.print("10");
  tft.setCursor(0, 162); tft.print("0");
  tft.setCursor(0, 182); tft.print("-10");
  tft.setCursor(0, 202); tft.print("-20");

  tft.setCursor(0, 238); tft.print("5");
  tft.setCursor(0, 258); tft.print("3");
  tft.setCursor(0, 272); tft.print("1.6");
  tft.setCursor(0, 292); tft.print("1.4");
  tft.setCursor(0, 312); tft.print("1.2");
  tft.setCursor(0, 332); tft.print("1");
  tft.setCursor(0, 352); tft.print("0.8");
  tft.setCursor(0, 372); tft.print("0.6");
  tft.setCursor(0, 392); tft.print("0.4");
  tft.setCursor(0, 412); tft.print("0.2");
  tft.setCursor(0, 432); tft.print("0");

  // Назначения датчиков
  tft.setCursor(0, 0); tft.setTextColor(RED); tft.print("Kompressor:");
  tft.setCursor(10, 10); tft.setTextColor(MAGENTA); tft.print("Overheat:");
  tft.setCursor(125, 10); tft.setTextColor(CYAN); tft.print("Defrost:");
  tft.setCursor(220, 10); tft.setTextColor(RED); tft.print("Isparitel:");
  tft.setCursor(120, 0); tft.setTextColor(YELLOW); tft.print("Water in:");
  tft.setCursor(220, 0); tft.setTextColor(GREEN); tft.print("Water out:");
  tft.setCursor(5, 210); tft.setTextColor(RED); tft.print("Heat power:");
  tft.setCursor(152, 210); tft.setTextColor(YELLOW); tft.print("COP:");
  tft.setCursor(218, 210); tft.setTextColor(GREEN); tft.print("Electr pow:");
  tft.setCursor(22, 220); tft.setTextColor(MAGENTA); tft.print("G water:");
}

// ============== SETUP ==============
void setup() {
  Serial.begin(9600);

  tft.reset();
  tft.begin(38022);
  tft.invertDisplay(true);

  pinMode(18, INPUT_PULLUP);
  stateCheck_voda = digitalRead(pinVoda_tick);

  pinMode(19, INPUT_PULLUP);
  stateCheck_electr = digitalRead(pinElectr_tick);

  for (int i = 0; i < 320; i++) {
    tIsparitel[i] = 80;
    tOverheat[i] = 80;
    tKompressor[i] = 80;
    tWaterIn[i] = 80;
    tWaterOut[i] = 80;
    tDefrost[i] = 80;
    heatPower[i] = 40;
    gWater[i] = 40;
    ElectrPow[i] = 40;
    COP[i] = 40;
  }

  pinMode(48, OUTPUT); digitalWrite(48, HIGH);
  pinMode(49, OUTPUT); digitalWrite(49, HIGH);
  pinMode(50, OUTPUT); digitalWrite(50, HIGH);
  pinMode(51, OUTPUT); digitalWrite(51, HIGH);

  StopFlag = false;
  HeatOptionFlag = false;
  HeatOptionPauseFlag = false;
  DefrostOptionFlag = false;
  StartHeatFlag = true;
  StartHeatTime = millis();
  Timer = millis();

  delay(1000);
}

// ============== LOOP ==============
void loop() {
  voda_tick();
  electr_tick();

  // Шаг 1: запуск конвертации (3-5 сек)
  if (millis() - Timer >= 3000 && millis() - Timer < 5000 && Flag1 == true) {
    resetTemp();
    Flag1 = false;
  }

  // Шаг 2: чтение температур (5-7 сек)
  if (millis() - Timer >= 5000 && millis() - Timer < 7000 && Flag2 == true) {
    gettingTemp();
    Flag2 = false;
  }

  // Шаг 3: расчёты и логика (7-9 сек)
  if (millis() - Timer >= 7000 && millis() - Timer < 9000 && Flag3 == true) {
    if (getMistakeFlag == false) {
      for (uint8_t i = 0; i < 6; i++) {
        TemperPrev[i] = Temper[i];
      }
      getMistakeFlag = true;
    }

    // Проверка границ (дополнительная, CRC уже отсеяла мусор)
    tempMistake(0, -100, 1200, 200);
    tempMistake(1, 0, 600, 100);
    tempMistake(2, 0, 900, 50);
    tempMistake(3, -400, 800, 400);
    tempMistake(4, -400, 600, 300);
    tempMistake(5, -400, 800, 200);

    tOverheatReal = (Temper[3] - Temper[5]);

    // Расход воды
    if (digitalRead(51) == HIGH) {
      gWaterReal = 0;
      gWaterRealPrew = gWaterReal;
      FlagWater = false;
    } else if (digitalRead(51) == LOW && millis() - WaterStartTimer > 400000) {
      StopFlag = true;
      StopTime = millis();
      StopKind = 7;
    } else if (SecondImpulsWater == false) {
      gWaterReal = gWaterRealPrew;
      FlagWater = true;
    } else {
      gWaterReal = round(3600000 / WaterCountTimer);
      gWaterRealPrew = gWaterReal;
      FlagWater = false;
    }

    // Электрическая мощность
    if (millis() - ElectrStartTimer > 60000)
      ElectrPowReal = 0;
    else
      ElectrPowReal = 72000 / ElectrCountTimer;

    // Тепловая мощность
    heatPowerReal = ((int32_t)Temper[2] - (int32_t)Temper[1]) * gWaterReal * 1163 / 10000;

    // COP
    if (ElectrPowReal < 10)
      COPreal = 0;
    else
      COPreal = 100 * heatPowerReal / ElectrPowReal;

    // Средние значения
    countHeat = countHeat + heatPowerReal;
    countElectr = countElectr + ElectrPowReal;
    countHeatAndElectr++;

    // Управление вентилятором при перегреве
    if (HeatOptionFlag == true && Temper[0] >= 850 && tOverheatReal >= 300 && digitalRead(48) == LOW) {
      tKompressorCounter++;
      if (tKompressorCounter >= 4) {
        bitClear(relay, 0);
        tKompressorCounter = 0;
      }
    } else if (HeatOptionFlag == true && (Temper[0] <= 750 || tOverheatReal <= 200) && digitalRead(48) == HIGH) {
      tKompressorCounter++;
      if (tKompressorCounter >= 2) {
        bitSet(relay, 0);
        tKompressorCounter = 0;
      }
    } else {
      tKompressorCounter = 0;
    }

    // Режимы нагрева
    if (HeatOptionFlag == true && Temper[1] >= 360) {
      HeatOptionCout++;
      if (HeatOptionCout >= 6) {
        HeatOptionFlag = false;
        HeatOptionPauseFlag = true;
        HeatOptionCout = 0;
        HeatOptionPauseStartTime = millis();
        bitClear(relay, 2);
        bitClear(relay, 0);
      }
    } else {
      HeatOptionCout = 0;
    }

    if (HeatOptionPauseFlag == true && digitalRead(49) == LOW && millis() - HeatOptionPauseStartTime >= 120000)
      bitClear(relay, 1);

    if (HeatOptionPauseFlag == true && digitalRead(51) == HIGH && millis() - HeatOptionPauseStartTime >= 960000) {
      if (digitalRead(51) == HIGH) {
        WaterStartTimer = millis();
        FirstImpulsWater = false;
        SecondImpulsWater = false;
      }
      bitSet(relay, 3);
      HeatOptionPauseStartTime = millis();
    }

    if (HeatOptionPauseFlag == true && digitalRead(51) == LOW && millis() - HeatOptionPauseStartTime >= 60000) {
      bitClear(relay, 3);
      HeatOptionPauseStartTime = millis();
    }

    if (HeatOptionPauseFlag == true && Temper[1] <= 280) {
      HeatOptionPauseCout++;
      if (HeatOptionPauseCout >= 5) {
        HeatOptionPauseFlag = false;
        HeatOptionPauseCout = 0;
        StartHeatTime = millis();
        StartHeatFlag = true;
      }
    } else {
      HeatOptionPauseCout = 0;
    }

    // Defrost
    if (HeatOptionFlag == true && Temper[5] < -100) {
      defrostCount++;
      if (defrostCount >= 6) {
        HeatOptionFlag = false;
        DefrostOptionFlag = true;
        DefrostTime = millis();
        defrostCount = 0;
      }
    } else {
      defrostCount = 0;
    }

    // Защиты
    if (HeatOptionFlag == true && digitalRead(49) == HIGH) {
      StopNo4xvalve++;
      if (StopNo4xvalve >= 3) {
        bitClear(relay, 2);
        StopFlag = true;
        StopTime = millis();
        StopKind = 1;
        StopNo4xvalve = 0;
      }
    } else {
      StopNo4xvalve = 0;
    }

    if ((StartHeatFlag == true || HeatOptionFlag == true || HeatOptionPauseFlag == true || DefrostOptionFlag == true) && Temper[0] >= 1000) {
      StopMaxTemp++;
      if (StopMaxTemp >= 5) {
        StopFlag = true;
        StopTime = millis();
        StopKind = 2;
        StopMaxTemp = 0;
      }
    } else {
      StopMaxTemp = 0;
    }

    if ((StartHeatFlag == true || HeatOptionFlag == true || HeatOptionPauseFlag == true || DefrostOptionFlag == true) && ElectrPowReal >= 151) {
      StopMaxElectr++;
      if (StopMaxElectr >= 6) {
        StopFlag = true;
        StopTime = millis();
        StopKind = 3;
        StopMaxElectr = 0;
      }
    } else {
      StopMaxElectr = 0;
    }

    if ((StartHeatFlag == true || HeatOptionFlag == true || HeatOptionPauseFlag == true) && Temper[4] <= 50) {
      StopMinTempHeater++;
      if (StopMinTempHeater >= 3) {
        StopFlag = true;
        StopTime = millis();
        StopKind = 4;
        StopMinTempHeater = 0;
      }
    } else {
      StopMinTempHeater = 0;
    }

    if (digitalRead(50) == LOW && ElectrPowReal <= 10) {
      StopNoElectric++;
      if (StopNoElectric >= 6) {
        StopFlag = true;
        StopTime = millis();
        StopKind = 5;
        StopNoElectric = 0;
      }
    } else {
      StopNoElectric = 0;
    }

    // Start heat
    if (StartHeatFlag == true && millis() - StartHeatTime >= 20000 && Temper[1] >= 50) {
      if (digitalRead(51) == HIGH) {
        WaterStartTimer = millis();
        FirstImpulsWater = false;
        SecondImpulsWater = false;
      }
      bitSet(relay, 3);
    }

    if (StartHeatFlag == true && digitalRead(51) == LOW && millis() - StartHeatTime >= 30000 && Temper[0] <= 850)
      bitSet(relay, 0);

    if (StartHeatFlag == true && digitalRead(51) == LOW && digitalRead(48) == LOW && millis() - StartHeatTime >= 40000)
      bitSet(relay, 1);

    if (StartHeatFlag == true && digitalRead(51) == LOW && digitalRead(48) == LOW && digitalRead(49) == LOW && millis() - StartHeatTime >= 45000)
      bitSet(relay, 2);

    if (StartHeatFlag == true && digitalRead(51) == LOW && digitalRead(48) == LOW && digitalRead(50) == LOW && digitalRead(49) == LOW) {
      StartHeatFlag = false;
      HeatOptionFlag = true;
    }

    // Stop
    if (StopFlag == true) {
      StartHeatFlag = false;
      HeatOptionFlag = false;
      HeatOptionPauseFlag = false;
      DefrostOptionFlag = false;
      bitClear(relay, 2);
      bitClear(relay, 0);
      if (millis() - StopTime >= 120000) bitClear(relay, 1);
      if (millis() - StopTime >= 180000) bitClear(relay, 3);
    }

    // Defrost
    if (DefrostOptionFlag == true) {
      if (digitalRead(51) == HIGH) {
        WaterStartTimer = millis();
        FirstImpulsWater = false;
        SecondImpulsWater = false;
      }
      bitSet(relay, 3);
      bitClear(relay, 2);
      bitSet(relay, 0);
      if (millis() - DefrostTime >= 120000) bitClear(relay, 1);
      if (millis() - DefrostTime >= 900000 && Temper[5] >= 20) {
        DefrostOptionFlag = false;
        StartHeatTime = millis();
        StartHeatFlag = true;
      }
    }

    // Очистка старых значений
    tft.fillRect(74, 0, 30, 20, BLACK);
    tft.fillRect(181, 0, 30, 20, BLACK);
    tft.fillRect(289, 0, 30, 20, BLACK);
    tft.fillRect(74, 210, 30, 20, BLACK);
    tft.fillRect(181, 210, 30, 10, BLACK);
    tft.fillRect(289, 210, 30, 10, BLACK);
    tft.fillRect(152, 220, 60, 10, BLACK);
    tft.fillRect(100, 470, 30, 8, BLACK);
    tft.fillRect(200, 470, 30, 8, BLACK);

    // Вывод значений на дисплей
    tft.setCursor(74, 0);
    tft.setTextColor((MistakeTemp[0] > 0) ? CYAN : RED);
    getDataInt10(Temper[0]);

    tft.setCursor(181, 0);
    tft.setTextColor((MistakeTemp[1] > 0) ? CYAN : YELLOW);
    getDataInt10(Temper[1]);

    tft.setCursor(289, 0);
    tft.setTextColor((MistakeTemp[2] > 0) ? CYAN : GREEN);
    getDataInt10(Temper[2]);

    tft.setCursor(74, 10);
    tft.setTextColor((MistakeTemp[3] > 0) ? CYAN : MAGENTA);
    getDataInt10(tOverheatReal);

    tft.setCursor(181, 10);
    tft.setTextColor((MistakeTemp[4] > 0) ? CYAN : CYAN);
    getDataInt10(Temper[4]);

    tft.setCursor(289, 10);
    tft.setTextColor((MistakeTemp[5] > 0) ? CYAN : RED);
    getDataInt10(Temper[5]);

    tft.setCursor(74, 210); tft.setTextColor(RED); getDataInt100(heatPowerReal);
    tft.setCursor(181, 210); tft.setTextColor(YELLOW); getDataInt100(COPreal);
    tft.setCursor(289, 210); tft.setTextColor(GREEN); getDataInt100(ElectrPowReal);
    tft.setCursor(74, 220);
    tft.setTextColor(FlagWater ? CYAN : MAGENTA);
    getDataInt100(gWaterReal);

    tft.setCursor(100, 470); tft.setTextColor(RED);
    getDataInt100(countHeat / countHeatAndElectr);
    tft.setCursor(200, 470); tft.setTextColor(GREEN);
    getDataInt100(countElectr / countHeatAndElectr);

    tft.setCursor(152, 220); tft.setTextColor(RED);
    if (StopFlag == true) {
      tft.print("Stop");
      switch (StopKind) {
        case 1: tft.print(" No 4x valve"); break;
        case 2: tft.print(" maxT Kompr"); break;
        case 3: tft.print(" maxW Electr"); break;
        case 4: tft.print(" minT Heater"); break;
        case 5: tft.print(" No Electr"); break;
        case 6: tft.print(" Err Temp"); break;
        case 7: tft.print(" No G_water"); break;
        case 8: tft.print(" No Option"); break;
      }
    } else if (StartHeatFlag == true) {
      tft.print("Start heat");
    } else if (HeatOptionFlag == true) {
      tft.print("Heat option");
    } else if (HeatOptionPauseFlag == true) {
      tft.print("Pause heat");
    } else if (DefrostOptionFlag == true) {
      tft.print("Defrost");
    } else {
      StopFlag = true;
      StopTime = millis();
      StopKind = 8;
    }

    // Состояния реле
    tft.setCursor(0, 440);
    tft.setTextColor(bitRead(relay, 2) ? RED : BLUE);
    tft.print("Kompressor");

    tft.setCursor(0, 450);
    tft.setTextColor(bitRead(relay, 3) ? RED : BLUE);
    tft.print("Pump");

    tft.setCursor(0, 460);
    tft.setTextColor(bitRead(relay, 1) ? RED : BLUE);
    tft.print("4 way valve");

    tft.setCursor(0, 470);
    tft.setTextColor(bitRead(relay, 0) ? RED : BLUE);
    tft.print("Fan");

    Flag3 = false;
  }

  // Шаг 4: управление реле (9-10 сек)
  if (millis() - Timer >= 9000 && millis() - Timer < 10000 && Flag4 == true) {
    digitalWrite(48, bitRead(relay, 0) ? LOW : HIGH);
    digitalWrite(49, bitRead(relay, 1) ? LOW : HIGH);
    digitalWrite(50, bitRead(relay, 2) ? LOW : HIGH);
    digitalWrite(51, bitRead(relay, 3) ? LOW : HIGH);
    Flag4 = false;
  }

  // Шаг 5: обновление графиков (сброс цикла)
  if (millis() - Timer >= 10000) {
    Timer = millis();

    if (countGetGrafic >= 9) {
      // Сдвиг массивов
      for (uint16_t x = 0; x < 319; x++) {
        tIsparitel[x] = tIsparitel[x + 1];
        tOverheat[x] = tOverheat[x + 1];
        tKompressor[x] = tKompressor[x + 1];
        tWaterIn[x] = tWaterIn[x + 1];
        tWaterOut[x] = tWaterOut[x + 1];
        gWater[x] = gWater[x + 1];
        heatPower[x] = heatPower[x + 1];
        ElectrPow[x] = ElectrPow[x + 1];
        COP[x] = COP[x + 1];
        tDefrost[x] = tDefrost[x + 1];
      }

      // Новые значения
      tKompressor[319] = (uint8_t)getDataForUintMassiv(Temper[0]);
      tWaterIn[319] = (uint8_t)getDataForUintMassiv(Temper[1]);
      tWaterOut[319] = (uint8_t)getDataForUintMassiv(Temper[2]);
      tOverheat[319] = (uint8_t)getDataForUintMassiv(tOverheatReal);
      tDefrost[319] = (uint8_t)getDataForUintMassiv(Temper[4]);
      tIsparitel[319] = (uint8_t)getDataForUintMassiv(Temper[5]);
      heatPower[319] = (uint8_t)getDataForUintMassiv32(heatPowerReal);
      COP[319] = (uint8_t)getDataForUintMassiv32(COPreal);
      ElectrPow[319] = (uint8_t)getDataForUintMassiv32(ElectrPowReal);
      gWater[319] = (uint8_t)getDataForUintMassiv32(gWaterReal);

      // Графики реле
      for (uint8_t i = 0; i < 20; i++) {
        trend_fan[i] = trend_fan[i] << 1;
        bitWrite(trend_fan[i], 0, bitRead(trend_fan[i + 1], 15));
        trend_kompressor[i] = trend_kompressor[i] << 1;
        bitWrite(trend_kompressor[i], 0, bitRead(trend_kompressor[i + 1], 15));
        trend_pump[i] = trend_pump[i] << 1;
        bitWrite(trend_pump[i], 0, bitRead(trend_pump[i + 1], 15));
        trend_4way_valve[i] = trend_4way_valve[i] << 1;
        bitWrite(trend_4way_valve[i], 0, bitRead(trend_4way_valve[i + 1], 15));
      }
      bitWrite(trend_fan[19], 0, bitRead(relay, 0));
      bitWrite(trend_kompressor[19], 0, bitRead(relay, 2));
      bitWrite(trend_pump[19], 0, bitRead(relay, 3));
      bitWrite(trend_4way_valve[19], 0, bitRead(relay, 1));

      getMainDisplay();

      getGraphics(245, RED, tKompressor);
      getGraphics(245, YELLOW, tWaterIn);
      getGraphics(245, GREEN, tWaterOut);
      getGraphics(245, MAGENTA, tOverheat);
      getGraphics(245, CYAN, tDefrost);
      getGraphics(245, RED, tIsparitel);
      getGraphics(475, RED, heatPower);
      getGraphics(475, YELLOW, COP);
      getGraphics(475, GREEN, ElectrPow);
      getGraphics(475, MAGENTA, gWater);

      for (uint8_t i = 0; i < 20; i++) {
        for (uint8_t j = 0; j < 16; j++) {
          if (bitRead(trend_fan[i], j) == 1) tft.drawPixel((i * 16 + (15 - j)), 479, YELLOW);
          if (bitRead(trend_kompressor[i], j) == 1) tft.drawPixel((i * 16 + (15 - j)), 449, YELLOW);
          if (bitRead(trend_pump[i], j) == 1) tft.drawPixel((i * 16 + (15 - j)), 459, YELLOW);
          if (bitRead(trend_4way_valve[i], j) == 1) tft.drawPixel((i * 16 + (15 - j)), 469, YELLOW);
        }
      }

      countGetGrafic = 0;
    }

    countGetGrafic++;
    Flag1 = true;
    Flag2 = true;
    Flag3 = true;
    Flag4 = true;
  }
}

вероятность маленькая… наверняка что то не учтено, но опять же если совсем идей нет… можно и потестить))) исправив если что то не так…

В основном да, но есть же еще и сыровары, птицеводы (инкубатор, брудер) и тепличники. По сути - всем (многим) нужна температура. )))

Речь идет о процессе прерывания. В дурдуине крайне “коряво” сделан процесс под названием millis ( ну, к примеру )

Уверен, что мало кто знает о SIOR0. И как с ним можно очень классно и быстро работать.

Еще Я уверен в том, что мало кто из “здешних” сможет написать код, допустим, когда для входа в “специальное меню” нужно после подачи питания и ожидания > 1 < 3 секунд нажать (допустим) пять раз на кнопку reset и будет вход в сервисное меню.

Тупой программист - он на то и тупой, что кроме как знать язык = больше ничего. Это “катит” в софте на OS → в эмбедде это никатит. Ты должен полностью знать и на лету анализировать как работает тот или иной датчик, протокол и прочее.

Советую “курить” гит и… там уже от знаний…