• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Делюсь опытом Изобрел велосипед, точнее снятие показаний счетчиков воды на Wemos D1 mini :))

Slacky

Member
Здесь предлагается альтернативный (по сравнению со скетчем @Slacky) подход - не на прерываниях, а на непосредственном анализе состояния пинов в loop. Наверно, так тоже можно, но переделки более существенные, чем предлагаемые мной.
На "не прерываниях" вряд ли получится. Если проверку осуществлять непосредственно из loop(), то будут пропуски импульсов, если например 8266 подключается к роутеру по WiFi или пытается засинхронизироваться по NTP ...
 

Slacky

Member
Супер! Я боялся, что Slacky нашел неведомую особенность счетчиков, которая вынудила к такому решению. ))
Да, kab, верно написал.
Не, просто при срабатывании геркона прибавляется не 10 литров, а от 10 до 130 ... Ну и аппаратный устранитель дребезга мне показался и проще и надежней ...
 

kab

New member
На "не прерываниях" вряд ли получится. Если проверку осуществлять непосредственно из loop(), то будут пропуски импульсов, если например 8266 подключается к роутеру по WiFi или пытается засинхронизироваться по NTP ...
Да, согласен.
 

Slacky

Member
В общем я тут немного поэкспериментировал :)))

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

Код:
extern "C" {
#include "user_interface.h"
}

os_timer_t hotTimer;

#define HOT_PIN D1
#define TIME_BOUNCE 50

volatile unsigned long counterHotWater;
int avail;


void setup() {
  Serial.begin(115200);
 
  pinMode(HOT_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(HOT_PIN), hotInterrupt, RISING);

  os_timer_setfn(&hotTimer, hotTimerCallback, NULL);

  counterHotWater = 0;
}

void loop() {


  if (counterHotWater) {
    Serial.printf("counterHotWater: %d\n", counterHotWater);
    counterHotWater = 0;
  }

  if (Serial.available()) {
    avail = Serial.parseInt();
    Serial.printf("Delay %d seconds\n", avail);
    pause(avail*1000);
  }

  yield();

}

void pause(unsigned long p) {
  while (p) {
    delay(1);
    p--;
  }
}

void hotInterrupt() {
  noInterrupts();
  os_timer_arm(&hotTimer, TIME_BOUNCE, true);

}

void hotTimerCallback(void *pArg) {

  int val;

  val = digitalRead(HOT_PIN);

  if (val) return;
 
  os_timer_disarm (&hotTimer);

  counterHotWater++;

  interrupts();

}
 
  • Like
Реакции: kab

Slacky

Member
Допилил код. То что выше неправильно - таймер не выключается и все время грузит систему.

В новый добавил вывод информации, чтобы было понятно, сколько прерываний сработало и сколько таймеров запустилось. Это чисто заготовка. На два канала.

Код:
extern "C" {
#include "user_interface.h"
}

os_timer_t hotTimer;
os_timer_t coldTimer;

#define HOT_PIN D1
#define COLD_PIN D2
#define TIME_BOUNCE 20


volatile unsigned long counterHotWater, counterColdWater;
unsigned long hotTimeBounce, coldTimeBounce;
int hotInt, coldInt;
int avail;


void setup() {
  Serial.begin(115200);
 
  pinMode(HOT_PIN, INPUT_PULLUP);
  pinMode(COLD_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(HOT_PIN), hotInterrupt, RISING);
  attachInterrupt(digitalPinToInterrupt(COLD_PIN), coldInterrupt, RISING);

  os_timer_setfn(&hotTimer, hotTimerCallback, NULL);
  os_timer_setfn(&coldTimer, coldTimerCallback, NULL);

  counterHotWater = 0;
  counterColdWater = 0;

  hotInt = 0;
  coldInt = 0;
}

void loop() {


  if (counterHotWater) {
    Serial.printf("counterHotWater: %d\n", counterHotWater);
    counterHotWater = 0;
  }

  if (counterColdWater) {
    Serial.printf("counterColdWater: %d\n", counterColdWater);
    counterColdWater = 0;
  }

  if (Serial.available()) {
    avail = Serial.parseInt();
    Serial.printf("Delay %d seconds\n", avail);
    pause(avail*1000);
  }

  yield();

}

void pause(unsigned long p) {
  while (p) {
    delay(1);
    p--;
  }
}

void hotInterrupt() {
 
  if (hotInt == 0) {
    hotInt++;
    hotTimeBounce = millis();
  }
  Serial.println("hotInterrupt");
  os_timer_arm(&hotTimer, TIME_BOUNCE, true);
}

void coldInterrupt() {

  if (coldInt == 0) {
    coldInt++;
    coldTimeBounce = millis();
  }
  Serial.println("coldInterrupt");
  os_timer_arm(&coldTimer, TIME_BOUNCE, true);
}

void hotTimerCallback(void *pArg) {

  Serial.println("hotTimerCallback");

  if (!digitalRead(HOT_PIN)) {
    hotTimeBounce = millis();
    return;
  }

  if (digitalRead(HOT_PIN) && hotTimeBounce + TIME_BOUNCE > millis()) return;
 
  os_timer_disarm (&hotTimer);

  hotInt = 0;

  counterHotWater++; 

}

void coldTimerCallback(void *pArg) {

  Serial.println("coldTimerCallback");

  if (!digitalRead(COLD_PIN)) {
    coldTimeBounce = millis();
    return;
  }

  if (digitalRead(COLD_PIN) && coldTimeBounce + TIME_BOUNCE > millis()) return;
 
  os_timer_disarm (&coldTimer);

  coldInt = 0;

  counterColdWater++; 

}
 

kab

New member
@Slacky

Вот тут я попытался упростить по идеям из поста №18. Вживую, правда, не отлаживал - но не вижу, что может быть тут нехорошего :)
Кстати, строчки
Код:
#define HOT_PIN 1
#define COLD_PIN 2
надо уточнить...

Интересно Ваше мнение, что не так в этом, более простом, подходе?


Код:
/*
extern "C" {
#include "user_interface.h"
}

os_timer_t hotTimer;
os_timer_t coldTimer;
*/ 
#define HOT_PIN 1
#define COLD_PIN 2
#define TIME_BOUNCE 200


volatile unsigned long counterHotWater, counterColdWater;
volatile unsigned long hotLastTime, coldLastTime;   //kab
//unsigned long hotTimeBounce, coldTimeBounce;
//int hotInt, coldInt;
//int avail;


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

  pinMode(HOT_PIN, INPUT_PULLUP);
  pinMode(COLD_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(HOT_PIN), hotInterrupt, RISING);
  attachInterrupt(digitalPinToInterrupt(COLD_PIN), coldInterrupt, RISING);

  //os_timer_setfn(&hotTimer, hotTimerCallback, NULL);
  //os_timer_setfn(&coldTimer, coldTimerCallback, NULL);

  counterHotWater = 0;
  counterColdWater = 0;

//  hotInt = 0;
//  coldInt = 0;
}

void loop() {


  if (counterHotWater) {
    Serial.print(counterHotWater);
    counterHotWater = 0;
  }

  if (counterColdWater) {
    Serial.print(counterColdWater);
    counterColdWater = 0;
  }
 /*
  if (Serial.available()) {
    avail = Serial.parseInt();
    Serial.printf("Delay %d seconds\n", avail);
    pause(avail*1000);
  }
 */
  //yield();

}
/* 
void pause(unsigned long p) {
  while (p) {
    delay(1);
    p--;
  }
}
*/ 
//kab
void hotInterrupt() {
   if (millis()-hotLastTime>TIME_BOUNCE) {
     counterHotWater++;
     hotLastTime = millis(); 
   }
   
 /*
  if (hotInt == 0) {
    hotInt++;
    hotTimeBounce = millis();
  }
  Serial.println("hotInterrupt");
  //os_timer_arm(&hotTimer, TIME_BOUNCE, true);
*/
}

void coldInterrupt() {
 if (millis()-coldLastTime>TIME_BOUNCE) {
     counterColdWater++;
     coldLastTime = millis(); 
   }

}
 

Slacky

Member
@kab сработает одно прерывание (без дребезга вообще, такое иногда случается) и счетчик не увеличится ...
 

kab

New member
@kab сработает одно прерывание (без дребезга вообще, такое иногда случается) и счетчик не увеличится ...
Пропуск реального срабатывания может быть один раз и только в маловероятном случае, если это срабатывание будет в период до 200 мсек после запуска MCU.
Т. к. минимальный период между реальными срабатываниями контактов около 10 сек - вероятность получается порядка 0.02. Т. е. достаточно малая.

:)Так это же и правильно - в течении 200 мсек 10 литров ну никак не "используются"...
 
Последнее редактирование:

Slacky

Member
Пропуск реального срабатывания может быть один раз и только в маловероятном случае, если это срабатывание будет в период до 200 мсек после запуска MCU.
Т. к. минимальный период между реальными срабатываниями контактов около 10 сек - вероятность получается порядка 0.02. Т. е. достаточно малая.

:)Так это же и правильно - в течении 200 мсек 10 литров ну никак не "используются"...
Я вижу логику работы по другому.

Ответьте себе (ну и мне заодно), когда программа попадет в это место?

Код:
void hotInterrupt() {
   if (millis()-hotLastTime>TIME_BOUNCE) {
     counterHotWater++;
     hotLastTime = millis();
   }
 

kab

New member
Я вижу логику работы по другому.

Ответьте себе (ну и мне заодно), когда программа попадет в это место?

Код:
void hotInterrupt() {
   if (millis()-hotLastTime>TIME_BOUNCE) {
     counterHotWater++;
     hotLastTime = millis();
   }
Ну, по порядку:
1. Сразу, как MCU и программа стартанула - millis() возвращает 0, hotLastTime инициирован в 0. 0-0=0, 0>200 - проверка возвращает false и тело процедуры прерывания не выполняется. Как я написал выше, до истечения 200 мсек в процедуре прерывания ничего не происходит.

2. Условно говоря, следующее прерывание состоялось через секунду с момента старта:
millis() возвращает 1000,
hotLastTime не изменялся, т. е. возвращает 0.
1000-0=1000, т. е. 1000>200 - условие true, пошло выполнение тела процедуры.
После выполнения процедуры
counterHotWater становится 1,
hotLastTime становится 1000

3. И снова в тело процедуры не попадаем, до момента 1200 мсек.

4. После 1200 мсек аналог п.2 с новыми значениями параметров.
 

Slacky

Member
@kab проверил Ваш код. Скорей всего это не связано с логикой (она вроде на первый взгляд верна), а связано с помехами.

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

Моя же реализация стабильно дает одно нажатие ...

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

kab

New member
@kab проверил Ваш код. Скорей всего это не связано с логикой (она вроде на первый взгляд верна), а связано с помехами.

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

Моя же реализация стабильно дает одно нажатие ...

Но у меня вылезли грабли другого рода, связанные с выходом из спящего режима light_sleep, но это уже немного другое ...
В программе стоит
-Это фронт импульса, т. е. замыкание. Т. е. счетчик срабатывает на замыкание.
А если кнопка была нажата более 200 мсек, то дребезг(фронт первого импульса "дребезга") при размыкании принимается
за информационный импульс.

Решение единственно - время отсечки дребезга TIME_BOUNCE устанавливать больше, чем длина импульса...

ЗЫ:)На самом деле счетчик за информативный импульс принимает первый импульс дребезга после достаточно большой паузы.
Усовершенствовать мой алгоритм можно введя определение длины каждого импульса. Для этого вводим еще прерывания по размыканию, определяем длину импульса как момент размыкания минус момент замыкания. И отсеиваем слишком короткие импульсы как принадлежащие "дребезгу". Логика слегка усложняется, но использование os_timer_... всё же удается избежать
 

kab

New member
@kab
А , наверно кнопка стоит на "землю" - тогда rizing - размыкание - ок.
Но дребезг присущ как замыканию, так и размыканию... Так что неважно, что ставить в программе - фронт или спад
 

Slacky

Member
Логика слегка усложняется, но использование os_timer_... всё же удается избежать
А есть какие-то противопоказания для использования os_timer* ? :)))

Так что неважно, что ставить в программе - фронт или спад
Мне было важно. Изначально я тоже сделал при замыкании на землю. Но потом в код добавился блок с отправкой платы в light_sleep. А выходом из этого режима служит опять же минусовой сигнал. Потому прерывания были перенесены на как-бы размыкание. И модуль по приходу минусового сигнала успевал проснуться и обработать это нажатие в прерывании ...
 

kab

New member
А есть какие-то противопоказания для использования os_timer* ?
Особых - нет.
Так, некоторые личные комплексы - стараюсь следовать принципам - основных два (взаимосвязанных):
- Keep it simple, ...
- Не умножай сущности без необходимости.

light_sleep. А выходом из этого режима служит опять же минусовой сигнал. Потому прерывания были перенесены на как-бы размыкание
Если так всё "запущено" :) - то, скорее всего, использование аппаратного подавления дребезга не избежать. Иначе - куча чередующихся фронтов и спадов по событию "замыкание" и по событию "размыкание" - да еще с командой на просыпание... Ужас :mad:...
 

Slacky

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

Добавил устранение дребезга программным способом, которое тут приводил за основу ...

GitHub - slacky1965/watermeter: The water counter on ESP8266 for Arduino IDE
 
Сверху Снизу