× Aktuálně z oboru

Programátoři po celém světě dnes slaví Den programátorů [ clanek/2018091300-programatori-po-celem-svete-dnes-slavi-den-programatoru/ ]
Celá zprávička [ clanek/2018091300-programatori-po-celem-svete-dnes-slavi-den-programatoru/ ]

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

[ http://programujte.com/profil/13267-matej-tomcik/ ]Google [ ?rel=author ]       [ http://programujte.com/profil/118-zdenek-lehocky/ ]Google [ ?rel=author ]       18. 11. 2013       21 605×

V tejto časti si vytvoríme GUI klientsku aplikáciu v jazyku C#, ktorej základom bude rozhranie na komunikáciu medzi ovládačom zariadenia a .Net aplikáciou vytvorené s pomocou PInvoke (Platform Invoke).

Úvod

V tomto kroku máme funkčné USB zariadenie a ovládač s ním komunikujúci pomocou libusb. Ďalším krokom je vytvorenie koncovej aplikácie, pomocou ktorej môže používateľ hardvér ovládať. Pre zmenu som si vybral platformu .NET a jazyk C#, vďaka ktorým bude jednoduchšie demonštrovať princípy.

Aplikácia bude tvorená jedným oknom, v ktorom sa bude zobrazovať stav tlačidiel na zariadení, teplota procesora a napätie na potenciometri. Taktiež bude obsahovať prvky na ovládanie LED diód, na vypísanie textu a odoslanie bitmapy na LCD. Pri vytvorení okna sa aplikácia pokúsi otvoriť spojenie s USB zariadením; pokiaľ zariadenie nebude pripojené, skončí s chybovým hlásením.

Celú komunikáciu so zariadením vložíme do jedného objektu, ktorý bude pomocou P/Invoke (Platform Invoke) [ http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx ] komunikovať s natívnou knižnicou ovládača vytvorenou v minulom dieli tohto seriálu. Tento objekt alebo wrapper sa bude taktiež starať o spracovanie vzniknutých chýb a namiesto číselných hodnôt vrátených ovládačom vyhodí príslušnú výnimku.

P/Invoke – Platform Invoke

Pokiaľ v .NET programujete už nejaký ten čas, je dosť možné, že ste sa stretli s potrebou importovať funkcie natívnych knižníc. V C/C++ by ste toho dosiahli jednoducho prilinkovaním knižnice k vášmu programu alebo by ste knižnicu načítali pomocou funkcie LoadLibrary, získali adresu funkcie volaním GetProcAddress a pretypovaním by vám vznikol odkaz na funkciu, ktorú by ste zavolali ako každú inú. V .Net to ale funguje trochu inak, na volanie funkcií v natívnych knižniciach musíme využiť DllImport (System.Runtime.InteropServices v knižnici System.Core).

V prvom kroku si v zdrojovom súbore zadeklarujeme použitie názvoslovia:

using System.Runtime.InteropServices;

Potom nám stačí v triede deklarovať statickú metódu nasledovným spôsobom:

/// <summary>
/// Vyčistí obrazovku jednou farbou
/// </summary>
/// <param name="color">Farba</param>
/// <returns></returns>
[DllImport("DeviceWrapper.dll",
 CallingConvention = CallingConvention.Cdecl)]
private static extern uint ClearScreen(uint color);

Čím prekladač vie, že metóda ClearScreen je externá a nachádza sa v knižnici DeviceWrapper.dll (to je náš ovládač). Každá takto vložená funkcia musí byť označená ako static extern s atribútom DllImport.

Naša klientska aplikácia bude importovať všetky funkcie exportované v ovládači DeviceWrapper, nebudú sa ale používať priamo. Napr. funkcia ClearScreen preberá jeden argument, ktorým je 24bitové číslo reprezentujúce farbu. Návratová hodnota je taktiež číslo (0 v prípade, že funkcia uspeje alebo iná hodnota popisujúca chybu). My ale túto metódu zabalíme v objekte (wrapper) tak, aby parameter farby nebol číslo, ale typ System.Color, a odstránime návratový typ, zmeníme ju na void. V prípade, že volanie funkcie ClearScreen z knižnice DeviceWrapper.dll zlyhá, nevrátime volajúcemu kód chyby, ale vyhodíme výnimku. Je to omnoho prijateľnejší spôsob. Týmto spôsobom dosiahneme to, aby bola metóda ClearScreen volaná vždy v správnom kontexte.

/// <summary>
/// Vyčistí obrazovku
/// </summary>
/// <param name="color">Farba</param>
public void ClearScreen(Color color)
{
    // Spojenie so zariadením musí byť otvorené
    if (!isOpen)
        throw new DeviceFailException(
            DeviceFailException.DeviceError.NotOpen);

    // Zavolať funkciu ClearScreen z DeviceWrapper.dll
    uint result = ClearScreen((uint)color.ToArgb());

    // Pokiaľ volanie zlyhalo, vyhodíme výnimku s kódom chyby
    if (result != 0)
        throw new DeviceFailException(
            (DeviceFailException.DeviceError)result);
}

Rozhranie na komunikáciu s ovládačom

Základ aplikácie bude tvorený jedným objektom – rozhraním na komunikáciu s ovládačom. Pri štarte aplikácie sa vytvorí inštancia tohto objektu, otvorí sa spojenie s USB zariadením a ďalšia komunikácia s ním už bude prebiehať iba pomocou tohto objektu. Pri ukončení aplikácie sa objekt odstráni, tým sa zatvorí spojenie s ovládačom a zariadením. Na obrázku je znázornené, aké metódy bude objekt obsahovať a ďalšie pomocné štruktúry, triedy a dátové typy.

Je dôležité uvedomiť si, že dátové štruktúry (pakety) sú rovnaké pre zariadenie, ovládač aj koncovú aplikáciu. Preto pri vývoji ovládača pracujeme s rovnakými dátovými typmi v zariadení aj na strane hosta. Táto klientska aplikácia definuje štyri takéto štruktúry, a to usbapp_bulk_out_t, usbapp_control_in_t, usbapp_control_out_t a usbapp_interrupt_in_t.

Ďalej potrebujeme definovať výnimky, ktoré objekt vyhodí pri chybnej komunikácii alebo pri nesprávnom použití rozhrania. Na to nám budú slúžiť dve triedy, DeviceFailException, ktorá bude vyhodená, ak dôjde k chybe v ovládači alebo zariadení, a DeviceNotOpenException, ktorú rozhranie vyhodí, ak sa programu nepodarí nadviazať spojenie so zariadením.

Nakoniec zadefinujeme štruktúry a enumy, ktoré poskytnú informácie z paketov cieľovému používateľovi rozhrania. Ako príklad uvediem nastavenie LED a LCD.

Aplikácia potrebuje odoslať zariadeniu príkaz, aby rozsvietilo modrú a zelenú LED diódu, zaplo LCD displej a taktiež aby posunulo obsah LCD displeja o 50 px. Keďže každé nastavenie v pakete je číselné, užívateľ nášho rozhrania by musel poznať, aké čísla pripadajú na modrú a zelenú LED. Taktiež by musel vedieť, akým príznakom zapne LCD displej. Preto je dôležité, aby naše rozhranie tieto čísla skrylo za enum dátové typy ako napr. zoznam LED diód a ich číselné hodnoty:

/// <summary>
/// Nastavenia LED diód
/// </summary>
public enum Led
{
    None = 0,
    Blue = 0x1,
    Green = 0x2,
    Amber = 0x4
};

Potom môže užívateľ jednoduchým spôsobom zostaviť nastavenia a odoslať ich zariadeniu:

// Vytvoriť nastavenie LED a LCD
Device.LedLcdOptions ledLcd = new Device.LedLcdOptions(
    Device.Led.Blue | Device.Led.Green, // Modrá a zelená LED
    0, // Oranžová nebude svietiť
    Device.DisplayOption.DisplayOn, // Zapnúť LCD displej
    0, // Displej nebude podsvietený
    50); // Obsah displeja bude posunutý o 50 px
 
// Odoslať nastavenie zariadeniu
device.SetupLedLcd(ledLcd);

Metóda Device.SetupLedLcd(LedLcdOptions options) skontroluje, či je spojenie na zariadenie otvorené a ak áno, zostaví paket usbapp_control_out_t z nastavení, ktoré užívateľ predal ako argument options. Zároveň skontroluje platnosť nastavení a to, či hodnota PWM a hodnota podsvietenia LCD displeja neprekročia max. hodnoty stanovené USB zariadením. Následne paket posunie ovládaču volaním funkcie SetupDevice z knižnice ovládača DeviceWrapper.dll.
Tá vytvorí USB paket, jeho dáta naplní obsahom štruktúry usbapp_control_out_t a odošle ho zariadeniu, ktoré paket prijme a spracuje. Akonáhle host potvrdí príjem paketu zariadením, funkcia SetupDevice vráti hodnotu určujúcu, či prenos prebehol v poriadku alebo sa vyskytla chyba.

Naše rozhranie túto vrátenú hodnotu porovná a pokiaľ zistí, že počas prenosu nastala chyba, vyhodí výnimku.

/// <summary>
/// Nastavi LED a LCD
/// </summary>
/// <param name="options">LED a LCD nastavenia</param>
public void SetupLedLcd(LedLcdOptions options)
{
    if (!isOpen)
        throw new DeviceFailException(
            DeviceFailException.DeviceError.NotOpen);
 
    // Vytvoriť paket obsahujúci nastavenia LED a LCD
    usbapp_control_out_t data = new usbapp_control_out_t();
    data.leds = options.Leds;
    data.pwmLedDuty = options.PwmLedDuty > MaxPwm ?
        MaxPwm : options.PwmLedDuty;
    data.display = options.LcdOptions;
    data.backlight = options.Backlight > MaxBacklight ?
        MaxBacklight : options.Backlight;
    data.displayScroll = options.LcdScroll;

    // Posunúť paket ovládaču, ktorý ho odošle
    uint result = SetupDevice(ref data);

    // Vyhodiť výnimku ak počas prenosu vznikla chyba
    if (result != 0)
        throw new DeviceFailException(
            (DeviceFailException.DeviceError)result);
}

Obdobným spôsobom trieda Device poskytuje prístup k ostatným funkciám ovládača. V princípe je potrebné iba „zabaliť“ volania ovládača takým spôsobom, aby klientska aplikácia na platforme .NET mohla využívať tieto metódy v súlade s konvenciami .NET a taktiež aby bolo možné spracúvať výnimky namiesto chybových kódov.

Front end – používateľské rozhranie

Na otestovanie funkčnosti poslúži jednoduchá GUI aplikácia, ktorá pri svojom štarte skontroluje, či je k počítaču pripojené naše zariadenia a ak áno, vytvorí klientske rozhranie pre komunikáciu s týmto zariadením. Používateľ tak bude mať možnosť jednoducho odosielať príkazy zariadeniu pomocou nami vytvoreného rozhrania.

Komunikácia bude pozostávať z troch častí:

  1. Príkazy odosielané klientom do zariadenia – nastavenie LED a vykresľovanie na LCD
  2. Pravidelná aktualizácia hodnôt z ADC prevodníka
  3. Vlákno bežiace na pozadí aplikácie čakajúce na prerušenia zo strany zariadenia

Keďže väčšinu práce bude mať na starosti rozhranie v triede Device, GUI aplikácii zostáva konvertovať vstupné hodnoty od používateľa a zobrazovať výstup zo zariadenia a čo je podstatnejšie, spracovanie chýb. V našom prípade pri vzniku chyby program zobrazí bližšie informácie o príčine vzniku chyby a následne sa ukončí.

Záver

A na záver si môžete stiahnuť zdrojový kód aplikácie [ http://matejtomcik.com/Download/?n=SAM3EvalKitFrontEnd ] a pozrieť ukážku toho, ako zariadenie a klientska aplikácia fungujú v praxi:


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