Счетчик воды, нужна помощь..

Waguka

✩✩✩✩✩✩✩
4 Ноя 2020
6
0
Добрый вечер, я рукожоп.. Сделал счетчик воды с выводом данных на страничку. При работе, особенно при большом расходе воды, счетчик холодной воды убегает вперед, т.е. есть ложные срабатывания. (С горячей водой несколько дней все хорошо, но там и расход меньше). Всю голову сломал. На макетной плате на кнопках все отрабатывает на отлично. Посмотрите свежим взглядом, может будут предположения, почему такое случается?

C++:
//--------------------------------Переменные------------------------------- 
int Cold = 0; //Переменная холодной воды
int Hot = 0; //Переменная горячей воды
int ColdPin = 5; // Пин холодной
int HotPin = 4; // Пин горячей
int MillisHot; //таймер времени обновления горячей воды
int MillisCold; // таймер времени обновления холодной воды
bool PreviousPinCold; //  Последний статус пина холодной воды 
bool PreviousPinHot; //  Последний статус пина горячей воды

//--------------------------------Код счета воды-------------------------------- 
  if (millis() - MillisCold > 500 && digitalRead(ColdPin) == HIGH) { //Если прошло время И текущий сигнал HIGH (кнопка отпущена)
  PreviousPinCold = 1;  // меняем статус флага
}
if (millis() - MillisHot > 500 && digitalRead(HotPin) == HIGH)  { //Если прошло время И текущий сигнал HIGH (кнопка отпущена)
  PreviousPinHot = 1;  // меняем статус флага
}
  if (digitalRead(ColdPin) == LOW && millis() - MillisCold > 500 && PreviousPinCold == 1) { //Если на пине сигнал LOW И время прошло И предыдущий сигнал был 1 (HIGH)
    Cold = Cold + 10; // Увеличиваем счетчик на величину литров тика счетчика
    PreviousPinCold =0; // Устанавливаем флаг состояния в 0 (LOW)
    MillisCold = millis(); // сбрасываем таймер срабатывания
  }
  if (digitalRead(HotPin) == LOW && millis() - MillisHot > 500 && PreviousPinHot == 1) {  //Если на пине сигнал LOW И время прошло И предыдущий сигнал был 1 (HIGH)
     Hot = Hot + 10; // Увеличиваем счетчик на величину литров тика счетчика
    PreviousPinHot =0; // Устанавливаем флаг состояния в 0 (LOW)
    MillisHot = millis(); // сбрасываем таймер срабатывания
  }
Счетчики VALTEC, подключены по релейной схеме в режиме INPUT_PULLUP с резистором на пин 1 кОм.
По паспорту счетчиков, максимальный расход через счетчик 2.5 куба в час (0,69 литров в секунду), срабатывание геркона каждые 10 литров.

При подключении счетчиков пользовался этим: http://www.bizkit.ru/2018/12/21/11181/
 

poty

★★★★★★✩
19 Фев 2020
3,228
939
@Старик Похабыч, хороший совет дал, но недостаточный. Если счётчик воды убегает, то проблема не с потерей пульсов, а с их избытком, очевидно из-за дребезга контактов. Т.е., первый вариант - подключить контакты счётчика к ардуино через интегрирующую цепочку (RC), либо через триггер Шмитта. Оба "аппаратных" варианта стоят копейки и действуют надёжнее всего.
Если заморачиваться не хочется, то можно воспользоваться многочисленными библиотеками, осуществляющими программный метод избавления от дребезга, хоть и гайверовской кнопочной. Либо самому учесть это в коде, что Вы, похоже, и попытались сделать. Только ошиблись немного:
C++:
//--------------------------------Константы--------------------------------
#define DebounceTime    20    // Время ожидания антидребезга
//--------------------------------Переменные-------------------------------
long Cold = 0;                 // Переменная холодной воды
long Hot = 0;                 // Переменная горячей воды
byte ColdPin = 5;             // Пин холодной
byte HotPin = 4;             // Пин горячей
unsigned long MillisHot;    // Таймер антидребезга горячей воды
unsugned long MillisCold;     // Таймер антидребезга холодной воды
bool PreviousPinCold;         // Последний статус пина холодной воды (true = разомкнуто)
bool SwitchDetectedCold;    // Определён переход между состояниями счётчика холодной воды (true = фронт/срез)
bool PreviousPinHot;         // Последний статус пина горячей воды (true = разомкнуто)
bool SwitchDetectedHot;        // Определён переход между состояниями счётчика горячей воды (true = фронт/срез)

void setup() {
                            // Первичная инициализация переменных
    PreviousPinCold = digitalRead(ColdPin);
    SwitchDetectedCold = false;
    PreviousPinHot = digitalRead(HotPin);
    SwitchDetectedHot = false;
}

void loop() {
    //--------------------------------Код счета воды--------------------------------
    if (SwitchDetectedCold) {
                            // отработка дебоунсинга
        if (millis() - MillisCold >= DebounceTime) {
                            // время ожидания дребезга прошло
            SwitchDetectedCold = false;
                            // новое состояние порта
            PreviousPinCold = digitalRead(ColdPin);
                            // Увеличиваем счетчик на величину литров тика счетчика
            if (!PreviousPinCold) Cold += 10;
        }
    } else if (PreviousPinCold != digitalRead(ColdPin)) {
        SwitchDetectedCold = true;
        MillisCold = millis();
    }
    if (SwitchDetectedHot) {
                            // отработка дебоунсинга
        if (millis() - MillisHot >= DebounceTime) {
                            // время ожидания дребезга прошло
            SwitchDetectedHot = false;
                            // новое состояние порта
            PreviousPinHot = digitalRead(HotPin);
                            // Увеличиваем счетчик на величину литров тика счетчика
            if (!PreviousPinHot) Hot += 10;
        }
    } else if (PreviousPinHot != digitalRead(HotPin)) {
        SwitchDetectedHot = true;
        MillisHot = millis();
    }
}
По хорошему, конечно, нужно записывать в энергонезависимую память хотя бы периодически, чтобы сохранить счётчик между выключениями питания.
И я бы сделал это на С++, было бы гораздо нагляднее.
 
Изменено:
  • Лойс +1
Реакции: Waguka

Старик Похабыч

★★★★★★★
14 Авг 2019
4,262
1,300
Москва
@poty, Там по ссылке есть статья, где описано применение прерываний, и достаточно подробно описано аппаратное устранение дребезга. Автор наверняка читал это.
 

poty

★★★★★★✩
19 Фев 2020
3,228
939
@Старик Похабыч, согласен. Но, как всегда у начинающих, - надежда, что "минёт чаша сия". Впрочем, о чём говорить про начинающих, если практически всё, что на этом форуме я видел, использует программные методы антидребезга...
 

Waguka

✩✩✩✩✩✩✩
4 Ноя 2020
6
0
Добрый день! Что то новогодние праздники закрутили. Спасибо за ответы.
Я конечно же читал статьи и по дребезгу контактов и там же (если покопаться) есть расчет RC цепи, развод печатной платы и т.д.
poty, попробовал ваш вариант кода, появились пропуски регистрации нажатий.
Попробовал использовать прерывания по Falling, результат не изменился. Собрал обратно на макетной плате с кнопками, сидел их давил и медленно, и быстро, и двойными кликами, на кнопках все отрабатывает отлично, но как только подключаешь к счетчику, все идет не так. Самое плохое, я не вижу в коде ошибку в логике :( Что еще можно посмотреть или переделать подключение на аппаратное подавление дребезга через RC цепочку и посмотреть на поведение той же прошивке?
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,262
1,300
Москва
Может провод взять потолше и покороче от счетчика к ардуино ? И для начала попробовать на макете сделать провод точно такой же как от счетчика к ардуино идет.
Может еще как то сказывается то, что трубы заземелны ?
 

poty

★★★★★★✩
19 Фев 2020
3,228
939
Из описания @Waguka, у меня несколько вопросов:
1. Как определили, что идёт пропуск импульсов?
2. Известно ли соотношение замкнутого и разомкнутого состояний геркона? Можно попробовать подключиться обычным тестером в режиме прозвона, чтобы понять, что там на выходе. Если один из импульсов в районе 20мс, то, возможно, антидребезг на предваряющем фронте нужно убрать. Можно также добавить пару светодиодов на выходы Ардуино, отражающих состояние входа.
3. INPUT_PULLUP включен на оба входа? Каким образом? Можно просто рисунок нарисовать как это всё соединено.

Попробовал использовать прерывания по Falling, результат не изменился.
Вот это не понял. В приведённом скетче нет прерываний.
 

Waguka

✩✩✩✩✩✩✩
4 Ноя 2020
6
0
Из описания @Waguka, у меня несколько вопросов:
1. Как определили, что идёт пропуск импульсов?
2. Известно ли соотношение замкнутого и разомкнутого состояний геркона? Можно попробовать подключиться обычным тестером в режиме прозвона, чтобы понять, что там на выходе. Если один из импульсов в районе 20мс, то, возможно, антидребезг на предваряющем фронте нужно убрать. Можно также добавить пару светодиодов на выходы Ардуино, отражающих состояние входа.
3. INPUT_PULLUP включен на оба входа? Каким образом? Можно просто рисунок нарисовать как это всё соединено.

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

2. Из паспорта счетчика:
  • Вес импульса (для счетчиков с импульсным выходом) дм3/имп 10​
  • Максимальный коммутируемый ток мА 100​
  • Максимальное коммутируемое напряжение В 24​
  • Сечение кабеля шт х мм2 4х0,11​
  • Длина кабеля м 1​
  • Длительность импульса с 0,6​
По номинальному расходу, можем определить минимальное время между импульсами - 6.9 секунд.
Тестером уже тыкал, замкнутое состояние может продолжаться сколь угодно долго, если воду остановить.


3. Да, INPUT_PULLUP подключен на оба входа, счетчики подключены согласно паспорту по релейной схеме:
1609836391707.png
Ардуинка подключена через резистор 1кОм, к портам D1 или D2, к одному проводу от счетчика и землей к другому.

Untitled Sketch 2_bb.jpg

p.s. С прерываниями я сюда не выкладывал, просто разнес код счета воды в функции по прерыванию FALLING.
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,407
974
58
Марий-Эл
Ну это очевидно, после подключения к счетчикам и выставления начальных значений, через некоторое время цифра счетчика ардуино не совпадает с цифрой на счетчике воды.
Ни капли не очевидно.
У нас ставятся электросчётчики, которые передают информацию о расходе на центральный пульт.
Так они накручивают на 8% больше, чем домовые счётчики. У нас по этому поводу идёт ужа вал судебных разбирательств.
 

poty

★★★★★★✩
19 Фев 2020
3,228
939
  • Длительность импульса с 0,6​
По номинальному расходу, можем определить минимальное время между импульсами - 6.9 секунд.
Тестером уже тыкал, замкнутое состояние может продолжаться сколь угодно долго, если воду остановить.
ОК! Т.е., Импульс (мин. 0,6с) - это когда замыкается геркон? Между импульсами минимум 6,9 секунд? Это подтверждается мультиметром?
Предлагаю подключить внешние резисторы PULL_UP (от каждого белого провода на +5В, прямо на плате, примерно 3-5кОм). Внутренний резистор в МП слишком велик для длинных проводов, будет собирать помехи, когда геркон разомкнут.
С прерываниями я сюда не выкладывал, просто разнес код счета воды в функции по прерыванию FALLING.
Ну, значит обсуждать здесь этого кота в мешке бессмысленно.
 
  • Лойс +1
Реакции: Waguka

Waguka

✩✩✩✩✩✩✩
4 Ноя 2020
6
0
ОК! Т.е., Импульс (мин. 0,6с) - это когда замыкается геркон? Между импульсами минимум 6,9 секунд? Это подтверждается мультиметром?
Предлагаю подключить внешние резисторы PULL_UP (от каждого белого провода на +5В, прямо на плате, примерно 3-5кОм). Внутренний резистор в МП слишком велик для длинных проводов, будет собирать помехи, когда геркон разомкнут.

Ну, значит обсуждать здесь этого кота в мешке бессмысленно.
Длина импульса, это по паспорту счетчика, время на которое замыкается геркон, не представляю как мультиметром подтвердить длину импульса в 0,6 секунды. о_О. Теоретически погрешность всегда будет, зависит от давления в системе водоснабжения. Отклонение в меньшую сторону будут на первых этажах многоэтажных зданий.

Если правильно понял, код будет таким:

C++:
// --------- объявление переменных ---------
volatile int Cold = 0; //Переменная холодной воды
volatile int Hot = 0; //Переменная горячей воды
byte ColdPin = 5; // Пин холодной D1
byte HotPin = 4; // пин горячей D2
volatile unsigned long MillisHot; //таймер обновления горячей воды
volatile unsigned long MillisCold; // таймер обновления холодной воды
volatile bool PreviousPinCold;         // Последний статус пина холодной воды (true = разомкнуто)
volatile bool SwitchDetectedCold;    // Определён переход между состояниями счётчика холодной воды (true = фронт/срез)
volatile bool PreviousPinHot;         // Последний статус пина горячей воды (true = разомкнуто)
volatile bool SwitchDetectedHot;        // Определён переход между состояниями счётчика горячей воды (true = фронт/срез)


void setup(void) {
  // --------- подготовка пинов на вход, схема с подтягивающим резистором---------
  pinMode(ColdPin, INPUT);
  pinMode(HotPin, INPUT);
  // --------- первичная инициализация переменных---------
  PreviousPinCold = digitalRead(ColdPin);
  PreviousPinHot = digitalRead(HotPin);
  SwitchDetectedCold = false;
  SwitchDetectedHot = false;
  // --------- инициализация Прерываний---------
  attachInterrupt(digitalPinToInterrupt(HotPin), HotWaterTick, FALLING);
  attachInterrupt(digitalPinToInterrupt(ColdPin), ColdWaterTick, FALLING);
    
}

void HotWaterTick() {
  if (SwitchDetectedHot) {
    // отработка дебоунсинга
    if (millis() - MillisHot >= 30) {  // время ожидания дребезга прошло  (300мс).
      SwitchDetectedHot = false;   // Флаг переводим в "Кнопка нажата"
      PreviousPinHot = digitalRead(HotPin);   // Записываем новое состояние порта
      if (!PreviousPinHot) Hot += 10; // Увеличиваем счетчик на величину литров тика счетчика
    }
  } else if (PreviousPinHot != digitalRead(HotPin)) { // Если предыдущее состояние порта отличается от текущего
    SwitchDetectedHot = true; // Флаг переводим в "кнопка отпущена"
    MillisHot = millis(); // Устанавливаем время
  }
}

void ColdWaterTick() {
  if (SwitchDetectedCold) {
    // отработка дебоунсинга
    if (millis() - MillisCold >= 30) {  // время ожидания дребезга прошло
      SwitchDetectedCold = false; // Флаг переводим в "Кнопка нажата"
      PreviousPinCold = digitalRead(ColdPin);   // новое состояние порта
      if (!PreviousPinCold) Cold += 10;     // Увеличиваем счетчик на величину литров тика счетчика
    }
  } else if (PreviousPinCold != digitalRead(ColdPin)) {
    SwitchDetectedCold = true; // Флаг переводим в "кнопка отпущена"
    MillisCold = millis(); // Устанавливаем время
  }
И подключаюсь по схеме:
Untitled Sketch 2_bb.png
Смог найти только 1кОм резисторы :(
 

poty

★★★★★★✩
19 Фев 2020
3,228
939
Второй раз не улавливаете мысль. В скетче мы пытаемся отловить как фронт импульса, так и срез. Нужно определиться, что является импульсом: замыкание - импульс - размыкание или размыкание - импульс - замыкание? И как-то оценить паузу между импульсами. Я не призываю Вас оценивать длительность, главное, чтобы это было больше 20-30 миллисекунд.
Резисторы пока можно последовательно соединить, сейчас просто нужно эту возможность деструктивного воздействия исключить.
 

Waguka

✩✩✩✩✩✩✩
4 Ноя 2020
6
0
Второй раз не улавливаете мысль. В скетче мы пытаемся отловить как фронт импульса, так и срез. Нужно определиться, что является импульсом: замыкание - импульс - размыкание или размыкание - импульс - замыкание? И как-то оценить паузу между импульсами. Я не призываю Вас оценивать длительность, главное, чтобы это было больше 20-30 миллисекунд.
Резисторы пока можно последовательно соединить, сейчас просто нужно эту возможность деструктивного воздействия исключить.
Видимо, я действительно не понимаю.. Физический смысл импульса - замыкание и размыкание геркона и тут все равно что считать. Можно считать количество замыканий, можно количество размыканий, это величины равнозначные, разнесенные по времени. Скважность, период и длительность импульсов в данном случае величины не постоянные, мы можем оперировать только техническими характеристиками из паспорта счетчика, а паспорт говорит, что минимальная длительность импульса 0,6 сек, что намного превышает время дребезга контактов. Именно поэтому в первом варианте скетча я использовал задержки по 500 мс, заведомо выше дребезга, но меньше минимальной длительности импульса.
Постараюсь завтра сегодня все пересобрать и поставить на тестирование.
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,262
1,300
Москва
Если использовать прерывания, то довольно просто на ардуино сделать и тестовую площадку. К примеру импульсы считаются в прерывании. Вывод кол-ва импульсов идет в сериал с задержкой через миллис в цикле loop. Там же, в цикле loop можно генерировать импульсы, которые будет считать прерывание на любом свободном пине. И тоже их считать. И даже сравнивать можно.
 

poty

★★★★★★✩
19 Фев 2020
3,228
939
Да мне кажется проще светодиодами проверить, если такие длительные импульсы. Надо скетч посмотреть, я последний вариант с прерываниями ещё не смотрел.
 

Waguka

✩✩✩✩✩✩✩
4 Ноя 2020
6
0
И снова, здравствуйте!
Скетч не рабочий, я поторопился.
сейчас счетчик подключен через 4кОм подтягивающий резистор.

Результаты тестирования:
Начальное значение счетчика (ардуино и счетчик выставлены одинаково)
100910​
Конечное значение счетчика адруино
101680​
Конечное значение счетчика на трубе
101360​
Должно быть учтено количество импульсов
45​
Учтено импульсов ардуино
77​
Учтено лишних импульсов ардуино
32​

Лог с ардуинки собирал по всем входам в прерывание, получилась табличка такого вида (полная версия во вложении):

Строка ColdПоказания счетчика. lastDebounceColdВремя изменения счетчика, мсек. millisВремя записи в лог, мсек.Время между входами в прерывание, сек.Флаг (1 - счетчик менялся, 0- не менялся)Расход, м3\час
5​
Cold
100950​
lastDebounceCold
7146218​
millis
7146256​
92,2​
1​
0,39​
6​
Cold
100960​
lastDebounceCold
7154523​
millis
7154564​
8,3​
1​
4,33​
7​
Cold
100970​
lastDebounceCold
7250297​
millis
7250337​
95,8​
1​
0,38​
8​
Cold
100980​
lastDebounceCold
7357568​
millis
7357608​
107,3​
1​
0,34​

Из лога видно, что задержка для устранения дребезга контактов отрабатывает(вход в прерывание без изменения счетчика Cold, строки 8-9, 14-15 и т.д. ), минимальное время между срабатыванием счетчика = 0,52 сек.
Нам известно время между срабатыванием счетчика и объем воды, который за это время проходит, рассчитываем расход для каждого интервала и сравниваем полученный расход с расходом из паспорта счетчика, таким образом, можем отсечь 25 ложных срабатываний. В таблице (строки 46-66) есть непонятная область, на протяжении 22 секунд геркон колбасило. Есть идеи, чем это может быть вызвано?
Код подсчета воды сейчас простой, debounceDelay =500мс. код взят из статьи:

C++:
void ColdWaterTick() {
  if ((millis() - lastDebounceCold) > debounceDelay) {
    Cold += 10;
    lastDebounceCold = millis();
  }
}
 

Вложения

  • 7 KB Просмотры: 8