AVR: Klawiatura PS/2

|

To pierwszy artykuł z cyklu "Programowanie AVR". Mam nadzieję, że Wam się spodoba forma, w jakiej przedstawiam wiedzę z tej tematyki. Postaram się przedstawić rys teoretyczny każdego zagadnienia oraz podać przykładowe funkcje, praktycznie od razu gotowe do użycia.

Jak to podłączyć?


Współczesne klawiatury z interfejsem PS/2 mają wtyczkę MINI-DIN6, taką samą jak myszka. Starsze mają większą wtyczkę DIN5. W obu używane są tylko cztery przewody. Vcc to zasilanie napięciem 5V. Klawiatura pobiera prąd 100mA. GND to oczywiście masa. CLK to sygnał zegarowy nadawany przez klawiaturę tylko po wciśnięciu klawisza, a DATA to linia danych. WAŻNE - klawiatura ma wyjścia z otwartym kolektorem. Oznacza to, że linie CLK i DATA należy połączyć z zasilaniem poprzez rezystor 4,7k (typowo).



Jak to działa?


Transmisja danych z klawiatury przebiega podobnie do I2C, a nawet trochę prościej. Sygnał CLK zawsze jest generowany przez klawiaturę. Normalnie, kiedy nic nie jest nadawane, obie linie mają stan 1. Transmisja wygląda tak:
  • bit startu, zawsze 0
  • 8 bitów danych, od najmłodszego
  • bit parzystości, ustawiany gdy jest parzysta liczba jedynek
  • bit stopu, zawsze 1
Stan linii DATA danych odczytuje się zawsze po wykryciu zbocza opadającego na linii CLK.

Mikrokontrolery XMEGA

Istnieje także możliwość wysyłania danych z mikrokontrolera do klawiatury - ale nie będę jej tu opisywał. Odsyłam do linków na dole strony.

Klawiatura niestety nie wysyła znaków ASCII - zamiast tego przesyła scan-code, czyli coś w rodzaju nazwy klawisza. Niestety scan-code nie jest w żaden sposób powiązany z kodem ASCII. Ponadto, niektóre klawisze mają scan-cody dłuższe niż inne, w szczególności klawisze nietypowe, takie jak PAUSE, klawiatura numeryczna, czy klawisze Windowsa. Po puszczeniu klawisza, klawiatura wysyła dodatkowo kod 0xF0 i jeszcze raz kod klawisza. Sprawia to, że po odebraniu wszystkich bitów nadanych z klawiatury, trzeba uruchomić procedurę interpretacji na kod ASCII. Link do tablicy scan-codów znajdziecie na dole strony.

Przykłady


Przykładowe funkcje napisałem na ATmegę128. Linia CLK podłączona jest do pinu E4, który generuje przerwanie INT4. Procedura translacji scan-codów na ASCII uruchamia się w przerwaniu dopiero po odebraniu bitu stopu. Wynik tej konwersji jest zapisywany do zmiennej globalnej key.

Funkcja inicjalizująca jest bardzo prosta. Ustawia odpowiednie nóżki mikrokontrolera jako wejścia bez podciągnięcia (bo jest zewnętrze podciągnięcie w postaci rezystora 4,7k na każdej linii), zezwolenie na przerwania i nic więcej!

Elementy elektroniczne, zestawy prototypowe, Arduino

Funkcja ps2_waitkey zatrzymuje działanie programu, aż użytkownik wciśnie jakiś klawisz, który zwraca. Przydaje się do wpisywania tekstu.

#include <avr/interrupt.h>

// definicje połączeń
#define PS2_DATA_DDR DDRE
#define PS2_DATA_PORT PORTE
#define PS2_DATA_PIN PINE
#define PS2_DATA_BIT 3
#define PS2_CLK_DDR DDRE
#define PS2_CLK_PORT PORTE
#define PS2_CLK_PIN PINE
#define PS2_CLK_BIT 4

// zmienne globalne
volatile uint8_t key; // ostatnio wciśnięty klawisz na klawiaturze PS/2 (w ASCII)


uint8_t ps2_waitkey(void) {
// funkcja czeka, aż użytkownik wciśnie jakiś klawisz, tzn. aż zmienna
// key przybierze inną wartość niż 0

uint8_t bufor;
while(!(bufor=key));
key = 0;
return bufor;
}

void ps2_init(void) {
// Przykładowe połączenie do portu E: CLK do PE3 czyli INT4, DATA do PE4
// ATmega128

PS2_DATA_DDR &= ~(1<<PS2_DATA_BIT); // kierunek pinu DATA jako wejście
PS2_CLK_DDR &= ~(1<<PS2_CLK_BIT); // kierunek pinu CLK jako wejście
EIMSK = (1<<INT4); // włącznie przerwania od INT4
EICRB = (2<<ISC40); // zbocze malejące powoduje przerwanie
sei(); // globalne zezwolenie przerwań
}

ISR(INT4_vect) { // klawiatura PS/2
// przerwanie jest wywoływane każdym zboczem opadającym
// na sygnale zegarowym generowanym przez klawiaturę.

static uint8_t nrbitu;
static uint8_t dane;
static uint8_t shift;
static uint8_t poprzedni;
static uint8_t ignoruj;

// część 1 - odczytywanie 8-bitowego scan-code

if(nrbitu == 0) { // bit startu ignorujemy
nrbitu++;
return;
} else if(nrbitu >= 1 && nrbitu <=8) { // bity od 1 do 8 to dane klawiatury
dane = dane >> 1; // zaczynamy od bitu najmłodszego do najstarszego
if(PS2_DATA_PIN & (1<<PS2_DATA_BIT)) dane |= _BV(7);
nrbitu++;
return;
} else if(nrbitu == 9) { // bit parzystości ignorujemy
nrbitu++;
return;
} else if(nrbitu >= 10 ) { // bit stopu
nrbitu = 0;

// rozpoczęcie interpretacji scan-code
if(poprzedni != 0xF0 && (dane == 0x12 || dane == 0x59)) { // shift
shift = 1;
poprzedni = dane;
return;
}

if(ignoruj) {
--ignoruj;
dane = 0;
poprzedni = 0;
return;
}

if(poprzedni == 0xF0) { // ignorowanie zwolnionego klawisza
if(dane == 0x12 || dane == 0x59) { // shift
shift = 0;
}
poprzedni = 0;
return;
}

if(dane == 0xF0) { // sygnal zwolnienia klawisza
poprzedni = dane;
return;
}

if(dane == 0xE1) { // klawisz PAUSE
// klawiatura wysyła E1,14,77,E1,F0,14,F0,77
// rozpoznawane są tylko dwa pierwsze kody, reszta jest ignorowana
key = PAUSE;
ignoruj = 7;
poprzedni = 0;
dane = 0;
return;
}

if(poprzedni == 0xE0 ) { // strzałki, keypad
switch(dane) {
case 0x75: {key = UP; break;}
case 0x6b: {key = LEFT; break;}
case 0x72: {key = DOWN; break;}
case 0x74: {key = RIGHT; break;}
case 0x5a: {key = ENTER; break;}
case 0x4a: {key = '/'; return;} // keypad /
}
poprzedni = 0;
dane = 0;
return;
}

poprzedni = dane;

switch(dane) { // przetwarzanie scankodow klawiatury na ascii
case 0x5a: {key = 13; return;} // enter
case 0x76: {key = 27; return;} // escape
case 0x66: {key = 127; return;} // backspace

case 0x1c:
if(shift) {key = 'A'; return;}
else {key = 'a'; return;}
case 0x32:
if(shift) {key = 'B'; return;}
else {key = 'b'; return;}
case 0x21:
if(shift) {key = 'C'; return;}
else {key = 'c'; return;}
case 0x23:
if(shift) {key = 'D'; return;}
else {key = 'd'; return;}
case 0x24:
if(shift) {key = 'E'; return;}
else {key = 'e'; return;}
case 0x2b:
if(shift) {key = 'F'; return;}
else {key = 'f'; return;}
case 0x34:
if(shift) {key = 'G'; return;}
else {key = 'g'; return;}
case 0x33:
if(shift) {key = 'H'; return;}
else {key = 'h'; return;}
case 0x43:
if(shift) {key = 'I'; return;}
else {key = 'i'; return;}
case 0x3b:
if(shift) {key = 'J'; return;}
else {key = 'j'; return;}
case 0x42:
if(shift) {key = 'K'; return;}
else {key = 'k'; return;}
case 0x4b:
if(shift) {key = 'L'; return;}
else {key = 'l'; return;}
case 0x3a:
if(shift) {key = 'M'; return;}
else {key = 'm'; return;}
case 0x31:
if(shift) {key = 'N'; return;}
else {key = 'n'; return;}
case 0x44:
if(shift) {key = 'O'; return;}
else {key = 'o'; return;}
case 0x4d:
if(shift) {key = 'P'; return;}
else {key = 'p'; return;}
case 0x15:
if(shift) {key = 'Q'; return;}
else {key = 'q'; return;}
case 0x2d:
if(shift) {key = 'R'; return;}
else {key = 'r'; return;}
case 0x1b:
if(shift) {key = 'S'; return;}
else {key = 's'; return;}
case 0x2c:
if(shift) {key = 'T'; return;}
else {key = 't'; return;}
case 0x3c:
if(shift) {key = 'U'; return;}
else {key = 'u'; return;}
case 0x2a:
if(shift) {key = 'V'; return;}
else {key = 'v'; return;}
case 0x1d:
if(shift) {key = 'W'; return;}
else {key = 'w'; return;}
case 0x22:
if(shift) {key = 'X'; return;}
else {key = 'x'; return;}
case 0x35:
if(shift) {key = 'Y'; return;}
else {key = 'y'; return;}
case 0x1a:
if(shift) {key = 'Z'; return;}
else {key = 'z'; return;}

case 0x16:
if(shift) {key = '!'; return;}
else {key = '1'; return;}
case 0x1e:
if(shift) {key = '@'; return;}
else {key = '2'; return;}
case 0x26:
if(shift) {key = '#'; return;}
else {key = '3'; return;}
case 0x25:
if(shift) {key = '$'; return;}
else {key = '4'; return;}
case 0x2e:
if(shift) {key = '%'; return;}
else {key = '5'; return;}
case 0x36:
if(shift) {key = '^'; return;}
else {key = '6'; return;}
case 0x3d:
if(shift) {key = '&'; return;}
else {key = '7'; return;}
case 0x3e:
if(shift) {key = '*'; return;}
else {key = '8'; return;}
case 0x46:
if(shift) {key = '('; return;}
else {key = '9'; return;}
case 0x45:
if(shift) {key = ')'; return;}
else {key = '0'; return;}

case 0x4e:
if(shift) {key = '_'; return;}
else {key = '-'; return;}
case 0x55:
if(shift) {key = '+'; return;}
else {key = '='; return;}
case 0x5d:
if(shift) {key = '|'; return;}
else {key = 92; return;} // \ backslash
case 0x54:
if(shift) {key = '{'; return;}
else {key = '['; return;}
case 0x5b:
if(shift) {key = '}'; return;}
else {key = ']'; return;}
case 0x4c:
if(shift) {key = ':'; return;}
else {key = ';'; return;}
case 0x52:
if(shift) {key = '"'; return;}
else {key = 39; return;} // ' apostrof
case 0x41:
if(shift) {key = '<'; return;}
else {key = ','; return;}
case 0x49:
if(shift) {key = '>'; return;}
else {key = '.'; return;}
case 0x4a:
if(shift) {key = '?'; return;}
else {key = '/'; return;}
case 0x0e:
if(shift) {key = '~'; return;}
else {key = '`'; return;}

case 0x29: key = ' '; return;
case 0x70: key = '0'; return; // keypad
case 0x69: key = '1'; return; // keypad
case 0x72: key = '2'; return; // keypad
case 0x7a: key = '3'; return; // keypad
case 0x6b: key = '4'; return; // keypad
case 0x73: key = '5'; return; // keypad
case 0x74: key = '6'; return; // keypad
case 0x6c: key = '7'; return; // keypad
case 0x75: key = '8'; return; // keypad
case 0x7d: key = '9'; return; // keypad
case 0x71: key = '.'; return; // keypad
case 0x79: key = '+'; return; // keypad
case 0x7b: key = '-'; return; // keypad
case 0x7c: key = '*'; return; // keypad

case 0x05: key = 1; return; // F1
case 0x06: key = 2; return; // F2
case 0x04: key = 3; return; // F3
case 0x0c: key = 4; return; // F4
case 0x03: key = 5; return; // F5
case 0x0b: key = 6; return; // F6
case 0x83: key = 7; return; // F7
case 0x0a: key = 8; return; // F8
case 0x01: key = 9; return; // F9
case 0x09: key = 10; return; // F10
case 0x78: key = 11; return; // F11
case 0x07: key = 12; return; // F12

default: key = 0; return;
}
}
}


Ciekawe linki: