De la zero: Cum am construit tastatura de vis a dezvoltatorului
Publicat: 2022-03-11Lucrând într-o zi din august 2007, nu m-am putut abține să nu-mi dau seama că tastatura obișnuită a computerului nu mi-a servit cât mai mult posibil. A trebuit să-mi mișc mâinile între diferitele blocuri ale tastaturii în mod excesiv, de sute, dacă nu de mii de ori pe zi, iar mâinile mele erau incomod aproape una de cealaltă. Trebuie să existe o cale mai bună, m-am gândit.
Această realizare a fost urmată de un sentiment copleșitor de entuziasm în timp ce mă gândeam să personalizez cea mai bună tastatură pentru dezvoltatori - și, mai târziu, să realizez că, în calitate de dezvoltator de software încorporat independent, nu aveam nicio idee despre hardware.
La acea vreme, eram destul de ocupat cu alte proiecte, dar nu a trecut o zi în care să nu mă gândesc la construirea tastaturii hackerilor. Curând am început să-mi dedic timpul liber pentru a lucra la proiect. Am reușit să învăț un set cu totul nou de abilități, să-l conving pe un prieten de-al meu, Andras Volgyi, extraordinar inginer mecanic, să se alăture proiectului, să adune câțiva oameni cheie și să dedic suficient timp creării de prototipuri funcționale. În zilele noastre, Ultimate Hacking Keyboard este o realitate. Facem progrese zilnice, iar lansarea campaniei noastre de crowdfunding este la îndemână.
Trecerea de la un fundal software, neștiind nimic despre electronică, la proiectarea și construirea unui dispozitiv hardware puternic, comercializabil, este o experiență interesantă și fascinantă. În acest articol, voi descrie designul modului în care funcționează această capodopera electronică. O înțelegere de bază a diagramelor de circuite electronice vă poate ajuta să urmați.
Cum faci o tastatură?
După ce mi-am dedicat mii de ore din viață acestui subiect, este o provocare uriașă pentru mine să dau un răspuns scurt, dar există o modalitate interesantă de a răspunde la această întrebare. Ce se întâmplă dacă începem cu ceva simplu, cum ar fi o placă Arduino, și o construim treptat pentru a deveni Ultimate Hacking Keyboard? Ar trebui să fie nu numai mai digerabilă, ci și extrem de educativă. Prin urmare, să înceapă călătoria noastră cu tutorialul pentru tastatură!
Pasul unu: O tastatură fără taste
Mai întâi, să facem o tastatură USB care emite caracterul x
o dată pe secundă. Placa de dezvoltare Arduino Micro este un candidat ideal în acest scop, deoarece dispune de microcontrolerul ATmega32U4 - un microcrontroller AVR și același procesor care este creierul UHK.
Când vine vorba de microcontrolere AVR compatibile cu USB, Lightweight USB Framework for AVRs (LUFA) este biblioteca de alegere. Le permite acestor procesoare să devină creierul imprimantelor, dispozitivelor MIDI, tastaturi sau aproape orice alt tip de dispozitiv USB.
Când conectați un dispozitiv la portul USB, dispozitivul trebuie să transfere niște structuri speciale de date numite descriptori USB. Acești descriptori îi spun computerului gazdă tipul și proprietățile dispozitivului conectat și sunt reprezentați printr-o structură arborescentă. Pentru a face lucrurile și mai complexe, un dispozitiv poate implementa nu numai una, ci și mai multe funcții. Să vedem structura descriptorilor UHK:
- Descriptor de dispozitiv
- Descriptor de configurare
- Descriptor de interfață 0: GenericHID
- Descriptorul punctului final
- Descriptor de interfață 1: Tastatură
- Descriptorul punctului final
- Descriptor de interfață 2: Mouse
- Descriptorul punctului final
- Descriptor de interfață 0: GenericHID
- Descriptor de configurare
Cele mai multe tastaturi standard expun doar un singur descriptor de interfață de tastatură, ceea ce are sens. Cu toate acestea, ca tastatură de programare personalizată, UHK expune și un descriptor de interfață de mouse, deoarece utilizatorul poate programa taste arbitrare ale tastaturii pentru a controla indicatorul mouse-ului, astfel încât tastatura să poată fi folosită ca mouse. Interfața GenericHID servește ca canal de comunicare, pentru a face schimb de informații de configurare pentru toate caracteristicile speciale ale tastaturii. Puteți vedea implementarea completă a dispozitivului și a descriptorilor de configurare ai UHK în LUFA aici.
Acum că am creat descriptorii, este timpul să trimitem caracterul x
în fiecare secundă.
uint8_t isSecondElapsed = 0; int main(void) { while (1) { _delay_us(1000); isSecondElapsed = 1; } } bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo, uint8_t* const ReportID, const uint8_t ReportType, void* ReportData, uint16_t* const ReportSize) { USB_KeyboardReport_Data_t* KeyboardReport = (USB_KeyboardReport_Data_t*)ReportData; if (isSecondElapsed) { KeyboardReport->KeyCode[0] = HID_KEYBOARD_SC_X; isSecondElapsed = 0; } *ReportSize = sizeof(USB_KeyboardReport_Data_t); return false; }
USB este un protocol interogat, ceea ce înseamnă că computerul gazdă interogează dispozitivul la un interval regulat (de obicei de 125 de ori pe secundă) pentru a afla dacă există date noi de trimis. Callback-ul relevant este funcția CALLBACK_HID_Device_CreateHIDReport()
, care în acest caz trimite codul de scanare al caracterului x
către gazdă ori de câte ori variabila isSecondElapsed
conține 1
. isSecondElapsed
este setat la 1
din bucla principală pe secundă și setat la 0
din callback.
Pasul doi: O tastatură cu patru taste
În acest moment, tastatura noastră nu este foarte utilă. Ar fi bine dacă am putea să scriem pe el. Dar pentru asta avem nevoie de taste, iar tastele trebuie aranjate într-o matrice de tastatură. O tastatură de dimensiune completă cu 104 taste ar putea avea 18 rânduri și 6 coloane, dar pur și simplu vom avea o matrice umilă de tastatură 2x2 pentru pornire. Aceasta este schema:
Și așa arată pe o placă:
Presupunând că ROW1
este conectat la PINA0
, ROW2
la PINA1
, COL1
la PORTB0
și COL2
la PORTB1
, iată cum arată codul de scanare:
/* A single pin of the microcontroller to which a row or column is connected. */ typedef struct { volatile uint8_t *Direction; volatile uint8_t *Name; uint8_t Number; } Pin_t; /* This part of the key matrix is stored in the Flash to save SRAM space. */ typedef struct { const uint8_t ColNum; const uint8_t RowNum; const Pin_t *ColPorts; const Pin_t *RowPins; } KeyMatrixInfo_t; /* This Part of the key matrix is stored in the SRAM. */ typedef struct { const __flash KeyMatrixInfo_t *Info; uint8_t *Matrix; } KeyMatrix_t; const __flash KeyMatrixInfo_t KeyMatrix = { .ColNum = 2, .RowNum = 2, .RowPins = (Pin_t[]) { { .Direction=&DDRA, .Name=&PINA, .Number=PINA0 }, { .Direction=&DDRA, .Name=&PINA, .Number=PINA1 } }, .ColPorts = (Pin_t[]) { { .Direction=&DDRB, .Name=&PORTB, .Number=PORTB0 }, { .Direction=&DDRB, .Name=&PORTB, .Number=PORTB1 }, } }; void KeyMatrix_Scan(KeyMatrix_t *KeyMatrix) { for (uint8_t Col=0; Col<KeyMatrix->Info->ColNum; Col++) { const Pin_t *ColPort = KeyMatrix->Info->ColPorts + Col; for (uint8_t Row=0; Row<KeyMatrix->Info->RowNum; Row++) { const Pin_t *RowPin = KeyMatrix->Info->RowPins + Row; uint8_t IsKeyPressed = *RowPin->Name & 1<<RowPin->Number; KeyMatrix_SetElement(KeyMatrix, Row, Col, IsKeyPressed); } } }
Codul scanează câte o coloană și în acea coloană citește stările comutatoarelor individuale cu cheie. Starea comutatoarelor cu cheie este apoi salvată într-o matrice. În cadrul funcției noastre anterioare CALLBACK_HID_Device_CreateHIDReport()
, codurile de scanare relevante vor fi apoi trimise în funcție de starea acelei matrice.
Pasul trei: o tastatură cu două jumătăți
Până acum, am creat începuturile unei tastaturi normale. Dar în acest tutorial pentru tastatură ne propunem o ergonomie avansată și, având în vedere că oamenii au două mâini, mai bine adăugăm o altă jumătate de tastatură la amestec.
Cealaltă jumătate va prezenta o altă matrice de tastatură, care funcționează în același mod ca cea anterioară. Lucrul nou interesant este comunicarea dintre jumătățile de tastatură. Cele mai populare trei protocoale pentru interconectarea dispozitivelor electronice sunt SPI, I 2 C și UART. În scopuri practice, vom folosi UART în acest caz.
Comunicarea bidirecțională circulă prin RX spre dreapta și prin TX spre stânga conform diagramei de mai sus. VCC și GND sunt necesare pentru a transfera puterea. UART are nevoie de egali să folosească aceeași viteză de transmisie, același număr de biți de date și același număr de biți de oprire. Odată ce transceiver-ul UART al ambilor colegi este configurat, comunicarea poate începe să curgă.
Pentru moment, jumătatea din stânga a tastaturii trimite mesaje de un octet către jumătatea tastaturii din dreapta prin UART, reprezentând evenimente de apăsare a tastei sau de eliberare a tastei. Jumătatea din dreapta a tastaturii procesează aceste mesaje și manipulează în mod corespunzător starea matricei matricei complete de tastatură din memorie. Acesta este modul în care jumătate din stânga tastaturii trimite mesaje:
USART_SendByte(IsKeyPressed<<7 | Row*COLS_NUM + Col);
Codul pentru jumătatea dreaptă a tastaturii pentru a primi mesajul arată astfel:
void KeyboardRxCallback(void) { uint8_t Event = USART_ReceiveByte(); if (!MessageBuffer_IsFull(&KeyStateBuffer)) { MessageBuffer_Insert(&KeyStateBuffer, Event); } }
Managerul de întrerupere KeyboardRxCallback()
este declanșat ori de câte ori un octet este primit prin UART. Având în vedere că manipulatorii de întreruperi ar trebui să se execute cât mai repede posibil, mesajul primit este introdus într-un buffer inel pentru procesare ulterioară. Bufferul de inel este în cele din urmă procesat din bucla principală, iar matricea tastaturii va fi actualizată în funcție de mesaj.

Cele de mai sus este cea mai simplă modalitate de a face acest lucru, dar protocolul final va fi ceva mai complex. Mesajele pe mai mulți octeți vor trebui gestionate, iar mesajele individuale vor trebui verificate pentru integritate prin utilizarea sumelor de control CRC-CCITT.
În acest moment, prototipul nostru de placa de laborator arată destul de impresionant:
Pasul patru: Faceți cunoștință cu afișajul LED
Unul dintre obiectivele noastre cu UHK a fost acela de a permite utilizatorului să definească mai multe hărți de tastatură specifice aplicației pentru a crește și mai mult productivitatea. Utilizatorul are nevoie de o modalitate de a fi conștient de harta tastaturii care este utilizată, așa că un afișaj LED integrat este încorporat în tastatură. Iată un prototip de afișaj cu toate LED-urile aprinse:
Afișajul LED este implementat de o matrice LED 8x6:
Fiecare două rânduri de simboluri cu LED-uri de culoare roșie reprezintă segmentele unuia dintre afișajele LED cu 14 segmente. Simbolurile LED albe reprezintă cei trei indicatori de stare suplimentari.
Pentru a conduce curentul printr-un LED și a-l aprinde, coloana corespunzătoare este setată la tensiune înaltă, iar rândul corespunzător la tensiune joasă. O consecință interesantă a acestui sistem este că, în orice moment, o singură coloană poate fi activată (toate LED-urile de pe acea coloană care ar trebui aprinse au rândurile corespunzătoare setate la tensiune joasă), în timp ce restul coloanelor sunt dezactivate. . S-ar putea crede că acest sistem nu poate funcționa pentru a utiliza setul complet de LED-uri, dar, în realitate, coloanele și rândurile sunt actualizate atât de repede încât nicio pâlpâire nu poate fi văzută de ochiul uman.
Matricea LED este condusă de două circuite integrate (IC), unul care conduce rândurile sale, iar celălalt conducând coloanele. Sursa IC care conduce coloanele este driverul LED PCA9634 I2C:
Circuitul integrat al receptorului cu matrice LED care conduce rândurile este registrul de schimbare a puterii TPIC6C595:
Să vedem codul relevant:
uint8_t LedStates[LED_MATRIX_ROWS_NUM]; void LedMatrix_UpdateNextRow(bool IsKeyboardColEnabled) { TPIC6C595_Transmit(LedStates[ActiveLedMatrixRow]); PCA9634_Transmit(1 << ActiveLedMatrixRow); if (++ActiveLedMatrixRow == LED_MATRIX_ROWS_NUM) { ActiveLedMatrixRow = 0; } }
LedMatrix_UpdateNextRow()
este apelat aproximativ la fiecare milisecundă, actualizând un rând al matricei LED. Matricea LedStates
stochează starea LED-urilor individuale, este actualizată prin UART pe baza mesajelor provenite din jumătatea dreaptă a tastaturii, aproape în același mod ca și în cazul evenimentului de apăsare a tastei/eliberare a tastei.
Imaginea de ansamblu
Până acum am construit treptat toate componentele necesare pentru tastatura noastră personalizată pentru hacker și este timpul să vedem imaginea de ansamblu. Interiorul tastaturii este ca o mini rețea de calculatoare: o mulțime de noduri interconectate. Diferența este că distanța dintre noduri nu se măsoară în metri sau kilometri, ci în centimetri, iar nodurile nu sunt computere cu drepturi depline, ci circuite integrate minuscule.
S-au spus multe până acum despre detaliile tastaturii dezvoltatorului la nivelul dispozitivului, dar nu atât despre UHK Agent, software-ul gazdă. Motivul este că, spre deosebire de hardware și firmware, Agent este foarte rudimentar în acest moment. Cu toate acestea, este decisă arhitectura de nivel înalt a Agentului, pe care aș dori să o împărtășesc.
UHK Agent este aplicația de configurare prin care tastatura poate fi personalizată pentru a se potrivi nevoilor utilizatorului. În ciuda faptului că este un client bogat, Agent folosește tehnologii web și rulează pe platforma node-webkit.
Agentul comunică cu tastatura utilizând biblioteca nod-usb prin trimiterea de solicitări speciale de control USB specifice dispozitivului și procesarea rezultatelor acestora. Utilizează Express.js pentru a expune un API REST pentru consum de aplicații terțe. De asemenea, folosește Angular.js pentru a oferi o interfață de utilizator îngrijită.
var enumerationModes = { 'keyboard' : 0, 'bootloader-right' : 1, 'bootloader-left' : 2 }; function sendReenumerateCommand(enumerationMode, callback) { var AGENT_COMMAND_REENUMERATE = 0; sendAgentCommand(AGENT_COMMAND_REENUMERATE, enumerationMode, callback); } function sendAgentCommand(command, arg, callback) { setReport(new Buffer([command, arg]), callback); } function setReport(message, callback) { device.controlTransfer( 0x21, // bmRequestType (constant for this control request) 0x09, // bmRequest (constant for this control request) 0, // wValue (MSB is report type, LSB is report number) interfaceNumber, // wIndex (interface number) message, // message to be sent callback ); }
Fiecare comandă are un identificator de 8 biți și un set de argumente specifice comenzii. În prezent, este implementată doar comanda de re-enumerare. sendReenumerateCommand()
face ca dispozitivul să fie reenumerat ca bootloader din stânga sau bootloader din dreapta, pentru actualizarea firmware-ului sau ca dispozitiv cu tastatură.
S-ar putea să nu aibă nicio idee despre caracteristicile avansate care pot fi realizate de acest software, așa că voi numi câteva: Agentul va putea vizualiza uzura cheilor individuale și va notifica utilizatorul despre speranța lor de viață, astfel încât utilizatorul să poată cumpărați câteva întrerupătoare noi pentru reparația iminentă. Agentul va oferi, de asemenea, o interfață de utilizator pentru configurarea diferitelor hărți de taste și straturi ale tastaturii hackerului. Viteza și accelerația indicatorului mouse-ului ar putea fi, de asemenea, setate, împreună cu o mulțime de alte funcții uber. Cerul este limita.
Crearea Prototipului
Se lucrează mult la crearea de prototipuri personalizate de tastatură. În primul rând, trebuie finalizat designul mecanic care, este destul de complex în sine și implică piese din plastic proiectate la comandă, plăci din oțel inoxidabil tăiate cu laser, ghidaje din oțel frezat cu precizie și magneți de neodim care țin împreună cele două jumătăți de tastatură. Totul este proiectat în CAD înainte de a începe fabricarea.
Iată cum arată carcasa pentru tastatură imprimată 3D:
Pe baza designului mecanic și a schemei, placa de circuit imprimat trebuie proiectată. PCB-ul din dreapta arată astfel în KiCad:
Apoi, PCB-ul este fabricat și componentele montate pe suprafață trebuie lipite manual:
În cele din urmă, după fabricarea tuturor pieselor, inclusiv imprimarea 3D, lustruirea și vopsirea pieselor din plastic și asamblarea tuturor, ajungem la un prototip de tastatură de hacker care funcționează ca acesta:
Concluzie
Îmi place să compar tastaturile dezvoltatorilor cu instrumentele muzicienilor. Tastaturile sunt obiecte destul de intime dacă te gândești la asta. La urma urmei, le folosim toată ziua pentru a crea software-ul de mâine, personaj cu caracter.
Probabil din cauza celor de mai sus, consider că dezvoltarea Ultimate Hacking Keyboard este un privilegiu și, în ciuda tuturor greutăților, de cele mai multe ori a fost o călătorie foarte interesantă și o experiență de învățare incredibil de intensă.
Acesta este un subiect larg și nu am putut decât să zgârie suprafața aici. Speranța mea este că acest articol a fost foarte distractiv și plin de material interesant. Dacă aveți întrebări, vă rog să-mi spuneți în comentarii.
În cele din urmă, sunteți binevenit să vizitați https://ultimatehackingkeyboard.com pentru mai multe informații și să vă abonați acolo pentru a fi notificat despre lansarea campaniei noastre.