Files
templates/c/keypad/keypad.c
Andrey Kruchinkin 54f0ba7b0e feat(keypad): опрос шести кнопок с антидребезгом и автоповтором
Перенесён из KONOR_ds18b20/lib/keypad; в OpticalTester лежала такая же копия.

О портах и таймерах библиотека не знает: уровни приходят обратным вызовом
read(), время — параметром Keypad_Poll(). События складываются в очередь
на восемь штук, поэтому быстрое нажатие не теряется, пока приложение
перерисовывает экран.
2026-08-23 01:15:11 +03:00

174 lines
5.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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];
}