Как заставить кнопку Arduino забыть о своем прошлом назначении?

Для своего школьного проекта я делаю спидометр на Arduino для велосипеда. Я буду использовать датчик геркона, чтобы получить скорость. Также будут часы и термометр, он будет на жк экране 16х2. Чтобы получить скорость, мне нужно, чтобы пользователь ввел размер своего велосипеда в zoll. Мне удалось позволить пользователю нажать кнопку до его размера, а затем дважды нажать кнопку, чтобы подтвердить это. Хорошо, хорошо. Теперь мне нужна та самая кнопка, чтобы ЗАБЫТЬ свою последнюю цель. Потому что я хочу использовать одну и ту же кнопку для переключения экрана между скоростью, часами и термометром. СКОРОСТЬ -> нажмите -> ЧАСЫ -> нажмите -> ТЕМПЕРАТУРА -> нажмите -> СКОРОСТЬ ........

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

Выберите свой размер. Нажмите, нажмите, нажмите, нажмите дважды, спасибо. И остановись! Я хочу, чтобы он сделал это только один раз и никогда больше. Мне понадобится тот же принцип, когда пользователь будет настраивать свои часы, ему нужно будет нажать, чтобы установить их, сохранить число, но сбросить функцию кнопок.

#include <LiquidCrystal_I2C.h>
#include <LiquidCrystal.h>
LiquidCrystal_I2C lcd(0x27, 16,2);
#define buttonPin 7        
int a = 16;
int b = 0;

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.print("RADGROESSE IN");
  lcd.setCursor(0, 1);
  lcd.print("ZOLL EINGEBEN");

   // Установить пин ввода кнопки
   pinMode(buttonPin, INPUT);
   digitalWrite(buttonPin, HIGH );
}
void loop() {
   int b = checkButton();
   if (b == 1) clickEvent();
   if (b == 2) doubleClickEvent();
}
void clickEvent() {

   lcd.setCursor(14, 1);
    lcd.print(a);
    delay(100);
    lcd.print(" ");
    delay(100);
    a++;
    delay(100);
    if(a >= 23){
      a = 16;
      }
}
void doubleClickEvent() {
      b = a - 1;
      Serial.println("Button was idle for one second or more");
      lcd.clear();
      lcd.print("ZOLL = ");
      lcd.setCursor(7, 0);
      lcd.print(b);

}

//=============================================== "="
// MULTI-CLICK: одна кнопка, несколько событий

// Переменные синхронизации кнопок
int debounce = 20;          // период устранения дребезга в мс для предотвращения мерцания при нажатии или отпускании кнопки
int DCgap = 420;            // макс. мс между кликами для события двойного клика
// Переменные кнопки
boolean buttonVal = HIGH;   // значение, считанное с кнопки
boolean buttonLast = HIGH;  // буферизованное значение предыдущего состояния кнопки
boolean DCwaiting = false;  // ждем ли мы двойного клика (вниз)
boolean DConUp = false;     // зарегистрировать ли двойной щелчок в следующем релизе или подождать и щелкнуть
boolean singleOK = true;    // можно ли сделать один клик
long downTime = -1;         // время нажатия кнопки
long upTime = -1;           // время, когда кнопка была отпущена

int checkButton() {   
   int event = 0;
   buttonVal = digitalRead(buttonPin);
   // Кнопка нажата
   if (buttonVal == LOW && buttonLast == HIGH && (millis() - upTime) > debounce)
   {
       downTime = millis();
       singleOK = true;
       if ((millis()-upTime) < DCgap && DConUp == false && DCwaiting == true)  DConUp = true;
       else  DConUp = false;
       DCwaiting = false;
   }
   // Кнопка отпущена
   else if (buttonVal == HIGH && buttonLast == LOW && (millis() - downTime) > debounce)
   {       
           upTime = millis();
           if (DConUp == false) DCwaiting = true;
           else
           {
               event = 2;
               DConUp = false;
               DCwaiting = false;
               singleOK = false;
           }
   }
   // Тест на нормальное событие щелчка: DCgap истек
   if ( buttonVal == HIGH && (millis()-upTime) >= DCgap && DCwaiting == true && DConUp == false && singleOK == true && event != 2)
   {
       event = 1;
       DCwaiting = false;
   }

   buttonLast = buttonVal;
   return event;
}

Спасибо! Буду очень признателен!

, 👍3

Обсуждение

Когда именно пользователю нужно снова ввести размер велосипеда? После перезапуска/сброса Arduino? Или вы хотите, чтобы пользователь дважды щелкнул мышью только один раз?, @Thomas Weller

кнопки не имеют памяти...нечего забывать...программа должна реагировать на нажатие кнопки в зависимости от состояния в котором находится программа, @jsotola

Да, после сброса. Я хочу, чтобы пользователь нажимал до своего размера и дважды нажимал, чтобы сохранить размер. После этого часть размера велосипеда больше никогда не должна появляться, ну, конечно, если вы ее сбросите, но это не так. После выбора размера кнопка теперь будет работать как функция пропуска. Я в основном хочу изменить функцию кнопки., @Marin Filipovic


2 ответа


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

3

Jsotola написал в комментариях очень важный термин: состояние.

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

Я уже написал довольно подробный ответ о FSM на этот вопрос. Вы можете прочитать ее, чтобы лучше понять FSM.

Для вашего случая я бы предложил следующие состояния: INPUT_SIZE, DISPLAY_SPEED, DISPLAY_CLOCK и DISPLAY_TEMPERATURE. INPUT_SIZE — это начальное состояние (которое вы можете пропустить после первого включения питания, после того как вы сохранили размер в постоянной памяти, такой как EEPROM). Там вы выполняете свой текущий код кнопки. Когда вы чувствуете двойное нажатие на кнопку, вы переходите в состояние DISPLAY_SPEED. Оттуда нажмите кнопку, чтобы перейти в состояние DISPLAY_CLOCK и так далее.

В моем связанном ответе я использовал простую целочисленную переменную в качестве переменной состояния, которая содержит текущее состояние. На самом деле лучше (легче читать), когда вы определяете enum для случаев (enum на самом деле являются только именованными целочисленными константами):

enum State {INPUT_SIZE, DISPLAY_SPEED, DISPLAY_CLOCK, DISPLAY_TEMPERATURE};

State state = INPUT_SIZE;

void loop(){
    switch(state){
        case INPUT_SIZE:
            // Здесь код кнопки
            // при двойном касании используйте следующую строку
            state = DISPLAY_SPEED;
            break;
        case DISPLAY_SPEED:
            // здесь код для отображения скорости
        ...
    }
}

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

,

Да, именно то, что мне было нужно. Хорошо, спасибо, буду изучать, искать в сети и пробовать реализовать СПАСИБО, @Marin Filipovic


4

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

В вашем случае конечный автомат может выглядеть так:

Конечный автомат

Что это значит?

  • черный кружок: здесь система включается
  • прямоугольник: состояние
  • стрелка: переход из одного состояния в другое
  • зеленый текст: действие

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

  • он меняет состояние с "установить размер" на "скорость"
  • во всех остальных случаях не меняет состояние (фактически меняет состояние на то же состояние)

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

Есть несколько способов реализовать это. Самым простым из них является switch/case. Более продвинутые реализации могут использовать объектную ориентацию. При использовании switch/case я предпочитаю иметь один метод для каждого состояния, например так:

void loop()
{
    switch(state):
        case setsize:
             setsize();
             break;
        case speed:
             speed();
             break;
        ...
}

Если вы знаете об указателях на функции, все становится еще проще:

enum State {
  setsize = 0,
  speed,
  clock,
  temperature
};

State state;

void setup() {
  state = State::setsize;
}


typedef void (* functionPointer) ();
functionPointer process[] = {do_setsize, do_speed, do_clock, do_temperature};
void loop() {
  process[state]();
}

void do_setsize()
{
  state = State::speed;
}
void do_speed()
{
  state = State::clock;
}
void do_temperature()
{
  state = State::speed;
}
void do_clock()
{
  state = State::temperature;
}
,

Спасибо. Мне удалось заставить его работать. Спасибо!, @Marin Filipovic