niedziela, 20 listopada 2016

[|||] STM32F4 - Discovery - Projekt - Obsługa klawiatur membranowych 20x20

Ten post chciałbym poświęcić na przygotowanie programu, którego zadaniem będzie obsługa klawiatury membranowej o zadanym zakresie. Maksymalna liczba linii oraz wierszy została ustawiona na 20. Czyli potrzebna zastosować 40 pinów mikrokontrolera. 

Podłączenie



Pomiędzy pin od klawiatury oraz pin od mikrokontrolera należy podłączyć rezystor zabezpieczający układ przed uszkodzeniem. Powinnien on być największy jak to tylko możliwe. Ja wykorzystałem 3kOhm na każdej linii. Wykorzystałem także buzzer do informacji o wciśnięciu klawisza oraz trzy diody. Zielona będzie informowała o podłączeniu zasilania do układu, dioda czerwona się cyklicznie zapalała oraz gasła dla kolejnych podłączeń przycisku.

Dodatkowo dane będą przesyłane przez UART oraz USB do komputera. Wyłączenie transmisji poprzez USB będzie dokonywane poprzez wciśnięcie klawisza zamontowanego na płytce. Informacja o poprawnym wykonaniu tej operacji będzie dostarczana poprzez zapalenie diody niebieskiej.

Schemat:


Przygotowałem dwa schematy oraz dwie płytki PCB. Jedną z nich jest shield na STM32F4, pozwalający na szybki montaż oraz demontaż takiego układu. Druga płytka zawiera mikrokontroler oraz wszystkie niezbędne układy, działa ono jako oddzielne urządzenie. W tym poście przedstawię tylko schemat oraz część programową, następna część będzie zawierała wszystkie schematy oraz przygotowane płytki PCB.


Rys. 1. Schemat dla nakładki

Drugi schemat będzie trochę bardziej rozbudowany, będzie on się składał z części zasilania 3.3V dla mikrokontrolera oraz pozostałych układów. Konwerter UART USB umieszczę bezpośrednio na płytce, tak samo złącze dla USB_HID. Ostatnim elementem będzie złącze z wyprowadzonymi sygnałami do programowania przez interfejs SWD, Reset oraz UART. 

UART będzie wykorzystywany nie tylko do przesyłania informacji o samym klawiszu ale także o samym programie, czy wszystkie inicjalizacje przeszły poprawnie. Czyli można powiedzieć, że do jego debugowania. USB w opcji np. klawiatury nie nadaje się do tego za bardzo ponieważ trzeba odczekać pewien okres czasu, czasem znaczny, aby rozpoczął pracę w systemie. Dlatego UART dużo lepiej się do tego nadaje. No i oczywiście jest dużo prostszy.

Programowanie:


Program został wykonany tak, aby dane były wysyłane przez USB oraz przez konwerter UART-USB. Dodatkową opcją będzie wyświetlacz, na którym również będą wyświetlane dane dotyczące wciśniętego klawisza.

Całość należy rozpocząć od definicji poszczególnych pinów:

  1. #ifndef KEYPAD_ROW_1_PIN
  2. #define KEYPAD_ROW_1_PORT           GPIOC
  3. #define KEYPAD_ROW_1_PIN            GPIO_Pin_1
  4. #endif
  5. #ifndef KEYPAD_COL_3_PIN
  6. #define KEYPAD_COL_3_PORT        GPIOE
  7. #define KEYPAD_COL_3_PIN         GPIO_Pin_6
  8. #endif

Pominę większość deklaracji bo trochę by się ich uzbierało, tak czy inaczej wszystkie definiuje się w ten sam sposób.

Kolejnym elementem jest deklaracja stanów wysokich i niskich dla poszczególnych wyprowadzeń. Dokonuje się to w następujący sposób:

  1. #define KEYPAD_COL_1_HIGH        GPIO_SetBits(KEYPAD_COLUMN_1_PORT, KEYPAD_COLUMN_1_PIN)
  2. #define KEYPAD_COL_1_LOW         GPIO_ResetBits(KEYPAD_COLUMN_1_PORT, KEYPAD_COLUMN_1_PIN)
  3. #define KEYPAD_COL_2_HIGH        GPIO_SetBits(KEYPAD_COLUMN_2_PORT, KEYPAD_COLUMN_2_PIN)
  4. #define KEYPAD_COL_2_LOW         GPIO_ResetBits(KEYPAD_COLUMN_2_PORT, KEYPAD_COLUMN_2_PIN)
  5. //.
  6. //.
  7. //.

Następnie sprawdzenie stanu na poszczególnych pinach wierszy:

  1. #define ROW_ON_1          (!(((KEYPAD_ROW_1_PORT)->IDR & (KEYPAD_ROW_1_PIN)) == 0 ? 0 : 1))
  2. #define ROW_ON_2          (!(((KEYPAD_ROW_2_PORT)->IDR & (KEYPAD_ROW_2_PIN)) == 0 ? 0 : 1))
  3. //.
  4. //.
  5. //.

Podane wyżej porty sprawdzają stany na poszczególnych pinach rejestru IDR GPIO.

Włączenie pinów oraz potrzebnych zegarów do klawiatury:

  1. void ENABLE_ALL_CLOCK(void)
  2. {
  3.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  4.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  5.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  6.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
  7.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
  8. }
  9. void GPIO_INIT_PIN(GPIO_TypeDef* GPIOx, uint32_t GPIO_Pin)
  10. {
  11.     GPIO_InitTypeDef  GPIO_InitStructure;
  12.    
  13.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin;
  14.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  15.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  16.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  17.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
  18.     GPIO_Init(GPIOx, &GPIO_InitStructure);
  19. }

Teraz funkcja włączająca dane wyprowadzenia:

  1. void GPIO_Keypad_Init_Pins(void)
  2. {
  3.     GPIO_INIT_PIN(KEYPAD_COL_1_PORT, KEYPAD_COL_1_PIN);
  4.     GPIO_INIT_PIN(KEYPAD_COL_2_PORT, KEYPAD_COL_2_PIN);
  5.     GPIO_INIT_PIN(KEYPAD_COL_3_PORT, KEYPAD_COL_3_PIN);
  6.     GPIO_INIT_PIN(KEYPAD_COL_4_PORT, KEYPAD_COL_4_PIN);
  7.     //.
  8.     //.
  9.     //.
  10.     GPIO_INIT_PIN(KEYPAD_ROW_1_PORT, KEYPAD_ROW_1_PIN);
  11.     GPIO_INIT_PIN(KEYPAD_ROW_2_PORT, KEYPAD_ROW_2_PIN);
  12.     GPIO_INIT_PIN(KEYPAD_ROW_3_PORT, KEYPAD_ROW_3_PIN);
  13.     GPIO_INIT_PIN(KEYPAD_ROW_4_PORT, KEYPAD_ROW_4_PIN);
  14.     //.
  15.     //.
  16.     //.
  17.    
  18.     KEYPAD_COL_1_HIGH;
  19.     KEYPAD_COL_2_HIGH;
  20.     KEYPAD_COL_3_HIGH;
  21.     KEYPAD_COL_4_HIGH;
  22.     //.
  23.     //.
  24.     //.
  25. }

Następna funkcja ma za zadanie ustawić odpowiednią ilość kolumn w stan wysoki, po czym będzie przechodzić po nich ustawiając odpowiednie kolumny w stan niski:

  1. void SET_col(uint8_t active_col, uint8_t column)
  2. {
  3.     if(0 == active_col) { KEYPAD_COL_ALL_HIGH(); }
  4.     else if(actice_col >= 1) { KEYPAD_COL_1_HIGH; }
  5.     else if(active_col >= 2) { KEYPAD_COL_2_HIGH; }
  6.     else if(active_col >= 3) { KEYPAD_COL_3_HIGH; }
  7.     //.
  8.     //.
  9.     //.
  10.     switch (column) {
  11.         case 1:
  12.             KEYPAD_COL_1_LOW;
  13.             break;
  14.         case 2:
  15.             KEYPAD_COL_2_LOW;
  16.             break;
  17.         case 3:
  18.             KEYPAD_COL_3_LOW;
  19.             break;
  20.         case 4:
  21.             KEYPAD_COL_4_LOW;
  22.             break;
  23.         case 5:
  24.             KEYPAD_COL_5_LOW;
  25.             break;
  26.         //.
  27.         //.
  28.         //.
  29.         default:
  30.             break;
  31.     }
  32. }

Funkcja KEYPAD_COL_ALL_HIGH(); ustawia wszystkie kolumny w stan wysoki.

Następna funkcja będzie miała za zadanie ustawienie sprawdzenie czy dany przycisk został wciśnięty:

  1. uint8_t ROW_STATE_CHECK(uint8_t col)
  2. {
  3.     if (ROW_ON_1) { return ROW_COL_BUTTON_NUMBER[0][col - 1];   }
  4.     if (ROW_ON_2) { return ROW_COL_BUTTON_NUMBER[1][col - 1];   }
  5.     if (ROW_ON_3) { return ROW_COL_BUTTON_NUMBER[2][col - 1];   }
  6.     if (ROW_ON_4) { return ROW_COL_BUTTON_NUMBER[3][col - 1];   }
  7.     if (ROW_ON_5) { return ROW_COL_BUTTON_NUMBER[4][col - 1];   }
  8.     //.
  9.     //.
  10.     //.
  11.     return 0; //Przycisk nie wciśnięty
  12. }

Tablica ROW_COL_BUTTON_NUMBER zawiera dane dotyczące numeru dla każdego z klawiszy. Jej wygląd przedstawiłem na listningu poniżej:

  1. uint16_t ROW_COL_BUTTON_NUMBER[20][20] = {
  2.     {0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, 0x009, 0x00A, 0x00B, 0x00C, 0x00D, 0x00E, 0x00F, 0x010, 0x011, 0x012, 0x013,0x014, }, //1
  3.     {0x015, 0x016, 0x017, 0x018, 0x019, 0x01A, 0x01B, 0x01C, 0x01D, 0x01E, 0x01F, 0x020, 0x021, 0x022, 0x023, 0x024, 0x025, 0x026, 0x027,0x028, }, //2
  4.     {0x029, 0x02A, 0x02B, 0x02C, 0x02D, 0x02E, 0x02F, 0x030, 0x031, 0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x039, 0x03A, 0x03B,0x03C, }, //3
  5.     {0x03D, 0x03E, 0x03F, 0x040, 0x041, 0x042, 0x043, 0x044, 0x045, 0x046, 0x047, 0x048, 0x049, 0x04A, 0x04B, 0x04C, 0x04D, 0x04E, 0x04F,0x050, }, //4
  6.     {0x051, 0x052, 0x053, 0x054, 0x055, 0x056, 0x057, 0x058, 0x059, 0x05A, 0x05B, 0x05C, 0x05D, 0x05E, 0x05F, 0x060, 0x061, 0x062, 0x063,0x064, }, //5
  7.     {0x065, 0x066, 0x067, 0x068, 0x069, 0x06A, 0x06B, 0x06C, 0x06E, 0x06F, 0x070, 0x071, 0x072, 0x073, 0x074, 0x075, 0x076, 0x077, 0x078,0x079, }, //6
  8.     {0x07A, 0x07B, 0x07C, 0x07D, 0x07E, 0x07F, 0x080, 0x081, 0x082, 0x083, 0x084, 0x085, 0x086, 0x087, 0x088, 0x089, 0x08A, 0x08B, 0x08C,0x08E, }, //7
  9.     {0x08F, 0x090, 0x091, 0x092, 0x093, 0x094, 0x095, 0x096, 0x097, 0x098, 0x099, 0x09A, 0x09B, 0x09C, 0x09D, 0x09E, 0x09F, 0x0A0, 0x0A1,0x0A2, }, //8
  10.     {0x0A3, 0x0A4, 0x0A5, 0x0A6, 0x0A7, 0x0A8, 0x0A9, 0x0AA, 0x0AB, 0x0AC, 0x0AD, 0x0AE, 0x0AF, 0x0B1, 0x0B2, 0x0B3, 0x0B4, 0x0B5, 0x0B6,0x0B7, },
  11.     //.
  12.     //.
  13.     {0x17D, 0x17E, 0x17F, 0x180, 0x181, 0x182, 0x183, 0x184, 0x185, 0x186, 0x187, 0x188, 0x189, 0x18A, 0x18B, 0x18C, 0x18D, 0x18E, 0x18F,0x190, },//20
  14. };

Część dotyczącą biblioteki HID została opisana wstępnie w jednym z postów dla STM32. Dlatego ten temat zostawię. Jeśli chodzi o samą funkcję to w momencie wywołania zdarzenia zostanie przesłany klawisz zarówno poprzez interfejs UART USB jak i przez HID.

Dioda działa na zasadzie zmiany stanu z włączonego na wyłączony i tak w kółko. Zmiana następuje co każde włączenie klawisza. Sygnał PWM generuje dźwięk na podczas każdorazowego wciśnięcia klawisza. Jego włączenie wygląda następująco:

  1. #define FREQ 1000
  2. TIM_OCInitTypeDef TIM_OCStruct;
  3. void InitPWM()
  4. {
  5.     //TIM2 CH2 PA5
  6.     GPIO_InitTypeDef GPIO_InitStruct;
  7.     TIM_TimeBaseInitTypeDef TIM_BaseStruct;
  8.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  9.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  10.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_TIM2);
  11.     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
  12.     GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  13.     GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
  14.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
  15.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
  16.     GPIO_Init(GPIOA, &GPIO_InitStruct);
  17.     TIM_BaseStruct.TIM_Prescaler = 0;
  18.     TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
  19.     TIM_BaseStruct.TIM_Period = ((SystemCoreClock / FREQ)) - 1;
  20.     TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
  21.     TIM_BaseStruct.TIM_RepetitionCounter = 0;
  22.     TIM_TimeBaseInit(TIM3, &TIM_BaseStruct);
  23.     TIM_Cmd(TIM2, ENABLE);
  24.     TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
  25.     TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
  26.     TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
  27. }
  28. void BUZZER_ON(uint16_t value)
  29. {
  30.     TIM_OCStruct.TIM_Pulse = value;
  31.     TIM_OC2Init(TIM2, &TIM_OCStruct);
  32.     TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
  33. }

W bloku Select Case sprawdzana jest wartość czy został wciśnięty klawisz zdefiniowany w projekcie. Dane do klawiszy zostały przypisane poprzez typ wyliczeniowy enum, gdzie każdemu z 400 klawiszy został przypisany jeden numer zdefiniowany w tablicy ROW_COL_BUTTON_NUMBER. Poniżej fragment bloku odpowiadającego za zapalenie diody, wysłanie danych poprzez USART oraz poprzez HID'a.

  1. void WYSLIJ_DANE(BUTTON_NUMBER_t button)
  2. {
  3.     switch(button)
  4.     {
  5.         case Button_0:
  6.             USART_Send_Data("PRZYCISK 0");
  7.             KeyboardWrite(0x00, KEY_0, 0x00, 0x00, 0x00, 0x00, 0x00);
  8.             BUZZER_ON((SystemCoreClock/FREQ/2 - 1);
  9.             DIODE_CHECK();
  10.         break;
  11.         //.
  12.         //.
  13.         //.
  14.     }
  15. }


Wnioski, zmiany itp. 


Jeśli chodzi o wnioski końcowe, to zdecydowanie jest to mało ekonomiczne rozwiązania, z powodzeniem można by było układ zastąpić dużo tańszym procesorem np. STM32F0 czy STM32F1. Kolejnym sposobem poprawy mogło by być zastosowanie rejestrów przesuwnych i wykorzystanie bardzo taniego układu  np STM32F031F4P6. Byłby on natomiast pozbawiony wbudowanej opcji USB-OTG, dzięki czemu mógłby pracować jako klawiatura pod USB.

Co się tyczy programu Circuit Maker to raczej nie będzie on moim ulubionym. Jest on dosyć toporny w obsłudze, mimo, że jest podobny dosyć podobny do Altium Designera. Mogą wystąpić problemy z zapisem projektu, ponieważ całość trzymana jest w chmurze. Mi osobiście zdarzyło się z dwa razy, że program nie zdążył zapisać projektu i za wcześnie go zamknąłem. Co spowodowało utratą dużej części projektu, mimo, że w trakcie wykonywałem zapis dosyć często. Innym problem są skróty klawiszowe, istnieją przypadki w których są one takie same dla kilku opcji. No i na koniec podstawowym minusem jest możliwość stworzenia maksymalnie dwóch projektów prywatnych. Reszta musi być udostępniona publicznie.

Dużym plusem natomiast jest łatwy dostęp do bibliotek z komponentami. Rozwiązane jest to poprzez wyszukiwarkę online z bazą komponentów. Jeśli danego elementu nie ma, to w bardzo łatwy sposób można go dodać i przygotować do niego element biblioteki, który będzie dostępny dla każdego.