Несколько неблокирующих таймеров обратного отсчета?
Я создаю систему управления тремя водоотливными насосами, используя один Arduino и набор реле. У меня есть поплавковые датчики для каждого из них, и мне нужно, чтобы Arduino запускал реле при срабатывании поплавковых датчиков. Датчик 1 запускает насос 1, датчик 2 запускает насос 2 и т. д.
Достаточно просто, но мне также нужно, чтобы каждый насос оставался включенным в течение 30 секунд после того, как его поплавковый датчик вернется в нормальное состояние. Это ставит меня в замешательство, поскольку мне нужно, чтобы другие насосы и датчики работали в течение этого времени задержки. Я быстро обнаружил, что блокирующая функция delay()
больше невозможна.
Как я могу активировать реле, держать их включенными в течение 30 секунд после того, как датчики вернутся в нормальное состояние, но при этом продолжать контролировать и контролировать другие датчики/реле? Будет ли работать неблокирующий таймер? Придется ли мне углубляться в прерывания?
@Darkhand, 👍2
4 ответа
Подойдет ли неблокирующий таймер?
Да. Ваш «таймер» не должен быть ничем иным, как временем начала из 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.
Шаблон "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().
Удачи!
Вы можете использовать что-то вроде этого:
#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 мс) для лучшего результата.
Используя информацию из каждого предложения, я пришел к следующему. Возможно, мне не придется снова прикасаться к этому коду в течение многих лет (или новому владельцу дома, возможно, придется просмотреть его, если я перееду), поэтому я постарался прокомментировать его как можно больше.
Мне не нравится, что я повторяю так много кода в функциях 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
- Использование значения MAX30100 SpO2 для включения светодиода
- Проблема с последовательностью воздушных поршней
- Почему мои часы реального времени показывают неверное время с моего ПК?
- (Код ультразвукового датчика: такого файла или каталога нет)
- Датчик HC-SR505 PIR выдает только HIGH уровень
- Отправка данных из ESP8266 в PHP
- Определение уровня заряда с помощью датчика тока (ACS758) с arduino uno
- Использование YS-IRTM с Arduino Uno
Через 30 секунд после запуска программы условие
(millis() - startTime >= PUMP_DURATION)
всегда будет истинным., @OctopusДа и? Это метод (пере)запуска таймера и определения истечения срока его действия. Для самого очевидного наблюдателя должно быть понятно, что это минимальная демонстрация техники. Различные интервалы, разные старты, перезапуски, несколько таймеров и т. д. оставлены в качестве упражнения для читателя. -_-, @slash-dev