Обработка кнопки на прерывании+power down mode. Где ошибка?

p-a-h-a
Offline
Зарегистрирован: 17.01.2019

Прошу помощи с багом обработки кнопки. При отпускании после долгого нажатия происходит ложное распознование клика.

Что уже делает - кнопка сидит на прерывании и через 1,5 секунды после последнего нажатия камень полностью выключается (SLEEP_MODE_PWR_DOWN).

Имеет три функции:

  if (ifHold()) Serial.println("Кнопка нажата");
  if (ifClick()) Serial.println("Был Клик");
  if (ifPress()) Serial.println("Было Долгое нажатие");

ifClick() возвращает 1 при коротком нажатии, ifPress() возвращает единицу один раз до отпускания. ifHold() возвращает единицу при каждом вызове до момента отпускания (это для инкремента).

Скетч состоит из двух файлов. Магия обработки кнопок во втором файле. Запросы функций в первом.

Первый файл:

#include <avr/sleep.h>//Для погружения в сон
#define ButtonPIN 2// кнопка подключена сюда (PIN --- КНОПКА --- GND)
#define TimePress 500 // Время долгого нажатия, в мс
#define DeBounce  300 // Время антидребезга, в мс
volatile unsigned long NumberInterruptsButton = 0; // Количество нажатий кнопки
volatile unsigned long TimePressButton = 0; // Время последнего нажатия кнопки

void setup() {
  Serial.begin(9600);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);//Настройка спящего режима
  pinMode(ButtonPIN, INPUT_PULLUP); //Порт D2 на вход с подтяжкой
    //прерывания от PORTD 2 2arduino
    PCICR = 0b00000111;  // разрешим уже сами прерывания в общем
    PCMSK2 = 0b00000100; // разрешим прерывание на 2 arduino (PORTD 2)

}

void loop() {

  if (ifHold()) Serial.println("Кнопка нажата");
  if (ifClick()) Serial.println("Был Клик");
  if (ifPress()) Serial.println("Было Долгое нажатие");

if (millis() > TimePressButton+1500)  powerdown(); // Через секунду после последнего нажатия выключаем микроконтроллер
}

void powerdown() { //Режим энергосбережения
  byte ddrB = DDRB; byte ddrC = DDRC; byte ddrD = DDRD; //Запоминаем состояние портов на вход/выход
  DDRB = 0; DDRC = 0; DDRD = 0; // Все ноги на вход для экономии энергии
  PRR = B11111101; //Отключаем переферию. Режим энергосбережения, не трогаем UART. Иначе снова нужно инициализировать.
  sleep_mode();//Отключаем генератор
  PRR = B11011101; // Включаем timer0 для работы millis
  DDRB = ddrB; DDRC = ddrC; DDRD = ddrD;
}// восстанавливаем состояние портов после сна

Второй файл:

ISR(PCINT2_vect) { //Прерывания от 0 - 7 (PORTD 0..7)
if (!(PIND & (1 << PD2))) { // проверяем пин на прерывание
 static volatile unsigned long DeBounceTime=0; //Переменная в которой хранится время окончания подавления дребезга

    if (DeBounceTime<millis()){ DeBounceTime=millis()+DeBounce;  // Антидребезг
                                  NumberInterruptsButton++; //Храним количество нажатий (сбрасываются функцией обработки)
                                  TimePressButton=millis(); //Храним время начала последнего нажатия
                              }
                           }
             }// Конец обработки прерывания



boolean ifClick(){// Возвращает 1 если был клик на кнопку 

  static unsigned long LastNumberInterruptsButton=0; // Последнее количество нажатий на кнопку
  boolean result = LOW; // Тут храним ответ для далнейшей передачи из функции

  if (millis()-TimePressButton>TimePress){// Пропускаем длинное нажатие и удержание
      LastNumberInterruptsButton=NumberInterruptsButton;
}
  if (digitalRead(ButtonPIN)&&NumberInterruptsButton!=LastNumberInterruptsButton){
      LastNumberInterruptsButton=NumberInterruptsButton;
            result = HIGH;
      }else result = LOW;
 
return result;
}// Конец функции одинарного клика



boolean ifPress(){// Возвращает 1 если было долгое нажатие на кнопку и 0 когда не было

  static unsigned long LastNumberInterruptsButton=0; // Последнее количество нажатий на кнопку
  boolean result = LOW; // Тут храним ответ для далнейшей передачи из функции

  if (digitalRead(ButtonPIN)&&(millis()-TimePressButton<TimePress)){//Пропускаем клик
            LastNumberInterruptsButton=NumberInterruptsButton; 
     }
  if ((NumberInterruptsButton!=LastNumberInterruptsButton)&&(millis()-TimePressButton>TimePress)){
            LastNumberInterruptsButton=NumberInterruptsButton;
            result = HIGH;
      }else result = LOW;
  
return result;
}// Конец функции определения долгого нажатия на кнопку



boolean ifHold(){// Возвращает 1 если кнопка нажата и 0 если кнопка отпущенна

  if (!digitalRead(ButtonPIN)&&(millis()-TimePressButton>TimePress*2))return HIGH; else return LOW;

}// Конец функции определения нажата ли кнопка
 
p-a-h-a
Offline
Зарегистрирован: 17.01.2019

проблема тут

 

boolean ifClick(){// Возвращает 1 если был клик на кнопку 

  static unsigned long LastNumberInterruptsButton=0; // Последнее количество нажатий на кнопку
  boolean result = LOW; // Тут храним ответ для далнейшей передачи из функции

  if (millis()-TimePressButton>TimePress){// Пропускаем длинное нажатие и удержание
      LastNumberInterruptsButton=NumberInterruptsButton;
}
  if (digitalRead(ButtonPIN)&&NumberInterruptsButton!=LastNumberInterruptsButton){
      LastNumberInterruptsButton=NumberInterruptsButton;
            result = HIGH;
      }else result = LOW;
 
return result;
}// Конец функции одинарного клика

 

T.Rook
Offline
Зарегистрирован: 05.03.2016

А можно спросить  : что является результатом "digitalread(ButtonPIN)&&NumberInterruptsButton" и какой смысл сравнивания с числом "количество нажатий"? Или я напутал порядок выполнения в IF?

p-a-h-a
Offline
Зарегистрирован: 17.01.2019

На входе пачка прерываний (дребезг при нажатии или отпускании кнопки). NumberInterruptsButton инкременируется при каждом событии нажатия/отпускания кнопки. Причем переменная может увеличится как на 1 так и на 5 при нажатии/отпускании. Ее скорее нужно флагом заменить что событие произошло да и все.

Т.е. NumberInterruptsButton это флаг того что произошло событие.

 

Обдумав тему прихожу к следующим заключениям: Чтобы понять что был клик и небыло длительных нажатий необходимо знать состояние кнопки за мгновение до события кнопки и после события. Чисто на прерывании без временной шкалы это сделать невозможно. Нужно проверить состояние кнопки после события. Прихожу к выводу что нужно задействовать отдельно таймер, который и будет каждые 10 мс опрашивать состояние кнопки. В таком случае все зря и достаточно библиотеки OneButton или GyverButton.

Хотелось конечно обойтись одним прерыванием порта, но мне все больше кажеться что это невозможно. т.к. хочется видеть следующее:

loop{

delay (100000)

if (ifClick()) // получить ответ что клик был вне зависимости от давности этого клика и текущем состоянии кнопки.

}

Думаю прерывание хорошо подойдет для отслеживания события приравненного к клику. т.е. любое нажатие (длинное или короткое) приравнивается к клику. 1 кнопка - одно действие.

ВН
Offline
Зарегистрирован: 25.02.2016

p-a-h-a пишет:
На входе пачка прерываний....

в том и дело. что не должо быть никакой пачки прерываний,

прерывание это событие - кнопка нажата, а вот далее это событие следует правильно обработать. 

p-a-h-a
Offline
Зарегистрирован: 17.01.2019

Ну смотрите как я представляю себе:

Поступило прерывание, причем штук 50 сразу в мс (дребезг). Вот диаграмма длинного нажатия

0                     10       30                               500   530                       540 Время, мс

1-----------------\/\/\/\/\/\________0________/\/\/\/\/--------------------1  Состояние порта

События           1            2                             3          4

событие между 1и2 кнопка замыкается, между 2и3 удерживается, между 3и4 размыкается. 

При событии от 1 до 2 мы запоминаем miliss()+debonce(=50мс). Это происходит при последнем событии в точке 2. После прохождения точки 2 необходимо определить состояние кнопки через время debonce. Если она все еще нажата спустя 50 мс, то событие с точки 1по 2 мы распознаем как нажатие на кнопку но результат пока не возвращаем. Еще через 50 мс снова нужно проверить состояние и если кнопка нажата то идентифицировать что это удержание, если отпущена то это короткое нажатие.

Вот и моя проблема - чисто прерыванием от порта невозможно проверить через 50, 100 мс. Необходимо выполнять отдельную проверку посаженную на таймер 1 или таймер 2. т.к  между точками 2 и 3 функция обработки прерывания кнопки не запущена. 

А поскольку необходимо производить проверку по таймеру - выходит что прерывания от кнопки излишни.

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

 

ВН
Offline
Зарегистрирован: 25.02.2016

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

Поступило прывание => запрещаем прерывание, ставим флаг прерывания,, можем что-то мелкое сделать (увеличить счетчик нажатий, например), выход из прерывания.

Обработка нажатия в функции (теле кода)

{если флаг установлен =>

если переменная времени =0 =>  грузим переменную времени, выход;

не = 0 => проверяем истекло время дребезга или нет, нет => выход;

время истекло =>проверяем Отпущена кнопка или нет, нет => выход;

отпущена, => обнулить флаг,  обнулить переменную времени, разрешить прерывание, выход;

флаг не установлен =>  выход}

 

теперь пара слов о физике процесса.

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

так вот, процессы замыкания и размыкания кнопки здесь  не равноправны. При замыкании происходит разряд источника энергии -конденсатора, а при размыкании его заряд. И если первый процесс импульный, то второй плавный, апериодический. Поэтому  словить звон при отпускании кнопки шансов куда как меньше , чем при замыкании. Только, примерно, через время= RC напряжение на конденсаторе дорастет до уровня 1 МК, и любое повторное замыкание кнопки в это время приведет только к  рестарту времени.

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

p-a-h-a
Offline
Зарегистрирован: 17.01.2019

Отказался от своей идеи. Всем спасибо. Пришел к следующему методу обработки кнопки


#define PIN 2				// кнопка подключена сюда (PIN --- КНОПКА --- GND)

#include "GyverButton.h"
GButton butt1(PIN);
int value = 0;

void setup() {
//Настраиваем таймер2 на 100Гц в режимеСТС
cli(); // Запрет прерываний
  TCCR2A |= (0 << COM2A1) | (0 << COM2A0) | (0 << COM2B1) | (0 << COM2B0) | (1 << WGM21) | (0 << WGM20);
  TCCR2B |= (1 << FOC2A)|(1 << FOC2B)|(0 << WGM22)|(1 << CS22)|(1 << CS21)|(1 << CS20);// CS22 CS21CS20 000 выкл, 001 без делителя, 010=8, 011=32, 100=64, 101=128, 110=256, 111=1024
  OCR2A=156; //16000000/1024/156=100,16Гц
  TIMSK2 |= (1 << OCIE2A);//Compare Match A interrupt is enabled.
sei(); // Разрешение прерываний
  Serial.begin(9600);
}

ISR(TIMER2_COMPA_vect){   // прерывание таймера
  butt1.tick();     // отработка теперь находится здесь
}

void loop() {
  if (butt1.isClick()) Serial.println("Click");         // проверка на один клик
//  if (butt1.isSingle()) Serial.println("Single");       // проверка на один клик
//  if (butt1.isDouble()) Serial.println("Double");       // проверка на двойной клик
//  if (butt1.isTriple()) Serial.println("Triple");       // проверка на тройной клик
//  if (butt1.hasClicks())                                // проверка на наличие нажатий
//    Serial.println(butt1.getClicks());                  // получить (и вывести) число нажатий
//  if (butt1.isPress()) Serial.println("Press");         // нажатие на кнопку (+ дебаунс)
//  if (butt1.isRelease()) Serial.println("Release");     // отпускание кнопки (+ дебаунс)
  if (butt1.isHolded()) Serial.println("Holded");       // проверка на удержание
  if (butt1.isHold()) Serial.println("Hold");         // возвращает состояние кнопки
//  if (butt1.isStep()) {                                 // если кнопка была удержана (это для инкремента)
//    value++;                                            // увеличивать/уменьшать переменную value с шагом и интервалом
//    Serial.println(value);                              // для примера выведем в порт
//  }
}
sadman41
Offline
Зарегистрирован: 19.10.2016

От ГэБаттона ещё откажитесь и всё будет вообще шоколадно.

p-a-h-a
Offline
Зарегистрирован: 17.01.2019

А какой взять?