- Elettronica Open Source - https://it.emcelettronica.com -

USB Mass Storage class su PIC24

Dopo che nei PIC32 [1] della serie PIC32MX4, Microchip ha incorporato un modulo USB host anche in alcuni microcontrollori della famiglia PIC24. Microchip fornisce una libreria per lo sviluppo di applicazioni USB sia device che host, che comprende diversi driver per alcune delle classi più diffuse di dispositivi USB.

I MICROCONTROLLORI PIC24

I PIC24 sono una famiglia di microcontrollori Microchip a 16 bit. Il core dei PIC 24 è basato su una CPU a 16 bit con un’architettura Harvard modificata: come sappiamo ciò significa che memoria programma e dati utilizzano due bus differenti. Si definisce modificata in quanto è possibile scambiare dati tra le memorie programma e dati. La piaaxpeline di esecuzione delle istruzioni permette di eseguire un’istruzione ogni due cicli di clock, vale a dire in un ciclo istruzione (Tcy). Le memorie programma e dati sono di larghezza differente, 24 e 16 bit rispettivamente: ciò permette di avere codici d’istruzione che comprendono eventualmente gli indirizzi degli operandi, così da poter eseguire quasi tutte le istruzioni in un solo ciclo. Non é però posssibile poter eseguire codice da RAM. Il modello di programmazione fa uso di 16 working register a 16 bit, W0W15, dove il registro W15 è utilizzato come stack pointer per uno stack software (usato in particolare dai compilatori per gli interrupt e le chiamate di funzione). Inoltre, alcune istruzioni permettono di eseguire calcoli su tre operandi, così da eseguire operazioni del tipo A+B=C in un solo ciclo. La CPU include anche un moltiplicatore hardware 17 x 17 bit che permette di eseguire una moltiplicazione con/senza segno 16 x 16 bit in un singolo ciclo, ed il supporto hardware per le divisioni 32/16 bit. Rispetto ai dsPIC33, con i quali condividono l’architettura di base, i PIC24 sono privi dell’unità DSP, e risultano nel complesso adatti ad applicazioni di complessità relativamente elevata che non necessitino appunto di un Digital Signal Processor. I PIC24 sono attualmente disponibili in due famiglie, PIC24FJ e PIC24HJ, che differiscono tra loro principalmente per la frequenza di clock massima. Nel caso dei PIC24FJ la fck può arrivare a 32 MHz, contro gli 80 MHz per i PIC24HJ. A queste frequenze corrisponde una potenza elaborativa massima di 16 MIPS e 40 MIPS rispettivamente.

IL MODULO USB OTG DEI PIC24FJ

Alcuni dispositivi della famiglia PIC24FJ sono dotati di un modulo USB OTG [2], analogo al modulo USB dei PIC32MX460. Questo modulo è compatibile USB 2.0 e può funzionare come device USB Full speed (12 Mbit/sec) o come Host Full Speed o Low Speed (1.5 Mbit/sec). Nel funzionamento OTG (On-The-Go) può funzionare come device o come host a seconda della connessione fisica usata (potete trovare una descrizione più approfondita in [1]). Le caratteristiche principali del modulo USB sono le seguenti:

In figura 1 è riportato lo schema a blocchi semplificato del modulo USB OTG dei PIC24FJ.

Figura 1: schema a blocchi del modulo USB dei PIC24FJ.

Figura 1: schema a blocchi del modulo USB dei PIC24FJ.

LA MICROCHIP USB LIBRARY: MCHPFSUSB

Microchip fornisce una  libreria per le applicazioni USB sia device che host denominata Microchip USB Library, o MCHPFSUSB. Fa parte di questa libreria l’Embedded Host Stack per le applicazioni host. Questo stack è compatibile con un eventuale RTOS (Real-Time Operating System) ed utilizza un meccanismo misto di interrupt e di polling. L’elaborazione è effettuata in background mediante una state machine. Una volta connesso un dispositivo al connettore USB host, avviene grazie alla state machine il processo di enumerazione, alla fine del quale il dispositivo è configurato ed entra nello stato di running. Successivamente, l’embedded host richiama l’event handler del client driver (descritto di seguito) per l’inizializzazione del dispositivo: alla fine di questo processo il device è pronto per iniziare i trasferimenti dati veri e propri. Quindi, lo stack embedded host richiede che siano definiti due funzioni nel client driver:

Il Device Initialization handler, come visto, è richiamato alla fine del processo di enumerazione, dopo che sia stata scelta una particolare configurazione del device (se questo ne presenta più di una). In questa funzione saranno definite le procedure d’inizializzazione specifiche del device: se l’inizializzazione va a buon fine l’handler dovrà ritornare il valore TRUE, in caso contrario dovrà ritornare FALSE. L’handler dovrà avere un prototipo corrispondente alla typedef seguente:

typedef BOOL (*USB_CLIENT_INIT)
(BYTE address, DWORD flags);
Nel caso del Mass Storage client
driver, il nome di questa funzione
è
USBHostMSDInitialize()

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

Sempre nel caso del Mass Storage client driver, il nome della funzione è USBHostMSDEventHandler() I nomi degli event handlers andranno inseriti 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.

IL MASS STORAGE CLASS CLIENT DRIVER

I dispositivi USB appartenenti alla classe di Mass Storage sono supportati nella libreria MCHPFSUSB dal Mass Storage Client driver. Questo driver implementa un trasferimento dati di tipo bulk, appartenente alla tipologia di trasferimento dati di tipo asincrono. Questi trasferimenti permettono di spostare grandi quantità di dati con garanzia dell’integrità degli stessi ma senza la garanzia dei tempi di consegna. Infatti, i trasferimenti bulk occupano tutta la banda che in un frame USB (slot temporale di 1 ms) è lasciata libera dagli altri protocolli di comunicazione (control, interrupt e isochronous). Sono quindi adatti al trasferimento di dati tipici dei dispositivi di memoria di massa, come Flash drive e memory card (ad esempio schede SD). Il driver di Microchip utilizza in particolare 3 endpoint:

I TRASFERIMENTI BULK NEL MASS STORAGE CLASS CLIENT DRIVER

Ogni trasferimento bulk consiste di tre fasi:

Il pacchetto command è incapsulato in un Command Block Wrapper (CBW) un pacchetto avente dimensione di 31 Byte. I campi principali di questo pacchetto sono

Il pacchetto status è a sua volta incapsulato in un Command Status Wrapper (CSW). I campi principali di questo pacchetto, della dimensione di 13 Byte, sono:

CLASS SUBCLASS E PROTOCOL DEI DEVICE SUPPORTATI

Com’è noto, per ogni dispositivo di una classe USB è necessario specificare la classe di appartenenza, la sottoclasse ed il protocollo di trasferimento, in un’apposita struttura dati. Nel caso della classe MSD sono usati i campi seguenti, contenuti nell’Interface Descriptor (invece che nel Device Descriptor)

Utilizzo del Mass Storage Class client driver

Le applicazioni che fanno uso della classe USB Mass Storage, generalmente interagiscono con il client driver tramite uno o più strati intermedi. Ad esempio, l’applicazione che fa uso del Mass Storage client driver per Flash drive utilizza due layer intermedi

In figura 2 è riportato lo schema dei layer che costituiscono l’applicazione flash disk che vedremo come esempio d’uso del Mass Storage client driver.

Figura 2: schema dei layers dell’applicazione di Mass Storage.

Figura 2: schema dei layers dell’applicazione di Mass Storage.

LE FUNZIONI D’INTERFACCIA TRA IL CLIENT DRIVER ED IL MEDIA INTERFACE LAYER

Il Mass Storage client driver richiede che siano definite due funzioni d’interfaccia nel layer soprastante (il Media Interface layer). La prima funzione è l’initialization handler, che dev’essere del tipo definito dalla seguente typedef:

typedef BOOL (*USB_CLIENT_INIT)
(BYTE address, DWORD flags).

Questa funzione svolge le funzionalità

d’inizializzazione richieste dal media interface. Viene richiamata dal Mass Storage client driver dopo che il dispositivo è stato enumerato ed inizializzato. La seconda funzione della quale va definita l’interfaccia nel media interface, è definita come

typedef BOOL
(*USB_CLIENT_EVENT_HANDLER) (BYTE
address, USB_EVENT event, void
*data, DWORD size)

La funzione gestisce gli eventi che si verificano durante il funzionamento del dispositivo. Un esempio è l’evento EVENT_DETACH, che si verifica quando il dispositivo viene disconnesso: in questo caso il livello media interface dovrà rimuovere il device dall’elenco dei media connessi, oltre ad eseguire le altre azioni necessarie.

INIZIALIZZAZIONE DEL CLIENT DRIVER

Il client driver viene inizializzato tramite la funzione USBHostMSDInit(); è necessario richiamare questa funzione una sola volta durante tutta l’esecuzione dell’applicazione. Le funzioni d’inizializzazione del media interface, dell’USB host driver e del client driver sono richiamate dalla macro USBInitialize(), il cui codice può essere generato tramite la utility USBConfigTool.

UTILIZZO DEL CLIENT DRIVER

L’USB Host driver ed i client driver eseguono le loro operazioni in background tramite delle routine che vanno richiamate in un loop dell’applicazione in maniera continuata. La cadenza con cui sono richiamate queste funzioni non è critica, dato che tutte le operazioni time critical sono eseguite tramite gli interrupt del modulo USB. In particolare, il Mass Storage client driver utilizza la routine USBMSDTasks(). mentre l’host driver usa la routine USBHostTasks(). Il tool di configurazione crea la macro USBTasks() che richiama a sua volta queste routine. Ad esempio, nel caso della classe Mass Storage, la macro sarà

#define USBTasks()           \
  {
 \
         USBHostTasks();     \
         USBHostMSDTasks();  \
 }

La comunicazione con il dispositivo tramite il Mass Storage client driver avviene grazie alle funzione seguente per poter leggere i dati dal dispositivo

BYTE USBHostMSDRead(
BYTE deviceAddress,
BYTE deviceLUN,
BYTE *commandBlock,
BYTE commandBlockLength,
BYTE *data,
DWORD dataLength);

e dalla funzione

BYTE USBHostMSDWrite(
BYTE deviceAddress,
BYTE deviceLUN,
BYTE *commandBlock,
BYTE commandBlockLength,
BYTE *data,
DWORD dataLength);

per poter scrivere i dati verso il dispositivo. Nel caso in cui si usi il layer SCSI Media interface, come per l’applicazione Flash drive, il parametro commandBlock contiene il comando SCSI della particolare operazione che il dispositivo dovrà eseguire. Queste funzioni sono non bloccanti, per cui è necessario attendere che l’operazione richiesta venga completata controllando il valore ritornato dalla funzione

BOOL
USBHostMSDTransferIsComplete(
BYTE deviceAddress,
BYTE *errorCode,
DWORD *byteCount)

Questa funzione andrà quindi eseguita in un loop, nel quale dovranno essere richiamate come già visto le operazioni da svolgere in background: il loop terminerà quando la funzione ritornerà il valore TRUE. Ad esempio, per trasferire dei dati dall’host al dispositivo, si userà del codice del tipo seguente

error = USBHostMSDRead (device,
0, command, commandlength, buffer,
buffersize);
if (!error) {
   while (!USBHostMSDTransferIsComplete(
device, &error,
&bytecount )) {
         USBHostTasks();
         USBHostMSDTasks();
   }
}

Un esempio d’uso del Mass Storage cliet driver: gestione di un Flash drive Lo stack USB fornito da Microchip fornisce numerosi esempi di applicazioni sia device che host. Notiamo che nelle versioni più recenti (nell’articolo ci riferiamo al MCHPFSUSB v. 2.7a – Agosto 2010) questo stack è incluso nella più ampia libreria denominata Microchip Application Libraries, comprendente lo stack TCP/IP, la libreria grafica Graphics Library, ed altre librerie di utilità. La libreria è scaricabile da [2]. Per quanto riguarda le applicazioni host di tipo Mass Storage, sono incluse due demo:

Ci riferiremo nel seguito a questa seconda applicazione. L’applicazione è strutturata come uno stack su cinque livelli, la cui struttura è riportata in figura 2.

COMANDI PER LA GESTIONE DEL FILE SYSTEM

Come risulta dalla figura 2, il livello File System fornisce al livello applicazione soprastante i comandi di base per la gestione dei file: apertura, chiusura, lettura, scrittura, ecc. Nel livello File System originale (descritto nell’application note AN1045 [3]) sono usate delle funzioni di basso livello per interfacciarsi direttamente con l’hardware. Nel caso dello stack USB invece, queste funzioni sono state sostituite con dei comandi SCSI (implementati dal livello sottostante, SCSI Command) che utilizzano il Mass Storage client driver per il trasferimento effettivo. Il livello applicativo mette a disposizione diversi comandi per la manipolazione dei file. Di seguito è riportato l’elenco di questi comandi con specificate le funzioni rese disponibili dal livello File System che sono utilizzate:

Figura 3: USB Configuration tool, configurazione Main.

Figura 3: USB Configuration tool, configurazione Main.

 

Figura 4: USB Configuration tool, configurazione Host

Figura 4: USB Configuration tool, configurazione Host

CONFIGURAZIONE HARDWARE E SOFTWARE

L’applicazione è già configurata per essere implementata su una delle configurazioni hardware fornite da Microchip. Una delle configurazioni possibili è la seguente:

È comunque possibile usare una scheda custom modificando il file di configurazione hardware HardwareProfile.h. Nello stack USB fornito da Microchip è incluso un tool di configurazione, USB Config Tool, richiamabile da MCHPFSUSB>Tools>USBConfig. Con questa utility è possibile configurare il driver generando gli opportuni file header e sorgenti che saranno poi inclusi nel progetto MPLAB da compilare. Vediamo come va utilizzato questo tool per configurare il progetto d’esempio (ricordiamo che la demo inclusa nello stack è già configurata).

  1. nella tab Main selezioniamo la famiglia di MCU che vogliamo utilizzare (PIC24/dsPIC nel nostro caso). Come Device Type selezioniamo Embedded Host, quindi lasciamo il resto come è.
  2. Nella tab Host sono configurate le opzioni di base per il driver Host. Selezioniamo solo il transfer di tipo Bulk (quello di tipo Control è sempre selezionato). Gli altri parametri vanno lasciati come sono.
  3. Nella tab Mass Storage viene configurato il Mass Storage class driver. è necessario cliccare la checkbox Mass Storage Client is used in Host mode. Le opzioni di default sono già adeguate nel caso in cui si utilizzi il protocollo SCSI come media interface layer, come nel nostro esempio. Notiamo i nomi delle due funzioni d’interfaccia che vanno definite per l’inizializzazione del dispositivo (USBHostMSDSCSIInitialize()) e per la gestione degli eventi (USBHostMSDSCSIEventHandler()), delle quali abbiamo parlato in precedenza.
  4. Un embedded host come il PIC24 con funzionalità USB OTG, richiede un elenco dei device supportati, identificati o tramite la coppia VID/PID o tramite la classe/sottoclasse di appartenenza. Nel nostro caso, per supportare tutti i Flash disk che utilizzano un file system di tipo FAT con un’interfaccia comandi SCSI, dovremo porre i valori dell’interface descriptor del dispositivo nei campi class, subclass e E’ anche necessario selezionare Mass Storage come Client Driver e fornire un nome nel campo Description. Cliccando su Add to TPL il dispositivo sarà aggiunto alla lista.
  5. Clicchiamo su Generate per generare i file di configurazione, usb_config.c ed usb_config.h, che copieremo poi nella directory sorgente del progetto. Notiamo che non è stato necessario definire i due handler del client driver, poiché nel caso del Mass Storage client driver sono già compresi nel codice (sono le funzioni USBHostMSDInitialize() ed USBHostMSDEventHandler()) e l’USB Configuration Tool è configurato in modo da includerli nel file di configurazione usb_config.c.

Per la compilazione utilizziamo l’IDE MPLAB di Microchip ed il compilatore C30 per PIC24 sempre di Microchip. Entrambi sono scaricabili (il compilatore in versione di valutazione) dal sito di Microchip. Si raccomanda di utilizzare sempre le versioni più recenti disponibili sia di MPLAB che del compilatore C30.

Figura 5: USB Configuration tool, configurazione Mass Storage client driver

Figura 5: USB Configuration tool, configurazione Mass Storage client driver

 

Figura 6: USB Configuration tool, configurazione della TPL.

Figura 6: USB Configuration tool, configurazione della TPL.

DIMOSTRAZIONE D’USO

In figura 7 è riportato un esempio di funzionamento dell’applicazione di Mass Storage, dove il microcontrollore è controllato tramite PC con un programma di terminale. Il PC è connesso tramite interfaccia seriale ad una delle UART del PIC24. Una volta connesso un Flash drive al connettore USB host, questo viene enumerato e riconosciuto (si noti il prompt alla fine del processo) dopodiché sarà possibile inviare i comandi per la gestione del file system, come visibile in figura.

Figura 7: demo di Mass Storage per Flash drive. Il PIC24 è connesso al PC tramite UART ed un’applicazione di terminale.

Figura 7: demo di Mass Storage per Flash drive. Il PIC24 è connesso al PC tramite UART ed un’applicazione di terminale.