
La comunicazione tramite Universal Serial Bus (USB) è ormai molto diffusa ed è diventata un’interfaccia “obbligatoria” per tutti i dispositivi embedded che devono essere collegati ad un personal computer. Senza dubbio un pregio dell’USB è la funzione plug&play che permette di riconfigurare automaticamente il bus USB all’aggiunta o rimozione di una periferica. Una panoramica generale del bus USB, del suo funzionamento e del protocollo: tutte le informazioni necessarie per implementare il protocollo USB su un dispositivo embedded.
L’UNIVERSAL SERIAL BUS
Pregi e difetti
Al bus si possono facilmente collegare un certo numero di dispositivi e, entro certe condizioni, le periferiche USB possono essere alimentate direttamente dal bus. La comunicazione può avvenire a tre diverse velocità: low (1.5Mbit/s), full (12Mbit/s), high (480Mbit/s), fino ad arrivare con la superspeed. Dispositivi funzionanti a diversa velocità di comunicazione possono condividere lo stesso bus. La massima distanza per una connessione USB è di 5m anche se è possibile arrivare fino a trenta metri interponendo 5 hub tra PC e la periferica. La comunicazione su bus USB è del tipo master/slave e, per un dispositivo embedded slave, non è possibile utilizzare il bus USB in assenza di un Host. Come supplemento della specifica USB2.0 è stato introdotto il protocollo USB OTG (On The Go) che aggiunge alla periferica slave alcune funzioni normalmente svolte dal master per consentire la connessione punto-punto di due apparati USB anche in assenza di un Host. Aspetto da non sottovalutare per chi volesse realizzare un dispositivo dotato di interfaccia USB è la necessita di acquisire una coppia di codici univoci VendorID e ProductID che identificano il prodotto. Il sistema operativo del PC host riconosce la periferica USB collegata ad una porta tramite questa coppia di codici. Da qui si capisce l’importanza del codice e soprattutto della sua univocità. Il VendorID viene assegnato dal consorzio USB e richiede il pagamento di una tassa mentre il Prdouct ID può essere assegnato dal produttore della scheda.
I COMPONENTI PRINCIPALI
Come già accennato, la comunicazione sul bus USB è di tipo master/slave. Un unico master è responsabile della configurazione e della comunicazione attraverso il bus. I tre componenti principali del bus sono l’Host, l’Hub e i device (spesso verrà usato impropriamente il termine periferica anziché device). L’Hub è in realtà un tipo particolare di device, nella specifica USB2.0 infatti i device si dividono in Hub e funzioni.
L’Host
È il responsabile della comunicazione sul bus (master). L’host inizia ogni comunicazione ed ha il compito di riconfigurare dinamicamente la rete ogni volta che un dispositivo viene connesso o disconnesso.
L’Hub
È l’elemento centrale delle connessioni a stella. Ogni hub permette di moltiplicare le connessioni USB. Gli hub possono essere connessi in cascata (massimo fino a 5 livelli) e gestiscono i device collegati alle loro porte. L’hub ha anche la funzione di adeguare, se necessario, la velocità di comunicazione con il device. Caso tipico è quello dell’hub high speed a cui è collegato un device low speed: la comunicazione tra host e hub avviene in modalità high speed, l’hub poi adegua la comunicazione per la periferica low speed. Per chi realizza un driver di comunicazione su bus USB l’hub è trasparente: è compito del protocollo riconoscere la presenza dell’hub interposto tra host e periferica e gestire questa comunicazione.
I device o funzioni
Sono i punti terminali della rete a stella e sono costituiti da tutti i dispositivi (mouse, stampanti, ecc…). Un device deve rispondere a tutti i comandi fondamentali del protocollo per consentire all’host di riconoscere la periferica durante la fase di configurazione. Un device potrebbe essere paragonato ai dispositivi slave di una rete RS485 anche se il paragone è improprio perché, come si vedrà, un unico device può rappresentare più slave.
LA CONNESSIONE
Il cavo utilizzato per le connessioni su bus USB è un cavo schermato a 4 fili. Due fili sono utilizzati per la comunicazione e due per distribuire l’alimentazione alle periferiche. I connettori sono noti e sono di tre tipi: USB-A di forma rettangolare che si innesta lato Hub. Il connettore USB-B che si innesta lato periferica o in alternativa il connettore mini-USB. La trasmissione avviene su linea differenziale che si indica con D+ e D-. Gli stati ammessi per le linea sono solo tre:
- D+ e D- a livello logico basso: è la condizione di reset utilizzata dall’host per segnalare al device la necessità di eseguire un reset della periferica;
- D+ e D- nello stato di 0 differenziale: un driver in ricezione riconosce questa condizione quando (D-) – (D+) > 200mV e (D-) > 2V;
- D+ e D- nello stato di 1 differenziale: un driver in ricezione riconosce questa condizione quando (D+) – (D-) > 200mV e (D+) > 2V.
La condizione in cui D+ e D- sono a tensione positiva è una condizione non ammessa sul bus. Per descrivere lo stato delle linee sul bus USB sono stati introdotti due simboli J e K. La tabella 1 indica la corrispondenza tra i valori J e K e lo stato delle linee D+ e D- . L’introduzione dei simboli J e K consente di utilizzare una terminologia univoca nella descrizione dello stato delle linee D+ e D- sia per comunicazioni low speed che full speed. Ad esempio a riposo le line D+ e D- di un bus USB si trovano nello stato J che corrisponde a uno 0 differenziale per una comunicazione low speed e un 1 differenziale per una comunicazione full speed. La trasmissione sulla linea USB inizia sempre con una commutazione da stato J a stato K. Il bus high speed prevede due ulteriori stati definiti chirpJ e chirpK utilizzati durante la configurazione per distinguere un device high speed da quello full speed. In [1] è possibile trovare una descrizione approfondita del bus high speed.
LA CODIFICA DEI DATI
La codifica dei dati è del tipo NRZI (No Return Zero Inverted). Rispetto alla comunicazione seriale non c’è corrispondenza tra lo stato delle linee e il valore del bit trasmesso. Nella comunicazione USB se lo stato J o K della linea non cambia tra due bit successivi allora il bit ricevuto è 1 se invece cambia il bit ricevuto è 0. Perciò trasmettendo una sequenza di zeri lo stato della linea cambia ad ogni bit e il ricevitore può facilmente sincronizzarsi. Quando invece si trasmettono lunghe sequenze di 1 la linea rimane sempre nello stesso stato J o K. Per garantire il sincronismo tra trasmettitore e ricevitore non si possono trasmettere più di sei bit consecutivi a 1. Dopo sei bit a 1 il trasmettitore è obbligato ad inviare uno zero che provoca la commutazione della linea e perciò fornisce un riferimento per il ricevitore. Ricevitore che a sua volta dovrà riconoscere la sequenza di 1 e scartare il bit a zero inviato per sincronismo. Inoltre, all’inizio e alla fine di ogni singola comunicazione il protocollo prevede dei pacchetti di sincronismo cioè delle particolari combinazioni di stati JK come descritto in [1].
IL PROTOCOLLO USB
La comunicazione tra Host e device si definisce transfer. L’Host è responsabile dell’apertura di una transfer con un device. Ogni transfer si suddivide a sua volta in una o più transaction. L’Host e un device possono avere solo una transaction in corso. Ogni transaction a sua volta è suddivisa in packets. La transaction è formata da un packet d’intestazione detto token che definisce il tipo di transaction e il device destinatario della transaction. Il token è seguito dal data packet che include eventuali dati inviati/ricevuti. A chiusura della transaction viene inviato l’handshake packet che contiene informazioni sul controllo d’errore nella transaction. Ogni packet ha una sua struttura: inizia con 8bit di sincronismo per comunicazioni low/full speed e 32 per high speed. Il sincronismo è seguito dal Packet Identifier (PID) codificato in 4+4bit, da informazioni aggiuntive dipendenti dal PID e, in chiusura, dal CRC per il controllo d’errore sul packet da non confondere con l’handsahake packet che controlla l’errore su tutta la transaction. Ricapitolando la comunicazione tra host e device si definisce transfer e ogni transfer è suddiviso in più transaction. A sua volta ogni transaction è suddivisa in packet. La trasmissione dei dati sulla linea avviene all’interno di pacchetti trasmessi periodicamente denominati frame. Per la trasmissione low/full speed le frame hanno periodo di 1ms mentre per la comunicazione high speed si parla di microframe di periodo 125msecondi. Ogni frame inizia con un particolare packet di sincronismo denominato SOF (Start of Frame). L’host assegna una porzione di frame alla comunicazione con ogni device in funzione del tipo di transfer richiesto, le singole transaction di una transfer possono occupare una o più frame anche non contigue.
Le transfer
Nel protocollo USB esistono quattro tipi di transfer
- Control
- Bulk
- Interrupt
- Isochronous
Control transfer:
Ogni dispositivo USB deve riconoscere le control transfer che sono i messaggi standard utilizzati nella fase di configurazione dei device. Ogni control transfer si compone di una o più transaction. Le control transfer si possono suddividere in tre fasi:
- la fase di setup in cui l’host invia la setup transaction che identifica la control transfer;
- la fase di data transfer che comprende zero o più data transactions (dipende dalla control transfer);
- la fase di status costituita da una transaction di handshake e verifica degli errori.
Come descritto in precedenza ogni transaction avrà la sua struttura a pacchetti e sarà formata da un Token packet un Data packet (opzionale) e un Handshake packet. La dimensione del Data packet per una control transfer dipende dalla velocità di comunicazione come indicato in tabella 2.
Se la massima dimensione del Data packet è superiore alla quantità di dati trasferiti in una singola data transaction, il trasferimento sarà suddiviso in più data transaction. In ogni caso la dimensione del Data packet scambiato in una control transfer deve coincidere con la dimensione specificata per la periferica. Si vedrà in seguito come l’Host può conoscere la dimensione del Data packet della periferica. La velocità massima raggiungibile con una control transfer considerando anche tutti i byte aggiuntivi di controllo è di 24Kbyte/s in low speed, 832Kbyte/s in full speed e 15.8Mbyte/s in high speed. La periferica deve rispondere a una control transfer entro 500ms oppure inviare un messaggio opportuno per informare l’Host di ripetere la richiesta successivamente.
Bulk transfer:
Permettono di inviare grosse quantità di dati senza però avere garanzie sul tempo di traferimento. La bulk transfer viene rallentata in caso di congestione del bus e non è supportata dalla modalità low speed. La bulk transfer si compone di una o più transaction di tipo data che hanno tutte la stessa direzione da host a device o viceversa. La dimensione massima di un Data packet per il trasferimento bulk è riportata in tabella 3.
La velocità massima di trasferimento per una bulk transfer è 1.2Mbyte in modalità full speed e 53Mbyte in modalità high speed.
Interrupt transfer:
Permette di trasferire dati con una periodicità presatabilita. L’indicazione interrupt potrebbe essere fuorviante perché non si tratta di interrupt hardware, ma nella fase di configurazione il device informa l’host sul tipo di transfer da instaurare e sul periodo di polling da utilizzare. È compito dell’host iniziare poi le transfer verso la device con la periodicità stabilita dal device. Contrariamente alle bluk transfer il bus USB in questo caso deve garantire con il minor ritardo possibile i tempi di trasferimento stabiliti. La struttura di una Interrupt transfer è costituita da una o più data transaction con dimensioni del Data packet indicate in tabella 4.
Isochronous transfer:
Ha un tempo di trasmissione garantito e può essere utilizzato per inviare ad esempio stream audio. Non è previsto il controllo d’errore. È disponibile solo per le modalità full speed o high speed. La dimensione del packet size è riportata in tabella 5.
Le transaction in una isochronous transfer includono solo token e data packet (manca l’handshake packet per il controllo d’errore). Si deve prestare molta attenzione alla definizione del tipo di transfer utilizzato dal device e alla dimensione del data packet. Il bus USB potrebbe rifiutare la connessione di una periferica qualora non disponesse della banda necessaria a soddisfare il tipo di transfer richiesto.
Le transaction
Come si è indicato in precedenza le transaction sono suddivise in packet, ogni transaction inizia con un token packet inviato dall’Host alla periferica. Il token packet è seguito da zero o più data packet. La transaction è chiusa dall’handsake packet di controllo dell’errore sulla transaction. Prima di iniziare una nuova transaction con un device l’Host deve concludere la transaction in corso.
I packet
Esistono diversi tipi di packet, alcuni comuni a tutte tre le modalità di trasmissione (low/full/high) altri specifici per alcune modalità. La trattazione completa si trova in [1] e [2]. Nella descrizione precedente sono stati citati i tipi principali di packet:
- i token packet identificano il tipo di transaction;
- i data packet utlizzati per trasferire i dati;
- gli handshake packet per il controllo di errore nella transaction.
Ogni packet ha questa struttura:
- Header di sincronismo (la lunghezza e il formato dipendono dalla velocità di trasmissione);
- intestazione del packet PID;
- dati (campo opzionale e di dimensione variabile in funzione del PID);
- handshake CRC di controllo dell’errore sul packet.
I token packet
Un token packet (figura 2.2) viene sempre inviato dall’Host all’inizio di una transaction. Esistono quattro tipi di token packet:
- SOF: viene inviato all’inizio di ogni frame per sincronizzare le frame. Il campo dati del SOF packet contiene il numero di frame (figura 2.1).
- SETUP: è il token che identifica la Setup transaction in una control message transfer.
- IN e OUT: definiscono la direzione della transaction dati cioè se i dati sono ricevuti o trasmessi dal device. (La definizione IN o OUT è riferita all’Host).
Il campo dati dei packet SETUP, IN o OUT contiene l’indirizzo del dispositivo con cui l’Host inizia la transaction.
I Data packet
Un Data packet (figura 2.3) contiene i dati trasferiti tra host e device. Il token packet inviato all’inzio della transaction stabilisce se si tratta di dati trasferiti in IN (verso host) o OUT (verso device). Esistono quattro tipi di data packet: DATA0/DATA1/DATA2 e MDATA Quando la transfer richiede più transaction dati, chi invia i dati alterna i PID DATA0 e DATA1 nell’intestazione del data packet. In questo modo chi riceve è in grado di riconoscere la sequenza dei packet. I PID DATA2 e MDATA sono utilizzati con lo stesso scopo nei trasferimenti Isochronous o high speed che coinvolgono più microframe.
L’handshake packet
Questo packet (figura 2.4) chiude una transazione e viene inviato da chi riceve i dati per segnalare la ricezione corretta o eventuali condizioni d’errore. I PID dell’handsake packet sono:
- ACK: è il packet restituito in caso di corretta transazione.
- NAK: non ha il significato di errore, ma semplicemente di dati non disponibili. Se l’Host richiede dei dati a una device che non ha dati disponibili oppure non è in grado di rispondere alla richiesta allora invia un NAK segnalando all’Host il riprova più tardi (il timeout è fissato in 500ms).
- STALL: ha il significato di richiesta non riconosciuta. Questa risposta viene inviata nel caso in cui la richiesta faccia riferimento a funzioni non implementate sul device interrogato.
- NYET: Utilizzato solo in una transazione high speed per segnalare la condizione device non disponibile per la comunicazione. A differenza del NAK che posticipa lo scambio dati in corso, quando il device chiude la transaction con NYET significa che accetta lo scambio dati richiesto dall’Host ma non è più disponibile per altre transactions. Sarà compito dell’Host ripristinare la comunicazione inviando dei PING al device.
Oltre a NYET ci sono altri PID (es. PING) omessi in questa trattazione che riguardano la comunicazione high speed.
LA PERIFERICA USB
Ogni device include al suo interno uno o più endpoint. Si definisce endpoint il punto finale della comunicazione tra host e device. Un device può includere più endpoint. Per analogia con la comunicazione RS485 si può dire che ogni endpoint rappresenta un dispositivo slave. Nella comunicazione USB questi slave non sono fisicamente separati ma possono essere raggruppati all’interno del device. C’è un’unica connessione fisica, ma diverse connessioni logiche tra Host e device. Ognuna di queste connessioni è definita PIPE. Gli endpoint possono essere di tre tipi: IN, OUT e Control. IN e OUT si riferiscono alla direzione della comunicazione (sempre riferita all’Host). Un endpoint OUT riceve dunque i dati inviati dall’Host, mentre un endpoint IN trasmette i dati all’Host. L’endpoint Control è invece bidiriezionale. Gli endpoint inclusi in un dispositivo sono identificati da un numero. Ogni transaction inizia con un token packet in cui l’Host specifica l’indirizzo del dispositivo e il numero dell’endpoint a cui è destinata. Ogni dispositivo USB deve includere l’endpoint0 che dev’essere di tipo Control. Quando una periferica viene collegata al bus USB l’Host inizia una transfer verso l’endpoint0 per acquisire le informazioni sul device. Questa fase iniziale che va sotto il nome di enumerazione del bus sarà descritta in seguito.
La SIE
La periferica hardware che gestisce l’interfaccia USB all’interno di un device è denominata SIE (Serial Interface Engine) che equivale alla UART di un dispositivo con interfaccia seriale. La SIE implementa tutte le funzioni di basso livello della comunicazione come i sincronismi di frame, la verifica e la decodifica dei packet, il controllo del sincronismo dei data packet (data toggle). Ogni SIE include un certo numero di endpoint. Ci sarà sempre l’endpoint0, a cui l’Host indirizza le sue richieste durante la fase di configurazione del device. In generale nei registri della SIE saranno definiti dei bit di interrupt attivati dal riconoscimento dei token packet di IN e OUT. Questi bit corrispondono ai bit di interrupt di TX e RX della UART. Non trova analogia diretta con la UART, perché tipico della comunicazione USB. L’interrupt associato al SETUP token è utilizzato nelle control transfer in cui è coinvolto l’endpoint0.
La PIPE
Le connessioni logiche tra host e device si definscono PIPE. Ogni PIPE è associata ad un endpoint. Ogni device USB all’avvio deve attivare la control PIPE associata all’endpoint0. La PIPE associata all’endpoint0 è bidirezionale di tipo message (control message). Le PIPE associate agli altri endpoint sono monodirezionali di tipo stream.
I Descrittori
Per realizzare il driver dell’interfaccia USB di una periferica embedded si inizia compilando i descrittori associati al device. I descrittori sono delle tabelle (in linguaggio di programmazione delle strutture) che definiscono in dettaglio le caratteristiche del device e permettono all’Host di acquisire tutte le informazioni necessarie per stabilire la comunicazione corretta con il device. I descrittori includono i numeri magici VendorID e DeviceID che l’Host poi utilizza per riconoscere il device. Il sistema operativo Windows utilizza questa coppia di codici per stabilire quale driver utilizzare per la comunicazione con il device USB. I descrittori definiscono inoltre il numero di endpoint inclusi nel device, il tipo di transfer supportato, tipo e formato dei dati. L’Host utilizza i control message per raccogliere tutte le informazioni contenute nei descrittori del dispositivo. Se il device non implementa un particolare control message risponde con STALL alla richiesta da parte dell’host.
Il device descriptor
Il primo descrittore che l’Host acquisisce quando un nuovo device viene collegato al bus è il device descriptor (tabella 6): La tabella 6 descrive i campi del device descriptor, per una spiegazione dei campi class, subclass e protocol si veda la descrizione del calss descriptor.
Il device descriptor include anche i riferimenti alle stringhe che descrivono il device. Queste stringhe appaiono sullo schermo del personal computer quando si inserisce un nuovo dispositivo USB. Per la descrizione delle stringhe si veda lo string descriptor
Il config descriptor
Il config descirptor (tabella 7) fornisce tutte le informazioni necessarie per impostare una comunicazione con il device e cioè: VendorID-DeviceID, la dimensione del data packet per l’edpoint0 e il numero di configurazioni incluse nel device.
Per conoscere la configurazione del dispositivo è necessario acquisire le informazioni contenute in tutti i descrittori di configurazione, interfaccia ed endpoint.
Interface descriptor
Leggendo il config descriptor l’host conosce quante interfacce sono presenti nel device. Per ognuna di esse il device deve fornire un descrittore (tabella 8).
Descrittore endpoint
Nell’interfaccia si stabilisce il numero di endpoints utilizzati, ora si devono descrivere gli endpoints ovvero definire l’identificativo, il tipo di transfer utilizzato e la direzione IN o OUT. La descrizione deve essere ripetuta per ogni endpoint e per ogni interfaccia (tabella 9).
Descrittore delle stringhe
Per ogni stringa viene definito un descrittore, la stringa è codificata in 16bit Unicode (tabella 10).
Tutti i puntatori ai descrittori stringa sono raccolti in una tabella. Ogni altro descrittore quando fa riferimento ad una stringa si riferisce all’indice della stringa nella tabella.
Ad esempio quando l’host legge il device descriptor ottiene gli indici delle stringhe manufacturer, product e serialnumber. L’Host richiederà al device la lettura dei descrittori stringa fornendo come parametro l’indice. Il device legge il puntatore al descrittore nella tabella delle stringhe in corrispondenza all’indice ricevuto. Come esempio nella figura 3 sono riportati i descrittori letti da un pen drive USB2.0. Per realizzare questo programma è stata utilizzata la libreria LibUSB-Win32 disponibile all’indirizzo [9].
I class descriptors
Ogni device USB deve includere i descrittori elencati in precedenza che sono definiti nel capitolo 9 della specifica USB. Un device si definisce conforme al capitolo 9 quando supera i test relativi alla definizione dei descrittori standard. La specifica USB definisce inoltre delle classi standard che raggruppano tutti i dispositivi con caratteristiche funzionali simili. Alcune di queste classi sono:
- Hub;
- Audio device;
- Mass storage;
- Printer;
- HID (Human Interface Device).
In [7] si possono trovare le specifiche per tutte le classi standard. Ogni classe definisce il numero di endpoint che deve attivare il device, la quantità di dati trasferiti, i protocolli utilizzati dal device, eventuali control message specifiche per la classe. Un device può anche appartenere a più classi, l’indicazione della classe infatti può essere inserita nel device descriptor (univoca per il device) oppure nell’interface descriptor. Un device con più interfacce può specificare una classe diversa per ogni interfaccia. Un esempio potrebbe essere quello di un microfono che avrà un trasferimento Isochronous per i dati audio e un interfaccia HID per la gestione del controllo on/off e del volume. All’interno della classe è possibile definire anche una sottoclasse per l’interfaccia. Ad esempio per una memoria di massa tipo pen drive la classe di appartenenza è mass storage, la sottoclasse definisce il set di comandi standard utilizzati per l’accesso ai dati. Ci sono evidenti vantaggi nel dichiarare un dispositivo appartenente ad una classe. La scrittura del firmware del driver lato device ne risulterà semplificata poiché tutta la funzionalità è descritta in dettaglio nella definizione della classe. Dal lato Host (ad esempio Windows) è possibile utilizzare un driver di comunicazione standard comune a tutti i dispositivi della classe. Il driver probabilmente sarà già installato con le stesse caratteristiche su tutti i computer che hanno un dato sistema operativo. Non solo, ma ad aggiornamenti del sistema operativo corrisponderanno anche aggiornamenti del driver eliminando così i problemi di incompatibilità. Una classe molto flessibile e molto semplice da utilizzare in un’applicazione embedded è la classe standard HID. In questa classe rientrano tutti i dispositivi tipo tastiere mouse joystick. La classe HID prevede l’utilizzo di tre PIPE:
- la control PIPE associata all’endpoint0;
- una PIPE di IN (di lettura dati) in modalità interrupt transfer (cioè ad interrogazione periodica);
- una PIPE di OUT (in scrittura dati) opzionale in modalità interrupt transfer.
La PIPE di OUT è opzionale, se non è presente, per la scrittura dei dati (OUT transfer), si utilizza il messaggio set_report sulla control PIPE. La control PIPE di un device HID deve implementare sei richieste in aggiunta alle undici standard. Nella Tabella 11 sono elencate le richieste standard aggiuntive per un dispositivo che appartiene alla classe HID. La richiesta Set_Idle determina il comportamento dell'endpoint in idle mode e può aiutare l'Host nell'allocazione della banda. La classe HID non ammette sottoclassi e prevede due protocolli predefiniti: mouse e keyboard. Difficilmente un’applicazione emedded proprietaria utilizzerà questi protocolli pertanto i campi subclass e protocol saranno lasciati a zero. Le caratteristiche della classe HID devono essere specificate nel class descriptor. L’HID class descirptor ha la struttura di tabella 12.
Ogni classe HID include almeno un report descriptor: se sono presenti più report allora le ultime due righe sono ripetute per tutti i report descriptor contenuti nel device. I report descriptor contengono informazioni dettagliate sulla quantità e il tipo di dati scambiati dal device. Un report descriptor non è una semplice tabella come i descrittori visti in precedenza ma contiene degli item. Ogni riga rappresenta un item, ogni item ha una sua struttura ben definita [3]. Ci sono delle regole precise per la creazione di un report descriptor, un aiuto può arrivare dall’HID descriptor tool che si può scaricare da [8]. Il listato 3 riporta un esempio di HID report descriptor.
Nell’esempio sono stati definiti tre main item: Feature, Input e Output. Per ognuno di essi viene specificato:
- l’item usage che può essere un valore standard predefinito [4] oppure un valore definito da chi realizza il device. Lo scopo dell’usage è quello di fornire all’host informazioni aggiuntive su dati che saranno definiti nel report;
- l range ammesso per i valori trasferiti (in questo caso 0x00..0xFF indicato però in notazione complemento a 2);
- a dimensione in bit della singola variabile;
- a dimensione del pacchetto dati in byte;
- l tipo di item (Feature, Input e Output).
La trattazione dettagliata della costruzione di un HID si trova in [1][3]. In questa carrellata sui descrittori non sono stati considerati i descrittori necessari quando il device supporta anche la modalità high speed [1].
REALIZZAZIONE DEL DRIVER USB
A questo punto le informazioni per realizzare il driver USB nel dispositivo ci sono tutte. Per semplificare lo sviluppo del software lato host è opportuno dichiarare il dispositivo appartenente ad una classe standard. L’approccio più semplice è quello di realizzare un dispositivo che rientri nella classe HID, per questo la SIE deve avere almeno un endpoint0 (in control mode) e un endpoint1 configurabile in direzione IN. La scrittura di un driver dipende molto dall’hardware che si ha a disposizione in questo caso dalla periferica SIE utilizzata. I listati riportati nell’articolo appartengono al codice dell’USB driver fornito come esempio per i microcontrollori uPSD di ST microelectronics.
Il listato 1 è il codice realizzato per la gestione delle control transfer.
void OnSetupPacket() /************************************************************* Function : void OnSetupPacket() Parameters : none Description: Basic handler for SETUP packets received on EP0. *************************************************************/ { pTransmitBufferEP0 = NULL; bytesToTransmitEP0 = 0; // If it’s a standard request... if ((setupPacket.bmRequestType & 0x60) == 0) { switch (setupPacket.bRequest) // Handle the request { case GET_STATUS: OnGetStatus(); return; case CLEAR_FEATURE: OnClearFeature(); return; case SET_FEATURE: OnSetFeature(); return; case SET_ADDRESS: OnSetAddress(); return; case GET_DESCRIPTOR: OnGetDescriptor(); return; case SET_DESCRIPTOR: OnSetDescriptor(); return; case GET_CONFIGURATION: OnGetConfiguration(); return; case SET_CONFIGURATION: OnSetConfiguration(); return; case GET_INTERFACE: OnGetInterface(); return; case SET_INTERFACE: OnSetInterface(); return; default: break; } } STAP0();// It’s not a request we handle, so stall endpoint }
Listato 1 |
Si noti che nel caso in cui la control transfer non sia gestita dal device, quest’ultimo risponde con uno STALL. Il listato 2 invece riporta la gestione della richiesta standard Get_descriptor.
static void OnGetDescriptor() /************************************************************* Function : static void OnGetDescriptor() Parameters : none Description: Handler for GET_DESCRIPTOR() control requests *************************************************************/ { data unsigned int bytesRequested; switch (setupPacket.wValue.hi) { case DT_DEVICE: pTransmitBufferEP0 = (unsigned char*) &deviceDesc; bytesToTransmitEP0 = sizeof(deviceDesc); break; case DT_CONFIGURATION: pTransmitBufferEP0 = (unsigned char*) &configDesc; bytesToTransmitEP0 = configDesc.wTotalLength.lo; break; case DT_STRING: pTransmitBufferEP0 = (unsigned char*) stringDescTable[setupPacket.wValue.lo<4?setupPacket.wValue.lo:4]; bytesToTransmitEP0 = *pTransmitBufferEP0;// choose requested string break; #if HID_DEVICE case DT_HID_CLASS: pTransmitBufferEP0 = (unsigned char*) &hidClassDesc; bytesToTransmitEP0 = hidClassDescSize; break; case DT_HID_REPORT: pTransmitBufferEP0 = (unsigned char*) &reportDesc; bytesToTransmitEP0 = reportDescSize; break; case DT_HID_PHYSICALD: pTransmitBufferEP0 = (unsigned char*) &PhysicalReportDesc; bytesToTransmitEP0 = PhysicalReportDescSize; break; #endif default: STALL_EP0(); // Unrecognized descriptor, so stall EP0 return; } bytesRequested = (setupPacket.wLength.hi << 8) | setupPacket.wLength.lo; shortTransfer = (bytesToTransmitEP0 < bytesRequested); if (bytesToTransmitEP0 > bytesRequested) { bytesToTransmitEP0 = bytesRequested; } UCON0 &= ~uTSEQ0; // TransmitDataEP0 will toggle sequence bit to DATA1 TransmitBufferEP0(); }
Listato 2 |
Si può notare il case statement GET_HID REPORT utilizzato dall’host per acquisire le informazioni sull’interfaccia HID del device. Nel listato 3 è riportato l’HID report utilizzato per il trasferimento dati.
const unsigned char code reportDesc[] = { 0x06, 0xA0, 0xFF, // Usage page (vendor defined) 0x09, 0xA5, // Usage (vendor defined) 0xA1, 0x01, // Collection (application) 0x09, 0xA6, // Usage (vendor defined) // Feature report 0x09, 0xA5, // Usage (vendor defined) 0x15, 0x80, // Logical min (-127) 0x25, 0x7F, // Logical max (128) 0x75, 0x08, // Report size (8 bits) 0x95, 0x40, // Report count (64 bytes) 0xB1, 0x02, // Feature (data, variable, absolute) // Input report 0x09, 0xA7, // Usage (vendor defined) 0x15, 0x80, // Logical min (-127) 0x25, 0x7F, // Logical max (128) 0x75, 0x08, // Report size (8 bits) 0x95, 0x08, // Report count (8 bytes) 0x81, 0x02, // Input (data, variable, absolute) // Output report 0x09, 0xA9, // Usage (vendor defined) 0x15, 0x80, // Logical min (-127) 0x25, 0x7F, // Logical max (128) 0x75, 0x08, // Report size (8 bits) 0x95, 0x40, // Report count (64 bytes) 0x91, 0x02, // Output (data, variable, absolute) 0xC0 // End Collection (Application) } ;
Listato 3 |
IL PROCESSO DI ENUMERAZIONE
Verrà ora analizzato come l’Host riconosce la periferica e acquisisce le informazioni inserite nei vari descrittori. Il processo inizia dal momento in cui una periferica USB viene inserita nel connettore dell hub del PC. Il driver dell’Hub in uscita ha due resistenze di pull-down da 15Kohm. Una device low speed ha una resistenza di pull-up da 1.5Kohm sulla linea D- mentre la full o high speed inserisce una resistenza di pullup da 1.5Kohm sulla linea D+. Inserendo un device nella porta dell’Hub la linea si trova nello stato J. Controllando lo stato delle linee D+/D- l’hub si accorge se è stato inserito un device e se si tratta di un low o full speed. Appena riconosce il nuovo dispositivo l’hub pone la linea USB nella condizione di reset entrambe le linee sono a zero. La condizione di reset permane per almeno 10ms. Un device high speed viene inizialmente riconosciuto come full speed. Per segnalare all’Hub che si tratta di una periferica high speed il device commuta la linea nella condizione K durante la fase di reset. Se anche l’Hub è high speed riconosce la condizione K e risponde con una sequenza JK al device. Se il device non riceve questa sequenza mantiene lo stato full speed senza commutare nella modalità high speed. Al termine di questo riconoscimento hardware il device si trova nello stato default e attiva la pipe sull’endpoint0. L’indirizzo del dispositivo nel bus USB al momento è 0. A questo punto host e device sono in grado di comunicare. l’Host acquisisce il device descriptor che contiene le informazioni principali sul device. È compito dell’Host assegnare al device un indirizzo tramite la control transfer set_address, indirizzo che sarà mantenuto dal device finchè rimarrà collegato al bus. Al termine della configurazione il dispositivo entra nello stato addressed. L’host a questo punto richiede la configurazione che trasferisce informazioni sull’interfaccia la classe e gli endpoint del dispositivo. Quando ha acquisito tutte le informazioni Windows cerca di caricare il driver per la nuova periferica. La strategia prevede la ricerca di un driver custom utilizzando vendorID e productID o, in assenza di questo, di utilizzare le informazioni di classe, subclasse e protocollo per ricavarlo da una serie di driver standard di Windows. Al termine di questa fase l’host comunica al device l’identificativo della configurazione da utilizzare. Il device si pone in configured state ed è pronto per comunicare secondo l’interfaccia specificata.
Conclusioni
In questo articolo è stata presentata una panoramica sul bus USB con le sue possibili varianti e applicazioni. Per facilitare l’implementazione del protocollo molte case costruttrici forniscono chip di conversione seriale/USB in modo da permettere la connettività USB anche su microcontrollori dotati solamente di UART.
LE CONTROL TRANSFERLe control transfer sono i messaggi standard utilizzati dall’Host per acquisire informazioni sulla configurazione del device. Ogni control transfer è suddivisa in tre fasi
Setup Fase Quando l’Host inizia una control transfer invia al device il token packet con PID = SETUP. Il token contiene l’indirizzo del dispositivo a cui è diretta la transfer. Il Setup token è seguito da un DATA0 packet di 8byte che ha questa struttura: A chiusura della setup transaction se tutto è corretto il device invia l’ACK handshake packet. Data Fase Inizia ora il trasferimento dei dati tra host e device che si compone di una o più transaction. All’inizio di ogni transaction l’Host invia il token packet al device con PID=IN per lettura dati e PID=OUT per scrittura dati. Il Data packet in questa fase contiene i dati che devono essere trasferiti. Chi invia i dati deve alternare i PID DATA0/DATA1 nell’intestazione del data packet. Chi riceve i dati risponde con l’handshake packet. Ogni data packet trasferisce un numero definito di byte. Le transaction nella data fase continuano finchè non sono stati trasferiti Length byte come specificato nella fase di Setup. In altre parole se ogni transaction trasferisce 8byte e si devono trasferire wLenght=21byte la data fase sarà formata da 3 data transaction. Status fase In chiusura della transfer c’è la status fase. In questa fase sono scambiate informazioni sull’errore rilevati durante tutta la transfer. La fase è formata da una sola transaction. Il transfer status handshake viene inviato da chi riceve i dati. Quindi nel caso di IN transfer l’Host riceve i dati e invia un token packet con PID=OUT. Viceversa per le OUT transfer sempre l’Host invia il token packet con PID=IN. In altre parole il PID del token packet nella Status fase è opposto al PID del token paket nella Data fase. Il Data packet della status transfer nel caso di transfer corretta ha lunghezza zero. Si dice anche che la status transaction ha uno zero-length data packet. Come sempre a chiusra della transaction c’è l’handshake packet che segnala eventuali errori o anomalie sulla transaction. Le richieste standard La tabella riassume le 11 richieste standard: Se un device non implementa una particolare funzione risponde con STALL alla richiesta. Oltre alle undici richieste standard elencate, la specifica USB prevede anche delle richieste specifiche per ogni classe di dispositivi. Il costruttore del device può implementare anche delle richieste proprietarie che saranno identificate dal valore 0x*10***** nella RequestType. |
Link utili
[1] http://www.usb.org/developers
[2] http://www.usb.org/developers/hidpage
[4] http://sourceforge.net/projects/libusb-win32

Molto Interessante e’ anche questo articolo http://it.emcelettronica.com/neppure-le-specifiche-usb-3-0-riusciranno-a-sostituire-gli-standard-di-dati-seriali-come-rs-232 – sul confronto tra rs232 e usb