Працюємо з 09:00 до 20:00 без вихідних.
Самовивіз - Київ біля ТЦ Квадрат бул.Перова

В этом проекте займемся чистой автоматикой. В данном случае озонатор будет очищать питьевую воду от излишка железа. При озонировании железо выпадет в осадок и полученную воду можно будет смело давать даже детям.

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

Технологическая схема представлена на рисунке ниже. Вода из скважины, имеющая в своем составе слишком много железа, закачивается насосом в сборник. Там она озонируется циркулированием через электро-озонатор. После этого очищенная вода полностью сливается через открытый клапан KL1. И процесс повторяется снова и снова.

Мощность насоса 1,5 кВт, клапан на 12 В, озонатор на 220 В мощностью 24 Вт.

Алгоритм автоматической системы

При включении устройства в автоматический режим проверяется уровень воды в баке. Если уровень меньше нижнего датчика L1 (ни один датчик уровня не сработал), закрывается клапан KL1, включается насос и озонатор. При достижении уровня воды до датчика L2, выключается насос и выдерживается время озонирования. После этого выключается озонатор и выдерживается время осаждения. После этого открывается клапан KL1 и алгоритм повторяется.

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

Интерфейс

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

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

Выбор элементной базы

Для реализации озвученной задачи просто идеально подойдёт плата контроллера Arduino Nano, установленная на монтажный шилд следующего вида


Данный шилд максимально упрощает монтажные работы и экономит место на клеммниках. Он позволяет подключать всю периферию контроллера без необходимости в перемычках, объединяющих плюсы и минусы питания.

Насос, клапан и озонатор в рамках єтой статьи рассматривать не будем - у всех будут свои. 

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

Символьный индикатор стандартный для любительской автоматики LCD 1602 с модулем I2C.

Для коммутации питания насоса возьмем твердотельное реле SSR-40DA.

Для коммутации питания озонатора используем одноканальный модуль твердотельного реле G3MB-202P.

Для управления клапаном используем модуль MOSFET транзистора IRF520, который будет коммутировать постоянное напряжение 12 В.

В качестве энкодера берём KY-040.

Для питания контроллера и клапана возьмем блок питания 12 В 2 А.

Все эти элементы смонтируем в электро-щитке с прозрачной передней панелью. А все внешние кабеля будем сажать на две клеммные колодки. На одной будут подключены исполнительные механизмы и ввод 220 В, а на другой датчики и ввод 12 В.

Схема подключения

На схеме было очень сложно понятным образом показать, как периферия подключается к шилду Arduino Nano, поэтому попробую описать это текстом.

Символьный индикатор подключаем к специальным выводам слева на шилде SCL SDA 5V GND.

Реле SSR-40DA подключаем к третьему дискретному входу - минус на пин G, а плюс на пин S (signal).

Модуль реле G3MB-202P подключаем к 4-му дискретному входу - минус на пин G, плюс на пин V, а сигнальный вывод на пин S.

Модуль MOSFET иранзистора подключаем ко 2-му дискретному входу - минус на пин G, плюс на пин V, а сигнальный вывод на пин S.

Датчик L1 подключаем к 5-му дискретному каналу контроллера - один провод к G, а другой к сигнальному S.

Датчик L2 подключаем к 8-му дискретному каналу контроллера - один провод к G, а другой к сигнальному S.

Датчик L3 подключаем к 9-му дискретному каналу контроллера - один провод к G, а другой к сигнальному S.

Энкодер нужно подключить следующим образом: вывод CLK к выводу S 7-го канала контроллера; вывод DT к выводу S 6-го канала шилда; вывод кнопки SW  к выводу S 11-го канала шилда; вывод + на вывод V 7-го канала шилда; вывод GND на вывод G 7-го канала монтажного шилда.

Разъем питания 12 В подключаем не к контроллеру, а на шилд. И эти же 12 В в разрыве кабеля блока питания заводим на клеммы питания модуля MOSFET транзистора.

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


Программа контроллера


#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h> //библиотека для запоминания настроечных данных в постоянную память контроллера

LiquidCrystal_I2C lcd(0x27, 16, 2);

const int L1_pin = 5;
const int L2_pin = 8;
const int L3_pin = 9;
const int M1_pin = 3;
const int ozon_pin = 4;
const int KL1_pin = 2;

boolean L1, L2, L3;
int L1_count = 0;
int L2_count = 0;
int L3_count = 0;

const int button = 11;
int button_old = 1;

int astep = 0;
int menu = 0;
int menu_ = 0;
int t_ozona;
int t_osad;

int avto = 0; //rezhim avto
boolean nasos = 0; //nasos on/off
boolean ozon = 0; //ozonator on/off
boolean klapan = 0; //klapan on/off

int pinA = 7;  // номер вывода, подключенный к CLK енкодера
int pinB = 6;  // номер вывода контроллера, подключенный к DT енкодера
 int encoderPosCount = 0; 
 int pinALast;  
 int aVal;
 boolean bCW;

unsigned long previousMillis = 0;
unsigned long currentMillis;
const long interval = 500;
unsigned long prevMillis_ozona = 0;
unsigned long curMillis_ozona;

unsigned long prevMillis_osad = 0;
unsigned long curMillis_osad;


void setup()
{
  t_ozona = EEPROM.read(9);
  t_osad = EEPROM.read(10);
  pinMode(button, INPUT_PULLUP);
  pinMode (pinA,INPUT_PULLUP);
  pinMode (pinB,INPUT_PULLUP);
  pinMode(L1_pin, INPUT_PULLUP);
  pinMode(L2_pin, INPUT_PULLUP);
  pinMode(L3_pin, INPUT_PULLUP);

  pinMode(M1_pin, OUTPUT);
  pinMode(ozon_pin, OUTPUT);
  digitalWrite(ozon_pin, !ozon); //zaschita ot promigivaniya pri puske
  pinMode(KL1_pin, OUTPUT);
	
  lcd.begin();
  Serial.begin(9600);
  // включение подсветки и вывод приветствия
  lcd.backlight();
  lcd.print("Pryvit,");
  lcd.setCursor(7, 1);
  lcd.print("gospodar!");
  delay(2000);
  pinALast = digitalRead(pinA);
  
  currentMillis = millis();
  previousMillis = currentMillis;
}

void loop()
{
  if (!digitalRead(L1_pin)) {if (L1_count<100) L1_count ++;} else {if (L1_count>0) L1_count --;} //filtracia din
  if (L1_count==100) L1 = 1;
  if (L1_count==0) L1 = 0;
  if (!digitalRead(L2_pin)) {if (L2_count<100) L2_count ++;} else {if (L2_count>0) L2_count --;} //filtracia din
  if (L2_count==100) L2 = 1;
  if (L2_count==0) L2 = 0;
  if (!digitalRead(L3_pin)) {if (L3_count<100) L3_count ++;} else {if (L3_count>0) L3_count --;} //filtracia din
  if (L3_count==100) L3 = 1;
  if (L3_count==0) L3 = 0;
  
  currentMillis = millis();

  func_encoder();

  if (avto==1) func_auto(); else {prevMillis_ozona = 0; curMillis_ozona = 0; prevMillis_osad = 0; curMillis_osad=0; astep=0;}

  func_menu();

  
  func_button();
  func_outs();
  
}
void func_outs()
{
  button_old = digitalRead(button); 
  
  digitalWrite(M1_pin, nasos);
  digitalWrite(ozon_pin, !ozon);
  digitalWrite(KL1_pin, klapan);
  delay(10);
}
void func_auto()
{
  if (astep==0){
    nasos = 0; 
    ozon = 0; 
    klapan = 1;
    if (!L1&&!L2&&!L3) astep = 100;
  }
  if (astep==100){
    nasos = 1; 
    ozon = 1; 
    klapan = 0;
    if (L1&&L2) {
      astep = 200;
      prevMillis_ozona = millis();
    }
  }
  if (astep==200){
    nasos = 0; 
    ozon = 1; 
    klapan = 0;
    curMillis_ozona = millis();
    if (curMillis_ozona - prevMillis_ozona >= t_ozona*60000){
      astep = 300; 
      prevMillis_osad = millis();
    }
  }
  if (astep==300){
    nasos = 0; 
    ozon = 0; 
    klapan = 0;
    curMillis_osad = millis();
    if (curMillis_osad - prevMillis_osad >= t_osad*60000) astep = 0;
  }
  
  if (L3) {
    avto = 0;
    nasos = 0; 
    ozon = 0; 
    klapan = 0;
  }
}
void func_button()
{
  if ((digitalRead(button)==LOW)&&(button_old==1))
    { 
      if ((menu == 1)and(button_old==1)) {
        switch (menu_) {
          case 0: menu = 100; encoderPosCount=0;
          break;
          case 1: if (avto==0){ menu = 200; encoderPosCount=0;}
          break;
          case 2: menu = 0; encoderPosCount=0;
          break;
        }
        button_old=0;
      }

      if ((menu == 100)and(button_old==1)) {
        switch (menu_) {
          case 0: if (avto==0) avto = 1; else {avto = 0; nasos = 0; ozon = 0; klapan = 0;}
          break;
          case 1: menu = 120; encoderPosCount = t_ozona;
          break;
          case 2: menu = 130; encoderPosCount = t_osad;
          break;
          case 3: menu = 0; encoderPosCount=0;
          break;
        }
        button_old=0;
        
      }

      if ((menu == 120)and(button_old==1)) {
        menu = 100;
        encoderPosCount = 1;
        button_old=0;
      }

      if ((menu == 130)and(button_old==1)) {
        menu = 100;
        encoderPosCount = 2;
        button_old=0;
      }

      if ((menu == 200)and(button_old==1)) {
        switch (menu_) {
          case 0: if (nasos==0) nasos = 1; else nasos = 0;
          break;
          case 1: if (ozon==0) ozon = 1; else ozon = 0;
          break;
          case 2: if (klapan==0) klapan = 1; else klapan = 0;
          break;
          case 3: menu = 0; encoderPosCount=0;
          break;
        }
        button_old=0;
      }
      
      if ((menu == 0)and(button_old==1)) {
        menu = 1;
        menu_ = 0;
        button_old=0;
        encoderPosCount=0;
      }
      
      
      menu_ = 0;
    }
}
void func_encoder()
{
  //encoder
   aVal = digitalRead(pinA);
   if (aVal != pinALast){ // проверка на изменение значения на выводе А по сравнению с предыдущим запомненным, что означает, что вал повернулся
     // а чтобы определить направление вращения, нам понадобится вывод В.
     
     if (digitalRead(pinB) != aVal) {  // Если вывод A изменился первым - вращение по часовой стрелке
       encoderPosCount ++;
       bCW = true;
     } else {// иначе B изменил свое состояние первым - вращение против часовой стрелки
       bCW = false;
       encoderPosCount--;
     }              
   }
   pinALast = aVal;
   //encoder
}
void func_menu()
{
  if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;
  if (menu == 1){
    
    if (encoderPosCount>2) encoderPosCount = 0;
    if (encoderPosCount<0) encoderPosCount = 2;
    menu_ = encoderPosCount;
    lcd.clear();
       lcd.setCursor(0, 0);
        lcd.print(">");
        lcd.setCursor(2, 0);
        switch (menu_) {
          case 0: lcd.print("AUTO");
          break;
          case 1: if (avto==0) lcd.print("RUCHNIY"); else lcd.print("ruchniy");
          break;
          case 2: lcd.print("VIHID");
          break;
        }
        
        lcd.setCursor(1, 1);
        switch (menu_) {
          case 0: if (avto==0) lcd.print("RUCHNIY"); else lcd.print("ruchniy");
          break;
          case 1: lcd.print("VIHID");
          break;
          case 2: lcd.print("AUTO");
          break;
        }
     
  }

  if (menu == 0){
      lcd.setCursor(0, 0);
      if (L1) lcd.print("L1 "); else lcd.print("l1 ");
      lcd.setCursor(3, 0);
      if (L2) lcd.print("L2 "); else lcd.print("l2 ");
      lcd.setCursor(6, 0);
      if (L3) lcd.print("L3 "); else lcd.print("l3 ");
      lcd.setCursor(9, 0);
      if (nasos) lcd.print("M1 "); else lcd.print("m1 ");
      lcd.setCursor(12, 0);
      if (avto == 0) lcd.print("RUCH"); else lcd.print("AUTO");
      lcd.setCursor(0, 1);
      if (klapan) lcd.print("KL1 "); else lcd.print("kl1 ");
      
      lcd.setCursor(4, 1);
      if (ozon) lcd.print("OZON  "); else lcd.print("ozon  ");
      lcd.setCursor(10, 1);
      if (avto==1) {
        switch (astep) {
          case 0: lcd.print("VYPUSK");
          break;
          case 100: lcd.print("NABOR ");
          break;
          case 200: lcd.print("OZONUV");
          break;
          case 300: lcd.print("OSADZH");
          break;
        } 
      }else lcd.print("      ");
       
  }

  if (menu == 100){
   
    if (encoderPosCount>3) encoderPosCount = 0;
    if (encoderPosCount<0) encoderPosCount = 3;
    menu_ = encoderPosCount;
    lcd.clear();
       lcd.setCursor(0, 0);
        lcd.print(">");
        lcd.setCursor(2, 0);
        switch (menu_) {
          case 0: if (avto==0) lcd.print("RUCHNIY"); else lcd.print("AUTO");
          break;
          case 1: lcd.print("CHAS OZONU");
          break;
          case 2: lcd.print("CHAS OSADU");
          break;
          case 3: lcd.print("VIHID");
          break;
        }
        
        lcd.setCursor(1, 1);
        switch (menu_) {
          case 0: lcd.print("CHAS OZONU");
          break;
          case 1: lcd.print("CHAS OSADU");
          break;
          case 2: lcd.print("VIHID");
          break;
          case 3: if (avto==0) lcd.print("RUCHNIY"); else lcd.print("AUTO");
          break;
        }
    
  }

    if (menu == 200){
      
        if (encoderPosCount>3) encoderPosCount = 0;
        if (encoderPosCount<0) encoderPosCount = 3;
        menu_ = encoderPosCount;
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print(">");
        lcd.setCursor(2, 0);
        switch (menu_) {
          case 0: if (nasos==0) lcd.print("nasos m1"); else lcd.print("NASOS M1");
          break;
          case 1: if (ozon==0) lcd.print("ozonator"); else lcd.print("OZONATOR");
          break;
          case 2: if (klapan==0) lcd.print("klapan kl1"); else lcd.print("KLAPAN KL1");
          break;
          case 3: lcd.print("VIHID");
          break;
        }
        
        lcd.setCursor(1, 1);
        switch (menu_) {
          case 0: if (ozon==0) lcd.print("ozonator"); else lcd.print("OZONATOR");
          break;
          case 1: if (klapan==0) lcd.print("klapan kl1"); else lcd.print("KLAPAN KL1");
          break;
          case 2: lcd.print("VIHID");
          break;
          case 3: if (nasos==0) lcd.print("nasos m1"); else lcd.print("NASOS M1");
          break;
        }
      
   }

   if (menu == 120){
      
        if (encoderPosCount>255) encoderPosCount = 255;
        if (encoderPosCount<0) encoderPosCount = 0;
        t_ozona = encoderPosCount;
        EEPROM.write(9, t_ozona);
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Chas ozonu");
        lcd.setCursor(0, 1);
        lcd.print(t_ozona); lcd.print(" hvilin");
      
   }

   if (menu == 130){
     
        if (encoderPosCount>255) encoderPosCount = 255;
        if (encoderPosCount<0) encoderPosCount = 0;
        t_osad = encoderPosCount;
        EEPROM.write(10, t_osad);
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Chas osadzhennia");
        lcd.setCursor(0, 1);
        lcd.print(t_osad); lcd.print(" hvilin");
   }
 }
}

Выводы

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

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

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

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

Подобные проекты можно заказать по телефонам, указанным сверху на сайте.

Написать отзыв

Примечание: HTML разметка не поддерживается! Используйте обычный текст.
    Плохо           Хорошо
Переходник USB 2.0 в TTL UART

Переходник USB 2.0 в TTL UART

Переходник на основе специализированной микросхемы CP2102 предназначен для связи различных устр..

56.73грн.

Универсальная плата под микросхемы FQFP 32 44 64 80 100

Универсальная плата под микросхемы FQFP 32 44 64 80 100

Универсальная монтажная плата, позволяет проводить монтаж различных микросхем с поверхностным монтаж..

17.50грн.

Датчик влажности и температуры DHT21

Датчик влажности и температуры DHT21

Это точный и готовый к вывешиванию прямо на улице датчик относительной влажности и температуры со ст..

108.32грн.

Драйвер шагового двигателя RAMPS 1.4 для CNC или 3D-принтера

Драйвер шагового двигателя RAMPS 1.4 для CNC или 3D-принтера

Драйвер A4988 под CNC-шилд RAMPS 1.4Есть возможность настраивать рабочий ток двигателя при помо..

35.55грн.

Мини кнопка SMD 6 x 6 x 5 мм

Мини кнопка SMD 6 x 6 x 5 мм

Миниатюрная кнопка с SMD выводамиРазмеры 6 x 6 x 5 мм..

0.64грн.