Всем привет. Нашел тут на просторах Инета интересный пример у Andrey Frolov "Длинный и короткий клик." В его примере 2 кнопки. И все мне в этом примере нравится, но вот возникла потребность выжать из него 5ый режим (а в идеале бы и 6-ой), обработку долгого нажатия на обе кнопки сразу. 2ой день голову ломаю - ничего не выходит. Подскажите как это решить - плиз!
// обработчик прерывания по переполнению таймера 0 ISR(TIMER0_OVF_vect){ // опрос первой кнопки if (PINC&(1<<PC0)) // если на ПИНе 1 { if (button_count>=5 && button_count<60) button_clk=1; //если в рамках поднять флаг короткого клика 1 button_count=0;// обнулить счетчик } else // ИНАЧЕ на ПИНе 0 { if (button_count<61) button_count++; // инкермент счетчика с ограничением 61 if (button_count==60) button_clk=101;// если в счетчике 60 поднять флаг длинного клика 1 }
// опрос второй кнопки if (PINC&(1<<PC1)) // если на ПИНе 1 { if (button2_count>=5 && button2_count<60) button_clk=2;// если в рамках поднять флаг короткого клика 2 button2_count=0;// обнулить счетчик } else // ИНАЧЕ на ПИНе 0 { if (button2_count<61) button2_count++; // инкермент счетчика с ограничением 61 if (button2_count==60) button_clk=102; //если в счетчике 60 поднять флаг длинного клика 2 } }
int main(void) { TCCR0|=(1<<CS01) | (1<<CS00);// запуск таймера 0 с делителем 64 TIMSK|=(1<<TOIE0); // разрешить прерывание по переполнению таймера 0
DDRB|=(1<<PB0) | (1<<PB1) | (1<<PB2) | (1<<PB3);// пины светодиодов на выход
sei();//глобально разрешить перерывания while (1) { if (button_clk) // проверка флажка клика { switch (button_clk) { case 1:// отработка короткого клика 1 PORTB^=(1<<PB0); break;
case 101:// отработка длинного клика 1 PORTB^=(1<<PB1); break;
case 2:// отработка короткого клика 2 PORTB^=(1<<PB2); break;
case 102:// отработка длинного клика 2 PORTB^=(1<<PB3); break; } button_clk=0;// сбросить флаг клика в 0 }
Странный пример какой-то... Вторая кнопка имеет более высокий приоритет... Может конечно так и задумывалось...
Я бы вместо вместо числового значения "button_clk" переделал всё на биты. Например так: бит 0 = нажата кнопка 1 бит 1 = долго нажата кнопка 1 бит 2 = нажата кнопка 2 бит 3 = долго нажата кнопка 2
В обработчике прерывания выставлять нужные биты. А в "main" соответственно анализировать биты. Таким образом в "button_clk" будет информация о двух кнопках.
Ну либо если по простому и не жалко памяти, то завести "button_clk_1" и "button_clk_2" и анализировать потом две переменные. Но, как программирующий для AVR на asm, этот подход считаю не верным.
private: State buttons; uint16_t counter; uint8_t prev, prevLongPressed; };
Если Port - это собственно 8-ми битный порт AVR с включенными подтяжками, то данный код может работать с 8-ю кнопками, для каждой будет устанавливаться бит короткого/длинного нажатия и отпускания, помимо текущего состояния. И т.к. счетчик один, то отсчет начнется когда состояние на всех пинах стабилизируется, т.е. нажав две кнопки висящие на двух младших битах получим 0x03 для длинного нажатия.
Приоритет т правда выше. Я этот эфект заметил, когда эесперементировал. Вот бы вы подробнее объяснили почему и из-за чего.
В обработчике прерывания вначале анализируется состояние пина 1-й кнопки. Результат сохраняется в "button_clk". Затем анализируется пин 2-й кнопки. Результат сохраняется опять же в "button_clk". Соответственно, мы "забыли" что там сохранили по первой кнопке.
Тогда пробуйте сделать две переменных (button_clk1 и button_clk2), состояние каждой кнопки сохраняйте в свою переменную. Тогда ничего не будет затираться и кнопки будут иметь одинаковый приоритет.
Я себе обработчик кнопок через конечный автомат делал. У каждой кнопки есть набор состояний, в суперлупе не чаще раза в миллисекунду запускается проверка состояния кнопок. Если нужно детектировать долгое одновременное нажатие нескольких кнопок, достаточно их состояния сравнить где-нибудь в любом месте программы.
_________________ Linux rules! Windows must die. Здравомыслящий человек добровольно будет пользоваться мастдаем лишь в двух случаях: под дулом автомата или под влиянием анального зонда. Я на гитхабе, в ЖЖ
Я хотел топикстартера в сторону ассемблера направить, а Вы ему ООП предлагаете.
ООП я как раз не предлагаю, потому от изначального варианта там мало что осталось, просто функции внутри класса которые без проблем можно использовать отдельно.
ДядяВован Я хотел топикстартера в сторону ассемблера направить, а Вы ему ООП предлагаете. Мы так его совсем запутаем
Тоже поддерживаю данный посыл. Но начинать новичку всё же Имхо нужно с графа, где расписана только логика процесса (сиречь последовательность событий). Т. е. поэтапно, не всё сразу. А на чём он её потом будет реализовывать (на ассемблере или любом другом языке) уже неважно. И из практики - такой подход проще осваиваем и многие потом сами на простых задачах запросто переходят на ассемблер.
Из примера в топике у меня получилось реализовать только еще более длительное нажатие на кнопку пудем добавления колличества в счетчике и отдельного условия под него. Но на мой взгляд, как пользователя, это не совсем удобно, поскольку среднее нажатие нужно ловить интуитивно. Я по удлинненному длинному клику делал быструю смену значений переменной. Что бы 900 секунд по одному клику не накручивать. Дабл клик, мне кажется тоже не лучшим вариантом, поскольку не исключает возможности случайного двойного нажатия.
Мне понравился приведенный пример тем, что как раз исключена возможность передержки длинного клика. А быстрый инкремент значения по длинному клику я оставил лишь в некоторых пунктах меню, где нужно будет нащелкать большие значения. В установке часов оставил только короткие нажатия. А установке минут - быстрый инкимент при длинном клике. Индикация прибора сейчас делается на 8 рязрядном семисерментнике, а меню получается довольно длинное. Установки до 16 параметров в отдельных ветках. Делать возврат к предыдущему пункту - не информативно, да и кнопок всего 3. Увеличение кол-ва кнопок не вариант. Поскольку планируется установка в заводской корпус прибора просто с увеличением его функционал.
Добавлено after 12 minutes 57 seconds: Ассемблер конечно хорошо. Я понимаю, что более низкоуровневый язык кодинга - более правильно. Но я когда то, для свох задач немного сталкивался с Си. По этому он мне сейчас проще для освоения. Ну и мне кажется, что он более универсален, в смысле разширения горизонтов не только в сторону кодинга контроллеров.
Короткие, как обычно на обычное управление. Длинные все уже заняты, что бы меню не получалось еще более длинное. Как раз не хватает нажатия 2х кнопок одновременно. Пока что длнинного нажатияна 2 кнопки. Тогда мне всего хватит. А уж если получится выжать длинное парное, то и короткое парное, думаю будет не проблема.
Последний раз редактировалось Vovik-78 Пт фев 11, 2022 09:50:24, всего редактировалось 1 раз.
Мне кажется в корне ущербным, задавать режим разными стилями нажатия кнопок, масштабируемости и логики ноль. Я делаю двумя кнопками всё что угодно. Одна кнопка - Mode (Step), вторая - Ok (Change) Пример: Установка времени в часах: Основной режим (отображение времени) -> Mode (установка десятков часов), кнопка Ok меняет значение -> Mode (установка единиц часов), кнопка Ok меняет значение -> ... -> Основной режим
я недавно реализовал, как мне кажется, неплохую систему обработки кнопок... любого количества (до 8 штук, чтобы не морочить голову с большим количеством портов), возможно реагирование на нажатие, на отпускание или на нажатие и удержание нажатой, а так же с автоповтором при удержании, в любых комбинациях нажатий. для корректной работы необходим какой-то независимый счетчик времени (таймер). если интересно, могу рассказать принцип
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
В переделываемом устройстве помимо часов есть срабатывание по отсчету импульсов внешнего устройства, срабатывание по времени, 5 различных режимов работы в зависимости от конфигурации и назначения. При этом пересекаются лишь 3 общих режима. В некоторых режимах работы до 14 различных установки. И если на 2х кнопках все это сделать, то для изменения, к примеру, самого дальнего режима в меню, да и если изменить нужно с 500 секунд до 0, для отключения, это сколько же нужно будет стоять и клацать кнопкой.
Когда доделается на восми семисегментниках, уже буду переделывать на жк 20*4. Там, по идее, будет проще, в плане информативности пользования.
Карма: 90
Рейтинг сообщений: 1432
Зарегистрирован: Чт мар 18, 2010 23:09:57 Сообщений: 4594 Откуда: Планета Земля
Рейтинг сообщения:0 Медали: 1
Vovik-78 писал(а):
то для изменения, к примеру, самого дальнего режима в меню,
Совсем не понятно, причём тут дальный/ближний/средний режим ? Каким образом, увеличение кол-ва кнопок, или их комбинаций, может уменьшить длину меню (аля, кол-во изменяемых параметров/настроек) ? PS: 14 пунктов - это вообще ни о чём. 14 раз нажать всего. Есть пром-приборы, в которых по меню можно минут 10 лазить, чтобы куда-то добраться и что-то настроить.
я недавно реализовал, как мне кажется, неплохую систему обработки кнопок... любого количества (до 8 штук, чтобы не морочить голову с большим количеством портов), возможно реагирование на нажатие, на отпускание или на нажатие и удержание нажатой, а так же с автоповтором при удержании, в любых комбинациях нажатий. для корректной работы необходим какой-то независимый счетчик времени (таймер). если интересно, могу рассказать принцип
Один таймер у меня на динамической индикации через сдвиговые регистры. Плюс на третьем сдвигающем слелал подключение/ отключение дополнительного внешнего оборудования (разные насосики и клапаны). Один таймер висит на подсчете импульсов внешнего источника импульсов по Т0 или Т1, не помню. Один вот, под кнопки. Внешний источник импульсов можно конечно и на внешнее прерывание завести. Еще один таймер освободив. Все это на Меге 8а. Так что да, интересно. Буду благодарен за любые варианты. Ибо не в этом, так в другом приборе пригодится.
Есть пром-приборы, в которых по меню можно минут 10 лазить, чтобы куда-то добраться и что-то настроить.
И что в этом хорошего? По мне так проще один раз 2 нажать, чем 20 раз одну. Тем более, что это не невыполнимое желание. Просто я пока не могу его реализовать. для чего и обратился за помощью. Если бы не размещение в заводском корпусе, который рассчитан на 3 кнопки, то может и добавил бы еще одну или 2. Но это и для обучающего процесса тоже не правильный подход, на мой взгляд. К такому варианту можно прибегнуть, если совсем нет выхода. А тут есть. просто моих знаний пока не хватает для их реализации. На мой взгляд, устранение пробелов в знании и понимании реализации - самый правильный выход. Делается это для себя, в первую очередь. И клацать 40 раз мне не очень охота.
под кнопки отдельного таймера не надо, достаточно в любом уже задействованном добавить один счетчик. этот счетчик должен считать примерно каждые 10 мс, можно от 1 до 20, больше не желательно, чтобы не терять события.
код в целом такой:
Код:
// эти константы измеряются в тиках счетчика, т.е. если счетчик // тикает каждые 10 мс, то время долгого удержания будет 3 сек, а врем автоповтора при удержании 300 мс #define LONG_PRESS_TIME (300) #define REPEAT_TIME (30) // если ни одна кнопка не нажималась это время, будет послано сообщение EVENT_DEFAULT_MODE #define DEFAULT_MODE_TIME (3000)
/** * функция возвращает код события в момент нажатия кнопки. * если кнопка удерживается нажатой долго, то через LONG_PRESS_TIME * код события возвращается повторно с флагом EV_LONG, * а затем повторяется каждые REPEAT_TIME. * при отпускании кнопки код событи не возвращается * каждые DEFAULT_MODE_TIME с момента выдачи последнего кода события * при отсутствии нажатий кнопок автоматически формируется событие * EVENT_DEFAULT_TIME * @return код события */ event_t get_event(void){ uint8_t btn = get_key(); // эта функция должна вернуть битовое состояние кнопок uint16_t now = get_ticks(); // вот эта функция должна вернуть значение упомянутого счетчика int16_t
event_t tmp;
if(btn == 0){ // ничего не нажато if(!(prev & EV_RELEASED) && (prev != EVENT_EMPTY)){ // фиксируем момент отпускания cnt = now; prev |= EV_RELEASED; // уточняем, сколько времени прошло с предыдущего нажатия } else { prev = EVENT_EMPTY; } if((now - cnt) > DEFAULT_MODE_TIME){ // если больше заданного - возвращаем событие сброса режима cnt = now; return EVENT_DEFAULT_MODE; } return prev; } else { // что-то нажато, разбираем кнопки по событиям // KEY_XXX - это БИТОВЫЕ МАСКИ кнопок switch(btn){ case KEY_UP: tmp = EVENT_UP; break; case KEY_DN: tmp = EVENT_DN; break; case KEY_ENT: tmp = EVENT_ENTER; break; case KEY_ESC: tmp = EVENT_ESC; break; // а вот и комбинация одновременно нажатых кнопок case KEY_UP | KEY_DN: tmp = EVENT_UPDN; break; default: // всё, что выше не описано, будет игнорироваться tmp = EVENT_EMPTY; } } // проверка долгое/короткое нажатие if(tmp == (prev & EVENT_IGNORE_LONG)){ // нажато то же самое, что и в прошлый раз if((now - cnt) > LONG_PRESS_TIME){ // если время удержания больше заданного, ставим флаг долгого события prev = tmp; tmp |= EV_LONG; cnt += REPEAT_TIME; return tmp; } } else { // нажато что-то другое, чем в прошлый раз cnt = now; // фиксируем момент нажатия prev = tmp; // запоминаем, что нажали return tmp; } return EVENT_EMPTY; }
/// любое первое событие, кроме пустого и "по умолчанию", сопровождает звуковым сигналом event_t get_key_event(void){ event_t ev = get_event(); //if((ev != EVENT_EMPTY) && (ev != EVENT_DEFAULT_MODE) && !(ev & (EV_RELEASED | EV_LONG))) // play_song(beep); return ev; }
в начале кода есть две функции, которые вы должны реализовать. первая опрашивает порт кнопок и возвращает ЕДИНИЦЫ в битах, соответствующих нажатм кнопкам. вторая просто должна вернуть значение счетчика тиков.
в дополнение к этому коду вы делаете такое описание комбинаций ваших кнопок
Код:
/// флаг повтора события (при долгом удержании) #define EV_LONG 0x80 /// флаг отпускания кнопки #define EV_RELEASED 0x40
/// варианты генерируемых событий typedef enum{ EVENT_EMPTY, //!< события отсутствуют EVENT_UP, //!< кнопка ВВЕРХ EVENT_DN, //!< кнопка ВНИЗ EVENT_ENTER, //!< кнопка ЭНТЕР EVENT_UPDN, //!< обе кнопки ВВЕРХ и ВНИЗ EVENT_ESC, EVENT_DEFAULT_MODE, //!< автособытие - перейти в режим времени EVENT_UP_LONG = EVENT_UP | EV_LONG, //!< ВВЕРХ удерживается долго EVENT_DN_LONG = EVENT_DN | EV_LONG, //!< ВНИЗ удерживается долго EVENT_ENTER_LONG = EVENT_ENTER | EV_LONG,//!< ЭНТЕР удерживается долго EVENT_UPDN_LONG = EVENT_UPDN | EV_LONG,//!< ВВЕРХ и ВНИЗ удерживаются долго EVENT_ENTER_RELEASED = EVENT_ENTER | EV_RELEASED,//!< кнопка ЭНТЕР отпущена EVENT_ESC_LONG = EVENT_ESC | EV_LONG, EVENT_ESC_RELEASED = EVENT_ESC | EV_RELEASED, // не добавлять ниже! EVENT_COUNT //!< общее количество событий (не используется) } event_t;
в главном коде вы получаете событие и выполняете то, что вам надо сделать
Код:
event_t ev = get_event(); switch(ev){ case EVENT_NONE: break; // ничего не нажималось и не отпускалось сase EVENT_UP | EV_RELEASED : break; // если отпущена кнопка UP case EVENT_UP_LONG: break; // если нажата и долго удерживается кнопка UP // и так далее }
единственное, что вы должны учитывать, что если вам надо реагировать раздельно на короткое и долгое нажатие комбинации кнопок, вы должны обрабатывать долгое, как выше, а короткое - НА ОТПУСКАНИЕ, то есть с EV_RELEASED, если сделать без EV_RELEASED просто EVENT_UP, то сначала сработает обработка КОРОТКОГО нажатия, а потом уже ДОЛГОГО.
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Спасибо вам огромное. Обязательно буду разбираться с вашим примером.
Добавлено after 10 minutes 24 seconds: А пока что получилось у меня из моего примера выжать Обработчик нажатия двух долгих кнопок. Код конечно сильно увеличился, и я уверен, что его можно сократить, но в нужном мне формате из всего, что я перепробовал, работает пока что только так.
// обработчик прерывания по переполнению таймера 0 ISR(TIMER0_OVF_vect){ // опрос первой кнопки if (PINC&(1<<PC0)) // если на ПИНе 1 { if (button1_count>=5 && button1_count<60) button1_clk=1; //если в рамках поднять флаг короткого клика 1 button1_count=0;// обнулить счетчик } else // ИНАЧЕ на ПИНе 0 { if (button1_count<61) button1_count++; // инкермент счетчика с ограничением 61 if (button1_count==60) button1_clk=2;// если в счетчике 60 поднять флаг длинного клика 1 }
// опрос второй кнопки if (PINC&(1<<PC1)) // если на ПИНе 1 { if (button2_count>=5 && button2_count<60) button2_clk=1;// если в рамках поднять флаг короткого клика 2 button2_count=0;// обнулить счетчик } else // ИНАЧЕ на ПИНе 0 { if (button2_count<61) button2_count++; // инкермент счетчика с ограничением 61 if (button2_count==60) button2_clk=2; //если в счетчике 60 поднять флаг длинного клика 2 } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if ((button1_count>0) && (button2_count>0)) { if ((button1_count==60) && (button2_count==60)) //Если кнопки нажаты реально одновременно button3_clk=1; if ((button1_count==61) && (button2_count==60)) //Если чуть раньше нажата кнопка 1 button3_clk=1; if ((button1_count==60) && (button2_count==61)) //Если чуть раньше нажата кнопка 2 button3_clk=1;
} else {
if (button1_clk>0) { if (button1_clk==1) button_clk=1; if (button1_clk==2) button_clk=101; }
if (button2_clk>0) { if (button2_clk==1) button_clk=2; if (button2_clk==2) button_clk=102; } }
if (button3_clk==1) { button_clk=111; } button2_clk=0; button1_clk=0; button3_clk=0; }
int main(void) { TCCR0|=(1<<CS01) | (1<<CS00);// запуск таймера 0 с делителем 64 TIMSK|=(1<<TOIE0); // разрешить прерывание по переполнению таймера 0
DDRB|=(1<<PB0) | (1<<PB1) | (1<<PB2) | (1<<PB3) | (1<<PB4);// пины светодиодов на выход
sei();//глобально разрешить перерывания while (1) { if (button_clk) // проверка флажка клика { switch (button_clk) { case 1:// отработка короткого клика 1 PORTB^=(1<<PB0); break;
case 101:// отработка длинного клика 1 PORTB^=(1<<PB1); break;
case 2:// отработка короткого клика 2 PORTB^=(1<<PB2); break;
case 102:// отработка длинного клика 2 PORTB^=(1<<PB3); break;
case 111:// отработка длинного клика обеих кнопок PORTB^=(1<<PB4); break; } button_clk=0;// сбросить флаг клика в 0 }
} }
Если кто то подскажет, как сократить код, сохранив функциональность - буду признателен.
Добавлено after 28 minutes 19 seconds: Хотяяяя, мне наверное даже нравится то, что получилось. Если убрать обработчик 100% одновременного нажатия двух кнопок - уж сильно маловероятно, и разделить нажатия чуть раньше одной кнопки перед другой , то в моем варианте из 3х кнопок можно получить 12 разных отдельных событий, практически без возможности ложного срабатывания.
Ну либо если по простому и не жалко памяти, то завести "button_clk_1" и "button_clk_2" и анализировать потом две переменные. Но, как программирующий для AVR на asm, этот подход считаю не верным.
Спасибо Вам за пинок в правильном направлении. Может то, что получилось, скорее всего не по программному феншую, мне так кажется. Но думаю имеет свое право на жизнь в рамках первого проекта
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 18
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения