Устранение дребезга кнопки с помощью прерывания

Я попытался найти ответ здесь: https://arduino.stackexchange.com/a/18545/51302

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

Что я пытаюсь сделать: получить кнопку, чтобы установить прерывание для запуска foo и, таким образом, записать шаги на мониторе.

Я оставил примечания, чтобы попытаться объяснить, что я пытаюсь сделать, чтобы было легче понять, что я упустил или использовал неправильно. Спасибо.

#define IRF520 A2
#define BUTTON 3
static unsigned long last_press = 0;
static bool button_state = HIGH;             // подтянуть резистор
volatile bool interrupt_flag = 0;            //при срабатывании прерывания, нажатии кнопки или шуме
bool is_pressed = false;                     //после проверки, что кнопка действительно была нажата
bool was_pressed = false;                    //последнее состояние кнопки
unsigned long button_pause_duration = 20;        //требуется пауза для принятия нажатия в мс
unsigned long interrupt_time=0;

void button_interrupt() {
  interrupt_flag = true;                      //может быть ложная тревога
  Serial.print("interrupt time: ");         // это единственный шаг, который работает
  Serial.print(millis());
  Serial.print("\n");
}

void setup() {
  Serial.begin(9600);
  pinMode(IRF520, OUTPUT); //A2 управляет IRF520
  pinMode(BUTTON, INPUT_PULLUP);  //pin D2 и D3 подходят для прерываний
  attachInterrupt(digitalPinToInterrupt(BUTTON), button_interrupt, FALLING);
}

void debounce_button() {
  if (digitalRead(BUTTON) != button_state) {
    button_state = digitalRead(BUTTON);         //если кнопка изменила состояние запомнить новое состояние
    interrupt_time=millis();
  }
  if (button_state == LOW) {
    last_press = millis();
    Serial.print("last_press was updated to: ");
    Serial.print(last_press);
    Serial.print("\n");
  } else {
    last_press = 0;
    interrupt_flag = false;
  }
  if ( last_press && (millis() - last_press) > button_pause_duration) {
    last_press = 0;     // устанавливаем 0 для следующей проверки
    is_pressed = true;   //кнопка действительно была нажата
    Serial.print("last_press was set to 0 and is_pressed to true - BUTTON WAS PRESSED at: ");
    Serial.print(millis());
    Serial.print("\n");
  }
}

unsigned int foo(bool is_pressed) {
  if (is_pressed) {
    Serial.print("foo is active, MOSFET does its thing");
    Serial.print("\n");
    }
  }
}

void loop() {
 if(interrupt_flag){
    Serial.print("the button is now being pressed: ");
    Serial.print(millis());
    Serial.print("\n");
 }
 debounce_button();
 foo(is_pressed);
}

, 👍5

Обсуждение

не используйте serial.print() внутри обработчика прерывания... установите флаг прерывания и сохраните значение millis()... сделайте serial.print() внутри цикла(), @jsotola

Отредактировал вопрос, попробовал, но это не решает проблему., @Toma

&& в операторе if выглядит неправильно... что вы пытаетесь сделать?, @jsotola

Я проверяю правильность времени и то, что last_press не равно 0. Потому что оно равно 0 для случаев, которые я игнорирую, и если оно не равно 0, наряду с другим условием, это реальное событие., @Toma

В общем, использовать прерывания для дребезга кнопок — плохая идея. И прерывания не могут делать отладочные распечатки., @DataFiddler


3 ответа


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

7

Итак, я взял Mega и несколько кнопочных переключателей (один из которых, к моему большому удивлению, оказался нажимным, нажимным) и состряпал небольшую программу для Arduino, чтобы проиллюстрировать кнопку, управляемую прерыванием. техника дебаунса. Пожалуйста, не стесняйтесь указывать на любые ошибки ;-)

Вот небольшая программа для устранения дребезга любого мгновенного контактного выключателя. Линия прерывания Mega (вывод 2, прерывание 0) срабатывает при любом переходе от низкого уровня к высокому или от высокого к низкому. подпрограмма обслуживания прерываний (ISR) просто устанавливает флаг в значение TRUE и завершает работу. Если несколько прерываний происходят до того, как функция loop() сможет обслужить флаг, второй и последующие вызовы ISR ничего не делают (ну, они устанавливают флаг, но поскольку он уже был установлен...). Код loop() ищет установленный флаг, и если он его находит, генерирует импульс длительностью 50 мкс на выбранном контакте, задерживает 100 мс, сбрасывает флаг и завершает работу. Пока действие дребезга контактов переключателя прекращается до истечения задержки 100 мс, будет генерироваться один и только один импульс на переход. YMMV.

/*
Небольшая программа для демонстрации техники устранения дребезга переключателей с использованием прерывания.
ISR вызывается для переходов от низкого к высокому и от высокого к низкому и может получить
вызывается несколько раз за одно нажатие кнопки. Однако код в loop() гарантирует
что каждый переход производит один и только один импульс за переход, предполагая
типичная максимальная продолжительность отскока 10-20 мс.

Обратите внимание, что если строка 'delayMicroseconds(50); //используется для отображения нескольких отказов
используется вместо строки 'delay(100);//игнорировать отказы в течение 100 мс', тогда
несколько отказов контактов будут генерировать свои собственные импульсы длительностью 50 мкс. Это
полезно для диагностики O'scope, если он у вас есть.
*/

#include <elapsedMillis.h>
#include <PrintEx.h> //позволяет распечатывать синтаксис в стиле printf

StreamEx mySerial = Serial; //добавлено для печати в стиле printf

//назначения контактов
const int LED_PIN = 13;
const int BUTTON_PIN = 2;
const int BUTTON_STATE_PIN = 3;

//переменные
bool buttonState = 0;
bool blinkState = 0;

// =============================================== ================
// === ПРОЦЕДУРА ОБНАРУЖЕНИЯ ПРЕРЫВАНИЯ ===
// =============================================== ================

volatile bool Interrupt = false;     // указывает, перешел ли вывод прерывания MPU в высокий уровень
void BtnInterrupt() 
{
    Interrupt = true;
}



// =============================================== ================
// === НАЧАЛЬНАЯ НАСТРОЙКА ===
// =============================================== ================

void setup() {
    // настраиваем пины
    pinMode(LED_PIN, OUTPUT);
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    pinMode(BUTTON_STATE_PIN, OUTPUT); // вывод монитора прицела
    digitalWrite(BUTTON_STATE_PIN, LOW);

    buttonState = digitalRead(BUTTON_PIN);
    mySerial.printf("Initial button state is %s\n", buttonState == LOW ? "LOW" : "HIGH");
    attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), BtnInterrupt, CHANGE);
}



// =============================================== ================
// === ГЛАВНЫЙ ПРОГРАММНЫЙ ЦИКЛ ===
// =============================================== ================

void loop() 
{
    if (Interrupt)
    {
        digitalWrite(BUTTON_STATE_PIN, HIGH);
        delayMicroseconds(50);
        digitalWrite(BUTTON_STATE_PIN, LOW);
        // задержка в микросекундах (50); //используется для отображения нескольких отказов
        delay(100);//игнорировать отказы в течение 100 мс
        Interrupt = false;
    }
}

Вот два снимка экрана O'scope. На одном показана последовательность импульсов, сгенерированная нажатием одной кнопки с задержкой 50 мкс, а на другом показан тот же переключатель с задержкой 100 мс. На обеих фотографиях шкала времени по горизонтали составляет 500 мкс/см.

,

Удивительный ответ. Это заслуживает больше лайков!, @Tono Nam

это отлично работает, спасибо!, @bardulia


0

Я думаю, проблема в том, что вы запускаете прерывание только при нажатии кнопки (прерывание ПАДЕНИЕ)

Поэтому нет возможности вызвать debounce при отпускании кнопки.

Поэтому невозможно изменить значение переменной состояния кнопки обратно на ВЫСОКОЕ (т. е. когда кнопка отпущена).

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

  If (millis () >= debugTime) {
       DebugTime = millis() + 1000:   // вывод каждую секунду...
       Serial.print("value of button state is: ");
        Serial.println(button_state);
      // и несколько других переменных.
  }

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

И я думаю, что вы уже это сделали, но также удалите операторы печати в ISR.

,

0

Проблема в том, что вам нужно проверить, было ли последнее прерывание достаточно далеко в прошлом, чтобы результат можно было считать стабильным. Для меня наиболее очевидным решением является запись ISR времени его вызова, а основной цикл может просто проверить, достаточно ли это время до текущего времени. Сравнение очень дешевое и не требует задержки. «То же самое время прерывания записи и последующего сравнения»; может охватывать несколько кнопок на микроконтроллерах, таких как ATTINY85, который использует один общий ISR. Разрешение неоднозначностей и т. д. находится за пределами ISR.

,