Выносной проводной термостат для газового котла.

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Простенький скетч для газового котла, у меня в деревне стоит BAXI, гистериз поставил +- 0.5 градуса, вполне комфортно, настраивается в скетче.
Перемычка от котла подключается к нормально замкнутым контактам реле, на случай внезапного отключения МК и/или проблем с термодатчиком, отопление должно работать в любом случае.
Сама плата была снят с ранее используемого устройства, поэтому оставил два реле и в схеме и в логике, выходные контакты реле запараллелил.
Управление одной кнопкой - циклическое переключение температуры контроля, пределы регулировки настраиваются в скетче.
На кнопке так же индикаторный светодиод - загорается когда включены - котел соотвественно отключен.

// heatterm - выносной термостат для газового котла

#include <avr\wdt.h> // стандартная библиотека работы с WDT
#include <OneWire.h>
#include "DallasTemperature.h"
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>

#define deltaTempControl ((signed short)5) // дельта в десятых долях градуса +- от установленной температуры вкл/откл реле котла
// устанавливать строго больше 0.5 градуса (т е 5 значение) - больше точности температурного датчика

signed short controlHeaterTemp; // температура контроля помещения
#define pos_eeprom_save_temp ((word)156) // позиция EEPROM где храниться температура контроля
#define min_lim_temp ((signed short)180) // минимальная возможная темп установки контроля
#define max_lim_temp ((signed short)280) // максимальная возможная темп установки контроля

#define pin_key_control ((unsigned char)A3) // пин кнопки изменения контрольной температуры
#define READKEYCTRL !digitalRead(pin_key_control) // макрос чтения кнопки управления

#define INLEDON digitalWrite(LED_BUILTIN,HIGH) // максрос зажеч встроенный на плате светодиод
#define INLEDOFF digitalWrite(LED_BUILTIN,LOW) // максрос погасить встроенный на плате светодиод

#define pin_led_status ((unsigned char)A2) // пин статусного светодиода
#define STONLED digitalWrite(pin_led_status,HIGH) // максрос зажеч светодиод - реле термостата включено - котел не работает
#define STOFFLED digitalWrite(pin_led_status,LOW) // максрос погасить светодиод - реле термостата отключено - работает котел

#define pin_relay1 ((unsigned char)4) // пин 1 реле
#define pin_relay2 ((unsigned char)5) // пин 2 реле

unsigned char flagIsHeaterON; // флаг что котел включен
unsigned long timerServiceRelay; // таймер профилактического переключения реле
unsigned long currentMillis; // глобальный миллисекундный таймер

// Data wire is plugged into port on the Arduino
#define ONE_WIRE_BUS A1 // пин куда подключен DS18B20
// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
// On an arduino UNO:       A4(SDA), A5(SCL)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void saveControlTemp(signed short locTemp) { // запись в EEPROM температуры контроля
  EEPROM.write(pos_eeprom_save_temp, (unsigned char) (locTemp / 10));
}

void loadControlTemp(void) { // чтение из EEPROM температуры контроля
  unsigned char lb = EEPROM.read(pos_eeprom_save_temp);
  signed short gtemp = lb * 10; // преобразуем в знаковый тип
  if ((gtemp >= min_lim_temp) && (gtemp <= max_lim_temp)) { // если значение в норме
    controlHeaterTemp = gtemp; // считали знгачение в переменную
  } else { // ошибка
    controlHeaterTemp = max_lim_temp - 30; // присваиваем максимальное значение контроля минус три градуса
    saveControlTemp(controlHeaterTemp); // и записываем его в EEPROM
  }
}

void HEATERON(void) { // отключение реле  - котел работает
  digitalWrite(pin_relay1, HIGH);
  delay(20);
  digitalWrite(pin_relay2, HIGH);
  STOFFLED;
  flagIsHeaterON = 1;
}

void HEATEROFF(void) { // включение реле  - не работает котел
  digitalWrite(pin_relay1, LOW);
  delay(20);
  digitalWrite(pin_relay2, LOW);
  STONLED;
  flagIsHeaterON = 0;
  timerServiceRelay = currentMillis; // сбросим таймер профилактики реле
}

// инициализация WDT (код из даташита + из форума)
uint8_t mcusr_mirror __attribute__ ((section (".noinit")));
void get_mcusr(void) \
__attribute__((naked)) \
__attribute__((used)) \
__attribute__((section(".init3")));
void get_mcusr(void)
{
  mcusr_mirror = MCUSR;
  MCUSR = 0;
  wdt_disable();
}

void setup() {
  MCUSR = 0; // отключение WDT
  wdt_disable(); // отключение WDT
  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
  display.display();
  delay(1000); // Pause for 1 second
  display.clearDisplay(); // Clear the buffer
  display.display();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.print("Start!");
  display.display();
  // настройка пинов МК
  pinMode(LED_BUILTIN, OUTPUT);
  INLEDOFF;
  pinMode(pin_key_control, INPUT_PULLUP);
  pinMode(pin_led_status, OUTPUT);
  HEATERON(); // инициализируем уровень на выходных пинов реле до того как установили режим выхода, чтобы при включении устройства реле не щелкало
  pinMode(pin_relay1, OUTPUT);
  pinMode(pin_relay2, OUTPUT);
  // put your setup code here, to run once:
  loadControlTemp(); // прочитаем из EEPROM температуру контроля
  wdt_enable(WDTO_1S); // включение WDT
}

void loop() {
  wdt_reset(); // сброс WDT
  currentMillis = millis(); // глобальный миллисекундный таймер
  static unsigned dispInSetMode = 0; // дисплей в режиме настройки температуры
  // put your main code here, to run repeatedly:
  static unsigned long timerGetTemp; // таймер получения температуры - каждые 2 секунды
  static unsigned char flagReqTemp = 1; // флаг запроса температуры
  if ((currentMillis - timerGetTemp) >= 1000UL) { // каждую секунду работаем с датчиком температуры
    timerGetTemp = currentMillis; // сбросили таймер
    if (flagReqTemp) { // запрос температуры
      INLEDON; // зажгли светодиод на плате
      sensors.requestTemperatures(); // Send the command to get temperatures
      flagReqTemp = 0; // сбросили флаг чтоб считать значение температуры в след шаге
    } else { // получение температуры
      flagReqTemp = 1; // установили флаг для последующего запроса DS18B20
      signed short currentTemp = (sensors.getTempCByIndex(0) * 10); // прочитали температуру с датчика и умножили на 10 чтоб с точностью до десятых хранить
      if ((currentTemp >= 50) && (currentTemp <= 350)) { // температура от 5 до 35 градосов плюс - внутри поменщения вполне реальная
        INLEDOFF; // погасили на плате светодиод
        // отображение температуры
        if (!dispInSetMode) { // если не в режиме настроек - выводим температуру
          display.clearDisplay(); // Clear the buffer
          display.setTextSize(4);
          display.setCursor(0, 0);
          display.print(currentTemp / 10);
          display.print(".");
          display.print(currentTemp % 10);
          display.print("`");
          display.display();
        }
        // начала котроля/проверки управлением
        static unsigned char modeGisteris = 0; // текущий режим работы 0 - начальный режим, котел включен, реле отключено, ждем как повысится температура
        if (modeGisteris) { // режим остывания, ждем как температура упадет
          if (currentTemp <= (controlHeaterTemp - deltaTempControl)) { // остыло помещение
            HEATERON(); // включили котел
            modeGisteris = 0; // переводим в режим нагрева
          }
        } else { // режим нагрева, начальный режим, ждем как температура повысится
          if (currentTemp >= (controlHeaterTemp + deltaTempControl)) { // нагрелось помещение
            HEATEROFF(); // отключили котел
            modeGisteris = 1; // переводим в режим остывания
          }
        }
        // окончание управления
      } else { // ошибочная температура или датчик дохлый/отсутствует
        HEATERON(); // в любом случае отключили реле - передали управление котлу
        INLEDOFF; delay(200); INLEDON; delay(200); INLEDOFF; // моргнули чтоб понять что проблема - для визуального контроля
        delay(1000); // сброс всего устройства - что то не так с DS18B20
      }
    }
  }
  // работаем с кнопкой контроля/дисплеем
  static unsigned char lastValuePin = 0; // предыдущее значение пина кнопки
  unsigned char currentPinKey = READKEYCTRL; // читаем текущее значение пина кнопки
  static unsigned long timerKeyDown; // таймер антидребезга кнопки и/или следующего нажатия
  static unsigned char flagWaitDeb = 0; // флаг что было нажание и ждем время антидребезга для проверки состояния пина
  static unsigned char flagNextPress = 0; // флаг что кнопка нажималась и ждем время до след нажатия
  static unsigned long timerOptionsMode; // таймер не нажатия кнопок для выхода из режима настроек
  if (flagNextPress) { // было нажатие - не реагируем пока не закончиться время
    if ((currentMillis - timerKeyDown) >= 100UL) { // ждем чтоб было не более 10 нажатий в секунду
      flagNextPress = 0; // сброс флага - можно ждать кнопку
    }
  } else { // не было нажатий - ждем кнопку
    if (flagWaitDeb) { // если сработала кнопка
      if ((currentMillis - timerKeyDown) >= 20UL) { // ждем время антидребезга
        flagWaitDeb = 0; // сбросили флаг антидребезга
        if (currentPinKey) { // если кнопка так и осталась нажатой
          timerKeyDown = currentMillis; // сбросили таймер
          flagNextPress = 1; // установили флаг, след нажатие может произойти только черех Н время
          // обработка кнопки нажатие
          timerOptionsMode = currentMillis; // сбросим таймер нахождения в режиме настроек
          if (dispInSetMode) { // если мы уже в режиме настройки температуры
            if (controlHeaterTemp >= max_lim_temp) { // если дощелкали до максимума
              controlHeaterTemp = min_lim_temp; // присваиваем минимальную температуру
            } else { // можно увеличивать
              controlHeaterTemp += 10; // увеличили на градус
            }
            saveControlTemp(controlHeaterTemp); // сохранили изменения в EEPROM
          } else { // были в режиме отображения текущей температуры
            dispInSetMode = 1; // переходим в режим настройки
          }
          display.clearDisplay(); // Clear the buffer
          display.setTextSize(4);
          display.setCursor(0, 0);
          display.print(" ");
          display.print(controlHeaterTemp / 10); // вывели текущую температуру контроля
          display.display();
          // конец обработки нажатия
        } else { // отжали кнопку
          flagNextPress = 0; // ждем след нажатия
        }
      }
    } else { // не нажималась кнопка
      if ((currentPinKey) && (currentPinKey != lastValuePin)) { // нажали кнопку
        timerKeyDown = currentMillis; // сбросили таймер
        flagWaitDeb = 1; // поставили флаг антидребезка
      }
    }
  }
  lastValuePin = currentPinKey; // всегда предыдущему значению пина присваиваем текущее
  if (((currentMillis - timerOptionsMode) >= 2000UL) && (dispInSetMode)) { // если мы в режиме настроек не нажимали кнопку
    dispInSetMode = 0; // переходим в режим отображения температуры
  }
  // поскольку реле по несколько часов будет во включенном состоянии, необходимо периодически перещелкивать им
  // что бы контакты не залипали и пружинки не уставали
  static unsigned char currentServiceRelay = 0; // текущее реле
  if (!flagIsHeaterON) { // есле реле включены
    if ((currentMillis - timerServiceRelay) >= 900000UL) { // прошло 15 минут
      timerServiceRelay = currentMillis; // сбросим таймер
      if (currentServiceRelay) { // по очереди переключаем реле
        currentServiceRelay = 0; // потом щелкаем другим реле
        digitalWrite(pin_relay1, HIGH);
        delay(20);
        digitalWrite(pin_relay1, LOW);
      } else {
        currentServiceRelay = 1; // потом щелкаем другим реле
        digitalWrite(pin_relay2, HIGH);
        delay(20);
        digitalWrite(pin_relay2, LOW);
      }
    }
  }
  // end loop
}

// Скетч использует 17040 байт (52%) памяти устройства. Всего доступно 32256 байт.
// Глобальные переменные используют 431 байт (21%) динамической памяти, оставляя 1617 байт для локальных переменных. Максимум: 2048 байт.

---

ShAlex13
Offline
Зарегистрирован: 19.01.2018

Куда и как крепили датчик температуры?

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

ShAlex13 пишет:

Куда и как крепили датчик температуры?

Выносной китайский DS18B20 в холодной комнате на высоте примерно 1 метр от пола, в ~40 см от стены.

 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Засунул в корпус.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

andycat пишет:

ShAlex13 пишет:

Куда и как крепили датчик температуры?

Выносной китайский DS18B20 в холодной комнате на высоте примерно 1 метр от пола, в ~40 см от стены.

В этом случае и гистерезис не нужен - после отключения котла температура еще с полчаса расти будет и наоборот )))

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

v258 пишет:

andycat пишет:

ShAlex13 пишет:

Куда и как крепили датчик температуры?

Выносной китайский DS18B20 в холодной комнате на высоте примерно 1 метр от пола, в ~40 см от стены.

В этом случае и гистерезис не нужен - после отключения котла температура еще с полчаса расти будет и наоборот )))

Ну сами попробуйте, потом нам расскажете как без гистериза термостат сделать, и да, нихрена она не растет в течении получаса после отключения котла, все выходные тестировал в реальном использовании.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

andycat, да шутю я, шутю ))

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

v258 пишет:

andycat, да шутю я, шутю ))

Начинающих вводите в заблуждение, мне то пофиг :)

 

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Ну, тут вроде готовый проект, а не просьба о помощи ))