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.
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.
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:
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:
Następnie sprawdzenie stanu na poszczególnych pinach wierszy:
Podane wyżej porty sprawdzają stany na poszczególnych pinach rejestru IDR GPIO.
Włączenie pinów oraz potrzebnych zegarów do klawiatury:
Teraz funkcja włączająca dane wyprowadzenia:
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:
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:
Tablica ROW_COL_BUTTON_NUMBER zawiera dane dotyczące numeru dla każdego z klawiszy. Jej wygląd przedstawiłem na listningu poniżej:
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:
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.
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.
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:
- #ifndef KEYPAD_ROW_1_PIN
- #define KEYPAD_ROW_1_PORT GPIOC
- #define KEYPAD_ROW_1_PIN GPIO_Pin_1
- #endif
- #ifndef KEYPAD_COL_3_PIN
- #define KEYPAD_COL_3_PORT GPIOE
- #define KEYPAD_COL_3_PIN GPIO_Pin_6
- #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:
- #define KEYPAD_COL_1_HIGH GPIO_SetBits(KEYPAD_COLUMN_1_PORT, KEYPAD_COLUMN_1_PIN)
- #define KEYPAD_COL_1_LOW GPIO_ResetBits(KEYPAD_COLUMN_1_PORT, KEYPAD_COLUMN_1_PIN)
- #define KEYPAD_COL_2_HIGH GPIO_SetBits(KEYPAD_COLUMN_2_PORT, KEYPAD_COLUMN_2_PIN)
- #define KEYPAD_COL_2_LOW GPIO_ResetBits(KEYPAD_COLUMN_2_PORT, KEYPAD_COLUMN_2_PIN)
- //.
- //.
- //.
Następnie sprawdzenie stanu na poszczególnych pinach wierszy:
- #define ROW_ON_1 (!(((KEYPAD_ROW_1_PORT)->IDR & (KEYPAD_ROW_1_PIN)) == 0 ? 0 : 1))
- #define ROW_ON_2 (!(((KEYPAD_ROW_2_PORT)->IDR & (KEYPAD_ROW_2_PIN)) == 0 ? 0 : 1))
- //.
- //.
- //.
Podane wyżej porty sprawdzają stany na poszczególnych pinach rejestru IDR GPIO.
Włączenie pinów oraz potrzebnych zegarów do klawiatury:
- void ENABLE_ALL_CLOCK(void)
- {
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
- }
- void GPIO_INIT_PIN(GPIO_TypeDef* GPIOx, uint32_t GPIO_Pin)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin;
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;
- GPIO_Init(GPIOx, &GPIO_InitStructure);
- }
Teraz funkcja włączająca dane wyprowadzenia:
- void GPIO_Keypad_Init_Pins(void)
- {
- GPIO_INIT_PIN(KEYPAD_COL_1_PORT, KEYPAD_COL_1_PIN);
- GPIO_INIT_PIN(KEYPAD_COL_2_PORT, KEYPAD_COL_2_PIN);
- GPIO_INIT_PIN(KEYPAD_COL_3_PORT, KEYPAD_COL_3_PIN);
- GPIO_INIT_PIN(KEYPAD_COL_4_PORT, KEYPAD_COL_4_PIN);
- //.
- //.
- //.
- GPIO_INIT_PIN(KEYPAD_ROW_1_PORT, KEYPAD_ROW_1_PIN);
- GPIO_INIT_PIN(KEYPAD_ROW_2_PORT, KEYPAD_ROW_2_PIN);
- GPIO_INIT_PIN(KEYPAD_ROW_3_PORT, KEYPAD_ROW_3_PIN);
- GPIO_INIT_PIN(KEYPAD_ROW_4_PORT, KEYPAD_ROW_4_PIN);
- //.
- //.
- //.
- KEYPAD_COL_1_HIGH;
- KEYPAD_COL_2_HIGH;
- KEYPAD_COL_3_HIGH;
- KEYPAD_COL_4_HIGH;
- //.
- //.
- //.
- }
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:
- void SET_col(uint8_t active_col, uint8_t column)
- {
- if(0 == active_col) { KEYPAD_COL_ALL_HIGH(); }
- else if(actice_col >= 1) { KEYPAD_COL_1_HIGH; }
- else if(active_col >= 2) { KEYPAD_COL_2_HIGH; }
- else if(active_col >= 3) { KEYPAD_COL_3_HIGH; }
- //.
- //.
- //.
- switch (column) {
- case 1:
- KEYPAD_COL_1_LOW;
- break;
- case 2:
- KEYPAD_COL_2_LOW;
- break;
- case 3:
- KEYPAD_COL_3_LOW;
- break;
- case 4:
- KEYPAD_COL_4_LOW;
- break;
- case 5:
- KEYPAD_COL_5_LOW;
- break;
- //.
- //.
- //.
- default:
- break;
- }
- }
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:
- uint8_t ROW_STATE_CHECK(uint8_t col)
- {
- if (ROW_ON_1) { return ROW_COL_BUTTON_NUMBER[0][col - 1]; }
- if (ROW_ON_2) { return ROW_COL_BUTTON_NUMBER[1][col - 1]; }
- if (ROW_ON_3) { return ROW_COL_BUTTON_NUMBER[2][col - 1]; }
- if (ROW_ON_4) { return ROW_COL_BUTTON_NUMBER[3][col - 1]; }
- if (ROW_ON_5) { return ROW_COL_BUTTON_NUMBER[4][col - 1]; }
- //.
- //.
- //.
- return 0; //Przycisk nie wciśnięty
- }
Tablica ROW_COL_BUTTON_NUMBER zawiera dane dotyczące numeru dla każdego z klawiszy. Jej wygląd przedstawiłem na listningu poniżej:
- uint16_t ROW_COL_BUTTON_NUMBER[20][20] = {
- {0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, 0x009, 0x00A, 0x00B, 0x00C, 0x00D, 0x00E, 0x00F, 0x010, 0x011, 0x012, 0x013,0x014, }, //1
- {0x015, 0x016, 0x017, 0x018, 0x019, 0x01A, 0x01B, 0x01C, 0x01D, 0x01E, 0x01F, 0x020, 0x021, 0x022, 0x023, 0x024, 0x025, 0x026, 0x027,0x028, }, //2
- {0x029, 0x02A, 0x02B, 0x02C, 0x02D, 0x02E, 0x02F, 0x030, 0x031, 0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x039, 0x03A, 0x03B,0x03C, }, //3
- {0x03D, 0x03E, 0x03F, 0x040, 0x041, 0x042, 0x043, 0x044, 0x045, 0x046, 0x047, 0x048, 0x049, 0x04A, 0x04B, 0x04C, 0x04D, 0x04E, 0x04F,0x050, }, //4
- {0x051, 0x052, 0x053, 0x054, 0x055, 0x056, 0x057, 0x058, 0x059, 0x05A, 0x05B, 0x05C, 0x05D, 0x05E, 0x05F, 0x060, 0x061, 0x062, 0x063,0x064, }, //5
- {0x065, 0x066, 0x067, 0x068, 0x069, 0x06A, 0x06B, 0x06C, 0x06E, 0x06F, 0x070, 0x071, 0x072, 0x073, 0x074, 0x075, 0x076, 0x077, 0x078,0x079, }, //6
- {0x07A, 0x07B, 0x07C, 0x07D, 0x07E, 0x07F, 0x080, 0x081, 0x082, 0x083, 0x084, 0x085, 0x086, 0x087, 0x088, 0x089, 0x08A, 0x08B, 0x08C,0x08E, }, //7
- {0x08F, 0x090, 0x091, 0x092, 0x093, 0x094, 0x095, 0x096, 0x097, 0x098, 0x099, 0x09A, 0x09B, 0x09C, 0x09D, 0x09E, 0x09F, 0x0A0, 0x0A1,0x0A2, }, //8
- {0x0A3, 0x0A4, 0x0A5, 0x0A6, 0x0A7, 0x0A8, 0x0A9, 0x0AA, 0x0AB, 0x0AC, 0x0AD, 0x0AE, 0x0AF, 0x0B1, 0x0B2, 0x0B3, 0x0B4, 0x0B5, 0x0B6,0x0B7, },
- //.
- //.
- {0x17D, 0x17E, 0x17F, 0x180, 0x181, 0x182, 0x183, 0x184, 0x185, 0x186, 0x187, 0x188, 0x189, 0x18A, 0x18B, 0x18C, 0x18D, 0x18E, 0x18F,0x190, },//20
- };
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:
- #define FREQ 1000
- TIM_OCInitTypeDef TIM_OCStruct;
- void InitPWM()
- {
- //TIM2 CH2 PA5
- GPIO_InitTypeDef GPIO_InitStruct;
- TIM_TimeBaseInitTypeDef TIM_BaseStruct;
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_TIM2);
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
- GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- TIM_BaseStruct.TIM_Prescaler = 0;
- TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
- TIM_BaseStruct.TIM_Period = ((SystemCoreClock / FREQ)) - 1;
- TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
- TIM_BaseStruct.TIM_RepetitionCounter = 0;
- TIM_TimeBaseInit(TIM3, &TIM_BaseStruct);
- TIM_Cmd(TIM2, ENABLE);
- TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
- TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
- TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
- }
- void BUZZER_ON(uint16_t value)
- {
- TIM_OCStruct.TIM_Pulse = value;
- TIM_OC2Init(TIM2, &TIM_OCStruct);
- TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
- }
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.
- void WYSLIJ_DANE(BUTTON_NUMBER_t button)
- {
- switch(button)
- {
- case Button_0:
- USART_Send_Data("PRZYCISK 0");
- KeyboardWrite(0x00, KEY_0, 0x00, 0x00, 0x00, 0x00, 0x00);
- BUZZER_ON((SystemCoreClock/FREQ/2 - 1);
- DIODE_CHECK();
- break;
- //.
- //.
- //.
- }
- }
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.