Přemýšlel jsem jaké téma zvolit pro další pokračování „Začínáme s STM32F4 kitem“ a napadlo mne, že by bylo záhodno napsat něco o USB. Jednak je otázka USB pro mnoho programátorů zastřena tajemnem, za druhé v češtině nic moc o tom napsáno není. Všude je obvykle jen první díl a dál se už nepokračuje. No a třetí důvod je ten že nový F4 kit umožňuje vytvoření jako OTG device tak HOST. Takže s chutí do USB. Začneme jak jinak než teoreticky a skončíme jak jinak než prakticky.


Úvod

Úvod můžeme přeskočit, stačí se podívat na první díl serveru mcu.cz, který naleznete zde.

K tomu, co je tam napsáno bych rád doplnil následující. V textu mluvíme o USB 2.0 a nový typ USB 3.0. přenecháme moudřejším. Stejně je toho dost k vysvětlení. V dalším textu tedy navazuji na první text, doporučuji se na něj podívat. Jde o návod na práci s STM32F4, takže konkrétní příklady budou cíleny tento mcu.
Kromě v prvním odkazovaném článku popsaných konektorů je od té doby k dispozici již několik nových typů. Jde o konektory micro a mini. Viz obrázek PLUGů.





Plug je koncovka která se zastrkuje do Receptacle, což je o něco větší konektor do kterého Plug pasuje
RECEPTACLE odpovídají Plugům s výjimkou jednoho univerzálního nazývaného Micro AB, do kterého jak je z názvu patrné pasuje jak Micro A, tak i Micro B plug.
Jak vypadá můžete vidět zde.

Pinů je v Micro a Mini celkem 5:
1. VBUS +5V
2. D-
3. D+
4. ID (identifikace A/B – v A konektoru je pin připojen na GND, v B konektoru nikoliv)
5. GND


Takže i když to není v prvním textu příliš zdůrazněno, situace v praxi vypadá tak, že máme jeden USB HOST, což je master (např. PC), který ovládá USB sběrnici, do které je zapojeno buď přímo zařízení USB Device (což může být třeba USB Flash disk) nebo přes rozšiřující USB HUB, což je rozdělovač USB sběrnice na více paprsků (rozuměj více Receptacle, do kterých jde něco strkat). Rozšiřování sběrnice pomocí HUB zanedbejme a uvažujme jen dvojici HOST – DEVICE (HoDe).
HoDe spolu komunikuje tak, že mezi zařízeními jsou vytvořeny virtuální spojení (nazývané PIPE) a můžeme si představovat že jde o několik spojení třeba pomocí USART. Minimálně potřebujeme 2 tato spojení. Prvním je Control Pipe, což je ovládací kanál, kterým můžeme sdělovat z HOST směrem k DEVICE požadavky (např. zresetuj se!) a naopak zpětně pomocí něj posílí DEVICE odpovědi (např. počkej jsem zaneprázdněn). A pak existuje jedno nebo více dalších spojení datových (datová spojení jsou na rozdíl od Control Pipe vždy jednosměrná). Na straně USB Device tato spojení končí v něčem co nazýváme Endpoint (pochopitelně že i na straně HOST, ale to teď nerozebírejme, zatím budujeme USB Device a až budeme popisovat tvorbu HOST, tak popíšu rozdíly). Na Endpoint můžeme hledět jako na obdobu přijímacího nebo vysílacího bufferu USART.





Na grafice jsou dvě datová spojení, ale tomu tak vždy být nemusí. Pokud nepotřebujeme do zařízení žádná data posílat, tak si vystačíme jen s jednou přijímací rourou (typicky myš, voltmetr, teploměr) nebo můžeme jen posílat data a pak nepotřebujeme přijímací rouru (např. displej, sada LED, atd.).
Spojením (rourou) Control můžeme posílat a přijímat jen „Control Transfers“ což jsou příkazy pro zařízení, dotazy atd. Ostatní typy komunikací se týkají jen datového spojení a tedy přenášení dat. Rozlišujeme tři různé typy.
Interrupt Transfers – po přerušení se pošle malé množství dat v definovaném čase.
Bulk Transfers – posílá se velké množství dat, přičemž na rozdíl od prvního typu není garantována rychlost ani latence přenosu. Garantuje se správnost dat přenosu.
Isochronous Transfer – posílá se velké množství dat s garantovanou dobou latence a je vyhrazena potřebné šíře sběrnice na USB. Není garantována správnost dat přenosu.

Z výše uvedeného je zřejmé, že obvykle vystačíme s prvním typem datového přenosu (Interrupt), tedy posíláním menšího počtu bajtů postupně po přerušeních. Pokud bychom potřebovali přenést více dat (např. načtená data z mcu), tak použijeme Bulk a přeneseme celý blok dat.
Isochronous se používá třeba pro přenos hudby a tam když nějaký bit vypadne, tak se asi nic neděje. Já tenhle typ komunikace ještě nikdy nevyzkoušel.

Jak se roury vytvoří?

Po vzniku fyzického spojení HoDe Host vytuší že se něco děje, protože USB Device má jeden z datových vodičů připojeno pomocí rezistoru 1.5 KOhm na +3.3V (podle rychlosti zařízení). Podrobnosti zde.
A Host začne proces nazývaný Enumerace – zkrátka se zařízení vyptá, co je zač, přidělí mu číslo - adresu na USB sběrnici, povolí potřebný napájecí proud pro toto zařízení a vytvoří se požadovaná spojení (datové roury).

Postupně to vypadá takto:
1. Device se připojí k Host
2. Host provede reset Device a vyžádá si Decriptor (to je popis zařízení Device)
3. Device odpoví, Host přidělí Device adresu v systému USB
4. Host osloví pod nově přidělenou adresou Device a vyžádá si opakovaně Descriptor
5. Device pošle Descriptor
6. Host nalezne.INF soubor, odpovídající číslům PID a VID (součást popisu Device). .INF soubor specifikuje Driver, který dané USB Device potřebuje
7. Host nahraje Driver do paměti
8. Host nastaví konfiguraci a vytvoří Data pipes
a pak už se jen vesele komunikuje

Jak chodí fyzicky data na USB kabelu, moc vědět nepotřebujete, berte to jako Black box, že se zázrakem objeví ta správná data na správném místě. Koho zajímají podrobnosti o NRZI Encoding a Bit Stuffing, ať si je najde. Stejně tak pro naše potřeby není zajímavé, jak probíhá CRC kontrola, potvrzování přenosů na lince, atd. Jen je třeba mít na paměti, že věci mohou být ve skutečnosti složitější, než se nám jeví.


Endpointy

Endpoint je důležité součást USB. Jak už výše bylo zmíněno, můžeme jej považovat za určitou obdobu přijímacího nebo vysílacího bufferu USART jednotky. Nicméně podrobněji si jej probrat musíme, bude se to hodit až budeme psát konkrétní zařízení.

  • Control Endpoint

Jak bylo uvedeno, tento endpoint je obousměrný a proto máme v zařízení vždy dva (IN a OUT), zkrátka USART číslo 0 má přijímací i vysílací buffer.

  • Interrupt Endpoint

Tento Endpoint je vždy buď IN nebo OUT. Tedy tímto USARTem můžeme jen přijímat nebo vysílat (podle toho, jak zařízení popíšeme v Descriptoru - viz dál v textu). Velikost bufferu je funkcí rychlosti datového přenosu. Pokud máme zařízení HighSpeed (to asi zatím moc často mít nebudeme), tak buffer může mít max 1024 bytů. Pro FullSpeed (to budeme mít nejčastěji) má buffer maximální velkost 64 bytů. A u LowSpeed si musíme vystačit s pouhými 8-mi byty.

  • Bulk Endpoint

Tento Endpoint je také vždy buď IN nebo OUT. Tedy tímto USARTem můžeme jen přijímat nebo vysílat (podle toho, jak zařízení popíšeme v Descriptoru - viz dál v textu). Velikost bufferu je funkcí rychlosti datového přenosu. Pokud máme zařízení HighSpeed (to asi zatím moc často mít nebudeme), tak buffer může mít max 512 bytů. Pro FullSpeed (to budeme mít nejčastěji) má buffer maximální velkost 64 bytů. A u LowSpeed utřeme hubu, neboť tento přenos neumí.

Velikosti Endpointů jsou dány velikostí paketů, které nám cestují po kabelu. To je zatím tak všechno, co nutně potřebujeme vědět o fungování. A konečně můžeme přistoupit k praktickým věcem.


Descriptory

Každé Device zařízení je popsáno descriptory, což je sada konstant, která se pošle USB Host zařízení, aby věděl co s Device má dělat. Po enumeraci již není možné nic změnit (jedině resetem zařízení a novou Enumerací, kdy se pošle pozměněný descriptor). Jsou různé typy descriptorů a bohužel je musíme probrat. Začneme Device Descriptor, které popíše o jaké zařízení jde.

  • Device Descriptor


0bLength1Number

Size of the Descriptor in Bytes (18 bytes)

1bDescriptorType1Constant

Device Descriptor (0x01)

2bcdUSB2BCD

USB Specification Number which device complies too.

4bDeviceClass1Class

Class Code (Assigned by USB Org)

If equal to Zero, each interface specifies it’s own class code

If equal to 0xFF, the class code is vendor specified.

Otherwise field is valid Class Code.

5bDeviceSubClass1SubClass

Subclass Code (Assigned by USB Org)

6bDeviceProtocol1Protocol

Protocol Code (Assigned by USB Org)

7bMaxPacketSize1Number

Maximum Packet Size for Zero Endpoint. Valid Sizes are 8, 16, 32, 64

8idVendor2ID

Vendor ID (Assigned by USB Org)

10idProduct2ID

Product ID (Assigned by Manufacturer)

12bcdDevice2BCD

Device Release Number

14iManufacturer1Index

Index of Manufacturer String Descriptor

15iProduct1Index

Index of Product String Descriptor

16iSerialNumber1Index

Index of Serial Number String Descriptor

17bNumConfigurations1Integer

Number of Possible Configurations




V praxi to může vypadat tak. že máme např. soubor usb_desc.c, který obsahuje definice descriptorů pro dané zařízení. Příklad části tohoto souboru odpovídající Device Descriptoru.
const uint8_t MASS_DeviceDescriptor[MASS_SIZ_DEVICE_DESC] =
  {
    0x12,   /* bLength  */
    0x01,   /* bDescriptorType 01=Device descriptor*/
    0x00,   /* bcdUSB, version 2.00= 0200 */
    0x02,
    0x00,   /* bDeviceClass : each interface define the device class */
    0x00,   /* bDeviceSubClass */
    0x00,   /* bDeviceProtocol */
    0x40,   /* bMaxPacketSize0 0x40 = 64 */
    0x83,   /* idVendor     (0483) */
    0x04,
    0x20,   /* idProduct    (5720) */
    0x57,
    0x00,   /* bcdDevice 2.00 = 0200*/
    0x02,
    1,              /* index of string Manufacturer  */
    2,              /* index of string descriptor of product*/
    3,              /* */
    0x01    /*bNumConfigurations */
  };


Jak je z uvedeného vidět, mnoho čísel a řetězců zkrátka můžeme převzít, ale je potřeba vědět, co které znamená abychom mohli upravit program pro řízení chování USB dle svých požadavků. V seznamu je důležitý parametr idVendor a idProduct, to jsou ty čísla PID a VID, která pak chce PC, když hledá ovladač. S produkty ST je možné používat čísla PID a VID z dema od ST.
Dříve jsem uvedl, že Device descriptor není bohužel jediným descriptorem, ale že máme i další.

  • Configuration Descriptor


0bLength1Number

Size of Descriptor in Bytes

1bDescriptorType1Constant

Configuration Descriptor (0x02)

2wTotalLength2Number

Total length in bytes of data returned

4bNumInterfaces1Number

Number of Interfaces

5bConfigurationValue1Number

Value to use as an argument to select this configuration

6iConfiguration1Index

Index of String Descriptor describing this configuration

7bmAttributes1Bitmap

D7 Reserved, set to 1. (USB 1.0 Bus Powered)

D6 Self Powered

D5 Remote Wakeup

D4..0 Reserved, set to 0.

8bMaxPower1mA

Maximum Power Consumption in 2mA units




Ha, věci se začínají komplikovat! Ale není to taková hrůza, jak to vypadá. V konfiguraci vlastně nastavíme kolik a jaké USB interfersy zařízení bude mít a jako poslední v této tabulce jakou bude mít zařízení maximální potřebu (v jednotkách 2 mA, takže doporučuji dávat 500 mA, což je maximum). A jak bmAttributes dáte 0xC0 což je bus powered. I když pak máte vlastní napájení vašeho USB zařízení, nijak to nevadí že si HOST myslí že USB Device nějakou šťávu potřebovat může.

A opět praktický příklad části tohoto souboru odpovídající konfiguračnímu Descriptoru.
const uint8_t MASS_ConfigDescriptor[MASS_SIZ_CONFIG_DESC] =
  {
    0x09,   /* bLength: Configuation Descriptor size */
    0x02,   /* bDescriptorType: Configuration */
    MASS_SIZ_CONFIG_DESC,
    0x00,
    0x01,   /* bNumInterfaces: 1 interface */
    0x01,   /* bConfigurationValue: */
    /*      Configuration value */
    0x00,   /* iConfiguration: */
    /*      Index of string descriptor */
    /*      describing the configuration */
    0xC0,   /* bmAttributes: */
    /*      bus powered */
    0xFF,   /* MaxPower 500 mA */

    /******************** Descriptor of Mass Storage interface ********************/
    /* 09 */
    0x09,   /* bLength: Interface Descriptor size */
    0x04,   /* bDescriptorType: */
    /*      Interface descriptor type */
    0x00,   /* bInterfaceNumber: Number of Interface */
    0x00,   /* bAlternateSetting: Alternate setting */
    0x02,   /* bNumEndpoints*/
    0x08,   /* bInterfaceClass: MASS STORAGE Class */
    0x06,   /* bInterfaceSubClass : SCSI transparent*/
    0x50,   /* nInterfaceProtocol */
    4,          /* iInterface: */
    /* 18 */
    0x07,   /*Endpoint descriptor length = 7*/
    0x05,   /*Endpoint descriptor type */
    0x81,   /*Endpoint address (IN, address 1) */
    0x02,   /*Bulk endpoint type */
    0x40,   /*Maximum packet size (64 bytes) */
    0x00,
    0x00,   /*Polling interval in milliseconds */
    /* 25 */
    0x07,   /*Endpoint descriptor length = 7 */
    0x05,   /*Endpoint descriptor type */
    0x02,   /*Endpoint address (OUT, address 2) */
    0x02,   /*Bulk endpoint type */
    0x40,   /*Maximum packet size (64 bytes) */
    0x00,
    0x00     /*Polling interval in milliseconds*/
    /*32*/
  };


Ale co to, praktický příklad je delší než předcházející tabulka?!? No je to proto, že součástí Configuration Descriptors jsou i Interface Descriptor (v konfiguraci nejméně jeden Interface) a Endpoint Descriptor (nejméně jeden přijímací nebo vysílací Endpoint). V příkladu výše máme jeden interface (konkrétně MassStorage) a ten má dva Endpointy (přijímací a vysílací, protože do Mass Storage zařízení musíme zadat zapisovat i z něj číst). Pokud bychom dělali třeba USB vstupní zařízení (myš, joystick) tak bychom měli descriptor tabulku kratší, protože by obsahovala jen jeden Endpoint (konkrétně Interrupt vysílací Endpoint).

  • Interface Descriptor


0bLength1Number

Size of Descriptor in Bytes (9 Bytes)

1bDescriptorType1Constant

Interface Descriptor (0x04)

2bInterfaceNumber1Number

Number of Interface

3bAlternateSetting1Number

Value used to select alternative setting

4bNumEndpoints1Number

Number of Endpoints used for this interface

5bInterfaceClass1Class

Class Code (Assigned by USB Org)

6bInterfaceSubClass1SubClass

Subclass Code (Assigned by USB Org)

7bInterfaceProtocol1Protocol

Protocol Code (Assigned by USB Org)

8iInterface1Index

Index of String Descriptor Describing this interface




Příklad si mohu ušetřit, máte jej už výše Důležitá je položka bInterfaceClass, kterou se nastavuje jaké je to vlastně zařízení. O možných HID zařízeních si povíme v dalším dílu. No a následuje poslední popisovač a to popisovač Endpointu.

  • Endpoint Descriptor


0bLength1Number

Size of Descriptor in Bytes (7 bytes)

1bDescriptorType1Constant

Endpoint Descriptor (0x05)

2bEndpointAddress1EndpointEndpoint Address
Bits 0..3b Endpoint Number.
Bits 4..6b Reserved. Set to Zero
Bits 7 Direction 0 = Out, 1 = In (Ignored for Control Endpoints)
3bmAttributes1BitmapBits 0..1 Transfer Type
    00 = Control
    01 = Isochronous
    10 = Bulk
    11 = Interrupt
Bits 2..7 are reserved. If Isochronous endpoint,
Bits 3..2 = Synchronisation Type (Iso Mode)
    00 = No Synchonisation
    01 = Asynchronous
    10 = Adaptive
    11 = Synchronous
Bits 5..4 = Usage Type (Iso Mode)
    00 = Data Endpoint
    01 = Feedback Endpoint
    10 = Explicit Feedback Data Endpoint
    11 = Reserved
4wMaxPacketSize2Number

Maximum Packet Size this endpoint is capable of sending or receiving

6bInterval1Number

Interval for polling endpoint data transfers. Value in frame counts. Ignored for Bulk & Control Endpoints. Isochronous must equal 1 and field may range from 1 to 255 for interrupt endpoints.




Tím máme popisovače za sebou. Je důležité vědět že popisovače zařízení jsou nutná součást enumerace po připojení zařízení Device k Host a slouží k tomu, aby Host věděl jaké má vytvořit spojení (roury-pipes) s USB zařízením a v jaké podobě mu může posílat a v jaké podobě od něj může očekávat data. V dalším dílu si ukážeme jak je řešeno přijímání dat přes USB a vysílání dat v mcu STM32F4. A jak si ze svého kitu můžete udělat myš, blikátko nebo USB disk.


Odkaz

Předcházející díl naleznete zde.