Naučili se bomo spremeniti Arduino Uno v tipkovnico, ki bo natipkala, kar ji bomo rekli in kadar ji bomo rekli. Tokratni blog bo spet zelo tehničen. Predpostavili bomo, da zna bralec programirati Arduina in programa zato ne bomo prav podrobno razlagali. Predvsem pa se bomo naučili nekaj o tem, kaj se dogaja, ko prenašamo program na Arduina.

# (Originalni) Arduino Uno ima dva mikrokontrolerja

Arduino je bolj ali manj en sam čip. Danes je to navadno Atmega328, starejši pa so imeli Atmega256. Atmega328 je v bistvu cel računalnik s procesorjem, pomnilnikom (tremi različnimi, pravzaprav), pretvorniki, ki berejo in pišejo na digitalne in analogne pine … Poleg Atmega328 je na Arduino le še priskrbi kristal (manj tehničnim tipom bi ga predstavili kot “metronom”, ki daje Arduinu ritem) 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. Programiramo ga lahko le prek posebnega vmesnika, ki ga priključimo na pina D0 in D1. Drugo ime zanju je zato tudi TX in RX, kar izhaja iz angleških besed transmit in receive. (Vam je že kdo povedal, da na pina D0 in D1 ni pametno priklapljati ničesar? Če nanju damo, na primer, diodi, to moti prenašanje programa – tudi na Arduinu Uno.)

  2. Kdor je že kdaj delal z Arduinom, je poleg “navadnih” pinov — D0-13, A0-A5, GND, Vcc in tako naprej — opazil tudi samostojni kupček 2×3 pinov, zraven katerih piše ICSP, In-Circuit Serial Programming. Namenjeni so tudi nalaganju programov, vendar nam jih navadno ni potrebno uporabljati. Danes se jih bomo nekoliko dotaknili (zelo dobesedno), vendar jih ne bomo uporabljali za programiranje, saj bi za to prav tako potrebovali poseben vmesnik.

  3. Arduine za našo rabo je mogoče programirati prek USB. Za to imajo dodatni čip, ki služi kot vmesnik, ki bere program z USBja in ga zapisuje na AtMega328, ko program že teče, pa skrbi za komunikacijo med računalikom in Arduinom. Na originalnih Arduinih Uno je ta “vmesni” čip mikrokontroler, Atmega16u2, ceneni Arduini Uno pa imajo cenejše čipe, na primer CH340. CH340 je narejen izključno za takšne namene, Atmega16u2 pa je splošen mikrokontroler, ki ga lahko pripravimo do tega, da počne kaj drugega kot tisto, zaradi česar je na Arduinu.

# 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. 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.

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.

# 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 takoj sporoči 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 “čakajočega” 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: kar pride na USB, bo namenjeno njemu na Atmega328. Bolj učeno: Atmega16u2 bo v načinu DFU.

Kako ga resetiramo?

Arduino Uno nima ene, temveč dve skupini 2×3 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. In čaka. In čaka.

# Arduino postane tipkovnica

Spodnje velja za macOS in Linux. Na Windowsih poženemo program flip in se znajdemo v uporabniškem vmesniku.

Najprej bomo prebrali program, ki se trenutno nahaja na Atmega16u2. To storimo 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 na Atmega16u2. 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. Atmega16u2 bo s tem prešel iz načina DFU in začel izvajati program, ki smo ga naložili nanj. Čim bo priključil napajanje, bo računalniku sporočil … da smo nanj priključili — tipkovnico!

Še enkrat: ko priključimo Arduina, se izvede program, zapisan v Atmega16u2. Ta, ki smo ga pravkar naložili, 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, kot običajno, poslal čipu Atmega16u2, ta pa bo to posredoval na USB … le, da bo računalnik zdaj mislil, da nekdo nekaj tipka.

# 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č. Računalnik (oziroma Arduno IDE) ne bo zaznal, da je nanj priključen kak Arduino. Zaznal bo le dodatno tipkovnico, te pa pač ne moremo programirati, ne?

Arduina bomo morali spremeniti 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 pustimo 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.