
#012 - LIB02 - Bilioteka BUTTONS
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:
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”