ARDUINO Печь отопления на отработанном масле с контроллером на aduino

MastarRak

✩✩✩✩✩✩✩
3 Май 2025
3
3
Итак, в процессе разработки и доработки "Управляемая печка на отработанном масле закрытого типа с контроллером на ардуино".
5285526203912028870.jpg5285123168475934418.jpg5285123168475934419.jpgголая печка.jpg

Принцип работы: на дно печи капельной подачей поступает масло, которое испаряясь и смешиваясь с воздухом, нагнетаемым вентилятором, сгорает практически без остатка и копоти.
Вдохновился кучей просмотренных видосов о печках на отработке, но вдохновили к реализации самой печки два видоса: в одном сама железяка и схема сверления отверстий, во втором прекрасная идея с масляным насосом от мотоцикла урал.
Первое видео, в котором отлично показано устройство самой печи. Скопировал максимально, но не точь в точь
Теперь же, проверив её работоспособность (видео сюда пока не выкладываю, показывал в телеге - работает) собираю контроллер на ардуино, благодаря которому планирую регулировать подачу масла/воздуха, и как следствие расход топлива и выдаваемое тепло.

О печке:
внешняя труба диаметром 150мм, металл 5 мм, высота Х?Х.
Внутренняя труба-инжектор для подачи воздуха диаметром 40мм, дырки просверлены по схеме, как в видео выше
(вот это параметры лучше взять даже не у меня, а из видео по ссылке выше. У меня были минимальные, но отклонения в диаметре отверстий, и всё же всё работает)
5285123168475934339.jpg
Внутри воздушной трубы-инжектора до самого дна проходит труба для подачи масла. На дне печки лежит чашка из нержавейки высотой 2-3 см для приёма масла.
5285123168475934338.jpg5285123168475934337.jpg

Для росжига использую смесь бензин+солярка 1к1 примерно грамм 50-100. Эта смесь хорошо загорается, стабильно горит и позволяет прогреть печь для перехода в рабочий режим.
В момент росжига нужно МАЛО воздуха - этому режиму соответсвует режим "START" в прошивке контроллера. Так же, нужно совсем немного масла, т.к. печка заправлена смесью бензина+солярки.
Через несколько минут (в прошивке задумана настройка "Таймаут режима", для автоматического перехода в рабочий режим по истечению времени) переходим в режим WORK - максимальная подача воздуха и масла.
Воздух нагнетает вентилятор-улитка с али (якобы BFB1012UH 6A), подача масла с помощью мотора, который вращает масляный насос от мотоцикла "Урал". Это основное.

НА ТЕКУЩИЙ МОМЕНТ (я надеюсь я позже смогу отредактировать этот кусок текста) С АЛИ ИДЁТ МОТОР ДЛЯ ВРАЩЕНИЯ МАСЛЯНОГО НАСОСА, поэтому реализация в скетче задумана, но... её ещё нет :)

Скетч какой есть - прикладываю. Надо понимать, что он в активной разработке и переработке, прошу не судить - пишу как умею )))
Для разработки использую ардуино уно, макетку, энкодер, экран 20х4, один подтягивающий резистор на 10 кОм для тахометра кулера и... пока несуществующий моторчик для масляного насоса с контроллером для него. Изображения схемы делать не умею, вот держите фото прям с рабочего стола
5285123168475934364.jpg


ИТОГ: сейчас основной упор на управление и определением "правильной и самой удачной" пропорцией потоком воздуха / подачей масла. В перспективе - датчик давления воздуха, как дополнительный способ контроля, термопара для более точного определения выхода печки в рабочий режим и переделка работающей конструкции для жидкостного отопления гаража.
Т.к. скетч в разработке - опишу его предполагаемый алгоритм работы
При первом запуске скетч проверяет нет ли в EEPROM сохраненных настроек режимов работы и если нет, загружает из по умолчанию из массива.
На экране появляется выбор из пяти режимов, по умолчанию ни один не включен.
Управление осуществляется энкодером: влево-вправо стрелка перемещается по режимам, один клик - включить режим, удержание - войти в настройку режима
Первый режим "PumpOil" - прокачка масла. Включается мотор масляного насоса. Выключается повторным нажатием, включением другого режима или через время, заданное в настройке режима (Timeout)
Второй режим "START" - печка заправлена смесью солярка+бензин, брошена спичка, масляный насос работает на низких оборотах, кулер тоже работает на низких оборотах. После опытов по находжению удачных настроек подачи масла/воздуха, а так же определения необходимого времени до устойчивого горения, должен по таймауту (устанавливается в настройках этого режима) перейти в режим "WORK"
Третий режим "WORK" - кулер подачи воздуха дует на полную, масло тоже подаётся на полную. Выжимаем из печки максимальную мощность
Четвертый режим "ECO" - после опытов найти минимальные значения воздуха/масла для устойчивой работы печки. В далёкой перспективе, поддержание установленной температуры в отопительной системе, где тоже будут два датчика (tIn, tOut)
Пятый режим "OFF" - подача масла прекращается, воздух дует по минимуму. Масло догарает, печь затухает. Режим выключается по таймауту установленному в настройке режима
Сейчас в скетч добавлена (в дальнейшем будет удалена или скрыта) дополнительная страница "DEBUG" для непосредственного и быстрого управления скоростью кулера и мотора подачи масла

PS: опыта создания тем на форумах нет, поэтому тоже прошу не судить за форматирование и прочее.
PPS: в отдельных сообщениях когда-нибудь распишу подробное устройство самой печки (железяка) или сниму и выложу видео.
PPPS: Ознакомлен с правилами раздела, не ною, не претендую, код открыт, задавайте вопросы, повторяйте, предлагайте улучшения и т.д. @viktorovich в тг.
 

Вложения

Изменено:
  • Лойс +1
Реакции: Wan-Derer

bort707

★★★★★★✩
21 Сен 2020
3,179
934
Спасибо за описание.
Меня всегда интересовало, откуда столько отработанного масла брать, чтобы им отапливаться? Не всем же открывать сервис бесплатной замены масла?
 

MastarRak

✩✩✩✩✩✩✩
3 Май 2025
3
3
@bort707, говорят масло стоит около 4-6 рублей за литр, а расход обещает быть около 300-400 грамм час. Но меня так же замотивировал бесплатный доступ к отработанному маслу, т.к. в соседнем гараже небольшой автосервис, а все соседи по кооперативу уже знают, что я приму масло и избавлю их от заботы "куда же теперь его деть или вылить"
 

MastarRak

✩✩✩✩✩✩✩
3 Май 2025
3
3
Ниже приложу код скетча с небольшими пояснениями:
  • используется библиотека #include <GyverPWM.h> для поднятия частоты ШИМ. Дело в том, что управление скоростью кулера через управляющий провод должно осуществляться ШИМ сигналом на 25кГц. Установка частоты ШИМ на 25кГц ломает внутренний таймер, что критично, но как выяснилось опытным путём, частота должна быть не ниже 25 кГц
  • настройки каждого режима содержатся в отдельных массивах, которые читаются и сохраняются в EEPROM (дорабатывается)
  • режимы PumpOil, START, WORK, ECO и OFF соответствуют значению переменной "mode" от 0 до 4. Значение переменной -1 это "все режимы выключены".
  • СЕЙЧАС при повороте энкодера влево "до конца" (против часовой) попадаем на страницу "DEBUG" (функция debugPage()) для быстрого и простого изменения скоростей кулера и масляного насоса. На этой страничке буду подбирать эталонные значения параметров для режимов
  • тахометр кулера подключается к пину D2, который обозначается как 0 (нулевой) пин аппаратного прерывания. Счетчик оборотов кулера вычисляет количество из расчета, что за один оборот крыльчатки кулера, провод тахометра дважды замыкается и дважды размыкается на землю (итого 4 раза CHANGE)

C++:
#define FAN_THM 0 // (2) //тахометр кулера
#define FAN_VOL 3        //управляющий кулера
#define ENC1 4    //энк S1
#define ENC2 5    //энк S2
#define ENC3 6    //энк KEY

#define PRESSURE_DOUT_PIN A0    // датчик давления
#define PRESSURE_SCK_PIN A1     // датчик давления



//#define PUMP_PIN 7       //пин мотора помпы

//#define thermoSCK 11  //  термопара
//#define thermoCS 12   //  термопара
//#define thermoSO 13   //  термопара


#include <Arduino.h>
#include <Wire.h>
#include <EEPROM.h>                  
#include <LiquidCrystal_I2C.h>  //lcd 20x4
LiquidCrystal_I2C lcd(0x27, 20, 4);

#include "HX711.h"
HX711 pressSensor;

#include <GyverPWM.h>  
#include <EncButton.h>
EncButton eb(ENC1, ENC2, ENC3);

///////////////////////////преременные///////////////////////
int8_t settingsSet;   //ключ первого запуска без сохраненных настроек

int8_t arrowPos = 0;  //позиция стрелки главного экрана режимов работы
int8_t arrowPosM = 0; //позиция стрелки в меню настройки режима
int8_t arrowPosD = 0; //позиция стрелки в меню DUBUG

int mode = -1; //режим работы (при запуске)
int thm, pwm, oil;  //значение тахометра, сигнал на кулер, значение помпы
int tSmoke, tIn, tOut; //значения температур
int needFan;// , needTemp;  //целевые значения текущего режима

int32_t pressure;   //значение текущего давления (в попугаях)

volatile int valFan; //тахометр
unsigned long timeoutMode = 0, waitingHuman = 30000;    //таймаут текущего режима, таймаут пользователя
unsigned long LastTimeReadSensors = 0, LastUpdateScreen = 0, LastUpdatePump = 0, LastHumanActive = 0;       //  LastUpdatePump - возможно удалить

/////////// настройки и названия параметров режимов///////////
const char *setName0[] = {"Speed pump (%)", "Timeout(sec)"};                //PumpOil
const char *setName1[] = {"Speed pump (%)", "RPM fan", "Timeout (min)"};    //START
const char *setName2[] = {"Speed pump (%)", "RPM fan"};                     //WORK
const char *setName3[] = {"Speed pump (%)", "RPM fan", "Temp (C)"};         //ECO
const char *setName4[] = {"RPM fan", "Timeout (min)"};                      //OFF
const char **setNames[] = {setName0, setName1, setName2, setName3, setName4};
int valMode0[2] = {100, 30};          //PumpOil   таймаут, ск. помпы
int valMode1[3] = {20, 3500, 5};      //START     ск. помпы, ск. кулера, таймаут до перехода в работу
int valMode2[2] = {80, 8500};         //WORK      ск.помпы, ск. кулера
int valMode3[3] = {60, 5500, 140};    //ECO       ск. помпы, ск. кулера, температура
int valMode4[2] = {6000, 10};         //OFF       ск. кулера, таймаут
const int *valModes[] = {valMode0, valMode1, valMode2, valMode3, valMode4};
int countSetings[5] = {2, 3, 2, 3, 2}; //количество пунктов настроек в режимах

int savedArray0[2], savedArray1[3], savedArray2[2], savedArray3[3], savedArray4[2]; //массивы сохраненных настроек
////////////////////////////////////////////////////////////////

unsigned long startTime, endTime, duration; //отладка. Замер времени выполнения кода

void rpm() {
  valFan++;
}

void setup() {

  Serial.begin(9600); //отладка

  attachInterrupt(FAN_THM, rpm, CHANGE); //тахометр
  pinMode(FAN_VOL, OUTPUT); //шим
  PWM_prescaler(FAN_VOL, 1);//шим+

  pressSensor.begin(PRESSURE_DOUT_PIN, PRESSURE_SCK_PIN);

  lcd.begin();
  lcd.backlight();

  eb.setEncType(EB_STEP4_LOW);
  eb.setFastTimeout(50);
  eb.counter = 0;

  EEPROM.get(1024, settingsSet);
  EEPROM.get(0, savedArray0);
  EEPROM.get(4, savedArray1);
  EEPROM.get(10, savedArray2);
  EEPROM.get(14, savedArray3);
  EEPROM.get(20, savedArray4);

  if (settingsSet == 1) {
    memcpy(valMode0, savedArray0, sizeof(valMode0));
    memcpy(valMode1, savedArray1, sizeof(valMode1));
    memcpy(valMode2, savedArray2, sizeof(valMode2));
    memcpy(valMode3, savedArray3, sizeof(valMode3));
    memcpy(valMode4, savedArray4, sizeof(valMode4));
  }

  printMainDisplay();
}

void loop() { /////////////////////////////////////////////////////////////////////// LOOP ///////////////////////////////////////////////////
  //startTime = millis();
  readSetSensors();  //читаем сенсоры. установка скорочти кулера
  //runPump();  //помпа

  if (millis() - LastUpdateScreen > 1000) { //обновляем температуры на экране
    LastUpdateScreen = millis();
    printStatusBar3();
    printStatusBar4();
  }

  ////////////////// События главного меню (закончено)//////////////////
  eb.tick();
  if (eb.turn()) {  //перемещение курсора по списку режимов
    if (eb.left()) arrowPos--;
    if (arrowPos == -1) debugPage();
    if (eb.right()) arrowPos++;
    arrowPos = constrain(arrowPos, 0, 4);  // ограничиваем

    lcd.clear();
    printMainDisplay();
    printStatusBar3();
    printStatusBar4();
    LastUpdateScreen = millis();
  }

  if (eb.click()) {  //включить режим под курсором
    if (mode == 0 && arrowPos == 0) {mode = -1;} else {mode = arrowPos;};
    applyModeSettings();
    lcd.clear();
    printMainDisplay();
    printStatusBar3();
    printStatusBar4();
    LastUpdateScreen = millis();
  }

  if (eb.hold()) {  //войти в настройки режима под курсором

    setupMode();  //->

    //updateEEPROM();

    lcd.clear();
    printMainDisplay();
    printStatusBar3();
    printStatusBar4();
    LastUpdateScreen = millis();
  }

  //endTime = millis();
  //duration = endTime - startTime;
  //Serial.println(duration);
  //delay(1000);
}
void updateEEPROM() { //сохранение настроек в EEPROM

  switch (arrowPos) {
    case 0:
      EEPROM.put(0, valMode0);
      break;
    case 1:
      EEPROM.put(4, valMode1);
      break;
    case 2:
      EEPROM.put(10, valMode2);
      break;
    case 3:
      EEPROM.put(14, valMode3);
      break;
    case 4:
      EEPROM.put(20, valMode4);
      break;
  }

  if (settingsSet != 1) {
    EEPROM.put(1024, 1); //флаг того, что настройки сохранены
  }
}

void debugPage() {  //дополнительная страница с непосредственной настройкой скорости кулера и помпы

  arrowPosD = 0;
  lcd.clear();
  printDebugPage();

  while (1) {  
    readSetSensors();
    if (millis() - LastUpdateScreen > 1000) { //обновляем температуры на экране
      printDebugPage();
      LastUpdateScreen = millis();
    }
   
    eb.tick();

    if (eb.turn()) {  //перемещение курсора по списку режимов
      if (eb.left()) arrowPosD--;
      if (arrowPosD == -1) return;
      if (eb.right()) arrowPosD++;
      arrowPosD = constrain(arrowPosD, 0, 1);  // ограничиваем
      printDebugPage();
    }
    if (eb.click()) {
      bool exit = 0;
      while (exit == 0) {
        readSetSensors();
        if (millis() - LastUpdateScreen > 1000) { //обновляем температуры на экране
          printDebugPage();
          LastUpdateScreen = millis();
        }

        blinkDebugArrow();
        eb.tick();

        if (eb.turn()) {
          if (arrowPosD == 0) {
            if (eb.left()) needFan = needFan - 500;
            if (eb.right()) needFan = needFan + 500;
            needFan = constrain(needFan, 0, 9500);
          } else if (arrowPosD == 1) {
            if (eb.left()) oil = oil-10;
            if (eb.right()) oil = oil+10;
            oil = constrain(oil, 0, 100);
          }
          printDebugPage();
          LastUpdateScreen = millis();
        }
        if (eb.click()) exit=1;
      }
    }
  }
}
void printDebugPage() { //выводит содержимое странички DEBUG

  // выводим стрелку
  switch (arrowPosD) {
    case 0:
      lcd.setCursor(0, 1); lcd.write(126); lcd.setCursor(0, 2); lcd.print(" ");
      break;
    case 1:
      lcd.setCursor(0, 1); lcd.print(" "); lcd.setCursor(0, 2); lcd.write(118);
      break;
  }

  lcd.setCursor(5, 0); lcd.print("DEBUG PAGE");
  lcd.setCursor(1, 1); lcd.print("NeedF:");
  if (needFan < 1000) lcd.print(" ");
  if (needFan < 500) lcd.print("  ");
  lcd.print(needFan);

  lcd.setCursor(3, 2);
  lcd.print("PWM:");
  if (pwm < 100) lcd.print(" ");
  if (pwm < 10) lcd.print(" ");
  lcd.print(pwm); lcd.setCursor(12, 2);

  printStatusBar4();
}
void blinkDebugArrow() { //мерцает стрелкой на регулируемом пункnе настроек DEBUG
  if (millis() - LastUpdateScreen > 600) {
    if (arrowPosD == 0) {
      lcd.setCursor(0, 1); lcd.print("      ");
    } else if (arrowPosD == 1) {
      lcd.setCursor(0, 2); lcd.print(" ");
      lcd.setCursor(0, 3); lcd.print(" ");
    }
  }
}

void setupMode() {  //вход в настройки режима под стрелкой с главного экрана

  const char **selSet = setNames[arrowPos];
  int *selVal = valModes[arrowPos];
  bool needSave = 0;
  LastHumanActive = millis();
  arrowPosM = 0;

  lcd.clear();
  printModeSeting(arrowPosM);

  while (1) {

    readSetSensors();
    //runPump();
    if (millis() - LastUpdateScreen > 1000) {
      LastUpdateScreen = millis();
      printStatusBar4();
    }
   
    eb.tick();

    if (eb.turn()) {
      if (eb.right()) arrowPosM++;
      if (eb.left()) arrowPosM--;
      arrowPosM = constrain(arrowPosM, 0, countSetings[arrowPos]-1);

      int value = selVal[arrowPosM];
      if (value > 1000) {
        if (eb.rightH()) value=value+100;
        if (eb.leftH()) value=value-100;
      } else {
        if (eb.rightH()) value++;
        if (eb.leftH()) value--;
      }
       
      selVal[arrowPosM] = value;
     
      printModeSeting(0);
      printStatusBar4();
     
      LastHumanActive = millis();

      if (eb.pressing() && mode == arrowPos) needSave = 1;
    }

    if (eb.click()) { //кликнул и кручу
      bool exit = 0;
      printModeSeting(1);
      while (!exit) {
        //runPump();
        eb.tick();
        if (eb.turn()) {
          int value = selVal[arrowPosM];
          if (eb.right()) value++;
          if (eb.left()) value--;
          if (eb.rightH()) value = value+100;
          if (eb.leftH()) value = value-100;
          selVal[arrowPosM] = value;
          printModeSeting(1);

          if (mode != 4 && arrowPosM == 0) {oil = value;}       ///////////////////////////ВОТ ЭТО ПРОВЕРИТЬ ЧТО ЭТО ТАКОЕ???? А, это типа установка настроек...
          //setPump();

          LastHumanActive = millis();
        }

        if (eb.click() || millis() - LastHumanActive > waitingHuman) exit = 1;
      }
      printModeSeting(0);
      if (mode == arrowPos) needSave = 1;
    }

    if (needSave) applyModeSettings();
    needSave = 0;

    if (eb.hold() || millis() - LastHumanActive > waitingHuman) return;
  }
}
void printModeSeting(bool choice) {  //рисует список настроек режима и стрелку

  const char **selSet = setNames[arrowPos];
  int *selVal = valModes[arrowPos];

  lcd.clear();

  for (byte i = 0; i < countSetings[arrowPos]; i++) {  // для всех строк
    lcd.setCursor(0, i);          // курсор в начало

    // если курсор находится на выбранной строке
    if (arrowPosM == i) lcd.write(126);  // рисуем стрелку
    else lcd.write(32);                  // рисуем пробел

    // выводим имя и значение пункта меню
    lcd.print(selSet[i]);
    if (choice == 1 && arrowPosM == i) {lcd.print(":"); lcd.write(126);} else {lcd.print(": ");}
    lcd.print(selVal[i]);
  }
}

void applyModeSettings() {  //установка переменных из массива с настройками режима

  if (mode == arrowPos || mode == -1) {
    int *selVal = valModes[arrowPos];
    switch (mode) {
      case -1 :
        oil = 0;
        needFan = 0;
        timeoutMode = 0;
        break;
      case 0: // Pump
        oil= selVal[0];
        needFan = 0;
        if (selVal[1] > 0) {timeoutMode = selVal[1] * 1000;} else {timeoutMode = 0;};
        break;
      case 1: //start
        oil= selVal[0];
        needFan = selVal[1];
        if (selVal[2] > 0) {timeoutMode = selVal[2] * 1000*60;} else {timeoutMode = 0;};
        break;
      case 2: //work
        oil= selVal[0];
        needFan = selVal[1];
        break;
      case 3: //eco
        oil= selVal[0];
        needFan = selVal[1];
        //needTemp = selVal[2];
        break;
      case 4: //off
        oil= 0;
        needFan = selVal[0];
        if (selVal[1] > 0) {timeoutMode = selVal[1] * 1000*60;} else {timeoutMode = 0;};
        break;
    }
  }

  debug();
}

void readSetSensors() {  //чтение сенсоров раз в секунду и установка значений выходов

  if (millis() - LastTimeReadSensors > 1000) {
    if (millis() - LastTimeReadSensors > 1100) {
      //Serial.println("пропуск расчета тахометра");
      valFan = 0;
      LastTimeReadSensors = millis();
    } else {
      //Serial.println("успешный расчет тахометра");
      thm = (valFan * 60) / 4;  //НЕ среднее из двух
      valFan = 0;
      LastTimeReadSensors = millis();
      //установка скорости вентилятора
      if (thm + 700 < needFan) pwm=pwm+20;
      else if (thm + 200 < needFan) pwm=pwm+3;
      else if (thm + 35 < needFan) pwm++;

      if (thm - 1000 > needFan) pwm=pwm-20;
      else if (thm - 200 > needFan) pwm=pwm-5;
      else if (thm - 35 > needFan) pwm--;

      pwm = constrain(pwm, 0, 255);
      analogWrite(FAN_VOL, pwm);
    }
   
    oil = constrain(oil, 0, 100);
    //analogWrite(PUMP_PIN, map(oil, 0, 100, 0, 255));   помпа

    if (pressSensor.is_ready()) {
      pressure = pressSensor.read();
    }

    //считать остальные датчики
    tSmoke = 0;
    tIn = 0;
    tOut = 0;  
  }
}

void printMainDisplay() { //отрисовка списка режимов на главном экране
  lcd.setCursor(2, 0); lcd.print("PumpOIL"); lcd.setCursor(13, 0); lcd.print("START");
  lcd.setCursor(2, 1); lcd.print("WORK"); lcd.setCursor(9, 1); lcd.print("ECO"); lcd.setCursor(15, 1); lcd.print("OFF");
  // выводим стрелку
  switch (arrowPos) {
    case 0:
      lcd.setCursor(0, 0);
      break;
    case 1:
      lcd.setCursor(11, 0);
      break;
    case 2:
      lcd.setCursor(0, 1);
      break;
    case 3:
      lcd.setCursor(7, 1);
      break;
    case 4:
      lcd.setCursor(13, 1);
      break;
    case 5:
      lcd.setCursor(8, 2);
      break;
  }
  lcd.write(126); lcd.write(126);

  //выделение активного режима
  if (mode >= 0) {
    switch (mode) {
      case 0:
        lcd.setCursor(1, 0); lcd.write(42); lcd.setCursor(9, 0);
        break;
      case 1:
        lcd.setCursor(12, 0); lcd.write(42); lcd.setCursor(18, 0);
        break;
      case 2:
        lcd.setCursor(1, 1); lcd.write(42); lcd.setCursor(6, 1);
        break;
      case 3:
        lcd.setCursor(8, 1); lcd.write(42); lcd.setCursor(12, 1);
        break;
      case 4:
        lcd.setCursor(14, 1); lcd.write(42); lcd.setCursor(18, 1);
        break;
    }
    lcd.write(42);
  }
}
void printStatusBar3() {  //отрисовка третей строки с температурами
  lcd.setCursor(0, 2); lcd.print("Sm:");lcd.print(tSmoke);
  lcd.setCursor(7, 2); lcd.print("In:");lcd.print(tIn);
  lcd.setCursor(14, 2); lcd.print("Ou:");lcd.print(tOut);
}
void printStatusBar4() {  //отрисовка статус бара (помпа, кулер, часы)
  lcd.setCursor(0, 3); //показания помпы
  lcd.print("P:");lcd.print((oil == 100) ? 99 : oil);

  lcd.setCursor(5, 3); lcd.print("F:"); //показания оборотов кулера
  if (thm > 0 && thm < 10) {lcd.print("   ");}
  else if (thm < 100) {lcd.print("  ");}
  else if (thm < 1000) {lcd.print(" ");};
  lcd.print(constrain(thm, 0, 9999));

  //вывод часов
  uint32_t sec = millis() / 1000ul;      // полное количество секунд
  int timeHours = (sec / 3600ul);        // часы
  int timeMins = (sec % 3600ul) / 60ul;  // минуты
  int timeSecs = (sec % 3600ul) % 60ul;  // секунды
  lcd.setCursor(12, 3);
  if (timeHours < 10) { lcd.print(0); } lcd.print(timeHours); lcd.print(":");
  if (timeMins < 10) { lcd.print(0); } lcd.print(timeMins); lcd.print(":");
  if (timeSecs < 10) { lcd.print(0); } lcd.print(timeSecs);
}

//+удалить
void debug() { //тупо отладка в монитор порта
    Serial.print("arrowPos: ");
    Serial.print(arrowPos);
    Serial.print(" arrowPosM: ");
    Serial.print(arrowPosM);
    Serial.print(" arrowPosD: ");
    Serial.print(arrowPosD);
    Serial.print(" mode: ");
    Serial.print(mode);
    Serial.print(" thm: ");
    Serial.print(thm);
    Serial.print(" pwm: ");
    Serial.print(pwm);
    Serial.print(" oil: ");
    Serial.print(oil);
    Serial.print(" needFan: ");
    Serial.print(needFan);
    Serial.print(" timeoutMode: ");
    Serial.print(timeoutMode);
    Serial.print(" settingsSet: ");
    Serial.print(settingsSet);


    Serial.println();

}