В предыдущей статье «Погодная станция с Ethernet и планшетом в качестве устройства отображения» я рассказывал о том, как я организовал сбор, складирование, обработку и вывод метеоданных в своем доме и за его пределами. Там же было упомянуто, что принципы, заложенные в основу системы, позволяют строить нечто более широкое, чем просто сбор погодных данных, и была анонсирована статья про термостатирование. Пришло время выполнять обещание и рассказать про это самое термостатирование.
Введение
Современный загородный дом с автономным отоплением обычно предполагает наличие газового котла. В самом примитивном варианте управление отоплением осуществляется двумя способами:
- Для задания, так сказать, общего термофона в доме выставляется мощность, которую котел «закачивает» в батареи. Обычно регулировка мощности осуществляется в терминах температуры воды после нагревательного контура.
- Поскольку комнаты в доме разные (разные окна, стены, стороны света, и т.д.), топить их нужно тоже по-разному. В условиях, когда контур отопления на весь дом один, эта цель достигается регулировкой подачи воды в сами батареи.
При этом, современные котлы, как правило, поддерживают внешнее управление. Основная цель такой фичи – подключение различного рода терморегуляторов, которые позволяют выставлять мощность котла не в абстрактных градусах воды в батареях (одни и те же цифры температуры воды могут приводить к совершенно разным результатам в зависимости от внешних условий), а в терминах гораздо более понятной целевой температуры места, где установлен регулятор. «Внешнее управление» может означать все что угодно, но обычно это либо какой-то проприетарный протокол конкретного производителя техники (как, например, делает Bosch), либо одна из реализаций протокола OpenTherm.
Главная проблема упомянутых терморегуляторов для меня – цена. При стоимости котла порядка 25000 руб цена регулятора в 9000 руб кажется несколько завышенной.
В результате двухсторонних консультаций с жабой была согласована смета, в полтора порядка меньшая стоимости типичного коммерческого регулятора, после этого началась отработка вариантов реализации доморощенного регулятора.
Перед регулятором были поставлены следующие цели:
- Он должен поддерживать целевую температуру в месте, температурный датчик которого выбран в качестве контрольного, с точностью +-0.5 градуса
- Он должен позволять выставлять целевую температуру
- Он должен позволять мониторить базовые аспекты самого регулятора и котла
OpenTherm
Исследование возможностей построения началось с изучения возможностей котла по внешнему управлению. После снятия кожуха был обнаружен некий разъем, на котором висела перемычка. Изучение документации к котлу вселило надежду, что оно – и есть тот самый разъем для внешнего управления. Тут надо упомянуть особое усердие, с которым готовилась русскоязычная документация на мой итальянский котел Ferroli. В документации есть описание разъемов. Напротив интересующего меня значилась сакральная надпись «Единица среды». Интуиция настойчиво намекала, что это работа переводчиков, незнакомых с предметной областью, надо бы искать англоязычную документацию (уж на английский-то всяко должны были грамотнее перевести). После выкапывания из интернета PDF-ки с английской версией руководства к котлу все прояснилось, напротив того же разъема красовался текст «Room unit». Там же была ма-а-а-ахонькая иконка, которую при определенной доле фантазии можно было принять за логотип OpenTherm. «Это то, что нужно» — подумал я и стал планировать зверские эксперименты над котлом (за окном было -25, так что самое время было спалить электронику отопителя).
Тут надо сделать необходимый экскурс в OpenTherm. Протокол это не закрытый и не открытый. Не закрытый потому что, если у вас окажется спецификация, то никто вас преследовать не будет. Не открытый потому что никто вам спецификацию протокола просто так не даст. Вступите в OpenTherm Alliance за $$$$$$$, тогда получите доступ к телу протокола. Порывшись в глубинах интернета выцепил спецификацию OT2.2.
На физическом уровне OpenTherm – это две слаботочные линии с открытым напряжением порядка 42В. Управляющее устройство, будучи подключено к ним, может одновременно и питаться и контролировать ведомое.
Спецификация определяет два уровня протокола: OT/+ и OT/Lite.
Первый – это протокол в смысле, в котором это слово понимают большинство людей в IT. Т.е. это некий набор запросов и ответов, которые ходят между управляющим устройством (оно же master unit, оно же room unit, в нашем случае таковым является регулятор) и ведомым(оно же slave unit, это котел). Протокол делится на обязательную часть, в которую входят команды «включить/выключить горелку», «отрапортовать статус» и «установить ту самую температуру воды после контура нагрева (параметр называется setpoint)». В необязательной — куча других, начиная от управления модуляцией пламени до съема расхода газа, давления и скорости движения воды в контуре. При этом управляющее устройство управляет изменением напряжения в этих линиях, а ведомое отвечает изменением тока.
OT/Lite – это более простая штука. Если у вас не хватает ума реализовать OT/+, вы можете просто генерировать ШИМ-сигнал, в соответствии со скважностью которого котел будет управлять установкой setpoint.
Кроме того, спецификация определяет еще тестовый режим, при котором линии накоротко замкнуты. В этом режиме котел греется до тех пор, пока ему не надоест (читай, пока не будет достигнута верхняя планка температуры воды, установленная на самом котле). Так котел работает без внешнего регулятора (поэтому там при вскрытии висела перемычка).
OpenTherm, но не совсем
Сразу развею интригу и скажу, что рулить котлом по OpenTherm не получилось. Была собрана схема сопряжения и даже написана библиотека, реализующая довольно значительную часть OpenTherm, но котел упорно отказывался включаться в режим управления по OpenTherm и ничего не отвечал. Точнее сказать, микроконтроллер ничего не регистрировал в качестве ответа. Отвечал котел или нет, без осциллографа сказать невозможно. Как только пришло понимание этого факта, был заказан USB осциллограф (он же восьмиканальный логический анализатор, он же USB-бластер) за 40 вечнозеленых (который, имею сказать, — просто мегавещь!). Осциллограф, как полагается, должен был идти из Китая неопределенной время и прийти где-то весной, когда актуальность терморегулирования, хм, несколько меньше чем зимой. Поэтому было решено использовать ту самую тестовую фичу OpenTherm для управления котлом, чтобы хоть примерно посмотреть что из затеи может получиться.
Как рулить котлом
Созерцание котла долгими зимними вечерами привело к понимаю, что логика его работы довольно проста. Вы выставляете температуру воды (setpoint), скажем 55С. Он начинает греться, пока температура не достигнет 60C. Затем он выключается и вновь включается, когда температура воды достигнет 50C. Этакие качели с гистерезисом вокруг заданного значения. Имея «тестовую фичу», очень легко с помощью внешнего устройства управлять котлом, чтобы он работал ровно так же, как и без оного.
Еще одно последствие созерцания котла – понимание какую температуру воды нужно выставить на нем в зависимости от температуры на улице, чтобы в доме плюс-минус лапоть было комфортно.
Итак, управление котлом складывается из двух алгоритмов:
- Алгоритма включения-выключения горелки в зависимости от желаемого setpoint и текущей температуры воды
- Алгоритма вычисления setpoint на основе имеющихся температурных данных
Тут надо сделать необходимое отступление в принцип работы терморегуляторов, имеющихся на рынке. Они бывают двух родов:
- Классические. Принцип работы – холоднее, чем должно быть — надо греть, теплее – не надо греть. Второй принцип – если сильно холоднее, нужно сильно греть
- Так называемые регуляторы c OTC (outdoor temperature compensation), т.е. регуляторы с компенсацией температуры на улице. Эти устроены хитрее. Они позволяют задать пользователю зависимость setpoint от температуры на улице. Теоретически они должны рулить температурой в доме аккуратнее
Поскольку у меня имелись данные о температуре и в комнатах и на улице, я, в принципе, мог реализовать устройство с OTC. На самом деле, были опробованы обе схемы, и, подтверждено опытами, вторая выгоднее.
Хочу напомнить, что в результате реализации погодной станции, в эфире вокруг моего дома появились данные о погоде. Алгоритм работы терморегулятора должен быть примерно таким:
- Поймать из эфира данные о погоде
- Померить температуру воды в контуре отопления
- На основе имеющихся данных вычислить целевую температуру воды в контуре
- Управлять горелкой так, чтобы поддерживать целевую температуру воды в контуре
Аппаратная часть
От аппаратной части требуется чтобы она:
- Принимала данные из эфира
- Позволяла выставлять целевую температуру в доме
- Выводила на экран базовые показатели
- Умела «закорачивать линию» управления котлом
- Посылать данные о своей работе и работе котла обратно в эфир, чтобы они могли быть пойманы центральным юнитом и затем визуализированы
Этими требованиями продиктована схема.
Начать стоит с самой простой части – части управления котлом. JP1 – разъем, к которому подключаются две линии управления котлом. S1 — тумблер, который закорачивает линии управления котлом и, таким образом, переводит его в автономный режим (мало ли чего с контроллером случится). Мост D1-D4 нужен для обеспечения толерантности по полярности подключения к котлу. Оптопара U1 выполняет роль реле и обеспечивает гальваническую развязку между высоковольтной линией управления котлом (напомню, в открытом состоянии это целых 42V) и TTL-частью схемы. R2 – ограничительный резистор, нужный для того, чтобы ток через вывод D5 (через который и осуществляется управление) микроконтроллера не был слишком большим. В принципе, эту часть можно было заменить слаботочным реле, но… В глубинах души теплится надежда, что котел поддерживает OT/+ и удастся управлять им цивилизованно. Реальная схема сопряжения с котлом несколько сложнее. Тут она описана лишь в мере, необходимой для управления котлом в режиме замкнуто/незамкнуто.
Температурный сенсор DS18B20 подключен к выводу 12 микроконтроллера. Между data-выводом DS18B20 и шиной питания в соответствии со спецификацией подключен резистор R4.
LCD экран на основе контроллера HD44780 – две строки по восемь символов. Схема включения классическая: Vcc – на шину питания, Vss – земля, на V0 подается опорное напряжение для регулировки контрастности, образованное делителем в лице построечного резистора R3. R3 можно один раз подкрутить по вкусу и забыть. Вывод R/W посажен на землю. Остальные выводы подключены так: RS->вывод 11 контроллера, Enable -> 10, DB4->4, DB5->7, DB6->8, DB7->9.
Приемник подключен к выводу 2, передатчик – к выводу 3.
К выводу A0 подключен переменный резистор, образующий делитель. На основании напряжения на выводе A0 прошивка контроллера будет делать вывод о целевой температуре в доме.
Программная часть
Алгоритм работы выглядит так:
В основном МК проводит время, ожидая сообщения в эфире. Если в течение заданного времени сообщение не пришло, алгоритм все равно выполняет основное тело(ведь за это время могла измениться температура воды в контуре и, возможно, пришла пора включить или выключить горелку).
Параллельно с этим по таймеру происходит измерение напряжения на A0, в соответствии с которым устанавливается целевая температура в доме. Напряжению 0 соответствует температура 18C, напряжению Vcc соответствует температура 26C. Если температура меняется, т.е. пользователь в данный момент крутит ручку, то происходит обновление экрана, на котором всегда отображаются три параметра: целевая температура в комнате, температура воды в контуре, целевая температура воды в контуре на текущий момент.
Целевая температура воды в контуре считается по следующей формуле:
Trrw = 20 + (Trr-To) + (Tr-Trr)*30, где
Trrw- целевая температура воды в контуре
Trr – целевая температура в доме
To – температура на улице
Tr – температура в доме
Как видно, есть некоторое слагаемое сдвига 20 (подобранное эвристически), слагаемое OTC (Trr-To) которое тем больше, чем больше разница между целевой температурой в комнате и температурой на улице и штраф за недостижение целевой температуры в комнате (Tr-Trr)*30.
Реализация алгоритма довольно тривиальна, понять можно, посмотрев скетч прошивки.
#include <OneWire.h>
#include <DallasTemperature.h>
#include <VirtualWire.h>
#include <EEPROM.h>
#include <WirelessSensorPipe.h>
#include <LiquidCrystal.h>
#define DEBUG
#define INVALID_TEMPERATURE (1000)
/* hardware configuration */
#define DSPIN 12
#define TRANSMITPIN 3
#define RECEIVEPIN 2
#define HEATERCONTROLPIN 5
#define TARGETTEMPCONTROLPIN A0
//LCD pin mapping
#define LCDRS 11
#define LCDENABLE 10
#define LCDD4 4
#define LCDD5 7
#define LCDD6 8
#define LCDD7 9
#define RECEIVETIMEOUT 30 // wireless receive timeout
bool heater_enabled = false;
void enable_heater()
{
digitalWrite(HEATERCONTROLPIN, HIGH);
heater_enabled = true;
}
void disable_heater()
{
digitalWrite(HEATERCONTROLPIN, LOW);
heater_enabled = false;
}
float target_room_temperature, previous_target_room_temperature;
/*reads target room temperature from the potentiometer and returns true if the value changed*/
bool updateTargetRoomTemp()
{
uint16_t potentiometer_value = analogRead(TARGETTEMPCONTROLPIN);
target_room_temperature = 22 + 4 / 1024.0 * potentiometer_value;
if (fabs(target_room_temperature — previous_target_room_temperature) < 0.1)
{
return false;
}
previous_target_room_temperature = target_room_temperature;
return true;
}
uint16_t cycle_counter = 0;
void targetTempChangeChecker()
{
if (cycle_counter++ % 16 == 0) //max period of timer2 is 16ms, so we throttle out 15 cycles of 16 to update target temp value approx 4 times per second
{
if(updateTargetRoomTemp())
updateOnScreenInfo();
}
}
#define OUTSIDE_SENSOR_ID 27327
#define ROOM_SENSOR_ID 13467
#define TEMPERATURE_HYSTERESIS 5
#define HEATER_MAX_TEMP 80
#define HEATER_MIN_TEMP 30
float room_temperature = INVALID_TEMPERATURE;
float outside_temperature = INVALID_TEMPERATURE;
float RW_temperature;
float target_RW_temperature = 30;
OneWire one_wire(DSPIN);
DallasTemperature sensor(&one_wire);
void updateTargetRWTemperature()
{
sensor.requestTemperatures();
RW_temperature = sensor.getTempCByIndex(0);
target_RW_temperature = 20 + (target_room_temperature — outside_temperature) + (target_room_temperature — room_temperature) * 30;
target_RW_temperature = min(target_RW_temperature, HEATER_MAX_TEMP);
target_RW_temperature = max(target_RW_temperature, HEATER_MIN_TEMP);
}
LiquidCrystal lcd(LCDRS, LCDENABLE, LCDD4, LCDD5, LCDD6, LCDD7);
void updateOnScreenInfo()
{
char floatBuffer[7];
lcd.setCursor(0,0);
lcd.print(F(«Room»));
lcd.print(dtostrf(target_room_temperature, 2, 1, floatBuffer));
lcd.setCursor(0,1);
lcd.print(F(«W»));
lcd.print((int)RW_temperature);
lcd.print(F(" TW"));
lcd.print((int)target_RW_temperature);
}
WirelessSensorPipe pipe;
void setup ()
{
#ifdef DEBUG
Serial.begin(9600);
Serial.println(F(«Entered setup»));
#endif
pinMode(HEATERCONTROLPIN, OUTPUT);
sensor.begin();
pipe.begin(TRANSMITPIN, RECEIVEPIN, 13);
#ifdef DEBUG
Serial.print(F(«Sensor id:»));
Serial.println(pipe.id());
#endif
updateTargetRoomTemp();
updateTargetRWTemperature();
TimerTwo::init(16384, targetTempChangeChecker);
TimerTwo::start();
lcd.begin(8, 2);
}
int current_transmission_phase = 0;
void loop()
{
#ifdef DEBUG
Serial.print(F(«Timestamp:»));
Serial.println(millis()/1000);
Serial.print(F(«RW temperature: „));
Serial.println(RW_temperature);
Serial.print(F(“target_RW temperature: „));
Serial.println(target_RW_temperature);
Serial.print(F(“outside temperature: „));
Serial.println(outside_temperature);
Serial.print(F(“room temperature: „));
Serial.println(room_temperature);
Serial.print(F(“target room temperature: „));
Serial.println(target_room_temperature);
// blink the LED to indicate that the readings are done
//digitalWrite(13, HIGH);
//delay(100);
//digitalWrite(13, LOW);
#endif
updateTargetRoomTemp();
lcd.begin(8,2); // periodically LCD goes crazy for some reason, so this is a temporary hack to get off of this state. shall be removed when the reason for such behaviour become clear.
lcd.clear();
updateOnScreenInfo();
WirelessSensorPipe::Packet packet;
if (pipe.receive(packet, RECEIVETIMEOUT * 1000))
{
if(packet.type == WirelessSensorPipe::TEMPERATURE)
{
switch (packet.id)
{
case OUTSIDE_SENSOR_ID:
outside_temperature = packet.value;
break;
case ROOM_SENSOR_ID:
room_temperature = packet.value;
break;
}
}
return; //we skip the rest of the code to avoid sending our data and conflicting with other devices sending data in series. In other words we listen until someone transmits and doing the rest of the work if no one transmits
}
updateTargetRWTemperature();
if (RW_temperature >= target_RW_temperature + TEMPERATURE_HYSTERESIS && target_RW_temperature < HEATER_MAX_TEMP )
{
disable_heater();
}
if (RW_temperature <= target_RW_temperature — TEMPERATURE_HYSTERESIS && target_RW_temperature > HEATER_MIN_TEMP)
{
enable_heater();
}
switch(current_transmission_phase++)
{
case 0:
pipe.send(WirelessSensorPipe::TEMPERATURE, room_temperature);
break;
case 1:
pipe.send(WirelessSensorPipe::HEATERSETPOINT, target_RW_temperature);
break;
case 2:
pipe.send(WirelessSensorPipe::HEATERRWTEMPERATURE, RW_temperature);
break;
case 3:
pipe.send(WirelessSensorPipe::HEATERTARGETROOMTEMPERATURE, target_room_temperature);
break;
case 4:
pipe.send(WirelessSensorPipe::HEATERFLAMEENABLED, heater_enabled);
current_transmission_phase = 0;
break;
}
}
Надо упомянуть, что больше всего проблем доставила работа с экраном. Детские болячки, вроде неправильного подключения и инициализации описывать не буду, а вот проблемы, связанные с неаккуратным использованием таймера, упомянуть можно. Дело в том, что первые реализации по таймеру считывали A0, вычисляли температуру и безусловно обновляли экран. Частота обновления была довольно высокая – 2Гц, а процедура обновления экрана достаточно длительная. Это приводило к тому, что основная петля алгоритма (которая основную часть времени проводит в попытке принять что-то из эфира) часто и надолго прерывалась обработчиком таймера. Это приводило к тому, что программная часть приемника ничего не могла принять из эфира, поскольку много пропускала во время отвлечения на выполнение обработчика таймера. Кроме того, экран эпизодически сходит с ума и его нужно переинициализировать, чтобы вернуть к жизни. С чем связано такое поведение, я пока не понял.
Вторая проблема, с которой я столкнулся – первое место, куда был установлен контроллер, оказалось местом жестокой радиотени.
Эти две проблемы отняли у меня приличное количество вечеров в попытках понять, почему контроллер ничего не принимает. Самое же противное в том, что устранение любой из них не улучшало ситуацию, надо было решать одновременно обе, поэтому я довольно долго находился в заблуждениях типа «радиотень тут не причем» и «с приемной частью все в порядке».
Еще очень плохой затеей оказалась передача всех контролируемых параметров скопом в радиоэфир на каждой итерации алгоритма. В передаваемых параметрах значатся: температура в комнате, температура на улице (просто повтор в целях контроля), установленная целевая температура в доме, вычисленное целевое значение температуры воды, текущее значение температуры воды, замкнута ли цепь управления котлом или нет. Поскольку выполнение одной итерации алгоритма занимает порядка 30 секунд, два раза в минуту в эфир вываливалась куча пакетов, что приводило к повышению вероятности коллизий. Поэтому пришлось модифицировать прошивку так, чтобы она за одну итерацию посылала в эфир только один параметр. Таким образом, каждый параметр стал обновляться раз в 3 минуты.
Part list
Arduino Pro Mini – 100 руб за штуку
DS18B20 – 34 руб за штуку
Передатчик + приемник 433MHz – 40 руб за штуку
LCD WH0802A-NGG-CT – 125 руб за штуку
4N35 — 9 руб за штуку
Диодный мост DB107 — 5.5 руб за штуку
Резисторы – всего на 5 руб
Тумблер (куплен оффлайн) – 20 руб
Корпус КР-606-ПС – 40 руб
Итог – с учетом припоя, макетной платы, проводов и амортизации паяльника – около 450 руб. Если б заказал LCD на Ebay, получилось бы еще на 80 руб дешевле.
Тестирование, выводы, планы
Фотографии самого контроллера и котла под спойлером. Прошу прощения за неровно вырезанное окошко под LCD.
А вот к выходу котла прикручен DS18B20
Фотография того, как «контрольная панель» смотрится на том же Стрике.
Как я уже сказал, было реализовано две схемы управления – классическая, т.е. «Не достаточно жарко? Жарим!», и с компенсацией уличной температуры.
В принципе, первая удовлетворяла требованиям, изначально предъявленным к системе статирования, т.е. обеспечивала температуру в помещении в пределах полуградуса от целевого значения. Однако недостатком являлись колебания вокруг этого самого целевого значения с амплитудой почти полградуса. Т.е. температура в комнате осциллировала вокруг цели, причем эта осцилляция явно не могла быть вызвана внешними факторами, поскольку они были стабильны.
Но вот введение OTC дало неожиданный хороший результат. Работа по алгоритму в соответствии с формулой выше стабилизировала температуру в пределах -0.2+0.1 градуса от цели. Надо отметить, что провалы в 0.2 градуса связаны даже не с особенностями процесса терморегулирования, а с тем, что когда кто-то открывает горячую воду (например, моется полчаса) котел занят тем, что обеспечивает нагрев горячей воды в контуре подачи горячей воды. Контур отопления при этом не греется.
Контроллер в таком режиме работает уже полмесяца, полет нормальный, отказов не было. Могу сказать, что термостатирование прилично повышает психологический комфорт. Если раньше нужно было как минимум каждые вечер и утро выставлять температуру воды в соответствии с текущей температурой на улице, прогнозом и показаниями собственной интуиции, что требовало некоторых усилий, то сейчас мы даже не вспоминаем, что у нас стоит котел – работает себе и работает.
В планах значится дожать OT/+, благо заказанный осциллограф пришел, с его помощью я уже выяснил, что даже посылал в сторону котла не то (была банальная опечатка в коде, которую я десятки раз перепроверял и не видел – глаз замылился), а о приеме я вообще молчу. В принципе, работу термостата это не улучшит, но позволит снимать больше диагностики. К тому же это теперь принципиальная проблема из разряда «кто кого».
Автор: Sermus