feat(keypad): опрос шести кнопок с антидребезгом и автоповтором

Перенесён из KONOR_ds18b20/lib/keypad; в OpticalTester лежала такая же копия.

О портах и таймерах библиотека не знает: уровни приходят обратным вызовом
read(), время — параметром Keypad_Poll(). События складываются в очередь
на восемь штук, поэтому быстрое нажатие не теряется, пока приложение
перерисовывает экран.
This commit is contained in:
2026-08-23 01:15:11 +03:00
parent c7798c02db
commit 54f0ba7b0e
3 changed files with 377 additions and 0 deletions

145
c/keypad/keypad.h Normal file
View 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 */