Автоматика для вентиляции на Arduino.

Всем привет! Сделал таки я для своей бездыханно висящей на потолке приточно-вытяжной системы вентиляции Naveka Node1 систему управления на Arduino UNO+WiFi R3 ATmega328P+ESP8266.

Старая система управления не пережила грозу и была отправлена производителю ПВВУ в ремонт. Ремонтировать контроллер производитель что-то не особо торопился, а я тем временем вник в логику работы вентиляционной установки и решил сделать для нее управление на Arduino. Чуточку позже, без каких-либо телодвижений с моей стороны, производитель ПВВУ — progress-nw.ru, вернул за пульт и контроллер деньги!!! Приятный ход со стороны производителя в сторону клиента!!! Вот видео этой установки, тогда еще несмонтированной :)).

Итак, из чего состоит вентиляционная установка Naveka Node1 и, соответственно, с чем нам придется работать:

  • AC вентиляторы Ebmpapst D2E146-HR93-03 с 4мя ступенями переключения скоростей (вариант с али);
  • электропривод Vilmann TAFA2-05S с возвратной пружиной, установленный на заслонке притока;
  • пластинчатый алюминиевый теплообменник (рекуператор);
  • нагревательные элементы PTC 1500W ~220В — 2 шт (неплохой магазин PTC нагревателей на али);
  • термостат нагревательных элементов — 1 шт;
  • твердотельное реле Maxwell MS-3DA3825 — 1 шт;
  • датчик температуры приточного воздуха (вместо «родного» используем цифровой DS18B20);
  • фильтр карманный G4 — 2 шт;
  • реле перепада давления Lefoo LF32-05 — 4шт;
  • контактор IEK КМ 20-20 — 1 шт;
  • реле промежуточное IEK РЭК 78/3;

Все это железо под управлением платы ZENTEC M100-B1 позволяло осуществлять приточно-вытяжную вентиляцию на первой или второй ступени скорости, поддерживать температуру приточного воздуха с помощью керамических саморегулируемых электронагревателей — PTC. Программировать работу установки по расписанию. Этой функцией так и не воспользовался ни разу и реализовывать в новой системе управления не стал. Для нового управления приточно-вытяжной вентиляционной установкой купил на али следующие комплектующие:

  1. Плата Arduino UNO+WiFi R3 ATmega328P+ESP8266 -1 шт;
  2. отверточный шилд (Screw Shield) — 1 шт;
  3. цифровые датчики температуры DS18B20 — 4 шт;
  4. дисплей LCD2004 c I2C — 1 шт;
  5. модуль из 6-ти твердотельных реле (Omron) High level Trigger — 1 шт.

Какова же логика работы приточно-вытяжной вентиляционной установки? Понимание принципа работы — это первый шаг к созданию управления на базе Arduino или подобной платы. Как логику работы ПВВУ понял я.

Принцип работы приточно-вытяжной вентиляционной установки.

Вентиляция без подогрева воздуха. При включении ПВВУ, контроллер проверяет состояние пожарного шлейфа и термостата нагревателя, в случае нормы происходит открытие клапана на притоке и запуск приточного и вытяжного вентиляторов на скорости, выбранной пользователем. Фактический запуск приточного вентилятора подтверждается сработкой соответствующего реле перепада давления (РПД ПВ). При эксплуатации в зимнее время, при низких температурах окружающего воздуха, возможно обмерзание рекуператора и сработка соответствующего реле перепада давления (РДП рекуператора). В этом случае контроллер отключает приточный вентилятор, закрывает заслонку приточного канала и начинается отогрев теплообменника вытяжным теплым воздухом. После завершения операции оттаивания рекуператора и восстановления РПД, происходит открытие заслонки на притоке, запуск приточного вентилятора, работа ПВВУ продолжается. При отключении вентиляционной установки происходит остановка вентиляторов и закрытие заслонки приточного канала.

Работу в режиме подогрева приточного воздуха возможно активировать, когда ПВВУ уже запущена. В этом режиме происходит подогрев и поддержание температуры приточного воздуха до температуры уставки. При эксплуатации в зимнее время, при низких температурах окружающего воздуха, возможно обмерзание рекуператора и сработка РДП рекуператора. В этом случае контроллер отключает PTC нагревательный элемент и происходит его продувка в течении заданного времени. Затем происходит закрытие заслонки приточного канала, останавливается приточный вентилятор и начинается отогрев рекуператора вытяжным теплым воздухом. После завершения процесса оттаивания рекуператора, заслонка на притоке открывается, запускается приточный вентилятор и работа ПВВУ в режиме нагрева и поддержания температуры приточного воздуха восстанавливается. При отключении вентиляционной установки с включенным подогревателем происходит отключение подогревателя, его продувка, затем выполняется остальная часть алгоритма отключения ПВВУ.

Работа ПВВУ автоматически прекращается в случае:

  • срабатывания пожарной сигнализации,
  • срабатывания термостата нагревателя,
  • остановки приточного вентилятора.

На дисплее будет отображаться соответствующее состояние. Для восстановления работы необходимо устранить неисправности и осуществить запуск ПВВУ.

Замена фильтров. Нужны они прежде всего для того, чтобы обитатели помещения могли дышать чистым воздухом. Во-вторых, для того чтобы рекуператор не забило пылью как со стороны притока, так и вытяжки. Сработка РПД фильтров свидетельствует о загрязнении фильтров, фильтра нуждаются в замене. На дисплее управления будет выведена соответствующая индикация, работа ПВВУ при этом продолжается.

Понимая таким образом логику работы приточно-вытяжной вентиляционной установки и ожидая нужную мне Arduino и другие железки, приступил к первым этапам реализации: «рисованию» схемы подключения и написанию кода. Пришлось немало времени провести под потолком с мультиметром, прозванивая и маркируя проводники, стоя на лесенке! Снимать установку категорически не хотелось =)). Используя Fritzing, сваял «картинку» подключения всех элементов к ардуино (картинка кликабельна):

Схема подключения вентиляционной установки к Arduino
Схема подключения вентиляционной установки к Arduino

Конечно-же, схемой эту картинку не назвать, но, глядя на нее, вполне можно подключить сенсоры и исполняющие модули к микроконтроллеру (плате). А обобщение всего этого материала — хорошая шпаргалка для меня. Соединяем проводники согласно схемы (на фото промежуточный результат):

ПВВУ NAVEKA и Ардуино
ПВВУ NAVEKA и Ардуино

Места релейной сборке в родном коммутационном отсеке приточно-вытяжной вентиляционной установки не нашлось. Разместил релейный модуль в распаечной коробке. Соединения датчиков также спрятал в отдельной распаечной коробке. Таким образом, избавился от львиной доли «бороды» проводов в основном отсеке.

Для соединения дисплея и кнопок с платой управления использовал ЭКРАНИРОВАННУЮ витую пару. Длинна линии составила 4 метра. Будет ли работать система при использовании кабеля большей длинны? Не факт, надо проверять. Работать контроллер с обычной витой парой отказался. Если стягивающие резисторы (Pull_down) кнопок на входах микроконтроллера отлично справлялись со своей задачей, то с дисплеем творилось нечто… На экране можно было наблюдать еле заметную, то и дело моргающую чехарду!!! Но это еще не все. Экран витой пары соединим с GND на плате, пустим по ней ‘-‘, таким образом, все «помехи» будем «сливать» на землю. Ну и лишнее это или нет — витую пару шины дисплея I2C (SCL и SDA) пропустим через ферритовые кольца, как возле дисплея, так и возле контроллера:

Ферритовое кольцо на шине I2C дисплея LCD (на стороне контроллера)
Ферритовое кольцо на шине I2C дисплея LCD (на стороне контроллера)
Ферритовое кольцо на шине I2C дисплея LCD (на стороне LCD дисплея)
Ферритовое кольцо на шине I2C дисплея LCD (на стороне LCD дисплея)

Скетч.

Скетч мы будем заливать в Arduino и в ESP. Каждому чипу свой код. Смысл в том, что данные о работе вентустановки будем передавать в базу данных MySQL и в веб-браузер по запросу клиента. В базу пишем данные для анализа и принятия решения о корректировки работы ПВВУ в случае необходимости. В программировании я не силен, только учусь этому, код далеко не оптимальный, но рабочий, проверял работу на столе на выявление каких-нибудь косяков. «Допиливал» скетч уже по месту (после коммутации платы управления с железом ПВВУ).

Скетч для Arduino (ATmega328P), версия от 02.02.2020:

//ivansmith.ru
//версия от 02.02.2020 исправлены косяки по отображению "С" на LCD
//версия от 12.03.2020 устранен баг с подсчетом КПД после выключения ПВВУ с режимом НАГРЕВ.

#include <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal_PCF8574.h>
#include <Wire.h>
#include <GyverPID.h>

//========================назначаем пины===========================
#define ONE_WIRE_BUS 2 //пин шины датчиков температуры
#define PIN_FILTER A0 //РПД фильтра
#define PIN_RECUP A1 //РПД рекуператора
#define PIN_IN_MOTOR A2 //РПД мотора
#define PIN_THERMOSTAT 12 //термостат нагревателя
#define PIN_FIRE 10 // пожарный датчик
#define PIN_POWER_BUTTON 13 //кнопка power
#define PIN_HEATER_BUTTON 9 //кнопка наревателя
#define PIN_PWM_MAXWELL 11 // ШИМ  для управления реле нагревателя Maxwell
#define RELAY_L1 3 //1ая скорость вентиляторов
#define RELAY_L2 4 //2ая скорость вентиляторов
#define RELAY_L3 5 //3яя скорость вентиляторов
#define RELAY_IN_MOTOR 6 //реле включения приточного вентилятора
#define RELAY_FLAP 7 // заслонка на притоке
#define RELAY_HEATER 8 //электронагреватель

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
GyverPID regulator(25.8, 0.79, 75.4, 100);  // коэф. П, коэф. И, коэф. Д, период дискретизации dt (мс)
LiquidCrystal_PCF8574 lcd(0x3F);

int t1, t2, t3, t4;
uint8_t sensor1[8] = { 0x28, 0x8, 0x26, 0x94, 0x97, 0x2, 0x3, 0x15 }; //датчик температуры наружного воздуха, поступащего в ПВВУ с улицы 
uint8_t sensor2[8] = { 0x28, 0x61, 0xB4, 0xB8, 0x5D, 0x14, 0x01, 0xD5 }; //датчик температуры вытяжного воздуха, поступающего в ПВВУ из помещения
uint8_t sensor3[8] = { 0x28, 0x54, 0x21, 0xD1, 0x5D, 0x14, 0x01, 0xB5 }; //датчик температуры воздуха, выбрасываемого из ПВВУ на улицу
uint8_t sensor4[8] = { 0x28, 0x14, 0xEA, 0x11, 0x5E, 0x14, 0x01, 0x4F }; //датчик температуры приточного воздуха,поступающего в помещение

bool power_state = false; // состояние активности ПВВУ
bool power_button_state = false; //состояние кнопки питания
bool power_button_long_state = false; //
bool heater_button_state = false; //состояние кнопки нагрева
bool heater_button_long_state = false;
bool fan_speed = false; //для изменения скорости вентиляторов
bool antifreez = false; //состояние процесса разморозки рекуператора
bool power_off = false; //
bool heater_on = false; // задаем состояние нагревателя по нажатию кнопки вкл-выкл нагрев
bool heater_state = false; //фактическое состояние нагревателя в зависимости от устовий
unsigned int antifreez_time = 60000; //задаем время оттаиваня рекуператора, мсек
unsigned int flap_time = 50000; //время открытия заслонки, мсек
int close_flap_time = 20000; //время открытия заслонки, мсек
int RPDMotor_time = 5000;
int heater_flow_time = 20000; //время продувки нагревателя
int fanspeed = 1; //скорость вентиляторов при старте
int controltemp = 14; //Температура уставки по умолчанию 
int temp_step = 1; // шаг изменения температуры уставки
unsigned long recup; //считаем millis для оттайки рекупа
unsigned long power_button; //для подсчета millis кнопки питания
unsigned long heater_button; // для подсчета millis кнопки нагевателя
unsigned long backlight_ms; //для подсчета длительности работы подсветки LCD
int p; //для подсчета заполнения ШИМ в процентном соотношении
int kpd; // КПД рекуператора
String to_esp;// для отправки на ESPэху посылочки


void setup() {
 
  Serial.begin(9600);
  delay(1000);
  sensors.begin();
  sensors.setResolution(sensor1, 9);
  sensors.setResolution(sensor2, 9);
  sensors.setResolution(sensor3, 9);
  sensors.setResolution(sensor4, 9);
  lcd.begin(20, 4);

//====Пины для РПД, термостата, ПС=====
  pinMode(PIN_FILTER, INPUT_PULLUP);
  pinMode(PIN_RECUP, INPUT_PULLUP);
  pinMode(PIN_IN_MOTOR, INPUT_PULLUP);
  pinMode(PIN_THERMOSTAT, INPUT_PULLUP);
  pinMode(PIN_FIRE, INPUT_PULLUP);
   

//============Пины для Кнопок===========    
  pinMode(PIN_POWER_BUTTON, INPUT);
  pinMode(PIN_HEATER_BUTTON, INPUT);
//============Пины для релюх============  
  pinMode(RELAY_L1, OUTPUT);
  digitalWrite(RELAY_L1, LOW);
  pinMode(RELAY_L2, OUTPUT);
  digitalWrite(RELAY_L2, LOW);
  pinMode(RELAY_L3, OUTPUT);
  digitalWrite(RELAY_L3, LOW);
  pinMode(RELAY_IN_MOTOR, OUTPUT);
  digitalWrite(RELAY_IN_MOTOR, LOW); 
  pinMode(RELAY_FLAP, OUTPUT);
  digitalWrite(RELAY_FLAP, LOW); 
  pinMode(RELAY_HEATER, OUTPUT);
  digitalWrite(RELAY_HEATER, LOW);
  pinMode(PIN_PWM_MAXWELL, OUTPUT);

  //=================PID================
  regulator.setDirection(NORMAL); // направление регулирования (NORMAL/REVERSE). ПО УМОЛЧАНИЮ СТОИТ NORMAL
  regulator.setLimits(0, 255);    // пределы (ставим для 8 битного ШИМ). ПО УМОЛЧАНИЮ СТОЯТ 0 И 255
  

  // в процессе работы можно менять коэффициенты
  regulator.Kp = 5.2;
  regulator.Ki += 0.5;
  regulator.Kd = 0; 


}//void setup()

void loop() {
  
  
sensors.requestTemperatures();
  t1 = sensors.getTempC(sensor1);
  t2 = sensors.getTempC(sensor2);
  t3 = sensors.getTempC(sensor3);
  t4 = sensors.getTempC(sensor4);
  regulator.setpoint = controltemp;// сообщаем регулятору температуру, которую он должен поддерживать
  regulator.input = t4; //сообщаем PID регулятору фактическую температуру притока

  kpd = ((float)(t4-t1)/(t2-t1))*100;
  if ( kpd < 0 or heater_on or heater_state or !power_state or antifreez) kpd=0;

  p = 100/(255/regulator.getResultTimer());  //выразим заполнение ШИМ нагревателя в процентном соотношении
  
//==================================================Работа с кнопкой power_button(вкл-выкл-скорость вентиляторов)=========================
//========================================================================================================================================
//фиксируем нажатие кнопки
 if( digitalRead (PIN_POWER_BUTTON) == HIGH && !power_button_state && ( millis() - power_button ) > 50 ) {
  power_button_state = true;
  power_button_long_state = false;  
  power_button = millis();
  backlight_ms = millis();
  }
 
//фиксируем длинное нажатие кнопки power_button и увеличиваем либо уменьшаем скорость вентиляторов ПВВУ
 if( digitalRead (PIN_POWER_BUTTON)  == HIGH && !power_button_long_state && power_state && ( millis() - power_button ) > 1000) {
  power_button_long_state = true;
  //Serial.print("Скорость вентиляторов:");
  
    
  if (fanspeed == 1 && !fan_speed){
      fanspeed = 2; 
      fan_speed = true;
      digitalWrite(RELAY_L1, LOW);
      digitalWrite(RELAY_L3, LOW);
      delay(10);
      digitalWrite(RELAY_L2, HIGH);
      //Serial.println(fanspeed);
      } else if (fanspeed == 2 && fan_speed){
        fanspeed = 3;
        fan_speed = false;
        digitalWrite(RELAY_L1, LOW);
        digitalWrite(RELAY_L2, LOW);
        delay(5);
        digitalWrite(RELAY_L3, HIGH);
        //Serial.println(fanspeed);
      } else if (fanspeed == 3 && !fan_speed){
        fanspeed = 2;
        digitalWrite(RELAY_L1, LOW);
        digitalWrite(RELAY_L3, LOW);
        delay(5);
        digitalWrite(RELAY_L2, HIGH);
        //Serial.println(fanspeed);
      } else if (fanspeed == 2 && !fan_speed){
        fanspeed = 1;
        digitalWrite(RELAY_L2, LOW);
        digitalWrite(RELAY_L3, LOW);
        delay(5);
        digitalWrite(RELAY_L1, HIGH);
        //Serial.println(fanspeed); 
      }
  
 }

 // фиксируем отпускание кнопки и в случае короткого нажатия включаем либо выключаем ПВВУ
if( digitalRead (PIN_POWER_BUTTON) == LOW && power_button_state && ( millis() - power_button ) > 50 ) {
  power_button_state = false;
  power_button = millis();
  if( !power_button_long_state && !power_state ){
    power_state = true;
    digitalWrite(RELAY_FLAP, HIGH);
    lcd.setCursor(5, 3);
    lcd.print("Start ");
    //Serial.println("Открытие заслонки притока");
    delay(flap_time);
    //Serial.println("Заслонка притока открыта");
    digitalWrite(RELAY_L2, LOW);
    digitalWrite(RELAY_L3, LOW);
    digitalWrite(RELAY_L1, HIGH);
    digitalWrite(RELAY_IN_MOTOR, HIGH);
    delay(RPDMotor_time);
   // Serial.println("ПВВУ включена");
    
    
    } else if ( !power_button_long_state && power_state ){ 
      lcd.setCursor(5, 3);
      lcd.print("Stop  ");
      power_off = true; 
      }

}  

//=======Работа с кнопкой heater_button- вкл-выкл нагревателя для поддержания Т и установка значения поддерживаемой Т притока =========
//=====================================================================================================================================

//фиксируем нажатие кнопки
 if( digitalRead (PIN_HEATER_BUTTON) == HIGH && !heater_button_state && ( millis() - heater_button ) > 50 ) {
  heater_button_state = true;
  heater_button_long_state = false;  
  heater_button = millis();
  backlight_ms = millis();
  }
 
//фиксируем длинное нажатие кнопки heater_button и вкл-выкл нагрев 
 if( digitalRead (PIN_HEATER_BUTTON)  == HIGH && digitalRead(PIN_THERMOSTAT) == LOW && digitalRead(PIN_IN_MOTOR) == HIGH && !heater_button_long_state && power_state && !antifreez &&( millis() - heater_button ) > 1000) {
  heater_button_long_state = true;
  if ( digitalRead (RELAY_HEATER) == LOW ) { 
    digitalWrite(RELAY_HEATER, HIGH);
    regulator.integral = 0;
    //Serial.println("НАГРЕВАТЕЛЬ ВКЛЮЧЕН!!!"); 
    heater_on = true;
    heater_state = true;}
  else if ( digitalRead (RELAY_HEATER) == HIGH ){ 
    digitalWrite(RELAY_HEATER, LOW); 
    //Serial.println("НАГРЕВАТЕЛЬ ВЫКЛЮЧЕН");
    analogWrite(PIN_PWM_MAXWELL, 0);
    heater_on = false;
    heater_state = false;}
 }
 // фиксируем отпускание кнопки, задаем поддерживаемую температуру
if( digitalRead (PIN_HEATER_BUTTON) == LOW && heater_button_state && ( millis() - heater_button ) > 50 ) {
  heater_button_state = false;
  heater_button = millis();
  
  if( !heater_button_long_state ){ 
    controltemp=controltemp+temp_step; 
    }
  if ( !heater_button_long_state && controltemp <= 14 || controltemp >= 22 ){
    temp_step = -temp_step;}
 }  
  
  //==========================================Продуваем рекуператор в случае обмерзания===================================================
  
  if  (digitalRead(PIN_RECUP) == HIGH && power_state && !antifreez){
  //Serial.println("Обмерзание рекуператора");
  antifreez = true;
  if ( heater_state ){
    digitalWrite(RELAY_HEATER, LOW); 
    analogWrite(PIN_PWM_MAXWELL, 0);
    heater_state = false; 
    //Serial.println("Нагреватель выключен, продувка"); 
    delay(heater_flow_time);
    }
    
  digitalWrite(RELAY_IN_MOTOR, LOW);
  //Serial.println("Приточный вентилятор выключен");
  digitalWrite(RELAY_FLAP, LOW);
  delay(close_flap_time);
  //Serial.println("Заслонка притока закрыта");
  
  recup = millis ();
  
  
  
  } else if (digitalRead(PIN_RECUP) == LOW && power_state && antifreez && millis() - recup >= antifreez_time){
    //Serial.println("Рекуператор разморожен");
         //Serial.print("Разморозка длилась:");Serial.print((millis()-recup)/1000);Serial.println(" сек.");
         digitalWrite(RELAY_FLAP, HIGH);
         delay(flap_time);
         //Serial.println("Заслонка притока открыта");    
         digitalWrite(RELAY_IN_MOTOR, HIGH);
         //Serial.println("Приточный вентилятор включен");
         delay(RPDMotor_time);
         antifreez = false;
         if (heater_on) { 
          digitalWrite(RELAY_HEATER, HIGH); 
          regulator.integral = 0;
          heater_state = true; 
          //Serial.println("Нагреватель включен");}
         }
  }

//====================================ОТОБРАЖАЕМ НЕИСПРАВНОСТИ, УПРАВЛЯЕМ НАГРУЗКОЙ======================================

if (t4 == -127){
  if (digitalRead(RELAY_HEATER) == HIGH){
    digitalWrite(RELAY_HEATER, LOW);
    analogWrite(PIN_PWM_MAXWELL, 0);
    heater_on = false;
    heater_state = false;
  }
  lcd.setCursor(5, 3);
  lcd.print("Temper");
  lcd.setBacklight(255);
}


if ( digitalRead(PIN_FILTER) == HIGH ){
  //Serial.println("меняй фильтр");
  lcd.setCursor(5, 3);
  lcd.print("Filter");
  lcd.setBacklight(255);
}


if ( power_state && digitalRead(PIN_IN_MOTOR) == LOW ){
  //Serial.println("Ошибка приточного вентилятора");
  power_off = true;
  lcd.setCursor(5, 3);
  lcd.print("Motor ");
  lcd.setBacklight(255);
} 


if ( digitalRead(PIN_FIRE) == HIGH ){
  //Serial.println("Пожарная тревога");
  if (power_state) power_off = true;
  lcd.setCursor(5, 3);
  lcd.print("FIRE  ");
  lcd.setBacklight(255);
  }
  

if ( digitalRead(PIN_THERMOSTAT) == HIGH ){
  //Serial.println("Сработка термозащиты нагревателя");
  if (power_state )power_off = true;
  lcd.setCursor(5, 3);
  lcd.print("TStat ");
  lcd.setBacklight(255);
  }


if ( heater_state ){ 
    if (digitalRead(RELAY_HEATER) == LOW) {digitalWrite(RELAY_HEATER, HIGH);}
    analogWrite(PIN_PWM_MAXWELL, regulator.getResultTimer());
   // Serial.print("заполнение ШИМ = "); 
    //Serial.println(regulator.getResultTimer());
    //Serial.print("Температура = ");
    //Serial.println(t4);
    //Serial.print("Уставка = ");
    //Serial.println(controltemp);
  }

  if ( power_off ) {
if ( digitalRead(RELAY_HEATER) == HIGH ){
  digitalWrite(RELAY_HEATER, LOW);
  heater_state = false;
  heater_on = false;
  analogWrite(PIN_PWM_MAXWELL, 0); 
  //Serial.println("Нагреватель выключен, продувка"); 
  delay(heater_flow_time); 
  }
      digitalWrite(RELAY_IN_MOTOR, LOW);
      digitalWrite(RELAY_L1, LOW);
      digitalWrite(RELAY_L2, LOW);
      digitalWrite(RELAY_L3, LOW);
      digitalWrite(RELAY_FLAP, LOW);
      //Serial.println("Закрытие заслонки притока");
      //Serial.println("Заслонка притока закрыта");
      //Serial.println("ПВВУ выключена");
      antifreez = false;
      power_state = false;
      power_off = false;
      delay(close_flap_time);
      }


  
//====================================Работаем с LCD==========================================  
//===============================активируем подсветку по нажатию батонов======================
if (millis()-backlight_ms < 10000) {
  lcd.setBacklight(255);
} else lcd.setBacklight(0);

 //===============================отображаем температуру и КПД рекуператора=====================================
 //=======28-01-2020 добавил условия по отображению "C"

lcd.setCursor(0, 0);
lcd.print("t1:");
lcd.print (t1);
lcd.print ("\xDF");
if (t1==-127) lcd.print("C");
else if (t1 <= -10 && t1 != -127) lcd.print("C ");
else if (t1 > -10 && t1 <= -1) lcd.print("C  ");
else if (t1 >= 0 && t1 <= 9) lcd.print("C   ");
else lcd.print("C  ");

lcd.setCursor(9, 0);  
lcd.print ("\x7E");

lcd.setCursor(11, 0);
lcd.print("t4:");
lcd.print (t4);
lcd.print ("\xDF");
if (t1==-127) lcd.print("C");
else if (t4 <= -10 && t4 != -127) lcd.print("C ");
else if (t4 > -10 && t4 <= -1) lcd.print("C  ");
else if (t4 >= 0 && t4 <= 9) lcd.print("C   ");
else lcd.print("C  ");

lcd.setCursor(0, 1);
lcd.print("t2:");
lcd.print (t2);
lcd.print ("\xDF");
if (t2==-127) lcd.print("C");
else if (t2 <= -10 && t2 != -127) lcd.print("C ");
else if (t2 > -10 && t2 <= -1) lcd.print("C  ");
else if (t2 >= 0 && t2 <= 9) lcd.print("C   ");
else lcd.print("C  ");

lcd.setCursor(9, 1);  
lcd.print ("\x7E");

lcd.setCursor(11, 1);
lcd.print("t3:");
lcd.print (t3);
lcd.print ("\xDF");
if (t3==-127) lcd.print("C");
else if (t3 <= -10 && t3 != -127) lcd.print("C ");
else if (t3 > -10 && t3 <= -1) lcd.print("C  ");
else if (t3 >= 0 && t3 <= 9) lcd.print("C   ");
else lcd.print("C  ");

lcd.setCursor(12, 3);
lcd.print("KPD=");
lcd.print(kpd);
if (kpd>=100) lcd.print("%");
else if (kpd > 9 && kpd < 100) lcd.print("% ");
else lcd.print("%  "); 


//=================отображаем состояние вентустановки========= 
if (!power_state){
  lcd.setCursor(0, 2);
  lcd.print("V:0");
} else {
  lcd.setCursor(0, 2);
  lcd.print("V:");
  lcd.print(fanspeed);  
}


if (!heater_on or !heater_state){
  lcd.setCursor(4, 2);
  lcd.print("H:OFF ");
} else {
  lcd.setCursor(4, 2);
  lcd.print("H:");
  lcd.print(p); 
  lcd.print("% "); 
}

lcd.setCursor(11,2);
lcd.print("SP:");
lcd.print(controltemp);
lcd.print ("\xDF");
lcd.print ("C");


lcd.setCursor(0,3);
lcd.print("Info:");

 
if (digitalRead(PIN_FILTER) == LOW &&
digitalRead(PIN_IN_MOTOR) == HIGH &&
digitalRead(PIN_FIRE) == LOW &&
digitalRead(PIN_THERMOSTAT) == LOW &&
digitalRead(RELAY_IN_MOTOR) == HIGH &&
t1 != -127){
lcd.print("All OK ");
}

if ( antifreez ) {
  Serial.println(millis()-recup); //наблюдаем за таймером оттайки в мониторе COM порта
  lcd.setCursor(5, 3);
  lcd.print("Freez ");
  lcd.setBacklight(255);
  }

/* Формируем и отпарвляем ESPэхе посылку с данными переменных:
 *  "$t1 t2 t3 t4 kpd p power_state antifreez heater_state digitalRead(PIN_THERMOSTAT);"
 *  всего 11 переменных
*/

to_esp = String("$")+String(t1)+String(" ")+
String(t2)+String(" ")+
String(t3)+String(" ")+
String(t4)+String(" ")+
String(kpd)+String(" ")+
String(p)+String(" ")+
String(power_state)+String(" ")+
String(antifreez)+String(" ")+
String(heater_state)+String(" ")+
String(controltemp)+String(" ")+
String(digitalRead(PIN_THERMOSTAT))+String(";");
Serial.println(to_esp);

}//void loop()

Скетч для чипа ESP8266:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Thread.h>

Thread MysqlThread = Thread(); // создаём поток управления для Mysql
Thread WebThread = Thread(); // создаём поток управления для Mysql


//===парсер значений из serial-посылки, автор AlexGyver.ru
#define PARSE_AMOUNT 11         // число значений в массиве, который хотим получить
int intData[PARSE_AMOUNT];     // массив численных значений после парсинга
boolean recievedFlag;
boolean getStarted;
byte ind_ex;
String string_convert = "";
void parsing() {
if (Serial.available() > 0) {
char incomingByte = Serial.read();        // обязательно ЧИТАЕМ входящий символ
if (getStarted) {                         // если приняли начальный символ (парсинг разрешён)
if (incomingByte != ' ' && incomingByte != ';') {   // если это не пробел И не конец
string_convert += incomingByte;       // складываем в строку
} else {                                // если это пробел или ; конец пакета
intData[ind_ex] = string_convert.toInt();  // преобразуем строку в int и кладём в массив
string_convert = "";                  // очищаем строку
ind_ex++;                              // переходим к парсингу следующего элемента массива
}
}
if (incomingByte == '$') {                // если это $
getStarted = true;                      // поднимаем флаг, что можно парсить
ind_ex = 0;                              // сбрасываем индекс
string_convert = "";                    // очищаем строку
}
if (incomingByte == ';') {                // если таки приняли ; - конец парсинга
getStarted = false;                     // сброс
recievedFlag = true;                    // флаг на принятие
}
}
}

/*значения этим переменным будем задавать из посылки от Arduino:
 * "$t1 t2 t3 t4 kpd p power_state antifreez heater_state controltemp tstat;"
 * всего 11 переменных
 */
int t1; 
int t2;
int t3;
int t4;
int kpd;
int p;
int controltemp;
int tstat;
bool power_state;
bool antifreez;
bool heater_state;


//=======Настройка интернет части =============
//Задаем путь к PHP HTTP Post скрипту-обработчику 
const char* serverName = "http://ваш_сайт.ru/script.php";

//Прописываем ключ, который указан в php файле скрипта-обработчика 
String apiKeyValue = "придумайте_ваш_ключ";

//SSID и Password для доступа к wifi сети
const char* ssid = "имя_сети_вифи";  // Имя вашей сети Wi-Fi
const char* password = "пароль_от_вифи";  //пароль от сети Wi-Fi

// Задаем 80ый порт WEB серверу
WiFiServer server(80);

// Variable to store the HTTP request
String header;
// Current time
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0; 
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;


void setup() {
Serial.begin(9600);

//подключаемся к локальной wi-fi сети             
  Serial.println("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  //проверяем подключение к wi-fi сети
  while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected..!");
  Serial.print("Got IP: ");  Serial.println(WiFi.localIP());

  server.begin();
  
  MysqlThread.onRun(MysqlSend);  // назначаем потоку задачу
  MysqlThread.setInterval(20000); // задаём интервал срабатывания, мсек
  
  WebThread.onRun(WebClient);  // назначаем потоку задачу
  WebThread.setInterval(1); // задаём интервал срабатывания, мсек

}//void setup



void loop() {

/*разбираем посылку от Arduino:
 * "$t1 t2 t3 t4 kpd p power_state antifreez heater_state controltemp tstat;",
 * заполняя переменные
 */
parsing();       // функция парсинга
if (recievedFlag) {                           // если получены данные
recievedFlag = false;
for (byte i = 0; i < PARSE_AMOUNT; i++) { // выводим элементы массива
t1 = intData[0];
t2 = intData[1];
t3 = intData[2];
t4 = intData[3];
kpd = intData[4];
p = intData[5];
power_state = intData[6];
antifreez = intData[7];
heater_state = intData[8];
controltemp = intData[9];
tstat = intData[10];
}
}

if (!heater_state) p=0; // обнуляем значение мощности, если нагреватель выключен

// Проверим, пришло ли время выполнения потока MySQL:
    if (MysqlThread.shouldRun() && power_state or MysqlThread.shouldRun() && tstat )
        MysqlThread.run(); // запускаем поток
    // Проверим, пришло ли время выполнения потока WebClient:
    if (WebThread.shouldRun())
        WebThread.run(); // запускаем поток
}// void loop


//===========отправляем данные в БД MySQL===============
void MysqlSend() { 
      
  HTTPClient http;
 
  String sensor = "DS18B20";
    //Отправляем данные на страницу PHP HTTP Post
  http.begin(serverName);
  //http.addHeader("content-Type", "text.plain");
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");
  String httpRequestData = "api_key=" + String(apiKeyValue) + 
  "&sensor=" + String(sensor) + 
  "&t1=" + String(t1) + 
  "&t2=" + String(t2) + 
  "&t3=" + String(t3) + 
  "&t4=" + String(t4) +
  "&kpd=" + String(kpd) + 
  "&antifreez=" + String(antifreez) + 
  "&heater_state=" + String(heater_state) + 
  "&heater_power=" + String(p)+
  "&controltemp=" + String(controltemp)+
  "&tstat=" + String(tstat);

  
  Serial.print("httpRequestData: ");
  Serial.println(httpRequestData);
  int httpResponseCode = http.POST(httpRequestData);
  if (httpResponseCode>0) {
      Serial.print("HTTP Response code: ");
      Serial.println(httpResponseCode);
    }
    else {
      Serial.print("Error code: ");
      Serial.println(httpResponseCode);
    }
 http.end();
  }

//=====================отправляем данные в браузер===========
void WebClient() {
          
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    currentTime = millis();
    previousTime = currentTime;
    while (client.connected() && currentTime - previousTime <= timeoutTime) { // loop while the client's connected
      currentTime = millis();         
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
                                    
            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta charset=\"UTF-8\" name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            
            // CSS to style  
            client.println("<style>html { font-family: Helvetica; font-size: 20px; display: inline-block; margin: 0px auto; text-align: center;}</style></head>");
                    
            
            // Web Page Heading
            client.println("<body><h1>Вентиляха</h1>");
            
            // Display current temps 
            client.println("t1: " );
            client.println(t1);
            client.println("&#8451");
            client.println("&#8658");
            client.println(" t4: " );
            client.println(t4);
            client.println("&#8451");
            client.println("<br>t2: " );
            client.println(t2);
            client.println("&#8451");
            client.println("&#8658");
            client.println(" t3: " );
            client.println(t3);
            client.println("&#8451");
            client.println("<br><br>Уставка: " );
            client.println(controltemp);
            client.println("&#8451");
            client.println("<br><br> Вентиляция: " );
            if (power_state){
            client.println("<font color='blue'>вкл</font>");
            } else  client.println("выкл");
            client.println("<br>КПД рекуператора: " );
            client.println(kpd);
            client.println("%");
            client.println("<br><br>Разморозка: " );
            if (antifreez){
            client.println("<font color='red'>вкл</font>");
            } else  client.println("выкл");
            
            client.println("<br><br>Термостат: " );
             if (tstat){
            client.println("<font color='red'>сработка</font>");
            } else  client.println("норма");
            client.println("<br><br>Нагреватель: " );
            if (heater_state){
            client.println("<font color='blue'>вкл</font>");
            } else  client.println("выкл");
            client.println("<br>Мощность нагревателя: " );
            client.println(p);
            client.println("%");
            client.println("</body></html>");
            client.println("<br><br><a href=https://ivansmith.ru/vent.php>статистика</a><br>" );
            
            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
 }

Алгоритм работы расписывать не буду, комментарии от части должны помочь разобраться, я надеюсь. В скетче для ардуино много закомментированных Serial.print (Serial.println) . Использовал для отладки. Эта функция жрет много оперативной памяти, поэтому после отладки заливаем скетч уже с закомментированным // Serial.print !!! Если никогда не сталкивались с платой Arduino UNO + Wi-Fi R3 ATmega328P+ESP8266 и не приходилось отправлять с ESP данные в базу MySQL, то будут полезными следующие заметки для прочтения:

Все соединения выполнены, скетчи отлажены и загружены, скрестили пальцы, запустили… работает =)).

"Пульт" версии 1.0 =)
«Пульт» версии 1.0 =)

Вентиляционная установка Naveka Node1 обрела вторую жизнь теперь уже с управлением на Arduino! Не очень хорошо настроил PID регулятор калорифера. Получаем изначально перелет от температуры уставки на два 2°C, потом регулятор стабилизируется. Учитывая длительную процедуру запуска и остановки вентиляхи (срабатывание заслонки, включение нагревателя), не стал подбирать коэффициенты до идеальной работы калорифера. То и дело приходилось перезаливать скетч в плату. Оставил так как есть сейчас! Можно посмотреть видео в заметке про PID регулятор: Автоподогрев приточного воздуха в системе вентиляции. PID регулятор на Arduino. А статистика работы вентиляхи доступна на страничке Вентиляха.

Поделиться ссылкой:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *