Toată lumea știe ce este o tastatură și pentru ce este proiectată, dar nu toată lumea știe ce și cum se întâmplă când apăsați o cheie.
În acest articol, voi vorbi despre lucrul la nivel scăzut cu tastatura și vom da un exemplu de implementare a unui handler de întrerupere a tastaturii simplu pentru modul real (driver).
Când apăsați orice tastă de pe tastatura electronică, acesta generează un cod de scanare pentru cheile de la 1 la 6 octeți în lungime, care pot fi citite prin citirea portului I / O 0x60. Codul de scanare este un număr unic care identifică în mod unic o cheie presată, dar nu un cod ASCII. Codurile de scanare sunt de două tipuri: atunci când este apăsată o tastă, se generează așa-numitul cod de marcă și, când este eliberat, codul Break. Ele diferă numai prin faptul că în codul de pauză cel mai mare bit al fiecărui octet este setat la unul. Codurile de scanare sunt împărțite în grupuri: cod obișnuit, extins, suplimentar și codul cheie Pauză. Codul obișnuit de scanare constă dintr-un octet, cel extins constă din 2 octeți, primul dintre care este 0xE0. Lungimea codului adițional este de la 2 la 4 octeți. De asemenea, începe cu 0xE0. Foarte puternic din șosea Pauza este scos - aceasta este singura cheie, al cărei cod constă în 6 octeți și nu există cod de rupere pentru ea.
Tastatura poate funcționa în două moduri: prin interogare și prin întrerupere. Consider doar cel de-al doilea mod, deoarece primul este mai puțin eficient și implementarea lui este mai simplă. Când există date în memoria tampon de intrare a tastaturii, apare o solicitare de întrerupere IRQ1. Pe lângă codurile de scanare, tastatura poate transmite coduri speciale computerului atunci când execută comenzi (de exemplu, schimbarea stării LED-urilor).
În cursul articolului se presupune că tastatura lucrează în modul "implicit": este instalat un set de coduri de scanare Set2, majoritatea cheilor funcționează în modul auto-repetare și așa mai departe.
Tabelul codurilor de scanare ale tastaturii:
În cazul în care o privire mai atentă la tastele keycode Pauză (E1 1D 45 E1 9D C5), se poate observa că primii 3 octeți sunt Asigurați-cod, iar al doilea Break-cod. Adică, tastatura atunci când este presată generează imediat și codul de presare și stoarcere. În viitor, acest lucru ne va simplifica puțin viața.
Dacă apăsați și țineți apăsată tasta, după un timp specificat, numit timpul de autorepunere, codul de scanare al cheii deținute va fi repetat la rata de repetare. La autorepeat, codurile suplimentare de scanare conțin două octeți în loc de patru. De exemplu, o secvență de octeți când deținerea și eliberarea tastele Page Up (E0 2A E0 49) este după cum urmează: 2A E0 E0 E0 49 49 E0 49 49 E0 E0 E0 49 49 49 E0 E0 C9 E0 AA. Notă: atunci când cheia cu codul suplimentar este eliberată, secvența este inversată - semnul codului adițional (E0 AA) este la sfârșit, nu la început.
Următoarele poziții din tabelul de cod de scanare sunt ocupate și nu pot fi utilizate fără echivoc de alte chei (și, prin urmare, de noi):
- codurile de scanare obișnuite: [0x01 - 0x53], [0x57 - 0x58]
- extins: [0x1C - 0x1D], 0x35, 0x38, [0x5B - 0x5F], 0x63
- suplimentar: 0x37, [0x47 - 0x49], 0x4B, 0x4D, [0x4F - 0x53]
Toate codurile de scanare prelungite sunt precedate de un octet 0x0E, iar o secvență octet suplimentară este 0xE0, 0x2A, 0xE0.
Dacă ne rezolvăm problema pe frunte, atunci trebuie să verificăm fiecare octet primit de la tastatură. Și, din moment ce octeții dintr-un set de coduri comune se suprapun cu ultimul octet din celelalte seturi (au aceeași valoare), coduri suplimentare atunci când este eliberat sunt răsturnate perechi întrerup apare atât de multe ori, cât de mulți octeți în scanare-cod, există încă și Pauză, apoi a determina ce pentru ca cheia a fost apăsată, nu devine foarte simplă. În cele din urmă, toate aceste aspecte vor duce la implementarea cu o grămadă de ramuri indigestibile.
Sincer, nici nu-mi pot imagina ce au fost ghidați dezvoltatorii, creând astfel o mizerie. În acest articol, mă voi concentra în mod special asupra modului în care toată această rușine duce la un aspect mai simplu și mai frumos.
Pentru a informa că codul de scanare este extins sau opțional, se folosesc valorile 0xE0, 0x2A / 0xAA și 0xE1 pentru Pauză. Acest fapt indică în mod clar faptul că aceste valori nu sunt, de asemenea, utilizate de niciunul dintre chei - altfel ar fi imposibil să se determine tasta apăsată / presată.
Introducem conceptul unei chei virtuale - acesta este numărul care determină funcția cheii. De exemplu: Ctrl este executat sub forma a două chei fizice, dar codul lor virtual este același.
Definițiile mele de chei virtuale sunt puțin diferite de cele utilizate în Windows, deși sunt foarte asemănătoare cu acestea.
Vom crea un tabel pentru traducerea codurilor de scanare în chei virtuale, constând din 256 elemente cu câte 2 octeți fiecare. În primul byte, vom stoca codul cheii virtuale în sine, iar în al doilea - unele atribute.
Dacă toate codurile constau dintr-un octet, apoi de cei șapte biți inferiori ai valorii acestui octet, ar fi posibilă identificarea unică a cheii. Cu toate acestea, pentru cele mai multe chei, această condiție este îndeplinită, iar transformarea poate fi redusă la următoarea regulă: resetarea MSB, iar valoarea obținută este utilizată pentru a indexa în tabelul de traducere pe care codul cheie virtuală dacă este apăsată sau stors.
Deoarece resetăm cifra cea mai înaltă, toate valorile din intervalul [128 - 255] al tabelului de traducere sunt gratuite pentru nevoile noastre. Codurile virtuale pentru cheile extensibile și suplimentare vor fi stocate în acest interval. Procedați după cum urmează: dacă codul de scanare este extins sau suplimentar (Pauză pentru moment este eliminat), apoi setați cel mai mic bit în ultimul octet la unul.
Totul este complicat de faptul că pentru o întrerupere avem doar un octet al codului de scanare și nu este posibil să instalați pur și simplu cel mai semnificativ bit al ultimului octet.
Să începem prin a privi cheile suplimentare. Toate încep cu un octet de 0xE0. Modificăm regula dată anterior: dacă codul virtual obținut din tabelul de traducere este zero, atunci codul virtual nu este gata și codul de scanare nu a fost încă acceptat pe deplin. Îndepărtând MSB de la 0xE0, vom obține o valoare de 0x60 (care nu este, de asemenea, utilizat aceeași cheie), care poate fi folosit ca un index în care este stocată o valoare de 0. Când următoarea procesare de întrerupere vom obține ultimul cod de scanare octet, și care ar trebui setați cea mai înaltă cifră. Cea mai ușoară modalitate de a face acest lucru este modificarea regulii de conversie. Reamintim că avem și un octet de atribut în tabel, care nu este încă implicat. Noua regula va arata astfel: de fiecare dată când o valoare în tabel se va stoca într-o variabilă, iar formarea indicelui va folosi orice categorie de bytes de atribute, care reflectă nevoia de a schimba MSB primit octet.
Acum determinăm ce categorie vom folosi și cum să o folosim pentru a obține indicele. Așa cum am spus, trebuie să instalăm cel mai semnificativ bit. Se dovedește totul foarte simplu și frumos: cel mai semnificativ bit de atribute, preluat dintr-o variabilă, este transferat la cel mai înalt bit al octetului recepționat.
Să aruncăm o privire la toate cele de mai sus cu exemplul cheii Ctrl Right (E0 1D):
Să numim variabila, care ar trebui să stocheze octetul codului virtual și octetul atributului, KeyInfo. Inițializați-l la 0. Primul octet primit este 0xE0. Null cel mai înalt și obțineți 0x60. Luăm cel mai semnificativ bit de octet atribut din variabila KeyInfo și îl transferăm pe cel mai înalt bit al indexului primit: (0xE0 0x7F) ((KeyInfo >> 8) 0x80). Ca rezultat, obținem indexul 0x60, conform căruia în tabelă va fi stocat codul virtual cu valoarea zero și octetul atribut cu cel mai mare set de biți: KeyInfo = 0x8000. Următorul octet din secvența 0x1D: KeyInfo = Tabel [(0x1D 0x7F) ((KeyInfo >> 8) 0x80)] == 0x9D - deci avem indexul final, conform căruia codul virtual va fi în tabel.
Mergem mai departe - în codurile suplimentare în coada de așteptare. Ele diferă numai prin faptul că primii 2 octeți au valoarea 0xE0, 0x2A. Cea mai ușoară cale de a le primi nu necesită nici măcar modificări în regulile pentru obținerea indexului. După ce a primit 0xE0 bytes 0x2A deveni un indice de 0xAA la care vom stoca o valoare de 0, indicând faptul că codul cheie virtuală nu este gata, și să modifice MSB următorului octet nu este necesară (ca și în cazul în care această secvență și nu a fost deloc). Următorii 2 octeți din secvență nu diferă de codul de scanare extins și totul este pregătit pentru recepția lor.
Toate cele de mai sus funcționează bine când primiți 2 octeți în loc de 4 atunci când repetați automat și inversați ordinea perechilor atunci când eliberați. Și când eliberarea primilor 2 octeți va da codul virtual al tastei presate, iar ultimele 2 (E0 AA) vor fi convertite în 0x0000 (ignorate).
Dar, după cum probabil ați uitat deja, am renunțat la cheia Pause (E1 1D 45) - haideți să o rezolvăm acum. Dacă urmați calea anterioară și cu indexul 0x61 (0xE1 0x7F) pentru a stoca valoarea 0x8000, atunci vom obține o coliziune asociată cu dreapta Ctrl '(E0 1D). Ce ar trebui să facem în acest caz? Ei bine, vom modifica din nou regula noastră obține indicele: o privire la reprezentarea binară a 0x1D - 0001 1101 Pentru a evita conflictele, este posibil, de exemplu, pentru a modifica al 5-lea, al 6-lea, sau ambele de descărcare de gestiune împreună, sau 7- a și a 1-a, dar din moment ce am început să modificăm cifrele mai vechi, să continuăm. Noua regulă pentru obținerea indexului este: (Valoare 0x7F) ((KeyInfo >> 8) 0xC0). Dar despre asta, torturile noastre cu cheia Pause nu au încetat. Conform indicelui va stoca valoarea 0x61 KeyInfo = 0x4000 (nu 0xC000, că nu a existat nici un conflict cu aplicații (E0 5D)), și să urmeze noua regulă, obținem: (1D 0x7F) ((KeyInfo >> 8) 0xC0) == 0x5D. Dar, ca și cod Pauza este format din trei octeți, atunci acest indice ar trebui să fie, de asemenea, un fel de modificator - și despre beneficiul: dacă luați 0x8000, atunci nicio coliziune nu va avea loc, iar indicele va fi 0xC5 codul virtuale Pauză.
În ciuda faptului că KeyInfo se modifică după fiecare octet primit, nu va exista probleme cu primirea următorului cod de scanare, deoarece doar octetul de atribute este folosit pentru a modifica octetul primit. Dacă se obține indicele final, atunci nu există biți de modificare în tabel (resetat la zero) și nu contează ce este în primul octet KeyInfo.
Tabelul de traducere:
Indicele 0x7A stochează o valoare zero pentru a ignora răspunsul de la tastatură atunci când comanda este recepționată, dar acesta nu este singurul răspuns posibil și toate acestea nu pot fi ignorate cu acest tabel. Dar această sarcină este rezolvată foarte simplu, de exemplu, atunci când trimiteți o comandă, puteți dezactiva total întreruperile și puteți lucra la sondaj sau puteți obține o variabilă care indică faptul că comanda a fost trimisă și nu este necesar să utilizați codurile de scanare.
Cu traducerea, ne-am dat seama complet, acum să folosim mai mulți biți de octeți de atribut. De exemplu, vom stoca semnul eliberării unei taste în a cincea cifră - când este apăsată, va fi setată la una. După cum am spus, codul virtual definește funcția cheii și nu locația fizică. Dar imaginați-vă că aceste informații ar putea fi necesare, prima cifră va fi un semn că cheia este corectă.
În exemplul de testare, este pus în aplicare manipulatorul de întrerupere a tastaturii (fără comenzi), care, atunci când apăsați sau eliberați o cheie, afișează informații despre ce sa întâmplat cu ce cheie.
P. S. critici constructive, corecții și completări sunt extrem de binevenite.