Как стать автором
Обновить

Техникум: Конечный Aвтомат Обработки Сигнала с Кнопки

Уровень сложностиПростой
Время на прочтение13 мин
Количество просмотров5.9K

В электронных устройствах часто бывают кнопки. Например в 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 для кнопочных устройств хороша тем, что тут можно эмитировать нажатие на кнопки и, тем самым, делать всяческие авто тесты прошивки.

Достоинства данного драйвера кнопок на FSM

1++Драйвер никого не блокирует. Он может работать даже в составе NoRTOS прошивки с одним лишь супер циклом и практически нисколько не нагружать процессор.

2++Простота реализации. Прозрачность дизайна. Всего три состояния. Конечные автоматы понимают все. А ещё тут все функции помещаются на один экран.

3++Легко масштабировать. Просто добавляем строчки в конфиге и драйвер сам поймет что появились новые кнопки.

4++При длинном нажатии не срабатывает обработчик короткого нажатия.

5++Код получился переносимый на разные аппаратные платформы.

Недостатки данного драйвера кнопок на FSM

1--Драйвер может пропустить сверх быстрые нажатия, например 10ms. Те которые длятся меньше периода опроса. Но такие нажатия, как правило, имеют нечеловеческую природу и обрабатывать их не нужно.

Вывод

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

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

Словарь

Акроним

Расшифровка

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 (или как выявлять вандализм)

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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы писали драйвер обработки кнопок?
78.13% да50
21.88% нет14
Проголосовали 64 пользователя. Воздержались 6 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы использовали в программировании конечные автоматы?
72.6% да53
27.4% нет20
Проголосовали 73 пользователя. Воздержались 3 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы программировали устройства на микроконтроллерах с кнопками?
88% да66
12% нет9
Проголосовали 75 пользователей. Воздержались 4 пользователя.
Теги:
Хабы:
Всего голосов 15: ↑12 и ↓3+9
Комментарии83

Публикации

Истории

Работа

Программист С
36 вакансий

Ближайшие события

Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург