Corso PIC-USB
Corso di programmazione per PIC: l’interfaccia USB B Alla scoperta della funzionalità USB implementata nei microcontrollori della Microchip. Un argomento di grande attualità in considerazione della crescente importanza di questa architettura nella comunicazione tra computer e dispositivi esterni. In questa prima puntata ci occupiamo degli aspetti teorici dell’USB, premessa fondamentale per affrontare lo studio del firmware.
1
a cura di Carlo Tauraso
uesto Corso si prefigge di spiegare le modalità di programmazione delle funzionalità USB incorporate nei microprocessori della famiglia PIC16C745/765 di Microchip. Le puntate sono state organizzate in maniera tale da offrire un panorama completo sull'argomento partendo dai rudimenti teorici dell'architettura USB fino ad arrivare allo sviluppo completo del firmware e del software necessario a far comunicare un computer con un dispositivo basato su tale microprocessore. Lo sviluppo verrà presentato attraverso l'utilizzo di un linguaggio molto semplice come il BASIC ma non mancheranno le porzioni di codice assembler e le spiegazioni su firmware già prodotto dalla casa madre. Per quanto riguarda lo sviluppo lato PC verranno presentate delle soluzioni basate su Delphi ma anche in questo caso ci saranno dei riferimenti analitici che permetteranno l'utilizzo di un qualsiasi ambiente orientato agli oggetti. Il percorso si alternerà tra esempi di programmazione e riferimenti di utilizzo pratico, attraverso la realizzazione di una serie di esperimenti che permetteElettronica In - ottobre 2004
ranno di mettere in pratica le nozioni apprese. Per mettere in condizione tutti di realizzare gli esempi proposti si farà riferimento all’interfaccia USB presentata sui numeri 90 e 91 (disponibile sia in kit col codice K8055 che già montata e collaudata col codice VM110). Dopo questa breve premessa, non ci resta che iniziare affrontando gli aspetti teorici e le definizioni che saranno indispensabili per comprendere gli argomenti affrontati nel prosieguo. USB, motivazioni e caratteristiche L'architettura USB (Universal Serial Bus) nasce come standard industriale riconosciuto alla fine del 1998 quando Compaq, Intel, Microsoft e NEC presentano le specifiche nella versione 1.1. L'idea alla base di tutto è stata quella di realizzare un sistema di interconnessione con le periferiche che fosse semplice da utilizzare, veloce, economico, che permettesse di collegare più dispositivi su una medesima porta e che tale collegamento supportasse una riconfigurazione dinami- > 81
L’integrato al quale faremo riferimento in questo Corso è il PIC16C745 di cui riportiamo in questo box lo schema a blocchi interno e la pin-out. Il micro dispone di una Program Memory da 8K e di 256 Byte di RAM. Il dispositivo integra anche 5 moduli A/D con una risoluzione di 8 bit e 2 moduli PWM , oltre all’interfaccia USB che fa capo ai pin 15 e 16.
ca permettendo la connessione e disconnessione dei dispositivi senza particolari interventi. Il sistema prevede inizialmente due modalità di trasmissione: low-speed (1,5 Mbps) e full-speed (12 Mbps). Nel 2000 vengono presentate le specifiche 2.0 che integrano le precedenti aggiungendo una terza modalità high-speed che raggiunge i 480 Mbps (Megabit al secondo). Noi ci occuperemo principalmente della versione 1.1 in quanto attualmente la famiglia PIC16C745/765 di Microchip permette di utilizzare esclusivamente la modalità low-speed, inoltre buona parte dei PC installati sono equipaggiati con una porta conforme a tali specifiche ed i driver sviluppati 82
per i vari sistemi Microsoft hanno raggiunto ormai un buon grado di affidabilità. C'è da precisare comunque che Hub e porte certificati USB 2.0 sono pienamente compatibili verso il basso tant'è vero che se devono comunicare con un dispositivo precedente eseguono una risincronizzazione della trasmissione, e tramite buffer riducono la velocità a 1,5 o 12 Mbps. In questo modo dispositivi USB 1.1 possono convivere con sistemi di nuova generazione senza dover subire alcuna modifica. Ciò varrà, naturalmente, anche per i prototipi che andremo a realizzare. L'architettura USB gestisce collegamenti multipli di periferiche ed in cascata fino ad un massimo di 127 dis- > ottobre 2004 - Elettronica In
Corso PIC-USB
Schema a blocchi e pinout dell’integrato PIC16C745
Corso PIC-USB
positivi per ciascun controller. Ciascun dispositivo, inoltre, può essere connesso e disconnesso mentre il PC e le altre periferiche sono attive e funzionanti attraverso un sistema ingegnoso di condivisione della banda ed una tecnica di rilevamento dello stato delle periferiche. In questo modo l'attività dell'utente per la riconfigurazione del proprio PC risulta enormemente semplificata. Chi di voi ha in passato fatto i conti con i conflitti di IRQ e lo spazio di indirizzamento quando installava una scheda PCI (o ISA nella notte dei tempi) può tirare un sospiro di sollievo; in questo caso non sarà necessario neppure aprire il case del PC. Infine, bisogna tener presente che lo standard USB prevede la possibilità di fornire l'alimentazione direttamente dal PC (fino a max 500 mA) risolvendo anche il problema delle batterie o degli alimentatori esterni. Architettura USB Il modello di interconnessione USB si compone principalmente di due componenti fondamentali: USB Host: ogni sistema USB ha un unico Host che è assimilabile al nostro PC. Più precisamente l'Host utilizza come interfaccia il controller USB integrato nella scheda madre o in una scheda PCI aggiuntiva. Quest'ultimo, a sua volta, include un dispositivo che gestisce le diverse porte presenti chiamato "root-hub". USB Device: sono i dispositivi che colleghiamo alle porte USB del nostro PC e possono essere a loro volta di due tipi: Hub che offrono ulteriori punti di connessione con una porta di upstream verso l'host e una o più porte di downstream verso altri hub o dispositivi, o Terminali che offrono invece una serie di funzionalità al sistema e possono essere tastiere, mouse, il nostro prototipo PIC ecc. Quindi dal punto di vista topologico l'architettura USB si basa su un'interconnessione a stella organizzata su più livelli, fino a 6 (vedi Fig. 1). Il centro stella è dato da un Hub, ogni segmento è rappresentato da un cavo di connessione che può collegare: - host e hub - host e terminale - hub e hub - hub e terminale Dal nostro punto di vista di sviluppatori PIC ci riferiremo fondamentalmente ad un modello semplificato a solo due strati, visto che in realtà ogni prototipo potrà essere collegato a qualunque Elettronica In - ottobre 2004
livello della catena rimanendo invariate le problematiche di comunicazione che affronteremo. Anticipo, che le periferiche USB sono divise in classi che raggruppano quelle con le medesime funzionalità. Ad ogni classe viene assegnata una certa priorità nell'utilizzo della banda passante. Il nostro PIC verrà trattato dal Host Controller in maniera preferenziale rispetto ad altri dispositivi. Sarà infatti un HID (Human Interface Device) cioè una periferica in grado di generare un interrupt quindi ad alta priorità (in realtà vedremo che il concetto di interrupt nel USB è un pò differente da quello solito che conosciamo). Il modello a due strati diviene quindi ampiamente condivisibile visto che le specifiche USB fanno sì che il controller ci prenoti sempre un "posto" nella sequenza di dati che transita nei vari strati. Per noi risulterà quindi assolutamente trasparente il fatto di collegare il nostro prototipo all'inizio o alla fine della catena visto che per il controller saremo sempre tra i primi della classe. Naturalmente bisogna far attenzione a non esagerare. Se si osserva il modello, infatti, ci accorgiamo che mano a mano che il traffico risale dalle singole periferiche verso il PC, attraversando i diversi Hub, tutto si concentra in un numero sempre minore di fili e alla fine un solo cavo trasporta al PC tutte le informazioni fornite in con-
Fig. 1
Modello Architettura USB
temporanea dai diversi apparati della catena. La banda passante disponibile è di 12 Mbps e viene divisa sulla base di chi prima arriva meglio alloggia, perciò il primo apparato collegato al sistema otterrà tutta la banda di cui ha bisogno e gli altri dovranno dividere quel che resta fino a quando ne resterà talmente poca da costringere il sistema di autoconfigurazione a rifiutare nuove periferiche che abbiano bisogno di più banda passante di quanta ne resti libera. Il PC si limiterà a scartare il dispositivo, senza però necessariamente segna- > 83
Fig. 2
Modello semplificato USB
84
problema dell'arbitraggio del canale di comunicazione. Se ci pensiamo un attimo è la situazione inversa del modello Ethernet utilizzato nelle reti di PC, dove il terminale comunica non appena ne ha la necessità e si è dovuto prevedere, studiando opportune tecniche di condivisione del canale, il caso in cui malauguratamente due terminali decidano di comunicare contemporaneamente. Endpoints e Buffers Assodato che la comunicazione USB avviene tra due personaggi principali: un host ed un device con ruoli ben distinti, focalizziamo la nostra attenzione sul seguente schema che ci permette di introdurre due altri concetti chiave nello sviluppo USB: Endpoints e Buffers (vedi Fig.3). Abbiamo visto che un Device si può considerare come un dispositivo che offre delle funzionalità. Il sistema USB vede ogni Device come un insieme di Endpoints che si possono considerare come degli oggetti indipendenti l'uno dall'altro in grado di trasmettere o ricevere dati o informazioni di controllo attraverso un canale di comunicazione detto "pipe". In pratica, (lo vedremo analizzando i descrittori) gli endpoints costituiscono le interfacce verso le funzioni che il dispositivo offre. Inoltre le interfacce comporranno dal punto di vista logico delle modalità di configurazione per cui un dispositivo potrà avere diversi modi di funzionamento. Dalla parte Host ad ogni endpoint si associa un buffer di memoria che il software Client utilizzerà per trasmettere e ricevere a sua volta i dati dal device. Quindi, dal punto di vista logico tutta la comunicazione USB avviene tra un buffer ed un endpoint attraverso una pipe. Gli endpoint ed i buffer sono in pratica gli estremi del flusso di comunicazione tra un host ed un device. Nel firmware e nel software che andremo a sviluppare si utilizzeranno esclusivamente questi due oggetti per trasferire dati da una parte all'altra. E' chiaro che bisognerà riuscire ad identificare univocamente ogni endpoint. Ebbene ogni dispositivo ha un numero identificativo assegnato dal sistema nel momento in cui esso viene collegato alla porta. Ogni endpoint a sua volta ha un numero che viene deciso al momento della costruzione del dispositivo. Infine, ogni endpoint ha una sua direzione di lavoro (IN/OUT) nel senso che trasmette o riceve. La direzione è sempre stabilita rispetto all'host. La combinazione dei due numeri e della direzione permette di identificare univocamente > ottobre 2004 - Elettronica In
Corso PIC-USB
lare l'errore. L'impressione sarà che l'oggetto sia difettoso, anche se in realtà funziona benissimo, ma chiede semplicemente più di quello che può essergli concesso, e perciò non riceve nulla. Nel caso esagerassimo riempiendo il PC di periferiche multimediali (apparati che abbiano un traffico isocrono come videocamere digitali o dispositivi audio) potremmo arrivare a saturare la banda passante e far si che il nostro bel prototipo di miti pretese (si consideri che utilizzeremo pacchetti con lunghezza max di 8 byte su 1500) possa dar fastidio a qualcuna di queste. Infatti, il nostro circuito riceverà l'attenzione di cui necessita (il nostro posto sul treno di pacchetti è prenotato) a scapito magari della nostra videocamera digitale. Qualcuno di voi forse avrà già intuito che con un sistema del genere è molto importante che la progettazione del dispositivo e del relativo driver siano fatte in maniera intelligente ed oculata. Si pensi a che cosa succederebbe se il driver non liberasse la banda utilizzata quando il dispositivo non è in uso. Nel prosieguo ci riferiremo ad un modello semplificato a due soli strati con un host ed un device terminale che sarà il nostro prototipo (vedi Fig. 2). I concetti di Host e Device sono fondamentali per capire i diversi ruoli che si vengono a creare nell'interazione attraverso l'USB. In particolare attraverso queste due definizioni si può ben comprendere la diversità di funzioni che dovranno essere svolte dai due oggetti principali di questo corso: il firmware (lato device) ed il Client software (lato Host). Si tenga ben presente, che l'architettura USB è centrata sull'host. Quindi, in ogni situazione è l'host a comandare nella comunicazione. Le periferiche non possono inviare dati se non sono contattate dall'host. Viene utilizzato un protocollo a "Token", l'host inizia inviando un pacchetto che autorizza il terminale a comunicare. Se la periferica è pronta risponde all'invito e si avvia la procedura di handshaking che inizia lo scambio dei dati. L'host controlla tutto il sistema ed in questo modo si elimina il
Corso PIC-U USB
Fig. 3
Flussi di comunicazione Endopints e Buffers
ogni endpoint. Ogni dispositivo USB deve obbligatoriamente contenere almeno una coppia che costituisce l'Endpoint numero 0 (0-IN e 0-OUT). Questo è necessario perchè è stata prevista una pipe di default chiamata "Default Control Pipe" che viene utilizzata dal sistema per inizializzare, configurare e controllare lo stato del dispositivo. Nel momento in cui un dispositivo è collegato alla porta, alimentato ed ha ricevuto un segnale di reset l'endpoint zero deve essere sempre accessibile attraverso la pipe di default. La famiglia PIC16C745/765 implementa 6 endpoints sono, cioè, disponibili i tre numeri di endpoint 0,1,2 ciascuno con due direzioni possibili. In generale secondo le specifiche USB 1.1 i dispositivi che lavorano in full-speed possono avere fino a 15 numeri di endpoint mentre quelli low-speed al massimo 6 (si devono sempre considerare a coppie una in ingresso ed una in uscita). Infine si tenga ben presente che le pipe aggiuntive e relativi endpoints sono disponibili esclusivamente dopo la configurazione del dispositivo e non sono direttamente accessibili se non dopo tale fase. Fondamentalmente il software Client richiede i dati attraverso una pipe usando un IRP (I/O Request Packet) ed attende che la loro trasmissione sia completata. Tipologie di trasferimento dati Le "pipes" possono utilizzare quattro tipologie di trasferimento dei dati: Trasferimenti di Controllo: vengono effettuati ad intervalli non regolari e sono iniziati dall'host attraverso una richiesta a cui segue una risposta da parte del dispositivo, tipico è il caso dell'host che richiede lo stato del device. Trasferimenti Isocroni: corrispondono ad una comunicazione continuativa tra host e device e riguardano tipicamente il trasferimento di informazioni audio e video dove il tempo è rilevante Elettronica In - ottobre 2004
nel stabilire una corretta comunicazione. Trasferimenti Interrupt: riguardano pacchetti di dati relativamente piccoli ed utilizzano un sistema chiamato "bounded-latency communication". Come vi avevo accennato il concetto di interrupt in questo caso è diverso da quello usuale in quanto la comunicazione è comandata dall'host ed in nessun caso il dispositivo può prendere l'iniziativa. Solitamente un interrupt viene visto come un segnale attraverso il quale un dispositivo richiede l'attenzione di un host provocando da parte di quest'ultimo l'esecuzione di una routine di servizio per gestire la condizione che ha generato tale richiesta. Nell'architettura USB tale definizione non può essere adottata per la posizione "master" dell'host pertanto al dispositivo viene assegnata una frequenza di interscambio dei dati ed il sistema non fa altro che interrogarlo a tale frequenza per vedere se ha pacchetti da inviare. Questa tipologia di trasferimento è usata tipicamente nelle tastiere e nei dispositivi di puntamento come i mouse. Trasferimenti Bulk: sono l'opposto di quelli isocroni nel senso che la loro trasmissione può essere dilazionata nel tempo tipicamente sono ad esempio i dati che provengono da uno scanner o vengono inviati ad una stampante. La famiglia PIC16C745/765 può utilizzare esclusivamente due tipi di trasferimento, quelli di Controllo e quelli Interrupt. Introduzione al concetto di descrittore Ogni dispositivo conserva una serie di informazioni generali che ne descrivono la tipologia ed il funzionamento, tale struttura viene chiamata descrittore. Nel momento in cui costruiremo i nostri prototipi una buona parte dello sviluppo sarà dedicata alla determinazione di tale struttura che risulta indispensabile per farli funzionare. Nel prosieguo verrà presentato un paragrafo che descrive analiticamente come si realizza un descrittore e quali sono i campi necessari. Per il momento ci basta considerarare che esistono 5 tipologie di descrittori: 1) Device: ogni dispositivo ha un unico "device descriptor" che contiene un insieme di informazioni generali come l'ID produttore, il serial number, la massima lunghezza dei pacchetti da usare per dialogare con l'endpoint 0 ecc. 2) Configuration: ogni dispositivo può avere uno o più "configuration descriptor" che contiene le informazioni relative a ciascuna modalità di > 85
il cambiamento di stato conseguente. Lo si può sintetizzare in 5 fasi fondamentali: 1) L'hub a cui viene collegato il dispositivo informa l'host dell'evento (attraverso una risposta ad una interrogazione a intervalli regolari da parte dello stesso), si attendono 100ms affinchè il processo di inserimento sia terminato e l'alimentazione dal bus si stabilizzi. 2) L'host abilita la porta a cui ci si è connessi ed invia un segnale di reset per un intervallo di 10ms. 3) L'host assegna un indirizzo univoco al dispositivo. 4) L'host legge i descrittori del dispositivo con le informazioni di configurazione. 5) Sulla base delle informazioni ricevute l'host invia un numero di configurazione al dispositivo che da questo momento in poi è pronto per essere utilizzato. Nel caso il dispositivo venga disconnesso, la cosa viene notificata all'host il quale disabilita la porta corrispondente ed aggiorna la situazione dei dispositivi collegati. Prima di addentrarci nel "USB Device Framework" ispiratore del Firmware Microchip che andremo ad utilizzare, soffermiamoci ancora su due argomenti di contorno relativi alle caratteristiche meccaniche ed elettriche dell'interfaccia USB. Caratteristiche Meccaniche Per facilitare la vita agli utenti che devono connettere fisicamente il dispositivo al PC sono stati realizzati dei connettori specifici per il down“A” Maschio
“B” Maschio
“A” Femmina
Il processo di enumerazione Un altro concetto "teorico" indispensabile per uno sviluppatore di interfacce USB è il processo di enumerazione. Quando un dispositivo viene collegato o scollegato dalla porta il sistema utilizza tale processo per identificarlo e per gestire 86
Corso PIC-USB
funzionamento dello stesso. In particolare ogni configurazione può definire una o più interfacce ed ogni interfaccia può a sua volta contenere zero o più endpoints (attenzione che non si considera l'endpoint 0 che deve essere sempre disponibile). Ad esempio in questo descrittore andremo a definire i livelli di corrente necessari in ciascuna modalità, quindi ad esempio potremo precisare una configurazione ad alto consumo ed un'altra a basso consumo. Nel caso di un’interfaccia ISDN che ha a disposizione due canali di comunicazione da 64Kbps, potremo stabilire una modalità a singolo canale ed una che prevede l'utilizzo cumuulativo di entrambe raggiungendo la velocità di 128Kbps. 3) Interface: permette di definire ciascuna interfaccia all'interno di una specifica configurazione precisando il numero di endpoints che utilizza. 4) Endpoint: stabilisce le caratteristiche di ciascun endpoint all'interno di un interfaccia, si precisa ad esempio la grandezza dei pacchetti utilizzata per comunicare con esso e l'intervallo di polling cioè l'intervallo di tempo a cui l'host interroga l'endpoint per conoscere il suo stato (vedremo che sarà essenziale per gestire i trasferimenti interrupt). 5) String: si tratta di descrittori opzionali codificati tramite UNICODE. Attraverso di essi è possibile codificare ad esempio le informazioni del dispositivo in più lingue. A ciascuno di essi, infatti, è associato un identificativo di linguaggio (LANGID) a 17 bit. L'host quando fa una richiesta, precisa tale id ed il dispositivo risponde inviando solo i descrittori nel linguaggio scelto. Non ci si preoccupi della grande libertà con cui sembra si possano definire device, configurazioni, interfacce, endpoint. Nella realtà dei dispositivi che sono stati implementati (PIC16C745/765) ci sono delle regole ben precise, delle configurazioni consigliate ed anche delle limitazioni che dovremo osservare affinché tutto funzioni. Ad esempio avremo a disposizione al massimo 6 endpoints e ci riferiremo a dei "Technical Brief" di Microchip per comprendere bene la struttura dei descrittori.
“B” Femmina
ottobre 2004 - Elettronica In
>
Corso PIC-USB
stream e per l'upstream in maniera da non confondere i due casi. In particolare si utilizzano connettori di tipo "A" per l'upstream quindi per il collegamento sullo host. Mentre si utilizzano connettori di tipo "B" per il downstream cioè per collegarsi al dispositivo. Naturalmente non è possibile inserire un connettore "A" in una porta "B" e viceversa. Il cavo è costituito da quattro fili: due per l'alimentazione e due per i dati (vedi Fig.4). In particolare cavi adatti all'utilizzo in full-speed devono avere il doppino per i dati intrecciato ed una schermatura esterna. Per l'utilizzo in low-speed, invece, non è richiesto nè l'intreccio nè la schermatura (i nostri prototipi utilizzeranno questa modalità pertanto i cavi si potranno auto-costruire senza particolari problemi). Caratteristiche Elettriche Su un cavo USB sono quindi veicolati i dati attraverso una coppia intrecciata e una sorgente di alimentazione a 5V attraverso un'altra coppia non intrecciata. Riguardo a quest'ultima possiamo suddividere i dispositivi USB in due grandi categorie: quelli che utilizzano il bus per alimentarsi (bus powered devices) e quelli che invece utilizzano una fonte esterna (self-powered devices). Ma quanta corrente è possibile assorbire dal cavo USB? Le specifiche 1.1 introducono il concetto di unità di carico (unit load) che equivale a 100mA. In base a tale definizione le periferiche si raggruppano in due classi: quelle a basso consumo (<=100mA) e quelle ad alto consumo (da 1 a 5 unit load). Di default tutti i dispositivi vengono trattati dal sistema come a basso consumo garantendo quindi una corrente di 100mA, mentre se si vuole consumare di più sarà necessario avviare una negoziazione per ottenere fino ad un massimo di 500mA. La distribuzione della corrente è, infatti, gestita via software, sarà quindi quest'ultimo ad autorizzare o meno il passaggio dallo stato a basso consumo a quello ad alto consumo. Realizzando i nostri prototipi bisognerà tener presente quindi che ciascuna porta USB può veicolare una quantità di corrente pari ad un massimo di 500mA e che comunque ne saranno sicuramente garantiti 100. Se progettiamo un dispositivo che utilizzerà come fonte di alimentazione il bus dobbiamo anche considerare che l'assorbimento non può superare 1 unità di carico per tutta la fase iniziale di configurazione della periferica. Soltanto successivamente si potrà Elettronica In - ottobre 2004
Fig. 4
negoziare ulteriori unità per far fronte alle necessità, ma, attenzione, non è detto che tale richiesta vada a buon fine. Nel caso, infatti, il software non riesca a reperire la corrente necessaria, non farà altro che negare l'autorizzazione alla periferica che rimarrà nello stato di basso consumo (e non ci sarà alcun modo di convincerlo ad agire in altra maniera!). Se si prevede, quindi, di utilizzare una funzione con un assorbimento di corrente piuttosto elevato o che comunque si avvicina pericolosamente al massimo consentito è bene dotare la nostra periferica di un alimentatore separato affinchè il dispositivo possa venir utilizzato in qualunque condizione si trovi la sorgente sul bus. Infine, un approfondimento per i più curiosi. I pacchetti trasmessi sul doppino dati USB vengono codificati attraverso un algoritmo chiamato NRZI (Non Return to Zero Invert) che elimina la necessità degli impulsi di clock (più propriamente li mescola alla sequenza dati). Attraverso la codifica NRZI uno '0' è rappresentato da un cambiamento nel livello di tensione mentre un '1' corrisponde all'assenza di tale cambiamento. Una lunga sequenza di 0 comporta l'alternanza di livello per ciascun bit mentre una lunga sequenza di 1 comporta un segnale statico sulla linea (vedi Fig.5). Per ovviare al problema di non avere alcuna transizione per un lungo periodo di tempo, viene inserito ogni 6 bit uno zero detto "stuffing bit". In questo modo viene forzata una transizione almeno ogni 7 bit garantendo quindi una sincronizzazione del sistema. Il ricevente deve, quindi, decodificare lo stream NRZI riconoscere lo "stuffing bit" e scartarlo. Ogni pacchetto è preceduto da 7 bit a 0 seguiti da un 1 che compongono la sequenza di sincroniz- > 87
operazioni definite nelle specifiche visto che risulta ben più produttivo conoscere l'implementazione specializzata Microchip. Si consideri che attraverso l'utilizzo di 9 API (Application Program Interfaces) e 2 funzioni ridefinibili dall'utente è possibile automatizzare tutte le opera-
Fig. 5
tive ai livelli di segnale (stato J,K,0,1) nelle varie modalità di comunicazione Low-Speed, Full-Speed, High-Speed vi rimando alle specifiche v2.0. Nel diagramma V-in è riferito al connettore posto sul dispositivo terminale mentre V-out alla porta sorgente del Hub (vedi Fig. 7). Nel caso del EOP, invece, D+ e D- vengono messi low per un tempo pari a 2 bitTime seguiti da un bitTime allo stato J. La durata del segnale EOP è chiaramente dipendente dalla velocità di comunicazione. Per lo stato di low di entrambe le linee ci si riferisce a SE0 che sta per "Single Ended Zero" (vedi Fig. 8). USB DEVICE FRAMEWORK Nelle specifiche 1.1 il capitolo 9 è riservato alla descrizione degli stati di un dispositivo USB e
zioni descritte permettendo all'utente di concentrarsi esclusivamente sui dettagli specifici della propria implementazione senza dover perder tempo a sviluppare delle funzionalità comuni. Definiamo quindi quali sono gli stati assumibili da un dispositivo e quali sono le operazioni che deve supportare. Gli stati possibili in cui può venirsi a trovare un dispositivo USB sono fondamentalmente 6: Collegato, Alimentato, Stato di Default, Indirizzato, Configurato o Sospeso. Ogni periferica è assimilabile ad una macchina a stati finiti che attraverso le successive interazioni con l'host cambia di stato a seconda della azione intrapresa da quest'ultimo. Il modello presentato ci permette di raccogliere le idee e di capire quale sarà il comportamento dinamico dei prototipi che andremo a costruire. Analizziamo quindi i vari casi:
Fig. 6
delle relative operazioni che esso supporta. Il firmware Microchip contiene un'implementazione di tali operazioni tant'e' vero che il sorgente è stato chiamato USB_CH9.asm (CH9 sta per Chapter 9). Noi non analizzeremo nel dettaglio le 88
Stato Collegato: la periferica viene collegata alla porta, l'alimentazione non viene erogata immediatamente pertanto si dice che il dispositivo è collegato ma non alimentato (connected but not powered). Soltanto successivamente alla confi- > ottobre 2004 - Elettronica In
Corso PIC-USB
zazione (vedi Fig. 6). Infine, ogni pacchetto è racchiuso tra due delimitatori: un segnale di SOP (Start of Packet) ed uno di EOP (End of Packet). In particolare il SOP si realizza nel momento in cui D+ e D- passano dallo stato di Idle al livello logico opposto (K state). Per le tensioni rela-
Corso PIC-USB
gurazione dell'hub da parte dell'host la porta viene alimentata. In generale, considerando il nostro modello semplificato a due soli strati, possiamo tranquillamente pensare che il nostro collegamento avverrà direttamente sul hub root (già configurato) pertanto l'unica azione dell'host è
Register" corrispondente ad una delle modalità di funzionamento elencate nel descrittore (di tipo configuration). Stato Configurato: il dispositivo è pronto per assolvere alle funzioni che gli sono state assegnate.
Fig. 7
quella di attendere un breve intervallo di tempo tra la notifica dell'hub e la stabilizzazione dell'alimentazione sul bus. E' proprio quello che abbiamo visto nella prima fase del processo di enumerazione. Stato Alimentato: il dispositivo è collegato e riceve l'alimentazione del bus, a questo punto interviene il segnale di Reset inviato dall'host che impone un altro cambiamento di stato. Stato di Default: una volta terminato il segnale di reset il dispositivo è indirizzabile attraverso un indirizzo di default e può comunicare con l'host attraverso una pipe preferenziale (vi ricordate la Default Control Pipe e l'endpoint 0 ?). L'host ancora una volta inizia un'azione che comporta un ulteriore cambiamento di stato, assegna cioè un indirizzo univoco al dispositivo. Stato Indirizzato: il dispositivo ha un suo indirizzo ben preciso ma non è ancora pronto a fun-
Stato Sospeso: per il risparmio di energia nel momento in cui il bus rimane inattivo (non c'e' traffico di pacchetti) per un determinato periodo di tempo (nelle specifiche si parla di 10ms). Durante tutto il periodo di sospensione il dispositivo mantiene l'indirizzo assegnato e le relative informazioni di configurazione. Si noti che per ciascun cambiamento di stato il modello prevede un'azione progressiva ed una regressiva che porta il dispositivo ad uno stato precedente. Ad esempio, se durante la sospensione il traffico sul bus riprende, il dispositivo si porterà di nuovo in uno stato attivo, tipicamente quello configurato (a meno che la sospensione non sia arrivata in uno stato intermedio). Veniamo ora alle operazioni che ciascun dispositivo USB deve supportare. Considerando, infatti, che ci troviamo in un'architettura centrata sull'host, il dispositivo non farà altro che rispondere
Fig. 8
zionare. Infatti l'host sulla base delle informazioni scambiate deve configurarlo. Nella pratica non fa altro che assegnargli un numero di configurazione, cioè scrivere un valore in un registro particolare chiamato "Device Configuration Elettronica In - ottobre 2004
ad una serie di richieste precisate dal suo "Comandante". Tutti i dispositivi USB che andremo a creare devono essere in grado di rispondere alle richieste dell'host attraverso la DCP (Default Control Pipe). In particolare tutte > 89
queste richieste sono costituite da 8 bytes così come definite nella tabella di Figura 10. Le specifiche USB definiscono delle richieste standard a cui tutti i dispositivi devono essere in grado di far fronte. Esse si possono schematizzare così come definito nella tabella di figura 11. Naturalmente nel campo bRequest nella realtà vengono passati due byte il cui valore convertito in decimale è stato messo tra parentesi (ad esempio [12] = 00000000 00001100). Inoltre il tipo descrittore che incontriamo in GET/SET DESCRIPTOR viene selezionato attraverso la tabella di figura 12. Infine il selettore di feature può essere solo di due tipi secondo la tabella di Figura 13. Analizziamo più nello specifico una sola di queste operazioni perchè ci ritornerà utile quando dovremo sviluppare il firmware del PIC. Sarà necessario infatti dare un significato specifico alla richiesta SET_CONFIGURATION a seconda del dispositivo che si vuole realizzare. Tale operazione è fortemente legata ai descrittori di 90
tipo configuration. L'operazione di SET_CONFIGURATION è relativamente semplice perchè non fa altro che precisare un valore di configurazione nel byte meno significativo del campo wValue. Questo valore può essere 0 oppure deve corrispondere esattamente a quello contenuto in un descrittore configuration che è stato precisato sul dispositivo che si vuole utilizzare. Attraverso tale valore sarà possibile scegliere attraverso l'host la modalità di funzionamento che avremo sviluppato. Si faccia inoltre attenzione al fatto che nel caso in cui il dispositivo si trovi nello stato "Indirizzato", se il campo passato è 0 il device rimane in tale stato altrimenti viene forzato un cambiamento e il dispositivo assume la modalità specificata dal descrittore corrispondente entrando in "Configurato". Nel caso invece il dispositivo si trovi già in quest'ultimo stadio l'operazione di SET_CONFIGURATION lo farà ritornare allo stato "Indirizzato" se il valore è zero. Mentre se il valore è diverso da zero verrà inizializzata una nuova modalità di funzionamento mantenendo lo stato "Configuration". A noi interesserà in particolare quest'ultimo caso quando vorremo realizzare nuove modalità di configurazione del nostro dispositivo e selezionarle attraverso il software lato Host. Le altre operazioni possono essere così sintetizzate: CLEAR_FEATURE: Disabilita la feature passata in wValue; GET_CONFIGURATION: Ritorna il valore di configurazione selezionato per il dispositivo. GET_DESCRIPTOR: Ritorna il descrittore selezionato attraverso il wValue nel linguaggio selezionato in wIndex. GET_INTERFACE: in ogni configurazione ci possono essere più interfacce i cui settaggi sono mutuamente esclusivi, con questa istruzione si possono reperire tali parametri alternativi. GET_STATUS: ritorna lo stato di un dispositivo, di un interfaccia o di un endpoint. Un dispositivo può essere: - "self-powered": riceve l'alimentazione dall'e- > ottobre 2004 - Elettronica In
Corso PIC-USB
Fig. 9
Corso PIC-USB
Fig. 10
Fig. 11
> Elettronica In - ottobre 2004
91
Fig. 12
necessità di uscire dal suo stato di sospensione per gestire un evento; - "remote wakeup disable": disabilita la feature precedente.
Fig. 13
Un'interfaccia ritorna due byte che sono posti a zero e secondo le specifiche sono riservati. Un endpoint può essere: - Halted: cioè bloccato, lo si usa esclusivamente per gli endpoint in grado di gestire trasferimenti interrupt e bulk; - Not Halted: sbloccato, quindi i trasferimenti possono avvenire tranquillamente. SET_ADDRESS: definisce l'indirizzo univoco che verrà utilizzato per l'accesso al dispositivo. SET_DESCRIPTOR: permette di aggiornare un descrittore o di aggiungerne altri. SET_FEATURE: permette di abilitare o disabilitare una specifica feature, chiaramente tale ope-
Fig. 14
92
razione influenza i possibili valori della get_status. SET_INTERFACE: le interfacce hanno dei settaggi mutuamente esclusivi, questa istruzione permette di selezionarli. SYNCH_FRAME: viene utilizzato esclusivamente nei trasferimenti isocroni in cui si utilizza uno schema di sincronizzazione specifico. In pratica la comunicazione può avvenire attraverso frame di lunghezza diversa a seconda di un formato definito. Con questa istruzione si indica all'host dove lo schema inizia a ripetersi (cioè quando si passa al pacchetto d'informazione successivo). Qualcuno, più attento, si sarà accorto che all'inizio di questo paragrafo (che presenta una panoramica sintetica ma sufficiente sul framework) non abbiamo fatto altro che ripercorrere il processo di enumerazione formalizzandolo in una serie di cambiamenti di stato. Questo fa capire che tale procedura è in effetti il cuore di tutto il meccanismo perchè permette di raggiungere lo stato "configurato" che potrà avere come evoluzione soltanto la creazione di sessioni di comunicazione tra buffers e endpoints. Il firmware Microchip si basa tutto proprio su questa considerazione. Arrivati a questo punto siamo in grado di introdurre il modello firmware presentato da Microchip che studieremo in maniera approfondita iniziando finalmente la parte sperimentale di questo corso. Via via che introdurremo le istruzioni necessarie, infatti, inizieremo a sperimentarne il funzionamento sia analizzando il codice reso disponibile, sia iniziando a scrivere delle routine che richiameranno tali funzionalità e che le manipoleranno per i nostri scopi. Si osservi lo schema di Figura 14 che riassume con grande semplicità il sistema di interazione con il firmware Microchip. Come si vede il codice che andremo a sviluppare andrà ad interfacciarsi con tre funzioni principali che realizzano proprio la considerazione a cui siamo giunti. Dopo il processo di enumerazione gestito attraverso InitUSB che permetterà di portare il dispositivo nello stato "Configurato", realizzeremo delle sessioni di comunicazione attraverso le funzioni PutEPn (EPn sta per Endpoint numero n) e GetEPn che ci permetteranno di scambiare dati attraverso gli endpoint che avremo definito. Bene, appuntamento alla prossima puntata ... preparate PC e demoboard ! ottobre 2004 - Elettronica In
Corso PIC-USB
sterno; - "bus powered": riceve l'alimentazione dal bus; - "remote wakeup enable": il dispositivo sospeso può notificare all'host (anche lui sospeso) la
Corso PIC-USB
Corso di programmazione per PIC: l’interfaccia USB B Alla scoperta della funzionalità USB implementata nei microcontrollori della Microchip. Un argomento di grande attualità in considerazione della crescente importanza di questa architettura nella comunicazione tra computer e dispositivi esterni. In questa seconda puntata analizziamo la struttura del firmware Microchip e le fasi di sviluppo relative al PIC16C745.
2
a cura di Carlo Tauraso
iprendiamo in mano lo schema che abbiamo visto la scorsa puntata e raccogliamo un po’ di idee (vedi fig. 1). C’eravamo lasciati con questa considerazione: nel momento in cui un device USB si trova nello stato “Configurato”, cioè successivamente al processo d’enumerazione, sarà possibile realizzare delle sessioni di comunicazione attraverso gli endpoint che avremo definito. Ricordiamo, inoltre, che il PIC16C745/765 implementa un modulo che supporta esclusivamente i trasferimenti Lowspeed (1,5Mbps) di tipo Control e Interrupt e che ci sono 3 numeri di endpoint a disposizione (0,1,2) per un totale di 6 endpoints (si ricordi che ogni endpoint può essere IN o OUT). Introduzione alla struttura del firmware Microchip Analizziamo ora un po’ più da vicino com’è strutturato il firmware Microchip. Attualmente sul mercato sono state rilasciate due versioni la 1.25 e la 2.00 che sono piuttosto differenti Elettronica In - novembre 2004
soprattutto per la filosofia di funzionamento più che per le funzioni disponibili. Ci riferiremo alla 1.25 perché risulta essere l’unica modificata dalla microEngineering Labs Inc. per il compilatore PBP (PICBasic PRO) e che quindi potremo utilizzare per i nostri programmi in Basic. Naturalmente, analizzeremo anche le innovazioni introdotte dalla 2.00 comparando i due pacchetti. Il firmware messo a disposizione col compilatore si compone di 5 file essenziali: USB_DEFS.INC : contiene la definizione delle variabili e delle macro utilizzate nel resto dei sorgenti. USB_CH9.ASM: contiene l’implementazione di tutte le funzioni definite nel capitolo 9 delle specifiche USB. HIDCLASS.ASM: contiene l’implementazione delle funzioni definite nelle specifiche degli HID (Human Interfaces Devices) che vedremo nei prossimi paragrafi. USB_MAIN.ASM: un’applicazione d’esempio. DESCRIPT.ASM: una struttura di descrittori d’esempio. > 83
Fig. 1
HID Report I dispositivi HID utilizzano tre descrittori suppletivi oltre a quelli standard già visti nella precedente puntata secondo il seguente schema: La presenza del terzo file (HIDCLASS.ASM) ci permette di introdurre nello specifico la definizione di HID. Nel primo capitolo vi avevamo anticipato che avremmo fatto in modo che il nostro PIC sarebbe stato trattato in maniera preferenziale dall’host. Ebbene nell’insieme dei dispositivi Low-speed che possono essere prototipati attraverso il PIC16C745 esiste una classe molto particolare denominata Human Interfaces Devices. Storicamente rappresenta la prima classe di dispositivi ad essere stata implementata per l’USB e ad essere integrata nei sistemi Microsoft. Nasce, com’è facile intuire dal suo nome, per lo sviluppo di tutti quei dispositivi utilizzati dall’uomo per controllare il funzionamento di un elaboratore. Quindi, tra gli HID sono da annoverare mouse, tastiere, joystick, gamepad ecc. Purtuttavia le specifiche HID sono sufficientemente ampie per inglobare tutti i dispositivi che devono dialogare con un host a bassa velocità (max 64Kb/s) ed in entrambe le direzioni. Ecco, quindi, che potremo tranquillamente utilizzare un HID come data logger, sistema di controllo accessi, termometro digitale, robot e chi ne ha più ne metta. Ma che cos’è che realmente ci fa propendere a sviluppare firmware per un HID anziché per un qualsiasi altro device? Sicuramente Windows è il sistema operativo più diffuso al mondo e comprende, nelle versioni 98, 2K, XP, i drivers necessari per la comunicazione con tutta la classe di dispositivi HID. Questo significa che il nostro prototipo non richiederà alcun altro software al di fuori del nostro firmware ed un’eventuale applicazione Host per funzionare. Non dovremo preoccuparci di sviluppare driver particolari o di conoscere a fondo le funzioni e le librerie di sistema, ma potremo personalizzare il modo con cui il sistema riconosce il dispositivo con dei semplici file di testo (.inf). In secondo luogo, un HID semplifica la defini84
Fig. 2
Il descrittore HID è in pratica una tabella che definisce quali sono gli altri descrittori che sono stati definiti. Semplificando, le informazioni minime necessarie sono un flag (SI/NO) per la presenza o meno del descrittore di tipo specificato ed un valore pari alla sua lunghezza. I descrittori successivi possono essere: Report: definisce tutti i dati che saranno scambiati con l’host. Fisico: è opzionale e definisce le parti fisiche del corpo umano che saranno utilizzate per azionare il dispositivo. Inserendo il tutto nella struttura dei descrittori standard (vista nella scorsa puntata) possiamo riassumere la situazione con il seguente diagramma:
Fig. 3
novembre 2004 - Elettronica In
Corso PIC-USB
zione dei descrittori permettendo allo sviluppatore di concentrarsi sulla definizione dell’interfaccia e dei dati che saranno scambiati con l’host. Con quest’ultima affermazione siamo arrivati al punto più importante per lo sviluppo firmware su USB: la definizione di report.
Corso PIC-USB
Focalizziamo la nostra attenzione sul descrittore Report perché ci permetterà di definire nel dettaglio quali saranno i dati scambiati con l’host che è basilare per poter poi realizzare il firmware che farà funzionare il dispositivo. Senza aver definito i report in pratica non potremo comunicare sul bus i dati acquisiti o elaborati dal PIC ed il nostro dispositivo diverrebbe inservibile. Ogni report si può vedere come un insieme di celle informative chiamate Item. Gli Item possono essere di due tipi: Short Item lunghi da 1 a 5 bytes utilizzati nelle applicazioni più comuni, e Long Item lunghi da 3 a 258 bytes utilizzati laddove è necessario scambiare strutture dati molto ampie. Vediamo com'è strutturato uno Short Item (fig. 4) ed a cosa corrisponde ogni singolo bit (fig. 5): Fig. 4
Fig. 5
Come si vede esistono diversi tipi di Item, i più importanti sono i Main Item perché permettono di stabilire i campi dati provenienti da un controllo (ad esempio il tasto premuto su una tastiera o il click del mouse). Essi possono essere di tre tipi: Input: dati che vanno dal dispositivo verso l’host Output: dati che vanno dall’host verso il dispositivo (ad es. lo stato dei led di una tastiera) Feature: dati sia d’input che d’output che non sono utilizzati dall’utente ma s’intendono per un utilizzo interno di controllo del dispositivo. Collection - End Collection: permettono di delimitare un raggruppamento di Input, Output o Feature. Mentre i Main Item stabiliscono la lunghezza di un campo, se il suo valore è assoluto o relativo e Elettronica In - novembre 2004
così via, i precedenti Global e Local stabiliscono degli attributi più generali ad esempio il minimo ed il massimo valore dei campi. In particolare i Local descrivono i campi del successivo Main Item mentre i Global descrivono degli attributi che si applicano a tutti gli item. Tutti questi tag formano una struttura che verrà analizzata da un parser incluso nel driver del sistema operativo che permetterà di rendere disponibili all’applicazione Host i dati necessari. Tutto ciò potrebbe generare un po’ di confusione allo sviluppatore per la quantità d’informazioni che deve strutturare per poter far funzionare il suo prototipo. Nella realtà la struttura da realizzare diviene molto più semplice e lineare se si vanno ad estrarre i campi necessari e sufficienti. L’architettura che è stata pensata per gli HID, infatti, è molto ampia nel senso che si possono utilizzare delle feature molto specifiche, ma allo stesso tempo considera come necessarie solo alcune informazioni che costituiscono le fondamenta su cui poggia tutta la struttura. Ebbene, ogni descrittore Report deve contenere gli Item mostrati in Fig. 6 (tutti gli altri sono opzionali). Tale struttura sarà quella che utilizzeremo in tutti i nostri esperimenti, ed è quella che è alla base dello sviluppo HID in ambito professionale. Per quanto riguarda gli Usage esistono dei documenti ufficiali che definiscono i cosiddetti Usage Tables (sul sito della rivista è possibile scaricarli): sfogliandoli è possibile trovare definizioni per i più disparati dispositivi. Ad esempio alla Usage Page numero 3 che tratta i dispositivi di realtà virtuale (VR=Virtual Reality Devices) si trova un usage denominato “Glove” che rappresenta fino a 20 valori angolari relativi alla posizione delle dita dell’utilizzatore. Forti delle conoscenze acquisite fino a questo punto possiamo iniziare a sviluppare qualcosa inaugurando la parte più interessante di questo corso. Fasi di sviluppo Firmware PIC16C745 I passi da compiere nello sviluppo di un sistema completo che utilizzi i moduli USB del nostro PIC si possono sintetizzare in 4 punti: 1) Analisi e precisazione degli Enpoints 2) Realizzazione e testing dei descrittori standard + HID 3) Sviluppo applicazione principale lato device 4) Sviluppo applicazione principale lato Host Al punto 2 nessuno ci vieta di sviluppare un dispositivo non HID. Infatti il PIC16C745 ha come > 85
unica limitazione quella di essere un device Low-Speed ma potrà funzionare sia come HID che non-HID. Nel secondo caso taglieremo una parte importante del firmware (HIDCLASS.asm) ma dovremo preoccuparci di sviluppare il driver di comunicazione per il S.O. (Sistema Operativo) che utilizzeremo. Analizziamo i diversi punti costruendo il nostro primo semplice firmware: un termometro USB. ESPERIMENTO nr. 1 Per i nostri esperimenti utilizzeremo la demoboard VM110 presentata sui numeri 90 e 91 della rivista, andando ovviamente a sostituire il microcontrollore PIC16C745 OTP con un modello finestrato, in modo da poterlo riprogrammare più volte. In questo primo esempio sfrutteremo le capacità di conversione A/D del nostro PIC, utilizzeremo infatti un’entrata analogica per calcolare la caduta di tensione su una NTC. Come tutti voi ben saprete questo componente (Negative Temperature Coefficient) è caratterizzato dal fatto che il suo valore ohmico diminuisce all’aumentare della temperatura. Utilizzeremo quindi la variazione di tensione corrispondente al fine di monitorare la temperatura dell’ambiente. In particolare abbiamo utiliz-
86
zato una NTC con R25=33 kOhm e l’abbiamo collegata direttamente sui pin del ponticello SK2 della demoboard. Nel programma utilizzeremo come tensione di riferimento proprio la Vdd del PIC quindi l’inserzione in questo punto è molto comoda. Inoltre eventuali tarature (anche per l’utilizzo di resistenze con R25 differenti) potranno essere effettuate agendo sul trimmer ATT1. Naturalmente si tratta di un dispositivo decisamente semplice ma che ci permetterà di analizzare tutti i passi di sviluppo che poi verranno ripercorsi con diversi livelli di dettaglio e d’integrazione negli esperimenti più complessi. Fase 1: Analisi degli Endpoints La decisione relativa a quali enpoints utilizzare è essenziale per impostare le modalità di comunicazione con l’host. Ci sono però delle limitazioni dovute al chip, al firmware e alle specifiche che dobbiamo scrupolosamente osservare. Innanzitutto il nostro PIC è un dispositivo LowSpeed pertanto secondo le specifiche USB 1.1 esso può avere soltanto due endpoint oltre all’endpoint0 che ricordiamo deve essere sempre presente e bidirezionale. Il chip utilizza due strutture principali per gestire gli endpoints: l’EPCn (Enpoint Control Register) che contiene i bit relativi all’abilitazione di ciascun endpoints e alla sua direzione di funzionamento (si ricordi che ogni ep è caratterizzato da un numero ed una direzione) e la BDT (Buffer Description Table) costituita da 40 byte suddivisi in 5 buffers da 8 byte. Ora, di questi 5 buffer dobbiamo riservarne 2 per implementare l’endpoint 0 In e Out (EP0 IN/ EP0 OUT), rimangono quindi 3 buffer da distribuire per i due endpoint suppletivi. La configurazione di default prevede l’assegnazione dei primi quattro buffer rispettivamente a EP0 OUT, EP0 IN, EP1 OUT, EP1 IN. L’ultimo buffer è novembre 2004 - Elettronica In
Corso PIC-USB
Fig. 6
Corso PIC-USB
La calibrazione della sonda di temperatura NTC SK2 ATT1
PIC16C745/JW
condiviso tra EP2 OUT e EP2 IN con un apposito sistema d’arbitraggio. Per le nostre applicazioni avremo quindi a disposizione esclusivamente le seguenti configurazioni: EP1 OUT o EP2 OUT (un solo endpoint in uscita comunicazione unidirezionale host -> device) EP1 IN o EP2 IN (un solo endpoint in entrata comunicazione unidirezionale device -> host) EP1 OUT -EP1 IN (due endpoint uno in ingresso ed uno in uscita comunicazione bidirezionale con l’host) EP1 OUT - EP2 IN (due endpoint uno in ingresso ed uno in uscita comunicazione bidirezionale con l’host) EP1 OUT - EP2 OUT (due endpoint tutti e due in uscita comunicazione unidirezionale host -> device) EP1 IN - EP2 OUT (due endpoint uno in ingresso ed uno in uscita comunicazione bidirezionale con l’host) EP1 IN - EP2 IN (due endpoint tutti e due in entrata comunicazione unidirezionale device -> host) L’unica configurazione non possibile è EP2 OUT - EP2 IN perché l’ultimo buffer è stato condiviso e può essere usato solo da un endpoint alla volta. Tale configurazione si può realizzare solo modificando il firmware Microchip alla funzione SET_CONFIGURATION (File ch9.asm). E’ qui, infatti, che viene stabilita la configurazione di default degli endpoint e l’assegnazione della struttura BDT. Bisogna tener presente comunque che i buffer disponibili sono sempre e soltanto 5 pertanto se diamo maggior libertà agli endpoint 2 dovremo toglierla ad altri. In generale, la configurazione di default permette di realizzare comunicazioni bidirezionali ad 1 canale o unidirezionali da 1 a 2 canali e ciò è più che sufficiente per il 99% dei dispositivi. La scelta di utilizzare uno o due canali, una Elettronica In - novembre 2004
La taratura del nostro sistema nel quale viene utilizzata una NTC con R25= 33 kOhm inserita sui pin di SK2 può essere effettuata con un termometro campione, ruotando il trimmer ATT1 fino a quando il valore visualizzato a monitor non corrisponde con quello rilevato dal termometro. E’ possibile tuttavia utilizzare anche NTC di diverso valore: nella prossima puntata mostreremo come il nostro software si può adattare a diversi sensori.
comunicazione unidirezionale o bidirezionale, dipende dal dispositivo che vogliamo creare. Nel nostro primo esperimento, oltre all’EP0 IN/OUT (bidirezionale), useremo la configurazione EP1 IN. Quindi una comunicazione unidirezionale device - host affinchè sia reso disponibile nella pipe il valore digitale corrispondente alla caduta di tensione. L’host leggerà ad intervalli regolari il buffer relativo acquisendo il dato e tramutandolo nel valore di temperatura corrispettivo. Passiamo alla seconda fase. Fase 2: creazione descrittori standard + HID Realizzeremo un dispositivo HID, pertanto ora dobbiamo cimentarci nella creazione di una struttura di descrittori standard e HID. Innanzitutto bisogna capire come creare tale strutture e quali regole seguire. Per farlo è necessario introdurre i file messi a disposizione dall’ambiente PICBasic Compiler Pro. Essenzialmente si tratta della medesima struttura firmware Microchip con alcune piccole modifiche necessarie per rendere le routine compatibili con il compilatore. Nella directory USB del pacchetto troviamo: USB_DEFS.INC: versione PICBasic compatibile del medesimo file presente nel firmware Microchip USB_CH9.ASM: versione PICBasic compatibile del medesimo file presente nel firmware Microchip HIDCLASS.ASM: versione PICBasic compatibile del medesimo file presente nel firmware Microchip USBDESC.ASM: file aggiunto al firmware contenente solo una include relativa al file che useremo come contenitore di descrittori. USBMOUSE.BAS: versione in Basic dell’applicativo USB_MAIN.ASM contenuto nel firmwa- > 87
LISTATO
DEL
FILE MOUSDESC.ASM
Diventa Tabella n.2 Descrittore CONFIGURATION riscritte le definizioni dei campi
retlw EndConfig1 - Config1 ; ************************************************ retlw 0x00 ; Given a configuration descriptor index, returns retlw 0x01 ; bNumInterfacesNumber of interfaces ; the beginning address retlw 0x01 ; bConfigValueConfiguration Value ; of the descriptor within the descriptions table retlw 0x04 ; iConfigString Index ; ************************************************ ; for this config = #01 Config_desc_index Diventa Tabella n.3 retlw 0xA0 ; bmAttributesattributes movwf temp Evidenziati punti di ingresso ulteriori Descrittore ; bus powered movlw HIGH CDI_start configurazioni del device. Come già retlw 0x32 ; MaxPowerself-powered draws INTERFACE movwf PCLATH spiegato ci possono essere piu' riscritte le definizioni ; 0 mA from the bus. movlw low CDI_start valori di configurazione che vengono Interface1 addwf temp,w dei campi retlw 0x09 ; length of descriptor btfsc STATUS,C scelti dall'host al termine del retlw INTERFACE incf PCLATH,f processo di enumerazione. retlw 0x00 ; number of interface, 0 based array movwf PCL retlw 0x00 ; alternate setting CDI_start retlw 0x01 ; number of endpoints used retlw low Config1 retlw 0x03 ; interface class-assigned by USB retlw high Config1 retlw 0x01 ; boot device ;this table calculates the offsets for each Diventa Tabella n.4 retlw 0x02 ; interface protocol - mouse ; configuration descriptor from the beginning retlw 0x05 ; index to string descriptor Descrittore HID ; of the table riscritte le definizioni HID_Descriptor ; more configurations can be added here retlw 0x09 ; descriptor size (9 bytes) dei campi secondo ; retlw low Config2 retlw 0x21 ; descriptor type (HID) ; retlw high Config2 standard (*) retlw 0x00 ; ************************************************ retlw 0x01 ; HID class release number (1.00) ; Given a report descriptor index, returns retlw 0x00 ; Localized country code (none) ; the beginning address retlw 0x01 ; # of HID class descriptor to follow (1) ; of the descriptor within the descriptions table retlw 0x22 ; Report descriptor type (HID) ; ************************************************ retlw (end_ReportDescriptor - ReportDescriptor) Report_desc_index Diventa la ROUTINE DI retlw 0x00 movwf temp Diventa Tabella n.5 Descrittore ENDPOINT. INDIRIZZAMENTO n.2 per l'indice del Endpoint1 movlw HIGH RDI_start Le modifiche riguarderanno soprattutto la descrittore REPORT retlw 0x07 ; length of descriptor movwf PCLATH grandezza del pacchetto e l'intervallo di retlw ENDPOINT movlw low RDI_start retlw 0x81 ; EP1, In addwf temp,w interrogazione useremo anche noi EP1/IN (*) retlw 0x03 ; Interrupt btfsc STATUS,C Evidenziati punti di ingresso ulteriori retlw 0x04 ; max packet size (4bytes) low byte incf PCLATH,f descrittori report retlw 0x00 ; max packet size (4bytes) high byte movwf PCL retlw 0x0A ; polling interval (10ms) RDI_start EndConfig1 retlw low ReportDescriptorLen retlw high ReportDescriptorLen ReportDescriptorLen ; this table calculates the offsets for Diventa retlw low (end_ReportDescriptor-ReportDescriptor) ; each report descriptor from the Tabella n.1 ; beginning of the table, effectively Descrittore ; more reports can be added here ReportDescriptor retlw 0x05 ; retlw low ReportDescriptorLen2 DEVICE retlw 0x01 ; usage page (generic desktop) ; retlw high ReportDescriptorLen2 retlw 0x09 ;************************************************** Diventa Tabella n.6 retlw 0x02 ; usage (mouse) ; This table is polled by the host immediately retlw 0xA1 ; after USB Reset has been released. This table Descrittore REPORT retlw 0x01 ; collection (application) Naturalmente i campi vengono ; defines the maximum packet size EP0 can take. retlw 0x09 ; See section 9.6.1 of the Rev 1.0 USB modificati in base alle nostre retlw 0x01 ; usage (pointer) ; specification. These fields are application esigenze, inoltre per chiarezza retlw 0xA1 ; DEPENDENT. Modify these to meet your retlw 0x00 ; collection (linked) ; specifications.The offset is passed in P0 and P1 si riportano le definizioni di retlw 0x05 ; (P0 is low order byte). ciascun byte che compone i retlw 0x09 ; usage page (buttons) ;************************************************** vari campi per far capire bene retlw 0x19 Descriptions Tutte le descrizioni dei campi vengono la sequenza di istruzioni. retlw 0x01 ; usage minimum (1) banksel EP0_start riscritte secondo standard USB evidenzian- retlw 0x29 movf EP0_start+1,w Buona parte dei nostri sforzi si do i campi con piu' byte in low e high byte retlw 0x03 ; usage maximum (3) movwf PCLATH focalizzeranno sulla ed evitando commenti di funzionamento che retlw 0x15 movf EP0_start,w definizione di un report retlw 0x00 ; logical minimum (0) movwf PCL verranno ben spiegati negli schemi sufficientemente "universale" retlw 0x25 DeviceDescriptor descrittivi creati per ciascuna tabella. retlw 0x01 ; logical maximum (1) per riutilizzarlo. Nel sorgente StartDevDescr retlw 0x95 retlw 0x12 ; bLengthLength of this descriptor originale si notano delle retlw 0x03 ; report count (3) retlw 0x01 ; bDescType This is a DEVICE descrizioni standard relative retlw 0x75 descriptor alla definizione di un mouse retlw 0x01 ; report size (1) retlw 0x10 ; bcdUSBUSB revision 1.10 (low byte) secondo specifiche HID. Noi retlw 0x81 retlw 0x01 ; high byte retlw 0x02 ; input (3 button bits) retlw 0x00 ; bDeviceClasszero means each useremo dei campi vendorretlw 0x95 ; interface operates independently defined perche' creeremo un retlw 0x01 ; report count (1) retlw 0x00 ; bDeviceSubClass dispositivo non contemplato retlw 0x75 retlw 0x00 ; bDeviceProtocol tra le specifiche HID. (*) retlw 0x05 ; report size (5) retlw 0x08 ; bMaxPacketSize0 retlw 0x81 ; inited inUsbInit() retlw 0x01 ; input (constant 5 bit padding) retlw 0xD8 ; idVendor Questa retlw 0x05 ; 0x04D8 is Microchip Vendor ID tabella viene retlw 0x04 ; high order byte retlw 0x01 ; usage page (generic desktop) retlw 0x09 retlw 0x00 ; idProduct divisa in 5 Nel sorgente originale qui viene messo uno 0 retlw 0x30 ; usage (X) sezioni per retlw 0x00 vanificando la possibilita' di estrarre il num retlw 0x09 retlw 0x41 ; bcdDevice rispecchiare retlw 0x04 0x31 ; usage (Y) seriale. Noi invece lo faremo puntare alla retlw 3° la sequenza retlw 0x01 ; iManufacturer stringa per poter usare la get relativa. retlw 0x15 retlw 0x81 ; logical minimum (-127) retlw 0x02 ; iProduct dei vari retlw 0x25 descrittori. retlw 0x00 ; iSerialNumber - 3 retlw 0x7F ; logical maximum (127) retlw NUM_CONFIGURATIONS ; bNumConfigurations retlw 0x75 ; ************************************************* retlw 0x08 ; report size (8) ; This table is retrieved by the host after the retlw 0x95 ; address has been set. This table defines the retlw 0x03 ; report count (2) ; configurations available for the device. See retlw 0x81 ; section 9.6.2 of the Rev 1.0 USB specification retlw 0x06 ; input (2 position bytes X & Y) ; (page 184). These fields are application retlw 0xC0 ; end collection ; DEPENDENT. Modify these to meet your retlw 0xC0 ; end collection ; specifications. end_ReportDescriptor ; ************************************************* StringDescriptions Config1 banksel EP0_start retlw 0x09 ; bLengthLength of this descriptor movf EP0_start+1,w retlw 0x02 ; bDescType2 = CONFIGURATION
88
novembre 2004 - Elettronica In
Corso PIC-USB
Diventa la ROUTINE DI INDIRIZZAMENTO n.1 per l'indice del descrittore CONFIGURATION
Corso PIC-USB
Diventa la ROUTINE DI INDIRIZZAMENTO n.2 per l'indice del descrittore STRING il codice sorgente fornito con il compilatore indica erroneamente l'indice del descrittore configuration.
movwf PCLATH movf EP0_start,w movwf PCL ; ************************************************ ; Given a configuration descriptor index, returns : the beginning address of the descriptor within ; the descriptions table ; ************************************************ string_index ; langid in W reg, ; string offset in EP0_start movwf temp bcf STATUS,C rlf temp, f pagesel langid_index call langid_index movwf temp2 incf temp, f Diventa la Tabella n.7 pagesel langid_index call langid_index Descrittori STRING movwf temp Dovendo gestire piu' linguaggi si movf temp, w fara' riferimento alla codifica movw fPCLATH Microsoft per i LANGID che si puo' movf temp2,w reperire su Internet presso l'MSDN. addwf EP0_start+1,w btfsc STATUS,C Sono evidenti i punti di ingresso incf PCLATH, f delle stringhe definite secondo una movwf PCL codifica del tipo String+nr.Stringa+l langid_index nr.linguaggio, in pratica la stringa 1 movlw high langids movwf PCLATH del linguaggio 2 e' String1_l2. Il movlw low langids sistema di denominazione ci è addwf temp, w apparso molto chiaro e funzionale btfsc STATUS,C quindi non e' stato modificato. (*) incf PCLATH,f movwf PCL langids retlw low lang_1 retlw high lang_1 retlw low lang_2 ; string indexes of ; different languages retlw high lang_2 lang_1 ; english retlw low String0; LangIDs retlw high String0 retlw low String1_l1 retlw high String1_l1 retlw low String2_l1 retlw high String2_l1 retlw low String3_l1 retlw high String3_l1 retlw low String4_l1 retlw high String4_l1 retlw low String5_l1 retlw high String5_l1 retlw low String6_l1 retlw high String6_l1 lang_2 retlw low String0; also point to LangID retlw high String0 retlw low String1_l2 retlw high String1_l2 retlw low String2_l2 retlw high String2_l2 Diventa il vettore dei LANGID retlw low String3_l2 Verranno previsti due linguaggi retlw high String3_l2 retlw low String4_l2 Italiano e Inglese anzichè Inglese e Cinese come nel sorgente originale. retlw high String4_l2 retlw low String5_l2 retlw high String5_l2 String0 retlw low (String1_l1-String0);length of string Nome Prodotto: retlw 0x03; descriptor type 3? useremo retlw 0x09; language ID (defined by MS 0x0409) retlw 0x04 "TermoUSB Corso PIC-USB" retlw 0x04; some other language ID for testing retlw 0x08 String0_end String1_l1 retlw String2_l1-String1_l1; length of string retlw 0x03; string descriptor type 3 retlw 'M' Nome Costruttore (indice retlw 0x00 iManufacturer=1) ... Microchip Le stringhe vengono ... modificate per le nostre retlw 'p' esigenze, in particolare retlw 0x00 quelle relative al nome String2_l1 retlw String3_l1-String2_l1 prodotto, numero seriale ecc. retlw 0x03 Si noti che le stringhe retlw 'P' seguiranno la sequenza che retlw 0x00 avremo indicato nei vari retlw 'i' retlw 0x00 campi index dei descrittori ... usati. Ad esempio per il PIC16C745/765 USB Mouse numero seriale indichiamo ... l'index 3 (campo retlw 'e' retlw 0x00 iSerialNumber) e quindi la String3_l1 descrizione si trovera' nella retlw String4_l1-String3_l1 stringa 3. retlw 0x03
Elettronica In - novembre 2004
Numero Seriale useremo ESP.1 che sta retlw 'V' per esperimento numero 1 retlw 0x00 retlw '1' retlw 0x00 retlw '.' retlw 0x00 retlw '1' retlw 0x00 retlw '1' retlw 0x00 String4_l1 retlw String5_l1-String4_l1 retlw 0x03 retlw 'C' Descrizione Configurazione, ce ne retlw 0x00 sara' soltanto una in questo primo retlw 'f' esempio. Useremo CFG1. retlw 0x00 retlw 'g' retlw 0x00 retlw '1' retlw 0x00 String5_l1 Descrizione Endpoint retlw String6_l1-String5_l1 useremo EP1/IN retlw 0x03 retlw 'E' retlw 0x00 retlw 'P' retlw 0x00 Stringhe Linguaggio 2. Si noti che nel retlw '1' sorgente originale e' stato usato il retlw 0x00 Cinese, anche se si tratta solo di un retlw '0' retlw 0x00 esempio perche' in realta' hanno retlw 'I' mantenuto i valori della lingua inglese. retlw 0x00 retlw 'n' retlw 0x00 String6_l1 String1_l2
; lang 2, chinese. ; String can be totally different ; than english String2_l2-String1_l2; length of string 0x03; string descriptor type 3 'M' 0x00
retlw retlw retlw retlw ... Microchip ... retlw 'p' retlw 0x00 String2_l2 retlw String3_l2-String2_l2 retlw 0x03 retlw 'P' Nome prodotto secondo retlw 0x00 linguaggio. retlw 'i' retlw 0x00 ... PIC16C745/765 USB Mouse ... retlw 'e' retlw 0x00 String3_l2 retlw String4_l2-String3_l2 retlw 0x03 Numero seriale secondo retlw 'V' retlw 0x00 linguaggio. retlw '1' retlw 0x00 retlw '.' retlw 0x00 retlw '1' retlw 0x00 retlw '1' retlw 0x00 String4_l2 retlw String5_l2-String4_l2 retlw 0x03 retlw 'C' Descrizione configurazione retlw 0x00 retlw 'f' secondo linguaggio. retlw 0x00 retlw 'g' retlw 0x00 retlw '1' retlw 0x00 String5_l2 retlw String6_l2-String5_l2 retlw 0x03 retlw 'E' retlw 0x00 retlw 'P' retlw 0x00 Descrizione endopoint retlw '1' secondo linguaggio. retlw 0x00 retlw '0' retlw 0x00 retlw 'I' retlw 0x00 (*) Queste tabelle verranno analizzate retlw 'n' nel corso della prossima puntata. retlw 0x00 String6_l2
89
Vediamo di analizzare tale struttura nel dettaglio: 1) Routine che dato un indice di descrittore Configuration ritorna l’indirizzo iniziale del descrittore all’interno della tabella dei descrittori 2) Routine che dato un indice di descrittore Report ritorna l’indirizzo iniziale del descrittore all’interno della tabella dei descrittori 3) Routine che dato un indice di descrittore String ritorna l’indirizzo iniziale del descrittore all’interno della tabella dei descrittori 4) Tabella 1 descrittore DEVICE 5) Tabella 2 descrittore CONFIGURATION 6)Tabella 3 descrittore INTERFACE 7)Tabella 4 descrittore HID
Tabella 1 - Descrittore DEVICE
LISTATO 1 retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw
90
0x12 0x01 0x10 0x01 0x00 0x00 0x00 0x08 0xd8 0x04
; ; ; ; ; ; ; ; ; ;
bLength bDescriptorType bcdUSB (low-b) bcdUSB (high-b) bDeviceClass bDeviceSubClass bDeviceProtocol bMaxPacketSize0 idVendor (low-b) idVendor (high-b)
retlw retlw retlw retlw retlw retlw retlw retlw
0x00 ; idProduct (low-b) 0x00 ; idProduct (high-b) 0x00 ; bcdDevice (low-b) 0x01 ; bcdDevice (high-b) 0x01 ; iManufacturer 0x02 ; iProduct 0x03 ; iSerialNumber NUM_CONFIGURATIONS ; bNumConfigurations
novembre 2004 - Elettronica In
Corso PIC-USB
re Microchip MOUSDESC.ASM: versione PICBasic compatibile del file DESCRIPT.ASM contenuto nel firmware Microchip e inserita nell’include del file USBDESC.ASM Quindi per creare i nostri descrittori dovremo fare un nuovo file che sostituirà MOUSDESC.ASM e inserirlo nell’include del file USBDESC.ASM. Chiameremo il file contenitore di descrittori: TERMODSC.ASM. Esso è costituito da 10 sezioni: 3 relative a procedure d’indirizzamento e 7 relative alla definizione delle tabelle contenenti i campi dei descrittori.
Corso PIC-USB
8)Tabella 5 descrittore ENDPOINT 9)Tabella 6 descrittore REPORT 10) Tabella 7 vettore LANGID e descrittori STRING Questa suddivisione è leggermente modificata rispetto al firmware ufficiale per permettere a tutti voi di capire bene come si susseguono le definizioni dei descrittori secondo quanto stabilito dalle specifiche USB. Le strutture e le descrizioni dei campi sono più chiare e rispecchiano proprio le definizioni di tali specifiche, in questo modo abbiamo creato una struttura conforme e allo stesso tempo riutilizzabile visto che riusciremo a identificare le sezioni da modificare per
che rendono il listato più chiaro e leggibile. In particolare sono ben evidenziati i punti in cui si devono inserire i vari campi della struttura di descrittori rendendo le cose senz’altro più semplici per chi si avvicina per la prima volta alla programmazione firmware USB. Procedendo nella lettura, consigliamo di stampare il listato Termodsc.asm che troverete sul sito della rivista. In questo modo potrete apprezzare il significato d’ogni singolo campo che andremo a modificare. Per ciascun descrittore abbiamo creato uno schema di riferimento attraverso il quale potrete capire il significato dei vari campi. Quindi scorrendo la sequenza d’istruzioni e leggendo la riga con il
Tabella 2 - Descrittore CONFIGURATION
ciascun dispositivo immediatamente. Nello specifico vediamo di analizzare il listato Mousdesc.asm fornito assieme al compilatore per vedere meglio le modifiche che sono state effettuate. Dopodiché per tutti i nostri esperimenti ci riferiremo al sorgente modificato. Come si vede ci sono delle modifiche formali
nome campo corrispondente sullo schema avrete una visione completa dello sviluppo. Per ovvii problemi di spazio la descrizione di tutta la struttura e poi del codice basic è stata divisa in due puntate. Tuttavia gli schemi che vedrete saranno fondamentali per capire gli esperimenti futuri dove non si riprenderà la spiegazione di ciascun >
LISTATO 2 retlw retlw retlw retlw retlw retlw retlw retlw retlw
0x09 0x02 EndConfig1 - Config1 0x00 0x01 0x01 0x04 0xA0 0x32
Elettronica In - novembre 2004
; bLengthLength ; bDescriptorType ; wTotalLength (low-b) ; wTotalLength (high-b) ; bNumInterfaces ; bConfigurationValue ; iConfiguration ; bmAttributes ; MaxPower
91
dove il valore è composto da due byte inseriamo nella colonna di sinistra il low-order-byte e in quella di destra l’high-order-byte per avere subito i valori da inserire nel file TERMODSC.asm. Laddove mettiamo tra parentesi “assegnato dall’USB” bisogna riferirsi ad un valore stabilito in un’apposita tabella delle specifiche USB o delle definizioni di classe HID (presentiamo i valori più importanti ma in caso di configurazioni particolari è bene avere a mano i documenti di riferimento). Tutte le tabelle sono riferite alle strutture dati descritte nelle specifiche USB 1.1. Inoltre, la relativa sequenza d’istruzioni inserita dopo ciascuna griglia deve essere allineata alla label d’ingresso che è indicata tra parentesi all’inizio della
Tabella 3 - Descrittore INTERFACE
scuna riga. Andiamo, quindi, ad effettuare l’editing delle tabelle creando il nostro primo insieme di descrittori. Una precisazione per l’utilizzo delle griglie: lad-
sequenza. Si capisce facilmente tenendo d’occhio il listato mentre si leggono gli schemi. Nel nostro file TERMODSC.ASM inseriremo nella sezione TABELLA 1 - DESCRITTORE
LISTATO 3 retlw retlw retlw retlw retlw retlw retlw retlw retlw
92
0x09 INTERFACE 0x00 0x00 0x01 0x03 0x01 0x02 0x05
; ; ; ; ; ; ; ; ;
bLength bDescriptorType bInterfaceNumber bAlternateSetting bNumEndpoints bInterfaceClass bInterfaceSubClass bInterface Protocol iInterface
novembre 2004 - Elettronica In
Corso PIC-USB
campo ma si dettaglieranno gli aspetti funzionali del firmware che costruiremo dando per assodata la conoscenza del significato dei campi. Ci permettiamo di suggerirvi un metodo di lavoro: trasferite gli schemi su dei file Excel mantenendo solo la descrizione dei relativi campi, avrete così a disposizione una sorta di check-list da valorizzare di volta in volta. Leggendo poi in sequenza la colonna relativa ai valori riuscirete a codificare il descrittore senza paura di aver dimenticato qualche campo o aver usato qualche valore errato. Naturalmente dopo aver scritto una decina di descrittori gli schemi non serviranno più e potrete lavorare direttamente sugli asm senza particolari problemi prendendo a riferimento i nomi di campo standard inseriti su cia-
Corso PIC-USB
Il software
Riportiamo le schermate dei software che potrete scaricare dal sito della rivista. Utilizzando il programma Leggi-HID avrete la possibiltà di interrogare il dispositivo e verificare il funzionamento di alcuni campi dei descrittori
(in particolare l'identificazione device e le stringhe inserite). La schermata al termine dell'interrogazione apparirà come mostrato in questo box. Questo programma interroga qualsiasi dispositivo collegato alla porta USB
DEVICE inseriremo le istruzioni (presenti nel Listato 1) in corrispondenza della label di inizio del descrittore (StartDevDescr). Passiamo ora a definire e valorizzare la tabella successiva (Tabella 2). Nel nostro file TERMODSC.ASM inseriremo nella sezione TABELLA 2 - DESCRITTORE CONFIGURATION inseriremo le istruzioni (presenti nel Listato 2) in corrispondenza della label di inizio del descrittore (Config1). Andiamo alla tabella 3. Nel nostro file TERMODSC.ASM inseriremo nella sezione TABELLA 3 - DESCRITTORE INTERFACE le istruzioni (presenti nel Listato 3) Elettronica In - novembre 2004
restituendo il nome del venditore, del prodotto e i relativi ID, pertanto potrete verificare i dati, per esempio, del vostro mouse o della vostra tastiera. Il software del TermoUSB verrà analizzato nel dettaglio nella prossima puntata, ma riportiamo come anticipazione una breve descrizione. Il programma, scritto in Delphi, non fa altro che leggere il dato campionato dal PIC e rappresentarlo su un LCD virtuale. Il programma ricerca il dispositivo e poi legge via via i dati che gli vengono messi a disposizione dal microcontrollore che dovrà essere ovviamente programmato col listato compilato TERMOUSB.BAS. Non appena collegate la demoboard il software si accorge della connessione valorizzando la lista dispositivi e i dati del device. Facendo click su "Avvia Monitoraggio" l'host rileverà i dati visualizzando la temperatura sul LCD. Si può bloccare il campionamento facendo click nuovamente su "Avvia Monitoraggio". Si noti che i dati ricevuti vengono visualizzati in tempo reale sul pannello "Ultimi 4 valori ricevuti".
in corrispondenza della label di inizio del descrittore (Interface1). Nella prossima puntata termineremo il nostro primo descrittore ed analizzeremo l’eseguibile basic necessario a far funzionare il tutto. Nel frattempo consigliamo di dare un’occhiata al listato sia del descrittore che del .bas per aver un’idea più precisa del nostro obiettivo. Anticipiamo fin da ora che useremo un report un po’ singolare perché la classe HID non prevede alcuna definizione specifica di un termometro, pertanto inseriremo dei campi cosiddetti “Vendor Defined”... Riprenderemo il discorso nella prossima puntata. 93
Corso PIC-USB
Corso di programmazione per PIC: l’interfaccia USB B Alla scoperta della funzionalità USB implementata nei microcontrollori della Microchip. Un argomento di grande attualità in considerazione della crescente importanza di questa architettura nella comunicazione tra computer e dispositivi esterni. In questa terza puntata completiamo la struttura per un dispositivo HID che permette il monitoraggio della temperatura ambientale attraverso un resistore NTC.
3
a cura di Carlo Tauraso
ella puntata precedente ci eravamo fermati all'editing della tabella relativa al descrittore INTERFACE. Stiamo, infatti, definendo la struttura per un dispositivo HID che avrà il compito di digitalizzare la caduta di tensione su una resistenza NTC che renderà disponibile il valore risultante sull'USB. Passiamo quindi a definire i
descrittori relativi agli endpoint che abbiamo deciso di utilizzare. Ricordiamo che stiamo sviluppando un dispositivo HID quindi ai descrittori standard dobbiamo aggiungere quelli specifici relativi a tali dispositivi. Andiamo dunque a modificare la tabella relativa al descrittore HID (vedi tabella 4). Nel >
Tabella 4 - Descrittore HID
Elettronica In - dicembre 2004 / gennaio 2005
99
retlw retlw retlw retlw retlw retlw retlw retlw retlw
0x09 ; bLength 0x21 ; bDescriptorType 0x00 ; bcdHID (low-b) 0x01 ; bcdHID (high-b) 0x00 ; bCountryCode 0x01 ; bNumDescriptors 0x22 ; bDescriptorType (end_ReportDescriptor - ReportDescriptor) ;wDescriptorLength (low-b) 0x00 ; wDescriptorLength (high-b)
nostro file TERMODSC.ASM inseriremo nella sezione TABELLA 4 - DESCRITTORE HID le istruzioni (presenti all’interno del Listato 4) in corrispondenza della label d’inizio del descrittore (HID_Descriptor). Ricordiamo che l’endpoint0 risulta predefinito e che abbiamo deciso di utilizzare una comunicazione unidirezionale verso l’host quindi EP1 IN. Si faccia molta attenzione a questo descrittore perchè qui si definisce realmente il modo in cui comunicheranno host e device. Nel nostro file TERMODSC.ASM inse-
riremo nella sezione TABELLA 5 - DESCRITTORE ENDPOINT le istruzioni riportate nel listato 5 in corrispondenza della label di inizio del descrittore (Endpoint1). Siamo arrivati ad un punto cruciale: la definizione del descrittore Report. Qui le cose si fanno leggermente più complesse perchè non è possibile definire una griglia di massima. Ogni dispositivo avrà una definizione a sé fortemente dipendente dal tipo di elaborazione che si vuol fare e dai dati che si desidera scambiare. Se ricordiamo però lo sche-
Tabella 5 - Descrittore ENDPOINT
LISTATO 5 retlw
100
0x07 retlw retlw retlw retlw retlw retlw
ENDPOINT 0x81 0x03 0x01 0x00 0xFA
; ; ; ; ; ; ;
bLength bDescriptorType bEndpointAddress bmAttributes wMaxPacketSize (low-b) wMaxPacketSize (high-b) bInterval
dicembre 2004 / gennaio 2005 - Elettronica In
Corso PIC-USB
LISTATO 4
Corso PIC-USB
Tabella 6 - Descrittore REPORT
ma relativo agli item obbligatori abbiamo già un’idea da cui partire. Sono da considerare necessari Input (Output o Feature), Usage, Usage Page, Logical Minimum, Logical Maximum, Report Size, Report Count. Inoltre si consideri che ogni item ha un byte di prefisso del tipo bSize, bType, bTag (rivedere lo schema delle
pagine precedenti). Presentiamo tale byte su due righe, una contenente il valore binario (con tra parentesi quello decimale) che si determina attraverso lo schema di riferimento, ed una contenente il valore esadecimale da inserire nel file del descrittore. Quest’ultimo è sempre presentato con il low-order-byte a sinistra ed il high-order- >
Elettronica In - dicembre 2004 / gennaio 2005
101
retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw
0x06 0x01 0xFF 0x09 0x01 0xA1 0x01 0x09 0x02 0xA1 0x00 0x06 0x02 0xFF 0x09 0x03 0x15 0x00 0x26 0xFF 0x00 0x75 0x08 0x95 0x01 0x81 0x02 0xC0 0xC0
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
Byte di prefisso (bTag,bType,bSize) Usage Page (low-b) ("Vendor Defined Page 1") Usage Page (high-b) ("Vendor Defined Page 1") Byte di prefisso (bTag,bType,bSize) Usage ("Vendor Defined Usage 1") Byte di prefisso (bTag,bType,bSize) Collection ("Application") Byte di prefisso (bTag,bType,bSize) Usage ("Vendor Defined Usage 2") Byte di prefisso (bTag,bType,bSize) Collection ("Physical") Byte di prefisso (bTag,bType,bSize) Usage Page (low-b) ("Vendor Defined Page 2") Usage Page (high-b) ("Vendor Defined Page 2") Byte di prefisso (bTag,bType,bSize) Usage ("Vendor Defined Usage 3") Byte di prefisso (bTag,bType,bSize) Logical Minimum (0) Byte di prefisso (bTag,bType,bSize) Logical Maximum (low-b) (255) Logical Maximum (high-b) Byte di prefisso (bTag,bType,bSize) Report Size (8 bits) Byte di prefisso (bTag,bType,bSize) Report Count (1 campo dati) Byte di prefisso (bTag,bType,bSize) Input (Data, Var, Abs) End Collection ("Physical") End Collection ("Application")
byte a destra per aver direttamente il valore inserito nel codice assembler. Le tabelle relative ai codici da assegnare per definire le Collection, Usage Page, Usage, Report Item si trovano nella documentazione HID (HID Usage Tables e HID Class Definitions 1.1). Introduciamo anche due collection anche se in realtà non sono obbligatorie ma ci permettono di definire un report che riutilizzeremo in altri esperimenti. Ora, infatti, utilizziamo un unico campo dati in uscita ma nel momento in cui dovremo scambiare dati sia in ingresso che in uscita le collection ci ritorneranno molto utili. Possiamo quindi realizzare una struttura del genere (vedi tabella 6). Abbiamo utilizzato i campi essenziali per definire il descrittore report che ci serve. Vedremo, negli esempi più complessi, che dovendo scambiare più campi dati in maniera bidirezionale, oppure far svolgere più funzioni diverse al nostro dispositivo, sarà necessario ampliare tale struttura inglobando più item Input Output. Si noti, inoltre
che stiamo definendo un dispositivo sufficientemente generico, cioè non legato ad una definizione di classe specifica, per poter in futuro utilizzare gli stessi campi per dispositivi diversi. Se avessimo definito un mouse o una tastiera avremmo dovuto seguire le definizioni HID appropriate precisando ad esempio gli usage x e y per le coordinate del puntatore. Qui, invece, vogliamo creare un dispositivo completamente definito da noi ma che si comporta come un HID e quindi facilmente riconoscibile dal sistema operativo. Naturalmente la definizione del descrittore report è fondamentalmente una questione di pratica, la Microchip fornisce dei technical brief di esempio anche se sono limitati a device specifici, nelle Usage Tables si trovano diversi casi anche interessanti. Sarà bene comunque tenere a mente questo primo esempio perchè rappresenta un caso semplice che si può duplicare ed integrare per creare strutture più complesse. Nel nostro file TERMODSC.ASM inseriremo nella sezione
Tabella 7 - Descrittore STRING
102
dicembre 2004 / gennaio 2005 - Elettronica In
Corso PIC-USB
LISTATO 6
Corso PIC-USB
Tabella A Identificatore della lingua 0x0000 0x0400 0x0800 0x0436 0x041c 0x0401 0x042b 0x042c 0x042d 0x0423 0x141a 0x0402 0x0403 0x0404 0x0804 0x041a 0x0405 0x0406 0x0413 0x0813 0x0409 0x0809 0x0425 0x040c 0x0437 0x0407 0x0408
Lingua
Identificatore della lingua
Linguaggio Neutrale Linguaggio di processo o User default Linguaggio System Default Africano Albanese Arabo (Arabia Saudita) Windows 2000/XP: Armeno Azero (Latino) Basco Bielorusso Bosniaco (Bosnia e Herzegovina) Bulgaro Catalano Cinese (Taiwan) Cinese(PRC) Croato Ceco Danese Olandese (Paesi Bassi) Olandese (Belgio) Inglese (USA) Inglese (Regno Unito) Estone Francese (Standard) Windows 2000/XP: Georgiano Tedesco (Standard) Greco
TABELLA 6 - DESCRITTORE REPORT le istruzioni riportate nel listato 6 in corrispondenza della label di inizio del descrittore (ReportDescriptor). Ora dobbiamo definire i descrittori String per descrivere tutti i campi che hanno un indice che rimanda a questa tabella (ad esempio iManufacturer, iProduct, iSerialNumber, iConfiguration). Possiamo definire stringhe in più lingue definendo un vettore di identificatori del linguaggio (LANGID). La lista dei valori ammessi è riportata nella tabella A. Nel nostro esempio utilizziamo due lingue: italiano e inglese. Attenzione che per Microsoft ogni valore è composto in realtà da un identificatore di linguaggio primario ed uno secondario (vedi tabella 7); il codice relativo è riportato nel listato 7. Per ovvii problemi di spazio evitiamo di ripetere la griglia per tutte le 12 stringhe definite (6 in italiano e 6 in inglese) presentando solo la tabella relativa alla descrizione del prodotto
0x040d 0x0439 0x040e 0x040f 0x0421 0x0410 0x0810 0x0411 0x0412 0x0427 0x0414 0x0415 0x0416 0x0816 0x0418 0x0419 0x0c1a 0x081a 0x0424 0x0c0a 0x041d 0x041f 0x0422 0x0443 0x0843 0x042a 0x0452
Lingua Ebreo Windows 2000/XP: Hindi. Ungherese Islandese Indonesiano Italiano (Standard) Italiano (Svizzera) Giapponese Coreano Lituano Norvegese Polacco Portughese (Brasile) Portoghese (Portogallo) Rumeno Russo Serbo (Cirillico) Serbo (Latino) Sloveno Spagnolo Svedese Turco Ucraino Uzbeco (Latino) Uzbeco (Cirillico) Vietnamita Gallese (Regno Unito)
(iProduct) in italiano. Il codice allegato riportato nel listato 8 è sufficientemente chiaro per utilizzare questa sezione (vedi anche tabella 8). Terminato l’editing delle tabelle dobbiamo far in modo che il compilatore inserisca il nostro lavoro nel codice eseguibile. Per farlo e sufficiente inserire l’include “termodsc.asm” nel file USBDESC.ASM. A questo punto non ci resta che realizzare il file in PICBasic che costituirà l’applicazione principale del dispositivo. Diciamo che il più è fatto in quanto il codice è piuttosto banale visto che deve soltanto acquisire il byte dal modulo A/D ed inviarlo sul bus. Quello che è interessante è la struttura del file che utilizzeremo anche in altri casi. In particolare si osservi la parte relativa al salvataggio di tutte le variabili che potrebbero essere modificate da un operazione interrupt USB. Poche istruzioni per evitare un bel pasticcio. Vengono richiamate due funzioni di base del firmware: USBInit per avviare il processo di enumerazione e portare il dispositivo >
LISTATO 7 String0 retlw retlw retlw retlw retlw retlw String0_end
low (String1_l1 - String0) ; bLength 0x03 ; bDescriptorType 0x10 ; LANGID0 (low-b)(Per Microsoft 0x0410=Italiano) 0x04 ; LANGID0 (high-b) 0x09 ; LANGID1 (low-b)(Per Microsoft 0x0409=Inglese) 0x04 ; LANGID1 (high-b)
Elettronica In - dicembre 2004 / gennaio 2005
103
LISTATO 8 String2_l1 retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw
String3_l1-String2_l1 0x03 'T' 0x00 'e' 0x00 'r' 0x00 'm' 0x00 'o' 0x00 'U' 0x00 'S' 0x00 'B' 0x00 ' ' 0x00 'C' 0x00 'o' 0x00 'r' 0x00 's' 0x00 'o' 0x00 ' ' 0x00 'P' 0x00 'I' 0x00 'C' 0x00 '-' 0x00 'U' 0x00 'S' 0x00 'B' 0x00
; ; ; ;
iProduct ("TermoUSB Corso PIC-USB") bLength bDescriptorType bString
allo stato configurato e ServiceUSBInt che gestisce gli interrupt generati dai moduli USB. Analizzeremo nel dettaglio tali funzioni quando descriveremo il funzionamento del firmware Microchip, per il momento si considerino come delle black-box. In effetti, la conoscenza del loro funzionamento può servire se si vogliono realizzare delle implementazioni particolari sostituendo il firmware Microchip, pertanto affronteremo l’argomento quando saremo sufficientemente preparati per farlo. Per quanto riguarda la conversione analogico digitale il PIC16C745 utiliz104
za tre registri: ADRES, ADCON0, ADCON1. ADCON0 permette di controllare l’operazione di conversione analogico-digitale (canali usati, clock, stato), ADCON1 permette di stabilire la funzione dei diversi pin (nel nostro caso siamo limitati solo a RA0 e RA1 a causa del modo con cui è costruita la demoboard), infine ADRES che conterrà il valore convertito. La cosa interessante è che grazie alla demoboard è possibile simulare la presenza di una tensione di ingresso attraverso il ponticello SK2 (per RA0) quindi si può realizzare l’esperimento senza dotarsi della
dicembre 2004 / gennaio 2005 - Elettronica In
Corso PIC-USB
Tabella 8
Corso PIC-USB
NTC, naturalmente i dati di temperatura riferiti dall’applicazione host saranno fittizi. Passiamo quindi al codice riportato nel listato 9. Analizziamo alcuni punti importanti. Innanzitutto per l’utilizzo dell’istruzione Basic ADCIN che ci permette di effettuare la conver-
sione analogico/digitale è necessario effettuare alcune definizioni. Precisare la lunghezza in bits del risultato (ADC_BITS) e determinare la sorgente per il segnale di clock che controlla la conversione (ADC_CLOCK). In questo caso selezioniamo l’oscillatore RC dedicato interno; si >
LISTATO9 ' Programma TERMOMETRO USB ' Esperimento n.1 Corso PIC-USB Elettronica-In '******Dichiarazioni variabili necessarie per uso firmware USB****** wsave ssave psave fsave
VAR VAR VAR VAR
BYTE BYTE BYTE BYTE
$70 system bank0 system bank0 system bank0 system
' ' ' '
permette permette permette permette
di di di di
salvare salvare salvare salvare
W STATUS PCLATH FSR
'******Dichiarazioni variabili applicazione temper VAR BYTE 'variabile che conterra il valore proveniente dal modulo A/D DEFINE DEFINE
OSC 24 SHOW_ENUM_STATUS
1
' Clock 24Mhz ' Visualizza lo stato relativo al processo di enumerazione ' su PORTB
' Definizioni per l'utilizzo dell'istruzione ADCIN DEFINE ADC_BITS 8 ' Numero di bit nel risultato DEFINE ADC_CLOCK 3 ' Clock RC TAD=4uS/bit 9,5TAD/byte DEFINE ADC_SAMPLEUS 50 ' Frequenza Campionamento uS PORTB = 0 TRISB = 0
' 8 LED uscite digitali spenti ' PORTB definita in uscita
GoTo INIZIO
' Salta al main
' Il gestore Interrupt inizia dalla label BUSINT DEFINE INTHAND BUSINT Asm BUSINT
RIPREG
EndAsm INIZIO:
CONV:
movf movwf movlw movwf btfsc Call
clrf movf movwf movf movwf swapf movwf swapf swapf retfie
FSR, W fsave High ServiceUSBInt PCLATH PIR1, USBIF ServiceUSBInt
STATUS fsave, FSR psave, PCLATH ssave, STATUS wsave, wsave,
; salvataggio di FSR
; Se non c'è' nessun interrupt da gestire vado a RIPREG ; Richiama la routine firmware che gestisce tutti gli ; Interrupt dei moduli USB del PIC ; Ripristino registri salvati
W W W F W
USBInit TRISA = %11111111 ADCON1= 4 Pause 500 Pause 100 ADCIN 0, temper Pause 150 USBOut 1, temper, 1, CONV GoTo CONV
; Torno al pgm principale
' ' ' ' '
Processo di enumerazione alla fine il device entra nello stato Configurato PORTA tutta in ingresso [DDDDADAA] RA0,RA1 analogici Vdd tensione di rif Attesa
' Campiona segnale su RA0 e metti risultato in temp ' Invia risultato sul bus e torna a campionare ' Continua all'infinito
Elettronica In - dicembre 2004 / gennaio 2005
105
Tabella9
Fig. 1
Attraverso la definizione di SHOW_ENUM_STATUS avremo la possibilità di vedere la progressione degli stati del processo di enumerazione sulla PORTB quindi potremo controllare il funzionamento del dispositivo attraverso i led (Ld1..Ld8) della demoboard secondo la tabella 9. Se colleghiamo la demo board dopo aver sostituito il PIC originale con quello contenente il file termousb.hex vedremo che si avvierà la procedura di riconoscimento Plug and Play di Windows che rileverà il nostro dispositivo come “ThermoUSB PIC-USB Course” e ci chiederà di inserire il percorso dei driver relativi. Si noti che l’inglese, per il PnP Microsoft, è il linguaggio predefinito pertanto 106
vengono visualizzate le stringhe del linguaggio 2 del nostro firmware. Procedendo senza scegliere alcuna opzione il sistema si accorgerà che il dispositivo è un HID e quindi presenterà la schermata di figura 1 (questa schermata non appare in windows XP). Windows, quindi caricherà il driver per periferiche HID. Noteremo che durante questa procedura si saranno accesi i led 1,2,3 che stanno a significare che il dispositivo è entrato nello stato “Indirizzato” ma non è ancora pronto per essere utilizzato. Terminata la procedura di installazione ci apparirà la schermata di figura 2 (questa schermata non appare in Windows XP).
Fig. 2
Facendo click su Fine sulla demoboard si accenderà anche il led 4 e il dispositivo comincerà ad inviare dati. Il corretto funzionamento verrà segnalato dall’accensione e dallo spegnimento ad intervalli regolari del led 7 che segnala attività sull’endpoint 1 (EP1/IN). In pratica ad ogni accensione il dispositivo rende disponibile il valore campionato sul bus eseguendo l’istruzione USBOUT. Se apriamo il pannello di controllo di Windows vedremo tra le periferiche il nostro dispositivo (vedi fig. 3). Utilizzando la routine Leggi-HID che si può scaricare dal sito della rivista avrete la possibilità di interrogare il dispositivo e verificare il funzionamento di alcuni campi dei descrittori (in partico-
dicembre 2004 / gennaio 2005 - Elettronica In
Corso PIC-USB
ricordi che il tempo di conversione per ciascun bit (TAD) deve essere di almeno 1,6 µS e che utilizzando una frequenza di sistema di 24 MHz l’RC interno ha un TAD di circa 4 µS. Inoltre è bene utilizzare INT/32 o RC come fonte per evitare la perdita di precisione dovuta alle esigenze dei moduli USB. Infine si precisa la frequenza di campionamento (ADC_SAMPLEUS). L’istruzione che opera la conversione è molto semplice (ADCIN 0,temper), in pratica usa come parametri il canale dal quale si ricava il segnale analogico (0=RA0) e il nome della variabile dove si vuol registrare il risultato (temper).
Corso PIC-USB
lare l’identificazione device e le stringhe inserite). La schermata al termine dell’interrogazione apparirà come in figura 4. Dopo aver installato il nostro prototipo e verificato il suo funzionamento vediamo un attimo di spiegare un paio di istruzioni chiave usate nel programma Basic. Nel listato ci sono due istruzioni fondamentali per il funzionamento su USB: USBINIT e USBOUT. Partiamo da quest’ultima. Essa permette al nostro dispositivo di rendere disponibile attraverso l’endpoint1 il valore campionato. La sintassi PicBasic è: - USBOUT Endpoint, Buffer, Count, Label; dove Endpoint è il numero dell’endpoint utilizzato (ad es. 2 = EP2); Buffer è la variabile array contenente i valori che intendiamo rendere disponibili sul bus; Count è il numero di byte da inviare; Label è l’etichetta alla quale prosegue l’esecuzione del codice se il dispositivo non riesce ad inviare i dati in quanto non risulta ancora terminata una trasmissione precedente, tipicamente si fa ciclare l’istruzione su se stessa per trasmetterla non appena possibile. Nel nostro caso usiamo un buffer array costituito
Stato “Configurato”
Comunicazione su EP1
TermoUSB con NTC montata
da un unico byte (un array povero ma efficace). L’istruzione nella realtà ha dei collegamenti molto stretti con il firmware Microchip perchè
Fig. 3
non fa nient’altro che richiamare la funzione PutEPn che abbiamo visto nel nostro modello a strati. Se analizziamo più da vicino questa funzione vedremo che utilizza gli stessi parametri della USBOUT quindi quest’ultima è solo un’interfaccia che ci permette un lavoro essenzialmente più comodo. In particolare PutEpn usa il registro W del PIC per il numero di byte da inviare, FSR e IRP per puntare al blocco di dati da inviare (il nostro buffer). Essa, inoltre, valorizza il Carry Flag per comunicare se la funzione è stata eseguita con successo (CF=1) oppure no (CF=0). Per quanto riguarda USBINIT la sua funzione è quella di avviare il processo di enumerazione e portare il dispositivo allo stato “Configurato”. In ogni sviluppo firmware USB è la prima funzione che si dovrà chiamare, pena l’impossibilità di comunicare con il device. Anche qui la situazione non è tanto differente visto che l’istruzione PicBasic è essenzialmente un’interfaccia verso l’istruzione firmware InitUSB. In realtà è una vera e propria call visto che non c'è alcun parametro da passare. Prendiamo in considerazione ora il lato host. la breve routine scritta in Delphi non fa altro che leggere il dato campionato dal PIC e rappresentarlo su una sorta di LCD. Il programma ricerca >
Elettronica In - dicembre 2004 / gennaio 2005
107
il dispositivo e poi legge via via i dati che gli vengono messi a disposizione (vedi fig. 5). Utilizzando una NTC con R25=33 kOhm la taratura si può effettuare semplicemente ruotando completamente il trimmer ATT1 affinché esso non introduca alcuna attenuazione. Inserendo il ponticello SK2 ed avviando il monitoraggio attraverso l’apposito tasto dell’applicazione il valore letto è 255. Quindi, si può togliere il ponticello ed inserire la NTC. Nel caso si disponga di una NTC con caratteristiche differenti si può procedere valorizzando i campi del pannello “Parametri NTC”. In pratica bisogna inserire il valore della temperatura magari rilevato attraverso un termometro classico e quello del valore campionato che si può ricavare dalla lista degli ultimi 4 valori ricevuti. Chiaramente bisogna effettuare due rilievi a temperatura differente affinché il software possa calcolare lo scarto per ciascun livello logico. Eventualmente si può agire sul trimmer della demoboard per adattare i valori campionati. Il sistema è sufficiente dal punto di vista speri-
108
Fig. 5
vuti vengono visualizzati in tempo reale sul pannello “Ultimi 4 valori ricevuti”. Analizzeremo il listato in maniera approfondita nella parte del Corso dedicata allo sviluppo lato Host. Bene, anche per questa puntata abbiamo fatto qualche passo avanti. Nella prossima ritorneremo sul nostro termometro USB analizzando ancora la sua struttura assembler, ed iniziando a capire come è possibile scambiare dati nella direzione opposta (da Host a Device). Sfrutteremo la struttura delle collection ed introdurremo un altro prototipo con l’esperimento n.2. Alla prossima.
dicembre 2004 / gennaio 2005 - Elettronica In
Corso PIC-USB
Fig. 4
mentale, anche se non brilla per precisione e sensibilità. Se non avete una NTC a disposizione non c'è’ problema, perchè potete testare il tutto con ponticello SK2 inserito e ruotando il trimmer ATT1, simulando le variazioni di temperatura. Non appena collegate la demoboard il software si accorge della connessione valorizzando la lista dispositivi e i dati del device. Facendo click su “Avvia Monitoraggio” l’host rileverà i dati visualizzando la temperatura sul LCD. Si può bloccare il campionamento facendo click nuovamente su “Avvia Monitoraggio”. Si noti che i dati rice-
Corso PIC-USB
Corso di programmazione per PIC: l’interfaccia USB B Alla scoperta della funzionalità USB implementata nei microcontrollori della Microchip. Un argomento di grande attualità in considerazione della crescente importanza di questa architettura nella comunicazione tra computer e dispositivi esterni. In questo quarto appuntamento approfondiamo alcuni aspetti del firmware implementato nel sistema di monitoraggio di temperatura presentato nelle puntate precedenti.
4
a cura di Carlo Tauraso
ell’ultima puntata abbiamo descritto un dispositivo che permette il monitoraggio della temperatura ambientale attraverso una resistenza NTC. Riprendiamo il discorso soffermandoci su alcuni importanti aspetti del firmware. TermoUSB un’analisi più approfondita Nel listato 1 sono state evidenziate le parti del sorgente che sono necessarie per la corretta integrazione con il firmware USB di Microchip. Tutte le modifiche per implementare i nostri prototipi verranno fatte mantenendo intatta questa struttura pena l’impossibilità di utilizzare le funzioni USBOUT, USBIN, USBINIT di PicBasic. Nella parte evidenziata c’è ancora un’istruzione che avevamo lasciato da parte: ServiceUSBInt. Si tratta di una routine ISR (Interrupt Service Routine) del firmware Microchip che gestisce tutti i segnali di interrupt provenienti dai moduli USB. Si noti che essa viene richiamata solo dopo aver verificato la valorizzazione del flag USBIF del registro PIR (Peripheral Interrupt Register). Elettronica In - febbraio 2005
Se vogliamo inserire una nostra routine di gestione di un altro interrupt dovremo mettere il codice relativo proprio in questo punto attraverso un’istruzione btfsc (bit test f, skip if clear) sul flag che ci interessa seguito dalla call alla procedura che abbiamo sviluppato. Dopo l’esecuzione il programma continuerà con la label RipREG che non fa altro che ripristinare tutti quei valori che possono essere stati influenzati dalla gestione dell’interrupt. Una volta che il flag USBIF è a 1, viene richiamata ServiceUSBInt. Se andiamo a vedere il suo codice che si trova in USB_ch9.asm ci accorgiamo che essa effettua un’ulteriore discriminazione andando ad analizzare un altro registro UIR (USB Interrupt Flags Register). Il “cuore” del firmware Microchip è presentato nel listato 2. Al suo interno troviamo una serie di ulteriori flag per identificare il tipo di segnale interrupt USB generato. Ad esempio troviamo il bit USB_RST (bit 0 di UIR) che, se valorizzato, indica la presenza del segnale di reset sul bus. L’UIR viene controllato sulla base della configurazione di un > 83
'******Dichiarazioni variabili necessarie per uso firmware USB****** wsave ssave psave fsave
VAR VAR VAR VAR
BYTE BYTE BYTE BYTE
$70 system bank0 system bank0 system bank0 system
'permette di salvare W 'permette di salvare STATUS 'permette di salvare PCLATH 'permette di salvare FSR
'******Dichiarazioni variabili applicazione temper VAR BYTE 'variabile che conterra il valore proveniente dal modulo A/D DEFINE DEFINE
OSC 24 SHOW_ENUM_STATUS 1
' Clock 24Mhz ' Visualizza lo stato relativo al processo di enumerazione ' su PORTB ' Definizioni per l'utilizzo dell'istruzione ADCIN DEFINE ADC_BITS 8 ' Numero di bit nel risultato DEFINE ADC_CLOCK 3 ' Clock RC TAD=4uS/bit 9,5TAD/byte DEFINE ADC_SAMPLEUS 50 ' Frequenza Campionamento uS PORTB = 0 TRISB = 0
' 8 LED uscite digitali spenti ' PORTB definita in uscita
GoTo INIZIO
' Salta al main
' Il gestore Interrupt inizia dalla label BUSINT DEFINE INTHAND BUSINT Asm BUSINT
RIPREG
EndAsm INIZIO:
CONV:
movf movwf movlw movwf btfsc Call
clrf movf movwf movf movwf swapf movwf swapf swapf retfie
FSR, W ;salvataggio di FSR fsave High ServiceUSBInt PCLATH PIR1, USBIF ;Se non c'e' alcun interrupt da gestire vado a RIPREG ServiceUSBInt ;Richiama la routine firmware che gestisce tutti gli ;Interrupt dei moduli USB del PIC STATUS fsave, FSR psave, PCLATH ssave, STATUS wsave, wsave,
;Ripristino registri salvati W W W F W
USBInit TRISA = %11111111 ADCON1= 4 Pause 500
; Torno al pgm principale
' Processo di enumerazione alla fine il device ' entra nello stato Configurato ' PORTA tutta in ingresso ' [DDDDADAA] RA0,RA1 analogici Vdd tensione di rif ' Attesa
Pause 100 ADCIN 0, temper 'Campiona segnale su RA0 e metti risultato in temp Pause 150 USBOut 1, temper, 1, CONV 'Invia risultato sul bus e torna a campionare GoTo CONV ' Continua all'infinito
altro registro chiamato UIE (USB Interrupt Enable Register). Questo registro ha un flag di abilitazione per ciascuno dei possibili segnali di interrupt. Se il flag è valorizzato a 1, il relativo segnale verrà rilevato, altrimenti no (mascheramento degli interrupt). Una volta controllato il registro UIR, la routine richiama l’esecuzione del codice di gestione opportuno a seconda dell’interrupt rilevato. Il file USB_ch9.asm permet84
te di analizzare l’implementazione delle funzionalità offerte dal firmware Microchip. Nel paragrafo che riguarderà la personalizzazione del firmware agiremo direttamente su questo file per venire incontro alle nostre esigenze. Se, invece, qualcuno ha la curiosità di vedere come siano implementate in assembler le operazioni Basic USBOUT e USBIN deve riferirsi al file usb_defs.inc. Prendiamo ad esempio l’istruzione febbraio 2005 - Elettronica In
Corso PIC-USB
LISTATO 1 ' Programma TERMOMETRO USB ' Esperimento n.1 Corso PIC-USB Elettronica-In
Corso PIC-USB
LISTATO 2
USB Interrupt Flag Register
ServiceUSBInt banksel UIR movf UIR,w ; get the USB interrupt register andwf UIE,w ; mask off the disabled interrupts bcf STATUS, RP0 ; BANK 2 pagesel ExitServiceUSBInt btfsc STATUS,Z ; is there any unmasked interrupts? goto ExitServiceUSBInt; no, bail out. ....... .......Lista di Interrupt con le Call relative ....... ExitServiceUSBInt banksel PIR1 Peripheral Interrupt Register bcf PIR1,USBIF return
USBOUT che abbiamo utilizzato in questo primo esperimento. Nel file USB_ch9.asm viene creata unâ&#x20AC;&#x2122;istanza per le funzioni PUTEP1 e PUTEP2 che non sono altro che delle macro definite nel file usb_defs.inc. (vedi listato 3 e 4).
USB Interrupt Enable Register
Per capirne il funzionamento bisogna considerare che il PIC16C745 per gestire al meglio la comunicazione sugli endpoints utilizza una tabella chiamata BDT (Buffer Descriptor Table) che riserva a ciascuno di essi 4 bytes. Ciascun >
LISTATO 3 ; ********************************************************************** PUTEP1 ; create instance of PUTEP1 PUTEP2 ; create instance of PUTEP2 ; *********************************************************************
Aprendo questâ&#x20AC;&#x2122;ultimo file troviamo le istruzioni relative dopo la label PutUSB1.
LISTATO 4 PutUSB1
movwf
GPtemp
movf STATUS,w banksel RP_save movwf RP_save
; save Bytecount temporarily in common RAM ; save bank bits before we trash them ; switch to bank 2
movf andlw movwf
GPtemp,w 0x0F counter
movf movwf
FSR,w source_ptr
movf banksel pagesel btfsc goto
counter,w BD1IST nobufferputep1 BD1IST,UOWN nobufferputep1
movwf pagesel btfsc goto movf bcf movwf
BD1IBC ; set byte count in BD exitputloop STATUS,Z ; is it a zero length buffer? exitputloop ; yes, bail out now and avoid the rush BD1IAL,w ; get address pointer STATUS,RP0 ; back to bank 2 dest_ptr
; extract byte count.
; prepare to copy the byte count ; bank 3 ; is the buffer already full? ; yes - don't write over it
.............. ..............Loop scaricamento dati nel buffer puntato .............. exitputloop bsf movf
STATUS,RP0 BD1IST,w
Questa funzione ha in ingresso un counter per il numero di byte da inviare in w e un puntatore all'area contenente i dati da trasmettere in FSR+IRP.
Verifica se il buffer descriptor status register per l'endpoint 1 IN, risulta libero, vedi sistema a semafori nelle righe di spiegazione seguenti.
Ecco il buffer descriptor byte counter per l'endpoint 1 IN, qui il firmware mette il numero di byte da inviare.
Ogni record della Buffer Descriptor Table punta attraverso il buffer descriptor address low ad un buffer nella memoria del PIC che contiene i dati da inviare. Questa funzione prende tale valore proprio da questo registro, sempre per l'endpoint 1 IN, e lo gira a w.
; back to bank 3
Elettronica In - febbraio 2005
85
andlw xorlw iorlw movwf banksel movf movwf bsf return nobufferputep1 bcf return endm
0x40 0x40 0x88 BD1IST RP_save RP_save,w STATUS STATUS,C
; save only the data 0/1 bit ; toggle the data o/1 bit ; set owns bit and DTS bit
; restore bank bits the way we found them ; set carry to show success
STATUS,C
record è condiviso tra l’MCU (processore+firmware) e l’USB (SIE: Serial Interface Engine) pertanto si utilizza un ingegnoso sistema a semafori. In pratica la tabella contiene un bit detto UOWN (deriva da owned) che stabilisce chi detiene il diritto di accedere ai record. Si dice che il record è posseduto (owned by) dall’interfaccia USB quando il bit è a 1. Quando è 0 soltanto il processore e quindi il relativo firmware possono accedervi. Naturalmente quando il record è posseduto da uno dei due contendenti, l’altro deve aspettare il suo turno. I 4 byte sono composti da due registri di stato (BDndST), un contatore dei byte da inviare (BDndBC), ed infine un registro che contiene l’indirizzo di base del buffer dove sono conservati i dati da inviare (BDndAL). Nei nomi “nd” sta per number e direction quindi BD1IST è il registro di stato per l’endpoint 1 in INPUT. Si ricordi che ogni endpoint è caratterizzato sempre da un numero e una direzione. Ogni record punta attraverso BDndAL ad un’area nello spazio di indirizzamento del processore (precisamente sul banco 3 nel range 1B8h 1DFh). Nel momento in cui si chiama la PUTEP1 viene verificato il bit OWN per il registro di stato BD1IST. Se è a 0 viene valorizzato il BD1IBC, con il counter passato in w viene letto il BD1IAL per puntare al buffer di destinazione e si avvia un ciclo che porta i dati puntati da FSR+IRP all’area puntata dal BD1IAL. Terminato il ciclo viene messo il bit OWN a 1 per rilasciare il lock del buffer in maniera che il controller USB possa accedervi (in particolare lo farà l’applicazione host). Infine viene valorizzato il bit di Carry come valore in uscita: 1=tutto OK, il buffer è disponibile, 0=il buffer è occupato, bisogna riprovare in un secondo momento. A questo punto il listato del nostro termometro USB ha svelato quasi ogni suo segreto. Vediamo quindi di passare al nostro secondo esperimento nel quale sfrutteremo le potenzialità di pilotaggio 86
Dopo aver caricato il buffer mette il bit OWN a 1 in maniera da rendere il buffer disponibile al controller USB.
Valori di uscita di questa funzione il bit di Carry viene messo a 1 se tutte le operazioni sono andate a buon fine ed il buffer è pronto per essere letto dall'USB. Altrimenti è a 0.
PWM del PIC16C745. Costruiremo, in particolare, un controller PWM per piccoli motori DC gestito tramite interfaccia USB. In questo modo potremo affrontare una nuova parte di sviluppo firmware che riguarda la ricezione di dati di controllo del nostro device. Credo sia chiaro l’intento didattico. Mentre con la prima esperienza abbiamo inviato dati verso l’host ora dobbiamo capire anche come si puo’ fare l’operazione inversa. In particolare, in questo caso, utilizzeremo una coppia di dati che ci permetteranno di precisare la velocità di rotazione e l’intervallo di tempo di accensione. Esperimento n.2 PWM-USB. Sulla nostra demoboard abbiamo già disponibili due belle uscite PWM (connettori PWM1 e PWM2) in grado di erogare però una corrente massima di 100mA / 40V. Ciò comporta una limitazione nella scelta del carico da utilizzare. Per questa esperienza utilizzeremo un piccolo motore a 12V/100mA che può essere tranquilla-
Fig. 1
febbraio 2005 - Elettronica In
Corso PIC-USB
(continuazione del listato 4)
Corso PIC-USB
mente controllato dai transistor driver (BC337) della nostra demoboard. Dovendo alimentare motori con esigenze più elevate si possono inserire dei MOSFET come l’IRZ44 (www.irf.com) che arrivano a veicolare correnti decisamente maggiori (senza particolari accorgimenti si raggiungono i 7-8A con tensioni max di 60V). Nel nostro caso abbiamo voluto ridurre al minimo l’intervento “hardware” per poterci concentrare sulle problematiche software, vero obiettivo di questo corso. E’ doveroso iniziare con una breve premessa sul pilotaggio PWM (Pulse Width Modulation). Dovendo controllare la velocità di rotazione di un motore in continua si può variare la tensione applicata collegando in serie una resistenza. Ma, se il carico del motore aumenta, aumenterà anche la richiesta di corrente che comporterà un ulteriore caduta di tensione sulla resistenza e quindi minor tensione ai terminali del motore. Quest’ultimo tenterà di assorbire ancor più corrente e alla fine si fermerà. Attraverso il PWM, invece, si simula una sorgente di tensione variabile inviando degli impulsi al motore e variandone la lunghezza per modificarne la velocità di rotazione. In pratica quanto più gli impulsi sono “lunghi” tanto più veloce girerà il nostro motore e viceversa. Lo si vede chiaramente in figura 1 dove la larghezza degli impulsi va a decrescere. Si consideri che il nostro motore non riesce ad accorgersi del fatto che noi furbescamente gli stacchiamo l’alimentazione anche perchè siamo decisamente rapidi nel farlo, il nostro PIC infatti è in grado di generare segnali dell’ordine dei 20KHz. Il carico “crederà” quindi di essere ali-
mentato da una sorgente di tensione variabile. C’è però un altro problema dovuto all’induttanza. L’avvolgimento del motore è in pratica un induttore, cioè qualcosa che tende a mantenere costante la corrente che vi scorre. Quando il nostro impulso è in discesa la corrente è costretta a variare repentinamente verso il basso ma l’induttore fa si che ciò non avvenga tendendo a far salire la tensione sul collettore del transistor driver. Si crea cioè un picco chiamato “FlyBack” che può danneggiare seriamente il driver. Inserendo in parallelo all’avvolgimento del motore un diodo di ricircolo si offre una via alternativa alla corrente che anzichè passare per il prezioso transistor fluisce attraverso il più robusto diodo. Il circuito risultante è immediato e lo si può vedere in figura 2. Il funzionamento è presto detto. Il segnale PWM proveniente dal pin RC2 viene trasferito alla base del transistor T1 attraverso la resistenza R15. Ad ogni impulso proveniente dal PIC, il transistor si porta in conduzione agendo come un vero e proprio interruttore. Attraverso il software host invieremo al device due valori: uno relativo alla percentuale di “dutycycle” e l’altro pari al numero di impulsi da inviare. Il device modulerà la larghezza degli impulsi a seconda del primo valore e farà variare il numero di giri del motore, in secondo luogo creerà dei treni di impulsi lunghi quanto il secondo valore modificando il tempo di accensione. A questo punto non ci resta che connettere il nostro motore alla demoboard e alla sorgente di alimentazione stabilizzata come nello schema. Terminale + Motore —> +12 v alimentatore est. Terminale - Motore —> PWM1 Demoboard Terminale - Alimentatore —> GND Demoboard
Fig. 2
Passiamo quindi allo sviluppo software. Questa volta non andremo a ripercorrere la formazione di tutti i descrittori ma prendendo ad esempio il listato precedente ci soffermeremo sulle modifiche necessarie. Inanzitutto analizziamo l’utilizzo degli endpoint. Analisi degli endpoints In questo caso dovremo realizzare un dispositivo che principalmente agisce come ricettore di comandi, pertanto questa volta aggiungeremo un altro endpoint 1 con direzione OUT. Si ricordi, >
Elettronica In - febbraio 2005
87
Descrittore Endpoint
che le direzioni sono sempre rispetto all’host in quanto ci troviamo in un sistema centrato su quest’ultimo. Per quanto riguarda invece i campi in ingresso (vedi descrittore report) sarà necessario predisporne due: un byte servirà per stabilire il “duty cicle” dell’impulso PWM e l’altro invece stabilirà il numero di “cicli” da effettuare quindi le ripetizioni che formano il treno di impulsi. Il primo ci permetterà di regolare la velocità di rotazione con 256 possibili valori (0=0%255=100%). Il secondo, invece, ci permetterà di stabilire l’intervallo di accensione del motore. Bisogna tener presente che la durata del ciclo dipende dalla frequenza dell’oscillatore utilizzato dal PIC, in generale utilizzando una frequenza di 24MHz la durata di ciascun ciclo si attesta intorno ad 1ms (0,83 ms). Noi utilizzeremo come intervallo minimo 5 sec e max 255*5 sec cioè 21
Questa volta partiremo direttamente dalle modifiche necessarie al listato senza soffermarci sulle definizioni di ciascun descrittore. Innanzitutto nel descrittore Interface dobbiamo modificare il valore bNumEndpoints da 1 a 2 visto che utilizzeremo EP1IN e EP1OUT. I descrittori device, configuration e hid si possono tranquillamente mantenere identici visto che stiamo definendo un dispositivo hid con una sola configurazione ed interfaccia. Vediamo nel concreto quali sono le modifiche da effettuare nella tabella del descrittore Endpoint: Nel file PWMDSC.ASM inseriremo nella sezione TABELLA 1 - DESCRITTORE ENDPOINT le istruzioni riportate nel listato 5 in corrispondenza della label di inizio del secondo descrittore (Endpoint2). Si faccia attenzione al fatto che in questo caso abbiamo definito due endpoint di dimensioni diverse. Uno in uscita di 1 byte ed uno in entrata di 2 byte. Il valore wMaxPacketSize è piuttosto importante perchè stabilisce la quantità di dati che possiamo scam- >
Tabella 1 - Descrittore ENDPOINT
LISTATO 5 retlw
88
0x07 retlw retlw retlw retlw retlw retlw
; bLength ENDPOINT ; bDescriptorType 0x01 ; bEndpointAddress 0x03 ; bmAttributes 0x02 ; wMaxPacketSize (low-b) 0x00 ; wMaxPacketSize (high-b) 0x0A ; bInterval
febbraio 2005 - Elettronica In
Corso PIC-USB
minuti. Naturalmente possiamo anche pensare di utilizzare più byte (uno low e uno high) per impostare intervalli più elevati. Per i nostri scopi però credo che sia sufficiente così.
Corso PIC-USB
Tabella 2 - Descrittore REPORT
LISTATO 6 retlw 0x06 ; Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Usage Page (low-b) ("Vendor Defined Page 1") retlw 0xFF ; Usage Page (high-b) ("Vendor Defined Page 1") retlw 0x09 ; Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Usage ("Vendor Defined Usage 1") retlw 0xA1 ; Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Collection ("Application") retlw 0x09 ; Byte di prefisso (bTag,bType,bSize) retlw 0x02 ; Usage ("Vendor Defined Usage 2") retlw 0xA1 ; Byte di prefisso (bTag,bType,bSize) retlw 0x00 ; Collection ("Physical") retlw 0x06 ; Byte di prefisso (bTag,bType,bSize) retlw 0x02 ; Usage Page (low-b) ("Vendor Defined Page 2") retlw 0xFF ; Usage Page (high-b) ("Vendor Defined Page 2") retlw 0x09 ; Byte di prefisso (bTag,bType,bSize) retlw 0x03 ; Usage ("Vendor Defined Usage 3") retlw 0x15 ; Byte di prefisso (bTag,bType,bSize) retlw 0x00 ; Logical Minimum (0) retlw 0x26 ; Byte di prefisso (bTag,bType,bSize) retlw 0xFF ; Logical Maximum (low-b) (255) retlw 0x00 ; Logical Maximum (high-b) retlw 0x75 ; Byte di prefisso (bTag,bType,bSize) retlw 0x08 ; Report Size (8 bits) retlw 0x95 ; Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Report Count (1 campo dati) retlw 0x81 ; Byte di prefisso (bTag,bType,bSize) retlw 0x02 ; Input (Data, Var, Abs) retlw 0x09 ; Byte di prefisso (bTag,bType,bSize) retlw 0x04 ; Usage ("Vendor Defined Usage 4") retlw 0x09 ; Byte di prefisso (bTag,bType,bSize) retlw 0x05 ; Usage ("Vendor Defined Usage 5") retlw 0x15 ; Byte di prefisso (bTag,bType,bSize) retlw 0x00 ; Logical Minimum (0) retlw 0x26 ; Byte di prefisso (bTag,bType,bSize) retlw 0xFF ; Logical Maximum (low-b) (255) retlw 0x00 ; Logical Maximum (high-b) retlw 0x75 ; Byte di prefisso (bTag,bType,bSize) retlw 0x08 ; Report Size (8 bits) retlw 0x95 ; Byte di prefisso (bTag,bType,bSize) retlw 0x02 ; Report Count (2 campo dati) retlw 0x91 ; Byte di prefisso (bTag,bType,bSize) retlw 0x02 ; Output (Data, Var, Abs) retlw 0xC0 ; End Collection ("Physical") retlw 0xC0 ; End Collection ("Application")
Elettronica In - febbraio 2005
Evidenziate in rosso le modifiche rispetto al descrittore report usato per il termoUSB.
89
Come si vede l’utilizzo delle collection rende l’integrazione del report decisamente semplice. Si noti come in questo caso abbiamo realizzato una struttura che descrive una comunicazione su tre campi, uno in ingresso e due in uscita.
Descrittore Report
Per quanto riguarda la parte string le modifiche sono immediate. Le lingue utilizzate sono sempre due: Italiano e Inglese. Dobbiamo solo inserire la descrizione relativa al prodotto “PWMUSB Corso PIC-USB”, il suo numero seriale “EXP.2” e l’interfaccia “EP1/INOUT” nella versione italiana ed inglese. Nel listato 7 è riportato il codice relativo al solo nome del prodotto italiano. Arrivati a questo punto il nostro nuovo descrittore è creato. Ci si ricordi di effettuare la solita modifica nel file USBDESC.ASM inserendo
Passiamo quindi al descrittore report. Se consideriamo la struttura dell’altra volta possiamo sfruttare le collection create inserendo direttamente prima della end collection Physical la definizione relativa a due ulteriori usage che serviranno per i due byte relativi al controllo del motore da parte dell’host (velocità e durata). Si ha quello riportato in tabella 2. Il descrittore report completo diventerà come riportato nel listato 6.
Descrittore String
LISTATO 7 String2_l1 retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw
90
; iProduct ("PWM-USB Corso PIC-USB") String3_l1-String2_l1 ; bLength 0x03 ; bDescriptorType 'P' ; bString 0x00 'W' 0x00 'M' 0x00 '-' 0x00 'U' 0x00 'S' 0x00 'B' 0x00 ' ' 0x00 'C' 0x00 'o' 0x00 'r' 0x00 's' 0x00 'o' 0x00 ' ' 0x00 'P' 0x00 'I' 0x00 'C' 0x00 '-' 0x00 'U' 0x00 'S' 0x00 'B' 0x00
febbraio 2005 - Elettronica In
Corso PIC-USB
biare con l’host attraverso l’istruzione USBIn. Se qui avessimo utilizzato il valore di 1 byte saremmo stati costretti a richiamare due volte la stessa istruzione per reperire i due campi dati. In questo modo, invece, possiamo lavorare direttamente con un buffer lungo 2 byte che registrerà entrambi i valori attraverso un’unica istruzione.
Corso PIC-USB
LISTATO 8 ; include "termodsc.asm" include "PWMdsc.asm"
; Descrittori TermoUSB-Esperimento 1 ; Descrittori PWM-USB-Esperimento 2
l’include relativa al file appena creato. Per rendere le cose più agevoli si commenti la riga del precedente esperimento aggiungendone un’altra per quello attuale. In questo modo sarà sufficiente eliminare il “;” per scegliere quale include far eseguire. Il file diventa come descritto nel listato 8. Il descrittore appena creato si può testare con la routine Leggi-HID, naturalmente bisogna prima scrivere un .bas fittizio cioè che non fa assolutamente niente se non richiamare USBInit per realizzare il processo di enumerazione. Se le cose vanno per il verso giusto la form dell’applicativo dovrebbe presentarsi come in figura 3. PWM-USB Codice Basic
Fig. 3
sari al funzionamento del motore cioè velocità e tempo di accensione, una variabile word (tempo) che permetterà il calcolo del tempo in millisecondi ed infine una variabile per il conteggio dei byte ricevuti dal device. Nella comunicazione con l’host il parametro relativo al tempo di accensione è codificato secondo intervalli di 5 secondi. In pratica se si decide di mantenere acceso il motore per 15 sec, l’host invierà per questo parametro un valore pari a 3. In secondo luogo sul form dell’applicazione PWMhost.exe (scaricabile dal sito della rivista...) si potrà scegliere il duty-cycle facendo riferimento ad una percentuale (da 0 a 100%), mentre i dati inviati saranno codificati secondo 255 livelli discreti. In pratica il 10% corrisponde al livello 26 (precisamente sarebbe pari a 25,5 c’è un arrotondamento per eccesso). Per capire bene il funzionamento del codice dobbiamo analizzare le due istruzioni chiave: PWM e USBIN. La sintassi dell’istruzione PWM è la seguente: PWM Pin,Duty,Cycle Il Pin è quello dal quale prendiamo l’impulso PWM risultante e si può precisare sia attraverso una costante (0-15), sia attraverso una variabile contenente un valore compreso tra 0 e 15, oppure utilizzando il nome di un pin appartenente ad >
Nel listato basic, in pratica, viene creata una sorta di interfaccia della funzione Basic PWM. L’host, infatti invierà sull’endpoint 1 OUT due byte che corrispondono esattamente a due dei parametri necessari all’utilizzo di tale funzione. L’ultimo parametro da valorizzare è quello del pin della porta utilizzata dal PIC per inviare l’impulso. Utilizziamo il connettore PWM1 della demoboard quindi sfrutteremo l’RC2 cioè il pin 2 della PORTC. Per questa esperienza non utilizzeremo l’endpoint 1 IN, che invece riprenderemo nel prossimo esperimento che ci permetterà di apprezzare la potenzialità della comunicazione bidirezionale su USB. Vediamo nel concreto il listato Basic risultante (listato 9). Se lo confrontiamo con il listato del termoUSB vediamo che viene mantenuta la struttura necessaria all’integrazione con il firmware di Microchip. Definiamo una variabile buffer di due byte (param) che conterrà i due parametri necesElettronica In - febbraio 2005
91
EndAsm INIZIO:
retfie USBInit
; Torno al pgm principale ' Processo di enumerazione alla fine il device ' entra nello stato Configurato ' Attesa
Pause 200 ASCOLTA: USBIN 1,param,conta,ASCOLTA tempo = param[1]*5460 PWM PORTC.2,param[0],tempo GoTo ASCOLTA
'Ricezione parametri da host 'ogni livello di param[1]=5sec 'Inviamo l'impulso PWM 'Continua all'infinito
una delle porte del PIC. Noi abbiamo scelto proprio quest’ultima possibilità precisando il pin relativo al morsetto PWM1, se avessimo voluto utilizzare PWM2, avremmo dovuto precisare RC1 cioè PORTC.1. Il Duty-Cycle è selezionabile attraverso un valore compreso tra 0 (0%) e 255 (100%). Nel nostro caso utilizziamo il valore registrato nella prima cella del buffer “param” che corrisponde al primo valore passato nella sequenza proveniente dall’host. Infine, Cycle determina la lunghezza del treno di impulsi ovvero rappresenta il numero di volte per cui il ciclo PWM viene ripetuto. Considerando che con un oscillatore di 4MHz ogni ciclo dura circa 5ms, e che sulla demoboard utilizziamo una frequenza di 24MHz vediamo che il nostro ciclo avrà una durata di poco inferiore ad 1 ms (0,83). Pertanto, il valore trasmesso dall’host (numero di 92
intervalli di 5sec) viene moltiplicato per 5460 per calcolare il numero di cicli da circa 1 ms necessari a far accendere il motore per il tempo precisato. Naturalmente si commette un errore non proprio trascurabile (si perde 1 sec ogni 10), però siamo giustificati dal fatto che stiamo solo facendo un po’ di esperienza. Per quanto riguarda l’istruzione che utilizziamo per comunicare attraverso l’interfaccia USB la sua sintassi è: USBIN Endpoint,Buffer,Countvar,Label Il parametro Endpoint permette di stabilire quale Endpoint si vuole utilizzare per la comunicazione. Nel nostro caso si utilizza l’endpoint 1 OUT. Il buffer è l’array che dovrà contenere i dati provenienti dall’host e quindi deve essere dimensionato a seconda di quanto abbiamo stabilito nei descrittori. Noi utilizziamo una lunghezza di 2 byte sufficiente a registrare la velocità di rotaziofebbraio 2005 - Elettronica In
Corso PIC-USB
LISTATO 9 ' Programma CONTROLLER PWM-USB ' Esperimento n.2 Corso PIC-USB Elettronica-In '******Dichiarazioni variabili necessarie per uso firmware USB****** wsave VAR BYTE $70 system 'permette di salvare W ssave VAR BYTE bank0 system 'permette di salvare STATUS psave VAR BYTE bank0 system 'permette di salvare PCLATH fsave VAR BYTE bank0 system 'permette di salvare FSR '******Dichiarazioni variabili applicazione param VAR BYTE[2] 'velocità di rotazione + tempo di accensione conta VAR BYTE 'contatore byte ricevuti tempo VAR WORD 'variabile per calcolo tempo di accensione DEFINE OSC 24 ' Clock 24Mhz DEFINE SHOW_ENUM_STATUS 1 ' Visualizza lo stato relativo al processo di enumerazione ' su PORTB PORTB = 0 ' 8 LED uscite digitali spenti TRISB = 0 ' PORTB definita in uscita GoTo INIZIO ' Salta al main ' Il gestore Interrupt inizia dalla label BUSINT DEFINE INTHAND BUSINT Asm BUSINT movf FSR, W ;salvataggio di FSR movwf fsave movlw High ServiceUSBInt movwf PCLATH btfsc PIR1, USBIF ;Se non c'e' nessun interrupt da gestire vado a RIPREG Call ServiceUSBInt ;Richiama la routine firmware che gestisce tutti gli ;Interrupt dei moduli USB del PIC RIPREG ;Ripristino registri salvati clrf STATUS movf fsave, W movwf FSR movf psave, W movwf PCLATH swapf ssave, W movwf STATUS swapf wsave, F swapf wsave, W
Corso PIC-USB
ne e il tempo di accensione. Countvar è una variabile contatore da 1 byte che permette di conservare il numero di byte ricevuti dall’host. Infine, la label finale è l’etichetta dove l’esecuzione si sposta quando i dati in ricezione non sono ancora disponibili. Noi, in pratica, cicliamo sull’istruzione principale finchè tutti i dati che ci servono non sono arrivati sul device. Appena i dati sono stati ricevuti il PIC esegue l’istruzione PWM dopo aver ricalcolato il numero di cicli. Anche questa volta abbiamo utilizzato la define SHOW_ENUM_STATUS per poter monitorare attraverso i leds della demoboard il funzionamento del nostro prototipo. Noteremo che quan-
Fig. 3
Accensione tramite PWM1 do inviamo i due byte ci sarà una rapida accensione del led7 che ci avverte dell’attività dell’endpoint1 OUT. Subito dopo vedremo il led PWM1 che si accenderà con un’intensità proporzionale al duty-cycle definito. Anche in questo caso non siamo costretti a disporre di componen-
Fig. 5
ti aggiuntivi per vedere se il nostro sistema funziona. Per chi ha voluto invece realizzare completamente questo esperimento, vedrà che all’aumentare del duty-cycle il motore aumenterà la velocità di rotazione. Un progetto di unione tra i due esperimenti può essere quello di fare in modo che il dispositivo esegua il monitoraggio della temperatura e, raggiunto un valore di soglia, attivi il motore (sul quale avremo posizioElettronica In - febbraio 2005
Fig. 4
nato una piccola ventola per la dissipazione) e riporti quindi la temperatura al di sotto della soglia definita. In particolare si potrebbe regolare la velocità di rotazione a seconda della differenza di temperatura da compensare. Un sistema del genere è stato realizzato su alcuni PC portatili per regolare il flusso d’aria sul dissipatore del processore. Per quanto riguarda il software lato host, abbiamo realizzato un’applicazione in Delphi (scaricabile dal sito della rivista) che permette di scegliere i valori da inviare al dispositivo attraverso pochi clic. La form principale - inviando un impulso al 65% per 5 secondi - si presenta come in figura 5. L’utilizzo è decisamente semplice. Si impostano i valori relativi alla velocità ed al tempo d’accensione attraverso il pannello “Controllo Motore” per poi inviarli all’host attraverso il pulsante “Invia Dati”. Si può controllare una rappresentazione dell’impulso nel diagramma di destra. Durante il periodo di accensione il bottone “Invia Dati” viene disabilitato per evitare ulteriori trasmissioni. Sul pannello “Ultimi valori” si vedranno i valori immessi sul bus. In questo caso 166 è il livello logico corrispondente al 65% (65*2,55) mentre 1 è il numero di intervalli di 5 sec per il tempo d’accensione. Anche in questo caso il sistema è pienamente hot-swap in quanto si può connettere e disconnettere il dispositivo a run-time: il pannello “IDDispositivo” verrà aggiornato automaticamente. Anche per questo mese siamo giunti al termine; nella prossima puntata analizzeremo in dettaglio il firmware del PWM-USB così come abbiamo fatto per il Termo-USB. Introdurremo anche un altro esperimento che sfrutterà un sistema di comunicazione bidirezionale. Appuntamento dunque al fascicolo di marzo. 93
Corso PIC-USB
Corso di programmazione per PIC: l’interfaccia USB B Alla scoperta della funzionalità USB implementata nei microcontrollori della Microchip. Un argomento di grande attualità in considerazione della crescente importanza di questa architettura nella comunicazione tra computer e dispositivi esterni. In questo quinto appuntamento analizziamo la comunicazione bidirezionale tra PC e un device USB proponendo un sistema che permetta di cifrare un testo scritto su PC, sfruttando le capacità di calcolo del nostro PIC.
5
a cura di Carlo Tauraso ella precedente puntata abbiamo presentato un dispositivo in grado di controllare un piccolo motore attraverso impulsi PWM. PWM-USB: il "dietro le quinte" Analizziamo ora il codice che sta dietro
MACRO
l'istruzione Basic USBIN, che abbiamo utilizzato per ricevere i dati relativi alla velocità e al tempo d'accensione. Dobbiamo riferirci, come al solito, al file USB_ch9.asm, all'interno del quale viene creata un'istanza per le funzioni GETEP1 e GETEP2; altro non sono che delle macro (di seguito riportate) definite nel file “usb_defs.inc”. >
DEL FILE USB_DEFS.INC
; ********************************************************************** GETEP1 ; create instance of GETEP1 ; create instance of GETEP1 GETEP2 ; create instance of GETEP2 ; create instance of GETEP2 ; *********************************************************************
LISTATO 1 GetUSB1
movf STATUS,w banksel RP_save movwf RP_save movf movwf
FSR,w dest_ptr
banksel BD1OST pagesel nobuffer btfsc BD1OST,UOWN
Elettronica In - marzo 2005
; save bank bits before we trash them ; switch to bank 2
La funzione prende in ingresso FSR+IPR come puntatore al buffer dove verranno registrati i dati ricevuti.
; save the buffer destination pointer ; bank 3 ; Has the buffer been filled?
Verifica bit OWN per il registro BD1OST (Buffer Descriptor Status endpoint1 OUT).
83
goto
nobuffer
; nope, OWN = 1 ==> SIE owns the buffer ; Yep: OWN = 0 ==> PIC owns the buffer
movf banksel movwf movwf pagesel btfsc goto banksel movf banksel movwf
BD1OBC,w counter counter bytecounter exitgetloop STATUS,Z exitgetloop BD1OAL BD1OAL,w source_ptr source_ptr
; get byte count ; bank 2 ; # of bytes that will be moved ; ; ; ; ;
is it a zero length buffer? yes, bail out now and avoid the rush bank 3 get address pointer bank 2
; This loop operates with the direct bank bits set to bank 2, while IRP ; gets switched as needed to for the buffer copy getEPloop ............ ............Loop di Trasmissione ............ exitgetloop bsf movf andlw xorlw iorlw movwf
nobuffer
STATUS,RP0 BD1OST,w 0x40 0x40 0x88 BD1OST
; bank 3
movlw movwf bcf movf movwf movf movwf movf bsf return
0x08 BD1OBC STATUS,RP0 bytecounter,w GPtemp RP_save,w STATUS GPtemp,w STATUS,C
; reset byte counter
banksel movf movwf bcf return endm
RP_save RP_save,w STATUS STATUS,C
; save only the data 0/1 bit ; toggle the data o/1 bit ; set owns bit and DTS bit
; ; ; ;
bank 2 return # of bytes moved in W reg move byte counter to temporary ram restore bank bits
Si utilizza BD1OAL (Buffer Descriptor Address Low endpoint1 OUT) per precisare l'indirizzo del buffer da cui si leggeranno i dati.
Il ciclo di trasmissione viene iterato decrementando la variabile counter in maniera che quando arriva a zero il ciclo termina.
Il bit OWN è messo a 1 cioè si libera dal lock la BDT per permettere l'accesso all'USB.
Parametri in uscita: w per il numero di byte inviati e Carry Flag per indicare che l'operazione è andata a buon fine.
; load W with the byte count ; signal success
; restore the bank bits
Aprendo questo file, troviamo le istruzioni relative alla label GetUSB1 (vedi listato 1). Vediamo ora cosa accade quando si richiama la GETEP1. Innanzitutto viene verificato il flag OWN di BD1OST (registro di stato endpoint 1 OUT). Quando è 1, il registro è occupato dal SIE, quindi, l'esecuzione continua dall'etichetta "nobuffer" che pone il Carry Flag a 0 permettendo di uscire dalla funzione; anche questa routine così come la PUTEP1 usa il Carry Flag per comunicare se l'operazione è andata a buon fine (CF=1) oppure no (CF=0). Nel caso invece il bit OWN sia a zero, viene valorizzato il registro w attraverso BD1OBC (Byte Counter dell'endpoint1 OUT). La routine utilizza le due variabili counter e bytecounter per salvare tale valore durante il ciclo di ricezione. In ingresso la funzione usa FSR+IRP come puntatore al buffer dove verranno registrati i dati rice84
Caricamento del numero di byte da ricevere nelle 2 variabili counter e bytecounter. Il valore deriva da BD1OBC (Buffer Descriptor Byte Counter endpoint1 OUT).
vuti. Durante il ciclo di ricezione i dati vengono spostati dall'indirizzo puntato da BD1OAL, al buffer di destinazione assegnato dalla funzione. Al termine dell’operazione, il bit OWN viene messo a 1, così come il bit DTS (Data Toggle Synchronization) per evitare la sovrascrittura dei registri con pacchetti errati. Anche CF è messo a 1 per comunicare che l'operazione è andata a buon fine e la variabile bytecounter passa al registro w (parametro di uscita) il numero di byte inviati. All’etichetta GetUSB1 segue un’altra definita GetUSB2 che rappresenta la macro per GetEP2. Le istruzioni sono uguali, l'unica variazione deriva dal fatto che vengono utilizzati i buffer descriptor dell'endpoint2 anzichè dell'endpoint1. Si può concludere anche alla luce di quanto abbiamo visto per PutEP1 che la BDT rappresenta una struttura versatile ed efficace per gestimarzo 2005 - Elettronica In
Corso PIC-USB
(Continuazione del listato 1)
Corso PIC-USB
re il passaggio dati sugli endpoint sia in entrata che in uscita. Siamo pronti, quindi, per introdurre il terzo esperimento che vuole fare da unione rispetto agli altri due implementando un sistema bidirezionale di comunicazione. Esperimento n.3 Cifrario-USB. Questa volta, per analizzare la comunicazione bidirezionale tra PC e device, proponiamo la realizzazione di un sistema che permetta di cifrare un testo, scritto sul proprio PC, utilizzando le capacità di calcolo del nostro PIC. Si tratta, chiaramente, di un esempio didattico di comunicazione bidirezionale su USB e niente di più, che ci consente di capire più a fondo in che modo debba essere gestita la comunicazione. Per effettuare il nostro esperimento utilizzeremo princìpi che sono alla base della crittografia moderna; per comprenderli facciamo un salto nel passato. Sul finire della Prima Guerra Mondiale si andavano diffondendo le prime telescriventi meccani-
che che utilizzavano un alfabeto di 32 caratteri (chiamato "codice Baudot") rappresentati in maniera binaria mediante la foratura di un nastro cartaceo (5 fori per ciascun carattere). Nel 1917, un giovane impiegato della compagnia americana AT&T, Gilbert Vernam, inventò un sistema di protezione dei testi scritti in Baudot costruendo una macchina che era in grado di leggere due nastri contemporaneamente generando, in uscita, un ulteriore nastro che essenzialmente era il risultato dell'OR esclusivo degli altri due. In pratica, inserendo nella macchina un nastro sul quale era incisa una sequenza casuale di caratteri (A), lunga quanto il testo da cifrare, e quello contenente il testo in chiaro (B), se ne produceva uno cifrato (C). Per risalire al testo in chiaro era sufficiente inserire nella macchina i nastri A e C ricevendo in uscita l'originale. Si trattava di un sistema crittografico simmetrico che ancor oggi è conosciuto con il nome di "Nastri di Vernam". Esso rappresenta un sistema in cui la sicurezza dipende, in maniera esclusiva, dalla segretezza della chiave e non da quella del metodo secondo >
Tabella 1a- Descrittore ENDPOINT [EP1IN] etichetta Endopoint1
Tabella 1b - Descrittore ENDPOINT [EP1OUT] etichetta Endopoint2
Elettronica In - marzo 2005
85
Endpoint1 retlw retlw retlw retlw retlw retlw retlw Endpoint2 retlw retlw retlw retlw retlw retlw retlw
0x07 ENDPOINT 0x81 0x03 0x02 0x00 0x0A
; ; ; ; ; ; ;
bLength bDescriptorType bEndpointAddress bmAttributes wMaxPacketSize (low-b) wMaxPacketSize (high-b) bInterval
0x07 ENDPOINT 0x01 0x03 0x01 0x00 0x0A
; ; ; ; ; ; ;
bLength bDescriptorType bEndpointAddress bmAttributes wMaxPacketSize (low-b) wMaxPacketSize (high-b) bInterval
il noto principio di Kerckhoffs che è alla base di tutta la crittografia moderna. Ma la cosa che lo rende ancor più interessante è che si tratta di un metodo teoricamente perfetto cioè un cifrario "indecrittabile". Sommando un testo casuale con un testo in chiaro, infatti, si annulla l'informazione del testo stesso rendendolo anch'esso casuale.
Il cifrato, almeno in linea di principio, può rappresentare qualsiasi testo in chiaro di uguale lunghezza e quindi il malintenzionato che vorrebbe leggerlo non ha alcun indizio da utilizzare per decifrarlo. Ma cosa c'entra il nostro PIC con tutto questo? In effetti noi realizzeremo un cifratore di Vernam che, ricevuto un testo in chiaro, genere-
LISTATO 3 retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw
86
0x06 0x01 0xFF 0x09 0x01 0xA1 0x01 0x09 0x02 0xA1 0x00 0x06 0x02 0xFF 0x09 0x03 0x09 0x04 0x15 0x00 0x26 0xFF 0x00 0x75 0x08 0x95 0x02 0x81 0x02 0x09 0x05 0x15 0x00 0x26 0xFF 0x00 0x75 0x08 0x95 0x01 0x91 0x02 0xC0 0xC0
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
Byte di prefisso (bTag,bType,bSize) Usage Page (low-b) ("Vendor Defined Page 1") Usage Page (high-b) ("Vendor Defined Page 1") Byte di prefisso (bTag,bType,bSize) Usage ("Vendor Defined Usage 1") Byte di prefisso (bTag,bType,bSize) Collection ("Application") Byte di prefisso (bTag,bType,bSize) Usage ("Vendor Defined Usage 2") Byte di prefisso (bTag,bType,bSize) Collection ("Physical") Byte di prefisso (bTag,bType,bSize) Usage Page (low-b) ("Vendor Defined Page 2") Usage Page (high-b) ("Vendor Defined Page 2") Byte di prefisso (bTag,bType,bSize) Usage ("Vendor Defined Usage 3") Byte di prefisso (bTag,bType,bSize) Usage ("Vendor Defined Usage 4") Byte di prefisso (bTag,bType,bSize) Logical Minimum (0) Byte di prefisso (bTag,bType,bSize) Logical Maximum (low-b) (255) Logical Maximum (high-b) Byte di prefisso (bTag,bType,bSize) Report Size (8 bits) Byte di prefisso (bTag,bType,bSize) Report Count (2 campi dati) Byte di prefisso (bTag,bType,bSize) Input (Data, Var, Abs) Byte di prefisso (bTag,bType,bSize) Usage ("Vendor Defined Usage 5") Byte di prefisso (bTag,bType,bSize) Logical Minimum (0) Byte di prefisso (bTag,bType,bSize) Logical Maximum (low-b) (255) Logical Maximum (high-b) Byte di prefisso (bTag,bType,bSize) Report Size (8 bits) Byte di prefisso (bTag,bType,bSize) Report Count (1 campo dati) Byte di prefisso (bTag,bType,bSize) Output (Data, Var, Abs) End Collection ("Physical") End Collection ("Application")
marzo 2005 - Elettronica In
Corso PIC-USB
LISTATO 2
Corso PIC-USB
rà la chiave casuale e quindi il testo cifrato in tempo reale, rendendoli disponibili su due pannelli nell'applicazione host sfruttando una comunicazione bidirezionale. Purtroppo la demoboard non può essere interfacciata con una EEPROM tradizionale ad accesso parallelo (nella quale sono disponibili solo linee in entrata o in uscita), perchè per dialogare con un bus I²C è necessario un pin bidirezionale, altrimenti avremmo potuto salvare la chiave su quest'ultima creando una chiave "fisica" vera e propria. In pratica soltanto il legittimo possessore della EEPROM avrebbe potuto leggere il testo cifrato, inserendo la stessa sulla schedina del PIC.
Il nostro prototipo, inoltre, sarà programmato in maniera tale da poter essere attivato solo dopo aver premuto una precisa combinazione dei tasti Inp1-Inp5 inseriti sulla demoboard. In questo modo vedremo come far partire l'enumerazione soltanto in determinate condizioni ed introdurremo due funzioni di base del Firmware Microchip che non sono interpretate dal Basic: DeinitUSB e ConfiguredUSB. Vediamo quindi di procedere con ordine analizzando questo nuovo progetto. Analisi degli endpoints Dobbiamo realizzare un dispositivo che comuni- >
LISTATO 4A String2_l1 retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw
String3_l1-String2_l1 0x03 'C' 0x00 'i' 0x00 'f' 0x00 'r' 0x00 'a' 0x00 'r' 0x00 'i' 0x00 'o' 0x00 '-' 0x00 'U' 0x00 'S' 0x00 'B' 0x00 ' ' 0x00 'C' 0x00 'o' 0x00 'r' 0x00 's' 0x00 'o' 0x00 ' ' 0x00 'P' 0x00 'I' 0x00 'C' 0x00 '-' 0x00 'U' 0x00 'S' 0x00 'B' 0x00
Elettronica In - marzo 2005
; ; ; ;
iProduct ("Cifrario-USB Corso PIC-USB") bLength bDescriptorType bString
87
; include "termodsc.asm" ; include "PWMdsc.asm" include "CIFRAdsc.asm"
; Descrittori TermoUSB-Esperimento 1 ; Descrittori PWM-USB-Esperimento 2 ; Descrittori Cifratore-USB-Esperimento 3
ca con l'host ricevendo un byte del testo in chiaro e risponde con due byte: uno relativo alla chiave ed uno riferito al testo cifrato. Predisporremo quindi l'endpoint1 IN e OUT specificando nel descrittore report che il primo avrà due campi ed il secondo uno solo ricordando che la direzione è sempre stabilita rispetto all’host. Ci troviamo, praticamente in una situazione esat-
tamente opposta rispetto all'esperimento 2. Per quanto riguarda la codifica utilizzeremo l'intramontabile ASCII ad 8bit. Un esercizio di approfondimento potrebbe essere quello di realizzare un sistema che funzioni con il nuovo UNICODE a 16 bit. In pratica, il sistema leggerà il byte del testo in chiaro, genererà il byte casuale della chiave e
LISTATO 5 ' Programma Cifrario-USB ' Esperimento n.3 Corso PIC-USB Elettronica-In '******Dichiarazioni variabili necessarie per uso firmware USB****** wsave VAR BYTE $70 system 'permette di salvare W ssave VAR BYTE bank0 system 'permette di salvare STATUS psave VAR BYTE bank0 system 'permette di salvare PCLATH fsave VAR BYTE bank0 system 'permette di salvare FSR '******Dichiarazioni variabili applicazione cifra VAR BYTE[2] 'carattere cifrato + random conta VAR BYTE 'contatore byte ricevuti testo VAR BYTE 'carattere testo in chiaro seme VAR WORD 'seme per funzione RANDOM DEFINE
OSC PORTB TRISB TRISA TRISC PORTC
= = = = =
24
' Clock 24Mhz
0 0 %00111111 %11000001 0
' ' ' ' '
8 LED PORTB PORTA PORTC Reset
Definizione pin IN e OUT delle porte A,B,C del PIC. Sono necessarie in particolare quelle di RC0 e RC7 per rilevare la sequenza di attivazione.
uscite digitali spenti definita in uscita RA1..RA5 in ingresso RC0-RC6-RC7 in ingresso sequenza attivazione
.........T1CON.1= 0 .........T1CON.0= 1
' Seleziona clock interno per TIMER1 TMR1CS=0 ' Avvia TIMER1 TMR1ON=1
GoTo INIZIO
' Salta al main
' Il gestore Interrupt inizia dalla label BUSINT DEFINE INTHAND BUSINT Asm BUSINT movf FSR, W ;salvataggio di FSR movwf fsave movlw High ServiceUSBInt movwf PCLATH btfsc PIR1, USBIF ;Se non c'è nessun interrupt da gestire vado a RIPREG Call ServiceUSBInt ;Richiama la routine firmware che gestisce tutti gli ;Interrupt dei moduli USB del PIC RIPREG ;Ripristino registri salvati clrf STATUS movf fsave, W movwf FSR movf psave, W movwf PCLATH swapf ssave, W movwf STATUS swapf wsave, F Ciclo per rilevare quando vengono swapf wsave, W premuti i tasti Inp3 e Inp5 della demoboard. retfie ; Torno al pgm principale EndAsm INIZIO:
88
if PORTC=%10000001 then goto USBINI GoTo INIZIO ' Cicla su se stesso fino alla sequenza
marzo 2005 - Elettronica In
Corso PIC-USB
LISTATO 4B
Corso PIC-USB
USBINI:
RICEVI:
INVIA:
' di attivazione esatta PORTB=%11111111 PAUSE 500 PORTB=0
' Tutti i led accesi sequenza OK
USBInit
' Processo di enumerazione alla fine il device ' entra nello stato Configurato
Pause 200
' Attesa
Accensione dei led della demoboard e avvio enumerazione.
Ricezione carattere dall'host.
Elaborazione dall'host.
carattere
USBIN 1,testo,conta,RICEVI PORTB=testo high seme=TMR1l low seme=TMR1h random seme cifra[0]= seme cifra[1]= testo ^ cifra[0]
'ricevi carattere testo in chiaro 'portB rappresenta il carattere ricevuto 'valorizzo il seme con swap Invio caratteri elaborati. 'randomizzo il seme 'carattere casuale 'XOR tra chiaro e casuale
USBOUT 1, cifra,2,INVIA GoTo RICEVI
'invia carattere casuale + cifrato 'Continua all'infinito
realizzerà un XOR tra i due creando il byte cifrato. Al termine, invierà i due byte risultanti all'host. Descrittore Endpoint Anche in questo caso, nel descrittore Interface, bNumEndpoints sarà pari a 2 visto che utilizzeremo EP1IN e EP1OUT. I descrittori device, configuration e hid rimarranno gli stessi dell'esperimento 2. Questa volta dobbiamo intervenire nel codice presente sia dopo l'etichetta Endpoint1 (EP1IN) che Endpoint2 (EP1OUT). In particolare è necessario prestare attenzione ai valori dei campi wMaxPacketSize, e bEndpointAddress, come mostrato in tabella 1a/b. Le istruzioni risultanti sono le stesse riportate nel listato 2. Descrittore Report Se ci ricordiamo come abbiamo creato il descrittore report dell'ultimo esperimento vedremo che in questo caso possiamo inserire due usage iniziali e limitare la seconda ad un solo campo. Grazie alla definizione della collection Physical la modifica è abbastanza semplice. La struttura gerarchica risultante si può sintetizzare in questo modo: USAGE_PAGE (Vendor Defined 1) USAGE (Vendor Usage 1) COLLECTION (Application) USAGE (Vendor Usage 2) COLLECTION (Physical) USAGE_PAGE (Vendor Defined 2) USAGE (Vendor Usage 3) USAGE (Vendor Usage 4) LOGICAL_MINIMUM (0) Elettronica In - marzo 2005
ricevuto
LOGICAL_MAXIMUM (255) REPORT_SIZE (8) REPORT_COUNT (2) INPUT (Data,Var,Abs) USAGE (Vendor Usage 5) LOGICAL_MINIMUM (0) LOGICAL_MAXIMUM (255) REPORT_SIZE (8) REPORT_COUNT (1) OUTPUT (Data,Var,Abs) END_COLLECTION END_COLLECTION Il descrittore report completo risulta come nel listato 3. Descrittore String Il descrittore String è immediato. Utilizziamo sempre due lingue e quindi dobbiamo inserire la descrizione relativa al prodotto "Cifrario-USB Corso PICUSB", il suo numero seriale "EXP.3" e l'interfaccia "EP1/INOUT" nelle due versioni. Riportiamo solamente il codice prodotto in lingua italiana (vedi listato 4a). Terminato il descrittore bisogna effettuare la solita modifica nel file USBDESC.ASM inserendo l’include relativa al file appena creato. Il file diventa come riportato nel listato 4b. Veniamo quindi al codice basic che questa volta sarà un pò più lungo ma abbastanza semplice. PWM-USB Codice Basic Il nostro dispositivo viene riconosciuto dal PC soltanto dopo aver premuto contemporaneamente i tasti Inp3 e Inp5 della demoboard. Come si vede nel listato 5 (in Basic) abbiamo soltanto > 89
90
immediata dei led LD1÷LD8. A questo punto compaiono, sul pannello di destra dell'applicativo, i dati identificativi del dispositivo e lo stato "Connesso". Spostiamoci ora sul primo memo dove dobbiamo digitare il testo che vogliamo cifrare. Facendo click sul pulsante "Invia", i singoli caratteri vengono trasmessi alla demoboard che risponde con la chiave e il cifrato. I caratteri ricevuti dall'host vengono inseriti nei due memo rimanenti. Per entrambi abbiamo previsto la possibilità di salvare il risultato in due file testo. Osserviamo ora la figura 1. Prendiamo i primi due numeri della chiave e del testo cifrato espressi in esadecimale cioè AC (=10101100) e E9 (=11101001). Calcolando l'XOR otteniamo il numero esadecimale 45 (=01000101) che in decimale è 69 cioè il valore ASCII della E maiuscola. Quindi, riotteniamo il testo di partenza in
Fig. 1
chiaro, così come avveniva nella macchina di Vernam leggendo contemporaneamente i due nastri creati. Abbiamo realizzato un cifratore che sfrutta la comunicazione bidirezionale in maniera abbastanza efficiente. Se variate il campo bInterval del descrittore Endpoint potete rallentare la velocità di esecuzione del dispositivo per vedere come i due pannelli vengono popolati via via che i dati vengono trasmessi al dispositivo. Per rendere la cosa più accattivante, ogni carattere ricevuto viene trasferito alla PORTB accendendo i relativi Led (effetto "Albero di Natale"...). In questo esperimento abbiamo visto come sia possibile controllare le condizioni nelle quali avviare il processo di enumerazione del dispositivo. Dopo aver fatto un po’ di prove possiamo passare a considerare le due ultime funzionalità di base del firmware Microchip: ConfiguredUSB e DeinitUSB. Prima di richiamare la InitUSB il "device" è pramarzo 2005 - Elettronica In
Corso PIC-USB
introdotto un ciclo prima di richiamare la funzione InitUSB che si occupa del processo di enumerazione. Nel ciclo non facciamo altro che controllare lo stato della porta C, in particolare dei pin RC0 e RC7 corrispondenti ai tasti Inp3 e Inp5. Non appena questi ultimi vengono premuti, e quindi i relativi bit vanno a 1, il programma scrive sulla porta B il valore 255 accendendo tutti i led (LD1-LD8) della demoboard per far vedere che la sequenza è stata accettata quindi richiama InitUSB avviando il processo di enumerazione finchè il dispositivo non si porta nello stato configurato. A quel punto iniziano i due cicli di ricezione ed invio dei dati. Per quanto riguarda la generazione del carattere casuale sfruttiamo la funzione Basic Random che utilizza un algoritmo pseudo-casuale su una variabile a 16-bit presa come seme. Per valorizzare quest'ultima, utilizziamo uno dei moduli timer del nostro PIC, precisamente il Timer1 che viene sincronizzato sul clock interno e avviato attraverso la valorizzazione di due flag del registro T1CON (Timer1 Control Register): TMR1CS (Timer1 Clock Source Select Bit), TMR1ON (Timer1 On bit). Il Timer1 valorizza due registri da 8 bit TMR1L e TMR1H incrementando il valore a 16 bit risultante sulla base della sorgente di clock selezionata. In questo caso, non potendo connettere ad RC1 e RC0 un oscillatore esterno per come è configurata la demoboard, è stato scelto il clock interno. L'assegnazione alla variabile seme avviene attraverso lo swap dei due byte, dopodiché si effettua l'operazione di OR esclusivo con il carattere del testo in chiaro. Al termine i due risultati dell'elaborazione vengono restituiti all'host attraverso l'endpoint1. Utilizzando l'applicativo (scaricabile dal sito della rivista) cifraUSB.exe possiamo provare la comunicazione bidirezionale con il nostro prototipo cifratore. L'utilizzo è molto semplice: dopo aver collegato la demoboard al PC, è necessario premere contemporaneamente i pulsanti Inp3 e Inp5 ottenendo come risultato l’accensione
Corso PIC-USB
LISTATO 6 ConfiguredUSB macro local enumloop banksel USWSTAT pagesel enumloop enumloop clrwdt movlw 0x03 andwf USWSTAT,w xorlw CONFIG_STATE btfss STATUS,Z goto enumloop endm
; clear the watch dog timer. ; ; ; ;
Estrazione degli ultimi 2 bit dal registro di stato e verifica del valore con CONFIG_STATE=11 per vedere se il dispositivo ha terminato l'enumerazione.
save lower 2 bits of USWSTAT compare with configured state are we configured? nope, keep waiting ...
ticamente inesistente per l'host, pur sfruttando l'alimentazione del bus. In Basic abbiamo a disposizione soltanto tre istruzioni per lavorare con dispositivi USB, una determina l'enumerazione, mentre le altre due ci permettono di controllare la comunicazione tramite gli endpoint. Nelle altre puntate abbiamo visto come, in effetti, queste istruzioni siano in realtà delle interfacce verso
trollare il valore di questo registro restituendo un 1 nel Zero Flag, se il dispositivo è configurato, e uno 0 in tutti gli altri casi. Tale funzionalità è stata sviluppata principalmente per verificare a run-time se è possibile avviare una comunicazione con il dispositivo attraverso gli endpoints. Essa viene richiamata successivamente alla InitUSB per controllare che il processo di enu-
LISTATO 7 DeInitUSB banksel UCTRL bcf UCTRL,DEV_ATT bsf UCTRL,SUSPND clrf
USWSTAT
bcf STATUS,RP1 bcf PIE1,USBIE ifdef SHOW_ENUM_STATUS bcf STATUS,RP0 movlw 0x01 movwf PORTB bsf STATUS,RP0 endif return
; D+/D- go high Z ; Place USB module in low power mode. ; set device state to powered. ; select bank 1 ; clear USB interrupt enable
Disabilitazione di tutti gli Interrupt USB.
Gestione led di controllo su PORTB. ; clear all lights except powered
altrettante funzionalità messe a disposizione dal firmware Microchip. Ebbene, nella realtà, quest'ultimo espone altre funzioni che possono essere richiamate attraverso l'assembler e possono risultare decisamente utili. La prima che andiamo ad analizzare si chiama ConfiguredUSB. All'indirizzo 197h del nostro PIC c'è un registro di utilizzo esclusivo chiamato USWSTAT(USB Software Status Register) del quale, i primi due bit vengono utilizzati dal firmware Microchip per conservare lo stato del dispositivo durante il processo di enumerazione. In particolare vengono utilizzati i seguenti valori: 00=Alimentato 01=Default 10=Indirizzato 11=Configurato La macro ConfiguredUSB non fa altro che conElettronica In - marzo 2005
Dispositivo posto in stand-by attraverso l'USB Control Register (UCTRL).
merazione sia stato concluso con successo. Non è possibile, infatti, utilizzare PutEPn e GetEPn senza aver passato tale controllo. Una piccola avvertenza: il registro USWSTAT è riservato all'utilizzo esclusivo del firmware Microchip ma non è protetto in scrittura quindi lo si può sovrascrivere erroneamente causando dei malfunzionamenti. Se entriamo nel file USB_defs.inc troviamo le istruzioni descritte nel listato 6. Come si vede chiaramente la routine estrae i bit 0 e 1 del registro USWSTAT per verificarne il valore. Il ciclo continua a girare finché non viene raggiunto lo stato "configurato". Da notare che l'utilizzo dell'istruzione xorlw permette di valorizzare direttamente il Zero Flag con il risultato del confronto. Il ciclo si conclude attraverso una btfss (Bit Test f, Skip if Set) che salta il goto successivo se il risultato del confronto è pari a 0 cioè i due bit di USWSTAT sono uguali a CONFIG_STATE (cioè '11' in binario). > 91
DIVENTA ; ****************************************************************** ; DeInit USB ; Shuts down the USB peripheral, clears the interrupt enable. ; ****************************************************************** _DeInitUSB
La seconda macro che andiamo ad analizzare si chiama DeinitUSB. Già dal nome avrete capito che si tratta di una funzionalità esattamente opposta ad InitUSB. In effetti grazie a questa macro è possibile fare in modo che il dispositivo
venga ignorato dall'host. La SIE (USB Serial Interface) viene posta in uno stato di stand-by e gli interrupt USB vengono completamente ignorati. Se apriamo il file USB_ch9.asm troviamo le istruzioni riportate nel listato 7. La routine acce-
LISTATO 9 ' Programma InitUSB-DeInitUSB ' Controllare completamente il processo di enumerazione ' Corso PIC-USB Elettronica-In '******Dichiarazioni variabili necessarie per uso firmware USB****** wsave VAR BYTE $70 system 'permette di salvare W ssave VAR BYTE bank0 system 'permette di salvare STATUS psave VAR BYTE bank0 system 'permette di salvare PCLATH fsave VAR BYTE bank0 system 'permette di salvare FSR '******Dichiarazioni variabili applicazione DEFINE OSC 24 ' Clock 24Mhz DEFINE SHOW_ENUM_STATUS 1 ' Visualizza lo stato del dispositivo PORTB = 0 TRISB = 0 TRISC = %11000001 PORTC = 0 GoTo INIZIO
' 8 LED uscite digitali spenti ' PORTB definita in uscita ' Salta al main
' Il gestore Interrupt inizia dalla label BUSINT DEFINE INTHAND BUSINT Asm BUSINT
RIPREG
EndAsm INIZIO: ATTESA1: SOSP: ATTESA2:
92
movf movwf movlw movwf btfsc Call clrf movf movwf movf movwf swapf movwf swapf swapf retfie USBInit
FSR, W fsave High ServiceUSBInt PCLATH PIR1, USBIF ServiceUSBInt STATUS fsave, FSR psave, PCLATH ssave, STATUS wsave, wsave,
;salvataggio di FSR
;Richiama la routine firmware che gestisce tutti gli ;Interrupt dei moduli USB del PIC ;Ripristino registri salvati
W W W F W
PORTC = 0
; Torno al pgm principale ' Avvio Enumerazione
if PORTC = %00000001 then goto SOSP goto ATTESA1 ' Ciclo fino a INP3
Il dispositivo entra nello stato "Configurato" al termine del processo di enumerazione.
Il dispositivo viene sospeso regredendo allo stato "Alimentato" nel momento in cui si preme il tasto Inp3.
call DeInitUSB goto ATTESA2
' Ciclo infinito
marzo 2005 - Elettronica In
Corso PIC-USB
LISTATO 8 ; ****************************************************************** ; DeInit USB ; Shuts down the USB peripheral, clears the interrupt enable. ; ****************************************************************** DeInitUSB
Corso PIC-USB
de ad un altro registro fondamentale per il firmware Microchip: UCTRL (USB Control Register). Prima mette a zero il bit 3 chiamato DEV_ATT (Device Attach) per porre le linee dati del bus ad alta impedenza, successivamente valorizza a 1 il bit 1 chiamato SUSPND (Suspend) per mettere il modulo USB del PIC in uno stato di sospensione (in pratica viene completamente isolato). Vengono valorizzati a '00' i bit 0 e 1 del registro USWSTAT. In questo modo il dispositivo passa allo stato "Alimentato". Infine, viene modificato anche il registro PIE1 (Peripheral Interrupt Enable1 Register), precisamente al bit 3 (USBIE: USB Interrupt Enable Bit) per disabilitare tutti i segnali di interrupt relativi al modulo USB. La seconda
(_) prima di tale etichetta come mostrato nel listato 8 e salviamo il file così modificato. Per capire bene il funzionamento speculare di InitUSB e DeinitUSB realizziamo un piccolo programma che permette di controllare il processo di enumerazione e la sospensione attraverso il tasto Inp3. Faremo avviare l'enumerazione, poi porremo il dispositivo in sospensione attraverso il tasto Inp3. Utilizzeremo come descrittore quello del cifrario; dato che non dobbiamo comunicare con il device, Endpoint e Report sono ininfluenti (vedi listato 9). Abbiamo creato un dispositivo che nel momento in cui premiamo Inp3 regredisce allo stato "Alimentato"; ciò è mostrato dai led della demoboard. Dopo l'enumerazione, vengono accesi i
Fig. 2
Prima e... parte della routine viene eseguita solo se, nel nostro sorgente, abbiamo utilizzato la define SHOW_ENUM_STATUS e serve esclusivamente a valorizzare opportunamente la PORTB affinché i led rispecchino lo stato del dispositivo. Tutti i led vengono spenti mantenendo acceso solo il primo, quello relativo allo stato alimentato. Per richiamare la funzione descritta dovremo fare in modo che il nostro compilatore PBP riesca ad accedere alla routine assembler del firmware Microchip. In pratica per utilizzare una call direttamente dal nostro codice Basic dovremo far una piccolissima modifica al file USB_ch9.asm. Alla riga 286 di questo file troviamo la label DeInitUSB che contrassegna l'inizio della routine che ci interessa. Inseriamo un "underscore" Elettronica In - marzo 2005
...dopo la pressione del tasto Inp3 primi 4 led segno che il dispositivo è configurato e quindi pienamente visibile all'host; premendo il tasto Inp3, 3 led si spengono e rimane acceso solo LD1 per cui il dispositivo viene ignorato. Utilizzando il software host del cifrario, potete osservare che, dopo aver premuto il tasto Inp3, il device scompare dalla lista dei dispositivi comportandosi come se fosse fisicamente disconnesso dal bus. La figura 2 permette di apprezzare la situazione prima e dopo la pressione del tasto Inp3 della demoboard. Nella prossima puntata analizzeremo le funzioni avanzate rese disponibili dal firmware Microchip e le possibili modifiche allo stesso. Entreremo, quindi, nell'ultima fase di questo corso quella relativa allo sviluppo lato host. Non perdetevela. 93
Corso PIC-U USB
Corso di programmazione per PIC: l’in nterrfaccia USB Alla scoperta della funzionalità USB implementata nei microcontrollori Microchip. Un argomento di grande attualità, vista la crescente importanza di questo protocollo nella comunicazione tra computer e dispositivi esterni. In questa puntata proseguiamo l’esame di alcune funzioni per la comunicazione, del riconoscimento della presenza di periferiche , ecc. Sesta puntata.
6
a cura di Carlo Tauraso el corso delle precedenti puntate abbiamo visto che le funzioni di base del firmware Microchip vengono interfacciate direttamente dal PICBasic attraverso le istruzioni USBIN, USBOUT, INITUSB. Esistono tuttavia alcune altre funzioni che svolgono operazioni di particolare rilievo e che possono tornare utili nello sviluppo di nuovi dispositivi; per esempio, nella scorsa puntata abbiamo analizzato e utilizzato la
DeInitUSB, che permette un comportamento speculare rispetto alla InitUSB: riutilizzeremo ora quanto già visto per chiarire le funzionalità SoftDetachUSB, RemoteWakeUp, StallUSBEP. SoftDetachUSB Questa routine permette di reinizializzare il processo di enumerazione. In altre parole, fa in >
LISTATO 1 SoftDetachUSB banksel UCTRL bcf UCTRL,DEV_ATT bcf clrf clrf pagesel SoftDetachLoop incfsz goto incfsz goto
STATUS, RP0
; clear attach bit ; bank 2
outer inner SoftDetachLoop
I contatori Outer e Inner vengono usati per creare un ritardo di circa 50ms affinché l'host si accorga della disconnessione.
inner,f SoftDetachLoop outer,f SoftDetachLoop
pagesel InitUSB call InitUSB return
Elettronica In - aprile 2005
Mettendo a zero il flag DEV_ATT si provoca la disconnessione fisica del dispositivo dal bus.
; reinitialize the USB peripheral
Viene riavviato il processo di enumerazione attraverso una call alla ormai nota InitUSB.
81
MODIFICA
FILE
scere il tipo di dispositivo ed inviare un'istruzione di SetConfiguration (vedi riferimento nella prima puntata) affinché il dispositivo funzionasse in un determinato modo. Attraverso la funzione SoftDetachUSB tutto questo non è necessario, perché possiamo spostare il controllo verso il PIC; in altre parole è quest'ultimo a decidere quale dispositivo deve essere, cambiando "faccia" nel momento in cui farà riavviare esplicitamente il processo di enumerazione. Ma come funziona tutto ciò? Per capirlo dobbiamo riferirci al solito file USB_ch9.asm, all'interno del quale troviamo il listato della routine; proviamo ad analizzarlo (vedi Listato 1). Ritroviamo il registro UCTRL(USB Control Register) che avevamo visto nella scorsa puntata a proposito della DeInitUSB. In questo caso si utilizza solo il flag DEV_ATT: valorizzandolo a zero, si fa in modo che le linee dati D+/D- siano forzate in uno stato ad alta impedenza, così come
USB- ch9.asm
; ********************************************************************* ; USB Soft Detach ; ********************************************************************* SoftDetachUSB DIVENTA ; ********************************************************************* ; USB Soft Detach ; ********************************************************************* _SoftDetachUSB
Il listato della nostra routine di prova è:
LISTATO 2 ' Programma SoftDetach ' Utillizzo della funzione SoftDetach ' Corso PIC-USB Elettronica-In '******Dichiarazioni variabili necessarie per uso firmware USB****** wsave ssave psave fsave
VAR VAR VAR VAR
BYTE BYTE BYTE BYTE
$70 system 'permette di salvare W bank0 system 'permette di salvare STATUS bank0 system 'permette di salvare PCLATH bank0 system 'permette di salvare FSR
'******Dichiarazioni variabili applicazione DEFINE OSC 24 ' Clock 24Mhz DEFINE SHOW_ENUM_STATUS 1 ' Visualizza lo stato del dispositivo PORTB = 0 TRISB = 0 TRISC = %11000001 PORTC = 0 GoTo INIZIO
' 8 LED uscite digitali spenti ' PORTB definita in uscita ' PORTC definizione IN/OUT ' PORTC reset ' Salta al main
RC0, RC6, RC7 in input sono collegati rispettivamente a Inp3, Inp4 Inp5. Il resto in output RC1 e RC2 comandano le uscite PWM1 e PWM2.
' Il gestore Interrupt inizia dalla label BUSINT DEFINE INTHAND BUSINT Asm BUSINT movf FSR, W ;salvataggio di FSR movwf fsave movlw High ServiceUSBInt movwf PCLATH
82
aprile 2005 - Elettronica In
Corso PIC-U USB
modo che il dispositivo venga disconnesso e riconnesso al bus in maniera tale che l'host faccia ripartire il processo di enumerazione, così come farebbe per l'inserimento di un nuovo device. L'operazione diventa particolarmente interessante perché rende possibile modificare "al volo" il funzionamento di un dispositivo, senza materialmente disconnetterlo. Naturalmente questa funzionalità è utile nel momento in cui creiamo dei dispositivi con due descrittori Configuration. Ad esempio, rende possibile creare un device che contiene due configurazioni: una per Mouse ed una per Tastiera. Il device sceglierebbe di essere visto dall'host nell'uno o nell'altro modo a seconda del traffico che dovrebbe svolgere. Un caso analogo è stato descritto in un Technical Briefing (TB058) apparso sul sito della Microchip (www.microchip.com). In un approccio standard avremmo dovuto creare un device driver lato host in grado di ricono-
Corso PIC-U USB
(Continuazione del listato 2)
RIPREG
EndAsm
btfsc PIR1, USBIF ;Se non c'e' nessun interrupt da gestire vado a RIPREG Call ServiceUSBInt ;Richiama la routine firmware che gestisce tutti gli ;Interrupt dei moduli USB del PIC ;Ripristino registri salvati clrf STATUS movf fsave, W movwf FSR movf psave, W movwf PCLATH swapf ssave, W movwf STATUS Enumerazione appena il device viene swapf wsave, F collegato. swapf wsave, W retfie ; Torno al pgm principale
INIZIO:
USBInit ' Processo di enumerazione iniziale PORTC = 0 ' Reset PORTC ATTESA1: if PORTC = %00000001 then goto SOFTDET goto ATTESA1 ' Cicla fino a INP3 SOFTDET: call SoftDetachUSB ATTESA2: PORTC = 0 ' Reset PORTC goto ATTESA1 ' Torna a controllare se si preme INP3
avviene nel momento in cui disconnettiamo fisicamente il dispositivo. Successivamente si avvia un ciclo (SoftDetachLoop) nel quale, attraverso due contatori (inner e outer) si realizza un ritardo di circa 50ms, necessario affinché l'host si accorga che il dispositivo è stato disconnesso. Al termine si fa una call alla InitUSB, che riavvia il processo di enumerazione. Per rendere la cosa più interessante, realizziamo un piccolo programma che permette di riavviare il processo di enumerazione attraverso la pressione del solito tasto Inp3. Questa volta utilizziamo un descrittore generico (firmdsc.asm) con un solo byte di input ed uno di output; inoltre eliminiamo il secondo linguaggio mantenendo come langID quello inglese, ma inserendo le descrizioni in Italiano: così facendo, il driver Microsoft crede di parlare in Inglese e non si accorge che sta parlando in Italiano. Useremo nuovamente questa struttura per tutti gli esempi che faremo sulle funzionalità avanzate. Ritenendo quest'ultima sufficientemente chiara, anche sulla base delle analisi fatte nelle precedenti puntate, possiamo passare a presentare il listato PicBasic, nel quale, lo vedete, abbiamo voluto utilizzare una call diretta, ragion per cui è necessario effettuare la solita modifica al file USB_ch9.asm, anteponendo alla label SoftDetachUSB il carattere di underscore, come si vede nello schema della pagina precedente (Modifica file). Il codice è piuttosto semplice e ricorda la prova che abbiamo fatto con la routine Elettronica In - aprile 2005
Ciclo che permette di rilevare se il tasto INP3 è stato premuto
Il device viene disconnesso e si forza il processo di enumerazione. Ritornando al controllo di INP3.
DeInitUSB. La differenza sostanziale è che in questo caso forziamo il processo di enumerazione ogni volta che premiamo il tasto Inp3, mentre nel precedente portavamo il device in uno stato di sospensione, rendendolo invisibile all'host. Alla pressione del tasto Inp3 il dispositivo ripassa nei vari stati che contraddistinguono l'enumerazione: Alimentato, Default, Indirizzato, Configurato. Contemporaneamente, sul PC il cursore diventerà una clessidra, segno che il sistema operativo sta nuovamente identificando il dispositivo. Il processo è piuttosto rapido, per cui non si possono apprezzare cambiamenti nei led di stato della demoboard. Grazie a SoftDetach, InitUSB, DeInitUSB, possiamo controllare con una certa precisione il processo di enumerazione fondamentale per un corretto funzionamento di qualsiasi dispositivo con interfaccia USB. Nel caso si voglia creare un di- >
Fig. 1
83
KYBD_ATT, che stabilisce se caricare la configurazione per la tastiera o quella per il mouse. Il flusso si può rappresentare come in Fig. 1. Apriamo il file usb_ch9.asm e vediamo come cambia essenzialmente la routine di
LISTATO 3 Get_Config_Descriptor ; starts in bank2 pagesel Descriptions ; set up PCLATH for call below movlw GET_DESCRIPTOR movwf USB_dev_req ; currently processing a get descriptor request banksel movlw btfsc movlw banksel movwf banksel movlw btfsc movlw banksel movwf
TYPE low Config1 KYBD_ATT low Config11 EP0_start EP0_start TYPE high Config1 KYBD_ATT high Config11 EP0_start EP0_start+1
movlw addwf btfsc incf call
2 EP0_start,f STATUS,C EP0_start+1,f Descriptions
Nel file contenente i descrittori sono stati predisposti due descrittori CONFIGURATION (uno tastiera e uno mouse) che iniziano rispettivamente con le etichette Config1 e Config11. A seconda del valore del flag KYBD_ATT viene caricato come indirizzo iniziale (2byte) quello dell'etichetta Config1 o Config11. ; bump pointer by 2 to get the complete descriptor ; length, not just config descriptor ; get length of the config descriptor
modificare sia Get_Device_Descriptor che Get_Report_Descriptor. Tutte queste routine hanno come funzione principale quella di calcolare l'indirizzo iniziale di ciascun descrittore. Successivamente viene richiamata la procedura Descriptions, che ritroviamo in ognuno dei descrittori che abbiamo costruito per inviare le informazioni relative all'host. Pertanto, in ognuna di queste routine dovremo aggiungere le istruzioni che ci permetteranno di scegliere quale descrittore caricare, a seconda delle varie configurazioni predisposte. Ad esempio, nel Technical Briefing n. 58 di Microchip viene definito un flag
Get_Config_Descriptor (vedi Listato 3). Nella routine si utilizza l'istruzione btfsc KYBD_ATT (Bit Test f, skip if clear) che, qualora il flag sia zero, salta l'istruzione successiva. In questo modo EP0_start conterrà il valore dell'indirizzo iniziale da cui caricare il descrittore configuration; più esattamente, caricherà quello che parte dalla label Config1 se KYBD_ATT=0, o quello che parte dalla label Config11 se KYBD=1. Analogamente, vanno modificate anche le routine che caricano i descrittori Report e Device. Per rendere le cose più semplici, a partire dalla versione 1.25 del firmware sono state introdotte
LISTATO 4 CDI_start retlw low Config1 retlw high Config1 ;********************************************* ;PUNTO DI INSERIMENTO ULTERIORI CONFIGURAZIONI ;********************************************* ; retlw low Config2 ; retlw high Config2 ; ecc.... ………………….. RDI_start retlw low ReportDescriptorLen retlw high ReportDescriptorLen ;********************************************* ;PUNTO DI INSERIMENTO ULTERIORI REPORT ;********************************************* ; retlw low ReportDescriptorLen2 ; retlw high ReportDescriptorLen2 ; etc.... ……………………….
84
aprile 2005 - Elettronica In
Corso PIC-U USB
spositivo con più configurazioni, è necessario predisporre gli appositi descrittori e riscrivere alcune parti del file usb_ch9.asm. Come anticipato, è infatti indispensabile dare un significato alla routine Get_Configuration_Descriptor e
Corso PIC-U USB
le funzioni Config_descr_index e Report_descr_index. Quindi, per aggiungere una configurazione è sufficiente inserire, nei punti d'ingresso evidenziati del descrittore, la label iniziale (vedi Listato 4). Il sistema è stato derivato da quello relativo ai descrittori string in cui ogni descrizione è contraddistinta da un'indice numerico. Pertanto, per i dispositivi "multifunzione" sarà sufficiente inserire indici differenti per i ciascun device: il firmware caricherà le descrizioni corrette non appena verrà scelta la configurazione voluta. Chi volesse approfondire l'argomento potrà scaricare i file sorgente contenuti nell'archivio TB058.zip disponibile nel sito Microchip. Adesso, passiamo, invece, ad un'altra funzione. CheckSleep Nel caso in cui sul bus venga rilevata assenza di traffico per un periodo di almeno 3ms, il PIC valorizza un flag nel registro UIR(USB Interrupt Flags Register) chiamato UIDLE (bit5). Si può
UIR posizionato sul bit2). Durante questa fase, l’assorbimento del dispositivo passa da un massimo di 20 mA (normale esercizio, ossia presenza di traffico) a 140µA (low power mode). Ma vediamo più da vicino il corrispondente listato (Listato 5): è molto lineare e gira tutto attorno al significato di due flag e dei relativi segnali di interrupt. Il primo è UIDLE, che permette di stabilire se il bus si trova in uno stato di "idle" e se, quindi, possiamo passare al low power mode. L'altro è ACTIVITY, che, invece, rileva qualsiasi traffico presente sulle linee dati. Nella routine iniziale si verifica lo stato di "idle": se è presente, viene disattivato UIDLE e attivato ACTIVITY, e il dispositivo viene messo in SLEEP. Non appena si rileva attività sul bus, si fa il percorso inverso, ossia si disattiva ACTIVITY e si attiva UIDLE per rilevare il prossimo stato di "idle". Infine, il passaggio allo stato di SLEEP viene segnalato attraverso il bit SUSPND del registro UCTRL (USB Control Register). Si noti che, per poter utilizzare la call direttamente da PicBasic, nel file USB_ch9.asm ante-
LISTATO 5 CheckSleep banksel IS_IDLE btfss IS_IDLE,0 return ifdef
endif
ifdef endif
SHOW_ENUM_STATUS banksel PORTB bsf PORTB,4 banksel UIR
; test the bus idle bit
Il flag UIDLE è rimappato sulla variabile IS_IDLE definita in pbpusb14.ram appartenente alle librerie del compilatore. Nel caso il bit=0 l'esecuzione torna immediatamente al programma principale.
Viene attivato l'interrupt ACTIVITY che ; turn on LED 4 to indicate we've gone to rileva sleep il traffico sulle linee dati attraverso il registro UIE (USB Interrupt Enable Register) e azzerato il flag relativo nel ; point to bank 3 registro UIR.
bsf STATUS,RP0 bcf UIR,ACTIVITY bsf UIE,ACTIVITY ; enable the USB activity interrupt bsf UCTRL,SUSPND ; put USB regulator and transciever in low power state sleep ; and go to sleep Il sistema entra in low power mode; si nop noti che nel caso sia stata inserita la defibcf UCTRL,SUSPND ne SHOW_ENUM_STATUS il led5 della bcf UIR,UIDLE demoboard (corrispondente al pin4 bsf UIE,UIDLE PORTB) viene acceso. bcf UIR,ACTIVITY bcf UIE,ACTIVITY SHOW_ENUM_STATUS Non appena viene rilevata attività sul bus banksel PORTB si ritorna dallo stato di sleep, facendo bcf PORTB,4 ; turn off LED 4 to indicate we're back. esattamente le operazioni opposte, cioè disattivando l'interrupt ACTIVITY return ed attivando UIDLE per rilevare la prossima mancanza di traffico. Inoltre il led4 viene spento.
quindi trasformare lo stato del dispositivo in "Sospeso", attivando una modalità a risparmio energetico: il cosiddetto low power mode. Tutte le operazioni sul bus vengono sospese, finchè non si rileva una qualsiasi attività sulle linee dati D+/D-, attività che corrisponde ad un segnale di interrupt di tipo Activity (analogo flag del Elettronica In - aprile 2005
poniamo sempre alla label CheckSleep il carattere di underscore. Notate altresì che il dispositivo entra in stato di sleep esclusivamente se il controllo del flag UIDLE va a buon fine, altrimenti l'esecuzione della procedura termina passando all'istruzione immediatamente successiva alla call. > 85
Questa routine presente nel firmware Microchip permette di informare l'host che, dopo uno stato di sospensione, il dispositivo è di nuovo pronto per comunicare. In pratica, nell'entrare in uno stato di sospensione il dispositivo chiede all'host di bloccare tutto il traffico sul bus, inclusi i segnali SOF; successivamente, ad esempio al verificarsi di un preciso evento, il device rientra dallo stato di sospensione e avverte l'host che il traffico può ricominciare. La capacità di un dispositivo di gestire il segnale di "resume" deve essere autorizzata dall'host secondo le specifiche USB1.1. e rappresenta una caratteristica opzionale che deve essere pienamente controllabile
LISTATO 6 RemoteWakeup Banksel USB_status_device; btfss USB_status_device, 1 return bsf bcf bsf bcf bcf bcf bsf bcf clrf movlw movwf pagesel RemoteLoop decfsz goto decfsz goto bsf bcf return
STATUS, RP0 ; BANK 3 UCTRL, SUSPND UIE,UIDLE UIR,UIDLE UIE,ACTIVITY UIR,ACTIVITY UCTRL, 2 ; RESUME SIGNALING STATUS, RP0 ; BANK 2 inner 0x80 outer RemoteLoop inner, f RemoteLoop outer, f RemoteLoop STATUS, RP0 ; BANK 3 UCTRL, 2 ; Clear Resume bit
dall'host, tant'è che se un dispositivo supporta il remote wakeup deve anche supportare la possibilità di abilitare o disabilitare tale capacità attraverso delle richieste USB standard come set_feature e clear_feature. In particolare, il remote wakeup deve essere predisposto attraverso il descrittore Configuration. Nel campo bmAttributes di quest'ultimo, al bit5 abbiamo un flag che stabilisce se il device supporta il remote wakeup (bit5=1) oppure no (bit5=0). In tutti i descrittori che abbiamo costruito avevamo già abilitato questa feature, per utilizzare la quale bisogna effettuare la solita modifica nel file USB_ch9.asm, anteponendo all'etichetta RemoteWakeUp il carattere underscore. Ma vediamo nel Listato 6 come si presenta il codice di questa funzione. Anche in questo 86
caso il codice è piuttosto chiaro. Nella prima parte viene verificato il flag per stabilire se il dispositivo è abilitato all'operazione. Poi c'è una sezione, praticamente identica a quella finale della CheckSleep, dove viene abilitato l'UIDL e disabilitato il segnale di ACTIVITY; in questo modo, quando sul bus non si rileva traffico per più di 3ms, viene generato un nuovo interrupt. Infine c'è il ciclo per l'invio del segnale di "resume", attraverso il quale il dispositivo informa l'host che è nuovamente pronto a comunicare. Secondo le specifiche USB 1.1, un dispositivo abilitato al remote wakeup non può inviare tale segnale se prima non c'è stata un'assenza di traffico per almeno 5ms. Questo perché in tal modo si dà il tempo all'Hub Viene verificato l'USB_status_device, una variabile che viene valorizzata quando si carica il descrittore configuration. Contiene lo stesso valore del bit5 del campo bmAttributes, quindi 1 se il device è abilitato al Remote Wakeup. Nel caso sia 0 il controllo ripassa al programma principale. Viene abilitato il segnale di UIDLE per rilevare il prossimo stato di "idle" del bus mentre si disattiva il segnale di ACTIVITY.
Se il bit2 del registro UCTRL viene posto a 1 si abilita il modulo USB ad inviare il segnale di "resume". Secondo le specifiche USB, esso deve durare tra 1ms e 15ms. Ecco perché nelle successive istruzioni vengono utilizzati i 2 soliti contatori inner e outer per attendere il tempo necessario.
Dopo il RemoteLoop necessario a far sì che il segnale di "resume" sia presente sul bus per un intervallo di tempo sufficiente, il bit2 viene riportato a 0 per continuare con le normali operazioni.
di entrare in uno stato di sospensione e di prepararsi al processo di propagazione del segnale di resume. Ciò perché ogni hub che si trova in stato di sospensione funziona da ripetitore e non appena riceve tale segnale lo propaga sia alla porta di upstream che a tutte quelle di downstream; così facendo, il segnale viene ripetuto verso tutti gli hub della rete fino ad arrivare all'host, o ad un hub non sospeso che prende il controllo di tale segnale: questo processo viene detto rebroadcast. Nel nostro caso la procedura risulta relativamente più semplice, in quanto abbiamo supposto di lavorare in un sistema con due soli livelli escludendo la presenza di hub intermedi, pertanto il segnale di "resume" viene preso in carico immediatamente dall'host attraverso il RootHub. aprile 2005 - Elettronica In
Corso PIC-U USB
RemoteWakeUp
Corso PIC-U USB
LISTATO 7 STALLO:
@ movlw 0x01 call StallUSBEP ' Comunicazione disattivata Pause 2000 ' Attesa @ movlw 0x01 call UnstallUSBEP ' Comunicazione riattivata
Vediamo di analizzare anche in questo caso il listato delle due funzioni:
LISTATO 8 StallUSBEP bsf andlw addlw movwf bsf return
STATUS,IRP 0x03 low UEP0 FSR INDF,EP_STALL
; select banks 2/3
Indirizzamento del flag di stallo aggiungendo all'indirizzo base UEP0 il valore degli ultimi 2 bit passati nel registro w.
; set stall bit
StallUSBEP/UnstallUSBEP A volte può essere necessario bloccare la comunicazione sugli endpoint fino al verificarsi di un determinato evento. Si pensi ad esempio al momento in cui si rileva che nella stampante non c'è più carta e si deve attendere fino a che non si è concluso l'intervento da parte dell'utente. Ebbene, attraverso StallUSBEP si informa l'host che la comunicazione su un determinato endpoint viene bloccata, mentre con UnstallUSBEP si riapre la comunicazione. Il numero relativo all'endpoint viene passato alla funzione tramite il registro W. La funzionalità viene sviluppata attraverso un flag chiamato EP_STALL appartenente al registro UEPn (USB Endpoint Control Register). Quando viene valorizzato a 1 il relativo Endpoint viene bloccato (si porta in una posi-
memoria RAM del PIC. In pratica, accedendo a INDF possiamo accedere alla locazione il cui indirizzo è contenuto in FSR (leggendo INDF quando FSR=0Ch si legge il valore contenuto nella cella 0Ch della RAM). Quindi nella routine, a seconda del valore passato in W, viene calcolata la locazione di memoria che controlla il relativo endpoint. Nella memoria del PIC esistono tre SFR(Special Function Register) chiamati UEP0 (198h), UEP1 (199h), UEP2 (19Ah) che contengono i flag di controllo per ciascun endpoint secondo la struttura di Figura 2. Focalizziamo la nostra attenzione sul bit0. Nella routine viene operato un AND logico tra W e il valore 3, così da estrarre gli ultimi 2 bit del registro (non avrebbe scopo leggere gli altri visto che gli endpoint disponibili sono 0,1,2). Successivamente viene aggiunto l'indirizzo della
bit7-4: Riservati e letti a 0. bit3-1: Stabiliscono se un endpoint è abilitato, oltre alla sua direzione. bit0: E’ il bit di stallo che blocca o sblocca la comunicazione sull'endpoint. zione di stallo). Per utilizzare questa funzione possiamo fare la solita modifica dell'underscore, avendo l'accortezza di valorizzare il registro W prima di fare la call alla funzione. Ad esempio, se dobbiamo bloccare la comunicazione sull'Endpoint1 useremo le istruzioni dei Listati 7 e 8. Per capire come funziona bisogna tenere presente che INDF e FSR ci permettono di realizzare l'indirizzamento indiretto all'interno della Elettronica In - aprile 2005
Fig. 2
locazione UEP0, in modo da posizionarci esattamente sul registro che vogliamo modificare. Supponendo che W sia 1, la routine calcola 198+1=199: esattamente la locazione contenente il registro UEP1. Valorizzato FSR attraverso un bsf (Bit Set f) su INDF viene posto a 1 il flag EP_STALL, che corrisponde al bit0 del registro. Così si blocca la comunicazione sul Endpoint1. Se andiamo a vedere come è fatta la > 87
UnstallUSBEP bsf andlw addlw movwf bcf return
STATUS,IRP 0x03 low UEP0 FSR INDF,EP_STALL
; select banks 2/3
Indirizzamento del flag di stallo aggiungendo all'indirizzo base UEP0 il valore degli ultimi 2 bit passati nel registro w.
; clear stall bit
UnstallUSBEP vedremo un comportamento speculare come nel Listato 9. Infatti si accede al bit di stallo valorizzando FSR, azzerandolo invece di porlo a 1. La comunicazione sul relativo endpoint può quindi riprendere. Si tenga ben presente la struttura degli UEPn (USB Endpoint Control Register) perché la approfondiremo nel prossimo paragrafo. Firmware Microchip: ottimizzazioni e modifiche Ottimizzare spazio Tutte le funzioni che abbiamo visto fino ad ora sono senz'altro utili e ci permettono di realizzare programmi capaci di soddisfare buona parte delle nostre esigenze; le routine possono comunque essere "alleggerite", ad esempio per cambiare il funzionamento dei dispositivi nel caso si debba-
mente cancellato per rendere il programma più breve e risparmiare nell'utilizzo delle limitate risorse del PIC. Ad esempio, lo stato del dispositivo viene segnalato attraverso la PORTB; tale funzionalità, sicuramente utile in fase di testing ma non necessaria in un prodotto finito, può essere rimossa semplicemente eliminando la define SHOW_ENUM_STATUS che abbiamo inserito nel programma PicBasic (risparmio 209 byte). Ancora, il firmware prevede una define COUNTERERRORS per il conteggio delle varie tipologie di errore attraverso i segnali di interrupt gestiti dal registro UEIE (USB Error Interrupt Enable Register); rinunciando a tale definizione risparmieremo un po' di codice (315 byte) con 14 byte di RAM che servono per i 7 contatori da 16 bit. Infine, in tutti i casi in cui una determinata funzione non viene utilizzata è bene eliminarne il relativo codice. Prendiamo ad esempio il nostro primo esperimento: il TermoUSB; in esso abbia-
La complilazione prima e dopo i tagli:
Fig. 3
Fig. 4
no realizzare particolari progetti. Nella maggior parte dei casi all'interno di un sistema non vengono utilizzate tutte le funzioni del firmware, pertanto il relativo codice può essere tranquilla88
mo utilizzato soltanto l'endpoint1 IN, quindi possiamo tranquillamente eliminare la PutEP2, la GetEP1, e la GetEP2 dal file usb_defs.inc, commentando le relative istanze nel file aprile 2005 - Elettronica In
Corso PIC-U USB
LISTATO 9
Corso PIC-U USB
usb_ch9.asm. Le Fig. 3 e 4 mostrano la compilazione prima e dopo i tagli operati: il compilato è 1153 byte più piccolo. Se poi eliminiamo anche tutte le funzionalità avanzate che non ci interessano (CheckSleep, RemoteWakeUp ecc.) si arriva a risparmiare altri 400 byte circa. Usando IC-PROG, caricando i due hex rispetti-
Fig. 5
vamente sul buffer 1 e 2 è possibile metterli a confronto: appare chiaro come ci sia un discreto risparmio di locazioni nella pagina 3 (1800h 1FFFh); infatti il primo hex occupa le locazioni dall'indirizzo 1800h al 1EB7h, mentre il secondo va dal 1800h al 1DA2h, con una differenza di 277 words. Si consideri che il compilatore segnala un'occupazione di 142 words, riferendosi esclusivamente alla sezione di codice iniziale che va in entrambe i casi da 00h a 8Dh terminando nella pagina 0. Per maggior chiarezza si veda lo schema. di Fig. 5, che riassume l'organizzazione della "program memory" nel PIC16C745. Quanto detto va tenuto ben presente quando si devono creare progetti complessi, che richiedono la maggior quantità possibile di locazioni libere di memoria. Modificare la configurazione degli endpoint In tutti i nostri progetti abbiamo utilizzato la configurazione di default degli endpoints, agendo soltanto sui descrittori per informare l'host sul modo con cui intendevamo comunicare. Si presti però attenzione al fatto che, per modificare il modo in cui il PIC fisicamente invia e riceve i dati, non basta manipolare i descrittori. Nel precedente paragrafo abbiamo visto che esiste una >
T ERMOUSB . HEX Vers. 1 sezione 1DA0h - 1EB7h 1DA0: 1DA8: 1DB0: 1DB8: 1DC0: 1DC8: 1DD0: 1DD8: 1DE0: 1DE8: 1DF0: 1DF8: 1E00: 1E08: 1E10: 1E18: 1E20: 1E28: 1E30: 1E38: 1E40: 1E48: 1E50: 1E58: 1E60: 1E68: 1E70: 1E78: 1E80: 1E88: 1E90: 1E98: 1EA0: 1EA8: 1EB0:
23B3 0852 0822 1683 1703 00AD 00B1 00B4 3A03 0824 0084 1683 3E2B 1703 0245 0AC0 1683 0008 1903 1903 1903 3A02 3A06 3A0A 1283 1903 3E01 3A02 0D24 0823 0826 3006 2014 00C4 2607
0008 2CD3 1683 0826 0084 1283 1783 ³.Óƒ&"ƒƒ 0080 1683 3001 00A5 30C8 00A4 0008 R€ƒ.¥È¤. 3C01 1C03 2CD3 0822 00D2 1903 2DBE "..Ó"Ò.¾ 3003 0097 1283 1303 1586 23B3 1683 ƒ.-ƒ.†³ƒ 30C8 00AA 3008 00A9 3088 00A8 3008 .Ȫ.©ˆ¨. 30D0 00AE 3048 00AC 30D8 00B2 3008 ЮHز. 3088 00B0 3008 00B5 30D8 00B6 3048 ±ˆ°.µØH 300E 0099 300E 009A 0008 1683 0817 ´.™.š.ƒ. 1D03 2CD3 1283 0824 3C00 1C03 2CD3 ..Óƒ$..Ó 3E2B 0084 1783 0800 00F1 1683 0826 $+"ƒ.ñƒ& 0871 0080 3001 00A5 30C8 00A4 0008 "q€.¥È¤. 0817 1283 3903 3A03 1D03 2CD3 0824 ƒ.ƒ...Ó$ 0084 1783 0822 0080 23B3 0008 1683 +"ƒ"€³.ƒ 1783 0826 0084 1283 1703 01C0 0840 .ƒ&"ƒ.À@ 1903 2E1F 0BC4 2E16 2E1E 2014 0080 E..Ä...€ 0A84 0FAE 2E0F 0AAF 2E0F 01D3 0840 À"®.¯.Ó@ 00A5 3040 0624 3940 3888 00A4 0008 ƒ¥@$@ˆ¤. 0008 0820 3A21 1903 2E43 0820 3A22 ...!.C." 2EB2 0820 3A23 1903 2EB2 0820 3AA1 .².#.².¡ 2EB2 0820 3AA2 1903 2EB2 0820 3AA3 .².¢.².£ 2EB2 2CD3 0821 3A01 1903 2EB2 0821 .²Ó!..²! 1903 2EB2 0821 3A03 1903 2EB2 0821 ..²!..²! 1903 2E60 0821 3A09 1903 2EB3 0821 ..`!..³! 1903 2EB2 0821 3A0B 1903 2EB2 2CD3 ..²!..²Ó 1703 3006 00D3 3008 00C5 0823 3A01 ƒ..Ó.Å#. 2E77 1003 0D24 200A 00AE 1003 0D24 .w.$.®.$ 200A 00AF 2014 00C4 0AAE 2E8D 0823 ..¯.Ä®€# 1903 2E88 1003 0D24 200A 00AE 1003 ..ˆ.$.®. 3E01 200A 00AF 2014 00C4 0AAE 2E8D $..¯.Ä®€ 3A03 1903 2CD3 0008 0827 1D03 2E95 #..Ó.'.o 0244 0826 1803 00C4 0AC4 2607 0008 &D&.ÄÄ.. 00D3 3008 00C5 303E 00AE 3018 00AF .Ó.Å>®.¯ 00C4 08A7 1D03 2EA9 0226 0826 1C03 .ħ.©&&. 0AC4 2607 0008 0008 0853 3A06 1903 ÄÄ...S.. 0008 2CD3 3021 00D3 1683 1703 0008 ..Ó!Óƒ..
Elettronica In - aprile 2005
T ERMOUSB . HEX Vers. 2 sezione 1DA0h - 1EB7h 1DA0: 1DA8: 1DB0: 1DB8: 1DC0: 1DC8: 1DD0: 1DD8: 1DE0: 1DE8: 1DF0: 1DF8: 1E00: 1E08: 1E10: 1E18: 1E20: 1E28: 1E30: 1E38: 1E40: 1E48: 1E50: 1E58: 1E60: 1E68: 1E70: 1E78: 1E80: 1E88: 1E90: 1E98: 1EA0: 1EA8: 1EB0:
1683 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF
1703 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF
0008 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF
3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF
3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF
3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF
3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF
3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF 3FFF
ƒ..ÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿ
89
struttura a registri specifica che controlla l'abilitazione e la direzione degli endpoint. Se andiamo a vedere come si compongono i registri UEP0, UEP1, UEP2 scopriamo che EP_CTL_DIS (bit 3), EP_OUT_EN (bit2), EP_IN_EN (bit1) sono riservati proprio a tale scopo; la Tabella 1 illustra la valorizzazione di questi 3 bit. Nel momento in cui utilizziamo il firmware Microchip, viene definita una configurazione di default che prevede EP0 abilitato IN/OUT e pacchetti di controllo, EP1 abilitato IN/OUT ed EP2 abilitato IN/OUT. Ricordiamo che, per definizione, i dispositivi low-speed utilizzano esclusivamente EP0 per veicolare informazioni di controllo. Come avevamo già visto, vengono riservati
Set_Configuration per gli altri. Entrambe si trovano nel file USB_ch9.asm, pertanto se volessimo modificare tale configurazione dovremmo cambiare la lista di istruzioni linea 1661 di usb_ch9 (vedi Listato 10). Per ogni Endpoint vengono valorizzati i relativi BC (Byte Counter), AL(Address Low) e ST(Descriptor Status); al termine si modificano i registri UEPn. Nei nostri precedenti esperimenti non abbiamo avuto alcuna necessità di modificare tale struttura perché risultava più che sufficiente; ci siamo limitati a precisare dei descrittori che formalmente definivano la sezione di comunicazione che avremmo utilizzato. Ben consci che avremmo utilizzato una configurazione fisica pre-
L I S T A T O 10 banksel BD1OAL movlw USB_Buffer+0x10 ; Endpoint 1 OUT gets a buffer movwf BD1OAL ; set up buffer address movlw 8 movwf BD1OBC ; set byte count movlw 0x88 ; set own bit of EP1 (SIE can write) movwf BD1OST movlw 8 movwf BD1IBC ; set byte count movlw USB_Buffer+0x18 ; Endpoint 1 IN gets a buffer movwf BD1IAL ; set up buffer address movlw 0x48 ; set own bit of EP1 (PIC can write) movwf BD1IST movlw USB_Buffer+0x20 ; Endpoint 2 OUT gets a buffer movwf BD2OAL ; set up buffer address movlw 8 movwf BD2OBC ; set byte count movlw 0x88 ; set own bit of EP2 (SIE can write) movwf BD2OST movlw 8 movwf BD2IBC ; set byte count movlw USB_Buffer+0x20 ; EP1 In and EP2 In share a buffer movwf BD2IAL ; set up buffer address movlw 0x48 ; set own bit of EP2 (PIC can write) movwf BD2IST ; ENDPT_CONTROL - Supports IN, OUT and CONTROL transactions - Only use ; ENDPT_NON_CONTROL - Supports both IN and OUT transactions movlw ENDPT_NON_CONTROL movwf UEP1 ; enable EP's 1 and 2 for In and Outs... movlw ENDPT_NON_CONTROL movwf UEP2
degli appositi buffer attraverso la struttura BDT (Buffer Descriptor Table) e, a causa del numero di byte disponibili un buffer, viene condiviso tra EP2 IN e EP2 OUT. Tutto ciò viene stabilito nella routine USB_Reset per EP0 e 90
BD1O = Buffer Descriptor Endpoint 1 OUT Vengono precisati indirizzo del buffer (AL), lunghezza in byte (BC), status (ST) valorizzando opportunamente il bit owner.
BD1I = Buffer Descriptor Endpoint 1 IN Vengono precisati indirizzo del buffer (AL), lunghezza in byte (BC), status (ST) valorizzando opportunamente il bit owner.
BD2O = Buffer Descriptor Endpoint 2 OUT Vengono precisati indirizzo del buffer (AL), lunghezza in byte (BC), status (ST) valorizzando opportunamente il bit owner.
BD2I = Buffer Descriptor Endpoint 2 IN Vengono precisati indirizzo del buffer (AL), lunghezza in byte (BC), status (ST) valorizzando opportunamente il bit owner. with EP0 EP1 e EP2 vengono configurati in maniera da essere abilitatati sia IN che OUT. EP2 però condivide il buffer tra IN e OUT. EP0 invece viene definito nella routine USB_Reset
stabilita. In generale, possiamo dire che una simile struttura riesce a soddisfare buona parte delle esigenze perché permette di abilitare comunicazioni sia unidirezionali che bidirezionali. Nello specifico, se dobbiamo realizzare una aprile 2005 - Elettronica In
Corso PIC-U USB
Tabella 1
Corso PIC-U USB
comunicazione diretta da Device a Host possiamo tranquillamente utilizzare EP1IN e EP2IN, raggiungendo un data rate di 1600 byte/sec, diciamo, più che sufficiente. Ma se, per qualche motivo, dobbiamo abilitare EP2 IN e EP2 OUT (unica configurazione impossibile vista la condivisione del buffer) ora sappiamo dove andare ad inserire le nostre istruzioni. Se poi abbiamo la necessità di aumentare ancora il data rate, possiamo sfruttare due funzioni che sono ridefinibili dall'utente e che vedremo nel prossimo paragrafo. Personalizzare HIDGetReport e HIDSetReport Per i dispositivi HID il firmware mette a disposizione due funzioni ridefinibili dall'utente, che permettono di veicolare dati anche attraverso EP0; così facendo si può "astutamente" aumentare il data rate, visto che in un trasferimento interrupt il limite massimo di polling su EP1 e EP2 è 10ms (lo si stabilisce nel descrittore ENDPOINT secondo quanto stabilito dalle specifiche USB1.1) mentre EP0 lavora con interrogazioni
L I S T A T O 11 STARTUP
code pagesel Main goto Main nop InterruptServiceVector movwf W_save ; save W movf STATUS,W clrf STATUS ; force to page 0 movwf Status_save ; save STATUS movf PCLATH,w movwf PCLATH_save ; save PCLATH movf FSR,w movwf FSR_save ; save FSR pagesel TMR0TEST Punti di inserimento istruzioni ; ************************** ; Interrupt Service Routine di gestione dei diversi tipi di ; ************************** interrupt: TMR0, RB0, PORTB Process_ISR Change TMR0TEST btfsc INTCON,T0IE btfss INTCON,T0IF goto INTTEST nop INTTEST btfsc INTCON,INTE btfss INTCON,INTF goto RBTEST nop RBTEST btfsc INTCON,RBIE btfss INTCON,RBIF goto PERIPHERALTEST nop PERIPHERALTEST btfss INTCON,PEIE ; peripheral interrupt? goto EndISR ; all done.... TEST_PIR1 banksel PIR1 movf PIR1,w banksel PIE1 andwf PIE1,w ; mask the enables with the flags banksel PIRmasked movwf PIRmasked pagesel ServiceUSBInt btfsc PIRmasked,USBIF ; USB interrupt flag call ServiceUSBInt ; Service USB interrupt
Elettronica In - aprile 2005
cadenzate a 1ms. Pertanto, abbiamo la possibilità di moltiplicare per 10 volte in ciascuna direzione il data rate di base, passando da 800byte/sec a 8000byte/sec. Allo scopo si utilizza HIDGetReport per inviare dati all'host e HIDSetReport per il percorso inverso; in particolare, nel firmware fornito da Microchip (file usb_ch9.asm) c'è una routine d'esempio, alla label HIDSetReport, che risulta commentata ma che ci permette di sviluppare immediatamente un canale di comunicazione su EP0 in maniera che funzioni come EP1OUT. Analogamente, nel file hidclass.asm si trova un riferimento alla HIDGetReport. La manipolazione di tali routine va condotta con una certa attenzione (non dobbiamo dimenticare che EP0 viene utilizzato in maniera esclusiva per il trasferimento dei dati di controllo: un errore su tale canale può determinare il malfunzionamento dell'intero dispositivo) e solo quando strettamente necessario; nella pratica, ciò si limita a casi sporadici, perché il data rate base di 1600byte/sec è più che sufficiente nella maggior >
L I S T A T O 12 STARTUP
code pagesel Main goto Main nop InterruptServiceVector movwf W_save movf STATUS,W clrf STATUS movwf Status_save movf PCLATH,w movwf PCLATH_save
; save W ; force to page 0 ; save STATUS ; save PCLATH
movf FSR,w movwf FSR_save ; save FSR ; ********************* ; Interrupt Service Routine ; ********************* ;Process_ISR PERIPHERALTEST pagesel EndISR btfss INTCON,PEIE ;a peripheral interrupt? goto EndISR ; all done.... TEST_PIR1 bsf STATUS, RP0 ; Bank1 movf PIE1,w bcf STATUS, RP0 ; Bank0 andwf PIR1,w ; mask the enables with the flags movwf PIRmasked pagesel TryADIF btfss PIRmasked,USBIF ; USB interrupt flag goto TryADIF bcf PIR1,USBIF banksel UIR movf UIR,w andwf UIE,w banksel USBMaskedInterrupts movwf USBMaskedInterrupts pagesel USBActivity btfsc USBMaskedInterrupts,ACTIVITY ; Is there activity on the bus? call USBActivity pagesel USBReset
91
banksel movf banksel andwf banksel movwf btfsc nop ; ************ ; End ISR, restore ; ************ EndISR clrf movf movwf movf movwf movf movwf swapf ting STATUS swapf retfie code Main movlw movwf registers decfsz goto
PIRmasked,ADIF
; AD Done?
PIRmasked,RCIF PIRmasked,TXIF PIRmasked,CCP1IF PIRmasked,TMR2IF PIRmasked,TMR1IF PIR2 PIR2,w PIE2 PIE2,w PIRmasked PIRmasked PIRmasked,CCP2IF context and return to the Main program STATUS ; select bank 0 FSR_save,w ; restore the FSR FSR PCLATH_save,w ; restore PCLATH PCLATH Status_save,w ; restore Status STATUS W_save,f ; restore W without corrupW_save,w
.30 ; delay 16 uS to wait for USB to reset W_save ; SIE before initializing W_save,f $-1
clrf PORTB clrf PORTA banksel TRISA ; Bank 1 clrf TRISB ; Set PORTB as all outputs movlw 0x10 movwf TRISA ; Set RA4 as input pagesel InitUSB ; call InitUSB ; initialize the USB ConfiguredUSB ; bcf STATUS,RP0 bcf STATUS,RP1 ;*********** ;CursorDemo ;*********** CursorDemo ->Istruzioni di rotazione del cursore del mouse
Osservando le parti evidenziate si vede come la routine che gestiva le operazioni dei moduli USB sia stata estratta dall'ISR, diventando una funzione che viene richiamata al momento opportuno all'interno del programma principale.
92
btfsc USBMaskedInterrupts,USB_RST ; is it a reset? call USBReset ; yes, reset the SIE pagesel TryADIF btfss USBMaskedInterrupts,TOK_DNE ; is it a Token Done? goto TryADIF ; no, skip the queueing process CheckFinishSetAddr banksel UIR bcf UIR, TOK_DNE ; clear Token Done bcf STATUS,RP0 ; bank 2 movf USB_dev_req,w ; yes: waiting for the In xorlw SET_ADDRESS ; transaction ack-ing the end of the set address? btfss STATUS,Z goto TryADIF ; no - skip the rest.. just queue the USTAT register pagesel finish_set_address call finish_set_address clrf STATUS ; bank 0 TryADIF ………-> Istruzioni commentate TEST_PIR2 ………-> Istruzioni commentate ; ************* ; End ISR, restore context and return to the Main program ; ************* EndISR clrf STATUS ; select bank 0 movf FSR_save,w ; restore the FSR movwf FSR movf PCLATH_save,w ; restore PCLATH movwf PCLATH movf Status_save,w ; restore Status movwf STATUS swapf W_save,f ; restore W without corrupting STATUS swapf W_save,w retfie code ServiceUSB banksel UIR movf UIR,w banksel USBMaskedInterrupts movwf USBMaskedInterrupts pagesel USBError btfsc USBMaskedInterrupts,UERR call USBError pagesel USBSleep btfsc USBMaskedInterrupts,UIDLE call USBSleep pagesel USBStall btfsc USBMaskedInterrupts,STALL call USBStall pagesel TokenDone btfsc USBMaskedInterrupts,TOK_DNE call TokenDone return Main movlw .30 ; delay 16 uS to wait for USB to reset movwf W_save ; SIE before initializing registers decfsz W_save,f goto $-1 clrf STATUS ; Bank0 clrf Button_RA4 clrf PORTB clrf PORTA banksel TRISA ; Bank 1 clrf TRISB ; Set PORTB as all outputs movlw 0x10 movwf TRISA ; Set RA4 as input movlw 0x07 movwf OPTION_REG pagesel InitUSB call InitUSB ; initialize the USB ;*********** ; CursorDemo ;*********** CursorDemo pagesel CursorDemo banksel INTCON btfss INTCON,T0IF goto CursorDemo bcf INTCON,T0IF pagesel ServiceUSB call ServiceUSB ;any USB tokens to process? ->Istruzioni di rotazione del cursore …….
aprile 2005 - Elettronica In
Corso PIC-U USB
TEST_PIR2
btfsc nop btfsc nop btfsc nop btfsc nop btfsc nop btfsc nop
Corso PIC-U USB
parte delle applicazioni. Abbandoniamo dunque questo argomento e andiamo invece a vedere quali sono le novità introdotte nella versione 2.00 del firmware, resa disponibile sul sito Microchip. Firmware versione 2.00 Microchip ha recentemente reso disponibile una nuova versione del firmware USB, scaricabile dal sito sotto forma di archivio compresso denominato USB200as (versione assembler); rispetto alle versioni 1.xx, è stata introdotta una sostanziale modifica nell'architettura: mentre prima le operazioni USB venivano gestite esclusivamente attraverso una routine (ServiceUSB) inserita nel gestore degli interrupt (ISR=Interrupt Service Routine), ora tutto viene trattato direttamente da una call all'interno del programma principale. Ciò comporta una notevole differenza nell'uso delle risorse del PIC: innanzitutto c'è una minore occupazione dello stack (che ha solo 8 possibili livelli) in quanto si libera la parte che prima doveva essere riservata all'ISR. In secondo luogo, quest'ultima risulta essere decisamente più breve, il che rappresenta un buon esempio di programmazione, visto che, per definizione, le procedure che gestiscono i segnali di interrupt devono restituire il più rapidamente possibile il controllo al programma principale. Infine, l'utilizzo di una call permette
Elettronica In - aprile 2005
di mantenere totalmente il controllo sulla gestione delle operazioni USB, cosa utile quando si devono creare progetti in cui il tempo di esecuzione è un parametro determinante.; infatti, l'esecuzione del codice necessario a gestire un interrupt USB potrebbe creare un ritardo non ammissibile dall'applicazione. Con il nuovo firmware questo problema viene completamente superato, visto che è il programmatore a decidere quando avviare la ServiceUSB e quindi gestire il segnale in arrivo. Se mettiamo a confronto il main dei due pacchetti, ci accorgiamo della differenza (Listato 11 e 12). Dopo aver chiarito anche questa evoluzione del firmware Microchip, non ci resta che iniziare ad affrontare l'ultima parte di questo corso, quella relativa alla programmazione lato host. Infatti, se da una parte abbiamo capito come far comunicare il nostro PIC attraverso l'interfaccia USB, ora dobbiamo analizzare in che modo dar vita all'altro interlocutore. Nella prossima puntata vedremo come il sistema operativo del nostro PC è in grado di comunicare con un dispositivo USB e quali funzionalità rende disponibili. Infine, analizzeremo un componente molto interessante per lo sviluppo di front-end USB in Delphi e faremo una panoramica sullo stato dell'arte in questo campo. Non mancheranno i riferimenti ai software host utilizzati nei nostri esperimenti.
93
Corso PIC-USB
Corso di programmazione per PIC: l’interfaccia USB B Alla scoperta della funzionalità USB implementata nei microcontrollori Microchip. Un argomento di grande attualità, vista la crescente importanza di questo protocollo nella comunicazione tra computer e dispositivi esterni. In questa settima puntata vediamo lo sviluppo di procedure per la gestione di dispositivi USB mediante il modello HID di Microsoft.
7
a cura di Carlo Tauraso n questa puntata affrontiamo un argomento piuttosto vasto, cercando di farlo nella maniera più chiara e sintetica possibile: lo sviluppo di procedure per la gestione dei dispositivi ad interfaccia USB; focalizzeremo la nostra attenzione sui device HID, un po’ per ridurre il carico di lavoro, un po' perché lo sviluppo di un dispositivo HID risulta la soluzione più rapida e versatile in buona parte delle applicazioni. Ci riferiremo ai sistemi operativi Win9X e Win2K di Microsoft, che rappresentano la piattaforma più diffusa. Quanto agli ambienti di sviluppo, avremo un occhio di riguardo per Delphi, vista la presenza in rete di un ottimo componente open source e la rapidità di sviluppo del suo IDE, che rende possibile realizzare un front-end USB in pochi minuti. Presenteremo naturalmente anche alcune altre soluzioni presenti sul mercato. Il modello HID di Microsoft Microsoft fornisce un framework completo per lo sviluppo di driver WDM (Windows Driver Elettronica In - maggio 2005
Model) per la gestione di dispositivi HID, rappresentato dal class driver chiamato HIDCLASS.SYS, che si trova nella directory systemdir\system32\drivers (premettiamo che per systemdir intendiamo C:\WINDOWS per Win9x e C:\WINNT per Win2K). Inoltre, fornisce un driver generico HIDUSB.SYS in grado di gestire tutti i dispositivi costruiti secondo le specifiche HID. Si ricordi che tale caratteristica si stabilisce all’interno del descrittore INTERFACE (campi InterfaceClass e InterfaceSubClass); proprio la presenza di questo file, ci permette di realizzare dei dispositivi USB facendoli funzionare senza la necessità di sviluppare uno specifico driver. Nella realtà HIDUSB si appoggia anche ad un terzo file chiamato HIDPARSE.SYS, che si trova nella medesima directory ed è un parser che permette la lettura dei contenuti gerarchici dei descrittori REPORT. La coppia HIDCLASS e HIDUSB implementa tutte le funzionalità necessarie stabilite nelle specifiche USB. Il sistema risulta sufficientemente modulare in quanto permette anche la realizza- > 81
spositivo USB attraverso delle funzionalità rese disponibili tramite una libreria a collegamento dinamico (DLL) dal class driver e, nello specifico, da hidusb.sys. In questo modo possiamo concentrarci sulla logica del nostro front-end senza dover conoscere il funzionamento a basso livello del sistema operativo sul quale lo facciamo girare. La libreria HID.dll Windows rende disponibile tutta una serie di API che si trovano (raccolte in una libreria chiamata HID.dll) nella directory systemdir\SYSTEM e che permettono la comunicazione attraverso i driver visti nel precedente paragrafo. Analizzandole, ad esempio attraverso PE-Explorer, vediamo nelle risorse una serie di stringhe che le descrivono (Listato 1). Si osservino i campi Language e Translation; 1033 non è nient’altro che la trasformazione in
LISTATO 1 Language/Code Page: CompanyName: FileDescription: FileVersion: InternalName: LegalCopyright: OriginalFilename: ProductName: ProductVersion: Translation:
1033/1252 Microsoft Corporation Hid User Library 4.10.2222 hid.dll Copyright (C) Microsoft Corp. 1981-1999 hid.dll Microsoft(R) Windows(R) Operating System 4.10.2222 1033/1252
mente all’hardware, modificare le strutture dati appartenenti al sistema operativo, eseguire istruzioni privilegiate della CPU, quindi di codice che viene eseguito a livello Ring0 (a riguardo, si veda l’architettura dei microprocessori Intel). Inoltre, un kernel-driver è un componente "trusted" del sistema operativo, pertanto può essere installato solo dall’account che possiede i diritti di amministratore del computer. Il driver in questione è inoltre del tipo WDM; supporta il Pnp (Plug & Play) in modalità nativa e l’interfaccia WMI (Windows Management Instrumentation) che permette ad una applicazione di leggere, in modo standard, parametri caratteristici di funzionamento del driver, oltre che di scegliere la sua configurazione. In pratica, intraprendere la seconda strada significherebbe dover acquisire una serie di nozioni tali che probabilmente non basterebbero le pagine del nostro intero corso sui PIC per esporle; pertanto ve le risparmiamo e sintetizziamo la situazione dicendo che il nostro applicativo dialogherà con il di82
decimale del LANGID 0409h che abbiamo utilizzato per i descrittori STRINGA del nostro dispositivo. Per impostazione predefinita, questa libreria va a leggere le stringhe scritte in inglese. Se poi andiamo alla pagina degli Export vediamo un elenco di ben 41 funzioni, che sono fondamentali per dialogare attraverso l’USB (vedi Listato 2, nella pagina seguente). Tutti i componenti deputati allo sviluppo di applicazioni per HID che si trovano sul mercato creano delle interfacce più o meno semplici e adattabili a queste funzioni. Per completezza riportiamo anche le proprietà degli altri file menzionati (vedi Listato 3). Il progetto Jedi: un po’ di storia JEDI è l’acronimo di "Joint Endeavour of Delphi Innovators"; si tratta di una comunità internazionale composta da migliaia di sviluppatori Delphi, scopo della quale è diffondere l’utilizzo di tale ambiente di sviluppo e del suo fratello minore maggio 2005 - Elettronica In
Corso PIC-USB
zione di dispositivi compositi che presentano più interfacce, il che rende possibile fare in modo che il sistema operativo carichi il driver HID solo al momento opportuno. Microsoft fornisce anche una serie di altri driver per i dispositivi HID più diffusi come tastiere e mouse, costituenti altrettante interfacce per componenti di sviluppo di livello elevato. In generale, i class driver come hidclass.sys realizzano le funzionalità comuni a un’intera classe di dispositivi e il produttore di un determinato dispositivo realizza per esso un driver che ne fornisca le funzioni specifiche. Possiamo quindi scegliere se utilizzare il driver generico hidusb oppure implementarne uno che lo sostituisca. La seconda strada è percorribile utilizzando il DDK (Device Driver Kit) fornito gratuitamente da Microsoft. La cosa è piuttosto complessa in quanto stiamo parlando di un kernel-mode driver (ne esistono di tre tipi: VxD, NT legacy e WDM) cioè di un insieme di routine in grado di accedere diretta-
Corso PIC-USB
LISTATO 2 HidD_FlushQueue; 1; HidD_FreePreparsedData; 2; HidD_GetAttributes; 3; HidD_GetConfiguration; 4; HidD_GetFeature; 5; HidD_GetHidGuid; 6; HidD_GetedString; 7; HidD_GetManufacturerString; 8; HidD_GetNumInputBuffers; 9; HidD_GetPhysicalDescriptor; 10; HidD_GetPreparsedData; 11; HidD_GetProductString; 12; HidD_GetSerialNumberString; 13; HidD_Hello; 14; HidD_SetConfiguration; 15; HidD_SetFeature; 16; HidD_SetNumInputBuffers; 17; HidP_GetButtonCaps; 18; HidP_GetCaps; 19; HidP_GetData; 20; HidP_GetExtendedAttributes; 21;
Kylix. L’idea nacque un venerdì pomeriggio con la pubblicazione sulla "COBB Delphi Developers mailing list" di un messaggio nel quale ci si chiedeva cosa si potesse fare per rendere disponibili le nuove API (Application Program Interfaces) nell’ambiente Borland. Un paio di settimane dopo nacque il sito ufficiale con una prima traduzione degli header C in unità, classi e componenti direttamente disponibili per lo sviluppo Delphi. Nel corso degli anni la cosa si è evoluta sempre più grazie al contributo di sviluppatori in tutto il mondo, tant’è che oggi risultano disponibili molti componenti ed esempi di programmazione
HidP_GetLinkCollectionNodes; 22; HidP_GetScaledUsageValue; 23; HidP_GetSpecificButtonCaps; 24; HidP_GetSpecificValueCaps; 25; HidP_GetUsageValue; 26; HidP_GetUsageValueArray; 27; HidP_GetUsages; 28; HidP_GetUsagesEx; 29; HidP_GetValueCaps; 30; HidP_InitializeReportForID; 31; HidP_MaxDataListLength; 32; HidP_MaxUsageListLength; 33; HidP_SetData; 34; HidP_SetScaledUsageValue; 35; HidP_SetUsageValue; 36; HidP_SetUsageValueArray; 37; HidP_SetUsages; 38; HidP_TranslateUsagesToI8042ScanCodes; HidP_UnsetUsages; 40; HidP_UsageListDifference; 41;
39;
utili per lo sviluppo di applicativi che richiamano direttamente le funzionalità API. Il package Hidcontroller.dpk Se facciamo una ricerca nel codice reso disponibile alla pagina www.jedi-delphi.org, troviamo un interessante componente chiamato HidController: è stato sviluppato da Robert Marquardt, un programmatore tedesco che per diverso tempo ha ricoperto il ruolo di direttore del progetto; può facilmente essere integrato nella VCL di Delphi attraverso il file package (.dpk) incluso nell’archivio scaricato. Per instal- >
LISTATO 3 Language/Code Page: 1033/1252 CompanyName: Microsoft Corporation HID Class Driver FileDescription: FileVersion: 4.10.2222 InternalName: HIDCLASS.SYS LegalCopyright: Copyright © Microsoft Corp. 1981-1999 OriginalFilename: HIDCLASS.SYS ProductName: Microsoft(R) Windows(R)Operating System ProductVersion: 4.10.2222 Translation: 1033/1252 ---------------------------------------Language/Code Page: 1033/1252 CompanyName: Microsoft Corporation MINI NT HID PARSER FileDescription: FileVersion: 4.10.2222 InternalName: HIDPARSE.SYS LegalCopyright: Copyright © Microsoft Corp. 1981-1999 OriginalFilename: HIDPARSE.SYS ProductName: Microsoft(R) Windows(R) Operating System ProductVersion: 4.10.2222 Translation: 1033/1252 ----------------------------------------Language/Code Page: 1033/1252 CompanyName: Microsoft Corporation USB Miniport Driver for Input Devices FileDescription: FileVersion: 4.10.2222 InternalName: HIDUSB.SYS LegalCopyright: Copyright (C) Microsoft Corp. 1981-1999 OriginalFilename: HIDUSB.SYS ProductName: Microsoft(R) Windows(R) Operating System ProductVersion: 4.10.2222 Translation: 1033/1252
Elettronica In - maggio 2005
83
Fig. 1
larlo basta fare doppio clic sull’icona e, una volta avviato l’ambiente, fare clic su Install (Fig. 1). Nella "Component Palette" appare la scheda "Project Jedi" nella quale troverete il TJvHidDeviceController (Fig. 2) che, una volta installato, è pronto all’uso e possiamo rilasciarlo nella form
Fig. 2
Fig. 3
L’oggetto TJvHidDeviceController Gestisce un insieme di altri oggetti chiamati TjvHidDevice, ognuno dei quali rappresenta un dispositivo HID presente sul nostro PC. In pratica per ogni HID che viene collegato alle porte USB del sistema il controller crea un oggetto TjvHidDevice. La rete di dispositivi viene gestita attraverso una lista nella quale per ciascuno è indicato lo stato: nel momento in cui scolleghiamo un dispositivo il rispettivo oggetto non viene rimosso ma se ne aggiorna lo stato, che passa da
Tabella 1 - PROPRIETA’
84
maggio 2005 - Elettronica In
Corso PIC-USB
del nostro progetto. Prima di vedere come usarlo, analizziamone la struttura e le proprietà e gli eventi che rende disponibili, come si usa nella programmazione orientata agli oggetti.
Corso PIC-USB
Tabella 2 - METODI
collegato a scollegato. In questo modo si ha un controllo completo su tutte le periferiche che dovranno comunicare sul bus USB. Non è quindi necessario utilizzare piÚ oggetti controller Elettronica In - maggio 2005
nello stesso progetto: anzi, se si tenta di rilasciare due componenti sullo stesso form si ha la segnalazione di errore riportata in Fig. 3, che lo vieta. Ma vediamo di elencare e dare una breve > 85
spiegazione delle proprietà di questo oggetto che sovraintende al funzionamento di tutti i dispositivi collegati. Nel proseguire si consideri che le proprietà Public sono accessibili da qualunque parte del programma, sia in lettura che in scrittura, mentre le Read-only possono essere solo lette (Tabella 1). Vediamo ora di spiegare in sintesi i metodi dell’oggetto TjvHidDeviceController (Tabella 2). Nella funzione CheckOutByClass abbiamo visto che, nel registry di Windows, viene effettuata una ricerca della chiave Class; infatti durante la procedura di enumerazione il sistema operativo crea in
HKEY_LOCAL_MACHINE\Enum\HID\ un’apposita struttura descrittiva per ciascun dispositivo. La registrazione per il nostro cifrario di Vernam è visibile in Fig. 4. Ora vediamo quali sono gli eventi connessi all’oggetto controller (Tabella 3). Attraverso le relative tabelle potete già cominciare a delineare quali saranno le operazioni da svolgere nello sviluppo di un’applicazione host. Molto importanti nel processo, saranno gli eventi OnDeviceChange, OnEnumerate, OnDeviceData, perché ci permetteranno di gestire l’intero ciclo vitale dell’oggetto device. Tale ciclo parte dalla connessione, prosegue nell’enumerazione,
Tabella 3
86
maggio 2005 - Elettronica In
Corso PIC-USB
Fig. 4
Corso PIC-USB
si concretizza con la comunicazione ed infine termina con la disconnessione. L’uovo di Pasqua All’interno della libreria HID.dll c’è una funzione non documentata e piuttosto curiosa.
L’oggetto TJvHidDevice Vediamo ora quali sono le caratteristiche più importanti dell’oggetto device che viene creato dal controller e che ci permette di gestire i dettagli di ogni singolo dispositivo HID fisico collegato alle porte USB del nostro PC. Ci soffermia-
Tabella 4 - PROPRIETA’
Probabilmente uno scherzo (sono i famosi Easter Eggs) di qualche programmatore buontempone di Microsoft. La funzione si chiama HidD_Hello e la trovate nell’elenco che abbiamo estratto tramite PEExplorer. Essa è stata comunque resa disponibile nel componente di Marquardt ed ha la seguente dichiarazione: function HidD_Hello (Buffer: PChar; BufferLength: ULONG): ULONG; stdcall; Non fa altro che riempire il Buffer con la seguente stringa ‘Hello\nI hate Jello\n’ che tradotta in italiano suona come ‘’Salve Io Odio Jello’’. Dopodichè ritorna la lunghezza della stringa +1 (=20). La cosa strana è che ritorna 20 anche se il buffer è più piccolo. Quando è stata scoperta, a prima vista sembrava un buon sistema per generare un buffer overflow, cosa che avrebbe potuto aprire una falla di sicurezza nel sistema operativo; ma con buona pace degli amministratori di sistema Windows, non è stato così. Elettronica In - maggio 2005
mo sugli aspetti più utili per le applicazioni comuni nel settore HID e quelli che abbiamo utilizzato nelle applicazioni host degli esperimenti (Tabella 4). Per quanto riguarda i metodi e gli eventi disponibili, analizziamo soltanto quelli necessari e sufficienti alla realizzazione di una comunicazione bidirezionale su USB, premettendo che i dispositivi vengono trattati come se fossero dei file sequenziali, il che rende le cose evidentemente molto semplici. Per inviare dati al dispositivo utilizziamo la seguente funzione: function WriteFile(var Report; ToWrite: DWORD; var BytesWritten: DWORD): Boolean; Specificando un buffer per i valori (Report), il numero di bytes da inviare (ToWrite), ed una variabile contatore per i bytes inviati (BytesWritten) è possibile realizzare una comunicazione diretta da host a device senza partico- > 87
TUsage; TUsage; Word; Word; Word; array [0..16] of Word; Word; Word; Word; Word; Word; Word; Word; Word; Word; Word;
lari problemi. Naturalmente è necessario dimensionare buffer e byte da inviare a seconda del descrittore REPORT che abbiamo definito nel nostro firmware. È anche possibile reperire tali informazioni leggendo una proprietà specifica dell’oggetto TjvHIDDevice chiamata Caps (Capacities) la quale rende disponibile la struttura visibile nel Listato 4. Propriamente, utilizza la funzione HidP_GetCaps della libreria HID.dll di Windows. Naturalmente, se abbiamo sviluppato il firmware del dispositivo non abbiamo alcuna necessità di richiamarla, visto che conosciamo bene il dimensionamento del device. Può essere utile per controllare che non abbiamo commesso errori. La funzione restituisce un valore booleano
Campi che permettono di dimensionare semplicemente i buffers dell'host a seconda di quanto stabilito nel descrittore REPORT inserito nel firmware.
procedure(const HidDev: TJvHidDevice; ReportID: Byte; const Data: Pointer; Size: Word); Questo evento avvia automaticamente un thread che gestisce la lettura dei dati dal dispositivo e rimane attivo finchè il dispositivo stesso resta nello stato CheckOut. Pertanto i dispositivi CheckedIn non hanno alcun Thread avviato. In pratica ogni volta che il dispositivo invia dei dati l’evento viene attivato ed il thread carica il buffer puntato da Data, agiornando la variabile Size con il numero di byte ricevuti. ‘’HidDev’’ è l’oggetto TJvHidDevice da cui vengono letti i dati. ‘’ReportID’’ è il numero identificativo del report
LISTATO 5 if not Dispos.WriteFile(Buf, 3, Inviati) then begin Err := GetLastError; log.ItemIndex := log.Items.Add(Format('Errore Scrittura: %s end
a true se la scrittura va a buon fine, mentre ritorna false nel caso contrario. Nel cifratore di Vernam utilizziamo l’istruzione del Listato 5. Dispos è l’oggetto TjvHIDDevice creato dopo la connessione e il processo di enumerazione. ‘’Buf’’ è un array di byte con due elementi che contengono rispettivamente l’identificativo del report e il carattere da cifrare. Nel nostro caso il primo elemento viene sempre posto a 0, visto che i nostri dispositivi possono inviare solo un report. ‘’Inviati’’ invece è una variabile intera che conterrà i bytes scritti. Sfruttiamo il valore booleano ritornato dalla funzione per scrivere nel pannello Errori della form la stringa contenente la descrizione dell’errore di sistema. Per quanto riguarda la lettura utilizziamo l’evento OnData dell’oggetto TjvHIDDevice così definito: 88
--->[%x]', [SysErrorMessage(Err), Err]));
inviato e nel nostro caso è sempre 0. Il puntatore Data si riferisce ad un’area di memoria che viene riscritta ad ogni report arrivato. L’evento può essere assegnato a ciascun dispositivo a runtime o direttamente per tutti durante la progettazione dell’applicativo attraverso l’OnDeviceData del controller. Dopo aver fatto una panoramica teorica sui due oggetti base non ci resta che passare a un po’ di pratica, analizzando il codice host degli esperimenti che abbiamo realizzato durante il corso. TERMOUSB - Il software In questo esperimento dobbiamo leggere un byte contenente il valore di temperatura campionato dal PIC sulla base delle variazioni di tensione ai maggio 2005 - Elettronica In
Corso PIC-USB
LISTATO 4 THIDPCaps = record Usage: UsagePage: InputReportByteLength: OutputReportByteLength: FeatureReportByteLength: Reserved: NumberLinkCollectionNodes: NumberInputButtonCaps: NumberInputValueCaps: NumberInputDataIndices: NumberOutputButtonCaps: NumberOutputValueCaps: NumberOutputDataIndices: NumberFeatureButtonCaps: NumberFeatureValueCaps: NumberFeatureDataIndices: end;
Corso PIC-USB
LISTATO 6 procedure TMainForm.FormActivate(Sender: TObject); begin HidCtl.OnDeviceChange := HidCtlDeviceChange; end;
capi di una resistenza NTC. Il programma deve trasformare il valore intero letto in un valore di temperatura valido a seconda della scala termometrica scelta e rappresentarlo su un apposito pannello. Il sistema deve essere in grado di avviare la lettura a comando e identificare il dispositivo al momento della connessione, nonché
la collezione viene avviato il processo di enumerazione (HidCtl.Enumerate). Al termine si pone come dispositivo corrente il primo della lista. Molti di voi avranno capito che la soluzione è abbastanza semplicistica, visto che potremmo avere diversi dispositivi collegati con caratteristiche completamente differenti dal TermoUSB. Beh, questo è il momento giusto per introdurre una modifica implementativa che rende la cosa più funzionale. Anziché assegnare il primo dispositivo trovato dovremmo fare una ricerca sul bus fino a trovare il nostro dispositivo, cosa che si può fare semplicemente attraverso la
LISTATO 7 procedure TMainForm.HidCtlDeviceChange(Sender: TObject); var Dev: TJvHidDevice; I: Integer; begin Leggi.Down := False; for I := 0 to Lista.Count - 1 do begin Dev := TJvHidDevice(Lista.Items.Objects[I]); Dev.Free; end; Lista.Items.Clear; HidCtl.Enumerate; if Lista.Items.Count > 0 then Lista.ItemIndex := 0; end;
gestire il momento in cui viene disconnesso. Considerando che si deve gestire un unico dispositivo è possibile utilizzare l’evento OnDeviceData fissando a priori le operazioni da effettuare nel momento in cui da esso arriveranno i dati. Si è deciso invece di assegnare l’evento OnData a runtime a un dispositivo specifico. In questo modo si utilizza un codice che può tornare utile nel caso si debbano gestire più dispositivi differenti sullo stesso bus, soluzione che può risultare decisamente più versatile. Analizziamo nel concreto l’unità principale del programma presentando le procedure e i vari gestori di eventi che la compongono (vedi Listato 6). Nel momento in cui la Form principale viene creata ed attivata assegnamo l’evento OnDeviceChange che viene eseguito nel momento in cui si avvia l’applicazione ed il dispositivo risulta già collegato. La CPU quindi prende in carico il codice espresso nel Listato 7. La lista dei dispositivi viene completamente svuotata. Si noti che la ListBox presente sul Form è una collezione di oggetti. In ciascun elemento di questo vettore un po’ particolare viene memorizzato l’oggetto device corrispondente a ciascun dispositivo fisico presente sul bus. Dopo aver ‘’pulito’’ Elettronica In - maggio 2005
La lista dei dispositivi collegati viene svuotata.
Viene avviato il processo di enumerazione.
Il dispositivo corrente è il primo della lista.
CheckOutByProductName, passando come parametro la stringa ‘’ThermoUSB PICUSBCourse’’ che abbiamo inserito nel descrittore del nostro device. L’enumerazione avviene come nel Listato 8. La procedura estrae il nome del prodotto e lo aggiunge alla lista presente sul Form. Nel caso la stringa nome prodotto sia nulla, viene utilizzata una descrizione composta da VendorID e ProductID. Non è il nostro caso ma in questo modo si vede come si può utilizzare la proprietà Attributes dell’oggetto TjvHiddevice. Successivamente il dispositivo viene estratto dalla lista tramite l’operazione di CheckOut e sfruttando l’indice Idx che viene passato dalla funzione di enumerazione. Dopo il CheckOut il dispositivo è pronto per essere utilizzato pertanto viene inserito nella collezione di oggetti. Al termine vengono valorizzati i campi del pannello ID-Dispositivo del form principale estraendo i campi che interessano (Nome prodotto, venditore, relativi ID e numero seriale). Realizzando la modifica che comporta la ricerca del dispositivo fate attenzione a modifcare anche l’enumerazione in quanto non si può fare il CheckOut di un dispositivo che è stato già estratto dalla lista. Nel caso il dispositivo venga con- > 89
function TMainForm.HidCtlEnumerate(HidDev: TJvHidDevice; Valorizzazione lista del form principale. const Idx: Integer): Boolean; var N: Integer; CheckOut del dispositivo tramite l’Idx e inserimento Dev: TJvHidDevice; nella collezione di oggetti. begin if HidDev.ProductName <> '' then N := Lista.Items.Add(HidDev.ProductName) else N := Lista.Items.Add(Format('Device VID=%.4x PID=%.4x', [HidDev.Attributes.VendorID, HidDev.Attributes.ProductID])); HidCtl.CheckOutByIndex(Dev, Idx); Lista.Items.Objects[N] := Dev;
Valorizzazione pannello ID-Dispositivo del form principale.
edit1.Text:=inttostr(Dev.Attributes.ProductID); edit2.Text:=Dev.ProductName; edit3.Text:=inttostr(Dev.Attributes.VendorID); edit4.Text:=Dev.VendorName; edit5.Text:=Dev.SerialNumber; Result := True; end;
nesso o disconnesso si eseguono i relativi aggiornamenti sullo stato dello stesso. Naturalmente, da ciò consegue anche un OnDeviceChange pertanto vengono aggiornati anche i controlli inerenti la lista di dispositivi (Listato 9). Arriviamo quindi alla parte più importante, la gestione della lettura dati che viene attivata nel momento in cui si fa clic sul bottone relativo
un ultimo controllo sull’accessibilità del dispositivo (HasReadWriteAccess) e se il controllo va a buon fine si assegna l’evento OnData del device, altrimenti il relativo puntatore viene posto a nil. Questa è l’assegnazione di Runtime di cui avevamo parlato all’inizio. Naturalmente considerando la semplificazione secondo cui dialoghiamo con un unico device avremmo potuto assegnare
LISTATO 9 procedure TMainForm.HidCtlArrival(HidDev: TJvHidDevice); begin Edit6.Text:='Connesso'; end;
Dispositivo che viene connesso ad una porta USB.
Dispositivo che viene sconnesso da una porta USB.
procedure TMainForm.HidCtlRemoval(HidDev: TJvHidDevice); begin Edit6.Text:='Disconnesso'; end;
(Listato 10). Dopo il clic viene effettuato un controllo sui parametri NTC per evitare errori comuni, nel caso vengano rilevate delle incongruenze viene rilasciato il bottone (Leggi.Down=false) altrimenti viene calcolato il valore ‘’raz’’ che corrisponde ai gradi per ciascun livello logico nell’intervallo di temperatura che l’utente ha impostato. Il sistema è fissato su parametri stabiliti attraverso una NTC con R25 a 33 kohm e non viene prevista la possibilità di salvare i nuovi parametri se non per la singola sessione di lavoro. Per rendere le cose più professionali, un’idea può essere quella di salvare i parametri in un file dati che viene riletto ad ogni avvio. L’oggetto device corrente viene valorizzato sulla base dell’ItemIndex attuale della lista di oggetti, nel nostro caso è il primo in elenco. Viene effettuato 90
l’evento in maniera comune a tutti direttamente durante la progettazione dell’applicazione. La scelta fatta però appare molto più versatile e dinamica. Ecco quindi la procedura eseguita nel momento in cui il dato è reso disponibile dal PIC e quindi si avvia l’evento OnData (Listato 11). La procedura è relativamente semplice in quanto il valore scambiato è unico, pertanto sappiamo già che size sarà pari a 1, quindi leggiamo il buffer indirizzando immediatamente il primo elemento. Altrimenti avremmo dovuto realizzare un ciclo for che legge in sequenza i vari elementi inviati. Una volta recuperato il valore, per estrarre la temperatura bisogna elaborarlo sulla base dei parametri NTC. Per impostazione predefinita si calcola il valore Celsius, poi, a seconda della scelta fatta nel RadioGroup, esso viene trasformaggio 2005 - Elettronica In
Corso PIC-USB
LISTATO 8
Corso PIC-USB
L I S T A T O 10 procedure TMainForm.LeggiClick(Sender: TObject); var T1,T2,V1,V2:integer; Controlli di congruenza sui parametri NTC inseriti begin dall’utente. In particolare non sono ammissibili i T1:=strtoint(Edit7.Text); seguenti casi: T2:=strtoint(Edit8.Text); - T1=T2 e V1=V2 V1:=strtoint(Edit9.Text); - T1<T2 e V1>V2 V2:=strtoint(Edit10.Text); - T1>T2 e V1<V2 if ((T1=T2) or (V1=V2)) then begin messagedlg('Errore: I parametri NTC devono definire un range.',mtcustom,[mbok],0); Leggi.down:=false; end else if ((T1<T2) and (V1>V2)) then begin messagedlg('Errore: parametri NTC incompatibili T1<T2 V1>V2',mtcustom,[mbok],0); Leggi.down:=false; end else if ((T1>T2) and (V1<V2)) then begin messagedlg('Errore: parametri NTC incompatibili T1>T2 V1<V2',mtcustom,[mbok],0); Leggi.down:=false; end Calcolo del rapporto tra temperatura e livelli logici. else begin raz:=abs(T1-T2)/abs(V1-V2); Dispos := nil; if (Lista.Items.Count > 0) and (Lista.ItemIndex >= 0) then begin Dispos := TJvHidDevice(Lista.Items.Objects[Lista.ItemIndex]); if not CurrentDevice.HasReadWriteAccess then Leggi.Down := False else Estrazione del puntatore al dispositivo corrente ed if Leggi.Down then assegnazione a RunTime dell’evento OnData del device. Dispos.OnData := LetturaTemp else Dispos.OnData := nil; end; end; end;
mato in base alla scala termometrica. Per ultimo si invia il dato rilevato all’LCD e al log. A proposito: abbiamo utilizzato un componente free scaricabile dalla rete (TLCD_Label) anche per l’LCD. Il codice che abbiamo visto è solo un esempio didattico ed è migliorabile in molti punti: prendetelo come spunto per lo sviluppo professionale.
PWMUSB - Il software Nel secondo esperimento avevamo realizzato un controller PWM per piccoli motori DC, sfruttando l’uscita preposta sulla nostra demoboard e lasciando all’intraprendenza del lettore la possibilità di adeguare il tutto attraverso l’uso di un MOSFET di potenza. L’esempio permetteva di >
L I S T A T O 11 procedure TMainForm.LeggiTemp(HidDev: TJvHidDevice; ReportID: Byte; const Data: Pointer; Size: Word); Lettura del valore inviato dal PIC, calcolo della var temperatura corrispondente in base al rapporto raz w: double; calcolato in precedenza. Str: string; begin Str := Format('%.2d', [Cardinal(PChar(Data)[0])]); w:=strtofloat(Str); w:=strtoint(edit7.text)-(strtoint(edit9.Text)-w) * raz; Calcolo del valore da visualizzare sulla base della scala termometrica selezionata. case RadioGroup1.ItemIndex of 1: w:=1.8*w+32; 2: w:=w*0.8; Visualizzazione sul pannello LCD del valore risultante. 3: w:=w+273; end; LCD.Caption:=Format('%3.1f', [w]);
Visualizzazione del livello logico ricevuto dal PIC con la cancellazione del log al quarto valore.
log.ItemIndex := log.Items.Add(Str); if log.ItemIndex>3 then log.Clear; end;
Elettronica In - maggio 2005
91
Valorizzazione del Buffer, attraverso il ReportID che per default è il primo elemento ed è azzerato, la velocità tradotta in livelli logici, il tempo tradotto in intervalli di 5 secondi.
Buf[0]:= 0; Buf[1] := round((strtoint(Edit2.text))* 2.55); Buf[2] := round((strtoint(Edit3.Text))/5);
Invio dei valori tramite la funzione WriteFile e gestione dell'eventuale errore.
if not Dispos.WriteFile(Buf, 3, Inviati) then begin Err := GetLastError; log.ItemIndex := log.Items.Add(Format('Errore Scrittura: %s end else begin log.ItemIndex := log.Items.Add(Format('%.3d Scrivi.Enabled:=false; Timer1.Interval:=1000*strtoint(edit3.Text); Timer1.Enabled:=true; end;
--->[%x]', [SysErrorMessage(Err), Err]));
', [Buf[1]])+ Format(' %.3d
', [Buf[2]]));
Scrittura dei valori inviati nel log e disabilitazione del pulsante d’invio per il tempo necessario all’esecuzione dell’ istruzione di accensione da parte del PIC.
end; end;
indagare sulla comunicazione da host a device completando il discorso iniziato con il TermoUSB. Ebbene, considerato che, sfruttando un ambiente di sviluppo RAD come Delphi la prima cosa da considerare è il riutilizzo del soft-
ware, abbiamo ripreso le procedure del TermoUSB e le abbiamo integrate con la funzione necessaria ad inviare i dati di accensione del motorino al PIC. Per brevità analizziamo direttamente questa procedura senza ripetere quelle già viste
L I S T A T O 13 procedure TMainForm.WriteBtnClick(Sender: TObject); begin Memo2.Clear; Cancellazione pannelli memo contenenti testo Memo3.Clear; cifrato e chiave. Dispos := nil; Leggi.Down:=true; if (Lista.Items.Count > 0) and (Lista.ItemIndex >= 0) then begin Estrazione dalla lista dell’oggetto device corrente e Dispos:= TJvHidDevice(Lista.Items.Objects[Lista.ItemIndex]); verifica della possibilità di accesso ad esso. if not CurrentDevice.HasReadWriteAccess then Leggi.Down := False else if Leggi.Down then Assegnazione dell’evento OnData a RunTime per il Dispos.OnData := LeggiCripto dispositivo corrente. else Dispos.OnData := nil; end; if Assigned(Dispos) then begin IX:=0; JX:=1; Scaricamento del memo contenente il testo in chiaro, if IX<= memo1.Lines.count then carattere per carattere e riga per riga. Viene utilizzata la begin funzione WriteFile. In questo caso i byte da inviare sono riga:=memo1.Lines[IX]; solo due: uno è il solito ReportID a 0 e l’altro è il codice if JX<= length(riga) then ASCII del carattere del testo in chiaro da trasmettere. begin Buf[0] := 0; Buf[1] := ord(riga[JX]); if not Dispos.WriteFile(Buf, 2, Inviati) then begin Err := GetLastError; log.ItemIndex := log.Items.Add(Format('Errore Scrittura: %s ---> [%x]', [SysErrorMessage(Err), Err])); end else inc(JX); end; end; end; end;
92
maggio 2005 - Elettronica In
Corso PIC-USB
L I S T A T O 12 procedure TMainForm.ScriviClick(Sender: TObject); var Buf: array [0..2] of Byte; Inviati: Cardinal; Err: DWORD; begin if Assigned(Dispos) then begin
Corso PIC-USB
per il primo esperimento (Listato 12): anche in questo caso il codice è piuttosto chiaro. Qualora il puntatore all’oggetto device corrente sia stato assegnato (non sia ‘’nil’’) dapprima si valorizza il buffer dati. Si consideri che per default il primo elemento è dato dal ReportID, che nel nostro caso è sempre pari a 0 in quanto il dispositivo gestisce un unico report. Agli altri due elementi si assegnano i valori relativi alla velocità convertita in livello logico e al tempo di accensione in intervalli di 5 sec. Si sfrutta il risultato della funzione per verificare se c’è stato qualche errore. In tal caso viene emesso un apposito messaggio. Infine i valori inviati sono visualizzati sul pannello di log e si disattiva il pulsante di Invio per fare in modo che l’accensione del motore sia terminata prima di inviare una nuova sequenza di accensione.
cedura LeggiCripto, la quale non fa altro che leggere i dati ritornati dal PIC attraverso le istruzioni del Listato 14. Esse trasferiscono i risultati nei relativi pannelli. Dopodichè viene richiamato nuovamente il codice di scrittura dei prossimi caratteri, in questo modo ogni scrittura è cadenzata con una lettura. Non viene avviata la scrittura del carattere successivo finchè non è terminata la lettura dei caratteri del testo cifrato e della chiave. Conclusione Siamo arrivati al termine del nostro viaggio nello sviluppo di firmware e software per dispositivi USB. La nostra trattazione non pretende di essere esaustiva ma ha voluto dare una visione abba-
L I S T A T O 14 memo2.Lines.Text:=memo2.Lines.Text+ Format('%.2x ', [Cardinal(PChar(Data)[0])]); memo3.Lines.Text:=memo3.Lines.Text+ Format('%.2x ', [Cardinal(PChar(Data)[1])]);
CifrarioUSB - Il software Come avrete già intuito il software host del CifrarioUSB è in pratica un merge dei primi due,
stanza dettagliata del sistema di sviluppo. Si tratta in ogni caso di un buon punto di partenza per intraprendere qualche progetto interessante per il PIC16C745 di Microchip. Speriamo di aver con-
realizzando un completo sistema di comunicazione bidirezionale su USB. Per brevità elenco il codice relativo all’invio dei dati e alla loro ricezione. Sarà facile riconoscere le istruzioni ricavate dai precedenti programmi (vedi Listato 13). L’evento OnData viene gestito attraverso la pro-
tribuito alla comprensione di un’interfaccia di comunicazione che avrà probabilmente sempre maggiore diffusione in futuro, soppiantando i vecchi sistemi (porte parallela e seriale RS232) che si stanno rivelando sempre più limitati, sia per velocità che per dinamicità d’uso.
Elettronica In - maggio 2005
93
Corso PIC-USB
Corso di programmazione per PIC: l’interfaccia USB B Alla scoperta della funzionalità USB implementata nei microcontrollori Microchip PIC18F2455 e 18F2550, argomento di grande attualità, vista la crescente importanza dell’Universal Serial Bus nella comunicazione tra computer e dispositivi esterni. In queste pagine ci occuperemo dei nuovi chip, analizzeremo la versione 2.46 del compilatore e studieremo lo sviluppo nel framework Microchip integrato nel C18.
8
a cura di Carlo Tauraso n seguito all’introduzione sul mercato da parte della Microchip della nuova famiglia di microcontrollori a 28 pin PIC18F2455/2550, ci sembra doveroso integrare il corso di programmazione PIC-USB con una serie di approfondimenti dedicati ai nuovi chip. L’evoluzione è decisamente interessante sia per il fatto che questi micro sono dotati di memoria flash, sia perché essi integrano le nuove funzionalità connesse alla modalità full-speed. Naturalmente, tutto quello che abbiamo spiegato nelle puntate precedenti per la nuova famiglia di chip. È anche necessario considerare la presentazione da parte del produttore di un nuovo framework che ci permette di sviluppare del firmware in grado di sfruttare sia la stabilità della modalità a bassa velocità che le prestazioni di quella fullspeed. Non solo: puntualmente la Microengineering Labs ha provveduto ad interpretare il firmware Microchip per adattarlo alle esigenze del compilatore PICBasic PRO, rilasciandone la versione 2.46. Se poi aggiungiamo la compatibilità “pin to pin” del Elettronica In - giugno 2005
PIC18F2455/2550 con il precedente PIC16C745/765, comprendiamo come possiamo facilmente migrare i progetti presentati durante il corso verso la nuova piattaforma. Queste puntate di approfondimento hanno come scopo principale quello di presentare tutte le informazioni necessarie e sufficienti all’utilizzo della nuova famiglia di PIC, illustrando le modifiche e le integrazioni da effettuare nel firmware presentato nel corso delle precedenti puntate. Vogliamo, inoltre, aggiungere alcuni concetti teorici inerenti ai trasferimenti bulk e sincroni al fine di permettere lo sfruttamento di tutte le potenzialità di tali chip. Per concludere, studieremo un nuovo approccio di sviluppo integrato nel framework Microchip in C18. Quest’ultimo approfondimento permetterà a tutti coloro che hanno seguito con attenzione il nostro corso PIC-USB di disporre di un diverso punto di vista che non deve mancare nel curriculum dello sviluppatore provetto. Bene, non ci resta che iniziare; buona lettura. > 81
Partiamo immediatamente con le innovazioni introdotte in questa nuova versione del compilatore PICBasic PRO in maniera da poter iniziare a ricompilare i nostri progetti e mettere subito in funzione il PIC18F2455/2550. Utilizzeremo la medesima demoboard usata durante il Corso (in kit cod. K8055 oppure montata cod. VM110, Futura Elettronica), per rendere le cose più semplici e per poterci concentrare esclusivamente sul firmware. Nella nuova versione oltre ad aver inserito il supporto per i nuovi PIC aggiungendo le relative include è stata prevista un’ulteriore sottodirectory denominata USB18. Qui troviamo tutti gli strumenti necessari ad affrontare lo sviluppo per il PIC18F2455/2550. Si faccia attenzione che è stata mantenuta anche la cartella USB contenente, invece, i file utilizzati per il 16C745/765. Vediamo di elencare i file chiave per la realizzazione del nuovo firmware: 1) USB18MEM.ASM 2) USB18.ASM 3) USB18.INC 4) USBDESC.ASM 5) *DSC.ASM 6) *.BAS 1) Questo primo file è un gestore di memoria. Per capirne la funzione dobbiamo considerare che i nuovi chip presentano un numero di endpoint decisamente superiore rispetto alla precedente versione. Si passa da 6 a 16 endpoints bidirezionali. Analogamente la memoria assegnata per la gestione della BDT (Buffer Descriptor Table) e dei buffer di trasferimento dati passa dai pochi 64 byte a ben 1 kb. Per ottimizzare l’utilizzo di questi spazi a seconda del numero di endpoint impiegati e del tipo di dispositivo (grazie alla modalità full-speed si possono emulare classi più potenti rispetto ai HID) è stato creato questo file che permette l’allocazione corretta della memoria del PIC. Se proviamo ad aprire con un editor il file ci accorgeremo che uno dei parametri di base su cui viene realizzata la struttura di memorizzazione è la variabile MAX_EP_NUMBER. Essa rappresenta il massimo numero di endpoints utilizzato nel progetto, e verrà stabilita nel file che raccoglie i descrittori. 2) 3) In questi due file troviamo lo sviluppo del firmware Microchip: essi sostituiscono fondamentalmente i fileusb_ch9.asm, usb_defs.inc, 82
hidclass.asm. Non vanno modificati ma sono interessanti da analizzare per capire come sono state sviluppate le funzioni di comunicazione sul bus USB. In particolare, il file USB18.INC permette la comprensione della definizione di alcuni importanti parametri come gli stati del dispositivo, i tipi di descrittori, i tipi di trasferimento dati tutte cose che abbiamo già spiegato nel corso. La creazione di questo nuovo framework ha permesso di sintetizzare il tutto in due file rendendo il sistema più modulare. C’è da considerare che l’interpretazione avvenuta per il PICBasic trae origine da una serie di file scritti in C18 come spiegheremo nelle prossime puntate. Pertanto, le definizioni nascono fondamentalmente da un insieme di strutture dati (istruzione struct). Sottolineiamo soltanto la presenza di una nuova struttura chiamata MUID (Microchip USB Class ID): #define MUID_NULL 0 #define MUID_USB9 1 #define MUID_HID 2 #define MUID_CDC 3 #define MUID_MSD 4 Nel corso abbiamo parlato più volte di Device Class, concentrando la nostra attenzione sui dispositivi HID che si potevano realizzare con facilità attraverso l’utilizzo dei PIC16C745/765. Attraverso la nuova famiglia 18F è possibile gestire anche altre tipologie come i CDC (Communication Device Class) o gli MSD (Mass Storage Device). Vedremo più avanti che questa struttura è la base di una routine (USBCTRLTRF) specifica del framework Microchip e permette di gestire i trasferimenti di controllo che tran-sitano sull’endpoint0. In pratica la tabella MUID raccoglie i record che identificano la classe proprietaria dell’attuale sessione di comunicazione. In questo modo il processo che deve smistare le richieste relative alle diverse classi può passarle senza problemi al gestore di ciascuna di esse. 4) Questo file è praticamente identico a quello che abbiamo utilizzato durante il Corso e contiene l’include necessaria a creare il collegamento con il file contenente i descrittori del progetto (*DSC.ASM). L’istruzione deve essere opportunamente modificata con il nome del file creato nei vari esperimenti. 5) Contiene l’insieme dei descrittori del nostro dispositivo. Questo file dovrà essere modificato durante lo sviluppo. Anche stavolta presentere- > giugno 2005 - Elettronica In
Corso PIC-USB
Le modifiche del compilatore PICBasic PRO 2.46
Corso PIC-USB
LISTATO 1 #define #define #define #define #define
EP0_BUFF_SIZE MAX_NUM_INT MAX_EP_NUMBER NUM_CONFIGURATIONS NUM_INTERFACES
#define MODE_PP #define UCFG_VAL ;#define UCFG_VAL
8 1 1 1 1
; 8, 16, 32, or 64 ; For tracking Alternate Setting ; UEP1
_PPBM0 _PUEN|_TRINT|_FS|MODE_PP _PUEN|_TRINT|MODE_PP
; Full-speed ; Low-speed
Diventa la TABELLA PARAMETRI GENERALI dove inseriremo una serie di valori che precisano il numero massimo di endpoints, la grandezza del buffer endpoint0 ecc. Vengono utilizzati dalle routine firmware per dimensionare opportunamente le strutture dati.
;#define USE_SELF_POWER_SENSE_IO ;#define USE_USB_BUS_SENSE_IO ; ********************************************************* ; DEVICE CLASS USAGE #define USB_USE_HID ; HID Diventa la TABELLA PARAMETRI CLASSE HID dove ; Endpoints Allocation #define HID_INTF_ID 0x00 inseriremo una serie di configurazioni di inizializzazio#define HID_UEP UEP1 ne della classe di dispositivi HID che abbiamo usato #define HID_BD_OUT ep1Bo diffusamente durante il corso. #define HID_INT_OUT_EP_SIZE 8 #define HID_BD_IN ep1Bi #define HID_INT_IN_EP_SIZE 8 #define HID_NUM_OF_DSC 1 ; ********************************************************* Questa tabella viene divisa in cinque sezioni per rispecDeviceDescriptor chiare la sequenza dei vari descrittori. retlw (EndDeviceDescriptor-DeviceDescriptor)/2; bLength retlw DSC_DEV ; bDescType This is a DEVICE descriptor retlw 0x10 ; bcdUSBUSB Revision 1.10 (low byte) retlw 0x01 ; high byte retlw 0x00 ; bDeviceClass retlw 0x00 ; bDeviceSubClass retlw 0x00 ; bDeviceProtocol Diventa la TABELLA 1 DESCRITTORE DEVICE riscritta retlw EP0_BUFF_SIZE ; bMaxPacketSize for EP0 retlw 0xD8 ; idVendor (low byte) descrizione dei campi retlw 0x04 ; (high byte) retlw 0x00 ; idProduct (low byte) retlw 0x00 ; (high byte) retlw 0x01 ; bcdDevice (low byte) retlw 0x00 ; (high byte) retlw 0x01 ; iManufacturer retlw 0x02 ; iProduct retlw 0x03 ; iSerialNumber retlw NUM_CONFIGURATIONS ; bNumConfigurations EndDeviceDescriptor ; ****************************************************************** USB_CD_Ptr Configs Diventa la TABELLA DI INDIRIZZAMENTO CONFIGURATION db low Config1, high Config1 db low Config1, high Config1 ; ****************************************************************** ; Configuration Descriptor Config1 retlw (Interface1-Config1)/2 ; bLength Length of this descriptor retlw DSC_CFG ; bDescType2 = CONFIGURATION Diventa la TABELLA 2 Config1Len DESCRITTORE CONFIGURAretlw low ((EndConfig1-Config1)/2) ; Length of this configuration retlw high ((EndConfig1-Config1)/2) TION riscritta la descrizione retlw 0x01 ; bNumInterfaces Number of interfaces dei campi retlw 0x01 ; bConfigValue Configuration Value retlw 0x04 ; iConfigString Index for this config = #01 retlw 0xA0 ; bmAttributes attributes - bus powered retlw 0x50 ; Max power consumption (2X mA) ; ****************************************************************** Interface1 Diventa la TABELLA 3 retlw (HIDDescriptor1-Interface1)/2 ; length of descriptor DESCRITTORE INTERFACE retlw DSC_INTF riscritta la descrizione dei retlw 0x00 ; number of interface, 0 based array campi secondo specifiche retlw 0x00 ; alternate setting retlw 0x01 ; number of endpoints used in this interface USB retlw 0x03 ; interface class - assigned by the USB retlw 0x01 ; boot device retlw 0x02 ; interface protocol - mouse retlw 0x05 ; index to string descriptor that describes this interface ; ****************************************************************** HIDDescriptor1 retlw (Endpoint1-HIDDescriptor1)/2 ; descriptor size (9 byte) Diventa la TABELLA 4 retlw 0x21 ; descriptor type (HID) DESCRITTORE HID riscritta la retlw 0x00 ; HID class release number (1.00) descrizione dei campi retlw 0x01 retlw 0x00 ; Localized country code (none) retlw 0x01 ; # of HID class descriptor to follow (1) retlw 0x22 ; Report descriptor type (HID) ; ****************************************************************** ReportDescriptor1Len retlw low ((EndReportDescriptor1-ReportDescriptor1)/2) retlw high ((EndReportDescriptor1-ReportDescriptor1)/2) Diventa la TABELLA 5 Endpoint1 DESCRITTORE ENDPOINT retlw (EndConfig1-Endpoint1)/2 ; length of descriptor retlw DSC_EP retlw 0x81 ; EP1, In retlw 0x03 ; Interrupt retlw 0x08 ; This should be the size of the endpoint buffer retlw 0x00 (continua) retlw 0x0A ; polling interval (10ms) EndConfig1
Elettronica In - giugno 2005
83
mo una versione "riordinata" dello stesso per permettere a ciascuno di voi di personalizzarlo con facilità e soprattutto di riutilizzarlo. In particolare, proporremo la riscrittura del nostro primo progetto: il Termo-USB. 6) È il programma PICBasic che svolge le funzioni assegnate al progetto. Il nostro intervento si deve concentrare sui file dei punti 4, 5, 6. Iniziamo dai descrittori che rappresentano la parte più importante. Anche qui, come nella seconda puntata del Corso, usiamo il file mousdesc.asm per analizzarne la struttura e 84
presentare le semplificazioni del nostro modello di sviluppo. Il file dei descrittori Il mousdesc.asm rappresenta i descrittori di un dispositivo che emula un mouse che ruota ciclicamente il cursore sullo schermo; si tratta di un esempio che Microchip aveva già incluso nella precedente versione del firmware e rappresenta un valido caso facilmente esportabile. Lo descrive il Listato 1, dove trovate anche le modifiche giugno 2005 - Elettronica In
Corso PIC-USB
; ****************************************************************** ReportDescriptor1 retlw 0x05 retlw 0x01 ; usage page (generic desktop) retlw 0x09 Diventa la TABELLA 6 retlw 0x02 ; usage (mouse) DESCRITTORE REPORT retlw 0xA1 retlw 0x01 ; collection (application) retlw 0x09 I campi vengono modificati in base alle retlw 0x01 ; usage (pointer) nostre esigenze e si riportano le definiretlw 0xA1 zioni di ciascun byte che compone i vari retlw 0x00 ; collection (linked) campi. Anche in questo caso, come per il retlw 0x05 retlw 0x09 ; usage page (buttons) descrittore del corso PIC-USB, ci concenretlw 0x19 triamo sulla definizione di un report suffiretlw 0x01 ; usage minimum (1) cientemente "universale" per riutilizzarlo. retlw 0x29 retlw 0x03 ; usage maximum (3) retlw 0x15 retlw 0x00 ; logical minimum (0) retlw 0x25 retlw 0x01 ; logical maximum (1) retlw 0x95 retlw 0x03 ; report count (3) retlw 0x75 retlw 0x01 ; report size (1) retlw 0x81 retlw 0x02 ; input (3 button bits) retlw 0x95 retlw 0x01 ; report count (1) retlw 0x75 retlw 0x05 ; report size (5) retlw 0x81 retlw 0x01 ; input (constant 5 bit padding) retlw 0x05 retlw 0x01 ; usage page (generic desktop) retlw 0x09 retlw 0x30 ; usage (X) retlw 0x09 retlw 0x31 ; usage (Y) retlw 0x15 retlw 0x81 ; logical minimum (-127) retlw 0x25 retlw 0x7F ; logical maximum (127) retlw 0x75 retlw 0x08 ; report size (8) retlw 0x95 retlw 0x03 ; report count (2) retlw 0x81 retlw 0x06 ; input (2 position byte X & Y) retlw 0xC0 ; end collection retlw 0xC0 ; end collection EndReportDescriptor1 ; ****************************************************************** langids retlw low lang_1 retlw high lang_1 retlw low lang_2 ; String indexes of different languages retlw high lang_2 lang_1 ; English retlw low String0 ; LangIDs Diventa la TABELLA 7 retlw high String0 DESCRITTORE STRING retlw low String1_l1 Per ragioni di spazio non includiamo tutta la retlw high String1_l1 sequenza di stringhe, visto che la sua struttura è retlw low String2_l1 retlw high String2_l1 praticamente identica a quella analizzata durante il retlw low String3_l1 corso. I punti di ingresso delle stringhe definite retlw high String3_l1 seguono una codifica del tipo String+nr.Stringa+l retlw low String4_l1 nr.linguaggio: in pratica, la stringa 1 del linguaggio 2 retlw high String4_l1 è inserita presso la label String1_l2.. retlw low String5_l1 retlw high String5_l1
Corso PIC-USB
che apporteremo per renderlo facilmente replicabile. Si vede chiaramente come l’intero insieme di descrittori risulti molto simile a quello visto durante il Corso. Abbiamo infatti raccolto tutti i campi nelle solite sette tabelle che possono tran-
configurazioni, una con due interfacce e l’altra con quattro, il campo dovrà essere pari a quattro. Finora, nel nostro Corso abbiamo sviluppato esclusivamente dispositivi con un’unica configurazione ed una sola interfaccia.
Tabella 1
quillamente essere riscritte con i valori inclusi nel termodsc.asm del primo esperimento. La vera innovazione è tutta concentrata nella prima parte del file. Le due tabelle che raccolgono i parametri generali e quelli specifici della classe HID non sono contemplate nella precedente versione del firmware e derivano principalmente da una serie di header C18 del framework. La tabella dei parametri generali Vediamo di analizzare nello specifico i singoli campi precisati nella tabella: EP0_BUFF_SIZE: definisce la grandezza del buffer in byte per l’Endpoint0 e quindi implicitamente stabilisce la grandezza dei pacchetti che il dispositivo può scambiare con l’host. In pratica sostituisce il valore del campo bMaxPacketSize0 che si definiva nel Descrittore Device. Per i dispositivi low-speed l’unico valore possibile è 8, mentre nella modalità full-speed si può usare 8, 16, 32 o 64 byte.
LISTATO 2 _PPBM0 _PPBM1 _PPBM2
0x00 0x01 0x02
; Pingpong Buffer Mode 0 ; Pingpong Buffer Mode 1 ; Pingpong Buffer Mode 2
MAX_NUM_INT: è la dimensione dell’array che tiene traccia dei settaggi alternativi relativi a ciascuna interfaccia (si ricordi che un dispositivo può avere più interfacce, ciascuna con diversi settaggi che l’host può selezionare). In particolare, nel caso di un dispositivo con diverse configurazioni è necessario inserire in questo campo il numero di interfacce della configurazione che ne ha di più. Questa condizione è obbligatoria per evitare di indicizzare in maniera errata l’array. Per fare un esempio, se un dispositivo ha più Elettronica In - giugno 2005
MAX_EP_NUMBER: è il massimo numero endpoint utilizzato nel progetto. Si faccia attenzione che stiamo parlando del numero che contraddistingue l’endpoint e non il numero di endpoint fisicamente utilizzati nel progetto. Non si deve considerare l’EP0, valore che viene utilizzato dal gestore di memoria USB18MEM.asm come parametro fondamentale per dimensionare correttamente la BDT e i relativi buffer. Se poniamo MAX_EP_NUMBER a 1 verranno inizializzate quattro BD, due delle quali sono generate di default per l’EP0 (IN e OUT) e servono a veicolare correttamente i trasferimenti di controllo. Le altre due sono quelle relative all’EP1 (IN e OUT) secondo la Tabella 1. Siccome l’indirizzo di allocazione di ciascuna BDT è fissato a livello hardware, un numero elevato di questo parametro dovuto ad un utilizzo non contiguo degli endpoint può comportare una gestione inefficiente della memoria. Infatti, se un progetto utilizza l’EP0 e l’EP4, per comunicare il MAX_EP_NUMBER è pari a 4 (e non a 2). Questo fa sì che il gestore inizializzi anche le BDT per EP1, EP2, EP3; 24 byte che risulteranno allocati ma inutilizzati. Naturalmente ha poco senso saltare degli endpoint ma la precisazione era necessaria visto che la decisione spetta esclusivamente allo sviluppatore. NUM_CONFIGURATIONS: è il numero di configurazioni del dispositivo. Negli esperimenti del Corso è sempre pari a 1. NUM_INTERFACES: è il numero di interfacce del dispositivo. Negli esperimenti del Corso è sempre pari a 1. MODE_PP: le opzioni utilizzabili per questo parametro sono definite nel file USB18.inc (Listato 2). Esse si riferiscono ad una particolare modalità di gestione dei buffer associati a ciascun endpoint. > 85
Fig. 1
In pratica, ad ogni endpoint viene associato un doppio buffer: uno per i trasferimenti pari, ed uno per quelli dispari. In questo modo è possibile ottimizzare il passaggio dati, visto che la CPU può elaborare il contenuto di un buffer mentre il SIE (USB Serial Interface Engine) carica l’altro. L’attuale versione del firmware permette esclusivamente di scegliere la modalità _PPBM0, che è proprio quella che disabilita tale possibilità. Infatti, il valore 0x00 viene registrato nei bit 0-1 del registro UCFG (USB Configuration Register) del PIC che corrisponde alla voce "disabled". UCFG_VAL: questo parametro include una serie di opzioni che sono sempre collegate ai bit del registro UCFG (USB Configuration Register) del PIC. Vediamo i casi più importanti: _LS o _FS: usato per stabilire se utilizziamo la modalità Low-Speed o Full-Speed. Si tenga presente che nel primo caso è necessario un clock in ingresso a 6 MHz, mentre nel secondo caso il segnale deve "viaggiare" ad una frequenza di 48 MHz. Questo valore è direttamente collegato al bit 2 del registro UCFG. I più curiosi diano un’occhiata alla pagina 168 dei datasheet del PIC; _PUEN: usato per stabilire se si vuole usare delle resistenze di pull-up interne per le linee dati USB. In particolare nel caso in cui il precedente parametro sia pari a LS (Low-Speed) il pull-up viene collegato alla linea D-. Nel caso, invece, il precedente parametro sia pari a FS (Full-Speed) il pull-up viene collegato alla linea D+. Si faccia attenzione che sulla demoboard da noi usata (cod. K8055, Futura Elettronica) è già inserita una resistenza di pull-up esterna, pertanto non è necessario abilitare quella interna; _TRINT o _TREXT: stabilisce se, per la comunicazione USB, si utilizza il transponder interno o quello esterno. È collegato al bit3 del registro UCFG. Si tratta di una possibilità offerta da questa nuova famiglia di PIC, che, even86
USE_SELF_POWER_SENSE_IO: il modulo USB può essere alimentato in diverse modalità. Quella che abbiamo utilizzato nei nostri esperimenti è la cosiddetta "Bus Power Only" attraverso la quale il PIC prende l’energia necessaria direttamente dal bus USB. Pertanto la relativa define può essere tranquillamente commentata. È possibile attraverso questo parametro abilitare la possibilità secondo cui il modulo utilizza un apposito pin di I/O per segnalare la presenza di una fonte di energia alternativa al bus. Viene usata, quindi, solo nel caso in cui si alimenti il PIC separatamente. USE_USB_BUS_SENSE_IO: analogamente è possibile utilizzare questo parametro per stabilire un pin di I/O che ha la funzione di segnalare il collegamento ad un bus USB e quindi avviare l’attivazione del modulo relativo. Non utilizziamo tale possibilità quindi non commentiamo questa define. USB_USE_HID: attraverso questa define stabiliamo la classe di dispositivo per cui stiamo sviluppando. Per tutti gli esperimenti del Corso abbiamo utilizzato dei HID (Human Interface Device). Nelle prossime puntate di approfondimento, svilupperemo anche un firmware per una differente classe di dispositivi, chiamata CDC (Communication Device Class: ad esempio modem e interfacce). Vedremo in quella occasione come questa definizione verrà modificata.
Fig. 2
La tabella dei parametri HID Questa struttura raccoglie una serie di parametri che stabiliscono alcune variabili specifiche della classe di dispositivi HID (Human Interface Device) relativamente all’allocazione degli endpoints. Naturalmente, se sviluppassimo per un giugno 2005 - Elettronica In
Corso PIC-USB
tualmente, permette di utilizzare, per la comunicazione, una sorta di bridge esterno. La cosa può essere utile quando è necessario isolare il nostro dispositivo dalla porta USB.
Corso PIC-USB
LISTATO 3 Termodsc.asm VER PIC18 ; *************************** ; TABELLA PARAMETRI GENERALI ; *************************** #define EP0_BUFF_SIZE 8 #define MAX_NUM_INT 1 #define MAX_EP_NUMBER 1 #define NUM_CONFIGURATIONS 1 #define NUM_INTERFACES 1 #define MODE_PP _PPBM0 #define UCFG_VAL _TRINT|MODE_PP #define USB_USE_HID ; **************************** ; TABELLA PARAMETRI CLASSE HID ; **************************** #define HID_INTF_ID 0x00 #define HID_UEP UEP1 #define HID_BD_OUT ep1Bo #define HID_INT_OUT_EP_SIZE 8 #define HID_BD_IN ep1Bi #define HID_INT_IN_EP_SIZE 8 #define HID_NUM_OF_DSC 1 ; **************************** ; TABELLA 1 DESCRITTORE DEVICE ; **************************** DeviceDescriptor retlw (EndDeviceDescriptor-DeviceDescriptor)/2 ; bLength retlw DSC_DEV ; bDescriptorType retlw 0x10 ; bcdUSB (low-b) retlw 0x01 ; bcdUSB (high-b) retlw 0x00 ; bDeviceClass retlw 0x00 ; bDeviceSubClass retlw 0x00 ; bDeviceProtocol retlw EP0_BUFF_SIZE ; bMaxPacketSize0 retlw 0xd8 ; idVendor (low-b) retlw 0x04 ; idVendor (high-b) retlw 0x00 ; idProduct (low-b) retlw 0x00 ; idProduct (high-b) retlw 0x00 ; bcdDevice (low-b) retlw 0x01 ; bcdDevice (high-b) retlw 0x01 ; iManufacturer retlw 0x02 ; iProduct retlw 0x03 ; iSerialNumber retlw NUM_CONFIGURATIONS ; bNumConfigurations EndDeviceDescriptor ; ***************************** ; Puntatore tabella di configurazione. ; ***************************** USB_CD_Ptr Configs db low Config1, high Config1 db low Config1, high Config1 ; ************************************ ; TABELLA 2 DESCRITTORE CONFIGURATION ; ************************************ Config1 retlw (Interface1-Config1)/2; bLengthLength retlw DSC_CFG ; bDescriptorType Config1Len retlw low ((EndConfig1-Config1)/2) ; wTotalLength (low-b) retlw high ((EndConfig1-Config1)/2) ; wTotalLength (high-b)
retlw 0x01 retlw 0x01 bConfigurationValue retlw 0x04 retlw 0xA0 retlw 0x32
; bNumInterfaces ; ; iConfiguration ; bmAttributes ; MaxPower
altro tipo di classe, la tabella dovrebbe essere opportunamente modificata (lo vedremo nel caso dei CDC) assieme alla precedente define. La tabella segue una struttura ripetuta per ciascun endpoint utilizzato nella comunicazione con l’host. Analizziamo i vari campi separatamente nel caso di un HID che utilizza l’EP1 IN e OUT: Elettronica In - giugno 2005
Termodsc.asm VER PIC16 ; **************************************** ; ROUTINE DI INDIRIZZAMENTO CONFIGURATION ; **************************************** OMISSIS ;************************************** ; INSERIMENTO ULTERIORI CONFIGURAZIONI ;************************************** OMISSIS ; ********************************* ; ROUTINE DI INDIRIZZAMENTO REPORT ; ********************************* OMISSIS ;************************************** ;PUNTO DI INSERIMENTO ULTERIORI REPORT ;************************************** OMISSIS ; **************************** ; TABELLA 1 DESCRITTORE DEVICE ; **************************** Descriptions banksel EP0_start movf EP0_start+1,w movwf PCLATH movf EP0_start,w movwf PCL DeviceDescriptor StartDevDescr retlw 0x12 ; bLength retlw 0x01 ; bDescriptorType retlw 0x10 ; bcdUSB (low-b) retlw 0x01 ; bcdUSB (high-b) retlw 0x00 ; bDeviceClass retlw 0x00 ; bDeviceSubClass retlw 0x00 ; bDeviceProtocol retlw 0x08 ; bMaxPacketSize0 retlw 0xd8 ; idVendor (low-b) retlw 0x04 ; idVendor (high-b) retlw 0x00 ; idProduct (low-b) retlw 0x00 ; idProduct (high-b) retlw 0x00 ; bcdDevice (low-b) retlw 0x01 ; bcdDevice (high-b) retlw 0x01 ; iManufacturer retlw 0x02 ; iProduct retlw 0x03 ; iSerialNumber retlw NUM_CONFIGURATIONS ; bNumConfigurations ; ************************************ ; TABELLA 2 DESCRITTORE CONFIGURATION ; ************************************ Config1 retlw 0x09 ; bLengthLength retlw 0x02 ; bDescriptorType retlw EndConfig1 - Config1 ; wTotalLength (low-b) retlw 0x00 ; wTotalLength (high-b) retlw 0x01 ; bNumInterfaces retlw 0x01 ; bConfigurationValue retlw 0x04 ; iConfiguration retlw 0xA0 ; bmAttributes retlw 0x32 ; MaxPower
(continua)
HID_INTF_ID: Definisce l’identificativo dell’interfaccia HID. Viene utilizzato dalla funzione USBCheckHIDRequest (inclusa nel file USB18.asm) che permette di stabilire se la richiesta arrivata al dispositivo è specifica della classe HID e quindi deve essere passata al gestore relativo. Per i nostri dispositivi utilizziamo il valore 0. > 87
; ******************************* ; TABELLA 3 DESCRITTORE INTERFACE ; ******************************* Interface1 retlw 0x09 ; bLength retlw INTERFACE ; bDescriptorType retlw 0x00 ; bInterfaceNumber retlw 0x00 ; bAlternateSetting retlw 0x01 ; bNumEndpoints retlw 0x03 ; bInterfaceClass retlw 0x01 ; bInterfaceSubClass retlw 0x02 ; bInterface Protocol retlw 0x05 ; iInterface ; ************************** ; TABELLA 4 DESCRITTORE HID ; ************************** HID_Descriptor retlw 0x09 ; bLength retlw 0x21 ; bDescriptorType retlw 0x00 ; bcdHID (low-b) retlw 0x01 ; bcdHID (high-b) retlw 0x00 ; bCountryCode retlw 0x01 ; bNumDescriptors retlw 0x22 ; bDescriptorType retlw (end_ReportDescriptor - ReportDescriptor) (low-b) retlw 0x00 ; wDescriptorLength (high-b) ; ******************************* ; TABELLA 5 DESCRITTORE ENDPOINT ; ******************************* Endpoint1 retlw 0x07 ; bLength retlw ENDPOINT ; bDescriptorType retlw 0x81 ; bEndpointAddress retlw 0x03 ; bmAttributes retlw 0x01 ; wMaxPacketSize (low-b) retlw 0x00 ; wMaxPacketSize (high-b) retlw 0xFA ; bInterval EndConfig1 ; ***************************** ; TABELLA 6 DESCRITTORE REPORT ReportDescriptorLen retlw low (end_ReportDescriptor-ReportDescriptor) retlw 0x06 ; Byte di prefisso (bTag,bType,bSize) retlw 0x01;Usage Page (low-b) ("Vendor Defined Page 1") retlw 0xFF;Usage Page (high-b) ("Vendor Defined Page 1") retlw 0x09 ;Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Usage ("Vendor Defined Usage 1") retlw xA1 ;Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Collection ("Application") retlw 0x09 ;Byte di prefisso (bTag,bType,bSize) retlw 0x02 ; Usage ("Vendor Defined Usage 2") retlw 0xA1 ;Byte di prefisso (bTag,bType,bSize) retlw 0x00 ; Collection ("Physical") retlw 0x06 ;Byte di prefisso (bTag,bType,bSize) retlw 0x02;Usage Page (low-b) ("Vendor Defined Page 2") retlw 0xFF;Usage Page (high-b) ("Vendor Defined Page 2") retlw 0x09 ;Byte di prefisso (bTag,bType,bSize) retlw 0x03 ; Usage ("Vendor Defined Usage 3") retlw 0x15 ;Byte di prefisso (bTag,bType,bSize) retlw 0x00 ; Logical Minimum (0) retlw 0x26 ;Byte di prefisso (bTag,bType,bSize) retlw 0xFF ; Logical Maximum (low-b) (255) retlw 0x00 ; Logical Maximum (high-b) retlw 0x75 ;Byte di prefisso (bTag,bType,bSize) retlw 0x08 ; Report Size (8 bits) retlw 0x95 ;Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Report Count (1 campo dati) retlw 0x81 ;Byte di prefisso (bTag,bType,bSize) retlw 0x02 ; Input (Data, Var, Abs) retlw 0xC0 ; End Collection ("Physical") retlw 0xC0 ; End Collection ("Application") EndReportDescriptor
HID_UEP: Stabilisce il registro di controllo del PIC a cui ci riferiamo nelle righe successive per la definizione dei diversi Buffer Descriptors. Nei dispositivi analizzati nel nostro Corso abbiamo
utilizzato esclusivamente l’UEP1 (USB Endpoint 1 Control Register). HID_BD_OUT: Definisce il BD per l’endpoint controllato dal registro precedente (nel caso del
88
giugno 2005 - Elettronica In
Corso PIC-USB
(continuazione Listato 3) ; ******************************* ; TABELLA 3 DESCRITTORE INTERFACE ; ******************************* Interface1 retlw (HIDDescriptor1-Interface1)/2; bLength retlw DSC_INTF ; bDescriptorType retlw 0x00 ; bInterfaceNumber retlw 0x00 ; bAlternateSetting retlw 0x01 ; bNumEndpoints retlw 0x03 ; bInterfaceClass retlw 0x01 ; bInterfaceSubClass retlw 0x02 ; bInterface Protocol retlw 0x05 ; iInterface ; ************************* ; TABELLA 4 DESCRITTORE HID ; ************************* HID_Descriptor1 retlw (Endpoint1-HIDDescriptor1)/2 ; bLength retlw 0x21 ; bDescriptorType retlw 0x00 ; bcdHID (low-b) retlw 0x01 ; bcdHID (high-b) retlw 0x00 ; bCountryCode retlw 0x01 ; bNumDescriptors retlw 0x22 ; bDescriptorType ReportDescriptor1Len retlw low ((EndReportDescriptor1-ReportDescriptor1)/2) retlw high ((EndReportDescriptor1-ReportDescriptor1)/2) ; ******************************* ; TABELLA 5 DESCRITTORE ENDPOINT ; ******************************* Endpoint1 (EndConfig1-Endpoint1)/2 ; bLength retlw DSC_EP ; bDescriptorType retlw retlw 0x81 ; bEndpointAddress retlw 0x03 ; bmAttributes retlw 0x01 ; wMaxPacketSize (low-b) retlw 0x00 ; wMaxPacketSize (high-b) retlw 0xFA ; bInterval EndConfig1 ; ****************************** ; TABELLA 6 DESCRITTORE REPORT ; ****************************** ReportDescriptor1 retlw 0x06 ; Byte di prefisso (bTag,bType,bSize) retlw 0x01;Usage Page (low-b) ("Vendor Defined Page 1") retlw 0xFF;Usage Page (high-b) ("Vendor Defined Page 1") retlw 0x09 ;Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Usage ("Vendor Defined Usage 1") retlw xA1 ;Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Collection ("Application") retlw 0x09 ;Byte di prefisso (bTag,bType,bSize) retlw 0x02 ; Usage ("Vendor Defined Usage 2") retlw 0xA1 ;Byte di prefisso (bTag,bType,bSize) retlw 0x00 ; Collection ("Physical") retlw 0x06 ;Byte di prefisso (bTag,bType,bSize) retlw 0x02;Usage Page (low-b) ("Vendor Defined Page 2") retlw 0xFF;Usage Page (high-b) ("Vendor Defined Page 2") retlw 0x09 ;Byte di prefisso (bTag,bType,bSize) retlw 0x03 ; Usage ("Vendor Defined Usage 3") retlw 0x15 ;Byte di prefisso (bTag,bType,bSize) retlw 0x00 ; Logical Minimum (0) retlw 0x26 ;Byte di prefisso (bTag,bType,bSize) retlw 0xFF ; Logical Maximum (low-b) (255) retlw 0x00 ; Logical Maximum (high-b) retlw 0x75 ;Byte di prefisso (bTag,bType,bSize) retlw 0x08 ; Report Size (8 bits) retlw 0x95 ;Byte di prefisso (bTag,bType,bSize) retlw 0x01 ; Report Count (1 campo dati) retlw 0x81 ;Byte di prefisso (bTag,bType,bSize) retlw 0x02 ; Input (Data, Var, Abs) retlw 0xC0 ; End Collection ("Physical") retlw 0xC0 ; End Collection ("Application") EndReportDescriptor1
Corso PIC-USB
nostro esempio è il numero 1) nella direzione host->device. Allo scopo si utilizza una convenzione di denominazione secondo il seguente formato: ep<#>B<d>. In esso # è il numero dell’endpoint mentre d è la direzione di trasferimento, direzione che può essere sia IN (i) che OUT (o). Nel nostro caso utilizzeremo solamente l’endpoint numero 1, con direzione "o" cioè OUT, quindi "ep1Bo".
HID_BD_IN: È analogo al HID_BD_OUT e riguarda l’endpoint con direzione device->host (IN). Si utilizza la stessa convenzione di denominazione, pertanto, il valore è "ep1Bi". HID_INT_IN_EP_SIZE: Definisce la grandezza in byte del buffer dell’endpoint della riga precedente. Anche qui imponiamo un valore di 8. HID_NUM_OF_DSC: Definisce il numero di descrittori HID del dispositivo e, nei nostri pro-
LISTATO 4 ' VERSIONE PER PIC18F 'Dichiarazioni variabili applicazione temper VAR BYTE DEFINE OSC 24 ' Clock 24Mhz 'DEFINE SHOW_ENUM_STATUS 1 'Visualizza Enumerazione ' Definizioni per l'utilizzo dell'istruzione ADCIN DEFINE ADC_BITS 8 ' Bits Risultato DEFINE ADC_CLOCK 3 ' Clock RC TAD=4uS/bit ' 9,5TAD/byte DEFINE ADC_SAMPLEUS 50 ' Frequenza Campionamento uS PORTB = 0 ' 8 LED uscite digitali spenti TRISB = 0 ' PORTB definita in uscita INIZIO: USBInit fine il device
di rif
in temp INVIA: tato
Pause 500
PORTB = 0 TRISB = 0
' Processo di enumerazione alla
' entra nello stato Configurato TRISA = %11111111 ' PORTA tutta in ingresso ADCON1= 4 ' [DDDDADAA] RA0,RA1 analogici Vdd
CONV:
'VERSIONE PER PIC16C 'Dichiarazioni variabili necessarie firmware USB wsave VAR BYTE $70 system 'salva W ssave VAR BYTE bank0 system 'salva STATUS psave VAR BYTE bank0 system 'salva PCLATH fsave VAR BYTE bank0 system 'salva FSR 'Dichiarazioni variabili applicazione temper VAR BYTE DEFINE OSC 24 DEFINE SHOW_ENUM_STATUS 1 ' Definizioni per l'utilizzo dell'istruzione ADCIN DEFINE ADC_BITS 8 DEFINE ADC_CLOCK 3 DEFINE ADC_SAMPLEUS 50
' Attesa
ADCIN 0, temper
GoTo INIZIO DEFINE Asm BUSINT
'Campiona segnale su RA0
USBService 'Gestione USB USBOut 1, temper, 1, INVIA
'Invia risul-
RIPREG
GoTo CONV ' Continua all'infinito
cipale EndAsm INIZIO:
CONV:
HID_INT_OUT_EP_SIZE: Stabilisce la grandezza in byte del buffer dell’endpoint precisato nella riga precedente. Nel nostro caso utilizziamo un valore pari a 8. Elettronica In - giugno 2005
' Salta al main
INTHAND BUSINT movf movwf movlw movwf btfsc Call clrf movf movwf movf movwf swapf movwf swapf swapf retfie
FSR, W ;salvataggio di FSR fsave High ServiceUSBInt PCLATH PIR1, USBIF ServiceUSBInt STATUS fsave, W FSR psave, W PCLATH ssave, W STATUS wsave, F wsave, W
; Torno al pgm prin-
USBInit TRISA = %11111111 ADCON1= 4 Pause 500 Pause 100 ADCIN 0, temper Pause 150 USBOut 1, temper, 1, CONV GoTo CONV
getti, equivale sempre a 1. Detto questo, possiamo finalmente iniziare a riscrivere il nostro descrittore per il Termo-USB (si veda il relativo articolo) utilizzando il nuovo framework. > 89
Per creare la nuova versione del file in maniera da capire quali sono le modifiche concrete rispetto al vecchio termodsc.asm, abbiamo pensato di affiancare i due listati. Non è difficile identificare, in entrambe le versioni, le strutture dei diversi descrittori. Se escludiamo le due tabelle iniziali di parametri ed il fatto che, in varie occasioni, si sono utilizzati dei nomi costante anziché valori discreti, possiamo tranquillamente affermare che le modifiche comportano, per lo sviluppatore, ben poco lavoro. La convenienza nel passaggio alla nuova famiglia di PIC è quindi reale, visto che a fronte di poche modifiche firmware si possono sfruttare delle feature molto interessanti. Nel nuovo listato abbiamo evidenziato le parti che più lo differenziano dal vecchio. Per questioni di spazio omettia-
ancora più semplice, visto che il gestore di ciascuna attività della porta USB è stato concentrato tutto in un’unica funzione chiamata USBService. Il codice risultante non è più basato sulla gestione dei segnali di interrupt ma esclusivamente su una continua interrogazione dei registri associati alla porta per identificare le varie operazioni che devono essere svolte. Ci troviamo perciò nella condizione di richiamare regolarmente (almeno ogni 10 ms) tale funzione. Mettiamo a confronto i due listati visibili contemporaneamente nel Listato 4. Nella nuova versione si vede chiaramente la mancanza di tutta la parte inerente alla gestione degli interrupt del modulo USB, sostituita direttamente dalla routine USBService. Tale routine è richiamata sia subito dopo l’operazione di campionamento, sia tutte le volte che nel buffer del-
Fig. 3
mo i descrittori stringa, perchè sono praticamente identici sia per struttura che contenuto in entrambe le versioni. Le modifiche inserite nel file dei descrittori sono piuttosto semplici da realizzare; si richiede soltanto un po’ di sforzo iniziale per "digerire" il significato dei parametri inclusi nelle prime due tabelle (Param. Generali e HID). Il file USBDESC.asm permette di collegare il file dei descrittori al nostro progetto e rimane essenzialmente identico visto che abbiamo chiamato le due versioni allo stesso modo. L’istruzione di inclusione relativa è visibile nel Listato 5. Siamo arrivati quasi alla conclusione della nostra operazione di riscrittura. Dobbiamo ancora affrontare le modifiche al programma principale. Il codice del programma principale Con la nuova versione del compilatore PICBasic il listato del nostro termometro USB diventa
LISTATO 5 include "termodsc.asm" ;Descrittori TermoUSB-Esp.1
90
l’endpoint1 non c’è nulla da inviare. Per quanto riguarda il resto del listato la sequenza di istruzioni è praticamente identica, abbiamo soltanto aggiunto la label INVIA affinché l’USBService venga richiamato ad intervalli di tempo quanto più brevi possibili. A questo punto non ci resta che ricompilare il nostro firmware. La compilazione del nuovo termousb.bas Per generare il file .hex da caricare nel PIC18F2455/2550 dobbiamo eseguire il compilatore PBP con l’opzione -a. Tale necessità si spiega facilmente pensando che il PBP, se la compilazione non ha errori, avvia automaticamente l’assembler PM; il problema è che il PM non supporta questa famiglia di microcontrollori. Se richiamiamo il compilatore alla solita maniera riceveremo in uscita il messaggio di Fig. 3. Dobbiamo quindi avviare il compilatore facendo in modo che sostituisca il PM con un altro assembler compatibile con la nuova famiglia di PIC. Lo stesso messaggio di errore ci indica la giugno 2005 - Elettronica In
Corso PIC-USB
Il nuovo file dei descrittori del Termo-USB
Corso PIC-USB
Fig. 4
strada da seguire: utilizzeremo l’MPASM (assembler) che esiste in due versioni: 1) MPASMWIN: a 32 bit per windows distribuita gratuitamente assieme all’IDE MPLAB o scaricabile direttamente dal sito della microengineering Labs (www.melabs.com) 2) MPASM: versione da linea di comando distribuita assieme al compilatore C18 della Microchip (www.microchip.com). Entrambe sono compatibili praticamente con tutte le versioni dei sistemi operativi Microsoft (9X, ME, NT, 2K, XP); quella a linea di comando non supporta i nomi dei file lunghi, pertanto è necessario utilizzare il formato 8.3 (nome file di
otto caratteri, estensione di tre). Nel primo caso dobbiamo utilizzare il comando: pbpw -ampasmwin -p18f2550 termousb.bas Al termine del processo di compilazione, se niente è andato storto, comparirà una finestra come quella di Fig. 4. Nel secondo caso la stringa da digitare sulla linea di comando diventa: pbp -ampasm -p18f2550 termousb.bas Anche qui, al termine della compilazione, avremo un messaggio di avvertimento (vedi Fig. 5). >
Fig. 5
Elettronica In - giugno 2005
91
Il Clock C’è ancora un piccolo problema da risolvere e lo affrontiamo ora perché direttamente collegato ad un file di configurazione utilizzato dall’MPASM. Il compilatore PICBasic PRO presuppone che
CONFIG1H). Questi particolari parametri sono letti dall’MPASM e vengono elencati nel file p18f2550.inc incluso nella directory del compilatore. Li riassumiamo nel Listato 7. C’è da dire che la famiglia di PIC della quale ci occupiamo permette diverse modalità di generazione del segnale di clock: quella che il compilatore in questione per impostazione predefinita impiega un moltiplicatore PLL (Phase Lock
LISTATO 6 __CONFIG __CONFIG
_CONFIG1L, _PLLDIV_5_1L & _CPUDIV_OSC1_PLL2_1L & _USBDIV_2_1L _CONFIG1H, _FOSC_HSPLL_HS_1H
nel nostro progetto venga usata una sorgente di clock a 20 MHz. Infatti, se andiamo a verificare gli header inclusi durante la compilazione, troveremo il file 18F2550.INC (naturalmente il nome dipende dal dispositivo scelto tramite l’opzione -p durante la compilazione). L’archivio in questione è salvato nella directory \pbp e contiene due stringhe fondamentali meglio definite nel Listato 6. Esse stabiliscono i valori di due registri di configurazione piuttosto importanti (CONFIG1L e
Loop) con un segnale in uscita a 96 MHz che viene opportunamente suddiviso per estrarre il clock di sistema. Nel registro CONFIG1L si precisano tre campi di base: PLLDIV (3bit): permette la selezione del circuito prescaler, nella riga di default viene precisato il valore che divide per 5 (PLLDIV=100b) il segnale in ingresso. Il PLL necessità di un segnale a 4 MHz, pertanto è corretto che i 20 MHz provenienti dall’oscillatore siano divisi per cinque.
LISTATO 7 ;----- CONFIG1L Options -------------------------------------------------_PLLDIV_1_1L EQU H'F8' ; No divide (4MHz input) _PLLDIV_2_1L EQU H'F9' ; Divide by 2 (8MHz input) _PLLDIV_3_1L EQU H'FA' ; Divide by 3 (12MHz input) _PLLDIV_4_1L EQU H'FB' ; Divide by 4 (16MHz input) _PLLDIV_5_1L EQU H'FC' ; Divide by 5 (20MHz input) _PLLDIV_6_1L EQU H'FD' ; Divide by 6 (24MHz input) _PLLDIV_10_1L EQU H'FE' ; Divide by 10 (40MHz input) _PLLDIV_12_1L EQU H'FF' ; Divide by 12 (48MHz input) _CPUDIV_OSC1_PLL2_1L _CPUDIV_OSC2_PLL3_1L _CPUDIV_OSC3_PLL4_1L _CPUDIV_OSC4_PLL6_1L _USBDIV_1_1L _USBDIV_2_1L
EQU EQU EQU EQU EQU EQU
H'E7' H'EF' H'F7' H'FF' H'DF' H'FF'
; ; ; ;
[OSC1/OSC2 [OSC1/OSC2 [OSC1/OSC2 [OSC1/OSC2
Src: Src: Src: Src:
/1][96MHz /2][96MHz /3][96MHz /4][96MHz
PLL PLL PLL PLL
Src: Src: Src: Src:
/2] /3] /4] /6]
; Clock source from OSC1/OSC2 ; Clock source from 96MHz PLL/2
;----- CONFIG1H Options -------------------------------------------------_FOSC_XT_XT_1H EQU H'F0' ; XT oscillator, XT used by USB _FOSC_XTPLL_XT_1H EQU H'F2' ; XT oscillator, PLL enabled, XT used by USB _FOSC_ECIO_EC_1H EQU H'F4' ; External clock, port function on RA6, EC used by USB _FOSC_EC_EC_1H EQU H'F5' ; External clock, CLKOUT on RA6, EC used by USB _FOSC_ECPLLIO_EC_1H EQU H'F6' ; External clock, PLL enabled, port function on RA6, EC used by USB _FOSC_ECPLL_EC_1H EQU H'F7' ; External clock, PLL enabled, CLKOUT on RA6, EC used by USB _FOSC_INTOSCIO_EC_1H EQU H'F8' ; Internal oscillator, port function on RA6, EC used by USB _FOSC_INTOSC_EC_1H EQU H'F9' ; Internal oscillator, CLKOUT on RA6, EC used by USB _FOSC_INTOSC_XT_1H EQU H'FA' ; Internal oscillator, XT used by USB _FOSC_INTOSC_HS_1H EQU H'FB' ; Internal oscillator, HS used by USB _FOSC_HS_1H EQU H'FC' ; HS oscillator, HS used by USB _FOSC_HSPLL_HS_1H EQU H'FE' ; HS oscillator, PLL enabled, HS used by USB _FCMEM_OFF_1H _FCMEM_ON_1H
EQU EQU
H'BF' H'FF'
; Disabled ; Enabled
_IESO_OFF_1H _IESO_ON_1H
EQU EQU
H'7F' H'FF'
; Disabled ; Enabled
92
giugno 2005 - Elettronica In
Corso PIC-USB
In entrambe le eventualità avremo generato nella stessa directory il file termousb.hex.
Corso PIC-USB
CPUDIV (2bit): permette la selezione del divisore da applicare al segnale di partenza da 96 MHz. Per impostazione predefinita il valore è pari a 2 (CPUDIV=01b) pertanto il clock di sistema "viaggia" a 48 MHz. Quest’ultima frequenza va bene per la modalità full-speed, ma non per quella low-speed, per la quale sarà necessario utilizzare il divisore pari a quattro (CPUDIV=11b).
i settaggi da precisare (con oscillatore a 20 MHz) nelle due modalità. Quindi, modifichiamo il file \pbp\18F2550.inc inserendo la stringa CPUDIVOSC3_PLL4_1L al posto di quella prevista di default e ricompiliamo il nostro firmware. A questo punto non ci resta che sostituire il quarzo a 6 MHz della demoboard con uno a 20 MHz variando anche la coppia di condensatori C4 e C5 da 33 pF con altri due da 15 pF. Programmiamo il PIC
Tabella 2
USBDIV (1bit): permette di stabilire se il clock viene prelevato dal moltiplicatore PLL (USBDIV=1b) oppure direttamente dall’oscillatore senza il postscaler. Questo campo è direttamente collegato al registro UCFG ed in particolare al bit 2 che stabilisce se la modalità full-speed è attivata o meno. Questo bit viene controllato attraverso il parametro _FS -- _LS che abbiamo visto nella tabella parametri generali del file dei descrittori. Nel registro CONFIG1H, invece si stabilisce con un valore a 4 bit il tipo di oscilla-
e inseriamolo sulla demoboard sostituendo la precedente versione finestrata del micro. Possiamo tranquillamente utilizzare i programmi presentati durante il Corso per collegarci al nostro dispositivo e verificare che il suo funzionamento sia corretto. Bene, siamo arrivati a riscrivere i nostri vecchi progetti per adeguarli alle caratteristiche del nuovo firmware Microchip. I nostri sforzi però non finiscono qua. Sarebbe poco "furbo" non sfruttare tutte le funzionalità e le prestazioni di questi nuovi chip.
tore. Per impostazione predefinita il firmware usa l’HSPLL (FOSC0-3=111xb) quindi un oscillatore ad alta velocità con il PLL attivato. (HSPLL High Speed Crystal/Resonator with PLL). Riassumiamo (nella Tabella 2) quali sono
Pertanto, nel prossimo numero inizieremo parlando di modalità full-speed e di trasferimenti Bulk e Isocroni. Capiremo che cosa sono i dispositivi CDC e vedremo uno sviluppo firmware che li coinvolge. Dunque: alla prossima!
Elettronica In - giugno 2005
93
Corso PIC-USB
Corso di programmazione per PIC: l’interfaccia USB B Alla scoperta della funzionalità USB implementata nei microcontrollori Microchip PIC18F2455 e 18F2550. In questa puntata affrontiamo le modalità di trasferimento Low e Full Speed ed i trasferimenti Bulk e isocroni, presentando un’applicazione di sicuro interesse: un convertitore da USB a C. seriale RS232-C Approfondiremo inoltre alla conoscenza della classe dei Comunication Device USB.
9
a cura di Carlo Tauraso ontinuiamo il discorso anticipato al termine della precedente puntata con una breve digressione sulla modalità full-speed, grazie alla quale vi chiarirete le idee prima di affrontare il nuovo esempio di sviluppo firmware. Modalità Full-Speed I nuovi chip PIC18F2455/2550 sono compatibili con le specifiche USB2.0 e vengono utilizzati secondo due possibili modalità: Low-Speed: permette i trasferimenti Interrupt e Control con un transfer rate massimo da 1,5 Mb/s, trasferimenti largamente utilizzati negli esempi del corso. Full-Speed: permette due ulteriori tipi di trasferimento per notevoli quantità di dati, denominati "Bulk" e Isocroni, caratterizzati da un transfer rate massimo di 12 Mb/s. Questa seconda modalità non è utilizzabile nella precedente famiglia di microprocessori, della quale rappresenta un’interessante evoluzione. Elettronica In - luglio / agosto 2005
Siccome è importante capire bene la differenza tra questi due tipi di trasferimento dati, di seguito esponiamo uno per uno i dettagli caratteristici. Bulk: sono usati per elevate quantità di dati sequenziali, per le quali deve essere garantita l’integrità, come nel caso di sequenze inviate verso una stampante o da uno scanner; il rapporto tra il numero di byte inviati e il tempo non è costante, pertanto l’utilizzo di banda sul bus USB può variare a seconda delle necessità. Sicccome una Pipe bulk può trasferire dati in un’unica direzione, se si desidera una comunicazione bidirezionale bisogna predisporre due Pipe, una per ciascun verso. Ogni Endpoint isocrono può veicolare pacchetti con una lunghezza di 8, 16, 32 e 64 byte. Tale lunghezza è stabilita nel campo wMaxPacketSize del descrittore ed è presa in considerazione anche dal lato host per verificare che i dati inviati verso il dispositivo client non la superino mai. Isocroni: servono per lunghe sequenze di dati quando il tempo di trasferimento è un parametro determinante, mentre non è necessaria la garan- > 83
CDC Communication Device Class Grazie alla piena compatibilità della nuova famiglia di chip con la modalità Full-Speed, i micro possono emulare una nuova tipologia di dispositivi che va ad aggiungersi a quella HID già sufficientemente analizzata durante il corso, una classe che definisce una serie di specifiche che descrivono dei modelli di comunicazione per dispositivi come telefoni, modem, interfacce Ethernet ecc. In realtà vengono definite tre diverse classi che formano un’architettura in grado di supportare qualunque tipo di dispositivo di comunicazione; esse sono: Communication Device Class: contiene una serie di definizioni utilizzate dall’host per identificare un dispositivo che può contenere diversi tipi di interfacce. Permette, quindi all’host di capire, ad esempio, quali protocolli di comunicazione sono gestibili dal dispositivo. Si faccia attenzione che già tale fatto differenzia notevolmente la classe CDC da quella HID presentata nelle precedenti puntate del corso, che utilizzava un’unica interfaccia. Communication Interface Class: descrive un meccanismo generale per realizzare qualunque servizio di comunicazione sul bus USB. 84
Data Interface Class: descrive un meccanismo generale per realizzare delle transazioni bulk o isocrone tramite il bus USB; in particolare essa viene utilizzata quando il formato di trasmissione non è assimilabile ad una classe già definita nelle specifiche: ad esempio l’audio. Un dispositivo di comunicazione può quindi presentare più interfacce; per comprendere bene questa affermazione si prenda ad esempio un normale telefono: secondo un modello presentato nelle specifiche CDC esso deve essere composto da almeno una Communication Interface Class, ma per essere funzionale allo scopo bisogna aggiungergli una tastiera numerica, che si può descrivere solo attraverso una HID class. Si comprende quindi come tali oggetti possano, aggiungendo via-via delle interfacce, diventare piuttosto complessi. Dunque, i nostri HID divengono dei dispositivi facenti parte di un sistema cooperativo ben più vasto. Si potrebbe pensare di aggiungere al telefono un’ulteriore interfaccia dati in grado di operare come un modem, al fine di inviare e ricevere stream sulla linea stessa: modelli di questo genere sono stati utilizzati, ad esempio, in alcuni tipi di telefono (di un noto marchio tedesco) che permettevano di gestire le chiamate sia voce che dati direttamente dal proprio PC, sfruttando un collegamento ISDN. Naturalmente in questa sede non abbiamo lo spazio necessario a descrivere tutti i modelli presenti nelle specifiche CDC, per questo rimandiamo al sito www.usb.org chi fosse interessato ad approfondire l’argomento: vi troverà tutti i dettagli nei documenti ufficiali che da esso si possono scaricare. Per la nostra trattazione abbiamo scelto un modello che, oltre ad essere particolarmente versatile, è anche decisamente utile, visto che permette la costruzione dei cosiddetti emulatori seriali su bus USB: si tratta di un esempio che inizialmente è stato descritto dalla Microchip come applicativo, ma che poi è stato ripreso anche dall’azienda produttrice del compilatore PICBasic che sicuramente molti di voi avranno utilizzato nei propri progetti. La possibilità di sviluppare codice direttamente in un linguaggio semplice e diffuso, nonchè la grande utilità del progetto, ci ha spinto ad analizzarlo in dettaglio, dando a tutti la possibilità di implementarlo. Inoltre la presenza di diverse funzionalità Microchip (scritte in C18) per la gestione della conversione RS232-USB, ci permetterà di terminare il nostro percorso nel vasto mondo luglio / agosto 2005 - Elettronica In
Corso PIC-USB
zia dell’integrità delle informazioni; il data/rate è costante. In questa categoria si contemplano le applicazioni di "data streaming", per le quali la perdita di qualche byte non influenza il risultato finale: ad esempio la trasmissione di segnale audio digitale. L’integrità dei dati inviati non viene controllata (come avviene, invece, nelle sequenze Bulk) perché non c’é tempo di ritrasmettere i dati stessi senza degradare il servizio. L’utilizzo di banda del bus è fisso, per garantire un flusso ininterrotto di dati che devono essere inviati in modo da non perderne la temporizzazione. Una Pipe isocrona può trasferire dati in un’unica direzione, quindi se occorre una comunicazione bidirezionale si devono predisporre due Pipe, una per ciascun verso. Ogni Endpoint isocrono può veicolare pacchetti con una lunghezza fino a 1023 byte. Queste tipologie di trasferimento possono essere molto utili per espandere i campi di utilizzo delle circuiterie equipaggiate con la nuova famiglia di PIC. Quando si vuole realizzare un progetto, si tenga ben presente la differenza tra le diverse modalità di trasferimento: ciò perché ciascuna di esse ha dei precisi limiti e potenzialità.
Corso PIC-USB
delle applicazioni USB PIC presentando un ambiente di sviluppo per certi versi più professionale, sebbene un po’ più complesso. Non ci resta che iniziare presentando un’altra applicazione pratica a corollario del corso: la costruzione di un’interfaccia USB-RS232. Si noti che le informazioni relative a questo progetto sono facilmente utilizzabili per dotare di un’interfaccia USB il proprio modem analogico, il cavo data-link del cellulare e, in generale, qualsiasi dispositivo funzionante in RS-232. È doveroso dire che questo esempio è stato già presentato sia dalla Microchip che dalla Microengineering Lab, tant’è che nella versione
molti è sorto il problema di adattare i propri prodotti per renderli compatibili al nuovo sistema di comunicazione; i primi sono stati senz’altro i produttori di modem, apparecchi che fino al 2000 erano prevalentemente seriali: la migrazione verso il nuovo sistema avrebbe certamente comportato dei grossi problemi, visto che si sarebbe dovuto riscrivere buona parte delle applicazioni host e che sarebbe occorso operare un radicale re-engineering delle circuiterie. Il problema è stato risolto realizzando un modello di dispositivo USB in grado di emulare pienamente il comportamento della canonica seriale. Così facendo, dal lato host non si è obbligati ad effet-
Tabella 1
2.46 del PicBASIC Pro troviamo il necessario firmware. Noi ne diamo una descrizione un po’ più dettagliata, approfondendo gli argomenti relativi alla costruzione dei descrittori e all’implementazione, consegnando a tutti voi gli strumenti e le informazioni necessarie a comprenderne tutti gli aspetti. In particolare, tutto il firmware è stato riordinato evidenziando in strutture tabellari le varie parti che lo compongono: per ogni tabella, inoltre, affianchiamo la descrizione teorica di ciascun campo riferendoci alle specifiche ufficiali CDC. Tutto ciò ha un intento didattico, ma nello stesso tempo vuole sposare la filosofia secondo la quale osservare il funzionamento di un dispositivo non ha valore senza aver capito il perché del suo funzionamento. La sezione 3.6.2.1 delle specifiche CDC Questa sezione presenta un modello di controllo astratto per l’emulazione seriale, che nasce principalmente per fornire ai produttori di modem analogici la documentazione necessaria al fine di migrare dall’interfaccia RS-232 verso l’USB. Negli ultimi anni si è palesata sempre più la tendenza da parte dei maggiori produttori di computer ad eliminare le porte COM, a pieno favore dello standard USB. È quindi chiaro che per Elettronica In - luglio / agosto 2005
tuare alcun cambiamento, visto che il sistema operativo vede il dispositivo stesso come una porta seriale virtuale che può essere gestita con le funzioni degli stessi applicativi previsti per il controllo di un modem seriale. Invece, sulla parte device, lo schema circuitale precedente rimane invariato: si aggiunge soltanto, a monte delle linee di comunicazione seriale, un apposito chip in grado di gestire il bus USB (un PIC18F2550 va benissimo). Per il corretto funzionamento del modello sono necessarie due interfacce: una Communication Class Interface ed una Data Class Interface. La prima utilizza un endpoint IN con trasferimenti di tipo interrupt e serve a notificare all’host lo stato della connessione. La seconda, invece, impiega due endpoint, uno IN ed uno OUT, con trasferimenti di tipo bulk per gestire il trasferimento delle sequenze di byte attraverso l’interfaccia USB-RS232. Nella documentazione viene descritta una serie di richieste specifiche che devono essere opportunamente gestite dalla classe. L’insieme necessario e sufficiente al corretto funzionamento di un dispositivo di comunicazione seriale è riassunto nella Tabella 1. Volendo farsi un’idea della funzione di tali richieste, si consideri che per inviare al dispositivo un comando del tipo ATDT (chiamata a toni) verrà utilizzata la richiesta SEND_ENCAPSULATED_COMMAND. Analogamente, se 85
>
USB-RS232
Corso PIC-USB
IL CONVERTITORE
ELENCO COMPONENTI: R1: 10 kohm R2: 470 ohm R3: 470 ohm R4: 18 kohm C1, C2: 15 pF ceramico C3: 1 µF 100 VL elettrolitico C4: 1 µF 100 VL elettrolitico
C5: 1 µF 63 VL elettrolitico C6: 1 µF 63 VL elettrolitico C7: 220 nF multistrato U1: PIC18F2550 (MF593) U2: MAX232 LD1: led 3 mm verde LD2: led 3 mm rosso
Varie: - Connettore USB-A - Connettore DB9 maschio - Zoccolo 14+14 - Zoccolo 8+8 - Circuito stampato codice S0593
Schema elettrico e piano di cablaggio del convertitore USB-RS232 realizzato col nuovo micro PIC18F2550. Il firmware può essere scaricato gratuitamente dal sito www.elettronicain.it l’host vorrà settare la velocità di comunicazione (ad esempio 9600 bps) utilizzerà il comando SET_LINE_CODING. Tali richieste sono previste dal firmware incluso nel PICBasic: in particolare, ne troviamo un gestore direttamente nel file USB18.asm inserito nel percorso \pbp\USB18. I più curiosi possono verificarlo ricercando all’interno dello stesso file le stringhe relative alle richieste in questione. 86
Esperimento n. 4 - Un’interfaccia USB-RS232 Il circuito Lo schema circuitale è piuttosto semplice, visto che buona parte delle funzioni è gestita direttamente dal firmware caricato nel PIC; esternamente è stato necessario aggiungere solamente un convertitore per i segnali da TTL a RS-232 costituito dal diffusissimo MAX232. luglio / agosto 2005 - Elettronica In
Corso PIC-USB
Con pochi componenti di contorno, grazie ad una pompa di tensione interna, questo integrato è in grado di convertire i livelli logici TTL (0/5 V) nei ±10 V utilizzati nella porta seriale del PC. Un’altra cosa che probabilmente avrete notato è che sulle linee dati dell’USB non abbiamo inserito alcuna resistenza di pull-up: anzi, lo stesso pin Vusb che, nei precedenti progetti forniva la necessaria tensione, è ora collegato al GND tramite C7. Tutto ciò è possibile perché su questo tipo di chip le resistenze di pull-up sono integrate ed è possibile controllarne il collegamento attraverso due bit del registro UCFG: in pratica il bit UPUEN (bit 4) stabilisce se esse sono abilita-
Fig. 1
sitivi CDC, scrivendo direttamente la struttura necessaria al nostro esperimento. Innanzitutto vediamo il descrittore di dispositivo nel Listato 1: se lo mettiamo a confronto con il descrittore
LISTATO 1 DeviceDescriptor retlw (EndDeviceDescriptor-DeviceDescriptor)/2 retlw DSC_DEV ; bDescType retlw 0x10 ; bcdUSB (low-b) retlw 0x01 ; bcdUSB (high-b) retlw CDC_DEVICE ; bDeviceClass retlw 0x00 ; bDeviceSubClass retlw 0x00 ; bDeviceProtocol retlw EP0_BUFF_SIZE ; bMaxPacketSize retlw 0xD8 ; idVendor (low-b) retlw 0x04 ; idVendor (high-b) retlw 0x00 ; idProduct (low-b) retlw 0x00 ; idProduct (high-b) retlw 0x00 ; bcdDevice (low-b) retlw 0x01 ; bcdDevice (high-b) retlw 0x01 ; iManufacturer retlw 0x02 ; iProduct retlw 0x03 ; iSerialNumber retlw NUM_CONFIGURATIONS ; bNumConfigurations EndDeviceDescriptor
te, mentre il bit FSEN (bit 2) precisa la linea sulla quale una di esse lavora. Se FSEN = 0 il PIC è un dispositivo Low-Speed e il pull-up è sulla linea D-, mentre se FSEN = 1 il micro lavora come un Full-Speed e il pull-up insiste sulla linea D+.
; bLength
del nostro TermoUSB ci accorgiamo che l’unica differenza è quella relativa al campo bDeviceClass, che precisa la classe cui appartiene il dispositivo. In questo caso viene utilizzata una costante pari a 2, così come stabilito nelle
LISTATO 2 Config1
retlw retlw Config1Len retlw retlw retlw retlw retlw retlw retlw
9 DSC_CFG
; bLength ; bDescType
low ((EndConfig1 - Config1)/2) ; wTotalLength (low-byte) high ((EndConfig1 - Config1)/2) ; wTotalLength (high-byte) NUM_INTERFACES ; bNumInterfaces 0x01 ; bConfigurationValue 0x02 ; iConfiguration 0x80 ; bmAttributes 0x50 ; Maxpower
Come vedremo nei prossimi paragrafi, il valore del registro UCFG viene stabilito all’inizio del file dei descrittori. In Fig. 1 trovate una tipica configurazione full-speed con un pull-up esterno. Il file dei descrittori Spieghiamo le differenze fondamentali tra i descrittori per dispositivi HID e quelli per dispoElettronica In - luglio / agosto 2005
specifiche CDC. Passiamo quindi al descrittore Configuration (Listato 2); continuiamo il confronto e focalizziamo la nostra attenzione sul campo bNumInterfaces. Nel caso del HID, TermoUSB avevamo messo il valore 1, mentre ora si inserisce una costante (NUM_INTERFACES) pari a 2. Ricordiamo, infatti, che stiamo realizzando un dispositivo con una doppia interfaccia costituita da una Communication Interface > 87
retlw retlw retlw retlw retlw retlw retlw retlw retlw
9 ; bLength DSC_INTF ; bDescriptorType INTERFACE 0x00 ; bInterfaceNumber 0x00 ; bAlternateSetting 0x01 ; bNumEndpoints COMM_INTF ; bInterfaceClass ABSTRACT_CONTROL_MODEL ; bInterfaceSubClass V25TER ; bInterfaceProtocol 0x00 ; iInterface
ed una Data Interface. Il resto dei campi è praticamente identico, tranne per quanto riguarda l’indice puntatore alla stringa descrittiva della configurazione: si tratta soltanto di una modifica formale che non comporta alcun cambiamento funzionale; la stessa cosa vale per MaxPower.
Tabella 2
la gestione dei comandi AT ed è descritto completamente in uno standard internazionale la cui documentazione di riferimento è identificata con la sigla V25-ter. Mentre nel descrittore del TermoUSB si passa al descrittore HID, qui ne viene inserito uno specifico della classe CDC; anche in questo caso la struttura è relativa esclusivamente alla Communication Interface. Ogni descrittore specifico della classe è costituito da uno o più descrittori funzionali preceduti da un Header iniziale. La struttura è formata dalle sezioni: 1) HEADER FUNCTIONAL DESCRIPTOR; 2) ABSTRACT CONTROL MANAGEMENT FUNCTIONAL DESCRIPTOR; 3) UNION FUNCTIONAL DESCRIPTOR; 4) CALL MANAGEMENT FUNCTIONAL DESCRIPTOR.
Tabella 3
Con il descrittore Interface le cose cambiano a causa della presenza di più interfacce; lo capiamo esaminando il caso della Communication Interface (Listato 3). In esso si faccia attenzione che i campi del descrittore sono da riferirsi alla singola interfaccia, pertanto il numero di endpoint è da intendersi per la Communication Interface e non per l’intero insieme di interfacce. Il byte identificativo della classe di interfaccia è pari a 2 (COMM_INTF) come stabilito nelle specifiche CDC; la sottoclasse si ricava dalla Tabella 2. Infine viene definito il protocollo specifico utilizzato dalla classe, il cui codice descrittivo è stabilito sulla base della Tabella 3.
Vediamole nel concreto con la solita rappresentazione tabellare (riferirsi alla Tabella 4); la relativa sequenza di istruzioni è visibile nel Listato 4. L’Abstract Control Management Functional Descriptor stabilisce i comandi supportati dalla Communication Class Interface. La sua struttura è rappresentata in Tabella 5, mentre la relativa sequenza di istruzioni è descritta nel Listato 5. Si faccia attenzione che nel campo bmCapabilities viene valorizzato solo il bit 1, che comprende l’insieme di richieste specifiche necessarie al corretto funzionamento del disposi-
Tabella 4
Il codice di cui parliamo è 1 ed è comunque definito come una costante chiamata V25TER. In effetti il protocollo Hayes Compatibile prevede 88
tivo. A proposito: se il bit viene valorizzato a 1 significa che la relativa funzionalità risulta abilitata, mentre se è a 0 la stessa viene disabilitata. luglio / agosto 2005 - Elettronica In
Corso PIC-USB
LISTATO 3
Corso PIC-USB
LISTATO 4 retlw retlw retlw retlw retlw
5 CS_INTERFACE DSC_FN_HEADER 0x10 0x01
; ; ; ; ;
bFunctionLength (HEADER_FN_DSC) bDescriptorType bDescriptorSubType bcdCDC (low byte) bcdCDC (high byte)
Il terzo descrittore definisce le relazioni intercorrenti in un gruppo di interfacce che possono considerarsi un’unità funzionale autonoma; una
ramente, il gruppo in questione consta di due sole interfacce: la prima, ossia la Communication Class Interface, viene definita come master perché è in grado di gestire al meglio i messaggi di controllo sullo stato della comunicazione. L’altra è uno slave, in quanto fondamentalmente funge da strato di trasporto. L’ultimo descrittore definisce le capacità di gestione del processo di chiamata da parte della Communication Class Interface. Detta così la
Tabella 5
delle interfacce viene definita come master del gruppo: lo scopo di ciò è fare in modo che alcune tipologie di messaggi inviati ad essa si possa-
LISTATO 5 retlw retlw retlw retlw
4 CS_INTERFACE DSC_FN_ACM 0x02
; ; ; ;
bFunctionLength (ACM_FN_DSC) bDescriptorType bDescriptorSubType bmCapabilities
no intendere come inviate al gruppo nel suo complesso. In Tabella 6 riepiloghiamo i campi che fanno parte di questa struttura, ciascuno con la relativa istruzione (Listato 6). Come si vede chia-
cosa sembra piuttosto complessa; in realtà si stabilisce se le interfacce possono gestire il controllo delle chiamate e se tale controllo è condiviso o esclusivo della Communication Class Interface. Nel nostro caso dovremo disabilitare la possibilità da parte del dispositivo di operare un effettivo controllo delle chiamate (vedi Tabella 7). Le relative istruzioni sono descritte nel Listato 7; come si vede, in esso il campo bmCapabilities è azzerato, quindi il dispositivo controlla le chiamate, ma non autonomamente. Per la Data Interface opzionale si usa il relativo indice, che è stato prefissato attraverso una costante chiamata, >
Tabella 6
Elettronica In - luglio / agosto 2005
89
retlw retlw retlw retlw retlw
5 CS_INTERFACE DSC_FN_UNION CDC_COMM_INTF_ID CDC_DATA_INTF_ID
; ; ; ; ;
bFunctionLength (UNION_FN_DSC) bDescriptorType bDescriptorSubType bMasterInterface bSlaveInterface0
appunto, CDC_DATA_INTF_ID e posta all’inizio del file, come vedremo al termine del paragrafo. A questo punto, così come avveniva per i
visto che per entrambe i dispositivi utilizziamo dei trasferimenti di tipo Interrupt. A questo punto l’interfaccia Communication è terminata. Dobbiamo ancora specificare la struttura che descrive l’interfaccia Data e, allo scopo, partiamo, come per la precedente, dal descrittore d’interfaccia (vedi Listato 9) nel quale il numero di endpoint usati è pari a 2 e la classe d’interfaccia è cambiata per la Data Class Interface; la relativa sottoclasse non viene specificata e non è
Tabella 7
dispositivi HID, precisiamo il descrittore Endpoint, ricordando che stiamo definendo l’interfaccia Communication che utilizzerà soltanto un endpoint IN con trasferimenti di tipo Interrupt (Listato 8). La struttura è praticamente identica a quella vista nel TermoUSB, tranne per il fatto
neppure necessario precisare alcun tipo di protocollo, visto che il livello in questione non avrà alcuna funzione di controllo della comunicazione, ma servirà esclusivamente per trasferire sequenze di dati in ingresso e in uscita. Anche il descrittore Endpoint è piuttosto semplice e, lo
LISTATO 7 retlw retlw retlw retlw retlw
5 CS_INTERFACE DSC_FN_CALL_MGT 0x00 CDC_DATA_INTF_ID
; ; ; ; ;
bFunctionLength (CALL_MGT_FN_DSC) bDescriptionType bDescriptionSubType bmCapabilities bDataInterface
che in questo caso in alcuni campi vengono utilizzate delle costanti al posto dei valori discreti. Si noti che il bmAttributes è sempre pari a 3,
vediamo, ricalca la medesima struttura vista per la precedente interfaccia: l’unica differenza è dovuta al fatto che in questo caso gli endpoint
LISTATO 8 retlw retlw retlw retlw retlw retlw retlw
90
7 DSC_EP _EP02_IN 3 low (CDC_INT_EP_SIZE) high (CDC_INT_EP_SIZE) 0x02
; ; ; ; ; ; ;
bLength (USB_EP_DSC) bDescriptorType bEndpointAddress bmAttributes wMaxPacketSize (low-byte) wMaxPacketSize (high-byte) bInterval
luglio / agosto 2005 - Elettronica In
Corso PIC-USB
LISTATO 6
Corso PIC-USB
le medesime strutture di quelli usati per i dispositivi già analizzati nelle precedenti puntate del corso PicUSB, spiegarli in questa sede non costituirebbe nulla di nuovo, ma, piuttosto, una ripetizione di concetti che, giunti a questo punto, dovreste già aver compreso a dovere. Dunque, per non sprecare spazio dedicandolo a un banale elenco di caratteri, non ne parliamo. Andiamo, invece, a vedere l’header del file RSUSBdsc.asm, dove troviamo finalmente la definizione di alcune delle costanti utilizzate nei descrittori. Come abbiamo già fatto in altre occasioni, il file è stato riscritto in maniera da riordinarne la struttura evidenziando le varie parti che lo compongono. Il descrittore specifico della classe CDC è stato diviso nelle quattro sezioni, spiegate affin-
LISTATO 9 retlw retlw retlw retlw retlw retlw retlw retlw retlw
9 DSC_INTF 0x01 0x00 0x02 DATA_INTF 0 NO_PROTOCOL 0x00
; ; ; ; ; ; ; ; ;
bLength bDescriptorType bInterfaceNumber bAlternateSetting bNumEndpoints bInterfaceClass bInterfaceSubclass bInterfaceProtocol iInterface
sono due, pertanto le definizioni raddoppiano. Ricordiamo che gli endpoint dell’interfaccia Data useranno dei trasferimenti di tipo Bulk, quindi sarà sicuramente necessario variare il campo bmAttributes (vedi Listato 10). La descri-
L I S T A T O 10 retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw
7 DSC_EP _EP03_OUT 2 low (CDC_BULK_OUT_EP_SIZE) high (CDC_BULK_OUT_EP_SIZE) 0x00 7 DSC_EP _EP03_IN 2 low (CDC_BULK_IN_EP_SIZE) high (CDC_BULK_IN_EP_SIZE) 0x00
zione dei due endpoint differisce esclusivamente per la direzione di utilizzo. In entrambi i casi si utilizzano delle costanti per stabilire la massima grandezza del pacchetto e l’indirizzo dell’endpoint. Facciamo anche notare che il campo bInterval ha senso nei trasferimen-
; ; ; ; ; ; ; ; ; ; ; ; ; ;
bLength (USB_EP_DSC) bDescriptorType bEndpointAddress bmAttributes wMaxPacketSize (low-byte) wMaxPacketSize (high-byte) bInterval bLength (USB_EP_DSC) bDescriptorType bEndpointAddress bmAttributes wMaxPacketSize (low-byte) wMaxPacketSize (high-byte) bInterval
ché sia possibile verificare "sul campo" quanto detto. L’header, così come per il file dei descrittori del TermoUSB, è diviso in due tabelle: una per i parametri generali ed un’altra per quelli specifici della classe CDC (vedi Listato 11). Nella prima tabella è facilmente riconoscibile la
L I S T A T O 11 ; ********************************************************************** ; TABELLA PARAMETRI GENERALI ; ********************************************************************** #define EP0_BUFF_SIZE 8 #define MAX_NUM_INT 1 #define MAX_EP_NUMBER 3 #define NUM_CONFIGURATIONS 1 #define NUM_INTERFACES 2 #define MODE_PP _PPBM0 #define UCFG_VAL _PUEN|_TRINT|_FS|MODE_PP ; Full-speed #define USB_USE_CDC
ti Interrupt, ma nel caso qui analizzato perde la sua utilità e viene pertanto azzerato. Con questo siamo arrivati al termine del file dei descrittori; riteniamo, infatti, che non sia necessario stabilire il descrittore Report, il quale è una struttura tipica degli HID e si può finire includendo i descrittori stringa. Avendo questi ultimi Elettronica In - luglio / agosto 2005
stessa sequenza presente nel file dei descrittori del TermoUSB; varia solo il massimo numero di endpoint utilizzati, che passa da 1 a 3: questi ultimi sono infatti destinati uno alla Communication Interface e due alla Data Interface. Infine, si osservi una definizione fondamentale che abbiamo evidenziato in rosso: si tratta di > 91
controllo (UEP2, UEP3) associati agli endpoint, nonché grandezza e direzione dei buffer utilizzati dagli endpoint stessi per i trasferimenti di cui essi devono occuparsi. L’indice identificativo per le due interfacce (COMM_INTF_ID e DATA_INTF_ID) è chiaramente differente: vale 0 per la Communication e 1 per la Data Interface. Bene, detto ciò siamo quindi arrivati al termine del file dei descrittori RSUSBDSC.asm. Come al solito, dobbiamo aggiornare il link nel file USBDESC.asm, affinché il compilatore includa correttamente i descrittori appena sviluppati. L’istruzione con cui provvediamo a fare ciò è visibile nel Listato 13, nel quale vi invitiamo a prestare eventualmente attenzione a commentare le righe relative ai descrittori utilizzati in altri progetti anteponendo un simbolo ; (punto e virgola) alla rispettiva include.
L I S T A T O 12 ; ********************************************************************** ; TABELLA PARAMETRI CLASSE CDC ; ********************************************************************** #define CDC_COMM_INTF_ID 0x00 #define CDC_COMM_UEP UEP2 #define CDC_INT_BD_IN ep2Bi #define CDC_INT_EP_SIZE 8 #define CDC_DATA_INTF_ID 0x01 #define CDC_DATA_UEP UEP3 #define CDC_BULK_BD_OUT ep3Bo #define CDC_BULK_OUT_EP_SIZE 64 #define CDC_BULK_BD_IN ep3Bi #define CDC_BULK_IN_EP_SIZE 64
chiarimenti sulle altre voci della tabella si veda la precedente puntata del corso pubblicata nel fascicolo n° 99 della rivista. La tabella che raggruppa i parametri specifici della classe CDC è, invece, di facile comprensione, tanto più se la si mette a confronto con quella utilizzata per il TermoUSB avendo bene a mente la configurazione degli endpoint appena esposta e visibile nel Listato 12. Nella sequenza in oggetto vengono definiti gli
Bene, con ciò anche questo mese lo spazio a nostra disposizione si è esaurito. Sperando che abbiate appreso le nozioni fondamentali sui trasferimenti bulk e isocroni, e sulle Communication Device Class dei dispositivi USB (chi volesse approfondire l’argomento troverà interessante la pagina Web http://www.usb.org/developers/devclass_docs) vi accenniamo che nella prossima puntata vedremo i dettagli dello sviluppo PICBasic necessario a
L I S T A T O 13 include "RSUSBDSC.ASM"
; Descrittori Convertitore RS232-USB Esp.4 Corso PIC-USB
endpoint relativi alle due interfacce. Nel caso della Communication Class Interface viene precisato un endpoint con trasferimenti interrupt a 8 byte, mentre per la seconda interfaccia si definiscono due endpoint con trasferimenti bulk a 64 byte. Per ogni interfaccia vengono definiti i registri di 92
far funzionare il nostro convertitore USB-RS232. Scopriremo altresì come creare il file .hex mediante la versione 2.46 del compilatore e testeremo "sul campo" il dispositivo con esso creato. Chiariremo, infine, un altro aspetto di sviluppo dei dispositivi CDC, analizzando il nuovo framework Microchip. Alla prossima! luglio / agosto 2005 - Elettronica In
Corso PIC-USB
#define USB_USE_CDC, che stabilisce la classe di dispositivo utilizzata nel firmware e quindi informa il compilatore delle istruzioni che deve necessariamente includere. Per esempio, se analizzate il contenuto del file USB18.asm localizzato nella directory \pbp\USB18 vedrete che esistono delle direttive di compilazione (#ifdef) le quali includono o meno delle routine, a seconda che nel sorgente si inseriscano la define USB_USE_CDC (utilizzo di Communication Device Class) o la USB_USE_HID (impiego di dispositivi di classe HID). Un tipico caso è quello della routine USBCheckCDCRequest, che verifica le richieste inviate e le passa al relativo gestore. Si noti che esiste un’altra routine, chiamata USBCheckHIDRequest, che viene inclusa soltanto con la define USB_USE_HID. Per ulteriori