Кнопка с таймером переключения и функцией сброса времени + светодиод обратной связи

Новичок здесь.

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

На данный момент мне удалось запустить счетчик изменения состояния кнопки и дать ему время ожидания. К сожалению, из моего скетча вытекает все странное поведение, и я еще не понял, почему.

Помощь приветствуется.

Это желаемое поведение таймера:

  1. При запуске Arduino готов принять нажатие кнопки. Реле выключено (HIGH).
  2. После нажатия кнопки:
    • Состояние реле изменяется на НИЗКИЙ, чтобы включить его
    • Загорается зеленый светодиод.
    • Включен таймер на базе millis().
  3. Если пользователь нажимает кнопку, когда переключатель включен, таймер сбрасывается на 0 и продолжает отсчет.
  4. Если время включения подходит к концу, начинает мигать красный светодиод. Скажем, в последнюю минуту.
  5. Как только таймер достигнет указанного времени включения:
    • Зеленый светодиод гаснет
    • Реле выключается • Система возвращается к шагу 1 и ожидает нажатия.

В моем примере кода красный мигающий светодиод еще не реализован. Следующий код включает переключатель и зеленый светодиод, но больше не выключает их.

//КОНСТАНТЫ

#define BUTTON_PIN        2  // Кнопка
int in1 = 7;
int greenLED = 4;
int redLED = 9;


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

int buttonPushCounter = 0;   // счетчик количества нажатий кнопок
int buttonState = 0;         // текущее состояние кнопки
int lastButtonState = 0;     // предыдущее состояние кнопки

// МИЛЛИС
unsigned long previousMillis = 0;
const long interval = 10000;


void setup()
{
  pinMode(BUTTON_PIN, INPUT);
  digitalWrite(BUTTON_PIN, HIGH); // остановить
  Serial.begin(9600);

  pinMode(greenLED, OUTPUT);
  pinMode(redLED, OUTPUT);

  //ОБРАБОТЧИКИ ПЕРЕКЛЮЧЕНИЯ
  pinMode(in1, OUTPUT);
  digitalWrite(in1, HIGH);

}


void loop(){

    // прочитать входной контакт кнопки:
    buttonState = digitalRead(BUTTON_PIN);
    unsigned long currentMillis = millis();

    // сравниваем состояние кнопки с предыдущим состоянием
    if (buttonState != lastButtonState) {

        buttonState = digitalRead(BUTTON_PIN);

        if (currentMillis - previousMillis < interval){
        // ВКЛЮЧЕНИЕ, СВЕТОДИОД ВКЛЮЧЕН
          digitalWrite(in1, LOW);
          digitalWrite(greenLED, HIGH);
        }

        if (currentMillis - previousMillis >= interval) {
            previousMillis = currentMillis;
            digitalWrite(in1, HIGH);
            digitalWrite(greenLED, LOW);
        } 


        // Небольшая задержка, чтобы избежать подпрыгивания
        delay(50);


        // Сбросить состояние кнопки:
        lastButtonState = buttonState;
   }
}

, 👍2

Обсуждение

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


1 ответ


Лучший ответ:

3

Написано и протестировано. Я также включил красный мигающий светодиод.

//КОНСТАНТЫ

const int BUTTON_PIN = 2; // Кнопка
const int in1 = 7;
const int greenLED = 4;
const int redLED = 9;


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

int buttonPushCounter = 0;   // счетчик количества нажатий кнопок
int buttonState = 0;         // текущее состояние кнопки
int lastButtonState = 0;     // предыдущее состояние кнопки
bool relayOn = false;

// МИЛЛИС
unsigned long previousMillis = 0;
const unsigned long interval = 10000;
const unsigned long redLedInterval = 2000;


void setup()
{
  pinMode(BUTTON_PIN, INPUT);
  digitalWrite(BUTTON_PIN, HIGH); // остановить
  Serial.begin(9600);

  pinMode(greenLED, OUTPUT);
  pinMode(redLED, OUTPUT);

  //ОБРАБОТЧИКИ ПЕРЕКЛЮЧЕНИЯ
  pinMode(in1, OUTPUT);
  digitalWrite(in1, HIGH);

}


void loop(){

    // прочитать входной контакт кнопки:
    buttonState = digitalRead(BUTTON_PIN);
    unsigned long currentMillis = millis();

    // если кнопка нажата, включить реле (если оно еще не было включено) и сбросить таймер
    if( buttonState==LOW ) // нет необходимости проверять предыдущее состояние, в этом конкретном случае
    {
        previousMillis = currentMillis;
        digitalWrite(in1, LOW);
        digitalWrite(greenLED, HIGH);
        digitalWrite(redLED, LOW);
        relayOn = true;
    }

    // если реле в данный момент включено...
    if( relayOn )
    {
        // включить красный светодиод, если близко к отключению реле
        if (currentMillis - previousMillis >= interval-redLedInterval )
            digitalWrite(redLED, (millis()/300)%2);//мигает красный светодиод; 300 мс включено; 300 мс выкл.

        // если прошло достаточно времени, включаем реле
        if (currentMillis - previousMillis >= interval) 
        {
            // .. поворот реле
            digitalWrite(in1, HIGH);
            digitalWrite(greenLED, LOW);
            digitalWrite(redLED, LOW);
            relayOn = false;
        }
    } 
}

Дополнительным преимуществом здесь является то, что таймер никогда не истечет, если кнопка будет нажата постоянно.

,

Спасибо. Это очень мило с твоей стороны. К сожалению, все, что он делает, это включает зеленый светодиод и реле, как только питание Arduino подается. Нажатие кнопки не дает никакого эффекта. Может ли это быть аппаратной проблемой? Я работал над очень похожим подходом к вашему bool relayOn, но с целым числом, изменяющимся от 0 до 1. Хотя это частично сработало, я застрял на реализации сброса таймера. Я постараюсь включить ваше решение в свой подход и опубликовать результаты., @ifthisthenthat

Тогда я думаю, что ваша кнопка подключена наоборот. Кнопка подключена к 5v или GND при нажатии?, @Gerben

Попробуйте изменить buttonState==LOW на buttonState==HIGH., @Gerben

Аллилуйя! Оно работает! Почему-то в голове засела мысль о важности изменения состояния кнопки. Я думал, что состояния buttonState==LOW и HIGH работают только тогда, когда это состояние действительно присутствует, то есть пока кнопка удерживается нажатой, а не после этого. У меня нет возможности выразить всю благодарность., @ifthisthenthat

Я тоже нахожу это выражение очень интересным. Почему это работает? digitalWrite(красный светодиод, (миллис()/300)%2);, @ifthisthenthat

В этом случае вам нужно удалить линию, которая включала «подтягивающий» резистор, поскольку, по-видимому, вы используете подтягивающий резистор на своей макетной плате., @Gerben

Изменение состояния кнопки весьма полезно во многих случаях. Например; нажмите кнопку, чтобы включить реле, а затем нажмите кнопку еще раз, чтобы включить реле. Но в этом очень конкретном случае изменение состояния не имеет значения,, @Gerben

millis()/300 дает вам количество прошедших интервалов в 300 мс. %2 дает модуль 2, поэтому остаток /2., или в этом случае 1, если он нечетный, и 0, если он четный. Это очень быстрый способ мигать светом. Единственным недостатком является то, что большую часть времени он не начинает мигать при значении миллисекунд, которое делится на 300. Таким образом, первое мигание может быть намного короче. Или это начинается с того, чтобы быть выключенным. Последнее может быть проблематичным, если вы используете медленное моргание., @Gerben

Пожалуйста, кстати. Пожалуйста, установите флажок рядом с моим ответом, чтобы принять его (как только вам это будет разрешено)., @Gerben

Итак, позвольте мне проверить, правильно ли я понимаю: millis()/300 проверяет, «как часто» интервал в 300 мс соответствует текущему значению millis(), верно? Извините за язык первого класса. Это помогает моему графическому уму понять. Итак, (300/300) = 0; 0%2 = 0, таким образом, НИЗКИЙ; (600/300)=2 %2= 0, таким образом, снова НИЗКИЙ уровень. (1300/300)=4,3 и 4,3%2=0,3, таким образом, не 0, что выводит на ВЫСОКИЙ уровень. Я приближаюсь? Я знаю, что делимое или делитель должны быть целыми. Значит ли это, что они округлены?, @ifthisthenthat

1300/300=4, так как остаток просто выбрасывается. (4%2=0). Таким образом, при делении целых чисел результат округляется в меньшую сторону. Это не то, чему нас учили по математике в первом классе q-;, @Gerben

Я понимаю. Итак, как доказательство понимания: 1500/300=5; 5%2=1; Таким образом, значение либо 0, либо 1, потому что каждое второе полное число, деленное на 2, равно 0. Теперь я понял., @ifthisthenthat

Ага. Итак, 0-299 => 0. 300-599=>1. 600-899=>0. И так далее, @Gerben

Просто и красиво., @ifthisthenthat

Сложно ли было бы реализовать отключение всего снова при нажатии кнопки на 2 секунды?, @ifthisthenthat

Затем вам нужно будет добавить buttonSate обратно в микс. Я бы посоветовал сначала попробовать самому., @Gerben

Я определенно буду., @ifthisthenthat