Kako z Arduinom simulirati tipkovnico

Naučili se bomo spremeniti Arduino Uno v tipkovnico, ki bo natipkala, kar ji bomo rekli in kadar ji bomo rekli. Tule se bomo naučili gole osnove: prišli bomo do tega, da bo Arduino, ko ga bomo priključili na USB, odtipkal f, r, i. Vendar bi moralo to zadoščati za odskočno desko. Senzorje in podobno šaro že znamo priključiti; in če znamo odtipkati f, r, i, znamo tudi karkoli drugega.

(Originalni) Arduino Uno ima dva mikrokontrolerja

Arduino je bolj ali manj en sam čip. Danes je to navadno Atmega328 podjetja Atmel; starejši so imeli Atmega256. Atmega328 je mikrokontroler s procesorjem, pomnilnikom (Flash, EEPROM, SRAM), analogno-digitalnimi pretvorniki ... Arduino priskrbi kristal (za uro) in, predvsem udobnejše načine za prenos programa na Atmega328. Okrog tega se bomo vrteli danes.

  1. Obstajajo Arduini, ki nimajo ničesar, kar bi olajšalo prenos programa. Primer je Arduino Mini. Atmega 328 v osnovi programiramo prek serijskega vmesnika ISP. Arduino Mini preprosto pine TX in RX s čipa Atmega328 do roba ploščice, zraven pinov pa piše znajdi se. Ker današnji računalniki nimajo več izhoda RS232 (poleg tega pa ima RS232 previsoko napetost, od 3 do 15 voltov), za programiranje na ta način potrebujemo kak primeren vmesnik. Najbrž kaj podobnega, kot ima Arduino Uno že na ploščici. Nekaj malega o tem, kako programirati Arduino Mini, bomo povedali na koncu.

  2. Na večini neminimalističnih Arduinov imamo poleg "navadnih" pinov -- D0-13, A0-A5, GND, Vcc in tako naprej -- tudi samostojni kupček 2x3 pinov, zraven katerih piše ICSP, In-Circuit Serial Programming. Razporeda 2x3 in 2x5 uporablja Atmel za svoje čipe serije AVR, kamor spada vse, povezano z Arduinom. Ampak tudi programiranje prek ICSP je nerodno; če imam na računalniku samo USB, ne bom čutil razlike med, hm, okornim RS232 in, hm, naprednejšim ISP.

  3. Arduini, namenjeni smrtnikom, imajo zato vmesnik do USB. Originalni Arduino Uno ima še en mikrokontroler, Atmega16u2, ceneni Arduini Uno pa imajo CH340. Za razliko od Atmega16u2, ki je splošen mikrokontroler, je CH340 namenjen izključno pretvorbi USB v RS232 (in še par drugih). To je cenejše (kar je dobro), ne moremo pa ga programirati (kar je slabo ;).

Programiranje Atmega16u2

Tokrat bomo torej delali z Arduinom Uno, ki ima Atmega16u2. Lahko gre za original, imajo pa ga tudi nekateri dražji kloni in ponaredki.

Kaj dela Atmega16u2

Ko programiramo Arduina Uno, Atmega16u2 dobi program prek USB in ga spravi na Atmega328 prek njegovih pinov TX in RX. Gre tudi v drugo smer: ko s Serial.print in podobnimi ukazi izpisujemo z Arduina na računalnik, Atmega16u2 prejema podatke prek teh dveh pinov in jih pošilja prek USB. (Mimogrede, RX in DX sta v resnici (tudi) D0 in D1. Če v programu izpisujemo prek serijskega izhoda, sta tadva pina zato neuporabna kot izhoda ali vhoda.)

Atmega16u2 torej nadzira vso komunikacijo prek USB - v eno smer programiramo Atmega328, v drugo smer Atmega328 izpisuje prek USB, kar potem vidimo v "serijskem monitorju" v okolju Arduino IDE, ki preprosto izpisuje, vse kar pride po USB.

Če želimo, da bi Arduino prek USB počel kaj drugega - recimo to, da bi se delal, da je tipkovnica - bomo morali torej zamenjati program, ki teče na Atmega16u2.

Do programa pridemo pred DFU, Device Firmware Upgrade. USB naprave, ki to dopuščajo (od Arduina do, ja, celo iPhona!), lahko preklopimo v način DFU, v katerem prek USBja prejmejo nov program - ali pa samo pobrišejo trenutnega, nam omogočijo, da ga preberemo...

DFU Programmer

Da bomo na ta način programirali Arduina, potrebujemo ustrezen program.

  • Na Windows je to flip, ki ga proizvaja Atmel.
  • Na Linuxu (lahko tudi na Raspberry Pi) namestimo dfu-programmer tako, da v konzoli napišemo sudo apt-get dfu-programmer
  • Na macOS je podobno. Če imamo homebrew (in če imamo macOS in kaj programiramo, potem pač imamo homebrew), ga namestimo z brew install dfu-programmer.

Postavljanje Arduina v način DFU

Ko Arduina priključimo prek USB v računalnik, s tem dobi napajanje, požene se program, ki teče na Atmega16u2. Ta pove računalniku, da ima na tem USBju tak in tak Arduino. Nato čaka, da mu računalnik pošlje kakšen program, ki ga bo posredoval na Atmega328, ali da Atmega328 kaj izpiše na računalnik.

Če hočemo programirati Atmega16u2, ga moramo spraviti iz tega stanja. To naredimo tako, da ga resetiramo. Po resetu se ne bo več obnašal kot posrednik med USB in Atmega328, temveč bo uporabljal USB zase. Bo torej v načinu DFU.

Kako ga resetiramo?

Arduino Uno nima ene, temveč dve skupini 2x3 pinov. Ena je poleg večjega Atmega328, druga zraven malega Atmega16u2. (Če imamo zmerno smolo, zraven Atmega16u2 ni šestih pinov, temveč očiten prostor, kjer bi le-ti lahko bili, če bi se kdo lotil tam prispajkati headerje. Na srečo nas to danes ne bo motilo, saj bomo ICSP potrebovali le za resetiranje.)

Resetiramo ga tako, da sklenemo in spet spustimo pina RST in GND. To sta leva pina, tista dva, ki sta najbližja USBju. Sklenemo ju kar tako, da obnju pritisnemo izvijač, ključ, kovanec... Ko prekinemo stik Atmega16u2 čaka.

Arduino postane tipkovnica

Zdaj preberimo vsebino pomnilnika na Arduino tako, da v terminalu natipkamo

dfu-programmer atmega16u2 dump

Morda boste morali na začetek vrstice pisati še sudo, torej sudo dfu-programmer atmega16u2 dump (enako tudi za spodnje vrstice).

To izpiše vsebino pomnilnika flash. Ker jo želimo shraniti v datoteko (ker tega najbrž ne mislimo brati, ne), napišemo

dfu-programmer atmega16u2 dump > originalni-program.hex

Tako si lahko shranimo trenutni "privzeti" program, ki teče na Atmega16u2. Sicer pa ga imamo tudi na računalniku, znotraj direktorija programa Arduino; datoteki je ime Arduino-usbserial-atmega16u2-Uno-Rev3.hex (če gre za Arduino Uno), najbrž v direktoriju Java/hardware/arduino/avr/firmwares/atmegaxxu2/arduino-usbserial/. Sicer pa jo najdete tudi na spletu.

Zdaj pa, končno, spremenimo Arduino v tipkovnico. Najprej poberite datoteko Arduino-keyboard-0.3.hex.

Prenesemo jo na Atmega16u2:

dfu-programmer atmega16u2 erase
dfu-programmer atmega16u2 flash Arduino-keyboard-0.3.hex
dfu-programmer atmeta16u2 reset

Zdaj izključimo Arduina iz USBja in ga vključimo nazaj. Računalnik bo "opazil", da smo priključili tipkovnico.

Dosegli smo tole: ko priključimo Arduina, se izvede program, zapisan v Atmega16u2. Ta se računalniku predstavi kot tipkovnica. Tako kot običajno lahko tudi zdaj napišemo program, ki nekaj izpisuje s Serial.print; Atmega328 bo to izpisal na TX, Atmega16u2 bo to posredoval na USB ... le računalnik bo zdaj mislil, da gre za kode tipk, ki jih nekdo pritiska.

Arduino postane Arduino

Vse lepo in prav, samo ta tipkovnica je brez tipk in ne zna ničesar. Potrebno jo bo sprogramirati.

In zdaj imamo problem: če poskušamo tega Arduina programirati kot pač vsakega Arduina, se ne bo zgodilo nič, saj menda ne moremo programirati tipkovnice, ne?

Dokler ne znamo drugače, bomo naredili takole: Arduina bomo spremenili nazaj v Arduina. Spet resetiramo Atmega16u2 tako, da sklenemo pina RST in GND na njegovem ICSPju in nato napišemo

dfu-programmer atmega16u2 erase
dfu-programmer atmega16u2 flash originalni-program.hex
dfu-programmer atmeta16u2 reset

Ker bomo to počeli stalno, se nam splača te in gornje tri vrstice shraniti v datoteki v_arduino in v_tipkovnico (če imamo Linux ali macOs) ali v_arduino.bat in v_tipkovnico.bat (če imamo Windows).

Arduina izključimo iz USB in nazaj. Če bomo držali pesti, se bo ponovno zbudil kot Arduino.

Kaj sporoča tipkovnica računalniku

Preden začnemo programirati našo tipkovnico, moramo vedeti, kako se tipkovnica pravzaprav pogovarja z računalnikom. Pošilja mu poročila (report), ki povedo, katere tipke so pritisnjene. Program, ki smo ga naložili (in potem odložili) z Arduina, simulira tipkovnico, katere poročila so dolga osem bajtov.

Prvi bajt pove, ali so pritisnjeni Shift, Ctrl, Alt in podobne tipke. Sestavimo ga po bitih.

7 6  5 4 3  2 1 0
R Gui(?)  R Alt  R Shift  R Ctrl L Gui  L Alt  L Shift  L Ctrl

Drugi bajt je rezerviran in ga putimo pri miru.

Bajti od tretjega do osmega vsebujejo kode (vseh) trenutno pritisnjenih tipk. Navadno bo hkrati pritisnjena le ena (Shift, recimo, je v prvem bajtu). Shranili jo bomo v tretji bajt, ostale pa pustili na vrednosti 0.

Tabela ne vsebuje kakih kod ASCII temveč kode tipk. Najdemo jih v dokumentih standarda USB nekaj zanimivejših pa je tule:

koda tipka
4 - 29 tipke od a do z (npr. od kode ASCII velike črke odštejemo 61)
39 tipka 0 in )
30 - 38 tipke od 1 do 9 (koda ASCII minus 47) in ! @ # $ % ^ & * (
58 - 69 F1 - F12
koda tipka koda tipka koda tipka
40 Enter 50 # in ~ 74, 77 Home, End
41 Escape 51 ; in : 75, 78 Page up, down
42 Backspace 52 ' in " 76 Delete
43 Tab 53 ` in ~ 79, 80 Right, Left
44 Preslednica 54 , in < 81, 82 Up, Down
45 - in _ 55 . in > 120 Stop
46 = in + 56 / in ? 127 Mute
47 [' in{` 57 Caps lock 128, 129 Volume up, down
48 ] in }
49 \ in

Arduino odtipka niz

Naredimo tole: ko se Arduino-tipkovnica prižge, naj počaka dve sekundi in nato natipka fri.

unsigned char buf[8] = {0};

void tipka(unsigned char c) {
    buf[2] = c;
    Serial.write(buf, 8);
    buf[2] = 0;
    Serial.write(buf, 8);
}


void setup() {
    Serial.begin(9600);
    delay(2000);

    tipka('F' - 61);
    delay(200);
    tipka('R' - 61);
    delay(200);
    tipka('I' - 61);
}

void loop() {
}

Za osem bajtov, ki jih moramo poslati, smo rezervirali tabelo nepredznačenih bajtov, nastavljenih na vrednost 0, unsigned char buf[8] = {0};. Zapišemo jih s Serial.write(), ki prejme dva argumenta. Prvi bo (kazalec na) tabelo, drugi pa njena dolžina. V našem primeru torej Serial.write(buf, 8);.

Funkcija tipka pritisne in spusti tipko s podano kodo. Kodo tipke zapiše v drugi bajt, zapiše tabelo na USB, nato spremeni drugi bajt nazaj na 0, in spet zapiše blok, s čimer sporoči, da je bila tipka spuščena. Takšno tipkanje je seveda precej omejeno; če bi hoteli narediti kaj zahtevnejšega, si bomo napisali boljšo funkcijo. A za tole vajo bo zadoščalo.

V funkciji setup pripravimo serijski izhod, kot da bi delali z "običajnim" Arduinom. Po dveh sekundah čakanja odtipkamo f, r, in i. Kode črk za 61 manjše od kod ASCII, zato od vsake črke odštejemo 61.

Arduino spet postane tipkovnica

Gornji program prenesemo na Arduina. Nato ponovimo vajo s spreminjanjem v tipkovnico: staknemo RST in GND na ISCPju in hipnotiziramo Arduino, da bo mislil, da je tipkovnica.

dfu-programmer atmega16u2 erase
dfu-programmer atmega16u2 flash Arduino-keyboard-0.3.hex
dfu-programmer atmeta16u2 reset

Iztaknemo iz USB, vtaknemo nazaj in če je vse v redu, se bo izpisalo fri.

Pa če se zmotimo?

Takšna tipkovnica ni preveč uporabna. Bolj zanimivo je na Arduino obesiti kakšne tipke ali senzorje. Vzamemo lahko, recimo, pospeškomere in igramo igro tako, da ob nagibih levo, desno, naprej in nazaj pritiska ustrezna tipke. Ali pa RFID čitalnik, pa bo Arduino odtipkal naše geslo, ko čitalniku približamo kartico.

Programiranje takšne tipkovnice je lahko kar zoprno. Če v programu naredimo napako, moramo Arduina spremeniti nazaj v Arduina, popraviti program, ga spremeniti nazaj v tipkovnico, preskusiti program... Splača se nam delati tako, da program najprej v resnici izpisuje katero tipko bi pritisnil, če bi bil tipkovnica; kličemo torej običajni Serial.print in opazujemo izhod, kot smo navajeni. Šele ko vse lepo deluje, ga spremenimo v tipkovnico.

Je Arduino Uno lahko tudi miška?

Lahko. Tule je blog, v katerem je to in še kaj drugega. Nisem pa našel datoteke, s katero bi bil oboje hkrati.

Takšnim hecom je namenjen drug Arduino, Leonardo. Ta ima le en mikrokontroler, Atmega32u4 in se že po naravi vede kot Arduino, tipkovnica in miška hkrati. Podoben je tudi SparkFunov Pro Micro.

Nekdo se je lotil pisanja programa, z katerim bi bil tudi Arduino Uno lahko hkrati tipkovnica, miška in Arduino, vendar projekta ni končal (nakopal mi je le nekaj preglavic, da sem spet spravil svojega Arduina v delujoče stanje). Če koga zanima: Hoodloader in Hoodloader2.

Leonardo zveni kot dobra ideja, vendar ni brez težav. Če pri programiranju kaj zamočimo in začne tipkati neumnosti, ga težko preprogramiramo: čim ga vključimo v računalnik in poskusimo prenesti program, nam bo že pokvaril program. (Trik: v program vstavimo komentar in postavimo kurzor vanj. Kar bo Arduino tipkal, bo tako del komentarja, torej ne bo ničesar pokvaril.) Z Unom teh težav ni: če resetiramo Atmega16u2 (kar sicer naredimo preden ga preprogramiramo), bo mirno čakal, ne da bi karkoli tipkal.

Zadnja sprememba: sobota, 17. junij 2017, 16.22