Implementazione del protocollo USB su AVR

In questo articolo si discuterà su come implementare dal punto di vista software una interfaccia USB per un microcontrollore della famiglia AVR.

Se dal punto di vista dell’utente finale l’interfaccia USB sta diventando sempre più popolare grazie soprattutto alla sua semplicità, dal punto di vista degli sviluppatori, realizzare una interfaccia USB piuttosto che una comune interfaccia RS-232 risulta più complesso. Soprattutto perché interfacce classiche, come appunto l’RS-232 non necessitavano di driver ne di supporto software dal lato PC. Con l’USB le cose si complicano, infatti se non si riescono a sfruttare i driver già inclusi nei sistemi operativi è necessario un lavoro supplementare di sviluppo software ad alto livello.

REALIZZAZIONE DI UN INTERFACCIA USB

I metodi più semplici a disposizione per inserire, in un dispositivo in corso di sviluppo, l’interfaccia USB sono essenzialmente due. Il primo prevede l’utilizzo di un microcontrollore con già integrato l’hardware USB. In questo caso è sufficiente sapere come lavora il protocollo USB e dedicarsi alla scrittura del firmware per il microcontrollore. Allo stesso tempo è necessario realizzare anche i driver software per il lato PC a meno che non si utilizzi una classe di dispositivi i cui driver sono standardizzati e inclusi nei sistemi operativi. Questa tipologia di microcontrollori tuttavia si rivolge ad una fascia elevata di mercato, per cui il prezzo complessivo della soluzione spesso non si abbina ai requisiti dell’applicazione. Il secondo metodo è quello di utilizzare opportuni circuiti integrati in grado di effettuare una conversione da protocollo USB ad una qualsiasi altra interfaccia. Solitamente viene sfruttata la classica RS232 perché diffusissima in ambito industriale. In questo caso non serve del firmare aggiuntivo , non è necessario conoscere in dettaglio le regole della comunicazione USB e non è neppure necessario scrivere un driver software poiché chi commercializza il convertitore lo fornisce comprensivo di driver per PC. Anche questa soluzione seppur più semplice rispetto alla precedente, rimane comunque abbastanza costosa e richiede spesso più hardware rispetto alla prima soluzione. In seguito ci concentreremo sulla realizzazione di una interfaccia USB su un microcontrollore low-cost della famiglia AVR di Atmel. Tale soluzione tuttavia ha il vincolo della velocità operativa, infatti il microcontrollore riesce a rispettare solamente la più bassa delle tre velocità imposte dalle specifiche USB. Il bus USB è in grado di lavorare a tre diverse velocità in base a come la preriferica collegata si fa riconoscere, le tre velocità sono: low-speed ossia 1.5Mbit/s, full-speed 12Mbit/s e highspeed 480 Mbit/s. In questa applicazione il microcontrollore si presenterà come periferica low-speed in grado di scambiare dati con il PC ad una velocità massima di 1.5Mbit/s.

Figura 1: cavo e collegamento delle resistenze per dispositivi Low Speed.

Figura 1: cavo e collegamento delle resistenze per dispositivi Low Speed.

NOZIONI BASE DI USB

Tutto quel che riguarda la teoria del protocollo USB può essere recuperata dal sito ufficiale www.usb.org, la documentazione a disposizione è veramente tanta e non facile da comprendere dopo una veloce lettura. Di seguito quindi alcune indicazioni veloci per comprendere almeno quelle che sono le basi del protocollo. L’interfaccia fisica dell’USB è composta da 4 fili, Vcc, GND e due linee di segnale DATA+ e DATA-. Sulle linee di alimentazione troviamo circa 5V e un passaggio di corrente di massimo 500mA. La connessione o disconnessione di un dispositivo dal bus è rilevato misurando l’impedenza sulle linee USB. È possibile riconoscere che un dispositivo USB è di tipo low-speed se questo ha una resistenza da 1.5Kohm tra DATAe Vcc. Se invece il dispositivo fosse full-speed la resistenza sarebbe collegata sulla linea DATA+. Rilevando questo pull-up il computer host riconosce il tipo di dispositivo collegato e inizia la comunicazione alla velocità corretta. Il protocollo di comunicazione è di tipo sincrono, la sincronizzazione tra trasmettitore e ricevitore è quindi indispensabile. Un semplice pattern che anticipa i dati consente di fare ciò, questo pattern è ritrasmesso ogni millisecondo per mantenere l’allineamento solo per i dispositivi full-speed. Senza entrare troppo nel dettaglio dei segnali, dei pacchetti e dei tipi di trasferimenti, diciamo solo che quattro tipi di trasferimenti sono possibili: di Controllo, ad interrupt, Sincroni o Bulk. Per ognuno dei tipi di trasferimenti i requisiti sono differenti. I trasferimenti di controllo servono per l’impostazione del dispositivo ma anche per alcune richieste general purpose, devono essere implementati per qualsiasi dispositivo USB. Attraverso i trasferimenti di controllo è possibile dichiarare diverse modalità di scambio dei dati, basandosi su trasferimenti ad interrupt, per dispositivi con un flusso dati costante, trasferimenti sincroni, per dispositivi di streaming, o trasferimenti bulk nel caso di grandi trasferimenti ad intervalli irregolari.

L’IMPLEMENTAZIONE HARDWARE

Lo schema dell’interfaccia USB per il microcontrollore ATtiny2313 è riportato in figura 2.

Figura 2: schema elettrico con ATtiny2313.

Figura 2: schema elettrico con ATtiny2313.

In particolare nello schema si implementa un convertitore USB-RS-232. Le linee dati USB sono collegate ai pin PB0 e PB1 del microcontrollore, questa scelta è obbligata poiché sono le uniche linee in grado di ricevere segnali veloci. I bit di segnale catturati e subiscono uno shift verso destra dal LSB (PB0) al bit di carry, e poi nel registro di ricezione che raccoglie i bit delle linee dati. Il pin PB1 è usato come segnale d’ingresso con funzione di interrupt esterno INT0. La connessione non è necessaria poiché la versione del microcontrollore è quella con il numero più limitato di pin a disposizione, sulle versioni più grandi del micro la connessione supplementare tra DATA+ e INT0 risulta necessaria. Si può vedere la resistenza di pull-up da 1.5Kohm sulla linea DATA- per permettere all’host di riconoscere la periferica come low-speed. Nello schema si può individuare anche il regolatore di tensione per generare una tensione tra i 3V e i 3.6V partendo dalla tensione di bus. Il regolatore utilizzato è un LE35 con uscita 3.5V. Gli altri componenti nello schema sono semplicemente un cristallo utilizzato come sorgente di clock e delle capacità come filtri per l’alimentazione. Lo schema è estremamente semplice ed è composto di pochissimi componenti, il minimo per realizzare un dispositivo in grado di collegarsi su un bus USB.

L’IMPLEMENTAZIONE SOFTWARE

Tutto quel che riguarda il protocollo USB (ricezione e decodifica) viene realizzato nel firmware. Per prima cosa viene ricevuto un flusso di bit di un pacchetto USB, i bit vengono accodati in un buffer. La ricezione ha inizio su segnalazione dell’interrupt esterno INT0, al momento del pacchetto di sync. Durante la ricezione si controllano i bit in attesa del pacchetto di fine (EOP), altro non si può fare vista l’elevata velocità del flusso di dati. Al termine della ricezione il firmware decodifica i pacchetti e li analizza. In primo luogo verifica che siano indirizzati proprio a quel dispositivo analizzando l’indirizzo. Se l’indirizzo è quello del dispositivo, un pacchetto di ACK (acknowledge) deve essere inviato velocemente all’host come risposta. In un secondo buffer lo stream dei bit viene decodificato secondo la codidifica NRZI con bit stuffing tipica del protocollo USB. Può capitare di ricevere un secondo pacchetto quando ancora si sta tentando di decodificare il primo, in tal caso il dispositivo rispondendo all’host con un pacchetto di NAK segnala di non essere ancora pron to. Per questo motivo, nella situazione descritta, il firmware pur ricevendo il pacchetto e interpretando l’indirizzo, se c’è una decodifica in atto risponde con un NAK. Una volta decodificata l’operazione da compiere questa viene realizzata, ad esempio la trasmissione di un carattere sulla linea RS-232 e l’attesa del completamento dell’operazione. Anche in questa fase il dispositivo potrebbe essere interrotto da pacchetti provenienti dall’host dalla comunicazione USB (tipicamente pacchetti di tipo IN). A questi pacchetti viene risposto con un NAK fino al termine dell’operazione sulla linea  RS-232. Quando la richiesta dell’host necessita di una risposta, questa viene preparata, viene calcolato il CRC e accodato, viene codificato NRZI e aggiunto il bit stuffing. Quando l’host interrogherà per la risposta il firmware sarà gia pronto per fornirla.

FIRMWARE

In figura 3 è mostrato il flowchart del funzionamento del firmware.

Figura 3: flowchart del firmware.

Figura 3: flowchart del firmware.

L’esecuzione del programma è divisa in blocchi: le routine di interrupt, le routine di decodifica, la ricezione USB, la trasmissione USB, la decodifica di un azione richiesta e l’esecuzione di un azione richiesta. Una delle parti più delicate del firmware è sicuramente la routine associata all’interrupt esterno 0. Questa è sempre attiva durante l’esecuzione del resto del codice. Un fronte di salita sul pin INT0, corrispondente al sincronismo di inizio trasmissione, attiva la routine di ricezione. Una veloce operazione di sincronizzazione deve essere realizzata con accuratezza sfruttando i bit rimasti del sincronismo, alcuni verranno persi per la latenza nell’esecuzione dell’interrupt. Dopo il sincronismo vengono campionati i dati, i dati arrivano a 1.5Mbit/s, cioè a 1.5Mhz, il microcontrollore lavora a 12MHz, perciò abbiamo a disposizione solo 8 cicli per il campionamento, la memorizzazione in un buffer, lo shift del buffer, la verifica della ricezione completa del byte, la memorizzazione in SRAM e il controllo della fine pacchetto (EOP). Come detto infatti questa è la parte più importante del firmware, ogni cosa deve essere effettuata con il giusto tempo. Completata la ricezione parte la decodifica del pacchetto. Molto rapidamente viene analizzato l’indirizzo contenuto nel pacchetto e la tipologia (SETUP, IN, OUT, DATA), il tutto ancora all’interno dell’interrupt poiché serve una risposta molto veloce. A questo punto viene inviato un ACK oppure un NAK all’host come descritto in precedenza. Terminata la ricezione i dati sono copiati in un altro buffer per iniziare la decodifica e il buffer di ricezione viene liberato per i nuovi pacchetti in arrivo. L’interrupt 0 deve poter lavorare velocemente indipendentemente dall’esecuzione momentanea del codice, per questo motivo non sono ammesse disabilitazioni degli interrupt e anche durante gli altri interrupt, INT0 rimane attivo. Durante la ricezione, il tipo di pacchetto viene individuato e determinati flag vengono impostati, nel main loop si analizzano i flag e se necessaria viene preparata una risposta per l’host. Il main loop è abbastanza semplice, deve infatti solo verificare i flag relativi alle azioni da intraprendere, in aggiunta deve verificare situazioni di reset sul bus per reinizializzare il dispositivo. Nel caso di risposte da fornire all’host il tutto viene preparato nel buffer di trasmissione e attivato un apposito flag di ok per l’invio. L’invio fisico è realizzato dalla routine di ricezione in risposta ad un pacchetto di IN.

 

 

Una risposta

  1. Giovanni Di Maria Giovanni Di Maria 31 maggio 2019

Scrivi un commento