Chociaż moim zdaniem fizyczne przyciski w urządzeniach do sterowania pomału robią się czymś “retro” (wygrywają ekrany dotykowe, czy też sterowanie bezprzewodowe) to jednak bibliotekę do ich obsługi warto mieć pod ręką.

Biblioteka znajduje się na GitLabie:

https://gitlab.com/embedownik/BUTTON

Przeznaczenie biblioteki

Jest to biblioteka do obsługi przycisków z wbudowanym debouncigiem.

Główne cechy:

  • multiplatformowość - bo bilbioteki należy podać callbacki sprawdzające stan przycisków
  • działa nie tylko do przycisków, ale może posłużyć do debouncingu zwykłych sygnałów cyfrowych
  • możliwość użycia biblioteki wiele razy w projekcie - dzięki wykorzystaniu struktur konfiguracyjnych
  • domyślnie pracuje z FreeRTOSem - tworzy osobny task, jednak jednym parametrem można wyłączyć ta opcję - wtedy użytkownik musi zadbać tylko o cykliczne wywołanie funkjcji “tick”
  • uruchamia callback użytkownika dla eventów takich jak wciśnięcie/puszczenie/przytrzymanie/repeat (czyli cyklicznie wywoływany callback kiedy przycisk jest cały czas wciśnięty)
  • konfiguracja w plikach użytkownika - więc nie ma potrzeby modyfikacji plików biblioteki

Użycie biblioteki

Biliotekę należy zainicjalizować poprzez utworzenie struktury konfiguracyjnej, a następnie przekazanie jej adresu do funkcji “BUTTONS_Init()”.

Biblioteka jest niezależna od sprzętu - jako pierwszy etap konfiguracji należy utworzyć funkcje, które umożliwiają pobranie stanu pinu z przyciskiem. Przykładowo dla STHAL będzie to wyglądać następująco:

bool button0_GetState(void)
{
    bool retVal = false;

    if(HAL_GPIO_ReadPin(BUTTON0_GPIO_Port, BUTTON0_Pin) == GPIO_PIN_RESET)
    {
        retVal = true;
    }

    return retVal;
}

bool button1_GetState(void)
{
    bool retVal = false;

    if(HAL_GPIO_ReadPin(BUTTON1_GPIO_Port, BUTTON1_Pin) == GPIO_PIN_RESET)
    {
        retVal = true;
    }

    return retVal;
}

bool button2_GetState(void)
{
    bool retVal = false;

    if(HAL_GPIO_ReadPin(BUTTON2_GPIO_Port, BUTTON2_Pin) == GPIO_PIN_RESET)
    {
        retVal = true;
    }

    return retVal;
}

Funkcje muszą być zgodne z typem narzuconym przez bibliotekę - czyli zwracać bool’a i nie przyjmować żadnych parametrów:

/* typedef of function to check button state - should return true if button is pressed */
typedef bool (*getButtonState_cb)(void);

Następnie należy przygotować tablicę grupującą konfigurację każdego przycisku np.

Button_SingleButton_Config_s buttonsArray[] =
{
    {
       .getState = button0_GetState,
       .debounceTimeHigh = 5,
       .debounceTimeLow = 5,
       .longPressTime = 0,
    },
    {
       .getState = button1_GetState,
       .debounceTimeHigh = 5,
       .debounceTimeLow = 5,
       .longPressTime = 0,
    },
    {
       .getState = button2_GetState,
       .debounceTimeHigh = 5,
       .debounceTimeLow = 5,
       .longPressTime = 0,
    },
};

Parametry jakie należy tam wprowadzić:

/*
 * Struct with all single button parameters.
 * Note: measure unit for "Time" is tick from settings -> "measIntervalTime"
 */
typedef struct
{
    /* to configure by user */
    getButtonState_cb getState; /* callback for getState function for button */

    uint8_t debounceTimeHigh;
    uint8_t debounceTimeLow;
    uint8_t longPressTime;
    uint8_t repeatTime;
    bool    releaseEventActive;

    /* this part is used by library - this way to avoid dynamic allocation */
    Button_internalParametes_s internal;
}Button_SingleButton_Config_s;

Należy utworzyć funkcję będącą zbiorczym callbackiem dla biblioteki. Musi być ona zgodna z typedefem:

/* typedef for callback after detecting button event */
typedef void (*buttonEventCallback)(uint8_t buttonNumber, ButtonEvent_e event);

Czyli biblioteka w przypadku wykrycia eventu na przycisku będzie zwracać użytkownikowi numer przycisku oraz typ eventu.

Dostępne typy eventów:

/* all possible buttons events */
typedef enum
{
    ButtonEvent_PRESSED,
    ButtonEvent_PRESSED_LONG,
    ButtonEvent_PRESSED_REPEAT,
    ButtonEvent_RELEASED,
}ButtonEvent_e;

UWAGA! - jest to funkcja wywoływana z kontekstu taska od przycisków - więc w przypadku dużych funkcji należy zwiększyć rozmiar stosu dla taska przycisków. Zalecane podejście w przypadku systemów “EventDriven” to przekonwertowanie odebranego eventu na aplikacyjny i wrzucenie jako rozkaz go w kolejkę do przetworzenia w innym tasku.

Ostatnim krokiem konfiguracyjnym jest utworzenie instancji struktury “ButtonsConfig_s” - to ona jest używana przez funkcję Init.

Pola tej struktury:

/*
 * Config for module - this way user can easly add any number of buttons.
 */
typedef struct
{
    Button_SingleButton_Config_s *buttonsArray;
    uint8_t buttonsNumber;
    buttonEventCallback eventCallback;
    uint8_t measInterval;
#ifndef BUTTON_DEF_NO_RTOS
    uint16_t buttonTaskStackSize;
    uint16_t buttonTaskPriority;
#endif
}ButtonsConfig_s;

Przykład wypełnienia:

ButtonsConfig_s buttonsConfig = 
{
    .buttonsArray = buttonsArray,
    .buttonsNumber = (sizeof(buttonsArray)/sizeof(buttonsArray[0])),
    .measInterval = 10U,
    .eventCallback = buttonsEventsCallback,
    .buttonTaskPriority = BUTTONS_TASK_PRIORITY,
    .buttonTaskStackSize = BUTTONS_TASK_STACK_SIZE,
};

Użycie bez RTOSa

W tym przypadku należy zdefiniować w projekcie symbol “BUTTON_DEF_NO_RTOS” - nie trzeba używać wtedy funkcji Init, natomiast należy zapewnić cykliczne wywoływanie funkcji “BUTTONS_Tick()”, a jako jej parametr przekazywać strukturę konfiguracyjną.

Podstawa czasu w bibliotece

Podstawą czasu dla bibioteki jest parametr “measInterval” - od niego zależy czas pomiędzy budzeniem się taska przycisków/lub w przypadku wersji bez RTOSa - interwał wywołania funkcji Tick jaki zapewni użytkownik. Czyli dla wywoływania co 1ms - “longPressTime = 30” oznacza ~30ms.

Wyjaśnienie eventów

Dla ustawienia przycisków jak poniżej:

Button_SingleButton_Config_s buttonsArray[] =
{
    {
       .getState = button0_GetState,
       .debounceTimeHigh = 3U,
       .debounceTimeLow  = 3U,
       .longPressTime    = 5U,
       .repeatTime       = 3U,
       .releaseEventActive = true,
    },
};

Wywołania eventów w czasie będą prezentować się jak na obrazku:

obr

Gdzie 1 cykl “zegara” w pierwszej linii to jeden “tick” taska przycisków/wywołania funkcji BUTTONS_Tick() przez użytkownika.

W opisowej wersji:

  • przycisk musi być wciśnięty przez 3 cykle (debounceTimeHigh) aby zostało wykryte jego wciśnięcie (jeśli nie będą to 3 eventy po sobie to odliczanie jest zerowane)
  • po tym evencie po 5 tickach (longPressTime) zostanie wywołany event “LongPress”
  • następnie co 3 ticki (repeatTime) będzie wywoływany event “repeat”
  • w przypadku puszczenia przycisku - jeśli był on puszczony przez 3 ticki (debounceTimeLow) zostanie wywołany event “release”