В электронных устройствах часто бывают кнопки. Например в Bluetooth колонке JBL Clip 3 пять кнопок. Обычно это кнопки для увеличения и уменьшения громкости, для перехода в режим загрузчика, для остановки звука и прочего.

Обычно кнопок мало, а функционала на кнопки разработчикам хочется добавить много. Поэтому у кнопок иногда бывает два режима: короткое нажатие, долгое нажатие. На каждый режим нажатия прошивка назначает отдельный программный код обработчика.

Очевидно, что программа должна как‑то однозначно распознавать тип нажатия на кнопку. Можно красиво и элегантно решить эту задачу прибегнув к конечно автоматной методологии разработки программного обеспечения.

Прежде всего надо зафиксировать некоторую терминологию:

  1. активный уровень — это то напряжение, которое устанавливается на пине микроконтроллера при нажатии на кнопку. В зависимости от схемотехники это может быть 0В или 3.3В. В той кнопке, что на открытке к посту активный уровень 0В

    Вот в кнопке B1 активный уровень это 0,0 Вольт (Low). Когда нажимают кнопку на проводе PC13 устанавливается 0V, отпускают - 3,3V
    Вот в кнопке B1 активный уровень это 0,0 Вольт (Low). Когда нажимают кнопку на проводе PC13 устанавливается 0V, отпускают - 3,3V
  2. граф — это множество вершин и ребер (палочки и кружочки).

  3. ориентированный граф — граф у которого ребра имеют направление

  4. автомат Мили — это конечный автомат у которого действия происходят в момент переходов

  5. автомат Мура — это конечный автомат у которого действия происходят в состоянии

  6. токен — абстрактная строка символов без пробелов, служащая для компактной записи более длинного текста.

В чем трудность обработки сигналов с тактовых кнопок?

  1. В реальном мире есть такое явление как дребезг контактов. Например если кнопка генерирует внешнее прерывание, то от одного нажатия из‑за дребезга контактов получится 100...200 перепадов от 0В в 3.3В (или наоборот). Программа может подумать, что кнопку нажали 150 раз хотя на самом деле нажали только один раз. Эту проблему решают как программно так и апаратно.

  2. Кнопок мало, а функционала много. Надо как‑то на одну кнопку назначить несколько действий.

В данном примере будем считать, что дребезг устранен на уровне аналоговой электроники или другого программного компонента уровнем ниже. Например, в случае аппаратного решения между кнопкой и микроконтроллером установили аналоговый RC‑фильтр первого порядка. А в случае программного решение пропустили измерения с кнопок через FIR фильтр скользящего среднего.

Вообще проектирование софта методом конечных автоматов это в чистейшем виде механическая работа. Всё кто имеют представление о конечных автоматах сможет синтезировать конечный автомат для решения задачи любой сложности просто аккуратно пройдя вот эти 8 шагов/фаз.

Фаза 1. Определить входы конечного автомата

Есть суперцикл, который измеряет напряжение на пинах GPIO и преобразует напряжение в логический уровень для данной конкретной кнопки в соответствии с конфигами. В свою очередь конфиги составлены исходя из схемотехники. Также есть тикающий таймер (аппаратный или программный), который считает сколько времени данная конкретная кнопка пребывает в нажатом состоянии.

вход для конечного автомата

Токен

1

GPIO прочитало пассивный уровень

ReadLow

2

GPIO прочитало активный уровень уровень

ReadHi

3

Произошло событие переполнения таймера длинного нажатия

TimeOut

Фаза 2. Определить высокоуровневые выходы конечного автомата

На высоком уровне от драйвера кнопок надо только два действия. Для данной конкретной кнопки запустить обработчик короткого нажатия. Для данной конкретной кнопки запустить обработчик долгого нажатия. Стоит заметить один контр интуитивный момент, что при долгом нажатии ни в коем случае не должен вызываться обработчик короткого нажатия.

Фаза 3. Определить низкоуровневые выходы конечного автомата

Пояснение

Токен

1

Обнулить таймер продолжительности нажатия

ResetTimer

2

Включить таймер измерения продолжительности нажатия на кнопку

StartTimer

3

Отключить таймер измерения продолжительности нажатия на кнопку

StopTimer

Фаза 4. Определить состояния конечного автомата

Пояснение

Токен

1

Кнопка не нажата

UnPressed

2

Кнопка нажата

Pressed

3

Кнопка нажата и обработана

Pressed Processed

Фаза 5. Построить таблицу переходов (Transfer Table)

Как известно из дискретной математики любой граф можно представить таблицей переходов. Вот таблица переходов для конечного автомата обработки нажатий кнопок.

Фаза 6. Построить таблицу действий (Action Table)

Так как у нас автомат Мили, то надо определить таблицу действий для конечного автомата обработки нажатий кнопки.

Фаза 7. Нарисовать граф

Любой конечный автомат это в сущности ориентированный граф. Поэтому конечный автомат можно представить в виде блок‑схемы. Граф хорош тем, что он представляет как таблицу переходов так и таблицу выходов. При этом граф проще понять человеку. А таблицы проще понять программе (языку программирования).

Этот граф я нарисовал в программе inkscape. Это бесплатная, свободная кроссплатформенная программа для составления векторной графики и даже чертежей! Inkscape бурно развивается уже много лет и стабильно появляются новые релизы. В inkscape есть слои, цвета, привязки, стили линий, стили концов линий и много чего ещё.

Фактически получился автомат Мили на три состояния. Что ж, с теорией определились. Вот только теперь подошло время накропать программный код.

Фаза 8. Написать программный код на языке программирования Си

У каждого программного компонента есть константы. Надо определить файл button_const.h с константами для кнопки.

#ifndef BUTTON_FSM_CONST_H
#define BUTTON_FSM_CONST_H

#define BUTTON_POLL_PERIOD_US 100000
#define BUTTON_LONG_PRESS_TIMEOUT_MS 3000

typedef enum {
    BUTTON_STATE_UNPRESSED=0,
    BUTTON_STATE_PRESSED=1,
    BUTTON_STATE_PRESSED_PROCESSED=2,

    BUTTON_STATE_UNDEF=3,
} ButtonState_t;


typedef enum {
    BUTTON_IN_PASSIVE=0,
    BUTTON_IN_ACTIVE=1,
    BUTTON_IN_TIME_OUT=2,

    BUTTON_IN_UNDEF=3,
} ButtonInput_t;

typedef enum {
    BUTTON_PRESS_SHORT=1,
    BUTTON_PRESS_LONG=2,

    BUTTON_PRESS_UNDEF=0,
} ButtonPressType_t;


#endif /* BUTTON_FSM_CONST_H  */

Теперь надо определить специфичные для кнопки типы данных

#ifndef BUTTON_FSM_TYPES_H
#define BUTTON_FSM_TYPES_H

#include <stdbool.h>
#include <stdint.h>


#include "button_const.h"
#include "gpio_types.h"

typedef bool (*ButtonIsrHandler_t)(void);

#define BUTTON_COMMON_VARIABLES                \
    uint32_t num;                              \
    uint32_t debug_led_num;                    \
    bool valid;                                \
    Pad_t pad;                                 \
    GpioLogicLevel_t active;                   \
    ButtonIsrHandler_t press_short_handler;    \
    ButtonIsrHandler_t press_long_handler;


#define BUTTON_NAME_SIZE 20
typedef struct {
    BUTTON_COMMON_VARIABLES
    char name[BUTTON_NAME_SIZE];
} ButtonConfig_t;

typedef struct {
    BUTTON_COMMON_VARIABLES
    bool init;
    uint32_t time_ms;
    ButtonState_t state;
    ButtonInput_t input;
    uint32_t short_pres_cnt;
    uint32_t long_pres_cnt;
    uint32_t un_pres_cnt;
    uint32_t err_cnt;
    uint32_t handler_cnt;
} Button_t;

typedef bool (*ButtonActionHandler_t)(Button_t* const Node);

#endif /* BUTTON_FSM_TYPES_H  */

Прежде всего нужен конфиг для драйвера кнопок button_config.c, где надо прописать сколько кнопок на электронной плате, какие у них пины GPIO, какие активные уровни, какие функции обработчики короткого нажатия, какие функции обработчики длинного нажатия, какой отладочный LED у каждой кнопки.

#include "button_config.h"

#ifndef HAS_BUTTON
#error "Add HAS_BUTTON"
#endif /*HAS_BUTTON*/

#include "data_utils.h"
#include "log.h"

static bool button1_short_proc(void) {
    bool res = true;
    LOG_WARNING(BUTTON, "BUTTON1 PressShort");
    return res;
}

static bool button1_long_proc(void) {
    bool res = true;
    LOG_WARNING(BUTTON, "BUTTON1 PressLong");
    return res;
}

const ButtonConfig_t ButtonConfig[] = {
    {
        .num = 1,
        .press_short_handler = button1_short_proc,
        .press_long_handler = button1_long_proc,
        .pad = {.port = PORT_C, .pin = 13},
        .active = GPIO_LVL_LOW,
        .name = "B1",
        .valid = true,
        .debug_led_num = 1,
    },
};

Button_t ButtonItem[] = {
    {
        .num = 1,
        .valid = true,
    },
};

uint32_t button_get_cnt(void) {
    uint32_t cnt = 0;
    uint32_t cnt1 = 0;
    uint32_t cnt2 = 0;
    cnt1 = ARRAY_SIZE(ButtonItem);
    cnt2 = ARRAY_SIZE(ButtonConfig);
    if(cnt1 == cnt2) {
        cnt = cnt1;
    }
    return cnt;
}

Код драйвера интеллектуальной обработки тактовых кнопок

#include "button_drv.h"

#include <stdint.h>

#include "log.h"
#include "led_mono_drv.h"
#include "button_diag.h"
#include "gpio_drv.h"
#include "gpio_general_diag.h"
#include "flash_drv.h"
#include "button_config.h"
#include "sys_config.h"

static bool button_run_callback(Button_t* Node, ButtonPressType_t press_type) {
    bool res = false;
    if(Node) {
        ButtonIsrHandler_t press_handler = NULL;
        switch((uint8_t)press_type) {
        case BUTTON_PRESS_SHORT: {
            LOG_WARNING(BUTTON, "%u ShortPressedProc %s, %u ms", Node->num, GpioPad2Str(Node->pad.byte), Node->time_ms);
            press_handler = Node->press_short_handler;
        } break;

        case BUTTON_PRESS_LONG: {
            LOG_WARNING(BUTTON, "%u LongPressedProc %s, %u ms", Node->num, GpioPad2Str(Node->pad.byte), Node->time_ms);
            press_handler = Node->press_long_handler;
        } break;
        }

        if(press_handler) {
            res = is_flash_addr((uint32_t)press_handler);
            if(res) {
                LOG_INFO(BUTTON, "HandlerInFlash 0x%p", press_handler);
                res = press_handler();
                Node->handler_cnt++;
            } else {
                LOG_ERROR(BUTTON, "HandlerOutOfFlash 0x%p", press_handler);
                Node->err_cnt++;
            }
        } else {
            LOG_ERROR(BUTTON, "NoHandler4 %s", ButtonPressType2Str(press_type));
        }
    }
    return res;
}

static const ButtonState_t TransferLookUpTable[3][3]={
        [BUTTON_STATE_UNPRESSED][BUTTON_IN_PASSIVE]=BUTTON_STATE_UNPRESSED,
        [BUTTON_STATE_UNPRESSED][BUTTON_IN_ACTIVE]=BUTTON_STATE_PRESSED,
        [BUTTON_STATE_UNPRESSED][BUTTON_IN_TIME_OUT]=BUTTON_STATE_UNPRESSED,
        [BUTTON_STATE_PRESSED][BUTTON_IN_PASSIVE]=BUTTON_STATE_UNPRESSED,
        [BUTTON_STATE_PRESSED][BUTTON_IN_ACTIVE]=BUTTON_STATE_PRESSED,
        [BUTTON_STATE_PRESSED][BUTTON_IN_TIME_OUT]=BUTTON_STATE_PRESSED_PROCESSED,
        [BUTTON_STATE_PRESSED_PROCESSED][BUTTON_IN_PASSIVE]=BUTTON_STATE_UNPRESSED,
        [BUTTON_STATE_PRESSED_PROCESSED][BUTTON_IN_ACTIVE]=BUTTON_STATE_PRESSED_PROCESSED,
        [BUTTON_STATE_PRESSED_PROCESSED][BUTTON_IN_TIME_OUT]=BUTTON_STATE_PRESSED_PROCESSED,
};

static bool button_action_nop(Button_t* Node){
    bool res = true;
    return res;
}

static bool button_action_stop_timer(Button_t* Node){
    bool res = false;
    if(Node) {
        LOG_DEBUG(BUTTON, "LongPressEnd %s, %u ms", GpioPad2Str(Node->pad.byte),Node->time_ms);
        Node->time_ms = 0;
        res = true;
    }
    return res;
}

static bool button_action_reset_timer(Button_t* Node){
    bool res = false;
    if (Node) {
        Node->time_ms = 0;
        res = true;
    }
    return res;
}

static bool button_short_press_timer(Button_t* Node){
    bool res = false;
    if(Node){
#ifdef HAS_LED_MONO
        led_mono_blink(Node->debug_led_num, 50);
#endif
        Node->short_pres_cnt++;
        LOG_DEBUG(BUTTON, "ShortPress %s, %u ms", GpioPad2Str(Node->pad.byte),Node->time_ms);
        //res=Node->press_short_handler();
        res = button_run_callback(Node, BUTTON_PRESS_SHORT);
        Node->time_ms = 0;
    }
    return res;
}

static bool button_long_press_timer(Button_t* Node){
    bool res = false;
    if(Node) {
#ifdef HAS_LED_MONO
        led_mono_blink(Node->debug_led_num, 100);
#endif
        Node->long_pres_cnt++;
        LOG_DEBUG(BUTTON, "LongPress %s, %u ms", GpioPad2Str(Node->pad.byte), Node->time_ms);
        res = button_run_callback(Node, BUTTON_PRESS_LONG);
        //res=Node->press_long_handler();
    }
    return res;
}

static const ButtonActionHandler_t ActionLookUpTable[3][3]={
        [BUTTON_STATE_UNPRESSED][BUTTON_IN_PASSIVE]=button_action_nop,
        [BUTTON_STATE_UNPRESSED][BUTTON_IN_ACTIVE]=button_action_reset_timer,
        [BUTTON_STATE_UNPRESSED][BUTTON_IN_TIME_OUT]=button_action_nop,

        [BUTTON_STATE_PRESSED][BUTTON_IN_PASSIVE]=button_short_press_timer,
        [BUTTON_STATE_PRESSED][BUTTON_IN_ACTIVE]=button_action_nop,
        [BUTTON_STATE_PRESSED][BUTTON_IN_TIME_OUT]=button_long_press_timer,

        [BUTTON_STATE_PRESSED_PROCESSED][BUTTON_IN_PASSIVE]=button_action_stop_timer,
        [BUTTON_STATE_PRESSED_PROCESSED][BUTTON_IN_ACTIVE]=button_action_nop,
        [BUTTON_STATE_PRESSED_PROCESSED][BUTTON_IN_TIME_OUT]=button_action_nop,
};


Button_t* ButtonGetNode(uint8_t num) {
    Button_t* Node = NULL;
    uint32_t i = 0;
    uint32_t cnt = button_get_cnt();
    for(i = 0; i < cnt; i++) {
        if(ButtonItem[i].valid) {
            if(num == ButtonItem[i].num) {
                Node = &ButtonItem[i];
                break;
            }
        }
    }
    return Node;
}

const ButtonConfig_t* ButtonGetConfigNode(uint8_t num) {
    const ButtonConfig_t* Node = NULL;
    uint32_t i = 0;
    uint32_t cnt = button_get_cnt();
    for(i = 0; i < cnt; i++) {
        if(ButtonConfig[i].valid) {
            if(num == ButtonConfig[i].num) {
                Node = &ButtonConfig[i];
                break;
            }
        }
    }
    return Node;
}


static bool button_get_input_ll( Button_t* const Node ){
    bool res = false;
    GpioLogicLevel_t logic_level = GPIO_LVL_UNDEF;
    res = gpio_get_state(Node->pad.byte, &logic_level);
    if(res) {
        LOG_DEBUG(BUTTON, "Read %s %s", GpioPad2Str(Node->pad.byte), GpioLevel2Str(logic_level));
        res = false;
        if(logic_level==Node->active ) {
            LOG_DEBUG(BUTTON, "Pressed %s %s", GpioPad2Str(Node->pad.byte), GpioLevel2Str(logic_level));
            Node->input = BUTTON_IN_ACTIVE;
            res = true;
            Node->time_ms += (uint32_t)USEC_2_MSEC(BUTTON_POLL_PERIOD_US);
            if(BUTTON_LONG_PRESS_TIMEOUT_MS < Node->time_ms) {
                Node ->input = BUTTON_IN_TIME_OUT;
                res = true;
            }
        } else {
            Node ->input = BUTTON_IN_PASSIVE;
            res = true;
        }
    }

    LOG_DEBUG(BUTTON, "Pad:%s Input %s", GpioPad2Str(Node->pad.byte), ButtonInput2Str(  Node->input));
    return res;
}

static bool button_proc_one(uint8_t num) {
    bool res = false;
    Button_t* Node = ButtonGetNode(num);
    if(Node) {
        ButtonState_t new_state = BUTTON_STATE_UNDEF;
        res = button_get_input_ll(Node);
        if(res) {
            new_state = TransferLookUpTable[Node->state][Node->input];
            if(new_state != Node->state) {
                LOG_WARNING(BUTTON, "%u %s %s->%s", Node->num, GpioPad2Str(Node->pad.byte),
                        ButtonState2Str(Node->state), ButtonState2Str(new_state));
            }
            ButtonActionHandler_t ActionHandler=ActionLookUpTable[Node->state][Node->input];
            res=ActionHandler(Node);
            Node->state = new_state;
        }
    }
    return res;
}

bool button_proc(void) {
    uint32_t cnt = button_get_cnt();
    LOG_DEBUG(BUTTON, "ButtonProc Cnt: %u", cnt);
    bool res = true;
    uint16_t i = 0;
    uint16_t ok = 0;
    for(i = 1; i <= cnt; i++) {
        res = button_proc_one(i);
        if(res) {
            ok++;
        }else{
            LOG_ERROR(BUTTON, "%u GetErr", i);
        }
    }
    if(ok){
        res = true;
    }else {
        res = false;
    }
    return res;
}

GpioPullMode_t ButtonActiveToPull(GpioLogicLevel_t active){
    GpioPullMode_t button_pull=GPIO__PULL_UNDEF;
    switch(active){
        case GPIO_LVL_LOW: {
            button_pull= GPIO__PULL_UP;
        }break;

        case GPIO_LVL_HI: {
            button_pull= GPIO__PULL_DOWN;
        }break;
    default: break;
    }

    return button_pull;
}

bool button_init(void) {
    bool res = true;

    uint32_t num = 0;
    uint32_t cnt = button_get_cnt();
    LOG_WARNING(BUTTON, "Init Cnt:%u", cnt);
    for(num = 0; num <= cnt; num++) {
        Button_t* Node = ButtonGetNode(num);
        if(Node) {
            const ButtonConfig_t* Config = ButtonGetConfigNode(num);
            if(Config) {
                ButtonConfigDiag(Config);
                LOG_INFO(BUTTON, "%u Init", num);
                Node->debug_led_num = Config->debug_led_num;
                Node->press_short_handler = Config->press_short_handler;
                Node->press_long_handler = Config->press_long_handler;
                Node->active = Config->active;
                Node->pad.byte = Config->pad.byte;
                Node->short_pres_cnt = 0;
                Node->long_pres_cnt = 0;
                Node->err_cnt = 0;
                Node->state = BUTTON_STATE_UNPRESSED;
                Node->input = BUTTON_IN_UNDEF;
                res = gpio_set_dir(Node->pad.byte, GPIO_DIR_IN);

                GpioPullMode_t button_pull = ButtonActiveToPull(Config->active);
                res = gpio_set_pull(Node->pad.byte, button_pull);
            }
            Node->init = true;
            res = true;
        }
    }
    return res;
}

Еще нужен код для диагностики кнопки. Чтобы мы могли из UART‑CLI просматривать диагностическую информацию о состояниях кнопок.

#include "button_diag.h"

#include <stdio.h>

#include "button_const.h"
#include "log.h"
#include "gpio_general_diag.h"

const char* ButtonState2Str(ButtonState_t state) {
    const char* name = "?";
    switch((uint8_t)state) {
    case BUTTON_STATE_UNPRESSED:
        name = "Relesed";
        break;
    case BUTTON_STATE_PRESSED:
        name = "Pressed";
        break;
    case BUTTON_STATE_PRESSED_PROCESSED:
        name = "PressedProcessed";
        break;
    default:
        name = "err";
        break;
    }
    return name;
}

const char* ButtonPressType2Str(ButtonPressType_t press_type) {
    const char* name = "?";
    switch((uint8_t)press_type) {
    case BUTTON_PRESS_SHORT:
        name = "Short";
        break;
    case BUTTON_PRESS_LONG:
        name = "Long";
        break;
    default:
        name = "err";
        break;
    }
    return name;
}

const char* ButtonInput2Str(ButtonInput_t input) {
    const char* name = "?";
    switch((uint8_t)input) {
    case BUTTON_IN_PASSIVE:
        name = "Passive";
        break;
    case BUTTON_IN_ACTIVE:
        name = "Active";
        break;
    case BUTTON_IN_TIME_OUT:
        name = "TimeOut";
        break;
    default:
        name = "err";
        break;
    }
    return name;
}

bool ButtonConfigDiag(const ButtonConfig_t* const Config){
    bool res = false;
    if(Config) {
        char text[120]="";
        sprintf(text,"%s,LED%u,Active:%s",
                GpioPad2Str(Config->pad.byte),
                Config->debug_led_num,
                GpioLevel2Str(Config->active));
        LOG_WARNING(BUTTON,"%s",text);
        res = true;
    }
    return res;
}

Ну и, конечно же, надо определить скрипт сборки программного компонента кнопок


$(info BUTTON_MK_INC=$(BUTTON_MK_INC))

ifneq ($(BUTTON_MK_INC),Y)
    BUTTON_MK_INC=Y

    mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
    $(info Build  $(mkfile_path) )

    BUTTON_DIR = $(DRIVERS_DIR)/button_fsm
    #@echo $(error BUTTON_DIR=$(BUTTON_DIR))

    INCDIR += -I$(BUTTON_DIR)
    SOURCES_C += $(BUTTON_DIR)/button_drv.c

    BUTTON=Y
    OPT += -DHAS_BUTTON

    ifeq ($(DIAG),Y)
        OPT += -DHAS_BUTTON_DIAG
        SOURCES_C += $(BUTTON_DIR)/button_diag.c
    endif

    ifeq ($(CLI),Y)
        ifeq ($(BUTTON_COMMANDS),Y)
            OPT += -DHAS_BUTTON_COMMANDS
            SOURCES_C += $(BUTTON_DIR)/button_commands.c
        endif
    endif
endif

Фаза 9. Верификация

Диагностика в UART‑CLI показывает, что кнопка про инициализировалась успешно

Итак, проверка в run-time короткого нажатия. Появился лог.

А это длинное нажатие. В обработчике длинного нажатия как раз была функция перезагрузки микроконтроллера.

Всё работает!

К слову, UART‑CLI для кнопочных устройств хороша тем, что тут можно эмитировать нажатие на кнопки и, тем самым, делать всяческие авто тесты прошивки.

Вывод

Как видите в разработке электронике чтобы просто прочитать состояние тактовой кнопки надо написать целую кучу кода и реализовать два конечных автомата. Первый для фильтрации дребезга, а второй для выявления типа нажатия: короткое или долгое.

Также можно заметить, что конечные автоматы это, не иначе как, универсальный шаблон проектирования программного обеспечения, особенно для микроконтроллеров.

Словарь

Акроним

Расшифровка

GPIO

General

RC

Resistor Capacitor

LED

light emitting diode

FIR

finite impulse response

UART

Universal Asynchronous Receiver-Transmitter

CLI

Command-line interface

Links

МКА (машина конечных автоматов) для чайников на примере класса «кнопка» в arduino

Конечный автомат (он же машина состояний) на чистом С

Вот тексты-приметы того как выручил конечный автомат:

Load-Detect для Проверки Качества Пайки

Распознавание Вещественного Числа из Строчки

Принцип Определения Дальности Между UWB Трансиверами (Конечный Автомат Для DS-TWR)

H-мост: Load Detect (или как выявлять вандализм)

Задача про две ёмкости для жидкости

Комментарии (80)


  1. mayorovp
    13.09.2023 04:27
    +4

    Неплохо вышло, но можно код упростить.


    Первое что бросается в глаза — это применение -1 ко всем индексам в таблицах. Мало того что не так красиво выглядит как могло бы, так ещё и опасно: легко забыть этот самый -1 написать и всё, привет крайне странная ошибка.


    Второе — а требуется ли вообще представление конечного автомата в памяти? Почему бы не сделать как-то так:


    static bool button_handle_input_passive(Button_t* Node) {
        switch (Node->state) {
            case BUTTON_STATE_UNPRESSED: return true;
            case BUTTON_STATE_PRESSED:
                Node->state = BUTTON_STATE_UNPRESSED;
                Node->short_pres_cnt++;
                LOG_DEBUG(BUTTON, "ShortPress %s, %u ms", GpioPad2Str(Node->pad.byte),Node->time_ms);
                Node->time_ms = 0;
                return button_run_callback(Node, BUTTON_PRESS_SHORT);
    
            case BUTTON_STATE_PRESSED_PROCESSED:
                Node->state = BUTTON_STATE_UNPRESSED;
                Node->time_ms = 0;
                return true;
        }
        return false;
    }

    При таком подходе каждому входному сигналу назначается своя функция, отдельная функция для действия не нужна, таблицы состояний и действий тоже не нужны. Вроде получается куда компактнее, и автоматная методология никуда не теряется.


    1. belousovsw
      13.09.2023 04:27
      +1

      Почему вы ждете на входе конкретный объект кнопку а в коде у вас какая то обстрактная нода по итогу. Почему нельзя нормальный нэйминг использовать button, input_button, etc.

      Потом смотришь исходники в чужих проектах и ничего не понятно что в коде происходит, приходится по 10 раз сверяться что же именно все таки в подобных переменных node, i, x, y лежит на самом деле.


      1. mayorovp
        13.09.2023 04:27
        +2

        Ну, это претензия к автору, я просто не стал переписывать его код совсем уж полностью.


      1. aabzel Автор
        13.09.2023 04:27
        -1

        Почему вы ждете на входе конкретный объект кнопку а в коде у вас какая то обстрактная нода по итогу. Почему нельзя нормальный нэйминг использовать button, input_button, etc.

        Потом смотришь исходники в чужих проектах и ничего не понятно что в коде происходит, приходится по 10 раз сверяться что же именно все таки в подобных переменных node, i, x, y лежит на самом деле.

        Код надо писать единообразно безобразно.
        https://habr.com/ru/articles/679256/


        1. mayorovp
          13.09.2023 04:27
          +1

          Не надо писать код безобразно!


          По вашей же ссылке: "29–Давайте переменным осмысленные имена".


    1. aabzel Автор
      13.09.2023 04:27
      -1

      Второе — а требуется ли вообще представление конечного автомата в памяти?

      Дык, код функций, он тоже в памяти лежит же.


    1. aabzel Автор
      13.09.2023 04:27

      Согласно стандарту безопасности автомобильного ПО ISO26262 в функциях запрещено делать несколько return(ов).

      https://habr.com/ru/articles/757216/


      1. mayorovp
        13.09.2023 04:27
        +2

        Ещё бы обоснование было какое-то у этого правила — было бы вовсе замечательно.


        Кстати, обратите внимание на первый комментарий по другой вашей же ссылке: https://habr.com/ru/articles/679256/#comment_24567550


        1. aabzel Автор
          13.09.2023 04:27

          Ещё бы обоснование было какое-то у этого правила — было бы вовсе замечательно.


          А обоснование в стандарте ISO26262 тоже присутствует. Код должен быть ремонтопригодным.

          Когда в каждой функии только один return, то всегда можно приписать в конце функции ремонтный код и этот код точно будет выполнен.


          1. iig
            13.09.2023 04:27
            +1

            ремонтный код

            WTF ремонтный код? Может, имеется в виду дебажный printf? ;)


            1. aabzel Автор
              13.09.2023 04:27

              WTF ремонтный код? Может, имеется в виду дебажный printf? ;)


              1. iig
                13.09.2023 04:27
                +1

                Тогда хотелось бы увидеть пример ремонтного кода с пояснением что он должен ремонтировать.


                1. aabzel Автор
                  13.09.2023 04:27
                  -1

                  Это уже зависит от обстоятельств.


                  1. iig
                    13.09.2023 04:27
                    +2

                    Неубедительно как то ;) Какие могут быть обстоятельства для ремонтного кода? Что-то сломалось и его ремонтируют? ;)


                    1. Ndochp
                      13.09.2023 04:27
                      +1

                      Да, значение результата на 15 отличается от нужного, но всегда. Мы вместо погружения в дебри перед единственным ретурном пишем -15 и радостно живем дальше.
                      Живые примеры мне встречались, когда это оправдано, но естественно редко. И давно, поэтому не вспомню.


                      1. randomsimplenumber
                        13.09.2023 04:27
                        +2

                        Мы вместо погружения в дебри перед единственным ретурном пишем -15 и радостно живем дальше.

                        Какой ужас :( Кровью написано, значит.. Забили костыль, и теперь то самолет точно не упалет.


                      1. iig
                        13.09.2023 04:27
                        +1

                        НЯП early return используют для аварийного завершения функции и возвращают ошибку. Это пожалуй самое разумное использование нескольких точек выхода. В этом случае тоже ничто не мешает умножать правильный результат на -15, а код ошибки не умножать.
                        Хотя если мы провели тестирование, и увидели, кто результат нужно корректировать всегда — что нам мешает исправить алгоритм? Может у нас наоборот, тесты неправильно работают, или не все значения покрывают?


                      1. Ndochp
                        13.09.2023 04:27
                        +1

                        Ну как бы "выбросить исключение" и "ранний возврат" это разные вещи.
                        В моей практике в трети где-то случаев ранний возврат — это просто движение по наиболее вероятному сценарию. Типа ответ есть в объекте — возвращаем. Потом можно поискать в связанном: есть — возвращаем.
                        На крайний случай — хитрый длинный алгоритм с глубоким поиском. Нашли — вернули. Не нашли — вернули пустышку или ошибку.


                      1. iig
                        13.09.2023 04:27
                        +2

                        движение по наиболее вероятному сценарию

                        Тоже хорошо. В любом случае это делает код чище и понятнее. А вот требование иметь место для вкрячивания костыля ремонтного кода — странное. Ну, надо значит надо. _разводитруками


          1. mayorovp
            13.09.2023 04:27

            …а теперь посмотрите ещё раз на функцию, и скажите: какой такой ремонтный код вам может понадобиться дописать ко всем веткам сразу?


            А теперь ещё более интересный вопрос: допустим, что есть ситуация, в которой вам и правда потребуется "ремонтный код" после окончания обработки сигнала PASSIVE. Как вы его добавите в ваш исходный конечный автомат, в конец какой функции? А никак, этого места там не предусмотрено. По крайней мере, единого места — так точно.




            Я совсем не против поддерживаемости кода (именно так правильно переводится maintainability). Но правило "одного return" не имеет к этому свойству никакого отношения, это просто тупая бюрократия.


            1. aabzel Автор
              13.09.2023 04:27
              -2

              Но правило "одного return" не имеет к этому свойству никакого отношения, это просто тупая бюрократия.


              Правила ISO26262 написаны кровью.


              1. mayorovp
                13.09.2023 04:27
                +1

                Сомневаюсь.


              1. iig
                13.09.2023 04:27
                +3

                Некоторые правила похожи на ритуалы.


          1. vassabi
            13.09.2023 04:27
            +4

            Когда в каждой функии только один return, то всегда можно приписать в конце функции ремонтный код и этот код точно будет выполнен.

            или переместить код исходной функции ф() в новую функцию ф1(),

            а в ф() вызвать ф1() и после нее - все что вам угодно.

            во-вторых - в том скрине на который вы ссылаетесь, еще пишут "никаких динамических объектов или переменных" - это весьма интересное ограничение!


            1. Ndochp
              13.09.2023 04:27
              -2

              Угу, найти все вызовы Ф и заменить на Ф1, особенно когда это библиотечный код, и не пойми кто с ним уже слинкован в соседнем отделе.


              1. mayorovp
                13.09.2023 04:27
                +2

                Прочитайте что именно вам предлагают ещё раз, это предложение не требует искать все вызовы старой функции и заменять их.


                1. Ndochp
                  13.09.2023 04:27
                  +1

                  Виноват, ошибся.


            1. RTFM13
              13.09.2023 04:27

              "никаких динамических объектов или переменных" - это весьма интересное ограничение!

              Это специфика эмбеда. В магнитоле на андроиде это нереализуемо, а почему это должно быть в блоке управления тормозами автомобиля - вполне понятно.


    1. aabzel Автор
      13.09.2023 04:27

      Первое что бросается в глаза — это применение -1 ко всем индексам в таблицах. Мало того что не так красиво выглядит как могло бы, так ещё и опасно: легко забыть этот самый -1 написать и всё, привет крайне странная ошибка.

      Хорошо. Исправил.


    1. aabzel Автор
      13.09.2023 04:27

      Второе — а требуется ли вообще представление конечного автомата в памяти? Почему бы не сделать как-то так:

      Будет больше строк кода по сравнению с LookUp таблицами. Да и с LUT быстрее будет работать.

      Тем более что LUT(ы) они тоже в Flash памяти микроконтроллера так как там прописано const.


      1. mayorovp
        13.09.2023 04:27
        +1

        Будет больше строк кода по сравнению с LookUp таблицами. Да и с LUT быстрее будет работать.

        Первое — возможно, но только при большом количестве одинаковых действий в ответ на разные входные сигналы.


        А вот насчёт "быстрее будет работать" — за счёт чего? Конструкция switch-то за кадром в такой же LUT компилируется.


  1. mayorovp
    13.09.2023 04:27

    Третье упрощение применимо к функции button_run_callback. Она вызывается ровно в двух местах кода, и в каждом из них совершенно точно известно какая именно функция вызывается.


    Так почему бы не передать аргументом сразу эту функцию?


    static bool button_run_callback(Button_t* Node, ButtonIsrHandler_t press_handler) {
        bool res;
    
        if(!press_handler) {
            LOG_ERROR(BUTTON, "NoHandler4 %s", ButtonPressType2Str(press_type));
            return false;
        }
    
        if (!is_flash_addr((uint32_t)press_handler)) {
            LOG_ERROR(BUTTON, "HandlerOutOfFlash 0x%p", press_handler);
            Node->err_cnt++;
            return false;
        }
    
        LOG_INFO(BUTTON, "HandlerInOfFlash 0x%p", press_handler);
        Node->handler_cnt++;
        return press_handler();
    }


  1. AndreyDmitriev
    13.09.2023 04:27
    +2

    Вот здесь можно подсмотреть пару неплохих идей:

    State Machine Design in C


  1. kotan-11
    13.09.2023 04:27
    +1

    Можно проще:

    constexpr uint8_t DEBOUNCE_TICKS = 40;
    constexpr uint8_t LONG_PRESS = 200;  
    
    struct Button {
        uint8_t port;
        uint8_t mask;
        uint8_t cool_down = 0;
        uint8_t pressed_for = 0;
    
        void (*on_press)(bool is_long);
    
        void tick() {
            if (cool_down > 0) {
                --cool_down;
                return;
            }
            if ((port & mask) != 0) { // not pressed
                if (pressed_for == 0) return;  // and was not pressed 
                on_press(pressed_for >= LONG_PRESS);
                pressed_for = 0;
            } else if (pressed_for < LONG_PRESS) {
                pressed_for++;
            }
            if (pressed_for < 2)
                cool_down = DEBOUNCE_TICKS;
        }
    };


    1. Albert2009Zi
      13.09.2023 04:27
      +1

      Ну и "дебаунс"(дребезг) компенсировать аппаратно, пассивным RC фильтром :)


      1. iig
        13.09.2023 04:27
        +1

        RC фильтр денег стоит. А 10 строк кода — почти бесплатно ;)


        1. Albert2009Zi
          13.09.2023 04:27
          -1

          Видимо в Самсунгах, Сименсах и иже полные дураки сидят, что ставят на кнопки RC фильтры. Или?
          зы даже у автора в иллюстрации к статье RC фильтр на кнопке присутствует...


          1. iig
            13.09.2023 04:27
            +1

            Сильное утверждение требует сильных доказательств ;)

            Вот вам кусок схемы телевизора Samsung.

            Кнопки без конденсаторов ;)
            image


            1. Albert2009Zi
              13.09.2023 04:27

              Чем Вам схема платы Nucleo (иллюстрация к статье) и её кнопки Blue не доказательство? Может Nucleo выходит в меньших количествах, чем телевизоры Самсунг, но это тоже массовая продукция. И вот прямо вся продукция Самсунг идёт без rc фильтров?

              зы и это вроде пульт ду? Там да, кстати, ничего кроме матрицы для мембран(или как их там), микросхемы управления и фотоэлемента(тоже точно не знаю, что там за ик излучатель) и батарейки нет.


              1. iig
                13.09.2023 04:27
                +2

                Чем Вам схема платы Nucleo (иллюстрация к статье) и её кнопки Blue не доказательство?

                Доказательство чего? ;) Так то с демо-платой будут играться разные люди, в том числе и непряморукие. А в процессоре телевизора, очевидно, уже есть антидребезг.


                И вот прямо вся продукция Самсунг идёт без rc фильтров?

                 - Настоящий шотландец всегда ставит RC-фильтр!
                 - Вот шотландец без фильтра
                 - Это ненастоящий шотландец!

                :D


                зы и это вроде пульт ду?

                COLOR TELEVISION RECEIVER
                Chassis: KS1A(P)_Rev.1


                Здоровенная простыня. Я просто отрезал кусок с кнопками ;)


                1. Albert2009Zi
                  13.09.2023 04:27

                  Вообще ничего не понял, кроме того, что не угадал с типом устройства. Давайте последовательно:

                  • Вы мне пишете: "Сильное утверждение требует сильных доказательств ;)" Пункт 1. Я отвечаю, что Nucleo прекрасный пример использования RC фильтров для антидребезга в массовой продукции.

                  • Следом от Вас идет: Доказательство чего? ;) Мой ответ может быть только - смотри Пункт 1.

                  А телевизорами Самсунг пользуются исключительно пряморукие люди?
                  Можете не отвечать. Согласен, Вы Д'Артаньян и Самсунг (на основании одного куска схемы и Вашей шуточки) использует исключительно программный дебаунс.


                  1. BARSRAB
                    13.09.2023 04:27
                    -1

                    Nucleo прекрасный пример использования RC фильтров для антидребезга в массовой продукции.

                    Это не массовая продукция, а отладочная плата. Не путайте.

                    использует исключительно программный дебаунс.

                    А какой смысл в аппаратном то, кроме удорожания продукции?


                  1. iig
                    13.09.2023 04:27
                    +3

                    Пролистайте немного вверх что ли..


                    Я утверждал только это. 10 строчек кода имеют цену около 0, особенно если они растиражированы на 100500 устройств. RC цепочка добавляет копеечку в каждое устройство. А почему вы решили, что абсолютно все должны всё делать именно как в Nucleo, или именно как в телевизоре, а кто делает иначе тот неправ — я правда не знаю ;)


                    1. Albert2009Zi
                      13.09.2023 04:27

                      Эээ, я где-то писал, что цитирую: "А почему вы решили, что абсолютно все должны всё делать именно как в Nucleo..."? Вот прямо дословно, что я так решил?Не привел пример в качестве ответа на вопрос в дискуссии, а вот прямо решил? Вы фантазируете.

                      Ей богу, троллинг какой-то. Пусть будет по вашему. Хорошего дня.


            1. BARSRAB
              13.09.2023 04:27
              +3

              Так тут кнопки через АЦП работают, потому там и нет RC цепей, весь дребезг компенсируется скоростью оцифровки. Но вообще да, RC цепочки нечасто встречаются в технике (я в свои устройства так вообще никогда их не ставлю на кнопки), дребезг без проблем компенсируется программно.


            1. Max_Krasnov
              13.09.2023 04:27
              +3

              А вас не смущает что в этой схеме кнопки заводятся не на GPIO, а на ADC! Тут нет большой нужды фильтровать дребезг аппаратно.


              1. iig
                13.09.2023 04:27
                +1

                Тут нет большой нужды фильтровать дребезг аппаратно.

                Обоснуете? ADC он достаточно быстрый и способен уловить дребезг.


                1. Albert2009Zi
                  13.09.2023 04:27
                  +1

                  Могу ошибаться, но может делать единичные замеры с интервалом "нечувствительным" к дребезгу и уже их анализировать? А так да, даже на старинных МК АЦП однозначно словит дребезг. Повторюсь чисто мои умозаключения, могу быть "тыщу" раз не прав.


                  1. iig
                    13.09.2023 04:27
                    +1

                    делать единичные замеры с интервалом "нечувствительным" к дребезгу и уже их анализировать?

                    Так и получился софтовый подавитель дребезга ;)


                    1. Albert2009Zi
                      13.09.2023 04:27
                      -1

                      Полностью согласен. Выход АЦП - информация. Можно наверное, как-то извратиться и сработать с каждым из компараторов (битов) АЦП аппаратно, в зависимости от его номера. Но это какое-то лечение глаза через попу будет. Я, опять же, могу ошибаться, но работаешь с ацп - работаешь с информацией на выходе, т.е. только софтово.


                      1. BARSRAB
                        13.09.2023 04:27
                        +1

                        Можно наверное, как-то извратиться и сработать с каждым из компараторов (битов) АЦП аппаратно, в зависимости от его номера.

                        Учитывая то, что на схеме представлен явно заказной МК, то в него вполне могли добавить программируемые компараторы на АЦП. Хотя особого смысла в этом и нет.


                  1. BARSRAB
                    13.09.2023 04:27
                    +1

                    даже на старинных МК АЦП однозначно словит дребезг.

                    Усреднение показаний в помощь.


                    1. Albert2009Zi
                      13.09.2023 04:27
                      +1

                      Не вопрос. Опять десятый раз оговорюсь, могу ошибаться. Но из даташита более менее знакомого мне PIC12F675, 20 летней давности МК с 64 БАЙТАМИ оперативки и 1 килословом флэша. 44 страница таблица 7-1 самый продолжительный такт АЦП 51.2 мкс (при частоте проца 1.25МГц, очень экзотично, обычно их тактуют на 4МГц), для одного измерения надо 11 тактов, итого 563.2 мкс. Что сильно ниже указанного Вами интервала "нечуствительности" в 50-100мс.
                      Словит он дребезг? Словит.
                      Я не разбираюсь сильно, но если заблуждаюсь, то можно разобрать мои заблуждения на данном примере.


                      1. BARSRAB
                        13.09.2023 04:27
                        +1

                        А кто мешает проводить измерение раз в 10 мс, к примеру, и делать усреднение по 8 измерениям?


                      1. Albert2009Zi
                        13.09.2023 04:27
                        +2

                        Сорри, не понял Вашего комментария выше. С однократными измерениями в нужном интервале и статистической обработкой полностью согласен. Даже выше писал об этом, может не очень понятно изъяснился.


                      1. iig
                        13.09.2023 04:27
                        +2

                        Получится программная реализация RC цепочки ;)


                      1. Albert2009Zi
                        13.09.2023 04:27
                        +1

                        Да, уважаемый. Идите, я Вас обниму уже :)))))


          1. aabzel Автор
            13.09.2023 04:27

            Видимо в Самсунгах,....и иже полные дураки сидят

            очевидно что да



            https://dzen.ru/a/XR9vcHAIDgCu4K9H


            1. Albert2009Zi
              13.09.2023 04:27
              +1

              Хмм, как применение и эксплуатация li-ионных батарей кореллирует с темой Вашей статьи? Только не через мой комментарий, пожалуйста.

              Зы А вообще и Тесла горит и взрывается, и Шаттлы с экипажами падают и даже "Луна 25" от самых лучших в мире специалистов, которые никогда не ошибаются, в отличии от всяких там Самсунгов, тоже вот что-то не заладилось :)))))


              1. aabzel Автор
                13.09.2023 04:27
                -1

                samsung привык пользоваться схемой разработки через outsource.
                Поэтому у них и случаются такие пафосные фейлы.

                https://habr.com/ru/articles/720464/


            1. BARSRAB
              13.09.2023 04:27
              +1

              Т.е. у компании, которая на рынке не один десяток лет и выпускает тысячи единиц продукции ежедневно, ошибка в одном устройстве позволяет сделать вывод о полной некомпетентности разработчиков? Серьезно?


              1. aabzel Автор
                13.09.2023 04:27
                -1

                Вот как раз в таких компаниях и возникает мнимая уверенность в абсолютной правоте, которая приводит к фейлам.


                1. BARSRAB
                  13.09.2023 04:27
                  +3

                  В любой компании бывают ошибки. Вспомнить тот же отзыв автомобилей.


      1. DimErm
        13.09.2023 04:27
        +1

        В реальной жизни все иначе - stm32 имеет пулап 10ком. Не использовали. Емкость на кнопке есть, но годы спустя кнопки окисляются до состояния такого шума при нажатии, что емкость не разрядят. Поэтому в чистоган надо только кнопку и только ногу с пулапом. И опять же - про прерывание в начале статьи заикнулись а где оно в коде? EXTI определения нет. В этом случае ловим прерывание, снимаем прерывания для ноги и проверяем дальнейшие состояния опросом.
        Также нужно понимать реакцию кода на событие. Если действие должно мгновенно получить отклик - пикнуть и включить нечто при нажатии, например - то потребуется чуть более хитрая логика обработки событий.
        А для устранения дебонса самым правильным были кнопки с переключением на два контакта.
        Лучшее, что мне удалось в коде на EXTI собрать - емкостные кнопки - они вообще не дребезжат, паяются на плате и тач контакт с обратной стороны работают, а чип стоик как механическая кнопка


  1. kiltum
    13.09.2023 04:27

    Выглядит, как оверинжиниринг. В реальных проектах делается гораздо проще. Вот пример псевдокода для отдельного потока. Просто, не требует ресурсов, легко поддерживается и легко читается.

    1. Сигнал на GPIO есть? если нет, то иди на 1

    2. пауза в Н миллисекунд

    3. сигнал на GPIO есть? если нет, то иди на 1

    4. кнопка_нажали_коротко = да

    5. пауза в М миллисекунд

    6. кнопка_нажали_коротко=нет

    7. сигнал на GPIO есть? если нет, то иди на 1

    8. копка_нажали_длинно=да

    9. сигнал на GPIO есть? если да, то иди на 9

    10. кнопка_нажали_длинно=нет

    11. иди на 1.


    1. aabzel Автор
      13.09.2023 04:27

      Вот пример псевдокода для отдельного потока.

      А что если это NoRTOS сборка?


    1. aabzel Автор
      13.09.2023 04:27

      Ваш алгоритм при длинном нажатии зарегистрирует и короткое нажатие, и длинное.

      Со стороны пользователя получится так.
      Нажал, подержал 10 сек, отпустил. А в прошивке отработало два обработчика: на короткое и на длинное.

      Выполнились два несовместимых действия и приложение упало.



      1. BARSRAB
        13.09.2023 04:27
        +1

        Никто не мешает перед установкой флага короткого нажатия делать еще одну проверку состояния кнопки. Если все еще нажата, то флаг короткого нажатия не ставится. Т.е. по сути, короткое нажатие будет срабатывать п отпусканию кнопки.


  1. AlanDrakes
    13.09.2023 04:27
    +4

    ИМХО. Конечнный автомат, это, конечно круто, но.. ЗАЧЕМ так сложно?
    Берём массив кнопок. Каждый квант времени (в случае моих устройств - каждую милисекунду, сразу после обработчика SysTick, возвращаемся в main() и читаем состояния кнопок (либо внутри самого SysTick'а) проверяем состояние пинов.

    Если надата - увеличиваем счётчик. Если отпущена - сбрасываем (ееееее, антидребезг!). Если при чтении нуля с пина в соответствующей ячейке массива значение больше порогового для кнопки - в другой массив кладём "1". Если же досчитали до длительного нажатия - "2". Если превышен порог залипания - "3" или "0" - в зависимости от желания. Всё. Особого конечного автомата нет, а код видит нажатие кнопки.


    1. BARSRAB
      13.09.2023 04:27
      +1

      А если читать состояние раз в 50-100 мс, то о дребезге можно вообще забыть...


      1. AlanDrakes
        13.09.2023 04:27
        +1

        Можно. А считая ещё реже можно пропустить нажатие.


        1. BARSRAB
          13.09.2023 04:27
          +1

          Как показывает практика, опроса кнопок раз в 100 мс достаточно для нормального опроса кнопок без пропусков. Вряд ли у кого-то получится отпустить кнопку быстрее.


          1. iig
            13.09.2023 04:27
            +1

            А если не удалось опросить кнопку как раз в момент дребезга? Со счетчиками надежнее как то.


            1. BARSRAB
              13.09.2023 04:27
              +1

              На практике такого не бывало никогда. Кнопка по любому будет удерживаться более 100 мс..


              1. iig
                13.09.2023 04:27
                +1

                Но чтобы определить что кнопка удерживается более 100 мс — все равно нужен счетчик же?


                1. BARSRAB
                  13.09.2023 04:27
                  +1

                  Тут нужен, но к подавлению дребезга он отношения не имеет же.


    1. aabzel Автор
      13.09.2023 04:27
      +1

      Ваш алгоритм при длинном нажатии зарегистрирует еще и короткое.
      Получится нажал, подержал 10 сек, отпустил и сработало два обработчика.


      1. AlanDrakes
        13.09.2023 04:27
        +1

        Если в массиве состояния уже "2", то "1" туда складывать нет смысла.

        А сбросом этого значения будет заниматься основной поток. Так что нет.
        Ну и мелкие проверки "на идиота" никто не отменял, а расписывать их все без псевдокода сильно долго.

        В общем, таким алгоритмом я спокойно пользуюсь в своих проектах и сбоев пока замечено не было. И длительное нажатие (даже слишком длительное, которое ошибка) не считается как "длительное нажатие + короткое".


  1. pavpet
    13.09.2023 04:27
    +1

    В каком редакторе такие красивые схемы переходов нарисовали?


    1. aabzel Автор
      13.09.2023 04:27

      В каком редакторе такие красивые схемы переходов нарисовали?


      Спасибо. Это inkscape.
      https://inkscape.org/



      Бесплатная, свободная кроссплатформенная программа для составления векторной графики и даже чертежей!

      inkscape бурно развивается уже много лет и стабильно появляются новые релизы.

      В inkscape есть слои, цвета, привязки, стили линий, стили концов линий и много чего ещё.

      https://habr.com/ru/articles/683592/