Перенесён из KONOR_ds18b20/lib/keypad; в OpticalTester лежала такая же копия. О портах и таймерах библиотека не знает: уровни приходят обратным вызовом read(), время — параметром Keypad_Poll(). События складываются в очередь на восемь штук, поэтому быстрое нажатие не теряется, пока приложение перерисовывает экран.
174 lines
5.7 KiB
C
174 lines
5.7 KiB
C
/**
|
||
* @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];
|
||
}
|