Веранда с рассадой жила у меня по одному термостату до апрельской недели, когда ночью упало до плюс восьми, а днём на солнце вышло двадцать девять. Ночью обогреватель спасал, днём я открывал дверь и обмахивался ладонью — и в какой-то момент решил расширить схему честно: второе реле, второй канал, кулер. Скетч собрал за десять минут по образцу первого: «если холодно — грей» и «если жарко — охлаждай» двумя независимыми блоками. Через три дня лог показали мне то, что я не хотел видеть: в переходные часы каналы включались по очереди, как будто дрались за право командовать верандой.
Конкретнее: охладитель включался на 23,5, температура сползала вниз, пересекала порог тепла — включался обогреватель, воздух прогревался обратно до порога холода — снова кулер. Каждый цикл — щелчок реле, пуск мотора, полградуса туда-сюда. Программа делала ровно то, что я написал: два термостата на одном датчике и ноль договорённостей между ними.
В этом уроке разберём, сколько градусов обязано лежать между каналами и почему это число нельзя выбрать «на глаз», как учесть инерцию нагревателя, что даёт сезонная блокировка и почему состояние каналов обязано быть общим, а не у каждого своим. Одиночный термостат с гистерезисом разобран в уроке про гистерезис реле, здесь — следующий этаж: два канала, которые должны уметь жить вместе.
Разнос каналов: мёртвая зона, которая стоит денег
Разнос каналов — это расстояние между уставкой выключения тепла и уставкой включения холода. В профессиональной климатике за ним закрепилось название мёртвой зоны, и число там не случайное: у большинства термостатов в автоматическом режиме дефолтный разнос между уставками тепла и холода — два-три градуса по Фаренгейту, то есть около одного-полутора градусов Цельсия, а монтажники советуют расширять его до четырёх-пяти фаренгейтов (два-три по Цельсию) именно ради того, чтобы система не скакала между режимами. Владельцы умных термостатов формулируют тот же опыт без терминов: при слишком близких уставках прибор переходит из нагрева в охлаждение и обратно без конца, воюя сам с собой.
Моя апрельская веранда повторила этот сценарий буквально. Рабочая арифметика выглядит так: при центральной уставке 21,5 канал тепла включается ниже 19,5 и выключается выше 20,5, канал холода включается выше 23,5 и выключается ниже 22,5. Между выключением тепла и включением холода лежат три градуса тишины, внутри каждого канала — своё окно в один градус. Шум DHT22 между опросами — две-четыре десятых градуса, и он физически не может перепрыгнуть зону: чтобы сменился режим, воздух обязан пройти её целиком. Окно одиночного канала я раскладывал в уроке про клацанье реле — здесь та же механика, но с оговоркой, что окон уже два и между ними обязана лежать тишина.
Отсюда правило, которое стоит записать отдельно: разнос каналов не меньше ширины окна тепла плюс ширины окна холода плюс запас на шум сенсора. Для пары DHT с окнами по одному градусу это даёт минимум два с половиной; я ставлю три. Сузить захочется на двухсотграммовой коробке с датчиком, где климат реагирует мгновенно, — и там же узкий разнос не нужен вовсе, потому что микрообъём держит температуру без борьбы каналов.
| Температура (центр 21,5) | Канал тепла | Канал холода | Что происходит |
|---|---|---|---|
| ниже 19,5 | включается | стоит | холод победил: обогреватель в деле до своего окна |
| 20,5-22,5 | выключен | выключен | мёртвая зона: три градуса тишины, никто не щёлкает |
| выше 23,5 | стоит | включается | жара победила: охладитель работает до своего окна |
| нет показаний | стоит | стоит | без показаний ни один канал не решает, пометка в Serial |
Инерция, пауза смены канала и сезонная блокировка
Нагреватель после выключения не остывает мгновенно: масляный радиатор отдаёт запасённое тепло ещё десятки минут и дожимает комнату на градус-полтора. Значит, верхнюю границу тепла честно ставить с упреждением — выключать не на границе зоны, а на полградуса раньше, навстречу остаточному теплу. У охладителя инерция зеркальная: компрессорный кондиционер после остановки ещё несколько минут дует холодным. Упреждение — это полградуса, взятые у физики, а не у эстетики чисел.
Вторая пауза — между сменой каналов. В климатике после остановки компрессора держат три-пять минут минимального простоя, прежде чем разрешить повторный пуск: короткие циклы убивают компрессор, и таймер простоя там стоит штатно. Верандный вентилятор не компрессор, но правило бесплатное: если только что работал обогреватель, а температура пролетела зону вверх, кулеру назначается пауза на пару минут. Реализуется одной меткой millis — когда канал выключился, запомнили время, и второй канал разрешён не раньше, чем через 120 секунд. Механика таймеров без блокировки цикла разобрана в уроке про millis вместо delay, здесь она работает в паре с автоматом.
Третья защита — сезонная. Летом канал тепла разъединяется физически: вилка обогревателя из розетки, и вот тут программная блокировка перестаёт быть перестраховкой. Кнопка «лето» запрещает каналу тепла включаться, даже если показание провалилось ниже порога: ночной провал до шестнадцати в июле — не повод щёлкать реле, к которому ничего не подключено. Двойная защита — железная и логическая — стоит ноль рублей, а экономит утренние поиски того, «кто греет». И сезон, и уставку стоит хранить так, чтобы перезагрузка платы их не потеряла: приём с порогами в EEPROM переносится сюда без изменений.
#include "DHT.h" DHT dht(2, DHT22); const int RELAY_HEAT = 7; // реле обогревателяconst int RELAY_COOL = 8; // реле охладителяconst int POT = A0; // уставка центра: 18..28 градусаconst int BTN_SUMMER = 3; // нажата - лето, канал тепла запрещён const float BAND = 2.0; // половина разноса каналовconst float HYST = 1.0; // окно каждого каналаconst unsigned long PAUSE_MS = 120000; // пауза смены канала, 2 минуты int rezim = 0; // 0 - тишина, 1 - греем, 2 - холодимunsigned long lastStop = 0; // когда выключился последний канал void setup() { Serial.begin(9600); pinMode(RELAY_HEAT, OUTPUT); pinMode(RELAY_COOL, OUTPUT); pinMode(BTN_SUMMER, INPUT_PULLUP); digitalWrite(RELAY_HEAT, LOW); digitalWrite(RELAY_COOL, LOW); dht.begin(); Serial.println("dva kanala: teplo 19.5-20.5, holod 22.5-23.5");} void loop() { float c = map(analogRead(POT), 0, 1023, 180, 280) / 10.0; float t = dht.readTemperature(); bool summer = digitalRead(BTN_SUMMER) == LOW; if (t > 100.0 || t < -40.0) { Serial.println("nan: sbor ne otvetil - oba kanala stoyat"); digitalWrite(RELAY_HEAT, LOW); digitalWrite(RELAY_COOL, LOW); rezim = 0; delay(2000); return; } bool coolAllowed = millis() - lastStop > PAUSE_MS; if (rezim == 0) { if (t < c - BAND - HYST && !summer) { rezim = 1; // ниже нижней границы - греем } else if (t > c + BAND + HYST && coolAllowed) { rezim = 2; // выше верхней - холодим } } else if (rezim == 1) { if (t > c - BAND + HYST) { // дотянули до своей верхней границы rezim = 0; lastStop = millis(); } } else if (rezim == 2) { if (t < c + BAND - HYST) { rezim = 0; lastStop = millis(); } } if (rezim == 1 && summer) { // сезонный запрет сильнее автомата rezim = 0; lastStop = millis(); } digitalWrite(RELAY_HEAT, rezim == 1 ? HIGH : LOW); digitalWrite(RELAY_COOL, rezim == 2 ? HIGH : LOW); Serial.print("t="); Serial.print(t, 1); Serial.print(" c="); Serial.print(c, 1); Serial.print(" -> "); if (rezim == 1) Serial.println("greem"); else if (rezim == 2) Serial.println("holodim"); else Serial.println("tihina"); delay(2000);}Главное в этом скетче — переменная rezim одна на оба канала. Одновременность исключена не уговорами в комментариях, а структурой: значения «греем» и «холодим» не существуют одновременно, переходы идут только через ноль. Каждый переход разрешён на своей границе, а после выключения канала метка lastStop даёт второму каналу две минуты тишины — тот самый таймер простоя, только бесплатный.
Пустое показание здесь решается жёстче, чем в одиночном термостате: nan сбрасывает режим в тишину и гасит оба реле, потому что без показаний ни один канал не имеет права решать. Откуда nan берётся и что он значит для сенсора — разложено в уроке про диагностику nan; правило «не знаю — оба стоят» стоит три строки и спасает от гонки слепых каналов. Этот же автомат с режимами hold, heat и alarm в развёрнутом виде собран в итоговом проекте раздела — когда освоите двухканальную логику, тот проект станет шагом вбок, а не вверх.
Проверка автомата на столе занимает вечер и стоит ноль рублей. Снимите реле с нагрузки, проведите датчик рукой от холодного к тёплому мимо обеих границ и смотрите в лог: переходы обязаны идти по одному, без череды greem-holodim-greem. Полезнее всего счётчик переключений за сутки — скетч печатает его перед сном в Serial, и у здорового двухканального автомата на веранде набирается десятки, а не сотни: сотни за сутки означают, что кто-то из каналов дребезжит на своей границе, и окно этого канала стоит расширить.
Заодно поймайте на столе паузу смены канала: переведите температуру через всю мёртвую зону быстро, феном, — второй канал включится только после таймера простоя, и в логе между выключением одного и включением другого будет видна пауза. На настоящей плате с компрессором эта пауза — не иллюстрация, а срок жизни оборудования.
- 1. Что обязано лежать между уставкой выключения тепла и уставкой включения холода?
- 2. Почему компрессорному кондиционеру дают три-пять минут покоя после остановки?
- 3. Как в скетче гарантируется, что обогрев и охлаждение никогда не включатся вместе?
- 4. Что делает сезонная кнопка «лето»?
- 5. Граница выключения тепла 20,5, включения холода 23,5, шум датчика плюс-минус 0,4. Насколько надёжна такая мёртвая зона?
- Разнос каналов — не меньше суммарных окон тепла и холода плюс запас на шум: для DHT с окнами по одному градусу — минимум три.
- В климатической автоматике дефолтный разнос один-полтора градуса, и его расширяют до двух-трёх именно против пинг-понга режимов.
- Одна переменная режима на оба канала исключает одновременную работу структурно, а не уговорами.
- Упреждение на полградуса и таймер простоя канала — плата за инерцию нагревателя и компрессора, у компрессора это три-пять минут.
- Сезонная блокировка запрещает каналу тепла включаться физически вынутой вилкой и логикой одновременно; уставку и сезон хранят в EEPROM.
Частые вопросы по теме урока
- Что обязано лежать между уставкой выключения тепла и уставкой включения холода?
- Разнос каналов — это цена отсутствия пинг-понга: окна тепла и холода плюс запас на шум. В климатике дефолт около одного-полутора градусов и его советуют расширять до двух-трёх. Мёртвая зона шириной хотя бы в суммарные окна обоих каналов плюс запас на шум
- Почему компрессорному кондиционеру дают три-пять минут покоя после остановки?
- Минимальное время простоя — штатная защита от коротких циклов: частые пуски под остаточным давлением бьют током по сети и механике. В нашем скетче ту же роль играет метка lastStop. Чтобы не допускать коротких циклов: повторный пуск идёт с ударным током и изнашивает компрессор
- Как в скетче гарантируется, что обогрев и охлаждение никогда не включатся вместе?
- Состояние общее, переходы идут через тишину. Два независимых блока if — классический просчёт, при котором каналы сменяют друг друга каждый цикл переходной погоды. Одной переменной режима на оба канала: значения «греем» и «холодим» не существуют одновременно
- Что делает сезонная кнопка «лето»?
- Сезонный запрет сильнее автомата: если вилка обогревателя вынута, программа не должна иметь права его «включить». Ночной провал температуры летом не щёлкает пустое реле. Запрещает каналу тепла включаться, даже если показание провалилось ниже порога
- Граница выключения тепла 20,5, включения холода 23,5, шум датчика плюс-минус 0,4. Насколько надёжна такая мёртвая зона?
- Три градуса зоны против двух-четырёх десятых шума между опросами — запас восьмикратный. Чтобы режим сменился, воздух обязан пройти зону целиком, а не дёрнуться на границе. Надёжна: шум втрое меньше зоны, перепрыгнуть её показание не может