Несколько неблокирующих таймеров обратного отсчета?

Я создаю систему управления тремя водоотливными насосами, используя один Arduino и набор реле. У меня есть поплавковые датчики для каждого из них, и мне нужно, чтобы Arduino запускал реле при срабатывании поплавковых датчиков. Датчик 1 запускает насос 1, датчик 2 запускает насос 2 и т. д.

Достаточно просто, но мне также нужно, чтобы каждый насос оставался включенным в течение 30 секунд после того, как его поплавковый датчик вернется в нормальное состояние. Это ставит меня в замешательство, поскольку мне нужно, чтобы другие насосы и датчики работали в течение этого времени задержки. Я быстро обнаружил, что блокирующая функция delay() больше невозможна.

Как я могу активировать реле, держать их включенными в течение 30 секунд после того, как датчики вернутся в нормальное состояние, но при этом продолжать контролировать и контролировать другие датчики/реле? Будет ли работать неблокирующий таймер? Придется ли мне углубляться в прерывания?

, 👍2


4 ответа


3

Подойдет ли неблокирующий таймер?

Да. Ваш «таймер» не должен быть ничем иным, как временем начала из millis() и флагом. Затем ваш цикл просто сравнивает время начала с текущим значением millis(), и если оно больше желаемого интервала, сделайте «что-то». В коде:

uint32_t startTime;
bool waiting = false;
const uint32_t PUMP_DURATION = 30000UL;

void setup()
{
  // запускаем "таймер"
  startTime = millis();
  waiting = true;
}

void loop()
{
  if (waiting && (millis() - startTime >= PUMP_DURATION)) {
    waiting = false;
    // сделай что-нибудь сейчас!
    foo();
  }

Флаг не позволяет проверять, когда насос не работает.

Если у вас есть несколько временных объектов, просто создайте другой набор переменных (например, startTime2 и waiting2). Дайте им имена получше, а?

Существуют также библиотеки Arduino, которые инкапсулируют этот общий шаблон проектирования. TimeMark — один из них.

Вот пример кода на форуме Arduino.

,

Через 30 секунд после запуска программы условие (millis() - startTime >= PUMP_DURATION) всегда будет истинным., @Octopus

Да и? Это метод (пере)запуска таймера и определения истечения срока его действия. Для самого очевидного наблюдателя должно быть понятно, что это минимальная демонстрация техники. Различные интервалы, разные старты, перезапуски, несколько таймеров и т. д. оставлены в качестве упражнения для читателя. -_-, @slash-dev


3

Шаблон "blink-without-delay" показывает, как это можно решить, но с использованием нескольких периодов времени. и логика становится сложной. Нужна некоторая абстракция. Библиотека Timemark предоставляет решение. Ниже приведена логика для одной пары датчик-реле с отслеживанием состояния по последовательному порту:

#include <Timemark.h>

const uint32_t TURN_OFF_TIMEOUT = 30000L;
Timemark turnOff(TURN_OFF_TIMEOUT);
const int sensorPin = 2;
const int relayPin = 3;

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

  pinMode(sensorPin, INPUT_PULLUP);
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);

  trace.start();
}

void loop()
{
  if (trace.expired()) {
    Serial.print(millis());
    Serial.print(F(":sensor="));
    Serial.print(digitalRead(sensorPin));
    Serial.print(F(", relay="));
    Serial.println(digitalRead(relayPin));
  }

  if (digitalRead(sensorPin) == LOW) {
    digitalWrite(relayPin, HIGH);
    turnOff.start();
  }
  else if (turnOff.expired()) {
    digitalWrite(relayPin, LOW);
  }
}

При расширении до трех датчиков-реле:

#include <Timemark.h>

const uint32_t TURN_OFF_TIMEOUT = 30000L;
const int CONTROL_MAX = 3;
Timemark turnOff[CONTROL_MAX];
const int sensorPin[CONTROL_MAX] = { 2, 4, 6 };
const int relayPin[CONTROL_MAX] = { 3, 5, 7 };

void setup()
{
  for (int i = 0; i < CONTROL_MAX; i++) {
    turnOff[i].limitMillis(TURN_OFF_TIMEOUT);
    pinMode(sensorPin[i], INPUT_PULLUP);
    pinMode(relayPin[i], OUTPUT);
    digitalWrite(relayPin[i], LOW);
  }
}

void loop()
{   
  for (int i = 0; i < CONTROL_MAX; i++) {
    if (digitalRead(sensorPin[i]) == LOW) {
      digitalWrite(relayPin[i], HIGH);
      turnOff[i].start();
    }
    else if (turnOff[i].expired()) {
      digitalWrite(relayPin[i], LOW);
    }
  }
}

Альтернативным решением является использование библиотеки Scheduler. Ниже приведена перезапись с использованием шаблонных функций и задачи контроллера для каждой пары датчик-реле.

#include <Scheduler.h>

const uint32_t TURN_OFF_TIMEOUT = 30000L;
const uint32_t DEBOUNCE_TIMEOUT = 40L;

template<const int sensorPin, const int relayPin> void setupController()
{
  pinMode(sensorPin, INPUT_PULLUP);
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);
}

template<const int sensorPin, const int relayPin> void loopController()
{
  if (digitalRead(sensorPin) == LOW) {
    digitalWrite(relayPin, HIGH);
    while (digitalRead(sensorPin) == LOW) delay(DEBOUNCE_TIMEOUT);
    delay(TURN_OFF_TIMEOUT);
    digitalWrite(relayPin, LOW);
  }
  yield();
}

void setup()
{
  Scheduler.start(setupController<2,3>, loopController<2,3>);
  Scheduler.start(setupController<4,5>, loopController<4,5>);
  Scheduler.start(setupController<6,7>, loopController<6,7>);
}

void loop()
{
  yield();
}

Обратите внимание, что для переключения между задачами планировщику требуется метод yield() или Delay().

Удачи!

,

0

Вы можете использовать что-то вроде этого:

#define AMOUNT_OF_TIMERS 10
unsigned int timer[AMOUNT_OF_TIMERS];
unsigned int sensors[AMOUNT_OF_TIMERS];
unsigned int pumps[AMOUNT_OF_TIMERS];
void setup()
{
  sensors[0] = 1;
  sensors[1] = 2;
  sensors[2...
  ......s[9] = 12;
  pumps[0] = 13;
  pumps[1....
  ....s[9] = 22;
  for(unsigned int i = 0; i< AMOUNT_OF_TIMERS; i++){
    PinMode(sensors[i],INPUT);
    PinMode(pumps[i],OUTPUT);
  }
}

void loop()
{
  checkPins();
  decreaseTimers();
  delay(1000);
}

void checkPins(){
  for(unsigned int i = 0; i< AMOUNT_OF_TIMERS; i++){
    if(digitalRead(sensors[i])
    pump[i] = 30;
  }
}

void decreaseTimers(){
  for(unsigned int i = 0; i< AMOUNT_OF_TIMERS; i++){
    if(timer[i]>0)
    timer--;
  }
}

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

Это не самая изящная вещь, но сойдет. Использование прерываний должно сделать его еще более эффективным.

  • Вам следует использовать прерывания.
  • Вы можете использовать беззнаковые символы, чтобы сэкономить дополнительную оперативную память.
  • Вам следует настроить таймеры/задержку. (установите таймер на 300 и задержку на 100 мс) для лучшего результата.
,

0

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

Мне не нравится, что я повторяю так много кода в функциях readFloatSensor, но на этом я пока и остановился. Как устранить дублирование кода? Я считаю, что мог бы использовать массивы и циклы for, но теперь я вошел в еще одну серую зону для моего уровня знаний.

// PumpController.ino

// ----ПОСТОЯННЫЕ
// ----Отредактируйте их, если вам нужно изменить расположение контактов или продолжительность помпы

#define RELAY_ON 0                     // Реле насоса активны-НИЗКИЙ; горит, когда НИЗКИЙ (0)
#define RELAY_OFF 1                    // и выключается, когда ВЫСОКИЙ (1). Эти #defines просто помогают отслеживать.

const int pumpRelayPin1 =  12;         // Номера контактов реле насоса
const int pumpRelayPin2 =  11;
const int pumpRelayPin3 =  10;

const int floatSensorPin1 = 7;         // Номера контактов поплавковых датчиков
const int floatSensorPin2 = 8;
const int floatSensorPin3 = 9;

const int pumpDuration = 500;          // Количество миллисекунд, в течение которых работают насосы
                                       // после того, как поплавковые датчики больше не обнаруживают воду

// ----ПЕРЕМЕННЫЕ

byte pumpState1 = RELAY_OFF;           // Используется для записи того, включены или выключены насосы
byte pumpState2 = RELAY_OFF;           // (по умолчанию ВЫСОКИЙ/ВЫКЛ)
byte pumpState3 = RELAY_OFF;

unsigned long currentMillis = 0;       // Сохраняет значение millis() на каждой итерации цикла()

unsigned long pumpTimerMillis1 = 0;    // Сохраняет время последнего срабатывания поплавковых датчиков
unsigned long pumpTimerMillis2 = 0;
unsigned long pumpTimerMillis3 = 0;


// ----SETUP (устанавливает контакты при запуске/перезагрузке)

void setup()
{

    // Отладка
    Serial.begin(9600);
    Serial.println("Starting PumpController.ino");

    // Реле насоса активны-НИЗКИЙ. Инициализация насоса
    // контакты HIGH, поэтому реле неактивны при запуске/сбросе
    digitalWrite(pumpRelayPin1, RELAY_OFF);
    digitalWrite(pumpRelayPin2, RELAY_OFF);
    digitalWrite(pumpRelayPin3, RELAY_OFF);

    // ЗАТЕМ устанавливаем контакты как выходы
    pinMode(pumpRelayPin1, OUTPUT);
    pinMode(pumpRelayPin2, OUTPUT);
    pinMode(pumpRelayPin3, OUTPUT);

    // Установите контакты поплавкового датчика в качестве входов и используйте встроенные подтягивающие резисторы
    pinMode(floatSensorPin1, INPUT_PULLUP);
    pinMode(floatSensorPin2, INPUT_PULLUP);
    pinMode(floatSensorPin3, INPUT_PULLUP);

}


// ----ГЛАВНЫЙ ЦИКЛ

void loop()
{
    // Получаем текущий счетчик часов

    currentMillis = millis() + pumpDuration;       // Добавляем длительность таймера обратного отсчета в
                                                   // часы, чтобы предотвратить работу насосов
                                                   // работает во время загрузки, пока часы
                                                   // счетчик все еще ниже значения таймера

    // Вызов функций, которые выполняют работу

    readFloatSensor1();             // проверяем каждый поплавковый датчик и решаем, есть ли
    readFloatSensor2();             // для запуска насоса и таймера обратного отсчета
    readFloatSensor3();
    triggerPumps();                 // Фактически переключает контакты реле в зависимости от
}                                   // данные из вышеуказанных функций.


// ----РАБОЧИЕ ФУНКЦИИ

void readFloatSensor1()
{
    if (digitalRead(floatSensorPin1) == LOW)   // Если поплавковый датчик сработал (переведен на низкий уровень)
    {
        pumpState1 = RELAY_ON;                 // затем активируем вывод реле насоса
        pumpTimerMillis1 = currentMillis;      // и устанавливаем таймер обратного отсчета насоса на текущее время
    }

    if (currentMillis - pumpTimerMillis1 >= pumpDuration)   // Если таймер обратного отсчета истек,
    {
        pumpState1 = RELAY_OFF;                             // затем выключаем вывод реле насоса
        pumpTimerMillis1 = 0;                               // и сбрасываем таймер на 0 для следующего триггера
    }
}

//========

void readFloatSensor2()
{
    if (digitalRead(floatSensorPin2) == LOW)   // Если поплавковый датчик сработал (переведен на низкий уровень)
    {
        pumpState2 = RELAY_ON;                 // затем активируем вывод реле насоса
        pumpTimerMillis2 = currentMillis;      // и устанавливаем таймер обратного отсчета насоса на текущее время
    }

    if (currentMillis - pumpTimerMillis2 >= pumpDuration)   // Если таймер обратного отсчета истек,
    {
        pumpState2 = RELAY_OFF;                             // затем выключаем вывод реле насоса
        pumpTimerMillis2 = 0;                               // и сбрасываем таймер на 0 для следующего триггера
    }
}

//========

void readFloatSensor3()
{
    if (digitalRead(floatSensorPin3) == LOW)   // Если поплавковый датчик сработал (переведен на низкий уровень)
    {
        pumpState3 = RELAY_ON;                 // затем активируем вывод реле насоса
        pumpTimerMillis3 = currentMillis;      // и устанавливаем таймер обратного отсчета насоса на текущее время
    }

    if (currentMillis - pumpTimerMillis3 >= pumpDuration)   // Если таймер обратного отсчета истек,
    {
        pumpState3 = RELAY_OFF;                             // затем выключаем вывод реле насоса
        pumpTimerMillis3 = 0;                               // и сбрасываем таймер на 0 для следующего триггера
    }
}

//========

void triggerPumps()
{
    // Включение и выключение контактов реле насоса в зависимости от
    // pumpState из функции readFloatSensor

    digitalWrite(pumpRelayPin1, pumpState1);
    digitalWrite(pumpRelayPin2, pumpState2);
    digitalWrite(pumpRelayPin3, pumpState3);
}

//=====КОНЕЦ
,

Что касается дублирования, вы можете создать общую функцию датчика и передать ей контакты интересующего датчика - посмотрите, например, как создается множество периферийных библиотек с назначением контактов в их конструкторе. Или вы можете поместить всю информацию о каждом насосе в структуру или пойти дальше и определить объект насоса, три экземпляра которого вы создаете. Затем вы помещаете их в массив и по очереди вызываете соответствующие методы..., @Chris Stratton