Ten post chciałbym poświęcić na opisanie obsługi klawiatury matrycowej 4x4 w mikrokontrolerze 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:
Dane odnośnie klikniętych przycisków oraz flag przechowuję w strukturze:
Inicjalizacja pinów klawiatury. W moim przypadku wykorzystałem piny podłączone do portu pierwszego.
Inicjalizacja timera, w którym będzie następowało zliczanie dla zmiennej wyzwalającej sprawdzenie klawiatury, bądź samo sprawdzenie:
Uruchomienie klawiatury polega na ustawieniu pinów oraz uruchomienia i ustawienia timera:
Funkcja skanująca przycisk wygląda następująco:
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.
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.
Pozostałe funkcje można znaleźć w bibliotece udostępnionej na dysku Google pod tym linkiem.
Definicje klawiszy zostały opisane w następujący sposób:
- typedef enum KEYPAD_NUM_UART_MB{
- _KEY_NUM_0 = 0,
- _KEY_NUM_1 = 1,
- _KEY_NUM_2 = 2,
- _KEY_NUM_3 = 3,
- _KEY_NUM_4 = 4,
- _KEY_NUM_5 = 5,
- _KEY_NUM_6 = 6,
- _KEY_NUM_7 = 7,
- _KEY_NUM_8 = 8,
- _KEY_NUM_9 = 9,
- _KEY_NUM_START = 0x0A,
- _KEY_NUM_HASH = 0x0B,
- _KEY_NUM_ENTER = 0x0C,
- _KEY_NUM_EXIT = 0x0D,
- _KEY_FREE_1 = 0x0E,
- _KEY_FREE_2 = 0x0F,
- _KEY_NUM_NONE = 0xFF
- }uartMb_Keypad_Number;
Dane odnośnie klikniętych przycisków oraz flag przechowuję w strukturze:
- typedef struct matrixKeyboard_TD{
- volatile uint8_t keyCheckFlag;
- volatile uint8_t keyInterruptFlag;
- volatile uint8_t oldClickedBtnCounter;
- volatile uartMb_Keypad_Number clickedBtn;
- volatile uartMb_Keypad_Number oldClickedBtn;
- }matrixKeyboard_TypeDef;
Inicjalizacja pinów klawiatury. W moim przypadku wykorzystałem piny podłączone do portu pierwszego.
- static void Keyboard_Gpio_Init(void)
- {
- PINSEL_CFG_Type PinCfg;
- PinCfg.Funcnum = PINSEL_FUNC_0;
- PinCfg.OpenDrain = PINSEL_PINMODE_NORMAL;
- PinCfg.Pinmode = PINSEL_PINMODE_PULLUP;
- PinCfg.Portnum = KEY_PORT_NR;
- PinCfg.Pinnum = KEY_K1_PIN_NR;
- PINSEL_ConfigPin( &PinCfg );
- PinCfg.Pinnum = KEY_K2_PIN_NR;
- PINSEL_ConfigPin( &PinCfg );
- PinCfg.Pinnum = KEY_K3_PIN_NR;
- PINSEL_ConfigPin( &PinCfg );
- PinCfg.Pinnum = KEY_K4_PIN_NR;
- PINSEL_ConfigPin( &PinCfg );
- PinCfg.OpenDrain = PINSEL_PINMODE_NORMAL;
- PinCfg.Pinmode = PINSEL_PINMODE_PULLUP;
- PinCfg.Pinnum = KEY_W1_PIN_NR;
- PINSEL_ConfigPin( &PinCfg );
- PinCfg.Pinnum = KEY_W2_PIN_NR;
- PINSEL_ConfigPin( &PinCfg );
- PinCfg.Pinnum = KEY_W3_PIN_NR;
- PINSEL_ConfigPin( &PinCfg );
- PinCfg.Pinnum = KEY_W4_PIN_NR;
- PINSEL_ConfigPin( &PinCfg );
- KEY_K1_PORT->FIODIR |= KEY_K1_PIN;
- KEY_K2_PORT->FIODIR |= KEY_K2_PIN;
- KEY_K3_PORT->FIODIR |= KEY_K3_PIN;
- KEY_K4_PORT->FIODIR |= KEY_K4_PIN;
- KEY_K1_PORT->FIOCLR |= KEY_K1_PIN;
- KEY_K2_PORT->FIOCLR |= KEY_K2_PIN;
- KEY_K3_PORT->FIOCLR |= KEY_K3_PIN;
- KEY_K4_PORT->FIOCLR |= KEY_K4_PIN;
- setPinRowAsInput();
- }
Inicjalizacja timera, w którym będzie następowało zliczanie dla zmiennej wyzwalającej sprawdzenie klawiatury, bądź samo sprawdzenie:
- static void KEYBOARD_TIMER(unsigned int time_ms)
- {
- TIM_TIMERCFG_Type TIM_ConfigStruct;
- TIM_MATCHCFG_Type TIM_MatchConfigStruct ;
- // Initialize timer 2, prescale count time of 1ms
- TIM_ConfigStruct.PrescaleOption = TIM_PRESCALE_USVAL;
- TIM_ConfigStruct.PrescaleValue = 1000;
- // use channel 0, MR0
- TIM_MatchConfigStruct.MatchChannel = 0;
- // Enable interrupt when MR0 matches the value in TC register
- TIM_MatchConfigStruct.IntOnMatch = TRUE;
- //Enable reset on MR0: TIMER will not reset if MR0 matches it
- TIM_MatchConfigStruct.ResetOnMatch = TRUE;
- //Stop on MR0 if MR0 matches it
- TIM_MatchConfigStruct.StopOnMatch = TRUE;
- //do no thing for external output
- TIM_MatchConfigStruct.ExtMatchOutputType = TIM_EXTMATCH_NOTHING;
- // Set Match value, count value is time (timer * 1000uS =timer mS )
- TIM_MatchConfigStruct.MatchValue = time_ms;
- // Set configuration for Tim_config and Tim_MatchConfig
- TIM_Init( LPC_TIM2, TIM_TIMER_MODE, &TIM_ConfigStruct );
- TIM_ConfigMatch( LPC_TIM2, &TIM_MatchConfigStruct );
- NVIC_SetPriority(TIMER2_IRQn, (NVIC_TIMER2_KEYBOARD|0));
- NVIC_EnableIRQ( TIMER2_IRQn );
- }
Uruchomienie klawiatury polega na ustawieniu pinów oraz uruchomienia i ustawienia timera:
- void Keyboard_Init(void){
- Keyboard_Gpio_Init();
- columnsClearPins();
- KEYBOARD_TIMER(20);
- startTimer();
- }
Funkcja skanująca przycisk wygląda następująco:
- void Keyboard_Use_Function(void){
- if(checkPressedKey() != 0) { return; }
- if ((matrixKey_TypeDef.clickedBtn != _KEY_NUM_NONE) && (matrixKey_TypeDef.oldClickedBtn != matrixKey_TypeDef.clickedBtn)){
- matrixKey_TypeDef.oldClickedBtn = matrixKey_TypeDef.clickedBtn;
- matrixKey_TypeDef.oldClickedBtnCounter = KEY_TIMER2_COUNTER;
- #ifdef ENABLE_MATRIX_KEYBOARD_DEBUG_MSG_UART
- debugFunction_DisplayClickedBtn(matrixKey_TypeDef.clickedBtn);
- #endif
- executeClickedBtnFunction(matrixKey_TypeDef.clickedBtn);
- clearKlawData(&matrixKey_TypeDef);
- }
- else
- {
- clearKlawData(&matrixKey_TypeDef);
- }
- }
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.
- void TIMER2_IRQHandler(void)
- {
- if (TIM_GetIntStatus(LPC_TIM2, 0) == 1)
- {
- TIM_ClearIntPending(LPC_TIM2, 0);
- matrixKey_TypeDef.keyInterruptFlag = 1;
- if(matrixKey_TypeDef.oldClickedBtnCounter > 0){
- matrixKey_TypeDef.oldClickedBtnCounter--;
- }
- else{
- matrixKey_TypeDef.oldClickedBtn = _KEY_NUM_NONE;
- }
- startTimer();
- }
- }
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.
- void TIMER2_IRQHandler(void)
- {
- if (TIM_GetIntStatus(LPC_TIM2, 0) == 1)
- {
- TIM_ClearIntPending(LPC_TIM2, 0);
- startTimer();
- matrixKey_TypeDef.keyInterruptFlag = 1;
- if(matrixKey_TypeDef.oldClickedBtnCounter > 0){
- matrixKey_TypeDef.oldClickedBtnCounter--;
- }
- else{
- matrixKey_TypeDef.oldClickedBtn = _KEY_NUM_NONE;
- }
- Keyboard_Use_Function();
- }
- }
Pozostałe funkcje można znaleźć w bibliotece udostępnionej na dysku Google pod tym linkiem.