<!doctype linuxdoc system>
<article opts="italian">

<title>Linux I/O port programming mini-HOWTO
<author>Autore: Riku Saikkonen <tt/&lt;Riku.Saikkonen@hut.fi&gt;/
<date>v3.0, 13/12/2000

<abstract>
Questo HOWTO descrive la programmazione delle porte I/O e come realizzare 
delle brevi attese di tempo nei programmi che girano in modo utente, su 
macchine basate sugli Intel x86.

Traduzione a cura di <htmlurl url="mailto:f.stef@tiscali.it"
name="Fabrizio Stefani">, 8 giugno 2003, e revisione a cura di <htmlurl
url="mailto:sacarde@tiscali.it" name="Sandro Cardelli">, 4 luglio 2005.
</abstract>

<toc>


<sect>Introduzione 
<p>
Questo HOWTO descrive la programmazione delle porte I/O e come realizzare 
delle brevi attese di tempo nei programmi che girano in modo utente, su 
macchine basate sugli Intel x86. Questo documento è derivato dal 
piccolissimo IO-Port mini-HOWTO dello stesso autore.

Questo documento &egrave; Copyright 1995-2000 di Riku Saikkonen. Vedere il
<url url="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/COPYRIGHT"
name="Linux HOWTO copyright"> per i dettagli.

Per correzioni o eventuali aggiunte sono raggiungibile via e-mail
(<tt/Riku.Saikkonen@hut.fi/)...


<sect>Usare le porte I/O nei programmi C


<sect1>Il metodo normale
<p>
Le routine per accedere alle porte I/O sono in <tt>/usr/include/asm/io.h</>
(o <tt>linux/include/asm-i386/io.h</> nella distribuzione del sorgente
del kernel). Tali routine sono delle macro inline, quindi &egrave; sufficiente
usare <tt>#include &lt;asm/io.h&gt;</>; non serve nessuna libreria
aggiuntiva.

A causa di una limitazione in gcc (presente in tutte le versioni di cui
sono al corrente, compreso egcs), <em/bisogna/ compilare tutti i sorgenti
che usano tali routine con l'ottimizzazione abilitata (<tt/gcc -O1/ o
maggiore), oppure usare <tt>#define extern static</> prima di 
<tt>#include &lt;asm/io.h&gt;</> (ricordarsi poi di mettere <tt>#undef
extern</>).

Per il debugging si pu&ograve; usare <tt/gcc -g -O/ (almeno con le ultime
versioni di gcc), sebbene l'ottimizzazione possa causare, a volte, 
un comportamento un pò strano del debugger. &Egrave; possibile evitare
tale inconveniente mettendo le routine che accedono alla porta I/O in 
un file sorgente separato e compilare solo quest'ultimo con l'ottimizzazione 
abilitata.


<sect2>Permessi
<p>
Prima di accedere ad una qualsiasi porta, bisogna dare al programma
il permesso per farlo. Ci&ograve; si fa chiamando la funzione <tt/ioperm()/
(dichiarata in <tt/unistd.h/ e definita nel kernel) da qualche parte
all'inizio del programma (prima di qualunque accesso ad una porta
I/O). La sintassi &egrave; <tt>ioperm(from, num, turn_on)</>, dove <tt/from/
&egrave; il primo numero di porta e <tt/num/ il numero di porte consecutive a 
cui dare l'accesso. Per esempio, <tt/ioperm(0x300, 5, 1)/ d&agrave; l'accesso
alle porte da 0x300 a 0x304 (per un totale di 5 porte). L'ultimo argomento
&egrave; un valore booleano che specifica se dare (true (1)) o togliere (false 
(0)) al programma l'accesso alle porte. &Egrave; possibile chiamare pi&ugrave; 
volte <tt/ioperm()/ per abilitare pi&ugrave; porte non consecutive. Vedere la 
pagina di man di <tt/ioperm(2)/ per i dettagli sulla sintassi.

La chiamata <tt/ioperm()/ necessita che il programma abbia i privilegi di 
root; quindi bisogna eseguirlo da utente root, oppure renderlo
suid root. Dopo la chiamata <tt/ioperm/ per abilitare le porte che si 
vogliono usare, si può rinunciare ai privilegi di root. Alla fine del 
programma non &egrave; necessario abbandonare esplicitamente i privilegi 
di accesso alle porte con <tt/ioperm(..., 0)/, ci&ograve; verr&agrave; 
fatto automaticamente quando il processo termina.

Un <tt/setuid()/ ad un utente non root non disabilita l'accesso alla porta
fornito da <tt/ioperm()/, mentre un <tt/fork()/ lo disabilita (il processo
figlio non ottiene l'accesso, il genitore invece lo mantiene).

<tt/ioperm()/ pu&ograve; fornire l'accesso solo alle porte da 0x000 a 0x3ff;
per porte pi&ugrave; alte bisogna usare <tt/iopl()/ (che, con una sola 
chiamata, fornisce l'accesso a tutte le porte). Per fornire al programma 
l'accesso a <em/tutte/ le porte I/O usare 3 come argomento di livello 
(cio&egrave; <tt/iopl(3)/) (quindi attenzione: accedere alle porte sbagliate
pu&ograve; provocare un sacco di sgradevoli cose al computer). Di nuovo, 
per effettuare la chiamata a <tt/iopl()/ sono necessari i privilegi di root. 
Per maggiori dettagli vedere le pagine di man di <tt/iopl(2)/.

<sect2>Accedere alle porte
<p>
Per leggere (input) un byte (8 bit) da una porta, chiamare <tt/inb(port)/, 
che restituisce il byte presente all'ingresso. Per scrivere (output) un 
byte, chiamare <tt/outb(valore, porta)/ (notare l'ordine dei parametri). 
Per leggere una word (16 bit) dalle porte <tt/x/ e <tt/x+1/ (un byte da 
ognuna per formare una word con l'istruzione assembler <tt/inw/) chiamare 
<tt/inw(x)/. Per scrivere una word sulle due porte si usa 
<tt/outw(valore, x)/. Se si hanno dubbi su quali istruzioni (byte o word) 
usare per le porte, probabilmente servono <tt/inb()/ e <tt/outb()/ --- la 
maggior parte dei dispositivi sono progettati per gestire l'accesso alle 
porte a livello di byte. Notare che tutte le istruzioni per accedere alle 
porte richiedono almeno un microsecondo (circa) per essere eseguite.

Le macro <tt/inb_p()/, <tt/outb_p()/, <tt/inw_p()/ e <tt/outw_p()/
funzionano esattamente come le precedenti, ma introducono un
ritardo, di circa un microsecondo, dopo l'accesso alla porta. Si può
allungare tale ritardo a circa quattro microsecondi mettendo
<tt/#define REALLY_SLOW_IO/ prima di <tt>#include &lt;asm/io.h&gt;</>.
Queste macro, di solito (a meno non si usi <tt/#define SLOW_IO_BY_JUMPING/, 
che probabilmente &egrave; meno preciso), effettuano una scrittura sulla 
porta 0x80 per ottenere il ritardo e quindi, prima di usarle, bisogna 
dargli l'accesso alla porta 0x80 con <tt/ioperm()/ (le scritture fatte 
sulla porta 0x80 non hanno conseguenze su nessuna parte del sistema). 
Più avanti è spiegato come ottenere dei ritardi con sistemi pi&ugrave; 
versatili.

Nelle raccolte di pagine di man per Linux, nelle versioni
ragionevolmente recenti, ci sono le pagine di man di <tt/ioperm(2)/,
<tt/iopl(2)/ e per le suddette macro.


<sect1>Un altro metodo: <tt>/dev/port</>
<p>
Un altro modo per accedere alle porte I/O &egrave; quello di aprire, in 
lettura e/o scrittura, con <tt/open()/, il dispositivo a caratteri 
<tt>/dev/port</> (numero primario 1, secondario 4) (le funzioni stdio 
<tt/f*()/ hanno un buffer interno e sono quindi da evitare). Poi 
posizionarsi con <tt/lseek()/ sul byte appropriato nel file 
(posizione 0 del file = porta 0x00, posizione 1 del file = porta 0x01 
e cos&igrave; via...) e leggere (<tt/read()/), o scrivere 
(<tt/write()/), un byte o una word da, o in, esso.

Ovviamente, perch&eacute; ci&ograve; funzioni, il programma avr&agrave;
bisogno dell'accesso in lettura/scrittura a <tt>/dev/port</>. Questo metodo 
&egrave; probabilmente pi&ugrave; lento del metodo normale precedentemente 
descritto, ma non ha bisogno n&eacute; di ottimizzazioni in compilazione
n&eacute; della funzione <tt/ioperm()/. Non serve nemmeno l'accesso 
da root, se si fornisce al gruppo o agli utenti non root l'accesso a 
<tt>/dev/port</> -- ma, in termini di sicurezza del sistema, far questo 
&egrave; una pessima idea perch&eacute; &egrave; possibile danneggiare il 
sistema, e forse anche ottenere l'accesso a root, usando <tt>/dev/port</> 
per accedere direttamente agli hard disk, alle schede di rete, ecc.

Non &egrave; possibile usare <tt/select(2)/ oppure <tt/poll(2)/ per 
leggere <tt>/dev/port</>, perch&eacute; l'hardware non ha la capacità
di notificare alla CPU il cambiamento del valore in una porta d'ingresso.

<sect>Gli interrupt (IRQ) e l'accesso DMA
<p>
Non &egrave; possibile usare gli IRQ o il DMA direttamente da un processo 
in modo utente, bisogna scrivere un driver per il kernel. Vedere <url
url="http://www.redhat.com:8080/HyperNews/get/khg.html" name="The
Linux Kernel Hacker's Guide"> per i dettagli e il codice sorgente del
kernel per gli esempi.

&Egrave; possibile disabilitare gli interrupt da un programma in modo 
utente, ma pu&ograve; essere pericoloso (anche i driver del kernel lo fanno
per il tempo pi&ugrave; breve possibile). Dopo la chiamata <tt/iopl(3)/
&egrave; possibile disabilitare gli interrupt con una semplice chiamata
<tt/asm("cli");/ e poi riabilitarli con <tt/asm("sti");/.


<sect>Temporizzazione ad elevata precisione


<sect1>Ritardi
<p>
Innanzi tutto &egrave; bene precisare che, a causa della natura multitasking 
di Linux, non &egrave; possibile garantire che un processo che gira in modo 
utente abbia un preciso controllo delle temporizzazioni. Un processo
potrebbe essere sospeso per un tempo che pu&ograve; variare dai, circa, 
dieci millisecondi, fino ad alcuni secondi (in un sistema molto carico). 
Comunque, per la maggior parte delle applicazioni che usano le porte I/O, 
ci&ograve; non ha importanza. Per minimizzare tale tempo &egrave; possibile 
assegnare al processo un pi&ugrave; elevato valore di priorit&agrave; (vedere 
la pagina di man di <tt/nice(2)/), oppure si pu&ograve; usare uno scheduling 
in real time (vedere sotto).

Per temporizzazioni pi&ugrave; precise di quelle disponibili per
i processi che girano in modo utente, ci sono delle "forniture" per il 
supporto dell'elaborazione real time in modo utente. I kernel 2.x di 
Linux forniscono un supporto per il soft real time; per i dettagli 
vedere la pagina di man di <tt/sched_setscheduler(2)/. C'&egrave; un 
kernel speciale che supporta l'hard real time; per maggiori informazioni 
vedere <url url="http://luz.cs.nmt.edu/&tilde;rtlinux/">.


<sect2>Pause: <tt/sleep()/ e <tt/usleep()/
<p>
Incominciamo con le pi&ugrave; semplici chiamate di funzioni di
temporizzazione. Per ritardi di pi&ugrave; secondi la scelta migliore 
&egrave;, probabilmente, quella di usare <tt/sleep()/. Per ritardi dell'ordine 
delle decine di millisecondi (il ritardo minimo sembra essere di circa 
10 ms) dovrebbe andar bene <tt/usleep()/. Queste funzioni cedono la CPU 
agli altri processi (vanno a dormire: "sleep"), in modo  che il tempo 
di CPU non venga sprecato. Per i dettagli vedere le pagine di man di
<tt/sleep(3)/ e <tt/usleep(3)/.

Per ritardi inferiori a, circa, 50 millisecondi (dipendentemente dal carico 
del sistema e dalla velocit&agrave; del processore e della macchina) il 
rilascio della CPU richiede troppo tempo; ci&ograve; perch&eacute; (per
le architetture x86) lo scheduler generalmente lavora, almeno, dai 10 ai 30 
millisecondi prima di restituire il controllo ad un processo. Per questo 
motivo, per i piccoli ritardi, <tt/usleep(3)/ in genere ritarda un po'
pi&ugrave; della quantit&agrave; specificatagli nei parametri, almeno 10 ms 
circa.


<sect2><tt/nanosleep()/
<p>
Nei kernel Linux della serie 2.0.x, c'&egrave; una nuova chiamata di 
sistema, <tt/nanosleep()/ (vedere la pagina di man di <tt/nanosleep(2)/), 
che permette di "dormire" o ritardare per brevi periodi di tempo (pochi 
microsecondi o pi&ugrave;).

Per ritardi fino a 2 ms, se (e solo se) il processo &egrave; impostato
per lo scheduling in soft real time (usando <tt/sched_setscheduler()/),
<tt/nanosleep()/ usa un ciclo di attesa, altrimenti dorme ("sleep"), 
proprio come <tt/usleep()/.

Il ciclo di attesa usa <tt/udelay()/ (una funzione interna del kernel usata 
da parecchi kernel driver) e la lunghezza del ciclo viene calcolata 
usando il valore di BogoMips (la velocit&agrave; di questo tipo di
cicli di attesa &egrave; una delle cose che BogoMips misura accuratamente).
Per i dettagli sul funzionamento vedere <tt>/usr/include/asm/delay.h</>.


<sect2>Ritardare tramite l'I/O sulle porte
<p>
Un altro modo per realizzare un ritardo di pochi microsecondi &egrave; di
effettuare delle operazioni di I/O su una porta. La lettura dalla, o la
scrittura sulla (come si fa &egrave; stato descritto precedentemente), porta 
0x80 di un byte impiega quasi esattamente un microsecondo, indipendentemente 
dal tipo e dalla velocit&agrave; del processore. &Egrave; possibile ripetere 
tale operazione pi&ugrave; volte per ottenere un'attesa di alcuni 
microsecondi. La scrittura sulla porta non dovrebbe avere controindicazioni
su nessuna macchina standard (e infatti qualche driver del kernel
usa questa tecnica). Questo &egrave; il metodo normalmente usato da 
<tt/{in|out}[bw]_p()/ per realizzare il ritardo (vedere <tt>asm/io.h</>).

In effetti una istruzione di I/O su una qualunque delle porte
nell'intervallo 0-0x3ff impiega quasi esattamente un microsecondo;
quindi se, per esempio, si sta accedendo direttamente alla porta parallela,
si possono semplicemente effettuare degli <tt/inb()/ in pi&ugrave; per 
ottenere il ritardo.


<sect2>Ritardare usando le istruzioni assembler
<p>
Se si conosce il tipo e la velocit&agrave; del processore della macchina
su cui girer&agrave; il programma, &egrave; possibile sfruttare del codice 
basato sull'hardware, che usa certe istruzioni assembler, per realizzare
dei ritardi molto brevi (ma ricordare che lo scheduler pu&ograve; sospendere 
il processo in qualsiasi momento, quindi i ritardi potrebbero essere 
impredicibilmente pi&ugrave; lunghi del previsto). Nella tabella
seguente la velocit&agrave; interna del processore determina il numero di
cicli di clock richiesti. Ad esempio, per un processore a 50 MHz (tipo un
486DX-50 o un 486DX2-50) un ciclo di clock dura 1/50.000.000 di secondo
(pari a 200 nanosecondi).



<tscreen><verb>
Istruzione      cicli di clock      cicli di clock
                  su un i386          su un i486
xchg %bx,%bx          3                   3
nop                   3                   1
or %ax,%ax            2                   1
mov %ax,%ax           2                   1
add %ax,0             2                   1
</verb></tscreen>

<p>
I cicli di clock dei Pentium dovrebbero essere gli stessi degli i486,
ma nei Pentium Pro/II <tt/add %ax, 0/ potrebbe richiedere solo 1/2 
ciclo di clock. Si può rimediare usando un'altra istruzione (a causa
dell'esecuzione fuori ordine non è necessario che tale istruzione sia
quella immediatamente successiva nel codice).

Le istruzioni <tt/nop/ e <tt/xchg/, indicate nella tabella, non dovrebbero 
avere effetti collaterali. Le altre potrebbero modificare i flag dei 
registri, ma ci&ograve; non dovrebbe essere un problema poich&eacute; gcc 
dovrebbe accorgersene. Per realizzare istruzioni di ritardo 
<tt/xchg %bx, %bx/ &egrave; una buona scelta.

Per usarle bisogna chiamare <tt/asm(&dquot;istruzione&dquot;)/.
La sintassi delle istruzioni &egrave; come nella tabella precedente. 
Se si vogliono mettere pi&ugrave; istruzioni in un singolo <tt/asm()/ 
bisogna separarle con dei punti e virgola. Ad esempio 
<tt/asm(&dquot;nop ; nop ; nop ; nop&dquot;)/ esegue quattro istruzioni 
<tt/nop/, generando un ritardo di quattro cicli di clock sui processori 
i486 o Pentium (o 12 cicli di clock su un i386).

Gcc traduce <tt/asm()/ in codice assembler inline, per cui si risparmiano 
i tempi per la chiamata di funzione.

Ritardi pi&ugrave; brevi di un ciclo di clock sono impossibili con 
le architetture Intel x86.


<sect2><tt/rdtsc/ per i Pentium
<p>
Con i Pentium &egrave; possibile ottenere il numero di cicli di clock 
trascorsi dall'ultimo riavvio del sistema con il seguente codice C (che
esegue l'istruzione RDTSC):


<tscreen><code>
   extern __inline__ unsigned long long int rdtsc()
   {
     unsigned long long int x;
     __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
     return x;
   }
</code></tscreen>

Si pu&ograve; sondare tale valore per ritardare del numero di cicli 
di clock desiderato.


<sect1>Misurare il tempo
<p>
Per tempi della precisione dell'ordine del secondo &egrave; probabilmente 
pi&ugrave; facile usare <tt/time()/. Per tempi pi&ugrave; precisi,
<tt/gettimeofday()/ &egrave; preciso fino a circa un microsecondo (ma vedere 
quanto gi&agrave; detto riguardo lo scheduling). Con i Pentium, il frammento 
di codice sopra (<tt/rdtsc/) ha una precisione pari a un ciclo di clock.

Se si desidera che un processo riceva un segnale dopo un certo quanto 
di tempo, usare <tt/setitimer()/ o <tt/alarm()/. Per i dettagli vedere le 
pagine di man delle suddette funzioni.


<sect>Altri linguaggi di programmazione
<p>
La descrizione precedente era relativa specificamente al linguaggio C. 
Dovrebbe valere inalterata per il C++ e l'Objective C. In assembler,
bisogna effettuare la chiamata a <tt/ioperm()/ o <tt/iopl()/ come in C,
ma dopo di ci&ograve; &egrave; possibile usare direttamente le 
istruzioni di lettura/scrittura per l'I/O sulla porta.

In altri linguaggi, se non si pu&ograve; inserire nel programma codice 
assembler o C inline, o se non &egrave; possibile usare le chiamate di 
sistema menzionate prima, &egrave; probabilmente pi&ugrave; facile scrivere 
un semplice file sorgente C contenente le funzioni per l'accesso in I/O alle 
porte o per realizzare i ritardi che servono, e compilarlo e linkarlo con 
il resto del programma. Oppure si pu&ograve; usare <tt>/dev/port</> come 
descritto precedentemente.


<sect>Alcune utili porte
<p>
Vengono ora fornite delle informazioni per la programmazione delle porte 
pi&ugrave; comuni che possono essere usate per l'I/O delle logiche TTL 
(o CMOS) per scopi generici.

Se si vogliono usare queste o altre porte per il loro scopo originale 
(cio&egrave; controllare una normale stampante o un modem), sarebbe 
meglio usare i driver esistenti (che, di solito, sono inclusi nel 
kernel), piuttosto che programmare direttamente le porte come descritto 
in questo HOWTO. Questa sezione &egrave; indirizzata a quelli che 
vogliono connettere alle porte standard di un PC degli schermi LCD, dei 
motori passo passo, o altri componenti specifici.

Se si desidera controllare un dispositivo di largo uso, come uno scanner
(che &egrave; sul mercato gi&agrave; da un pò), cercate se c'&egrave; 
gi&agrave; un driver di Linux che lo riconosce. L'<url
url="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Hardware-HOWTO"
name="Hardware-HOWTO"> &egrave; un buon posto da cui iniziare la ricerca.

<url url="http://www.hut.fi/Misc/Electronics/"> &egrave; una buona fonte
di informazioni sulla connessione di dispositivi ai computer (e 
sull'elettronica in generale).


<sect1>La porta parallela
<p>
L'indirizzo base della porta parallela (detto "<tt/BASE/" nel seguito)
&egrave; 0x3bc per <tt>/dev/lp0</>, 0x378 per <tt>/dev/lp1</> e 0x278 per
<tt>/dev/lp2</>. Per controllare qualcosa che si comporta come una 
normale stampante, vedere il <url
url="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Printing-HOWTO"
name="Printing-HOWTO">.

Nella maggior parte delle porte parallele, oltre al modo standard di sola 
scrittura descritto di seguito, esiste un modo bidirezionale "esteso".
Per maggiori informazioni su tale argomento e sui nuovi modi ECP/EPP, 
vedere <url url="http://www.fapo.com/"> e <url
url="http://www.senet.com.au/&tilde;cpeacock/parallel.htm">. Ricordare
che poich&eacute; non &egrave; possibile usare gli IRQ o il DMA in un 
programma che gira in modo utente, per usare ECP/EPP probabilmente sar&agrave;
necessario scrivere un driver per il kernel. Credo che qualcuno stia 
gi&agrave; scrivendo un tale driver, ma non conosco i dettagli della cosa.

La porta <tt/BASE+0/ (porta dati) controlla i segnali dei dati della porta
(da D0 a D7 per i bit da 0 a 7, rispettivamente; stati: 0 = basso (0 V),
1 = alto (5 V)). Una scrittura su tale porta fissa i dati sui pin. Una 
lettura restituisce i dati che sono stati scritti per ultimi, in modo 
standard (oppure esteso), oppure restituisce i dati provenienti dai pin di 
un altro dispositivo che lavora in modalit&agrave; di lettura estesa.

La porta <tt/BASE+1/ (porta di Stato) &egrave; di sola lettura e restituisce 
lo stato dei seguenti segnali d'ingresso:
<itemize>
<item>Bit 0 e 1, riservati.
<item>Bit 2 stato dell'IRQ (non &egrave; un pin e non so come funziona)
<item>Bit 3 ERROR (1 = alto)
<item>Bit 4 SLCT (1 = alto)
<item>Bit 5 PE (1 = alto)
<item>Bit 6 ACK (1 = alto)
<item>Bit 7 -BUSY (0 = alto)
</itemize>

La porta <tt/BASE+2/ (porta di Controllo) &egrave; di sola scrittura (una 
lettura restituisce l'ultimo dato scritto) e controlla i seguenti segnali 
di stato:
<itemize>
<item>Bit 0 -STROBE (0 = alto)
<item>Bit 1 -AUTO_FD_XT (0 = alto)
<item>Bit 2 INIT (1 = alto)
<item>Bit 3 -SLCT_IN (0 = alto)
<item>Bit 4, quando impostato ad 1, abilita l'IRQ della porta parallela 
      (che si verifica nella transizione di ACK da basso ad alto)
<item>Bit 5 controlla la direzione del modo esteso (0 = scrittura, 1 = lettura)
      ed &egrave; di sola scrittura (una lettura di questo bit non restituisce
      nulla di utile).
<item>Bit 6 e 7, riservati.
</itemize>

Configurazione dei pin (connettore a "D" femmina a 25-pin sulla porta) 
(i = input, ingresso; o = output, uscita):
<tscreen><verb>
1io -STROBE, 2io D0, 3io D1, 4io D2, 5io D3, 6io D4, 7io D5, 8io D6,
9io D7, 10i ACK, 11i -BUSY, 12i PE, 13i SLCT, 14o -AUTO_FD_XT,
15i ERROR, 16o INIT, 17o -SLCT_IN, 18-25 Ground
</verb></tscreen>

Le specifiche IBM dicono che i pin 1, 14, 16 e 17 (le uscite di controllo)
hanno i driver dei collettori aperti connessi a 5 V attraverso resistori da 
4.7 kohm (pozzo 20 mA, fonte 0.55 mA, uscita a livello alto pari a 0.5 V 
meno il pullup). I rimanenti pin hanno il pozzo a 24 mA, la fonte a 15 mA,
e la loro uscita a livello alto &egrave; di 2.4 V (minimo). Per entrambi, lo 
stato basso &egrave; di 0.5 V (massimo). Le porte parallele non IBM 
probabilmente si discostano da questo standard. Per maggiori informazioni a 
tal riguardo vedere <url 
url="http://www.hut.fi/Misc/Electronics/circuits/lptpower.html">.

In ultimo un avvertimento: attenzione con i collegamenti a massa.
Personalmente ho rotto diverse porte parallele collegandoci qualcosa 
mentre il computer era acceso. Per giochetti del genere sarebbe buona 
cosa usare una porta parallela che non sia integrata sulla piastra madre.
(Di solito &egrave; possibile ottenere una seconda porta parallela, per 
la propria macchina, tramite una economica e standard scheda "multi-I/O"; 
basta disabilitare le porte di cui non si ha bisogno e impostare 
l'indirizzo I/O della porta parallela sulla scheda ad un indirizzo libero.
Non &egrave; necessario conoscere l'IRQ della porta parallela se non lo 
si usa.)


<sect1>La porta giochi (joystick)
<p>
La porta giochi &egrave; situata agli indirizzi 0x200-0x207. Per controllare
i normali joystick &egrave; probabilmente meglio usare i driver
distribuiti con il kernel di Linux.

Configurazione dei pin (connettore a "D" femmina a 15 pin):
<itemize>
<item>1, 8, 9, 15  : +5 V (alimentazione)
<item>4, 5, 12     : massa
<item>2, 7, 10, 14 : ingressi digitali BA1, BA2, BB1 e BB2, rispettivamente
<item>3, 6, 11, 13 : ingressi "analogici" AX, AY, BX e BY, rispettivamente
</itemize>

I pin +5 V sembra che siano spesso collegati direttamente alle linee di 
alimentazione sulla scheda madre, quindi dovrebbero poter fornire un bel 
pò di potenza, a seconda della scheda madre, dell'alimentatore e della 
porta giochi.

Gli ingressi digitali sono usati per i pulsanti dei due joystick (joystick 
A e joystick B, con due pulsanti ciascuno) collegabili alla porta.
Dovrebbero usare i normali livelli d'ingresso TTL ed &egrave; possibile 
leggerne lo stato direttamente dalla porta di stato (vedere sotto). Quando 
il pulsante &egrave; premuto, il joystick restituisce uno stato basso (0 V), 
altrimenti restituisce uno stato alto (i 5 V del pin dell'alimentazione
attraverso un resistore di 1 kohm).

I cos&igrave;detti ingressi analogici in effetti misurano una resistenza. 
La porta giochi ha un quadruplo multivibratore monostabile (un chip
558) collegato ai quattro ingressi. Ad ogni ingresso, fra il pin di ingresso 
e l'uscita del multivibratore, c'&egrave; un resistore da 2.2 kohm e, fra 
l'uscita del multivibratore e la massa, c'&egrave; un condensatore di
temporizzazione pari a 0.01 uF. Il joystick &egrave; dotato di un 
potenziometro per ogni asse (X e Y), connesso fra +5 V e l'appropriato pin 
d'ingresso (AX o AY per il joystick A, oppure BX o BY per il joystick B).

Il multivibratore, quando attivato, imposta alte (5 V) le sue linee di
uscita ed aspetta che ogni condensatore di temporizzazione raggiunga i
3.3 V prima di abbassare le rispettive linee di uscita. Cos&igrave; facendo,
la durata dello stato alto del multivibratore sar&agrave; proporzionale alla 
resistenza del potenziometro nel joystick (cio&egrave; alla posizione della 
leva sull'asse corrispondente), secondo la relazione:
<quote>
   R = (t - 24.2) / 0.011,
</quote>
dove R &egrave; la resistenza (in ohm) del potenziometro e t la durata dello
stato alto (in microsecondi).

Quindi, per leggere gli ingressi analogici, bisogna prima attivare il
multivibratore (con una scrittura sulla porta; vedere sotto), poi
controllare (con letture ripetute della porta) lo stato dei quattro assi
finch&eacute; non scendono dallo stato alto a quello basso, e quindi misurare 
la durata del loro stato alto. Tale controllo richiede abbastanza tempo di 
CPU e, su di un sistema multitasking non in real time come Linux (in modo 
utente normale), il risultato non &egrave; molto preciso perch&eacute; non 
&egrave; possibile controllare costantemente la porta (a meno che si usi un 
driver a livello di kernel e si disabilitino gli interrupt durante il 
controllo; ma cos&igrave; si spreca ancor pi&ugrave; tempo di CPU). Se si sa
che il segnale impiegher&agrave; parecchio tempo (decine di ms) per tornare 
basso, si pu&ograve; chiamare usleep() prima di cominciare il controllo, 
regalando cos&igrave; quel tempo di CPU ad altri processi.

La sola porta di I/O a cui serve di accedere &egrave; la porta 0x201 (le
altre porte o si comportano allo stesso modo, o non fanno nulla). Qualsiasi
scrittura su questa porta (non importa cosa si scrive) attiva il 
multivibratore. Una lettura da questa porta restituisce lo stato dei
segnali di ingresso:
<itemize>
<item>Bit 0: AX (stato dell'uscita del multivibratore (1 = alto))
<item>Bit 1: AY (stato dell'uscita del multivibratore (1 = alto))
<item>Bit 2: BX (stato dell'uscita del multivibratore (1 = alto))
<item>Bit 3: BY (stato dell'uscita del multivibratore (1 = alto))
<item>Bit 4: BA1 (ingresso digitale, 1 = alto)
<item>Bit 5: BA2 (ingresso digitale, 1 = alto)
<item>Bit 6: BB1 (ingresso digitale, 1 = alto)
<item>Bit 7: BB2 (ingresso digitale, 1 = alto)
</itemize>


<sect1>La porta seriale
<p>
Se il dispositivo d'interesse supporta qualcosa che somiglia alla 
RS-232, dovrebbe essere possibile usare la porta seriale per comunicare con 
esso. Il driver di Linux per le porte seriali dovrebbe andar bene per la
maggior parte delle applicazioni (non &egrave; necessario programmare 
direttamente la porta seriale, per farlo, probabilmente, bisognerebbe 
scrivere un driver per il kernel); &egrave; piuttosto versatile e quindi,
usando velocit&agrave; di trasmissione (b/s) non standard, o cose del 
genere, non dovrebbero esserci problemi.

Per maggiori informazioni sulla programmazione delle porte seriali sui
sistemi Unix, vedere la pagina di man di <tt/termios(3)/, il codice 
sorgente del driver per la porta seriale 
(<tt>linux/drivers/char/serial.c</>) e <url
url="http://www.easysw.com/&tilde;mike/serial/">.


<sect>Suggerimenti
<p>
Se si desidera un buon I/O analogico, collegare dei chip ADC e/o
DAC alla porta parallela (suggerimento: per l'alimentazione usare 
il connettore della porta giochi o un connettore di alimentazione
per i dischi ancora libero che va cablato fino all'esterno del computer,
a meno che non si abbia un dispositivo a basso consumo e si possa usare
la porta parallela stessa per l'alimentazione, o una fonte di 
alimentazione esterna), o comprare una scheda AD/DA (la maggior parte
di quelle pi&ugrave; vecchie, e pi&ugrave; lente, vengono controllate dalle 
porte I/O). Oppure, se ci si accontenta di 1 o 2 canali, se non d&agrave; 
fastidio l'imprecisione e (probabilmente) uno spostamento di fuori zero, 
dovrebbe bastare (e risulta veramente veloce) una scheda audio economica 
che sia supportata dai driver audio di Linux.

Con dispositivi analogici precisi, una cattiva messa a terra pu&ograve; 
generare degli errori negli input o output analogici. Se capita qualcosa 
del genere, provare ad isolare elettricamente il dispositivo dal computer, 
usando degli accoppiatori ottici (su <em/tutti/ i segnali tra il computer 
ed il dispositivo). Per ottenere un migliore isolamento provare a 
prendere l'alimentazione per gli accoppiatori dal computer (i segnali non 
usati sulla porta potrebbero fornire potenza sufficiente).

Se si sta cercando un programma per Linux per il progetto di circuiti 
stampati, c'&egrave; un'applicazione per X11, chiamata Pcb, che funziona 
piuttosto bene, almeno per applicazioni non molto complesse. &Egrave; 
inclusa in parecchie distribuzioni Linux ed &egrave; disponibile 
in <url url="ftp://sunsite.unc.edu/pub/Linux/apps/circuits/"> (nome del
file: <tt/pcb-*/).


<sect>Risoluzione dei problemi
<p>
<descrip>

<tag/D1./ Quando accedo alle porte ottengo "segmentation faults".

<tag/R1./ Il tuo programma non ha i privilegi di root, oppure la chiamata
<tt/ioperm()/ &egrave; fallita per qualche altro motivo. Controlla il 
valore restituito da <tt/ioperm()/. Inoltre, assicurati di stare 
accedendo proprio alle porte che hai abilitato con <tt/ioperm()/ (vedi 
D3). Se stai usando le macro di ritardo (<tt/inb_p()/, <tt/outb_p()/, e 
via dicendo), ricordati di effettuare una chiamata a <tt/ioperm()/ per 
ottenere l'accesso anche alla porta 0x80.


<tag/D2./ Non riesco a trovare le funzioni <tt/in*()/ e <tt/out*()/
definite ovunque, e gcc si lamenta per dei riferimenti non definiti 
(undefined references).

<tag/R2./ Non hai compilato con l'ottimizzazione abilitata (<tt/-O/)
e quindi gcc non riesce a risolvere le macro contenute in <tt>asm/io.h</>.
Oppure hai dimenticato di mettere <tt>#include &lt;asm/io.h&gt;</>


<tag/D3./ <tt/out*()/ non f&agrave; nulla, o f&agrave; qualcosa di strano.

<tag/R3./ Controlla l'ordine dei parametri; deve essere 
<tt/outb(valore, porta)/ e non <tt/outportb(porta, valore)/ come &egrave; 
in MS-DOS.


<tag/D4./ Voglio controllare un dispositivo standard RS-232/una stampante
parallela/un joystick...

<tag/R4./ Probabilmente ti conviene usare i driver esistenti (nel
kernel di Linux, o in un server X, o da qualche altra parte). I driver 
generalmente sono abbastanza versatili, tanto da far funzionare anche i 
dispositivi non standard, di solito. Vedere le informazioni precedentemente 
date riguardo le porte standard per indicazioni sulla documentazione.

</descrip>


<sect>Codice d'esempio
<p>
Ecco un esempio di codice per l'accesso alla porta I/O:


<tscreen><code>
/*
 * example.c: un semplicissimo esempio di I/O su porta
 *
 * Questo codice non fa nulla di utile, solo una scrittura sulla
 * porta, una pausa e una lettura dalla porta. Compilarlo con
 * `gcc -O2 -o example example.c' ed eseguitelo da root con "./example".
 */


#include <stdio.h>
#include <unistd.h>
#include <asm/io.h>

#define BASEPORT 0x378 /* lp1 */

int main()
{
  /* Richiede l'accesso alle porte */
  if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);}
  
  /* Imposta bassi (0) i segnali di dati (D0-7) della porta */
  outb(0, BASEPORT);
  
  /* Va in pausa (dorme) per un pò (100 ms) */
  usleep(100000);
  
  /* Legge dalla porta lo stato (BASE+1) e mostra il risultato */
  printf("stato: %d\n", inb(BASEPORT + 1));

  /* La porta non serve più */
  if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}

  exit(0);
}

/* fine dell'esempio example.c */
</code></tscreen>


<sect>Ringraziamenti
<p>
Ha contribuito troppa gente perch&eacute; possa elencarla, ma grazie
tante, a tutti. Non ho risposto a tutti i contributi che mi sono
giunti; me ne scuso, e grazie ancora per l'aiuto.
</article>
