Интерфейс — одна кнопка

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

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

Итак, что у умеет наша кнопка?
 

  • Ее можно нажимать кратко
  • Можно жать длинно
  • Можно делать разные комбинации нажатий
  • Ее можно отпускать в нужный момент

 

Не густо, но вполне ничего. Для одной кнопки то. Главный затык при написании этого не просрать массу системных ресурсов (время процессора, таймеры и тыды) на обработку этой несчастной кнопки. Ведь нам придется отслеживать факт нажатия, факт отжатия, время нажатия, число нажатий с разным временем. Я видел такие адовые реализации этого интерфейса, что просто диву давался как можно нагородить столько тупняков и тормозов в этой одной сосне, да еще потратить все таймеры :)
 

Так что ТЗ выставим следующее:
 

  • Никаких аппаратных таймеров, кроме таймера диспетчера.
  • Никаких временных задержек, только вызов себя же по таймеру.
  • Никаких ожиданий нажатия-отжатия в цикле. Зашли, проверили — отдали управление.
  • Введем временной интервал mode_time, в течении которого будем отслеживать комбинацию нажатий. Скажем 2с
  • На выходе будем иметь число коротких и длинных нажатий за данный интервал

 

Алгоритм
Сделаем все на конечном автомате. У него будут три состояния:
 

  • Up — кнопка не нажата
  • Dn — кнопка нажата
  • Al — кнопка отпущена после длительного нажатия

 
А также будет одна служебная процедура, которая спустя mode_time (2c) после первого экшна с кнопкой сгребет все результаты и что-нибудь с ними сделает. Что — это уже не важно. От программы зависит.
И вся эта дребедень будет крутиться в цикле, вызывая сама себя через диспетчер (или каким еще образом) раз в 20мс.
 


 

Up
Входим.
Смотрим не нажата ли кнопка? Если нет — выходим. Если нажата, то переводим автомат в положение Dn
Проверяем первый ли раз за интервал мы тут? Если первый, то поставим нашу служебную процедуру на отложенный запуск (через 2с), взведем флаг, что процесс пошел.
Выходим.
 

Dn
Входим.
Еще нажата? Если нет, значит кнопка уже отпущена, скидываемся в состояние в Up и засчитываем одно короткое нажатие, увеличивая счетчик коротких нажатий cnt_s. Если еще нажата, то щелкаем счетчиком времени замера длительности нажатия Timе. Замер длительности у нас идет в итерациях автомата. Одна итерация 20мс. В лимит длинного нажатия я заложил 20 итераций, что дает около 400мс. Все что больше 0.4с считаем длинным нажатием. Как натикает больше 20 итераций, то засчитываем одно длинное нажатие и перекидываем автомат в состояние Al. Выходим.
 

Al
Входим.
Еще не отпустили? Если нет, то выходим. Если кнопка отпущена, то перебрасываемся в Up, скинув переменную Time.
 


 

За время mode_time, за те две секунды, сколько успеем натыкать — все наше. Запустится процедура анализа собранных данных и разгребет натыканное. Там уже все просто. Банальным case’ом делаем нужный нам экшн. Я вот, например, флажки выставляю которые перехватывает другая задача. Главное не блокировать эту задачу ничем тяжелым, чтобы не прозевать следующую комбинацию.
 

Код
 

Показать »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <button.h>
u08 bt1,bt2,bt3,bt4,bt5,bt_l,bt_l2,bt_al;	// Переменные - флажки нажатых кнопок. 
						// Эффективней их сделать битовыми полями
				// Но мне было лень. Оптимизируйте сами :)
u16 bt_mode_time = 2000;	// Длительность последовательности анализа
				// Сделано переменной, а не константой
				// чтобы можно было менять на лету. Снижая
				// там, где не надо ждать длительных комбинаций
				// что сильно повышает отзывчивость интерфейса
 
u08 bt_cnt;		// Флаг сигнализирующий, что идет анализ последовательности		
u08 bt_cnt_s;		// Счетчик коротких нажатий		
u08 bt_cnt_l;		// Счетчик длинных нажатий
 
void bt_scan(void)	// Эта функция сканер. Она должна вызываться раз в 20мс
{
#define up 	0	// Дефайны состояний автомата. 0 - по дефолту.
#define dn 	1
#define al 	3
 
 
static u08 bt_state=0;		 // Переменная состояния автомата
static u08 bt_time	=0;	// Переменная времени работы автомата
 
switch(bt_state)		// Собственно автомат
	{
	case up:	
		{
		if(Close)			// Если нажата кнопка
			{	
			bt_state = dn;		// Стадию в Down
			bt_time = 0;		// Обнуляем счетчик времени
 
			if(bt_cnt==0)		// Если первый раз в комбинации
				{
				SetTimerTask(bt_ok,bt_mode_time);	// Запускаем процедуру анализа
				bt_cnt =1;				// Подсчет пошел!
				}
			}
			break;					// Выход
			}
 
	case dn:
		{
		if(Close)			// Все еще нажато? 
			{
			if (20 > bt_time)	// Нажато меньше чем 20*20мс? 
				{		// Да
				bt_time++;	// Увеличиваем счетчик итераций
				}
			else
				{
				bt_state = al;	// Нет, уже больше! Да у нас длинное нажатие! Переходим в АЛ
				bt_time = 0;	// Сбрасываем время замера нажатия					
				bt_cnt_l++;	// Засчитываем одно длинное нажатие
				}
			}
		else
			{
			bt_state = up;		// Кнопка отпущена? 
			bt_time = 0;		// Время замера в ноль
 
			bt_cnt_s++;		// Счетчик коротких нажатий
			}
 
		break;				// Выход
		}
 
	case al:				// А тут мы если было длинное нажатие
		{
		if(Open)			// Отпустили?
			{
			 bt_state = up;		// Да! Стадию в Up
			 bt_time = 0;		// Время в 0
 
			 bt_al = 1;		// Зафиксировали событие "Отпускание после длинного"
			}
 
		break;
		}
	}
SetTimerTask(bt_scan,20);		// Зациклились через диспетчер.
}
 
 
// А это функция которая через 2 секунды сработает и подберет все результаты подсчета
void bt_ok(void)			// Ловим дешифровку событий тут
{
switch(bt_cnt_s)			// Смотрим сколько нажатий коротких
	{
	case 1: bt1 = 1; break;	// Такой флажок и ставим
	case 2: bt2 = 1; break;
	case 3: bt3 = 1; break;
	case 4: bt4 = 1; break;
	case 5: bt5 = 1; break;
	default: break;
	}
 
switch(bt_cnt_l)			// Смотрим сколько нажатий длинных
	{
	case 1: bt_l = 1; break;	// Такой флажок и ставим
	case 2: bt_l2 = 1; break;	
	default: break;
	}
 
bt_cnt = 0;	// Сбрасываем счетчики
bt_cnt_s = 0;
bt_cnt_l = 0;
 
}

 

Код написан так, что на AVR там завязана буквально пара строчек. По крайней мере в коде обработчика нажатий кнопки. Все привязки на железо идут в хидере, да и их там всего ничего:
 

1
2
3
4
5
6
7
8
9
10
11
#include <avrlibtypes.h>
#include <avrlibdefs.h>
 
#define Open	(BTN_PIN & 1<<BTN)
#define Close	(!Open)
 
extern void bt_scan(void);
void bt_ok(void);
 
extern u08 bt1,bt2,bt3,bt4,bt5,bt_l,bt_l2,bt_al;
extern u16 bt_mode_time;

 

Так что портировать это на другую архитектуру дело смены двух строчек. Ну и, возможно, вам потребуется изменить механизм автозапуска и запуска функции обработчика. Если вы будете использовать какой то свой диспетчер, ОС или еще какую систему организации прошивки. Но это две строчки в коде поправить.
 

Все описанное в статье мясо лежит в двух файлах button.c и button.h
 

Видео работы


 

Дребезг
Боротся с дребезгом тут уже не обязательно. Т.к. частота сканирования небольшая, так что даже голимая и наглухо окисленная кнопка модели ТМ2 не давала дребезга — он кончался раньше, чем наступал следующий скан. А вот что тут можно докурить, так это защиту от ложных сработок в результате наводок. Ведь стоит помехе продавить линию в момент считывания и засчитается сработка однократного нажатия. Это можно избежать сделав проверочные состояния автомата. Скажем добавив в Up счетчик итераций, чтобы в течении, скажем, двух-трех итераций подтвердить, что кнопка таки нажата и только тогда переходить в Dn.
 

Варианты
Правда в своем проекте я несколько изменил обработку. Т.к. мне не нужны были множественные длинные нажатия, то я сделал выставление флага «Длинное нажатие» сразу же в обработчике AL и, заодно, убрал подсчет числа длинных нажатий. Что позволило повысить отзывчивость работы интерфейса прибора, где длительным нажатием осуществлялся вход в пункт меню, а комбинаций с двумя длинными нажатиями не использовались вообще.
 

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

37 thoughts on “Интерфейс — одна кнопка”

  1. Вот за применение таких «3 раза попрыгать на правой ноге, 2 раза присесть на левой, и полаять» надо кастрировать, четвертовать и голову оторвать (именно в такой последовательности). вам чё, вывода жалко и пользователю теперь надо трахаться с вашим интерфейсом?
    Реализация принимается (как решение поставленной задачи), но сама идея говно.

    P.S.: блоксхема на двоечку: ветки да/нет у вас могут быть спокойно выведены в одних направлениях. Вы же поставили в разнобой. Итог: блоксхема не читаема (читается с ошибками).

    1. Хыыы, чувак. Дело не в том, что жалко вывода, а в том, что часто кнопку поставить ТУПО НЕКУДА. Сейчас делаю контроллер нагревателя. Конструктив весь уписывался в цилиндр высотой 2см и диаметром 2см, причем там должна быть кнопка управления, силовой ключ на 10А, аналоговая обвязка замера тока с шунта, обвязка замера напряжения, контроллер управления и семисегментный дисплей на 2 разряда. Исходя из ручного серийного монтажа минимальный типоразмер резисторов 0805 и корпуса все должны быть выводные, т.е. tqfp и им подобные и все это монтируется в фрезерованную алюминиевую трубу. Габарит, напомню, цилиндр 2 на 2см. Одну сторону занимал 7сегментный дисплей, с другой был крепежный винт. Еще одна сторона под кнопку. Вторую там поставить было просто некуда. А сам девайс подразумевал сложное древовидное меню и кучу всего. Сама логика перемещения простая. Одно нажатие — вниз по меню, два быстрых — вверх по меню. Длинное нажатие — вход в меню. Короткое и длинное — выход на предыдущий уровень. Ниче, вполне логично и удобно получилось.

          1. ну я так и подумал.
            а че так долго разрабатываете? если это уже не первый заказ, мож подумать о гиковских версиях уже пора?
            Ну типа радиобрелок с дисплеем и кнопками как автосигналка, чтоб на поясе висел, а сам девайс без индикаторов, просто пикать будет что принял с брелка комманду. было бы прикольно )
            а то дисплеи и всякая дрянь на девайсе в общем-то чаще портят дизайн.

          1. Вот за это и убивать. Вам не хочется, а пользователю страдать. ладно если сами мудохаетесь, так нет, вы и другим навязываете.
            Есть же нормальные решения.

            1. Представь себе электронную сигарету. Представил? А теперь представь к ней еще и пульт управления на TSOP. Угу. На одной кнопке тоже можно удобно сделать. Если ввести логичную систему навигации, равно как и на двух десятках сделать наиуебищный интерфейс.

    1. именно!!! операция на глаз через задний проход!
      а не проще обучить пользователя править машинный код? очень гибкая настройка будет?

      рамки разумности должны быть.

      у меня есть простой и логичный обработчик одной кнопки:
      single_short_click (SSC)
      double_short_click (DSC)
      single_long_click (SLC)
      больше режимов — излишества.

      и ненадо учить никакой морзянки!
      меню организовать? легко!

      часы электронные двукнопочные все помнят? тут не сложнее

      SSC — листаем меню, изменяем параметр.
      DSC — возврат в предыдущее меню, выход из изменения параметра без сохранения данных.
      SLC — вход в меню, вход в изменение параметра, выход из изменения параметра с сохранением данных.

      можно добавить сервисное меню: включение питания при нажатой кнопке.

  2. Спасибо! Полезная статья! Хотя в своих поделках я всегда ограничивался длинным/коротким нажатием и отпусканием в нужный момент, например по цвету светодиода. Но вот управлять меню я не осиливаю, даже если «на бумаге» получается трижды распиздатый и логичный алгоритм. Как пример могу привести «Термостат, меньше не бывает» от ARV, штуку он сделал полезную, но муторную в плане управления, когда лезешь в дебри. Основное управление там попроще. Т.е. все-таки должна быть какая-то разумность и достаточность что-ли, у этого однокнопочного решения.

  3. Согласен — разумность нужна во всем
    Статья очень понравилась
    недавно делал один проект где кнопок 4
    и не потому что трудно было поставить пятую просто платы готовые на ебэе были именно с 4
    пока жду платы решил написать прошывку — сделал макет и как мне не хватало 5 кнопки для комфортного управления
    вот и пригодиться такое описанное в статье мне очень
    буду исправлять -платы то еще не пришли
    Спасибо Di Halt

  4. Можно ведь еще использовать Очень длинное нажатие. Например после того, как прошло время длинного нажатия, зажигать какую-нибудь, например, точку в индикаторе, и если отпустить кнопку после зажигания точки, то это будет очень длинное нажатие.
    Например, как это сделали в Эппле:
    1 короткое — плей/пауза, 2,3 коротких — треки вперед-назад, 1 длинное — послушать название, 1 Очень длинное (после короткого бипа) — поменять плейлист.

  5. А у меня свой алгоритм:
    Опрос состояния кнопки происходит в периодически происходящем прерывании. Где происходит установка флага действия action и «режима кнопки» mode при выполнении определенных условий. Основная программа, обнаружив установленный флаг действия action от кнопки, в зависимости от собственного режима работы и «режима кнопки» mode выполняет действия реакции (перелистывание строк меню, изменение какой-либо переменной и т.д., либо ничего не делает), после чего СБРАСЫВАЕТ флаг действия action (даже если нет никакой реакции в основной программе на определенный «режим кнопки»).

    «режим кнопки» mode =
    1. прошел антижребезг нажатия
    2. прошла пауза перед автоповтором или идет автоповтор нажатия
    3. отпустили после «короткого» нажатия (во время паузы перед автоповтором)
    4. отпустили после «длинного» нажатия (после режима 2)
    5. идет автоповтор отпущенного состояния (удобен, например, для автоматического выхода вверх по меню.)

  6. Статья хорошая,
    Зачем одна кнопка? да мало ли….,вопрос не в этом даже ,а в том,что это возможно,когда нет надобности усложнять в простом устройство пихать сложную индикацию и клавиатуру,когда достаточно всего одной кнопки управления. мне лично это удобно было в своё время,наделал массу устройств на Attiny13 где всего 8 ног,и тоже с управлением одной кнопкой,
    и эта проблема актуальна. Важнее другое, тут звучали мнения,что это не удобно,трудно привыкать,
    несколько устройств повторяли люди,специально спрашивал,насколько удобно? кто раз..два попробовал, говорят что очень быстро привыкаешь и потом дискомфорта от этого нет,
    вот кстати ,как пример :
    одно из подобных моих устройств,кодовый замок,с управлением одной кнопкой а индикация (обратная связь на действия от кнопки — зуммер) на слух,что удобно даже при отсутствии хорошего освещения
    http://radioded.ru/shema/kodovyy-zamok-s-odnoy-knopkoy

  7. Если так критичны габариты, то лучше вместо обычной кнопки поставить джойстик от мобильника. Получим 4 или 5 кнопок в корпусе одной обычной тактовой кнопки и спасем мозг пользователя от затраха с настукиванием команд «азбукой морзе»

      1. Да не нужно никакого своеобразия. Джойстик нужно взять не такой, который широкий и плоский, а который как тактовая кнопка, типа как у нокии N73

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

  9. Не смотря на чужую критику, от меня спасибо.
    Сам только завязался с контроллерами, делаю блок управления насосом с устанавливаемыми при помощи кнопок гистерезисами включения и выключения.
    У меня там аж три кнопки но тоже пришлось придумать службу которая различает 1-(нажатие/длинное нажатие/очень длинное нажатие(для увеличения скорости изменения задержки)/ отпускание).
    2- (кнопку/комбинацию кнопок) с которыми происходит «первое»
    Возник вопрос, в какой программе вы рисовали блок схемму?
    (На данный момент пользуюсь openoffice.draw — для блоксхем, algorithmbuilder — для кодинга, proteus — у меня симулянт.)

  10. Дихалт, извини вопрос немного не по теме. А как ты составляешь алгоритмы? Пользуешься блок-диаграммами или теорией графов? Что по твоему удобнее? Можешь посоветовать книги по составлению алгоритмов?

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

    1. Ни тем ни другим. Как то из головы пишется. А теория графов мне вообще не не ведома, ибо теория программирования и связанную с ней математику я нигде не изучал. Т.к. не программист.

      Из матана тоже сложней квадратных уравнений ничего не пригождалось :)))) Если надо смоделировать схему, то SPICE моделирование в помощь. Ну или сколхозить прототип из говна и палок.

Добавить комментарий