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

173
c/keypad/keypad.c Normal file
View 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];
}