Обработка кнопки на прерывании+power down mode. Где ошибка?
- Войдите на сайт для отправки комментариев
Втр, 19/02/2019 - 02:46
Прошу помощи с багом обработки кнопки. При отпускании после долгого нажатия происходит ложное распознование клика.
Что уже делает - кнопка сидит на прерывании и через 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; }// Конец функции определения нажата ли кнопка
проблема тут
А можно спросить : что является результатом "digitalread(ButtonPIN)&&NumberInterruptsButton" и какой смысл сравнивания с числом "количество нажатий"? Или я напутал порядок выполнения в IF?
На входе пачка прерываний (дребезг при нажатии или отпускании кнопки). NumberInterruptsButton инкременируется при каждом событии нажатия/отпускания кнопки. Причем переменная может увеличится как на 1 так и на 5 при нажатии/отпускании. Ее скорее нужно флагом заменить что событие произошло да и все.
Т.е. NumberInterruptsButton это флаг того что произошло событие.
Обдумав тему прихожу к следующим заключениям: Чтобы понять что был клик и небыло длительных нажатий необходимо знать состояние кнопки за мгновение до события кнопки и после события. Чисто на прерывании без временной шкалы это сделать невозможно. Нужно проверить состояние кнопки после события. Прихожу к выводу что нужно задействовать отдельно таймер, который и будет каждые 10 мс опрашивать состояние кнопки. В таком случае все зря и достаточно библиотеки OneButton или GyverButton.
Хотелось конечно обойтись одним прерыванием порта, но мне все больше кажеться что это невозможно. т.к. хочется видеть следующее:
loop{
delay (100000)
if
(ifClick()) // получить ответ что клик был вне зависимости от давности этого клика и текущем состоянии кнопки.
}
Думаю прерывание хорошо подойдет для отслеживания события приравненного к клику. т.е. любое нажатие (длинное или короткое) приравнивается к клику. 1 кнопка - одно действие.
в том и дело. что не должо быть никакой пачки прерываний,
прерывание это событие - кнопка нажата, а вот далее это событие следует правильно обработать.
Ну смотрите как я представляю себе:
Поступило прерывание, причем штук 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 функция обработки прерывания кнопки не запущена.
А поскольку необходимо производить проверку по таймеру - выходит что прерывания от кнопки излишни.
Ну или после прерывания от кнопки запускать таймер. В подпрограмме таймера проверять состояние и в зависимости от него обнулять и останавливать таймер. Тоже вариант.
Старайтесь излагать свои мысли короче, без лишней воды и желательно так, чтобы каждую фразу можно было превратить в строку кода.
Поступило прывание => запрещаем прерывание, ставим флаг прерывания,, можем что-то мелкое сделать (увеличить счетчик нажатий, например), выход из прерывания.
Обработка нажатия в функции (теле кода)
{если флаг установлен =>
если переменная времени =0 => грузим переменную времени, выход;
не = 0 => проверяем истекло время дребезга или нет, нет => выход;
время истекло =>проверяем Отпущена кнопка или нет, нет => выход;
отпущена, => обнулить флаг, обнулить переменную времени, разрешить прерывание, выход;
флаг не установлен => выход}
теперь пара слов о физике процесса.
Кнопку обычно вешают с пина на землю и при этом подтягивают пин резистором к питанию, а между пином и землей ставят конденсатор для подавления дребезга.
так вот, процессы замыкания и размыкания кнопки здесь не равноправны. При замыкании происходит разряд источника энергии -конденсатора, а при размыкании его заряд. И если первый процесс импульный, то второй плавный, апериодический. Поэтому словить звон при отпускании кнопки шансов куда как меньше , чем при замыкании. Только, примерно, через время= RC напряжение на конденсаторе дорастет до уровня 1 МК, и любое повторное замыкание кнопки в это время приведет только к рестарту времени.
Отсюда следует, что при правильно подобранной RC цепи, программная обработка отпущенной кнопки обычно не требуется.
Отказался от своей идеи. Всем спасибо. Пришел к следующему методу обработки кнопки
От ГэБаттона ещё откажитесь и всё будет вообще шоколадно.
А какой взять?