Comunicazione dati su bus USB

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:

 

  1. 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;
  2. D+ e D- nello stato di 0 differenziale: un driver in ricezione riconosce questa condizione quando (D-) – (D+) > 200mV e (D-) > 2V;
  3. 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].

Figura 1. Struttura di una transfer

Figura 1. Struttura di una transfer

Tabella 1. Notazione JK dello stato del bus USB

Tabella 1. Notazione JK dello stato del bus USB

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:

  1. la fase di setup in cui l’host invia la setup transaction che identifica la control transfer;
  2. la fase di data transfer che comprende zero o più data transactions (dipende dalla control transfer);
  3. 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.

Tabella 2. Dimensione del data packet di una control transfer

Tabella 2. Dimensione del data packet di una control transfer

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.

Tabella 3. Dimensione del data packet di una bluk transfer

Tabella 3. Dimensione del data packet di una bluk transfer

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.

Tabella 4. Dimensione del data packet di una interrupt transfer

Tabella 4. Dimensione del data packet di una interrupt transfer

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.

Tabella 5. Dimensione del data packet di una isochronous transfer

Tabella 5. Dimensione del data packet di una isochronous transfer

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).
Figura 2.1 . Struttura di una start of frame packet

Figura 2.1 . Struttura di una start of frame packet

Figura 2.2 . Struttura di un token packet

Figura 2.2 . Struttura di un token packet

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.

Figura 2.3 . Struttura di un data packet

Figura 2.3 . Struttura di un data packet

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:

Figura 2.4 . Struttura dell handshake packet

Figura 2.4 . Struttura dell handshake packet

  • 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.

Tabella 6. Struttura del device descriptor

Tabella 6. Struttura del device 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.

Tabella 7. Struttura del config descriptor

Tabella 7. Struttura del config descriptor

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).

Tabella 8. Struttura di un interface descriptor

Tabella 8. Struttura di un interface descriptor

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).

Tabella 9. Struttura di un endpoint descriptor

Tabella 9. Struttura di un endpoint descriptor

Descrittore delle stringhe

Per ogni stringa viene definito un descrittore, la stringa è codificata in 16bit Unicode (tabella 10).

Tabella 10. Struttura di uno string descriptor

Tabella 10. Struttura di uno string descriptor

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].

Figura 3. Lettura descrittori di un Pen drive da 128Mbyte USB2.0

Figura 3. Lettura descrittori di un Pen drive da 128Mbyte USB2.0

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.

Tabella 12. Le richieste standard per un dispositivo che appartiene alla classe HID

Tabella 12. Le richieste standard per un dispositivo che appartiene alla classe HID

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 TRANSFER

Le control transfer sono i messaggi standard utilizzati dall’Host per acquisire informazioni sulla configurazione del device. Ogni control transfer è suddivisa in tre fasi

  1. La setup fase
  2. La data fase
  3. La Status fase

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

[3]    www.beyondlogic.org

[4]  http://sourceforge.net/projects/libusb-win32

Iscriviti e ricevi GRATIS
Speciale Pi

Fai subito il DOWNLOAD (valore 15€) GRATIS

Una risposta

  1. Maurizio Di Paolo Emilio Maurizio 1 novembre 2016

Scrivi un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Iscriviti e ricevi GRATIS
Speciale Pi

Fai subito il DOWNLOAD (valore 15€) GRATIS