× Aktuálně z oboru

SHIELD Experience Upgrade 7 – méně hledání a více zábavy [ clanek/2018052902-shield-experience-upgrade-7-mene-hledani-a-vice-zabavy/ ]
Celá zprávička [ clanek/2018052902-shield-experience-upgrade-7-mene-hledani-a-vice-zabavy/ ]

Vývoj USB ovládača – 4. časť

[ http://programujte.com/profil/13267-matej-tomcik/ ]Google [ ?rel=author ]       [ http://programujte.com/profil/118-zdenek-lehocky/ ]Google [ ?rel=author ]       10. 6. 2013       27 927×

Zatiaľ čo v predchádzajúcich častiach sme sa venovali vývoju softvéru pre zariadenie, v tých nasledujúcich budeme pracovať na softvéri ovládača a na klientskej aplikácií, ktorá bude s pomocou ovládača komunikovať s našim zariadením.

Čo je to ovládač?

Je to špeciálny druh aplikácie, ktorá slúži ako rozhranie medzi hardvérom a softvérom. Windows  rozoznáva dva druhy ovládačov:

  • Kernel mode – bežia ako súčasť operačného systému, majú prístup k systémovým prostriedkom a hardvéru, sú taktiež o trochu rýchlejšie. Avšak vývoj takýchto ovládačov je komplikovaný, čisto procedurálne programovanie na platforme, ktorá je komplikovaná sama o sebe. Nemôžete volať funkcie WinAPI, ladenie kernel mode ovládačov je veľmi náročné a zdĺhavé, väčšinou potrebujete dva počítače, z toho jeden slúži ako cieľový PC, na ktorom je nainštalovaný testovaný ovládač, druhý sa k nemu pripája napr. pomocou sériového kábla. I najmenšia chyba vyvolá BSoD.
  • User mode – bežia ako klientska služba (podobne ako iné klientske aplikácie). Na rozdiel od kernel mode ovládačov majú prístup k WinAPI funkciám, avšak strácajú prístup k systémovým prostriedkom a hardvéru. Pokiaľ ale potrebujú pristúpiť k hardvéru, robí tak prostredníctvom iných ovládačov, z ktorých minimálne jeden musí byť na úrovni kernel mode. Vývoj takýchto ovládačov je jednoduchší, ich ladenie je možné napr. pomocou Visual Studia.

V zásade platí, že pokiaľ nepotrebujete, aby váš ovládač bežal v privilegovanom móde a mal priamy prístup k systémovým prostriedkom, mali by ste ho napísať ako user mode ovládač. Pri USB zariadeniach to platí obzvlášť, hlavne ak môžeme využiť WinUSB alebo libusb. Každý operačný systém má hneď niekoľko predinštalovaných ovládačov pre USB, pre ovládanie zbernice, HCI ovládač (Host Controller), ktoré sa starajú o samotnú abstraktnú komunikáciu s USB zariadeniami.

Ako komunikuje ovládač s klientskou aplikáciou

Keďže kernel mode a WinAPI sú oddelené, Windows na komunikáciu medzi týmito dvoma vrstvami používa tzv. I/O manager. Každá požiadavka smerujúca ovládaču je reprezentovaná pomocou IRP (I/O Request Packet), ten ju posunie špecifickému ovládaču a pri jej dokončení informuje klientsku aplikáciu. Ako príklad uvediem odoslanie údajov pomocou sériového COM portu.

Program zavolá funkciu WinAPI CreateFile. Pomocou nej môžu všetky programy komunikovať s rôznymi ovládačmi, či už so súborovým systémom (ak by sme chceli napr. čítať/písať do súboru), USB zariadením, skenerom, zvukovou kartou apod.

HANDLE file = CreateFile("\\\\.\\COM10",
                         GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);

Ovládač sériového portu má teraz vytvorenú inštanciu COM zariadenia, pomocou ktorej budeme odosielať dáta. Všimnite si názov zariadenia, ktorý sme zadali: \\.\COM10. Každý ovládač si môže zaregistrovať tzv. symbolický odkaz, pomocou ktorého je možné so zariadením komunikovať. V prípade, že by sme symbolický odkaz nevytvorili, musíme použiť úplnú cestu k ovládaču, ktorá môže byť napr.:

\\?\USB#VID_DDDD&PID_AAAA#5&1a5f5070&0&1#{b89ea1d5-cb2c-4744-a230-1f422dd34106}

Použitím funkcie WriteFile dáta odošleme.

DWORD written;
WriteFile(file, "AT\r\n", 4, &written, 0);

I/O manager v tomto kroku vytvorí IRP, ktorá bude obsahovať informácie ako adresu zásobníka dát, ktorý odosielame, adresu premennej, do ktorej ovládač uloží koľko bajtov bolo prenesených apod. Následne I/O manager oznámi ovládaču novú IRP cez event funkciu priradenú k udalosti zápisu do zariadenia, ktorú si sám definoval pri načítaní do systému. Tá spracuje IRP požiadavku a dokončí ju, I/O manager skontroluje výsledok a posunie ho volajúcemu funkcie WriteFile, ktorá tým pádom skončí buď úspešne alebo s chybou (pokiaľ vznikne problém pri prenose dát, nesprávna veľkosť zásobníka apod.).

A nakoniec zatvoríme inštanciu ovládača zariadenia pomocou CloseHandle.

libusb

V tejto časti sa budeme venovať vytvoreniu ovládača pomocou libusb. Podobne ako WinUSB tak aj libusb sa skladá z kernel mode časti a funkcií exportovaných v knižnici, pomocou ktorých sú určité USB zariadenia prístupné koncovej aplikácii. Rozdiel medzi WinUSB a libusb bude zásadný v skutočnosti, že ovládač vyvinutý pre WinUSB bude bežať ako služba, tým pádom získame plnohodnotný user mode ovládač a aplikácie, ktoré budú chcieť s USB zariadením komunikovať, tak budú musieť robiť práve cez funkcie WinAPI ako CreateFile, ReadFile, WriteFileDeviceIoControl. Naopak libusb umožňuje priamy prístup k USB zariadeniam aj koncovým aplikáciám bez potreby vyvinúť intermediate ovládač, teda niečo, čo bude stáť medzi aplikáciou a kernel mode ovládačom (WinUSB).

Kompatibilita medzi aplikáciou a ovládačmi libusb / WinUSB

Koncové riešenie bude pozostávať z dvoch ovládačov, jeden vytvorený s pomocou libusb, druhý s WinUSB a klientskej aplikácie. Našim cieľom je, aby aplikácia vedela pracovať s obidvoma ovládačmi, preto je dobré stanoviť si určité rozhranie, pomocou ktorého bude aplikácia pristupovať k hardvéru. Ako som už spomenul, výhoda libusb spočíva v tom, že programátor môže „ovládač“ zakomponovať priamo do jeho koncového programu. Predstavte si ale, že potrebujete vaše zariadenie zdieľať medzi viacerými aplikáciami, ktoré nemusia byť napísané v rovnakom programovacom jazyku. V takýchto prípadoch je dobré vyvinúť medzičlánok, v našom prípade to bude vždy knižnica .dll, ktorá bude exportovať vopred definované metódy.

V prípade WinUSB budeme okrem ovládača musieť vyvinúť ešte jednu knižnicu, podobne ako pri libusb bude táto knižnica obsahovať preddefinované metódy.

Na obrázku môžete vidieť základné rozvrstvenie nášho riešenia. Horná časť znázorňuje, ktoré komponenty budeme využívať pri písaní ovládača s pomocou WinUSB, dolná znázorňuje ovládač pre libusb. Fialovou farbou sú označené komponenty, ktoré sa viažu na klientsku aplikáciu, to znamená, že ich úloha splýva s koncovou aplikáciou.

Príklad rozhrania

Ako príklad uvediem funkciu, ktorou klientska aplikácia otvorí spojenie so zariadením. Tou funkciou je:

// Otvorí inštanciu zariadenia
__declspec(dllexport) unsigned int OpenDevice();

Je to exportovaná funkcia, nepreberajúca žiadny parameter a vracia číselnú hodnotu, ktorou môže byť 0 v prípade úspešného otvorenia, alebo iná hodnota popisujúca chybu, ktorá nastala.

WinUSB

Keďže ovládač napísaný pre WinUSB beží ako user mode driver, získame k nemu prístup iba pomocou funkcií WinAPI ako CreateFile, ReadFile apod. Preto bude náš wrapper pre WinUSB ovládač (wrapper alebo rozhranie) v metóde OpenDevice volať WinAPI funkciu CreateFile s prvým parametrom nastaveným na absolútnu cestu k nášmu zariadeniu.

libusb

V prípade libusb budeme musieť pri vytváraní spojenia so zariadením najskôr naše zariadenie nájsť na zbernici, pretože libusb môže byť predvoleným ovládačom pre viac ako jeden hardvér. Na identifikáciu toho nášho nám postačia dva údaje, a to VID (Vendor ID) a PID (Product ID).

Obidva spôsoby definujú otvorenie inštancie zariadenia iným spôsobom, avšak koncová aplikácia bude vždy vidieť iba jednu funkciu, ktorou je OpenDevice. Pokiaľ bude chcieť vývojár prepnúť medzi ovládačom libusb a WinUSB, iba jednoducho načíta inú DLL.

Kód

Je dôležité ešte raz zdôrazniť význam slova „ovládač“ v tomto článku. Keďže v prvom kroku vytvoríme knižnicu, ktorá bude s pomocou libusb komunikovať so zariadením a použijeme ju priamo v klientskej aplikácií, nejedná sa ani tak o ovládač ako o rozhranie medzi koncovou aplikáciou a ovládačom libusb. Pointou takéhoto medzičlánku, ako už bolo spomenuté, je definovať presné rozhranie, s ktorého pomocou budú ostatné aplikácie pristupovať k fyzickému zariadeniu. Libusb je dostupné pre rôzne programovacie jazyky a platformy, s malou pomocou hostovskej aplikácie sa aj JavaScript môže zmeniť na jazyk, ktorým bude možné komunikovať s hardvérom. Ku klasickému ovládaču, aký bežne nájdete vo Windowse, sa dostaneme neskoršie, a to pri programovaní s pomocou WinUSB.

Štruktúry paketov, enum hodnoty a konštanty sme si definovali v predchádzajúcom článku. Klientské aplikácie v zariadení a na počítači medzi sebou komunikujú pomocou svojich dátových štruktúr, ktoré sú rovnaké ako pre zariadenie, tak aj pre aplikáciu bežiacu na hostovi. Odkaz na stiahnutie kompletného zdrojového kódu je zverejnený na konci článku.

DllMain

Začneme základnou funkciou každej Windows knižnice, a to vstupnou metódou DllMain. Pokiaľ máte skúsenosti s tvorbou knižníc, tak iste viete, že v DllMain máte možnosť inicializovať/uvoľniť prostriedky pri načítaní/uvoľnení knižnice s procesom. V našom prípade pri načítaní knižnice k procesu inicializujeme libusb a pri uvoľnení knižnice z procesu zatvoríme existujúce spojenie na zariadenie (klientská aplikácia mohla zabudnúť zatvoriť spojenie).

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,
  LPVOID lpReserved)
{
  switch (ul_reason_for_call)
  {
  case DLL_PROCESS_ATTACH:
    // Inicializovať libusb
    usb_init();
    break;
  case DLL_PROCESS_DETACH:
    // Zatvoriť zariadenie, pokiaľ tak klient neurobil
    CloseDevice();
    break;
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
    break;
  }
  return TRUE;
}

OpenDevice

Touto funkciou ovládač zistí, či je k počítaču pripojené zariadenie a ak áno, otvorí s ním spojenie. Implementácia funkcie je nasledovná:

// Otvorí inštanciu zariadenia
__declspec(dllexport) unsigned int OpenDevice()
{
  // Skontrolovať, či už neexistuje otvorená inštancia
  if (deviceHandle)
    return ResultOpen_Error;
 
  struct usb_bus * bus;
  struct usb_device * device;
 
  // Nájsť všetky zbernice a zariadenia
  usb_find_busses();
  usb_find_devices();
 
  // Pre každú USB zbernicu
  for (bus = usb_get_busses(); bus; bus = bus->next)
  {
    // Pre každé zariadenie na zbernici
    for (device = bus->devices; device; device = device->next)
    {
      // Porovnať VID a PID zariadenia s našim
      if (device->descriptor.idVendor == DEVICE_VID &&
        device->descriptor.idProduct == DEVICE_PID)
      {
        // Otvoriť inštanciu zariadenia
        deviceHandle = usb_open(device);
 
        if (deviceHandle)
        {
          // Skonfigurovať zariadenie
          unsigned int config = ConfigureDevice();
          if (config != ResultOpen_OK)
            CloseDevice();
 
          return config;
        }
        else
          return ResultOpen_ErrorOpen;
      }
    }
  }
 
  return ResultOpen_NotFound;
}

V prvom rade je potrebné pomocou libusb obnoviť zoznam zberníc a na nich pripojených zariadení. Následne v dvoch cykloch, pre každú zbernicu a pre každé zariadenie na nej pripojené, budeme kontrolovať, či sa Vendor ID a Product ID rovnajú požadovaným hodnotám. Pokiaľ áno, tak vieme, že zariadenie je na zbernici pripojené a libusb je pripravené s ním komunikovať. Volaním funkcie usb_open otvoríme komunikáciu so zariadením a následne zariadenie nakonfigurujeme, pretože ako už viete, host musí zvoliť konfiguráciu a rozhranie, tým sa zariadenie aktivuje. Pokiaľ všetko prebehne v poriadku, vrátime klientovi hodnotu ResultOpen_OK, v opačnom prípade vrátime kód chyby, ktorý viac napovie o pôvode chyby.

Implementácia ConfigureDevice:

// Skonfiguruje otvorené zariadenie
unsigned int ConfigureDevice()
{
  if (!deviceHandle)
    return ResultOpen_Error;
 
  // Zvoliť konfiguráciu
  if (usb_set_configuration(deviceHandle, DEVICE_CONFIG) < 0)
    return ResultOpen_Configuration;
  // Zvoliť rozhranie
  if (usb_claim_interface(deviceHandle, DEVICE_INTERFACE) < 0)
    return ResultOpen_Interface;
  // Zvoliť alternatívne rozhranie, zariadenie sa aktivuje
  if (usb_set_altinterface(deviceHandle, 1) < 0)
    return ResultOpen_AltInterface;
 
  return ResultOpen_OK;
}

CloseDevice

Pokiaľ je inštancia na zariadenie otvorená, zatvoríme ju pomocou volania funkcie usb_close:

// Zatvorí otvorenú inštanciu zariadenia
__declspec(dllexport) void CloseDevice()
{
  if (deviceHandle)
  {
    // Zatvoriť inštanciu zariadenia
    usb_close(deviceHandle);
    deviceHandle = 0;
  }
}

ReadADC

Prečíta hodnoty výstupu ADC prevodníka. Ovládač odošle SETUP paket pomocou libusb funkcie usb_control_msg, ktorého parametre budú nastavené nasledovne:

  • dev – inštancia zariadenia
  • requesttype – typ požiadavky – USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_ENDPOINT_INT – požiadavka bude doručená endpointu CONTROL (SETUP) IN, smerovaná rozhraniu a spracovaná aplikáciou (keďže je vendor-specific).
  • request – požiadavka – tento parameter využíva väčšinou iba host, a to pri konfigurácii zariadenia apod. Implementácia UDI v Atmel Software Framework SETUP paket posunie aplikácii iba v prípade, ak je hodnota request rovná 0
  • value – hodnota, ktorú môže klient využiť podľa svojich potrieb. Náš ovládač nastaví tento parameter na hodnotu ControlType_In. V prípade, že budeme odosielať dáta zariadeniu, nastavíme hodnotu na ControlType_Out. So smerom prenosu tento parameter nijako nesúvisí.
  • index – využíva väčšinou iba host, napr. pri zvolení aktívneho rozhrania sa v tomto parametri bude nachádzať index z konfigurácie, ktorú host získal pri inicializácii
  • bytes – zásobník dát (payload) – bude obsahovať náš paket usbapp_control_in_t
  • size – veľkosť zásobníka dát (payload)
  • timeout – max. čas na vykonanie operácie – hodnota 0 znamená, že táto operácia nemá max. čas na vykonanie

Ak libusb operáciu úspešne dokončí, funkcia usb_control_msg vráti počet prenesených dát (payload) alebo zápornú hodnotu pri chybe. V prípade, že by sme žiadny payload neposielali, pri úspešnom dokončení vráti hodnotu 0.

// Prečíta hodnoty z prevodníka ADC (potenciometer a teplotu)
__declspec(dllexport) unsigned int ReadADC(usbapp_control_in_t * data)
{
  if (!deviceHandle)
    return DeviceError_NotOpen;
  if (!data)
    return DeviceError_InvalidParameter;
 
  // Odoslať CONTROL IN paket
  int ret = usb_control_msg(deviceHandle,
    USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_ENDPOINT_IN,
    0, ControlType_In, DEVICE_INTERFACE,
    (char *)data, sizeof(usbapp_control_in_t), 0);
 
  if (ret < 0)
    return DeviceError_Transfer;
  if (ret != sizeof(usbapp_control_in_t))
    return DeviceError_InvalidSize;
 
  return DeviceError_OK;
}

SetupDevice

Nastaví komponenty zariadenia, čiže ktoré LED majú svietiť, úroveň podsvietenia LCD a či má byť LCD zapnutý. Využijeme tú istú funkciu libsub ako pri ReadADC, a to usb_control_msg. Líšiť sa budú iba niektoré parametre, requesttype bude obsahovať endpoint OUT a nie IN, hodnota parametra value bude nastavená na ControlType_Out a dáta (payload), ktoré bude funkcia odosielať, budú v štruktúre usbapp_control_out_t. Jediný krok navyše oproti ReadADC bude kontrola vstupných údajov. Zariadeniu by sme nemali odoslať dáta, o ktorých vieme, že ich nespracuje.

// Nastaví zariadenie (LED a LCD)
__declspec(dllexport) unsigned int SetupDevice(usbapp_control_out_t * data)
{
  if (!deviceHandle)
    return DeviceError_NotOpen;
  if (!data)
    return DeviceError_InvalidParameter;
 
  // Opraviť hodnoty v parametroch
  if (data->pwmLedDuty > usbapp_control_pwm_max)
    data->pwmLedDuty = usbapp_control_pwm_max;
 
  // Odoslať CONTROL OUT paket
  int ret = usb_control_msg(deviceHandle,
    USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_ENDPOINT_OUT,
    0, ControlType_Out, DEVICE_INTERFACE,
    (char *)data, sizeof(usbapp_control_out_t), 0);
 
  if (ret < 0)
    return DeviceError_Transfer;
  if (ret != sizeof(usbapp_control_out_t))
    return DeviceError_InvalidSize;
 
  return DeviceError_OK;
}

WaitForInterrupt

Ako z názvu vyplýva, ovládač bude čakať na paket typu interrupt, ktorý odošle zariadenie hostovi. Takáto situácia nastane v prípade interakcie používateľa s vývojovou doskou (dotkne sa tlačidiel), preto by mohla byť funkcia pomenovaná príznačnejšie, napr. WaitForUserInteraction. Vo všeobecnosti sa ale jedná o interrupt IN paket. Vieme, že zariadenie nikdy nezačne prenos na USB zbernici, prenos je vždy inicializovaný hostom. Zavolaním funkcie usb_interrupt_read začne host generovať TOKEN IN pakety a odosielať ich zariadeniu, ktoré odpovie iba v prípade, ak bude mať na endpointe INTERRUPT IN pripravený paket s dátami, čiže ak sa používateľ dotkne niektorých z tlačidiel na vývojovej doske.

Na rozdiel od kontrolných paketov, pri interrupt, bulk a isochronous prenosoch určujeme aj adresu endpointu, z ktorého dáta žiadame alebo na ktorý dáta posielame. V našom prípade máme iba jeden interrupt IN endpoint, preto parameter ep funkcie usb_interrupt_read bude nastavený na hodnotu DEVICE_EP_INTERRUPT_IN. Ďalej funkcii odovzdáme zásobník, do ktorého sa prijatý paket uloží, a veľkosť zásobníka, čiže veľkosť štruktúry nášho paketu usbapp_interrupt_in_t. Podobne ako v predchádzajúcich príkladoch, ani v tejto funkcii nevyužívame timeout.

// Počká na vstupný paket prerušenia
__declspec(dllexport) unsigned int WaitForInterrupt(
  usbapp_interrupt_in_t * data)
{
  if (!deviceHandle)
    return DeviceError_NotOpen;
  if (!data)
    return DeviceError_InvalidParameter;
 
  // Prijať INTERRUPT IN
  int ret = usb_interrupt_read(deviceHandle, DEVICE_EP_INTERRUPT_IN,
    (char *)data, sizeof(usbapp_interrupt_in_t), 0);
 
  if (ret < 0)
    return DeviceError_Transfer;
  if (ret != sizeof(usbapp_interrupt_in_t))
    return DeviceError_InvalidSize;
 
  return DeviceError_OK;
}

ClearScreen

Vyčistí obrazovku (zafarbí určitou farbou). Všetky ostatné funkcie rozhrania, vrátane tejto, budú pracovať už iba s bulk prenosom. Najjednoduchším využitím bulk prenosu je v našom prípade práve zafarbenie LCD displeja jednou farbou.

Všetky funkcie pracujúce s bulk využívajú zdieľaný zásobník dát, nazvaný deviceBulkBuffer. Je to čisto moja voľba, každá funkcia môže mať vlastné dátové štruktúry. A keďže zásobník deviceBulkBuffer obsahuje dostatok miesta pre najväčší možný bulk out paket, jediné, čo potrebujeme urobiť, je pretypovať jeho adresu na adresu štruktúry dát usbapp_bulk_out_t. Tú potom vynulujeme pomocou funkcie memset a následne nastavíme jej jedinú hodnotu, a to options, do ktorej vpíšeme príznak DrawOption_Clear a 24bitovú RGB farbu (zariadenie ju samo skonvertuje na 16bitovú).

Paket odošleme funkciou usb_bulk_write, parameter ep (endpoint) nastavíme na DEVICE_EP_BULK_OUT a odovzdáme parametre našej štruktúry usbapp_bulk_out_t. libusb paket odošle a vráti počet odoslaných bajtov alebo zápornú hodnotu pri chybe.

// Vyčistí obrazovku jednou farbou
__declspec(dllexport) unsigned int ClearScreen(unsigned int color)
{
  if (!deviceHandle)
    return DeviceError_NotOpen;
 
  // Nastaviť parametre hlavičky
  usbapp_bulk_out_t * data = (usbapp_bulk_out_t *)deviceBulkBuffer;
  memset(data, 0, sizeof(usbapp_bulk_out_t));
  data->options = DrawOption_Clear | (color & DrawOption_ColorMask);
 
  // Odoslať BULK OUT paket
  int ret = usb_bulk_write(deviceHandle, DEVICE_EP_BULK_OUT,
    deviceBulkBuffer, sizeof(usbapp_bulk_out_t), 0);
 
  if (ret < 0)
    return DeviceError_Transfer;
  if (ret != sizeof(usbapp_bulk_out_t))
    return DeviceError_InvalidSize;
 
  return DeviceError_OK;
}

DrawString

Vypíše text na LCD displeji. Funkcia dostane tri parametre. Nastavenia vykreslenia textu (či sa má text vykresľovať v riadkoch, farba textu a jeho pozícia), ďalej adresu v pamäti, na ktorej sa nachádza text na vykreslenie, a dĺžku textu. Je veľmi dôležité, aby ovládač skontroloval správnosť parametrov predtým, ako ich odošle zariadeniu. Napr. dĺžka textu nesmie prekročiť max. hranicu (20 znakov na riadok), pozícia X/Y nesmie byť mimo rozsah LCD displeja a ďalšie nastavenia vykresľovania musia obsahovať príznak DrawOption_Text. Po kontrole parametrov si vyplníme paket. Na začiatok skopírujeme hlavičku obsahujúcu spomínané nastavenia (usbapp_bulk_out_t) a za ňu skopírujeme text. Ako náhle zariadenie tento paket prijme, extrahuje z neho hlavičku a podľa príznaku DrawOption_Text zistí, že host požaduje vykreslenie textu na LCD displeji.

// Vykreslí text na displeji
__declspec(dllexport) unsigned int DrawString(usbapp_bulk_out_t * options,
  const char * text, unsigned int textLength)
{
  if (!deviceHandle)
    return DeviceError_NotOpen;
  if (!options)
    return DeviceError_InvalidParameter;
  if (!text)
    return DeviceError_InvalidParameter;
  if (textLength > DEVICE_MAX_TEXT_LENGTH || textLength == 0)
    return DeviceError_InvalidParameter;
 
  // Skopírovať nastavenia do zásobníka pre bulk prenos
  usbapp_bulk_out_t * data = (usbapp_bulk_out_t *)deviceBulkBuffer;
  memcpy_s(deviceBulkBuffer, DEVICE_BULK_BUFFER_SIZE,
    options, sizeof(usbapp_bulk_out_t));
  // Opraviť hodnoty v parametroch
  if (data->posX >= DEVICE_LCD_WIDTH)
    data->posX = 0;
  if (data->posY >= DEVICE_LCD_HEIGHT)
    data->posY = 0;
  data->options |= DrawOption_Text;
  data->options &= ~(DrawOption_Bitmap);
 
  // Skopírovať text za hlavičku prenosu
  memcpy_s(deviceBulkBuffer + sizeof(usbapp_bulk_out_t),
    DEVICE_BULK_BUFFER_SIZE - sizeof(usbapp_bulk_out_t),
    text, textLength);
  
  int size = sizeof(usbapp_bulk_out_t) + textLength;
 
  // Odoslať BULK OUT paket
  int ret = usb_bulk_write(deviceHandle, DEVICE_EP_BULK_OUT,
    deviceBulkBuffer, size, 0);
 
  if (ret < 0)
    return DeviceError_Transfer;
  if (ret != size)
    return DeviceError_InvalidSize;
 
  return DeviceError_OK;
}

GetBitmapBuffer

Získa adresu v pamäti (v zásobníku dát pre bulk prenos), kde je možné umiestniť 16bitovú bitmapu. Ovládač si udržuje jeden zásobník pre bulk prenos o maximálnej veľkosti vypočítanej ako: veľkosť hlavičky (usbapp_bulk_out_t) + (max. plocha bitmapy v px × 2 bajty na pixel).

Zariadenie je teda schopné prijať bitmapu o maximálnych rozmeroch 90 × 90 px. Veľkosť hlavičky je 12 bajtov, plocha najväčšej možnej bitmapy je 90 × 90 = 8 100 px a keďže LCD displej podporuje iba 16bitové farby, číslo 8 100 je vynásobené dvojkou (16 bitov = 2 bajty). Výsledná veľkosť zásobníka dát pre bulk prenos je: 16 212 bajtov, klient môže využiť 16 200 bajtov.

// Získa adresu, kde je možné vložiť bitmapu na vykreslenie
__declspec(dllexport) uint16_t * GetBitmapBuffer()
{
  // Adresa, kde je možné umiestniť
  return (uint16_t *)(deviceBulkBuffer + sizeof(usbapp_bulk_out_t));
}

DrawBitmap

Vykreslí časť obrázku na displeji. Úlohou tejto funkcie je skontrolovať správnosť hlavičky prenosu a odoslať ju spolu s bitmapou nachádzajúcou sa v zásobníku dát pre bulk prenos. Podobne ako pri vykresľovaní textu aj tu platí, že pozícia X/Y nesmie byť mimo rozsah LCD displeja, šírka a výška nesmú prekročiť rozmery displeja a nastavenia nesmú obsahovať príznak pre vykreslenie textu. Klient pred volaním tejto funkcie vyplní zásobník pre bitmapu pomocou GetBitmapBuffer.

// Vykreslí časť obrázku na displej
__declspec(dllexport) unsigned int DrawBitmap(usbapp_bulk_out_t * options)
{
  if (!deviceHandle)
    return DeviceError_NotOpen;
  if (!options)
    return DeviceError_InvalidParameter;
  if ((options->height * options->width) > DEVICE_MAX_BITMAP_SIZE)
    return DeviceError_InvalidParameter;
  if (options->width > DEVICE_LCD_WIDTH ||
    options->height > DEVICE_LCD_HEIGHT)
    return DeviceError_InvalidParameter;
 
  // Opraviť hodnoty v parametroch
  usbapp_bulk_out_t * data = (usbapp_bulk_out_t *)deviceBulkBuffer;
  memcpy_s(deviceBulkBuffer, DEVICE_BULK_BUFFER_SIZE,
    options, sizeof(usbapp_bulk_out_t));
  if (data->posX >= DEVICE_LCD_WIDTH)
    data->posX = 0;
  if (data->posY >= DEVICE_LCD_HEIGHT)
    data->posY = 0;
  data->options &= ~(DrawOption_Text);
  
  int size = sizeof(usbapp_bulk_out_t) + 
    ((options->height * options->width) * 2);
 
  // Odoslať BULK OUT paket
  int ret = usb_bulk_write(deviceHandle, DEVICE_EP_BULK_OUT,
    deviceBulkBuffer, size, 0);
 
  if (ret < 0)
    return DeviceError_Transfer;
  if (ret != size)
    return DeviceError_InvalidSize;
 
  return DeviceError_OK;
}

Funkcie libusb

V ovládači sme využili hneď niekoľko funkcií libusb, tieto a ďalšie iné v stručnosti opíšem. Viacej informácií ku každej z nich môžete nájsť na http://libusb.sourceforge.net/doc/functions.html [ http://libusb.sourceforge.net/doc/functions.html ].

usb_init()
Inicializuje libusb knižnicu. Túto funkciu je potrebné zavolať iba raz, a to pred volaním ostatných libusb funkcií.

usb_find_busses()
Aktualizuje zoznam všetkých USB zberníc. Vráti počet pripojených/odpojených zberníc od posledného volania tejto funkcie.

usb_find_devices()
Aktualizuje zoznam všetkých zariadení na každej USB zbernici. Vráti počet pripojených/odpojených zberníc od posledného volania tejto funkcie.

usb_get_busses()
Získa adresu prvej USB zbernice.

usb_open(usb_device * dev)
Otvorí zariadenie. Parameter dev je možné získať prechádzaním zoznamu zariadení pripojených ku zberniciam.

usb_close(usb_dev_handle * dev)
Zatvorí zariadenie. Parameter dev získate ako výsledok volania funkcie usb_open.

usb_set_configuration(usb_dev_handle * dev, int configuration)
Zvolí aktívnu konfiguráciu zariadenia. Parameter configuration získate z deskriptora, je to číslo (index) konfigurácie.

usb_claim_interface(usb_dev_handle * dev, int interface)
Aktivuje rozhranie zariadenia. Parameter interface získate z deskriptora, je to číslo (index) rozhrania konfigurácie. Index musí byť platný v rámci zvolenej konfigurácie.

usb_set_altinterface(usb_dev_handle * dev, int alternate)
Aktivuje alternatívne rozhranie zariadenia. Parameter alternate získate z deskriptora, je to číslo (index) alternatívneho nastavenia rozhrania. Niektoré zariadenia, ako napr. naše, rozhranie aktivuje až pri zvolení alternatívneho nastavenia rozhrania.

usb_control_msg(usb_dev_handle * dev, int requesttype, int request, int value, int index, char * bytes, int size, int timeout)
Odošle kontrolný paket. Parametre, ktoré funkcia preberá, závisia od smeru prenosu (IN/OUT) a ich hodnoty sú v obsiahnuté v SETUP pakete. Parametre bytes a size slúžia k nastaveniu dodatočných dát (payload) SETUP paketu. V prípade, že od zariadenia dáta vyžadujeme (smer IN), parameter requesttype musí obsahovať adresu kontrolného endpointu IN, v opačnom prípade adresu endpointu OUT. Funkcia vracia počet prenesených bajtov dodatočných dát (payload) alebo zápornú hodnotu pri chybe.

usb_bulk_write(usb_dev_handle * dev, int ep, char * bytes, int size, int timeout)
Odošle dáta na bulk endpoint. Parameter ep je adresa BULK OUT endpointu, bytes a size slúžia k nastaveniu dát, ktoré budú pomocou bulk prenosu odoslané. Parameter timeout určuje max. čas v milisekundách na vykonanie operácie. Funkcia vráti počet odoslaných bajtov alebo zápornú hodnotu pri chybe.

usb_bulk_read(usb_dev_handle * dev, int ep, char * bytes, int size, int timeout)
Prijme dáta z bulk endpointu. Parameter ep je adresu BULK IN endpointu, bytes a size slúžia k nastaveniu dát, ktoré budú pomocou bulk prenosu prijaté. Parameter timeout určuje max. čas v milisekundách na vykonanie operácie. Funkcia vráti počet prijatých bajtov alebo zápornú hodnotu pri chybe.

usb_interrupt_write(usb_dev_handle * dev, int ep, char * bytes, int size, int timeout)
Podobne ako usb_bulk_write, ale prenos sa realizuje cez INTERRUPT OUT endpoint.

usb_interrupt_read(usb_dev_handle * dev, int ep, char * bytes, int size, int timeout)
Podobne ako usb_bulk_read, ale prenos sa realizuje cez INTERRUPT IN endpoint.

Kde získať libusb

libusb je opensource projekt, spravovaný na stránke http://www.libusb.org [ http://www.libusb.org ]. Pre vývojárov pod Windows je určená stránka http://sourceforge.net/projects/libusb-win32 [ http://sourceforge.net/projects/libusb-win32 ], kde nájdete už zostavené ovládače a knižnice spolu s hlavičkovými súbormi. Release verzia obsahuje taktiež utilitu na vytvorenie inštalácie ovládača (INF Wizard), ktorú si bližšie popíšeme v ďalšom článku spolu s ukážkou vytvorenia, nainštalovania a použitia ovládača.

Pre prilinkovanie knižnice k projektu potrebujete vo svojom vývojovom prostredí urobiť 3 veci:

  • Pridať cestu k hlavičkovým súborom libusb do vášho projektu. Vo Visual Studiu 2010 a vyššie kliknete pravým tlačidlom na projekt > Properties > Configuration Properties > VC++ Directories > Include Directories > rozbalíte zoznam a pridáte cestu, napr. „C:\libusb-win32-bin-1.2.6.0\include“
  • Pridať cestu k lib knižniciam do vášho projektu. Pre Visual Studio 2010 a vyššie platí obdobný postup ako v prvom kroku, ale namiesto Include Directories pridáte cestu do Library Directories, napr. „C:\libusb-win32-bin-1.2.6.0\lib\msvc“
  • Prilinkovať knižnicu k projektu môžete dvoma spôsobmi, buď v zdrojovom súbore pridáte nasledovný riadok:
    #pragma comment(lib, "libusb.lib")
    alebo v nastaveniach projektu pridáte knižnicu libusb.lib manuálne. Pre Visual Studio 2010 a vyššie kliknete pravým tlačidlom na projekt > Properties > Configuration Properties > Linker > Input > Additional Dependencies > rozbalíte zoznam a pridáte hodnotu: libusb.lib

Záver

V ďalšej časti preskočíme tvorbu ovládača pre WinUSB a ukážeme si, ako vytvoriť klientsku aplikáciu v C#, ktorá bude pomocou PInvoke (Platform Invoke [ http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx ]) volať funkcie ovládača, resp. rozhrania, ktoré sme týmto článkom dokončili.

Zdrojový kód ovládača si môžete stiahnuť tu: http://programujte.com/storage/redakce/2012/27_ovladac.zip [ http://programujte.com/storage/redakce/2012/27_ovladac.zip ]


Článek stažen z webu Programujte.com [ http://programujte.com/clanek/2012120700-vyvoj-usb-ovladaca-4-cast/ ].