Con l’avvento di microcontrollori dotati di funzionalità Host, come i PIC32MX460 introdotti tempo fa, è ora possibile usare tutto un insieme di periferiche USB.
USB (Universal Serial Bus) è ormai diventato lo standard di comunicazione per interfacciare un PC con i dispositivi più diversi, dai Flash drive ai mouse, alle tastiere, ai dispositivi di acquisizione audio-video ed innumerevoli altri. Con l’avvento di microcontrollori dotati di funzionalità Host, come i nuovi PIC32MX460, è ora possibile usare tutto un insieme di periferiche USB. Per ridurre il time-to-market di applicazioni USB, Microchip ha sviluppato uno stack denominato Microchip USB Library for PIC32MX che copre sia le funzionalità device che host di questi nuovi microcontrollori a 32 bit.
Le modalità operative degli apparati USB
Il bus USB è un’interfaccia seriale asincrona con una topologia a stella connessa, dove il centro di ogni stella è costituito da un hub. In un sistema USB è presente un solo host e fino a 127 device (compresi un massimo di 7 hub). Ad ogni device è assegnato dall’host un indirizzo nel corso del processo di enumerazione, durante il quale il device viene identificato e configurato. Nel seguito faremo riferimento allo standard USB 2.0. Sono state definite le seguenti modalità di funzionamento di un apparato USB:
» USB Device: usata dalle periferiche come Flash drive, Mouse, Tastiere, ecc.
» USB Standard Host: usato normalmente dai PC.
» USB Embedded Host: una versione di host adatta ad essere implementata da un microcontrollore.
» OTG Dual Role: usata da dispositivi che possono cambiare il proprio ruolo da device a host a seconda del cavetto usato per il collegamento.
In particolare, i dispositivi Embedded Host presentano le caratteristiche seguenti:
» usano una presa tipo ‘A’ per ogni porta; il supporto per gli hub è opzionale;
» ogni porta deve fornire un minimo di 100 mA di alimentazione per un dispositivo non configurato, e fino a 500 mA per un dispositivo configurato;
» devono supportare un elenco specifico di dispositivi, contenuto in una TPL (Targeted Peripheral List);
» devono supportare solo le velocità e le modalità di trasferimento (bulk, interrupt, isochronous) usate dai dispositivi della TPL;
» Non è necessario che i driver dei dispositivi siano aggiornabili.
I microcontrollori della famiglia PIC32MX4XX comprendono un modulo USB 2.0 in grado di funzionare secondo le modalità Device, Embedded Host od OTG (On-The-Go). Nel caso di funzionamento come device, il modulo USB è Full Speed (12 Mb/s), mentre come Embedded Host od OTG può funzionare sia in Low Speed (1.5 Mb/s) che Full Speed. In figura 1 è riportato lo schema a blocchi del modulo USB dei PIC32MX4XX.
Tipologie di trasferimento
In un bus USB, l’unico host è responsabile dell’inizio di ogni trasferimento dati. La comunicazione su un bus USB avviene secondo diverse modalità di trasferimento:
» Control: usato nella fase di enumerazione dei device e per operazioni di controllo nella fase di operatività; il 10% della banda disponibile è sempre riservato a trasferimenti di questo tipo.
» Interrupt: in questa modalità di trasferimento, l’host alloca dei time slot in modo da trasferire i dati con il dispositivo in maniera periodica. In questa modalità è assicurata anche l’avvenuta ricezione dei dati, tramite un meccanismo di ACK.
» Isochronous: anche in questa modalità di trasferimento, l’host alloca dei time slot in modo da trasferire i dati con il dispositivo in maniera periodica. Non è però verificata l’avvenuta ricezione dei dati. Questa modalità assicura il throughput più elevato ed è tipicamente usata per lo streaming audio/video.
» Bulk: questo trasferimento è complementare all’isochronous in quanto i dati sono trasferiti solo quando c’è banda disponibile, mentre è assicurata l’avvenuta ricezione dei dati. Utile per trasferimenti di file di grosse dimensioni quando le tempistiche non siano stringenti. Ad esempio con dispositivi di Mass Storage come i Flash drive USB. In tutte le modalità di trasferimento infine viene controllata l’integrità dei dati tramite CRC. In tabella 1 è riportato un confronto tra le diverse modalità di trasferimento.
Endpoints e descrittori
La comunicazione tra dispositivi USB avviene tra due Endpoint. Durante il processo di enumerazione, il device comunica all’host le sue caratteristiche tramite una serie di descrittori. Lo standard USB prevede 8 tipi di descrittori, i più rilevanti dei quali sono i seguenti
» Device descriptor: produttore (VID), codice del prodotto (PID), classe del dispositivo, numero di configurazioni ed altre informazioni generali.
» Configuration descriptor: corrente richiesta, numero di interfacce della configurazione.
» Interface descriptor: numero di endpoints dell’interfaccia, classe della stessa.
» Endpoint descriptor: tipologia di trasferimento (Control/Bulk/…) e direzione (IN/OUT), byte trasmessi per transazione.
» String descriptor: informazioni ‘human readable’ riguardanti alcuni dei descrittori visti, utili soprattutto per facilitare l’identificazione dei dispositivi da parte degli utenti.
I dispositivi USB sono suddivisi in classi di appartenenza: ad esempio la classe Mass Storage comprende i Flash disk, mentre della classe HID (Human Interface Device) fanno parte periferiche come mouse, tastiere ma anche dispositivi custom. Dal lato host è necessario un client driver per poter operare con un dispositivo di una data classe, mentre sarà necessario sviluppare un driver apposito se il device USB non appartiene ad una classe predefinita.
Struttura dei trasferimenti
Come già accennato, tutto il traffico di un sistema USB è controllato dall’unico host. Un dispositivo può iniziare un trasferimento dati solo in risposta ad una richiesta da parte dell’host, sia che si tratti di una lettura dati (OUT da parte dell’host) che di una scrittura (IN da parte dell’host). Come visto la direzione del flusso di dati è sempre vista rispetto all’host USB. Un trasferimento di dati ha una dimensione massima compresa tra 64 e 1023 byte a seconda del protocollo di trasferimento usato: ogni trasferimento è a sua volta composto da più transazioni. Esistono tre tipi di transazioni: Setup, Data e Status; in un trasferimento possono esserci più transazioni di tipo Data. Ogni transazione è a sua volta composta da più packet, le unità elementari di trasferimento per il modulo USB. I packet sono a loro volta di 4 tipi diversi: SETUP, IN/OUT, DATA0/1 (il payload) e ACK. Per esemplificare, in figura 2 sono raffigurate le transazioni ed i packet che costituiscono un trasferimento secondo la modalità Control di cui abbiamo parlato prima.
In figura 3 è raffigurato invece un trasferimento di tipo Bulk di 128 byte di dati tramite due packet consecutivi di 64 byte.
Queste due tipologie di trasferimento ci interessano particolarmente, dato che sono le uniche usate nell’esempio di Host per USB Flash Drive che vedremo più avanti. Il modulo USB del PIC32MX gestisce in hardware i trasferimenti a livello di packet, tranne i packet di tipo ACK che, se previsti dalla tipologia di trasferimento, sono generati automaticamente. Quindi nell’esempio in figura 3 il firmware dovrà richiamare esplicitamente i comandi per trasferire i pacchetti di tipo OUT e DATA0/1. Abbiamo visto che in un Embedded Host, la TPL è l’insieme dei dispositivi supportati dall’host. I PIC32MX nella modalità Embedded Host possono supportare dispositivi identificati nella TPL tramite la classe di appartenenza oppure tramite gli identificatori VID/PID:
» VID/PID (Vendor ID/Product ID) identificano uno specifico dispositivo di una certa classe, ad esempio un Flash Disk del produttore X del modello Y.
» Classe/sottoclasse/protocollo identificano tutti i dispositivi di una data classe che supportano un certo protocollo di trasferimento. Ad esempio tutti i Flash Disk che supportano i comandi SCSI ed un protocollo Bulk.
In tabella 2 è riportato un esempio di TPL basato sulla classe dei dispositivi.
USB Embedded Host Stack di Microchip
Lo stack Embedded Host di Microchip è compatibile con l’uso di un RTOS (per quanto non necessario) ed utilizza un meccanismo misto di interrupt e di polling. Una state machine è usata per il processing in background, in particolare nel processo di enumerazione dei dispositivi USB connessi. Alla fine del processo di enumerazione, dopo che il dispositivo è stato configurato entra nello stato di running. Quindi lo stack richiama l’event handler per l’inizializzazione del dispositivo, contenuto nel client driver: se il dispositivo è inizializzato con successo, possono iniziare i trasferimenti dati normali. In figura 4 sono mostrate le transizioni della state machine.
I trasferimenti si avvalgono di un meccanismo di interrupt per soddisfare gli eventi time-critical: in particolare l’interrupt di SOF (Start Of Frame) che si verifica con la cadenza di 1 ms per indicare l’inizio di una sessione di trasferimenti. All’inter no di un frame possono essere trasmesse più transazioni: l’interrupt di Transaction Complete si verifica appunto quando l’ultima transazione è stata completata: lo stack determinerà allora la successiva transazione da eseguire prelevandola da una lista apposita.
Funzioni di callback (handlers) da definire
Lo stack Embedded Host richiede che siano definiti due handler nel client driver (il driver che gestisce un particolare device):
» Device Initialization handler
» Event handler
Il Device Initialization handler, del quale abbiamo già parlato, è richiamato alla fine del processo di enumerazione, dopo che sia stata scelta una particolare configurazione del device (se ne presenta più di una). Nella funzione saranno definite le procedure d’inizializzazione specifiche del device: se l’inizializzazione va a buon fine l’handler dovrà ritor nare il valore TRUE, in caso contrario dovrà ritornare FALSE.
L’handler dovrà avere un prototipo corrispondente alla typedef seguente (è un puntatore a funzione):
typedef BOOL (*USB_CLIENT_INIT)
(BYTE address, DWORD flags);
L’Event Handler gestisce invece eventi che si verificano durante l’operatività normale del device. Un esempio di evento è quello della disconnessione di un device dal bus, definito come EVENT_DETACH: in questo caso, oltre alle operazioni pertinenti eseguite dallo stack USB, il client driver potrebbe eseguire ulteriori attività specifiche, come ad esempio rimuovere il device da un elenco di dispositivi collegati. L’handler dovrà avere un prototipo corrispondente alla typedef seguente
typedef BOOL
(*USB_CLIENT_EVENT_HANDLER)
(BYTE address, USB_EVENT event,
void *data, DWORD size);
Altri event handler possono essere necessari a livello di applicazione piuttosto che di client driver. Ad esempio, un evento che si verifica quando un device collegato richiede di essere alimentato con una certa corrente massima, andrà gestito tramite un event handler che eseguirà le azioni opportune al ricevimento dell’evento definito come EVENT_REQUEST_POWER. Il prototipo di queste funzioni deve essere del tipo seguente
BOOL USB_HOST_APP_EVENT_HANDLER
( BYTE address, USB_EVENT event,
void *data, DWORD size );
I nomi degli event handlers andranno inseriti obbligatoriamente nei file usb_config.c ed usb_config.h, specifici di ogni progetto. In alternativa, è possibile usare la utility di configurazione USB Config Tool, fornita con lo stack USB di Microchip, della quale vedremo l’uso nell’esempio di host per Flash drive USB.
Inizializzazione ed uso dello stack
L’inizializzazione dello stack USB è eseguita tramite la chiamata della funzione
BYTE USBHostInit(void);
Questa funzione va richiamata una volta sola nell’applicazione. L’USB Config Tool è utilizzabile per creare automaticamente la macro USBInitialize() (presente nel file usb_config.h) che provvederà a richiamare le funzioni di inizializzazione sia dello stack USB che, eventualmente, dei client driver dell’applicazione. Subito dopo l’inizializzazione dello stack, sarà necessario richiamare la funzione
void USBHostTasks(void);
Questa funzione implementa la state machine dello stack utilizzata nella fase di enumerazione: è quindi necessario richiamarla regolarmente all’inter no del loop principale dell’applicazione. Anche in questo caso, è possibile usare la utility USB Config Tool per generare la macro USBTasks(), la quale richiamerà la funzione USBHostTasks() ed eventuali altre funzioni richieste dai task dei client driver. Il codice di questa macro è presente nel file usb_config.h. Per comunicare con il dispositivo, un dato client driver utilizza alcune funzioni dello stack USB. Un’operazione di lettura dal dispositivo è iniziata tramite la funzione
BYTE USBHostRead(BYTE deviceAddress,
BYTE endpoint, BYTE *data,
DWORD size);
mentre la scrittura verso un device usa la funzione
BYTE USBHostWrite(BYTE deviceAddress,
BYTE endpoint, BYTE *data,
DWORD size);
Durante l’esecuzione delle funzioni, può essere necessario controllare l’avvenuta esecuzione della transazione, tramite la funzione
BOOL
USBHostTransferIsComplete(BYTE
deviceAddress, BYTE endpoint,
BYTE *errorCode, DWORD *byte-
Count);
In questo caso è indispensabile richiamare periodicamente la macro USBTasks() per evitare malfunzionamenti dello stack, come si vede nell’esempio di codice seguente
error = USBHostWrite( device,
EP1, buffer, sizeof(buffer) );
if (error)
{
// errore di scrittura
//…
}
else
{
while (!USBHostTransferIsComplete(
device, EP1, \
&error, &count ))
{
USBHostTasks();
}
if (error)
{
// transazione errata
// …
}
else
{
// transazione eseguita
// …
}
}
Un esempio di Embedded Host: USB Flash Drive
Vedremo ora un esempio di applicazione per l’uso di un Flash drive USB con i PIC32 MX. Un Flash drive USB è un dispositivo di Mass Storage: è possibile quindi utilizzare il client driver sviluppato da Microchip (incluso con la distribuzione dello stack USB), per i device appartenenti alla classe di dispositivi Mass Storage. Il client driver fornito nell’esempio è compatibile con i Flash Drive che usano un file system FAT16 ed un’interfaccia comandi di tipo SCSI. Questi comprendono la maggior parte dei Flash Drive di dimensioni inferiori ai 2 GB, mentre quelli di dimensione superiore utilizzano una FAT32.
L’applicazione Data Logger
L’applicazione d’esempio è un data logger, che memorizza sul Flash Drive due tipologie di dati provenienti da alcuni ingressi dell’Explorer 16:
» Misurazioni a bassa frequenza: il potenziometro presente sulla scheda è monitorato tramite l’ADC del PIC32, interrogato una volta al secondo. Il valore letto viene quindi memorizzato, insieme all’orario ricavato dal Real T ime Clock (RTCC) del PIC32, su un file del Flash Drive.
» Misurazioni ad alta frequenza: il sensore di temperatura presente sulla scheda è monitorato tramite l’ADC del PIC32, ogni 10 ms. Il valore letto viene quindi memorizzato, insieme all’intervallo di tempo trascorso, su un file del Flash Drive. L’applicazione di Embedded Host per Flash Drive USB è stata configurata per il seguente sistema hardware:
» Modulo PIM MA320002, equipaggiato con il PIC32MX460F512L
» Scheda di sviluppo Explorer 16
» Scheda di espansione USB Pictail Plus L’applicazione di data logger si basa sullo stack multi-layer seguente, la cui struttura è visibile in figura 5.
USB Embedded Host Driver
Lo strato inferiore è costituito dallo stack Embedded Host del quale abbiamo discusso nella prima parte dell’articolo. Il Mass Storage client driver si interfaccia direttamente con questo layer.
Mass Storage Client Driver
Questo layer è un esempio di client driver di cui abbiamo parlato genericamente in precedenza. Costituisce l’interfaccia di basso livello verso un dispositivo di Mass Storage (del quale il Flash Drive rappresenta un caso particolare).
Gestione del File System e supporto per i comandi SCSI
La gestione del file system del Flash Disk è eseguita da questo layer. Il file system originario, descritto nell’application note AN1045, utilizza le funzioni riportate nella tabella 3 per interfacciarsi con l’hardware. In questa applicazione, tali funzioni sono state sostituite con dei comandi SCSI, come si vede in tabella, in modo da potersi interfacciare con il client driver di Mass Storage.
Applicazione
Infine, il layer superiore costituisce l’applicazione vera e propria di data logging.
Funzionalità dell’applicazione
L’applicazione consiste di tre componenti funzionali
» Interfaccia comandi
» Gestione del File system
» Data Logging
L’applicazione interagisce con l’utente tramite un collegamento via terminale seriale, ad esempio con Hyperterminal di Windows. Se si dispone della scheda Explorer 16, sarà necessario collegarla al PC tramite il connettore DB9 dell’interfaccia RS232. L’interfaccia andrà settata a 57600 baud, 8 bit di dati, nessuna parità, 1 bit di stop, no flow control. L’applicazione riconosce una serie di comandi per la gestione del file system e del data logging.
Comandi per la gestione del file system
Sono riconosciuti i comandi seguenti, dei quali forniamo una breve descrizione:
» CD: cambia la directory
» COPY: copia un file
» DEL: cancella un file
» DIR: visualizza una directory
» MD: crea una nuova directory
» RD: cancella una directory
» TYPE: visualizza il contenuto di un file
Comandi per il Data Logging
tramite il comando
LOG POT <file>
sarà eseguito il log a bassa frequenza del valore del potenziometro presente sulla scheda, interrogato una volta al secondo. Il valore letto, insieme all’orario ricavato dal RTCC del PIC32, è quindi memorizzato sul file prescelto del Flash Drive. Il file generato è formattato in maniera da essere compatibile con tool di analisi come Excel. Il comando
LOG TMP <file>
Esegue la misurazione ad alta frequenza del sensore di temperatura presente sulla scheda Explorer 16. Tramite l’interrupt del timer 3, ogni 10 ms la tensione generata dal sensore viene convertita tramite l’ADC del PIC32: il valore letto viene quindi memorizzato, insieme all’intervallo di tempo trascorso, in un buffer temporaneo. Quando il buffer è pieno, il suo contenuto è copiato sul file prescelto del Flash Drive. In questo modo si supera l’impossibilità di eseguire misure in real-time ad alta velocità con il PC.
La utility USB Config Tool
Sebbene l’applicazione di data logger sia già fornita configurata, vediamo come si può utilizzare l’USB Config Tool per generare i file di configurazione usb_config.c ed usb_config.h (possiamo trovare questi file nella directory del progetto ‘USB Data Logger’). I passi necessari per configurare il progetto sono i seguenti.
■ 1-Nella scheda Main selezioniamo USB Embedded Host come Device Type, quindi Ping-Pong an All Endpoints come PingPong Mode.
■ 2-Nella scheda Host selezioniamo solo Bulk come Transfer Type (i trasferimenti di tipo Control sono sempre selezionati per default). Immettiamo il nome dell’event handler del livello applicazione (si veda il paragrafo sulle funzioni di callback) nella casella Name of Application Event Handler : nel nostro caso sarà USB_ApplicationEventHandler. Lasciamo le altre opzioni come da default.
■ 3-Nella scheda TPL, nella casella Description immettiamo un nome per l’elemento della lista, ad esempio Flash Drive. Selezioniamo Mass Storage dalla lista Client Driver, poi Support via Class ID, quindi immettiamo i codici relativi al device di classe Mass Storage (riportati in tabella 2) nelle caselle Class ID, Subclass ID e Protocol ID. Lasciamo il resto come da default e clicchiamo su Add to TPL per aggiungere la classe alla lista. Il risultato dovrà essere come in figura 6.
■ 4-Nella scheda Mass Storage, selezioniamo Mass Storage Client is used in Host mode, quindi scegliamo SCSI Interface come Media Interface. Notiamo che in questo caso, saranno preselezionati i nomi degli handler utilizzati dagli handler Initialization ed Event, dei quali abbiamo parlato nel paragrafo sulle funzioni di callback. Si veda la figura 7. Anche il nome del file header va lasciato come da default.