poniedziałek, 2 sierpnia 2021

C - Mapowanie funkcji

W tym poście chciałbym opisać sposób mapowania kodu, w celu wykonywania podobnych operacji w kodzie. Pozwoli to na znaczne zredukowanie ilości instrukcji warunkowych oraz na zwiększenie czytelności całego rozwiązania.


Sposób opiszę na przykładzie obsługi panelu dotykowego.

Kod dla panelu dotykowego:


W tym przypadku sprawdzany będzie panel dotykowy rezystancyjny. Wysyła on dane w formie standardowej, czyli położenie palca względem osi X oraz Y (szerokość i wysokość) kliknięcia na ekranie.

  1. typedef struct btn {
  2.     const uint8_t buttonId;
  3.     ButtonClickedHandler Handler;
  4.     const uint16_t posTop;
  5.     const uint16_t posBot;
  6.     const uint16_t posLeft;
  7.     const uint16_t posRight;
  8.     bool enabled;
  9. } Button_Typedef;

Powyższe dane zawierają indeks przycisku, pozycję przycisku na ekranie w postaci pozycji czterech linii: góra, dół, lewy bok oraz prawy bok. Kolejnym elementem jest status przycisku, czyli czy został on uruchomiony w programie i ma być sprawdzany. Kluczowym elementem w powyższej strukturze jest Handler, który będzie przechowywał funkcję jaka ma zostać wywołana podczas sprawdzania dotyku.

Inicjalizacja przykładowych przycisków wygląda następująco:

  1. static Button_Typedef addValBtn = {
  2.         .buttonId = 0x01,
  3.         .Handler = addValBtnHandler,
  4.         .posTop = 0,
  5.         .posBot = 68,
  6.         .posLeft = 360,
  7.         .posRight = 480,
  8.         .enabled = false
  9. };
  10.  
  11. static Button_Typedef substrBtn = {
  12.         .buttonId = 0x01,
  13.         .Handler = substrBtnHandler,
  14.         .posTop = 68,
  15.         .posBot = 136,
  16.         .posLeft = 360,
  17.         .posRight = 480,
  18.         .enabled = false
  19. };

Funkcje obsługujące kliknięcie przycisku mogą wyglądać następująco:

  1. static void addValBtnHandler(uint8_t btn_id)
  2. {
  3.     value++;
  4. }
  5.  
  6. static void substrBtnHandler(uint8_t btn_id)
  7. {
  8.     value--;
  9. }

Następnym elementem jest przygotowanie tablicy zawierającej wskaźniki do wszystkich przycisków.
 
  1. static Button_Typedef * tpButtons[BTN_COUNT] = {
  2.         &addValBtn, &substrBtn, &example1Btn, &example2Btn, &example3Btn, &example4Btn
  3. };

Teraz przejdźmy do funkcji pozwalającej na uruchomienie/wyłączenie przycisków przycisków na ekranie:

  1. static void SetNewButtonState(Button_Typedef * btn, ButtonState_Typedef newState) {
  2.     if(newState == ENABLED) { btn->enabled = true; }
  3.     else { btn->enabled = false; }
  4. }

Jako argument podawany jest wskaźnik do struktury dla przycisku.

Np. w celu uruchomienia wszystkich przycisków można wykorzystać tak przygotowaną funkcję:

  1. static void SetStateForAllBtn(ButtonState_Typedef newState) {
  2.     uint8_t i;
  3.     for(i = 0; i < BTN_COUNT; i++) {
  4.         SetNewButtonState(tpButtons[i], newState);
  5.     }
  6. }

Przechodzi ona przez wszystkie elementy tablicy. Maksymalny rozmiar tablicy zdefiniowany jest dyrektywą define. 

Teraz przejdę do opisu sposobu sprawdzenia klikniętej pozycji na wyświetlaczu.

Skanowanie pozycji na wyświetlaczu musi się odbywać co pewien interwał czasowy (przynajmniej w przypadku omawianego tutaj rodzaju wyświetlacza który wysyła ramki dosyć szybko do mikrokontrolera). W związku z tym aby zdarzeń kliknięcia przycisku nie było za dużo, to ramkę analizuje co 200ms. Pozwoli to na w miarę szybką i dokładną reakcję na wciśnięcie.

  1. static Button_Typedef * IdentifyButton(TPCoordinates_Typedef clickedPos) {
  2.  
  3.     if(tpDelay > 0){ return NULL; }
  4.  
  5.     Button_Typedef * clickedBtn = NULL;
  6.     uint8_t i = 0;
  7.  
  8.     for(i = 0; i < BTN_COUNT; i++) {
  9.         if(tpButtons[i]->enabled)
  10.         {
  11.             if((clickedPos.posX < tpButtons[i]->posRight) &&
  12.                 (clickedPos.posX > tpButtons[i]->posLeft) &&
  13.                 (clickedPos.posY > tpButtons[i]->posTop) &&
  14.                 (clickedPos.posY < tpButtons[i]->posBot))
  15.             {
  16.                 clickedBtn = tpButtons[i];
  17.             }
  18.         }
  19.     }
  20.     return clickedBtn;
  21. }

Powyższa funkcja na początku sprawdza czy interwał już został zakończony. Jeśli tak to następuje sprawdzenie czy przycisk jest uruchomiony oraz czy pozycja odebrana od wyświetlacza znajduje się w przedziale zdeklarowanego przycisku. Zwracamy wskaźnik do struktury przechowującej ustawienia przycisku. 

  1. typedef struct tpCoords{
  2.     uint16_t posX;
  3.     uint16_t posY;
  4. } TPCoords_Typedef;
  5.  
  6. TPCoordinates_Typedef evtCoordinates;
  7.  
  8. void CheckClickedPosEvent(void) {
  9.     evtCoordinates = getTouchPanelData(rxbuf, sizeof(rxbuf));
  10.     Button_Typedef * btn = IdentifyButton(evtCoordinates);
  11.  
  12.     if(btn != NULL) {
  13.         btn->Handler(btn->buttonId);
  14.         tpDelay = TP_DELAY;
  15.     }
  16. }

Powyżej funkcja sprawdza pozycję kliknięte na wyświetlaczu. Następnie jeśli jakiś przycisk został wciśnięty to wywołuje funkcję przypisaną do tego przycisku, po czym zwiększa opóźnienie przed kolejnym odczytem.

Tą samą zasadę można zastosować do sprawdzania ramki komunikacyjnej, ustawień w urządzeniu itp itd. Ogólnie każdy element który wymaga od nas wielu instrukcji warunkowych wywoływanych do ustawienia czy sprawdzenia różnych części programu.