feat(keypad): опрос шести кнопок с антидребезгом и автоповтором
Перенесён из KONOR_ds18b20/lib/keypad; в OpticalTester лежала такая же копия. О портах и таймерах библиотека не знает: уровни приходят обратным вызовом read(), время — параметром Keypad_Poll(). События складываются в очередь на восемь штук, поэтому быстрое нажатие не теряется, пока приложение перерисовывает экран.
This commit is contained in:
59
c/keypad/README.md
Normal file
59
c/keypad/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# keypad
|
||||
|
||||
Опрос шести кнопок навигации с антидребезгом, автоповтором и удержанием.
|
||||
|
||||
Библиотека не знает ни о портах, ни о таймерах: уровни кнопок приходят через
|
||||
обратный вызов порта, время — параметром `Keypad_Poll()`. События складываются
|
||||
в короткую очередь, поэтому быстрое нажатие не теряется, пока приложение
|
||||
перерисовывает экран.
|
||||
|
||||
```
|
||||
порт платы keypad.c приложение
|
||||
read(key) ──────► антидребезг, автоповтор ──► Keypad_GetEvent()
|
||||
удержание, очередь на 8
|
||||
```
|
||||
|
||||
## Состав
|
||||
|
||||
| Файл | Что делает | Зависимости |
|
||||
|---|---|---|
|
||||
| `keypad.h`, `keypad.c` | шесть кнопок, антидребезг, автоповтор, длинное нажатие, очередь событий | `stdint.h` |
|
||||
|
||||
Кнопок ровно шесть: четыре направления, ввод и возврат — набор покрывает
|
||||
навигацию по меню без матрицы и сдвиговых регистров.
|
||||
|
||||
## Что нужно от платформы
|
||||
|
||||
Один вызов и монотонное время в миллисекундах:
|
||||
|
||||
```c
|
||||
uint8_t read(void *ctx, uint8_t key); /* 1 — кнопка нажата */
|
||||
```
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```c
|
||||
Keypad keypad;
|
||||
Keypad_Config config = { .read = board_key_read, .context = &board };
|
||||
/* нулевые поля заменяются умолчаниями: дребезг 20 мс, автоповтор 400/120 мс,
|
||||
удержание 800 мс, автоповтор только для стрелок */
|
||||
|
||||
Keypad_Init(&keypad, &config);
|
||||
|
||||
for (;;) {
|
||||
Keypad_Poll(&keypad, board_millis());
|
||||
|
||||
Keypad_Event event;
|
||||
while (Keypad_GetEvent(&keypad, &event)) {
|
||||
if (event.type == KEYPAD_EVENT_PRESS) { /* ... */ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Опрашивать `Keypad_Poll()` достаточно раз в 1..5 мс — из основного цикла
|
||||
или из системного тика.
|
||||
|
||||
## Проверено в проектах
|
||||
|
||||
`KONOR_ds18b20`, `OpticalTester`. Естественная пара — [`menu`](../menu):
|
||||
`Keypad_Key` отображается в `Menu_Key` один в один.
|
||||
173
c/keypad/keypad.c
Normal file
173
c/keypad/keypad.c
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* @file keypad.c
|
||||
* @brief Антидребезг, автоповтор и очередь событий шести кнопок.
|
||||
*
|
||||
* Опрос программный: уровни читаются обратным вызовом порта, состояние
|
||||
* подтверждается выдержкой debounce_ms, а автоповтор и удержание отсчитываются
|
||||
* от момента подтверждённого нажатия. Прерывания не используются, поэтому
|
||||
* библиотека одинаково работает с выводами МК, расширителями и матрицами.
|
||||
*/
|
||||
|
||||
#include "keypad.h"
|
||||
|
||||
/** Значения по умолчанию для незаданных параметров. */
|
||||
#define KEYPAD_DEFAULT_DEBOUNCE_MS 20U
|
||||
#define KEYPAD_DEFAULT_REPEAT_DELAY_MS 400U
|
||||
#define KEYPAD_DEFAULT_REPEAT_PERIOD_MS 120U
|
||||
#define KEYPAD_DEFAULT_LONG_PRESS_MS 800U
|
||||
|
||||
/**
|
||||
* @brief Кладёт событие в очередь, отбрасывая его при переполнении.
|
||||
*
|
||||
* @param keypad Состояние клавиатуры.
|
||||
* @param key Кнопка.
|
||||
* @param type Вид события.
|
||||
*/
|
||||
static void keypad_push(Keypad *keypad, uint8_t key, Keypad_EventType type)
|
||||
{
|
||||
uint8_t tail;
|
||||
|
||||
if (keypad->count >= (uint8_t)KEYPAD_QUEUE_SIZE) {
|
||||
keypad->overflow = 1U;
|
||||
return;
|
||||
}
|
||||
tail = (uint8_t)((keypad->head + keypad->count) % (uint8_t)KEYPAD_QUEUE_SIZE);
|
||||
keypad->queue[tail].key = (Keypad_Key)key;
|
||||
keypad->queue[tail].type = type;
|
||||
keypad->count++;
|
||||
}
|
||||
|
||||
uint8_t Keypad_Init(Keypad *keypad, const Keypad_Config *config)
|
||||
{
|
||||
uint8_t index;
|
||||
|
||||
if ((keypad == 0) || (config == 0) || (config->read == 0)) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
keypad->config = *config;
|
||||
if (keypad->config.debounce_ms == 0U) {
|
||||
keypad->config.debounce_ms = KEYPAD_DEFAULT_DEBOUNCE_MS;
|
||||
}
|
||||
if (keypad->config.repeat_delay_ms == 0U) {
|
||||
keypad->config.repeat_delay_ms = KEYPAD_DEFAULT_REPEAT_DELAY_MS;
|
||||
}
|
||||
if (keypad->config.repeat_period_ms == 0U) {
|
||||
keypad->config.repeat_period_ms = KEYPAD_DEFAULT_REPEAT_PERIOD_MS;
|
||||
}
|
||||
if (keypad->config.long_press_ms == 0U) {
|
||||
keypad->config.long_press_ms = KEYPAD_DEFAULT_LONG_PRESS_MS;
|
||||
}
|
||||
if (keypad->config.repeat_mask == 0U) {
|
||||
keypad->config.repeat_mask = KEYPAD_MASK_ARROWS;
|
||||
}
|
||||
|
||||
for (index = 0U; index < (uint8_t)KEYPAD_KEY_COUNT; index++) {
|
||||
keypad->keys[index].raw = 0U;
|
||||
keypad->keys[index].stable = 0U;
|
||||
keypad->keys[index].long_sent = 0U;
|
||||
keypad->keys[index].changed_ms = 0U;
|
||||
keypad->keys[index].pressed_ms = 0U;
|
||||
keypad->keys[index].repeated_ms = 0U;
|
||||
}
|
||||
keypad->head = 0U;
|
||||
keypad->count = 0U;
|
||||
keypad->overflow = 0U;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
void Keypad_Poll(Keypad *keypad, uint32_t now_ms)
|
||||
{
|
||||
uint8_t index;
|
||||
|
||||
if ((keypad == 0) || (keypad->config.read == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (index = 0U; index < (uint8_t)KEYPAD_KEY_COUNT; index++) {
|
||||
Keypad_KeyState *state = &keypad->keys[index];
|
||||
const uint8_t level = (uint8_t)((keypad->config.read(keypad->config.context, index) != 0U)
|
||||
? 1U : 0U);
|
||||
|
||||
if (level != state->raw) {
|
||||
/* Уровень изменился: отсчёт выдержки начинается заново. */
|
||||
state->raw = level;
|
||||
state->changed_ms = now_ms;
|
||||
continue;
|
||||
}
|
||||
if (level == state->stable) {
|
||||
/* Подтверждённое нажатие: автоповтор и порог удержания. */
|
||||
if (state->stable == 0U) {
|
||||
continue;
|
||||
}
|
||||
if ((state->long_sent == 0U)
|
||||
&& ((now_ms - state->pressed_ms) >= keypad->config.long_press_ms)) {
|
||||
state->long_sent = 1U;
|
||||
keypad_push(keypad, index, KEYPAD_EVENT_LONG);
|
||||
}
|
||||
if ((((keypad->config.repeat_mask >> index) & 1U) != 0U)
|
||||
&& ((now_ms - state->pressed_ms) >= keypad->config.repeat_delay_ms)
|
||||
&& ((now_ms - state->repeated_ms) >= keypad->config.repeat_period_ms)) {
|
||||
state->repeated_ms = now_ms;
|
||||
keypad_push(keypad, index, KEYPAD_EVENT_REPEAT);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ((now_ms - state->changed_ms) < keypad->config.debounce_ms) {
|
||||
continue;
|
||||
}
|
||||
|
||||
state->stable = level;
|
||||
if (level != 0U) {
|
||||
state->pressed_ms = now_ms;
|
||||
state->repeated_ms = now_ms;
|
||||
state->long_sent = 0U;
|
||||
keypad_push(keypad, index, KEYPAD_EVENT_PRESS);
|
||||
} else {
|
||||
keypad_push(keypad, index, KEYPAD_EVENT_RELEASE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Keypad_GetEvent(Keypad *keypad, Keypad_Event *event)
|
||||
{
|
||||
if ((keypad == 0) || (event == 0) || (keypad->count == 0U)) {
|
||||
return 0U;
|
||||
}
|
||||
*event = keypad->queue[keypad->head];
|
||||
keypad->head = (uint8_t)((keypad->head + 1U) % (uint8_t)KEYPAD_QUEUE_SIZE);
|
||||
keypad->count--;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
uint8_t Keypad_IsDown(const Keypad *keypad, Keypad_Key key)
|
||||
{
|
||||
if ((keypad == 0) || (key >= KEYPAD_KEY_COUNT)) {
|
||||
return 0U;
|
||||
}
|
||||
return keypad->keys[key].stable;
|
||||
}
|
||||
|
||||
uint8_t Keypad_Overflow(Keypad *keypad)
|
||||
{
|
||||
uint8_t flag;
|
||||
|
||||
if (keypad == 0) {
|
||||
return 0U;
|
||||
}
|
||||
flag = keypad->overflow;
|
||||
keypad->overflow = 0U;
|
||||
return flag;
|
||||
}
|
||||
|
||||
const char *Keypad_KeyName(Keypad_Key key)
|
||||
{
|
||||
static const char *const names[KEYPAD_KEY_COUNT] = {
|
||||
"UP", "DOWN", "LEFT", "RIGHT", "ENTER", "BACK"
|
||||
};
|
||||
|
||||
if (key >= KEYPAD_KEY_COUNT) {
|
||||
return "?";
|
||||
}
|
||||
return names[key];
|
||||
}
|
||||
145
c/keypad/keypad.h
Normal file
145
c/keypad/keypad.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @file keypad.h
|
||||
* @brief Портируемый опрос шести кнопок навигации с антидребезгом.
|
||||
*
|
||||
* Библиотека не знает ни о портах, ни о таймерах: уровни кнопок она получает
|
||||
* через обратный вызов порта, а время — параметром Keypad_Poll(). События
|
||||
* складываются в короткую очередь, поэтому быстрое нажатие не теряется, даже
|
||||
* если приложение перерисовывает экран.
|
||||
*
|
||||
* Кнопок ровно шесть: четыре направления, ввод и возврат. Такой набор
|
||||
* покрывает навигацию по меню без матрицы и сдвиговых регистров.
|
||||
*/
|
||||
|
||||
#ifndef KEYPAD_H
|
||||
#define KEYPAD_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** @brief Кнопки клавиатуры; порядок задаёт индексы для обратного вызова. */
|
||||
typedef enum {
|
||||
KEYPAD_KEY_UP = 0, /**< Вверх: предыдущий пункт меню. */
|
||||
KEYPAD_KEY_DOWN, /**< Вниз: следующий пункт меню. */
|
||||
KEYPAD_KEY_LEFT, /**< Влево: уменьшение значения. */
|
||||
KEYPAD_KEY_RIGHT, /**< Вправо: увеличение значения. */
|
||||
KEYPAD_KEY_ENTER, /**< Ввод: вход в пункт или подтверждение. */
|
||||
KEYPAD_KEY_BACK, /**< Назад: возврат на предыдущий экран. */
|
||||
KEYPAD_KEY_COUNT /**< Число кнопок; не является кнопкой. */
|
||||
} Keypad_Key;
|
||||
|
||||
/** @brief Вид события клавиатуры. */
|
||||
typedef enum {
|
||||
KEYPAD_EVENT_PRESS = 0, /**< Нажатие после подтверждения антидребезгом. */
|
||||
KEYPAD_EVENT_REPEAT, /**< Автоповтор при удержании. */
|
||||
KEYPAD_EVENT_LONG, /**< Удержание дольше порога, выдаётся один раз. */
|
||||
KEYPAD_EVENT_RELEASE /**< Отпускание кнопки. */
|
||||
} Keypad_EventType;
|
||||
|
||||
/** @brief Событие очереди клавиатуры. */
|
||||
typedef struct {
|
||||
Keypad_Key key; /**< Кнопка, вызвавшая событие. */
|
||||
Keypad_EventType type; /**< Вид события. */
|
||||
} Keypad_Event;
|
||||
|
||||
/** Глубина очереди событий; хватает на любой темп нажатий вручную. */
|
||||
#define KEYPAD_QUEUE_SIZE 8U
|
||||
|
||||
/** Маска кнопок направлений для поля repeat_mask. */
|
||||
#define KEYPAD_MASK_ARROWS ((uint8_t)((1U << KEYPAD_KEY_UP) | (1U << KEYPAD_KEY_DOWN) \
|
||||
| (1U << KEYPAD_KEY_LEFT) | (1U << KEYPAD_KEY_RIGHT)))
|
||||
|
||||
/** Маска всех кнопок клавиатуры. */
|
||||
#define KEYPAD_MASK_ALL ((uint8_t)((1U << KEYPAD_KEY_COUNT) - 1U))
|
||||
|
||||
/**
|
||||
* @brief Параметры клавиатуры; нулевые поля заменяются значениями по умолчанию.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Читает состояние кнопки: 1 — нажата. Индекс — значение Keypad_Key. */
|
||||
uint8_t (*read)(void *context, uint8_t key);
|
||||
void *context; /**< Указатель платы для обратного вызова. */
|
||||
uint16_t debounce_ms; /**< Время подтверждения уровня, по умолчанию 20. */
|
||||
uint16_t repeat_delay_ms; /**< Пауза до автоповтора, по умолчанию 400. */
|
||||
uint16_t repeat_period_ms;/**< Период автоповтора, по умолчанию 120. */
|
||||
uint16_t long_press_ms; /**< Порог удержания, по умолчанию 800. */
|
||||
uint8_t repeat_mask; /**< Кнопки с автоповтором, по умолчанию стрелки. */
|
||||
} Keypad_Config;
|
||||
|
||||
/** @brief Состояние одной кнопки. */
|
||||
typedef struct {
|
||||
uint8_t raw; /**< Последний прочитанный уровень. */
|
||||
uint8_t stable; /**< Подтверждённое состояние: 1 — нажата. */
|
||||
uint8_t long_sent; /**< Признак выданного события удержания. */
|
||||
uint32_t changed_ms; /**< Момент последнего изменения сырого уровня. */
|
||||
uint32_t pressed_ms; /**< Момент подтверждённого нажатия. */
|
||||
uint32_t repeated_ms; /**< Момент последнего события автоповтора. */
|
||||
} Keypad_KeyState;
|
||||
|
||||
/** @brief Состояние клавиатуры; принадлежит вызывающему коду. */
|
||||
typedef struct {
|
||||
Keypad_Config config; /**< Копия параметров. */
|
||||
Keypad_KeyState keys[KEYPAD_KEY_COUNT]; /**< Состояния кнопок. */
|
||||
Keypad_Event queue[KEYPAD_QUEUE_SIZE]; /**< Кольцевая очередь событий. */
|
||||
uint8_t head; /**< Индекс чтения очереди. */
|
||||
uint8_t count; /**< Число событий в очереди. */
|
||||
uint8_t overflow; /**< Признак потери событий. */
|
||||
} Keypad;
|
||||
|
||||
/**
|
||||
* @brief Готовит клавиатуру к работе и подставляет значения по умолчанию.
|
||||
*
|
||||
* Состояние всех кнопок считается отпущенным, поэтому зажатая при включении
|
||||
* кнопка даёт обычное событие нажатия после выдержки антидребезга.
|
||||
*
|
||||
* @param keypad Состояние клавиатуры.
|
||||
* @param config Параметры; обязателен только обратный вызов чтения.
|
||||
* @return 1 при успешной настройке, 0 если обратный вызов не задан.
|
||||
*/
|
||||
uint8_t Keypad_Init(Keypad *keypad, const Keypad_Config *config);
|
||||
|
||||
/**
|
||||
* @brief Опрашивает кнопки и наполняет очередь событий.
|
||||
*
|
||||
* Вызывается из главного цикла с любым темпом: шаг антидребезга задаётся не
|
||||
* периодом вызова, а метками времени.
|
||||
*
|
||||
* @param keypad Состояние клавиатуры.
|
||||
* @param now_ms Текущее монотонное время в миллисекундах.
|
||||
*/
|
||||
void Keypad_Poll(Keypad *keypad, uint32_t now_ms);
|
||||
|
||||
/**
|
||||
* @brief Забирает следующее событие из очереди.
|
||||
*
|
||||
* @param keypad Состояние клавиатуры.
|
||||
* @param event Приёмник события.
|
||||
* @return 1, если событие получено, 0 если очередь пуста.
|
||||
*/
|
||||
uint8_t Keypad_GetEvent(Keypad *keypad, Keypad_Event *event);
|
||||
|
||||
/**
|
||||
* @brief Сообщает подтверждённое состояние кнопки.
|
||||
*
|
||||
* @param keypad Состояние клавиатуры.
|
||||
* @param key Опрашиваемая кнопка.
|
||||
* @return 1, если кнопка нажата.
|
||||
*/
|
||||
uint8_t Keypad_IsDown(const Keypad *keypad, Keypad_Key key);
|
||||
|
||||
/**
|
||||
* @brief Возвращает и сбрасывает признак переполнения очереди.
|
||||
*
|
||||
* @param keypad Состояние клавиатуры.
|
||||
* @return 1, если события терялись с прошлого вызова.
|
||||
*/
|
||||
uint8_t Keypad_Overflow(Keypad *keypad);
|
||||
|
||||
/**
|
||||
* @brief Даёт короткое имя кнопки для отладочных сообщений и экранов.
|
||||
*
|
||||
* @param key Кнопка.
|
||||
* @return Строка вида "UP"; для неизвестного кода — "?".
|
||||
*/
|
||||
const char *Keypad_KeyName(Keypad_Key key);
|
||||
|
||||
#endif /* KEYPAD_H */
|
||||
Reference in New Issue
Block a user