USB HID kommunikáció
A fejezet tartalma:Az
USB HID eszközosztály
Az USB eszközök funkció szerint elkülönülnek egymástól, ennek megfelelően az USB 2.0
specifikációja
különféle osztályokba sorolja az eszközöket. Ilyen osztályok pl. a
hangeszköz (Audio), képeszköz (Image), kommunikációs eszköz
(Communication), Nyomtató, Tárolóeszköz (Mass Storage), stb. Egy
speciális osztály a HID (Human Interface Device). A HID eszközök
eredetileg olyan periféria csoportot jelentenek, amelyek segítségével a
felhasználó képes vezérelni a számítógépet, illetve a számítógép
megjelenítheti a saját kimeneteit. Ilyen eszközök pl. a billentyűzet,
egér, játékvezérlő, különböző kijelzők. Ezek a hardverek nagyon
elterjedtek, ezért az USB szabványban előre definiált alosztályok
szerepelnek a fent említett eszközök számára. A HID kapcsolat főbb jellemzői:
- Minden tranzakció vezérlő vagy megszakítás típusú átvitelt használ.
- Tranzakciónként legfeljebb 64 bájt vihető át.
- A maximális átviteli sebesség 1 tranzakció/ms, ami
átszámítva legfeljebb 64 KB/s adatátviteli sebességet jelent. Ez ugyan
elmarad az ömlesztett (bulk) átvitelnél elvileg elérhető 1 MB/s körüli
sebességtől, de sok alkalmazáshoz bőven elegendő, s a robusztusabb
kapcsolat érdekében felvállalható.
- Csak egy kiviteli és egy beviteli végpont használható (a vezérlő csatorna mellett).
- A gazdagép periodikusan kérdezi le a HID eszközt.
Az általános (generic) HID eszközök alosztálya
Amikor egy USB eszközt csatlakoztatunk a számítógéphez, akkor egy "bemutatkozó párbeszéd" kezdődik, melynek során a gazdagép (ami esetünkben a PC) "kikérdezi" az USB eszközt (adatsebesség, eszközosztály, gyártó, VID/PID azonosítók stb.). Ezeket az információkat az eszköz firmware ún. eszközleíró táblába tárolja. A HID eszközök emellett még egy jelentésleíró táblát is tartalmaznak, amelyek a HID eszköz által küldött adatcsomagokról (az úgynevezett jelentésekről) nyújtanak tájékoztatást. A szabványos HID eszközöknél az ún. HID report descriptor (HID jelentés leíró tábla) megmondja, hogy az átvitt adatcsomag egyes bájtjai (vagy bitjei) mit jelentenek.A HID eszközosztály azonban nem korlátozódik kizárólag ilyen előre definiált, szabványos eszközökre, hanem teret nyújt az egyedileg készített eszközöknek is. A HID eszközosztály legáltalánosabb alosztálya az Általános HID Eszköz (Generic HID Device), amelynél nincs kötött protokoll. Ezeknél az eszközöknél a HID jelentésleíró tábla csak az átvitt adatok mennyiségét mondja meg, az adatok értelmezése azonban a mikrovezérlőbe töltött firmware-re és a PC-n futó alkalmazásra van bízva. Ebben az esetben tehát csak az átvitel mikéntje szabványos, az adatok felhasználása pedig gyártóspecifikus.
Az USBHID objektumosztály
Az USBHID objektumosztály a fentebb említett "Általános HID Eszközt" (Generic HID Device) valósítja meg. Az USBHID osztály tagfüggvényeivel üzeneteket küldhetünk és kaphatunk az USB buszon. Saját protokollt dolgozhatunk ki és eszerint kommunikálhatunk a mikrovezérlő és a számítógép között.A mikrovezérlő oldalon ehhez a kommunikációs típushoz az USBHID objektumosztályt kell használnunk. Az mbed API alapértelmezetten nem tartalmazza az USB kommunikációt kezelő programkönyvtárakat. Projektjeinkhez - eszközosztálytól függetlenül - az USBDevice programkönyvtárat kell importálnunk.
Az USBHID objektumosztály legfontosabb tagfüggvényeit az alábbi táblázatban foglaltuk össze.
Függvénynév |
Rövid leírás |
---|---|
USBHID (uint8_t
output_report_length=64, uint8_t input_report_length=64, uint16_t
vendor_id=0x1234, uint16_t product_id=0x0006, uint16_t
product_release=0x0001, bool connect=true) |
Konstruktor
függvény.
Példányosítja és inicializálja az USBHID objektumosztályt, megadja a
kimenő és bejövő adatblokk méretét, a VID/PID azonosítót, s a connect
paraméter értékétől függően kapcsolódik (vagy nem) |
read(HID_REPORT* report) |
Egy adatblokk fogadása blokkoló várakozással |
readNB(HID_REPORT* report) |
Egy adatblokk fogadása nem blokkoló módon |
send(HID_REPORT* report) |
Egy adatblokk küldése blokkoló várakozással |
sendNB(HID_REPORT* report) | Egy adatblokk küldése nem blokkoló módon |
Megjegyzések:
- A read, readNB, send, sendNB tagfüggvényeknél a logikai típusú visszatérési érték jelzi, hogy sikeres volt-e a küldés vagy fogadás.
- Az USB eszközöket a gyártó VID és a termék PID azonosító száma azonosítja. Az mbed USBHID alapértelmezett eszközazonosítója VID = 0x1234, PID = 0x0006. Ha ezt megváltoztatjuk (a konstruktor hívásánál), akkor a PC oldalon a kapcsolatot kezelő alkalmazásnál is módosítanunk kell a számokat!
- A konstruktor függvény első két paramétere a kimenő és bejövő adatcsomagok mérete, melyek maximális értéke 64 bájt lehet (ez az alapértelmezett érték).
- Az USBHID használatához a számítógépen szükségünk lesz egy olyan alkalmazásra, amely "szóba áll velünk", azaz képes kezelni az USBHID adatforgalmat. Ennek nem feltétlenül kell grafikus felhasználói felülettel rendelkeznie, lehet akár egy szkript is, amihez használhatjuk a Python értelmező pywinusb kiegészítését. Egy másik lehetőség a multiplatformos hidapi könyvtár használata, amelyet C/C++ konzol alkalmazásokból vagy grafikus alkalmazásokból is használhatunk. Az USBHID bindings és az USBHID C bindigs oldalon találunk mintapéldákat mindkettő használatára.
Mintapélda: USB HID kommunikáció
Az alábbi programot a Cypress AN82072 alkalmazási mintapéldájához
igazítva készítettük el, hogy annak a letölthető ZIP csomagjában
található grafikus PC alkalmazását használni tudjuk. Ennek megfelelően
mindkét irányba 8 bájtos csomagokat küldünk, s az adatok értelmezése és
felhasználása az alábbiak szerint történik:1. ábra: Az adatcsomagok értelmezése és felhasználása
Az Input/Output irány mindig a gazdagép szempontjából értendő. Az Input Report tehát a mikrovezérlő által küldött 8 bájtos csomagot jelenti, melynek első bájtja egy nyomógombhoz rendelt bemenet állapotát jelzi (0: lenyomva, 1: felengedve), a következő négy bájt pedig egy analóg bemenet jelének ADC-vel konvertált eredményét jelenti (a Cypress mikrovezérlőknél van 20 bites ADC is, ezért nev volt elég egy kétbájtos adatküldés az ADC-hez). A bájtsorrend úgynevezett Big Endian, azaz a legmagasabb helyiértékű bitek állnak elől.
Az Output Report (a gazdagép által kiküldött üzenetcsomag) pedig vezérlési funkciót lát el: az elsó bájt egy LED-et vezérel (0: ki, 1: be), a második bájt pedig egy másik LED fényesrősségét vezérli (1 - 100 közötti értéket küldhetünk ki, ami a PWM jel százalékos kitöltését adja meg).
Esetünkben a D3 (belső felhúzásra kapcsolt) bemenet figyeli a nyomógomb állapotát, s az A0 bemenetre kapcsolt feszültséget mérjük meg. A kimeneti vezérlésnél LED1 (a piros LED) lesz a ki-be kapcsolható LED, s LED2 (a zöld LED) lesz az, melynek fényerősségét folyamatosan szabályozzuk (PWM).
Végeredményben tehát az egyszerű mintapéldán keresztül megmutatjuk, hogy az USB HID kapcsolat segítségével hogyan végezhetünk analóg és digitális adatgyűjtést/adatbeolvasást, és hogyan létesíthetünk digitális és (kvázi) analóg vezérlést.
Hardver követelmények:
- FRDM-KL25Z kártya
- Analóg jel az A0 (PTB0) bemenetre kötve
- Nyomógomb a D3 (PTA12) digitális bemenet és a GND közé kötve.
- A KL25Z USB jelzésű aljzat csatlakoztatása a számítógéphez
#include "mbed.h"
#include "USBHID.h"
//We declare a USBHID device. By default input and output reports are 8 bytes long.
USBHID hid(8, 8);
HID_REPORT send_report; //This report will contain data to be sent
HID_REPORT recv_report; //This report will contain data received
DigitalOut LED_1(LED1);
PwmOut LED_2(LED2);
DigitalIn SW1(D3,PullUp);;
AnalogIn adc(A0);
int main(void) {
send_report.length = 8;
LED_1 = 1;
LED_2.period_ms(20);
while (1) {
uint16_t raw = adc.read_u16(); //Read ADC (A0 chan)
for (int i = 0; i < send_report.length; i++) //Fill the report
send_report.data[i] = 0x00;
send_report.data[0] = SW1.read();
send_report.data[3] = (raw>>8);
send_report.data[4] = (raw & 0xff);
hid.send(&send_report); //Send the report
if(hid.readNB(&recv_report)) { //try to read a msg
LED_1 = !recv_report.data[0];
LED_2.write(1.0 - recv_report.data[1]*0.01f);
}
}
}
A program elején be kell csatolnunk az USBHID.h fejléc állományát. Ehhez az új projekt létrehozása után importálnunk kell az USBDevice programkönyvtárat. Ezután példányosítanunk kell az USBHID
objektumosztályt. Programunkban csak az ki- és bemenő üzenetcsomag
méreténél tértünk el az alapértelmezett értékektől. Az üzenetcsomagok
tárolásához deklarálnunk kell egy-egy HID_REPORT típusú tömböt is (a
ki- és bemeti buffer elnevezése itt a mikrovezérlő szempontjából
értendő!). A program végtelen ciklusában az ADC mérés után automatikusan összeállítjuk és kiküldjük a gazdagépnek szánt csomagot. A nem használt bájtokat nullával töltjük fel.
A LED-ek vezérlése a bejövő üzenetcsomagok első két bájtja szerint történik. Ha az első bájt nullától különböző, akkor kigyújtjuk a piros LED-et. A második bájtot úgy konvertáljuk, hogy 100 esetén kapjuk a maximális kitöltést. Mindkét LED vezérlésénél invertálnunk kell a jelet a közös anódú LED-ek fordított logikája miatt.
Megjegyzések:
- A bejövő csomagok figyeléséhez a nem blokkoló típusú readNB() tagfüggvényt kell használnunk, különben "elakad" a programunk, és várakozik, amíg újabb üzenetcsomag nem érkezik!
- A PWM vezérlésnél nincs beépített védelem a 0-100 tartományon kívül eső értékek kezelésére, ennek figyelése a mintaként használt AN82072 alkalmazási mintapélda PC oldali programjában történik.
- Vegyük figyelembe, hogy az AN82072 alkalmazási mintapélda közzétett programjai csak a Cypress termékekhez használható jogszerűen, ezért a kipróbáláson és egyéni tanuláson kívül ne használjuk másra a FRDM-KL25Z kártyánkkal!
- A program futtatásakor be kell állítanunk a VID és PID azonosítókat és rá kell kattintanunk a Set gombra. Ezután a zöld Connected felirat jelenik meg, ha a 13_USBHID_demo programmal felprogramozott kártya csatlakoztatva van.
- A piros LED-et a LED felirat melleti jelölőnégyzet kiválasztásával kapcsolható be és ki.
- A zöld LED fényereje a PWM Duty Cyle címke mellett állítható be 1 és 100 közötti értékre, de ennek kiküldése csak az Update gomb kattintásakor történik meg.
- A bejövő értékek (az ADC konverzió 16 bites eredménye és a D3 digitális bemenet állapota) a vezérlőelemek alatt látható. Az ON kiírás a digitális bemenet magas szintjét, az OFF
pedig az alacsony szintjét jelenti. Mivel a nyomógombunk a GND felé
húz, ezért a fordított logika miatt a felirat a nyomógomb állapotát
fordítva jelzi ki.
2. ábra: A Cypress AN82072 alkalmazási mintapéldából "kölcsönvett" program futtatása
HID alkalmazások és tesztprogramok
Az alábbiakban bemutatunk néhány lehetőséget, amellyel gyorsan és egyszerűen kipróbálhatjuk mbed USB HID mikrovezérlő programjainkat, vagy akár saját igényeinkhez igazított PC alkalmazásokat is készíthetünk - ingyen, s természetesen jogszerűen. Jan Axelson HID Page című honlapjának Tools szekciója számos további lehetőséget is felsorol, ám azok közül nem mindegyik vált be a fenti mintaprogramunkkal kipróbálva.SimpleHidWrite
A SimpleHidWrite alkalmazás egyszerű segédeszköz a HID eszközök kipróbálásához vagy felderítéséhez. Minimális HID és USB ismeretek kellenek a program megértéséhez. A grafikus alkalmazás ablakának felső részében található lista a pillanatnyilag elérhető HID eszközöket sorolja fel. A kattintással kiválasztott eszközt ismételten lekérdezi a program, s a beérkezett üzeneteket kilistázza a program ablakának középső részen látható szövegdobozban. A szövegdoboz alatti részben a kimenő üzeneteket állíthatjuk össze (a beírt számok hexadecimálisan lesznek értelmezve, az üresen hagyott mezők automatikusan nullával töltődnek fel), illetve a nyomógombok segítségével az alábbi tevékenységek indíthatók:- "Info" - a kiválasztott eszközről rendelkezésre álló információkat jeleníti meg
- "Save As..." - szöveges naplófájlba menti az addig beérkezett üzeneteket.
- "Playback..." - lehetővé teszi egy elmentett naplófájl újrajátszását (értelemszerűen csak az eszközkiválasztás és az eszközre írás hatásos).
- "Clear" - törli a naplózó ablakot.
- "Write" - adatküldés, ami akkor engedélyezett, ha az USB eszköz deklarál kimenő jelentést.
- "Get Report" és "Set Report" - Windows 98 esetén tiltottak, mivel a HID API abban nem támogatja ezeket a funkciókat. A Get_Report
egyébként arra szolgál, hogy a vezérlő csatornán (EP0 végpont) kapjon
információt a gazdagép az USB eszköz állapotáról. A Set_Report pedig az
eszköz alaphelyzetbe állítására szolgál (pl. egy pointer eszköz esetén
a jelenlegi pozíció legyen az új viszonyítási helyzet).
- "Get Feature" és "Set Feature" akkor engedélyezettek, ha az USB eszköz deklarált "feature reportot".
- Töltsük le és bontsuk ki SimpleHIDWrite3.zip csomagot!
- Indítsuk el a SimpeHidWrite.exe alkalmazást!
- A megnyíló ablak felső részében kattintsunk a felismert HID eszközünk sorára (a HID DEVICE (Serial=0123456789) eszközt keressük)! A listázó ablakban ekkor elkezdenek pörögni a bejövő üzenetek. Figyeljünk rá, hogy az RD (olvasást) jelzést követő első szám még nem az üzenetcsomagunk része, hanem a jelentés alapértelmezett azonosítója (00)! Az ez követő bájt jelzi a nyomógombunk állapotát, a következő négy bájt pedig az ADC konverzió eredményét.
- Állítsunk össze egy adatcsomagot! A Report ID kötelezően 0, az alatta levő 8 bájt pedig a kiküldenő adatok. Csak a nullától különböző mezőket kell kitölteni! Például: 01 00 00 00 00 00 00 00 bekapcsolja a piros LED-et és leoltja a zöld LED-et. A 0064 00 00 00 00 00 00 csomag lekapcsolja a piros LED-et és teljes fényerőn (100 %-os kitöltés) kigyújtja a zöld LED-et.
- Küldjük ki az adatcsomagot a Write gombra kattintva!
- Mentsük el egy napló állományba a beérkezett és kiküldött csomagokat (Save As... gomb), majd tanulmányozzuk az adatokat! A kiküldött adatokat a WD kezdetű sorok tartalmazzák.
3. ábra: A SimpleHidWrite program használata
HIDAPI - testgui
Alan Ott multiplatformos programkönyvtára
Linux, Mac OS X és Windows alatt is kezelni tudja az USB, illetve
Bluetooth HID eszközöket. A programkönyvtár C/C++ alkalmazásainkból
meghívható, így egyszerűen készíthetünk USB HID eszközt kelzelő saját
alkalmazásokat. A HIDAPI
programcsomag egy grafikus felületű tesztprogramot is tartalmaz,
amellyel egyszerűen kipróbálhatjuk és vizsgálhatjuk HID eszközeinket. A
programcsomag legegyszerűbben a Letöltések oldalról tölthető le. Nekünk csak a legfrissebb hidapi-0.7.0.zip lesz szükségünk (a hidapi-externals.zip csomag csupán a Windowsos testgui.exe újrafordításához vagy módosításához kellhet). A kibontott csomagból először a testgui.exe alkalmazást keressük meg és próbáljuk ki!
- A program elindításakor megnyíló ablak felső részén keressük meg az 1234:0006 - mbed.org eszközt!
- Ha a program elindítása után csatlakoztattuk a kártyánkat, akkor a Re-Scan devices gombra kattintva frissítsük az elérhető eszközök listáját!
- Válasszuk ki az 1234:0006 - mbed.org eszközt és kattintsunk a Connect gombra! Az ablak alsó részén, az Input szövegdobozban ekkor megjelennek a beérkező üzenetcsomagok (ezek mérete esetünkben 8 bájt, tehát a jelentés azonosítószáma (Report ID) nincs megjelenítve.
- A Output rovatban állítsunk össze egy üzenetet, és küldjük ki a Send Output Report gombra kattintva!
- Az Output rovatba a 8 db adatbájt elé be kell írnunk az alapértelmezett jelentés azonosítót (0) is, tehát szigorúan mindig 9 bájtot kell beírnunk, s az első bájt kötelezően nulla legyen! Minden más esetben hibajelzést kapunk.
- Az
általunk beírt adatokat alapértelmezetten decimálisnak veszi a program.
Hexadecimális bájtokat 0x prefix-szel adhatunk meg. A beérkezett
adabájtokat mindig hexadecimálisan jeleníti meg a program.
Példa adatok:
- A 0 0 0 0 0 0 0 0 0 kimenő adatcsomag (ebben már benne van a Report ID is!) lekapcsolja mindkét LED-et.
- A 0 1 0 0 0 0 0 0 0 kimenő adatcsomag bekapcsolja a piros LED-et.
- A 0 0 10 0 0 0 0 0 0 kimenő adatcsomag 10 %-os PWM kitöltéssel bekapcsolja a zöld LED-et.
4. ábra: A HIDAPI testgui.exe alkalmazás futtatása
HIDAPI - hidtest
Alan Ott multiplatformos programkönyvtárának legfontosabb része a hidapi.dll, amelyet saját C/C++ programjainkból meghívhatjuk. A hidapi csomag windows mappájában található egy Visual C++ 2008 Express Edition-nal lefordítható projekt (a hidapi.sln állományt nyissuk meg!), amely a hidapi.dll mellett egy konzolos (nem grafikus felületű) mintalkalmazást is lefordít (hidtest.exe). Ennek foráskódját némileg módosítottuk. A módosítások lényege:- Módosítottuk a Vid/Pid azonosítót (0x1234, 0x0001), és az üzenetcsomagok méretét (9 byte a Report ID-vel együtt).
- Összesen 20 adatcsomagot küldünk ki és fogadunk (minden küldés után 500 ms várakozást iktatunk be).
- A kiküldött adatokat úgy állítjuk elő, hogy a piros LED csak kétszer villan fel, a zöld LED fényereje pedig fokozatosan növekszik 5 %-os lépésekben.
- A kiküldött és a beérkező adacsomagokat egymás mellé kiíratjuk.
2. lista: A módosított hidtest.c program listája
#include <stdio.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>
#include "hidapi.h"
// Headers needed for sleeping.
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
int main(int argc, char* argv[])
{
int res;
unsigned char buf[9];// 1 extra byte for the report ID
#define MAX_STR 255
wchar_t wstr[MAX_STR];
hid_device *handle;
int i;
#ifdef WIN32
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
#endif
struct hid_device_info *devs, *cur_dev;
devs = hid_enumerate(0x0, 0x0);
cur_dev = devs;
while (cur_dev) {
printf("Device Found\n type: %04hx %04hx\n path: %s
\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id,
cur_dev->path, cur_dev->serial_number);
printf("\n");
printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string);
printf(" Product: %ls\n", cur_dev->product_string);
printf(" Release: %hx\n", cur_dev->release_number);
printf(" Interface: %d\n", cur_dev->interface_number);
printf("\n");
cur_dev = cur_dev->next;
}
hid_free_enumeration(devs);
// Set up the command buffer.
memset(buf,0x00,sizeof(buf));
buf[0] = 0x01;
buf[1] = 0x01;
buf[2] = 0x01;
// Open the device using the VID, PID,
// and optionally the Serial number.
////handle = hid_open(0x4d8, 0x3f, L"12345");
handle = hid_open(0x1234, 0x6, NULL);
if (!handle) {
printf("unable to open device\n");
return 1;
}
// Read the Manufacturer String
wstr[0] = 0x0000;
res = hid_get_manufacturer_string(handle, wstr, MAX_STR);
if (res < 0)
printf("Unable to read manufacturer string\n");
printf("Manufacturer String: %ls\n", wstr);
// Read the Product String
wstr[0] = 0x0000;
res = hid_get_product_string(handle, wstr, MAX_STR);
if (res < 0)
printf("Unable to read product string\n");
printf("Product String: %ls\n", wstr);
// Read the Serial Number String
wstr[0] = 0x0000;
res = hid_get_serial_number_string(handle, wstr, MAX_STR);
if (res < 0)
printf("Unable to read serial number string\n");
printf("Serial Number String: (%d) %ls", wstr[0], wstr);
printf("\n");
// Read Indexed String 1
wstr[0] = 0x0000;
res = hid_get_indexed_string(handle, 1, wstr, MAX_STR);
if (res < 0)
printf("Unable to read indexed string 1\n");
printf("Indexed String 1: %ls\n", wstr);
// Set the hid_read() function to be non-blocking.
hid_set_nonblocking(handle, 1);
// send and receive 20 reports
for (int r = 0; r < 20; r++) {
// send some dummy data
buf[0] = 0; //Report ID
buf[1] = ((r%10)==8); //Only for 8 and 18
buf[2] = 5*r; //0,5,10,15...
buf[3] = 0;
buf[4] = 0;
res = hid_write(handle, buf, sizeof(buf));
if (res < 0) {
printf("Unable to write()\n");
printf("Error: %ls\n", hid_error(handle));
} else {
printf("Data sent: ");
// Print out the returned buffer.
for (i = 0; i < res; i++) printf("%02hhx ", buf[i]);
}
// Read requested state. hid_read() has been set to be
// non-blocking by the call to hid_set_nonblocking() above.
// This loop demonstrates the non-blocking nature of hid_read().
res = 0;
while (res == 0) {
res = hid_read(handle, buf, sizeof(buf));
if (res == 0)
printf("waiting...\n");
if (res < 0)
printf("Unable to read()\n");
#ifdef WIN32
Sleep(500);
#else
usleep(500*1000);
#endif
}
printf(" Data read: ");
// Print out the returned buffer.
for (i = 0; i < res; i++) printf("%02hhx ", buf[i]);
printf("\n");
}//end 20 reports
//close HID device
hid_close(handle);
/* Free static HIDAPI objects. */
hid_exit();
#ifdef WIN32
system("pause");
#endif
return 0;
}
A program futási eredménye az alábbi ábrán látható.5. ábra: A módosított hidtest program futtatása
Megjegyzés: A testgui.exe programnál említett kettősség itt is megfigyelhető: A kimenő adacsomagnál a Report ID is megjelenik, beolvasásnál viszont a HIDAPI "lenyeli" a Report ID-t, a bejövő csomagoknál csak a nyolc adatbájtok kapjuk meg!