«Лучшая» архитектура для обработки событий

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

я имею в виду, что если я сделаю что-то вроде if(condition) doSomething(), это будет выполнять doSomething() много раз или нажатием кнопки....

bool condition = false;
void loop(){
  if(condition){
    doSomething();
  }
  
}


void doSomething(){
  condition = false;
  // ........
}

Я делаю такие вещи, но чувствую, что это неправильно

Итак, я пытаюсь понять, почему может быть "лучшим" архитектура для выполнения doSomething() только один раз, чтобы это не превратилось в адское управление состоянием, какое-то управление событиями, источник событий или что-то большее, проверенное в боях на arduino

Я совершенно новичок в этом мире аппаратного обеспечения/arduino

, 👍2

Обсуждение

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

Я предполагаю, что для приложения веб-браузера, когда вы регистрируете событие в скрипте, скажем, нажатие кнопки, в цикле (где-то спрятанном) этот объект кнопки будет регулярно проверяться, и при нажатии будет выполняться обработчик события. В Arduino нет системы времени выполнения, которая могла бы напрямую поддерживать такие вещи, поэтому, если вы хотите регулярно тестировать кнопку, вам придется делать это явно в цикле., @6v6gt


3 ответа


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

9

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

Самая поразительная разница между Arduino и обычным Среда JavaScript (будь то браузер или Node.js) заключается в том, что в В JavaScript цикл событий неявный. Вы его не видите, но он бежит под капотом вашего переводчика. Концептуально это выглядит так:

void JavaScript_event_loop() {
    if (there_is_a_pending_event()) {
        dispatch_the_event_to_the_registered_callback();
    }
}

Однако на Arduino вам придется написать цикл событий самостоятельно. Большинство часто вам не нужно полноценное управление очередью событий, как вы знаете заранее набор событий, которые ваша программа должна будет обработать. Твой типичный цикл Arduino обычно должен выглядеть так:

void loop() {
    if (event_foo_happened())
        handle_event_foo();
    if (event_bar_happened())
        handle_event_bar();
    if (event_baz_happened())
        handle_event_baz();
    // etc...
}

Существует несколько повторяющихся идиом для тестирования распространенных событий. Вот несколько примеров:

// Обработка периодического события (например, setInterval()).
uint32_t now  = millis();
static uint32_t last_event_time;
if (now - last_event_time >= event_period) {
    // Используйте `last_event_time = now;` вместо этого, если хотите
    // гарантируем минимальный интервал между событиями:
    last_event_time += event_period;  // это позволяет избежать систематического дрейфа
    handle_periodic_event();
}

// Обработка нарастающего фронта на входе input_pin.
uint8_t pin_state = digitalRead(pin);
static uint8_t previous_pin_state;
if (previous_pin_state == LOW && pin_state == HIGH)
    handle_input_rising_edge();
previous_pin_state = pin_state;

// Обработка последовательных данных.
if (Serial.available())
    handle_input_byte(Serial.read());

// Обработка кнопки с помощью библиотеки устранения дребезжания.
button.update();
if (button.fell())
    handle_button_press();

Несколько рекомендуемой литературы:

  • В учебнике Blink Without Delay Arduino показано, как обрабатывать периодические мероприятия
  • Чтение сериалов на Arduino это учебник, показывающий, как буферизовать последовательный ввод, чтобы обрабатывать одно полное сообщение за раз.
  • Bounce2 — пример библиотеки устранения подпрыгивания кнопок.
  • Конечный автомат — хороший учебник по написанию конечных конечные автоматы, которые рано или поздно понадобятся вам для обработки некоторых ситуации неблокирующим способом.

Дополнение. Что касается опубликованного вами примера кода, я хотел бы переписать его. ваш loop() выглядит следующим образом:

void loop() {
    if (condition) {
        doSomething();
        condition = false;  // <- переместим это из doSomething()
    }
}

Это не выглядит большим изменением, но есть идея, которая может помогите сделать ваш код более читабельным и удобным в сопровождении. Идея состоит в том, чтобы думать вашей программы с точки зрения «событий», на которые вы должны реагировать. Это должно кажутся естественными для человека, выросшего на JavaScript, и это также хорошее подход к программированию микроконтроллеров. Затем вы хотите обработать Обнаружение/диспетчеризация событий и реагирование на события как отдельные задачи. Функция doSomething() не должна заботиться о Переменная condition, поскольку она используется только для обработки обнаружения событий. и диспетчеризация, за которую отвечает цикл событий.

Когда я сказал «выполнить один раз», я имел в виду, что при нажатии кнопки оно должно выполняться. что-то только один раз, этот контроль над тем, что должно/было выполнено, немного обманом, может выйти из-под контроля и превратиться в беспорядок.

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

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

У настоящих кнопок есть дополнительная проблема, а именно механический отскок: когда кнопка нажата, сигнал может проходить множество быстрых переходов прежде чем перейти в стабильное состояние. Чтобы справиться с этим, вам надо посмотреть сроки переходов и разобраться в законности одни из поддельных. Это называется устранение дребезга. Есть немногие библиотеки могут справиться с этим за вас. Большинство также сделает обнаружение краев, что делает обработку нажатий кнопок такой же простой, как тестирование для button.fell() (опять же, см. пример выше). Некоторые даже сообщат отдельные типы событий для короткого, длительного и двойного нажатия.

,

Отличный ответ, но я бы просто внес изменения в ваш периодический пример. Я думаю, что обычно при периодической проверке вы предпочитаете иметь что-то периодическое, а не что-то, что гарантированно превышает время. Я имею в виду, что если период составляет 1 секунду, а первый цикл - 1,1 с, то я бы предпочел, чтобы второй был 0,9 с, чтобы «догнать», а не 1 с (чтобы задержки не суммировались). Поэтому обычно я предпочитаю писать last_event_time += event_ period;., @frarugi87

@ Frarugi87: Отредактировал ответ, спасибо., @Edgar Bonet


0

Вам не обязательно использовать их "установку" и "петля"; парадигма, знаете ли. Это сработает:

#include <Arduino.h>

int main ()
  {
  init ();  // инициализируем таймеры
  Serial.begin (115200);
  Serial.println ("Hello, world");
  Serial.flush (); // даем завершиться серийной печати
  }  // конец основного

Итак, внутри вашего кода, если вы хотите сделать что-то один раз, сделайте это один раз. Если вы хотите сделать петлю, сделайте ее. Точно так же, как в С++. Вот что такое код Arduino.


Итак, я пытаюсь понять, почему может быть "лучшим" архитектура для выполнения doSomething() только один раз

Поместите его в setup, если хотите запустить их "setup" и "петля"; парадигма. Он был создан специально для новичков, чтобы разъяснить, что некоторые действия нужно делать один раз, а некоторые — циклично.

,

3

Обычный способ сделать что-то только один раз (при запуске) — поместить их в setup().

В loop() помещаются только операторы, требующие бесконечного повторения.

Вы можете представить базовую функцию main как:

int main() {
    setup();
    for (;;) {
        loop();
    }
}

Следовательно, ваш пример может быть:

void setup() {
    doSomething();
}

void loop() {
}

void doSomething() {
    // ........
}

Добавление после уточнения

Вы прокомментировали:

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

С этим требованием вы правы: вам нужно поместить проверку и реакцию внутри loop(). Однако вы можете "скрыть" «механик»; внутри вызываемой функции:

void setup() {
}

void loop() {
    doSomething();
}

void doSomething() {
    static bool isDone = false;
    if (!isDone && condition) {
        isDone = true;
        // ........
    }
}

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

,

Насколько я знаю, я не могу обнаружить нажатие кнопки вне цикла, верно? когда я сказал выполнить один раз, я имел в виду, что при нажатии кнопки она должна что-то сделать только один раз, этот контроль над тем, что должно / было выполнено, немного обманчив, может выйти из-под контроля и превратиться в беспорядок, @Neuber Oliveira

@NeuberOliveira Посмотрите мою правку. Всегда лучше сообщить нам о своих полных намерениях в вопросе. ;-), @the busybee