Двухступенчатое водоснабжение загородного дома

Тема в разделе "Глядите, что я сделал", создана пользователем Angbor, 18 дек 2015.

  1. Angbor

    Angbor Нерд

    Всем привет.

    Делаю водоснабжение для своего дома в деревне. Добрался до автоматики, ну и остановился на ардуинке. Никогда с ней раньше не имел дела. Заказал на алиэкспрессе, помигал дидиком :)

    Вот добрался до описания проекта, и надеюсь на ваши комментарии.

    Проще всего объяснить на видео.

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

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

    Открыли кран, насосная станция начала забирать воду из емкости. Когда уровень ушел ниже среднего датчика в емкости, включается скваженый насос.

    Теперь самое сложное. Специфика скважины - если часа 3 из скважины не выкачивали воду, то первые минут 15 пойдет мергель. Так что нужно эту воду слить. Перед емкостью стоит трехходовой клапан. Если по таймеру с последнего включения скваженного насоса прошло более 3-х часов, то вода минут 15 будет сливаться на улицу в накопительный пруд в конце участка. Уровень среднего датчика в емкости надо будет так подобрать, чтобы при максимальном расходе потребителем воды хватило как раз на эти 15 минут слива.

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

    Постепенно посылки с Китаю приходят и я начинаю экспериментировать.
    Логика кода вот такая (извините за много стрелочек :)
    Без аварийного уровня.png
    Код (Text):
    ОПИСАНИЕ ЛОГИКИ

    Существует 6 состояний системы, исходя из состояния датчиков.

    1.    Замкнут верхний датчик (TOP) перелива. Аварийное состояние.
    2.    Замкнуты все три датчика уровня (A1, A2, A3). Полная емкость.
    3.    Замкнуты два датчика (A2, A3). Емкость неполная, включение насоса не требуется (идет расход воды после максимума).
    4.    Замкнуты два датчика (A2, A3). Емкость неполная, насос включен. Идет закачка воды после выключения второго датчика.
    5.    Замкнут только нижний датчик (A3). Включение насоса (если не включен). Идет закачка воды.
    6.    Разомкнут нижний датчик A3. Аварийная состояние (исключение режим FORCE).

    Предполагается, что система может начать работу в любом из 6 состояний, при этом насос не включен: замкнут датчик перелива, замкнуты все три датчика, только два, только один, ни один.
    При первичном включении и пустом баке предусмотреть пропуск анализа нижнего датчика А3 в течении 5 минут.

    Последовательность
    •    Проверка на переполнение. Если да – аварийный режим.
    •    Нет – диодик ОК
    •    Проверяем не нажата ли кнопка форсажа (первичное включение). Да – запускаем обратный таймер FORCE.
    •    Проверяем включен ли таймер простоя. Если нет – то это первое включение и принудительно прокачиваем на улицу 15 минут. Если включен, то проверяем А1.
    •    А1 замкнут. Диодик. Таймер включен? Нет – включаем.
    •    Выключаем насос (если был включен)
    •    Дальше цикл проверки оставшихся датчиков. Если кто-то не работает при рабочем верхнем, значит это глюк (за исключением режима «форсаж» для нижнего датчика). Переходим в аварийный режим. Поскольку это может быть первым включением, то зажигаем зеленые диодики в случае ОК по остальным датчикам.
    •    Если А1 разомкнут. Красный диодик.
    •    А2 замкнут? Если да, зажигаем зеленый диодик.
    •    Насос включен? Если да, то мы в режиме наполнения. Проверим нижний датчик. Если ОК (или «форсаж») на начало.
    •    Если выключен – проверяем включен ли был таймер простоя. Нет – включаем. Проверка А3 и т.д.
    •    А2 разомкнут. Красный диодик.
    •    Если таймер простоя > 3 часов, то слив. Таймер простоя останавливаем и сбрасываем. Включаем вентиль, задержка на перекрытие, включаем насос. 15 минут сливаем, переключаем вентиль.
    •    Если А2 не успел включиться к следующему циклу, то таймер еще будет остановлен. Проверяем работает ли насос.
    •    Если насос работает, то значит перед этим мы только что делали слив и находимся в режиме заполнения. Идем в начало цикла.
    •    Если насос выключен, значит это первое включение системы. Тогда идем в цикл слива. И т.д.

    Предусмотреть после включения режим паузы в ожидании нажатии кнопки СТАРТ. В это время можно установить режимы СЛИВ и/или FORCE, просмотреть состояние датчиков, войти в режим настройки параметров и т.д.
     
    Последнее редактирование: 18 дек 2015
  2. Angbor

    Angbor Нерд

    Текущее состояние кода
    Код (C++):
    // определяем пины для переключателей
    #define TOP_SWITCH      2
    #define MIDDLE_SWITCH   3
    #define BOTTOM_SWITCH   4
    #define OVERFLOW_SWITCH 5

    #define BLINK_DELAY   200

    // устанавливаем управление сдвиговым регистром
    int latchPin  = 8;
    int clockPin  = 12;
    int dataPin   = 11;
    uint8_t ledsBurn = 0; //переменная для передачи в регистр

    // остальное
    byte switchState[] = {0, 0, 0, 0}; // состояние переключателей
    byte lastSwitchState[] = {0, 0, 0, 0}; // предыдущее состояние
    byte flagS[] = {0, 0, 0, 0}; // флаги для хранения состояний переключателей
    byte blinkF[] = {0, 0, 0, 0}; //флаги для включения blink
    bool flagT = false; // временный флаг
    String ledSName[] = {"Top switch", "Middle switch", "Bottom switch", "OverFlow switch"}; // названия для вывода в порт

    byte ledS[] = {B00000001, B00000010, B00000100, B00001000}; //тут храним значения для "зажигания" диодов. Пока их три

    void setup() {
      // настройка сдвигового регистра
      pinMode(latchPin, OUTPUT);
      pinMode(clockPin, OUTPUT);
      pinMode(dataPin, OUTPUT);

      // инициализация кнопок
      pinMode(TOP_SWITCH, INPUT);
      pinMode(MIDDLE_SWITCH, INPUT);
      pinMode(BOTTOM_SWITCH, INPUT);
      pinMode(OVERFLOW_SWITCH, INPUT);

      // инициализируем порт
      Serial.begin(9600);
    }


    void led_switching (int switchNumber) {
      if ((switchState[switchNumber] == HIGH) && (flagS[switchNumber] == 0)) {
        // если состояние HIGH и флаг не был установлен
        // включаем
        Serial.println(ledSName[switchNumber] + " is ON");
        ledsBurn = ledsBurn ^ ledS[switchNumber]; // побитовое XOR
        blinkF[switchNumber] = 1; // установка флага для blink на все подряд
        // по-хорошему надо бы как-то по-другому
        flagS[switchNumber] = 1;
      }
      else {
        if (switchState[switchNumber] == HIGH && flagS[switchNumber] == 1) {
          // если состояние LOW
          // выключаем
          Serial.println(ledSName[switchNumber] + " is OFF");
          if (bitRead(ledsBurn, switchNumber) == 1) {
            ledsBurn = ledsBurn ^ ledS[switchNumber];
          }
          blinkF[switchNumber] = 0;
          flagS[switchNumber] = 0;
        }
      }
      // немного ждем
      delay(10);
      lastSwitchState[switchNumber] = switchState[switchNumber];//обновляем значение последнего состояния переключателя
    }

    void loop() {

      // читаем состояние переключателей
      switchState[0] = digitalRead(TOP_SWITCH);
      switchState[1] = digitalRead(MIDDLE_SWITCH);
      switchState[2] = digitalRead(BOTTOM_SWITCH);
      switchState[3] = digitalRead(OVERFLOW_SWITCH);

      //в случае изменения состояния переключателей идем в функцию
      if (switchState[0] != lastSwitchState[0]) {
        led_switching(0);
      }
      if (switchState[1] != lastSwitchState[1]) {
        led_switching(1);
      }
      if (switchState[2] != lastSwitchState[2]) {
        led_switching(2);
      }
      if (switchState[3] != lastSwitchState[3]) {
        led_switching(3);
      }
      // мигаем
      if (blinkF[3] == 1) {
        if ((millis() / BLINK_DELAY) % 2 == 1)   {
          if (!flagT) {
            ledsBurn = ledsBurn ^ ledS[3];
            flagT = true;
          }
        }
        else
        {
          flagT = false;
        }
      }

      // работаем со сдвиговым регистром
      digitalWrite(latchPin, LOW);
      shiftOut(dataPin, clockPin, MSBFIRST, ledsBurn);
      digitalWrite(latchPin, HIGH);
    }
    4 кнопки через опторазвязку, 4 диода через сдвиговый регистр.
    Последний диодик мигает без использования delay.
    Для унификации решил в led_switching по любому устанавливать флаги "мигания". Ну и потом уже жестко определяю, что это только для 4 диодика.

    Все работает, но от кода ощущение какой-то коряватости...
    Может какие предложения будут?
     
  3. Angbor

    Angbor Нерд

    тишина...
     
  4. sanik

    sanik Гик

    Для чего нужны лишние сдвиговые регистры? И странное подключение кнопок?
     
  5. DrProg

    DrProg Вечный нерд

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

    Что именно вас смущает в решении? Сузьте задачу.

    ПС: единственное, что мне поцарапало глаз: если у вас состояния всех переключателей считывается в массив, зачем вы обрабатываете его в столбик, если можно в цикле?
    Код (C++):
    for (byte i = 0; i < 5; i++) {
    if (switchState[i] != lastSwitchState[i]) {
        led_switching(i);
      }
    }
    И еще, если придираться, запись вида:
    Код (C++):
    if (blinkF[3] == 1) {
    Может быть сокращена до:
    Код (C++):
    if (blinkF[3]) {
    Ну и, понятное дело, shiftin() заменить на что то вроде:
    Код (C++):
    void writeByteP(byte byteW) {  // аналог shiftOut, работает раз в 200 быстрее
      for (int i = 7; i >= 0; i--)
      {
        if (bitRead(byteW, i)) {
          PBdigWH(DATA_PIN);
        } else {
          PBdigWL(DATA_PIN);
        }
        PBdigWH(CLOCK_PIN);
        PBdigWL(CLOCK_PIN);
      }
    }

    inline void PBdigWH(byte NB) {
      PORTB |= 1 << NB;
    }

    inline void PBdigWL(byte NB) {
      PORTB &= ~(1 << NB);
    }
    (вариант для Attiny85, под другой МК требуется допиливание).
     
    Последнее редактирование: 24 дек 2015
    Angbor и ИгорьК нравится это.
  6. Angbor

    Angbor Нерд

    не совсем понимаю ваш вопрос
    в каком смысле странное подключение? и что вас смущает в сдвиговом регистре (одна штука)?
     
  7. Angbor

    Angbor Нерд

    от спасибо :)
    про диодики не мигающие уже столько всего написано, что спрашивать - ну просто публично признаться в своей лени разобраться.
    спасибо за подсказки
    я тут неделю был вне интернета, но с ноутом. Кое что поковырял, попозже выложу. и интересно, что ход моих мыслей был также в сторону большего использования циклов
    про shiftin и замену - нужно поразмыслить и понять
     
  8. ИгорьК

    ИгорьК Гуру

    ИМХО, будьте внимательнее верить всему подряд.
    if (blinkF[3]) { - правильно использовать с булевыми данными, а не с байтами, как у Вас.
    За одно получите объяснение зачем перед функцией стоит inline.
    Кроме того, думаю Вам было бы полезным разобраться с тем, что такое битовое поле.
     
    Последнее редактирование: 28 дек 2015
    Angbor нравится это.
  9. Angbor

    Angbor Нерд

    это понятно
    попробую
    начал разбираться - выглядит хорошо. разберусь подробнее, добавлю :)

    Так, вот результат моих off-line размышлений
    стал использовать for и bitWrite
    над остальными советами думаю
    Код (C++):
    // определяем пины для переключателей
    #define TOP_SWITCH      2
    #define MIDDLE_SWITCH   3
    #define BOTTOM_SWITCH   4
    #define OVERFLOW_SWITCH 5
    #define BLINK_DELAY   200

    // устанавливаем управление сдвиговым регистром
    int latchPin  = 8;
    int clockPin  = 12;
    int dataPin   = 11;
    uint8_t ledsBurn = 0; //переменная для передачи в регистр

    // остальное
    byte switchState[] = {0, 0, 0, 0}; // состояние переключателей
    byte lastSwitchState[] = {0, 0, 0, 0}; // предыдущее состояние
    byte flagS[] = {0, 0, 0, 0}; // флаги для хранения состояний переключателей
    byte blinkF[] = {0, 0, 0, 1}; /*флаги для включения blink, здесь указываем по умолчанию какие диоды будут мигать (1),
                                  а какие гореть постоянно (0)*/

    bool flagT[] = {false, false, false, false}; // временный флаг

    String ledSName[] = {"Top switch", "Middle switch", "Bottom switch", "OverFlow switch"}; // названия для вывода в порт
    byte  ledS[] = {B00000001, B00000010, B00000100, B00001000}; //тут храним значения для "зажигания" диодов. Пока их три

    void setup() {
      // настройка сдвигового регистра
      pinMode(latchPin, OUTPUT);
      pinMode(clockPin, OUTPUT);
      pinMode(dataPin, OUTPUT);

      // инициализация кнопок
      for (int i = 2; i < 6; i++) {
        pinMode(i, INPUT);
      }
      // инициализируем порт
      Serial.begin(9600);
    }

    void led_switching (int switchNumber) {
      if ((switchState[switchNumber] == HIGH) && (flagS[switchNumber] == 0)) {
        // если состояние HIGH и флаг не был установлен
        // включаем
        Serial.println(ledSName[switchNumber] + " is ON");
        bitWrite(ledsBurn, switchNumber, 1);
        flagS[switchNumber] = 1;
      }
      else {
        if ((switchState[switchNumber] == HIGH) && (flagS[switchNumber] == 1)) {
          // если состояние LOW
          // выключаем
          Serial.println(ledSName[switchNumber] + " is OFF");
          bitWrite(ledsBurn, switchNumber, 0);
          flagS[switchNumber] = 0;
        }
      }
      // немного ждем
      delay(10);
      lastSwitchState[switchNumber] = switchState[switchNumber];//обновляем значение последнего состояния переключателя
    }

    void loop() {
      // читаем состояние переключателей
      for (int i = 2; i < 6; i++) {
        switchState[i - 2] = digitalRead(i);
      }
      //в случае изменения состояния переключателей идем в функцию
      for (int i = 0; i < 4; i++) {
        if (switchState[i] != lastSwitchState[i]) {
          led_switching(i);
        }
      }
      // мигаем

      for (int i = 0; i < 4 ; i++) {
        if ((blinkF[i] == 1) && (flagS[i]) == 1) {//добавил проверку установки флага flagS и теперь не сбрасываю blinkF
          if ((millis() / BLINK_DELAY) % 2 == 1)   {
            if (!flagT[i]) {
              /* если не устанавливать этот флаг, то диод будет мелко мигать пока
                               значение millis() в допустимом диапазоне значений */

              ledsBurn = ledsBurn ^ ledS[i];
              //попробовать использовать сдвиг влево в байте 00000001 [i-1] вместо хранения всех вариантов в массиве
              flagT[i] = true;
            }
          }
          else
          {
            flagT[i] = false;
          }
        }
      }
      // работаем со сдвиговым регистром
      digitalWrite(latchPin, LOW);
      shiftOut(dataPin, clockPin, MSBFIRST, ledsBurn);
      digitalWrite(latchPin, HIGH);
    }
     
    Последнее редактирование: 28 дек 2015
  10. ИгорьК

    ИгорьК Гуру

    Работает? Не трогайте!
    А если из любви к искусству - тогда еще разберитесь с обращением к массиву по ссылке. Массив перещелкивается быстрее не перебором элементов а арифметикой указателей. Гораздо быстрее.
    Теперь можете поразмышлять вот над чем: if ((blinkF == 1) менять на if ((1 == blinkF). Рекомендую ввести в привычку такую странную запись. Зачем? Поразмышляйте сами, подсказка: что будет в обоих случаях, если вместо == Вы случайно напечатаете = .
     
    Последнее редактирование: 29 дек 2015
  11. Angbor

    Angbor Нерд

    я конечно понимаю, что Worse is Better. Но с другой стороны не на ПК исполняться будет.
    интересно, поставлю на очередь изучения. а вы на чем основываете этот вывод?
    а я все равно с ":=" и "=" переучиваюсь и мне по любому внимание включать приходится
    ну и потом есть у меня некоторые стереотипы, как должно выглядеть удобо-читаемый код.
    по факту - ваш метод необычен, но хорош
     
    Последнее редактирование: 29 дек 2015
  12. Angbor

    Angbor Нерд

    Возникла странная ситуация - если загружаю скетч из Arduino IDE - то скетч работает криво (диоды не загораются, но в порт все пишется).
    Если выключить плату из USB и включить снова (или нажать reset) то скетч работает нормально.
    Шо це такэ?
     
  13. ИгорьК

    ИгорьК Гуру

    Это новогоднее... или это не Уно. Ибо Леонардо etc. требует перезагрузки.
     
    Последнее редактирование: 1 янв 2016
  14. Angbor

    Angbor Нерд

    а почему до этого месяца полтора он ничего такого не требовал, а сразу выполнял скетч после заливания?

    Сейчас вспомнил, после чего это могло произойти.
    Мне пришел китайский клон Arduino NANO, ну он конечно IDE-шкой не определялся. Закачал дрова специальные для него, установил - все пучком. NANO определяется и все в нее заливается.
    Но вот видимо что-то эти дрова добавили "необратимого", чего до этого не было...
     
    Последнее редактирование: 1 янв 2016
  15. Angbor

    Angbor Нерд

    и при этом еще странность
    заливаю скетч, открываю порт - в порт выводится все правильно (т.е. обработка нажатия кнопок), а вот диоды не загораются
    после перезагрузки - все ОК и то и другое работает...
     
  16. Angbor

    Angbor Нерд

    кстати о битовых полях - памяти занимает больше, код выглядит более громоздким (не понимаю почему)
    вернулся к массивам
    а быстродействие мне в данном проекте фиолетово
     
  17. ИгорьК

    ИгорьК Гуру

    Это особенность платы - после открытия порта она не перегружается, как делает стандартная Уно. Соответственно, пины не инициализируются как выходные. После сброса все работает нормально.
     
  18. Angbor

    Angbor Нерд

    дык толкую же, что этого не было с этой платой никогда на протяжении всего месяца с хвостиком.
     
  19. Angbor

    Angbor Нерд

    не знаю, что произошло - но теперь плата опять после загрузки скетча работает сразу без всяких перетыканий и перезагрузок
    в общем - веселуха
     
  20. Angbor

    Angbor Нерд

    Так, ну вот даже записал себе конспект в Fritzing :)
    WaterSystem_mid.jpg
    Не нашел нормального шилда LCD с кнопками. Так что просто подписал подключение двух пинов шилда. Понятное дело, что эти монстрообразные кнопки представляют собой поплавковые выключатели. Первая кнопка специально инвертирована (по сравнению с остальными 3-мя)

    Код написал, работает. Хочу немного причесать и вечерком выложу