Emacs About Copertina Buon Senso

Articoli


Uso dell'Interfaccia Parallela

In questo articolo vedremo come si può utilizzare l'intefaccia parallela in Linux. L'interfaccia parallela riveste un certo interesse per chi si diverte a giocare con il saldatore a stagno in quanto è un'interfaccia molto semplice da usare e si può trovarla in molti computer: la discussione seguente vale per PC e Alpha, ma non per Sparc; per le altre piattaforme supportate non so dire...

Cos'è la parallela

L'interfaccia parallela del calcolatore, nella sua forma più utilizzata, è la periferica più semplice che si possa immaginare. Non mi riferisco qui ai nuovi standard, quelli con nomi tipo EPP, ma al protocollo originale della parallela, che è supportato da tutti i PC, dall'8088 in poi.

In pratica, sul connettore a 25 piedini si trovano direttamente i segnali corrispondenti ad alcuni bit di I/O, in logica TTL (con segnali a 0V e 5V). Con "direttamente" intendo dire che i bit che vengono scritti dal processore sulle porte di output appaiono sui piedini del connettore, e i bit della porta di ingresso vengono letti dal segnale di tensione sul connettore.

Il connettore parallelo porta 12 bit di output e 5 bit di input, più 8 segnali di terra. L'interfaccia software si compone quindi di due porte di output ed una porta di input. Per rendere le cose un pò più complicate, alcuni bit subiscono una inversione tra quello che appare sul connettore e quello che viene visto dal processore.

Prima che qualcuno collegi lampadine da 20W ai segnali di output della parallela è forse il caso di dire due parole sui segnali elletrici utilizzati: TTL (Transistor-Transistor Logic) è una famiglia logica molto utilizzata, almeno in passato, che presenta specifiche di ingresso/uscita non simmetriche: mentre un'uscita bassa (0V) può assorbire una corrente significativa, un'uscita alta (5V nominali) non è in grado di erogare più di un paio di mA di corrente, e risulta spesso a tensione molto più bassa di 5V (il minimo assicurato è 2.7 V). Allo stesso modo, per abbassare un ingresso occorre assorbire tanta corrente, mentre per alzarlo basta una resistenza di 5k verso 5V.

Le porte parallele più recenti forniscono prestazioni elettriche migliori, ma la cosa non è affatto garantita; lampade da 20W non possono comunque essere collegate direttamente, e l'uso di fotoaccoppiatori è comunque consigliabile per proteggere il calcolatore (ma io personalmente non l'ho mai fatto).

L'intefaccia software

Ogni porta parallela del sistema viene vista tramite tre indirizzi di I/O consecutivi: "base", "base+1", "base+2". Ad ogni porta parallela è anche associata un'interruzione, che vedremo a breve. I valori di "base" più utilizzati ed i relativi numeri di interruzione sono 0x378 (IRQ 7), 0x278 (IRQ 2) e 0x3bc (IRQ 5).

Il registro "base+1" è un registro di input, e viene usato per leggere i 5 segnali di ingresso del connettore, "base" e "base+2", invece, sono registri di output, e quando vengono letti restituiscono l'ultimo valore scritto (cioè lo stato attuale dei segnali al connettore).

L'effettiva mappatura tra i bit nei registri e i piedini del connettore è spiegata meglio con una figura che a parole.


La figura è anche disponibile in postscript come parportcolor.ps.

Poichè non interessa qui spiegare come si possa controllare una stampante, non resta nient'altro da dire sulla parallela. Chi non è interessato a questioni hardware può tranquillizzarsi, in quanto non parlerò più di segnali elettrici fino agli esempi finali.

Le porte di I/O

La discussione dell'uso delle porte di input/output ripete parzialmente quanto detto nel primo articolo sui device drivers, ma un minimo di introduzione mi sembra dovuta.

In Linux sono definite le seguenti funzioni per accedere alle porte:

Le funzioni precedenti sono definite sia per Intel che per Alpha, nonostante il processore Alpha non supporti uno "spazio di I/O". Alpha supporta solo l'I/O mappato in memoria, e i segnali di I/O per il bus vengono generati da circuiti che rimappano alcune locazioni di memoria sullo spazio di I/O (questo permette di usare periferiche ISA e PCI in calcolatori basati su Alpha, con l'ulteriore ausilio di un emulatore 8086 per eseguire l'inizializzatione delle schede stesse che si trova sulla loro stessa ROM in forma di codice intel).

L'architettura Sparc è differente da questo punto di vista, e non esiste nessuna delle funzioni precedenti quando Linux gira su Sparc. L'I/O per Sparc è mappato in memoria come per Alpha, e le stesse periferiche non hanno alcuna nozione di indirizzi di input/output. Poichè le nuove Sparc supportano PCI è probabile che verrà introdotto un modo per accedere al bus compatibilmente con le funzioni introdotte sopra e circuiteria simile a quella per Alpha.

E' interessante notare come le funzioni di "I/O di stringa" sono singole istruzioni sulla piattaforma Intel mentre vengono implementate da cicli software per Alpha. La versione 2.0 del kernel non esporta outsl() e insl() per Alpha, mentre linux-2.1.3 e seguenti le esportano. Questo non è comunque un problema finchè ci limitiamo ad usare porte a 8 bit.

Per alcune architetture sono anche definite delle istruzioni "con pausa" per operare su periferiche lente, ma non è questo il luogo per parlarne. Il lettore curioso può sempre guardare in <asm/io.h>

Uso delle porte dallo spazio utente

Il modo più semplice per leggere e scrivere sulle locazioni di I/O è farlo nello spazio utente. Questo però comporta alcune limitazioni:

Avendo il vizio di attaccare semplici circuiti al mio calcolatore, ho scritto due stupidi programmi per leggere e scrivere le porte dallo spazio utente. Si chiamano inp e outp, ed utilizzano solo le porte ad 8 bit. I sorgenti si chiamano, ovviamente, inp.c e outp.c.

inp e outp possono essere fatti "set-userid" root in modo da non dover diventare esplicitamente root per usarli. Io difficilmente lavoro come root sulla mia macchina, per cui ho usato questa tecnica; bisogna però fare molta attenzione perchè lasciare outp libero a tutti gli utenti è un serio rischio per la sicurezza della macchina -- io non saprei esattamente cosa farci, ma un utente smaliziato e abbastanza competente può tirar fuori di tutto da un buchetto del genere.

L'unica attenzione da ricordare quanto si compilano programmi che usano le funzioni di I/O è di specificare -O tra le opzioni del compilatore. Questo perchè le funzioni di I/O sono dichiarate come "extern inline", e le funzioni inline non vengono espanse da gcc se l'ottimizzazione non è abilitata. La problematica e' spiegata in maniera chiara nella pagine del manuale di gcc.

Uso delle porte dallo spazio kernel

Il codice per accedere alle porte di I/O da parte del kernel è uguale a quello usato nello spazio utente, tranne che non occorre usare ioperm(), in quanto il codice del kernel (che gira in "modo supervisore" sul processore), ha sempre accesso a tutte le risorse hardware.

Quello che occorre per utilizzare la porta parallela è un device driver che permetta di leggere o scrivere le porte in questione. Il modulo "short" (Simple Hardware Operations and Raw Tests) permette di leggere e/o scrivere quattro porte a 8 bit consecutive: i nodi in /dev creati da short sono direttamente mappati sulle porte di I/O: /dev/short0 serve per leggere/scrivere la porta base, /dev/short1 accede a base+1 eccetera. Il valore di default di base è 0x378 e permette quindi di usare la porta parallela; una linea di comando come "insmod short.o short_base=0x278" si può usare per accedere a porte diverse.

Inoltre, per permettere di vedere il differente comportamento di outb(), outsb() e outb_p() (la versione con pausa), il modulo crea diversi nodi in /dev a questo fine:

Il seguente esempio mostra il tempo che il dispositivo impiega a scrivere 1MB sulla porta usando i tre diversi nodi:


morgana.root# time dd bs=1k count=1000 if=/dev/zero of=/dev/short0
0.020u 2.040s 0:02.06 100.0% 0+0k 0+0io 64pf+0w
morgana.root# time dd bs=1k count=1000 if=/dev/zero of=/dev/short0p
0.020u 3.510s 0:03.57 98.8% 0+0k 0+0io 64pf+0w
morgana.root# time dd bs=1k count=1000 if=/dev/zero of=/dev/short0s
0.020u 1.640s 0:01.66 100.0% 0+0k 0+0io 64pf+0w

Come si vede, le chiamate con pausa aggiungono un attesa di circa un microsecondo dopo ogni operazione, mentre le chiamate "stringa" sono circa il 25% più veloci dei cicli software.

Bisogna però dire che scrivere tanti dati consecutivamente sulla porta parallela è difficilmente di interesse, e il controllo di semplici periferiche esterne viene di solito effettuato scrivendo o leggendo un byte alla volta.

Uso delle interruzioni della parallela

La porta parallela è anche in grado di generare interruzioni quando il piedino numero 10 passa da una tensione bassa ad una alta. Le interruzioni, però, a differenza delle porte, non si possono gestire nello spazio utente, e bisogna appoggiarsi a codice nel kernel. Perchè la porta parallera interrompa il processore occorre scrivere un 1 nel bit 4 della porta base+2.

Il modulo short è in grado di gestire le interruzioni: quando il modulo viene caricato abilita l'interfaccia a riportare interruzioni.

Il device /dev/shortint riporta nello spazio utente gli istanti di tempo (in secondi e microsecondi) in cui il processore viene interrotto dall'interfaccia. La scrittura di /dev/shortint fa si che vengano scritti alternativamente 0x00 e 0xff sulla porta dat (base+0). Si possono perciò generare delle interruzioni collegando insieme il piedino 9 e il piedino 10 dell connettore della parallela. Questo è quello che succede sulla mia macchina mettendo il ponticello:


morgana% echo 1122334455 > /dev/shortint ; cat /dev/shortint
50588804.876653
50588804.876693
50588804.876720
50588804.876747
50588804.876774

Una trattazione approfondita della gestione delle interruzioni è fuori argomento in questa sede, ed

Il pacchetto short

Il driver short è distribuito in formato sorgente secondo la GPL ed è composto dal sorgente, un header per risolvere alcune dipendenze dalla versione del kernel e due script per caricare e scaricare il modulo. Più, ovviamente, il Makefile.

La cosa migliore per chi intende provarlo è comunque scaricare il tar completo: short.tar.gz.

Tre esempi

Vediamo ora tre esempi di uso della porta parallela per il collegamento di circuiti personali.

Il primo è veramente banale, e consiste nell'applicazione di un LED (o più di uno) per la visualizzazione dello stato del (dei) bit della porta. In figura è rappresentato il monitoraggio del bit 0 della porta dati. Il ponticello tra il piedino 9 (bit 7 della porta dati) e il piedino 10 permette di giocare con le interruzioni.


La figura è anche disponibile in postscript come parsample1.ps.


Il secondo esempio riguarda il controllo di un relay ("relé"). tramite logica TTL. Per l'implementazione di questo circuito occorre un'alimentazione esterna a 5V e l'alimentazione per il relay.

In questo esempio un '244 (buffer a 8 bit) isola il segnale della parallela dalla circuiteria esterna, dove un '138 (multiplexer 3-to-8) abbassa una delle otto linee di output in base ai tre bit di indirizzo A, B, C. I tre segnali di enable del '138 sono collegati in modo da rispondere ai dati compresi tra 0x20 e 0x27. Il relay mostrato in figura viene attivato scrivendo 0x20 e disattivato scrivendo 0x21.

Collegare direttamente i bit della porta al relay non permette di preservare lo stato dell'interruttore quando si spegne o riaccende la macchina. L'uso dei codici 0x20-27 permette invece una persistenza dello stato comandato dal calcolatore (se non si toglie l'alimentazione esterna). Ho usato personalmente questo circuito per collegare 8 relé al mio calcolatore, tramite due '138 con i fili di enable collegati diversamente.


La figura è anche disponibile in postscript come parsample2.ps.


Il terzo esempio è una segreteria telefonica che fa uso di una scheda audio per la ripetizione del proprio messaggio e la registrazione della telefonata.

Per implementare la segreteria basta riconoscere quando il telefono squilla e quando invece il telefono viene messo giù. La porta parallela può essere usata per generare delle interruzioni quando la tensione sulla linea telefonica oscilla bruscamente, e il riconoscimento dello squillo può poi avvenire via software.

La trasmissione e la ricezione del segnale audio possono avvenire tramite accoppiamento capacitivo, mentre la cornetta può essere "sollevata" tramite un relé di segnale. Questo permette di mantenere i due circuiti elettricamente separati.

A differenza dei due esempi precedenti, non ho avuto modo di provare questo circuito in pratica, ed è quindi possibile che contenga delle sviste macroscopiche da parte mia. Prima di toccare i fili del telefono, comunque, conviene reperire le informazioni sui livelli di tensione usati ed accertarsi che i valori dei condensatori e degli zener usati siano corretti per accoppiare la scheda audio, poichè i valori mostrati sono spannometrici, in quanto non conosco i livelli di tensione della scheda audio.


La figura è anche disponibile in postscript come parsample3.ps.

di Alessandro Rubini

Emacs About Copertina Buon Senso