czwartek, 6 grudnia 2018

LPC1769 - Klawiatura membranowa 4x4

Ten post chciałbym poświęcić na opisanie obsługi klawiatury matrycowej 4x4 w mikrokontrolerze LPC1769.

Znalezione obrazy dla zapytania lpc1769
[Źródło: https://www.nxp.com]

Biblioteka:


Poniżej przejdę przez opis poszczególnych funkcji wraz z ich zasadą działania.

Definicje klawiszy zostały opisane w następujący sposób:

  1. typedef enum KEYPAD_NUM_UART_MB{
  2.     _KEY_NUM_0 = 0,
  3.     _KEY_NUM_1 = 1,
  4.     _KEY_NUM_2 = 2,
  5.     _KEY_NUM_3 = 3,
  6.     _KEY_NUM_4 = 4,
  7.     _KEY_NUM_5 = 5,
  8.     _KEY_NUM_6 = 6,
  9.     _KEY_NUM_7 = 7,
  10.     _KEY_NUM_8 = 8,
  11.     _KEY_NUM_9 = 9,
  12.     _KEY_NUM_START = 0x0A,
  13.     _KEY_NUM_HASH = 0x0B,
  14.     _KEY_NUM_ENTER = 0x0C,
  15.     _KEY_NUM_EXIT = 0x0D,
  16.     _KEY_FREE_1 = 0x0E,
  17.     _KEY_FREE_2 = 0x0F,
  18.     _KEY_NUM_NONE = 0xFF
  19. }uartMb_Keypad_Number;

Dane odnośnie klikniętych przycisków oraz flag przechowuję w strukturze:

  1. typedef struct matrixKeyboard_TD{
  2.         volatile uint8_t keyCheckFlag;
  3.         volatile uint8_t keyInterruptFlag;
  4.         volatile uint8_t oldClickedBtnCounter;
  5.         volatile uartMb_Keypad_Number clickedBtn;
  6.         volatile uartMb_Keypad_Number oldClickedBtn;
  7. }matrixKeyboard_TypeDef;

Inicjalizacja pinów klawiatury. W moim przypadku wykorzystałem piny podłączone do portu pierwszego.

  1. static void Keyboard_Gpio_Init(void)
  2. {
  3.     PINSEL_CFG_Type PinCfg;
  4.     PinCfg.Funcnum = PINSEL_FUNC_0;
  5.     PinCfg.OpenDrain = PINSEL_PINMODE_NORMAL;
  6.     PinCfg.Pinmode = PINSEL_PINMODE_PULLUP;
  7.     PinCfg.Portnum = KEY_PORT_NR;
  8.     PinCfg.Pinnum = KEY_K1_PIN_NR;
  9.     PINSEL_ConfigPin( &PinCfg );
  10.     PinCfg.Pinnum = KEY_K2_PIN_NR;
  11.     PINSEL_ConfigPin( &PinCfg );
  12.     PinCfg.Pinnum = KEY_K3_PIN_NR;
  13.     PINSEL_ConfigPin( &PinCfg );
  14.     PinCfg.Pinnum = KEY_K4_PIN_NR;
  15.     PINSEL_ConfigPin( &PinCfg );
  16.     PinCfg.OpenDrain = PINSEL_PINMODE_NORMAL;
  17.     PinCfg.Pinmode = PINSEL_PINMODE_PULLUP;
  18.     PinCfg.Pinnum = KEY_W1_PIN_NR;
  19.     PINSEL_ConfigPin( &PinCfg );
  20.     PinCfg.Pinnum = KEY_W2_PIN_NR;
  21.     PINSEL_ConfigPin( &PinCfg );
  22.     PinCfg.Pinnum = KEY_W3_PIN_NR;
  23.     PINSEL_ConfigPin( &PinCfg );
  24.     PinCfg.Pinnum = KEY_W4_PIN_NR;
  25.     PINSEL_ConfigPin( &PinCfg );
  26.     KEY_K1_PORT->FIODIR |= KEY_K1_PIN;
  27.     KEY_K2_PORT->FIODIR |= KEY_K2_PIN;
  28.     KEY_K3_PORT->FIODIR |= KEY_K3_PIN;
  29.     KEY_K4_PORT->FIODIR |= KEY_K4_PIN;
  30.     KEY_K1_PORT->FIOCLR |= KEY_K1_PIN;
  31.     KEY_K2_PORT->FIOCLR |= KEY_K2_PIN;
  32.     KEY_K3_PORT->FIOCLR |= KEY_K3_PIN;
  33.     KEY_K4_PORT->FIOCLR |= KEY_K4_PIN;
  34.     setPinRowAsInput();
  35. }

Inicjalizacja timera, w którym będzie następowało zliczanie dla zmiennej wyzwalającej sprawdzenie klawiatury, bądź samo sprawdzenie:

  1. static void KEYBOARD_TIMER(unsigned int time_ms)
  2. {
  3.     TIM_TIMERCFG_Type TIM_ConfigStruct;
  4.     TIM_MATCHCFG_Type TIM_MatchConfigStruct ;
  5.     // Initialize timer 2, prescale count time of 1ms
  6.     TIM_ConfigStruct.PrescaleOption = TIM_PRESCALE_USVAL;
  7.     TIM_ConfigStruct.PrescaleValue  = 1000;
  8.     // use channel 0, MR0
  9.     TIM_MatchConfigStruct.MatchChannel = 0;
  10.     // Enable interrupt when MR0 matches the value in TC register
  11.     TIM_MatchConfigStruct.IntOnMatch   = TRUE;
  12.     //Enable reset on MR0: TIMER will not reset if MR0 matches it
  13.     TIM_MatchConfigStruct.ResetOnMatch = TRUE;
  14.     //Stop on MR0 if MR0 matches it
  15.     TIM_MatchConfigStruct.StopOnMatch  = TRUE;
  16.     //do no thing for external output
  17.     TIM_MatchConfigStruct.ExtMatchOutputType = TIM_EXTMATCH_NOTHING;
  18.     // Set Match value, count value is time (timer * 1000uS =timer mS )
  19.     TIM_MatchConfigStruct.MatchValue   = time_ms;
  20.     // Set configuration for Tim_config and Tim_MatchConfig
  21.     TIM_Init( LPC_TIM2, TIM_TIMER_MODE, &TIM_ConfigStruct );
  22.     TIM_ConfigMatch( LPC_TIM2, &TIM_MatchConfigStruct );
  23.     NVIC_SetPriority(TIMER2_IRQn, (NVIC_TIMER2_KEYBOARD|0));
  24.     NVIC_EnableIRQ( TIMER2_IRQn );
  25. }

Uruchomienie klawiatury polega na ustawieniu pinów oraz uruchomienia i ustawienia timera:

  1. void Keyboard_Init(void){
  2.     Keyboard_Gpio_Init();
  3.     columnsClearPins();
  4.     KEYBOARD_TIMER(20);
  5.     startTimer();
  6. }

Funkcja skanująca przycisk wygląda następująco:

  1. void Keyboard_Use_Function(void){
  2.      if(checkPressedKey() != 0) { return; }
  3.      if ((matrixKey_TypeDef.clickedBtn != _KEY_NUM_NONE) && (matrixKey_TypeDef.oldClickedBtn != matrixKey_TypeDef.clickedBtn)){
  4.          matrixKey_TypeDef.oldClickedBtn = matrixKey_TypeDef.clickedBtn;
  5.          matrixKey_TypeDef.oldClickedBtnCounter = KEY_TIMER2_COUNTER;
  6.          #ifdef ENABLE_MATRIX_KEYBOARD_DEBUG_MSG_UART
  7.          debugFunction_DisplayClickedBtn(matrixKey_TypeDef.clickedBtn);
  8.          #endif
  9.          executeClickedBtnFunction(matrixKey_TypeDef.clickedBtn);
  10.          clearKlawData(&matrixKey_TypeDef);
  11.      }
  12.      else
  13.      {
  14.          clearKlawData(&matrixKey_TypeDef);
  15.      }
  16. }

Oprócz samej obsługi przycisku sprawdzany jest stan wcześniejszego przycisku, co zapobiega zbyt częstemu wykonywania kliknięcia tej samej linii w przypadku dłuższego trzymania przycisku.

  1. void TIMER2_IRQHandler(void)
  2. {
  3.     if (TIM_GetIntStatus(LPC_TIM2, 0) == 1)
  4.     {
  5.         TIM_ClearIntPending(LPC_TIM2, 0);
  6.         matrixKey_TypeDef.keyInterruptFlag = 1;
  7.         if(matrixKey_TypeDef.oldClickedBtnCounter > 0){
  8.             matrixKey_TypeDef.oldClickedBtnCounter--;
  9.         }
  10.         else{
  11.             matrixKey_TypeDef.oldClickedBtn = _KEY_NUM_NONE;
  12.         }
  13.         startTimer();
  14.     }
  15. }

W tej interpretacji przerwania od timera najpierw czyszczę flagę wyzwolenia przerwania, po nim zaznaczymy zmienną informującą, że czas przeminął i nastąpiło wywołanie przerwania. W tym samym timerze obsługuję także przetrzymywanie klikniętego przycisku. Wywołana flaga wyzwala wykonywanie funkcji Keyboard_Use_Function() w pętli głównej.

Drugie może nie co lepsze rozwiązanie, zwłaszcza gdy system wykonuje więcej operacji to skanowanie przycisku w przerwaniu. Tutaj obsługa przerwania będzie się wykonywała dłużej, z tego powodu w niej należy zostawić tylko samo sprawdzenie i przypisanie wygenerowanego klawisza. Wszystkie akcje związane z jego obsługą (wysłanie komendy, zmianę funkcji w programie itp.) należy przerzucić do pętli głównej, tak aby przerwanie zachować możliwie krótkie. Takie rozwiązanie pozwoli na skanowanie klawiatury w stałych odstępach czasowych.

  1. void TIMER2_IRQHandler(void)
  2. {
  3.     if (TIM_GetIntStatus(LPC_TIM2, 0) == 1)
  4.     {
  5.         TIM_ClearIntPending(LPC_TIM2, 0);
  6.         startTimer();
  7.         matrixKey_TypeDef.keyInterruptFlag = 1;
  8.         if(matrixKey_TypeDef.oldClickedBtnCounter > 0){
  9.             matrixKey_TypeDef.oldClickedBtnCounter--;
  10.         }
  11.         else{
  12.             matrixKey_TypeDef.oldClickedBtn = _KEY_NUM_NONE;
  13.         }
  14.         Keyboard_Use_Function();
  15.     }
  16. }

Pozostałe funkcje można znaleźć w bibliotece udostępnionej na dysku Google pod tym linkiem.