Sophie

Sophie

distrib > Mandriva > 9.1 > i586 > by-pkgid > f1098342ec4a2b28475e34123ce17201 > files > 1389

howto-html-it-9.1-0.5mdk.noarch.rpm

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
 <META NAME="GENERATOR" CONTENT="SGML-Tools 1.0.9">
 <TITLE>Linux I/O port programming mini-HOWTO</TITLE>


</HEAD>
<BODY>
<H1>Linux I/O port programming mini-HOWTO</H1>

<H2>Autore: Riku Saikkonen <CODE>&lt;Riku.Saikkonen@hut.fi&gt;</CODE></H2>v, 28 dicembre 1997
<P><HR>
<EM>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 di 
<A HREF="mailto:fabrizio_stefani@yahoo.it">Fabrizio Stefani</A>, 15 ottobre 1999.</EM>
<HR>
<H2><A NAME="s1">1. Introduzione</A></H2>

<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 &egrave; derivato dal 
piccolissimo IO-Port mini-HOWTO dello stesso autore.
<P>Questo documento &egrave; Copyright 1995-1997 di Riku Saikkonen. Vedere il
<A HREF="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/COPYRIGHT">Linux HOWTO copyright</A> per i dettagli.
<P>Se avete delle correzioni o qualcosa da aggiungere, contattatemi 
liberamente via e-mail (NdT: in inglese) presso (<CODE>Riku.Saikkonen@hut.fi</CODE>)...
<P>Cambiamenti rispetto alla versione precedente (30 Mar 1997):
<UL>
<LI>Chiarite le cose a proposito di <CODE>inb_p</CODE>/<CODE>outb_p</CODE> e la porta 
0x80.</LI>
<LI>Tolte le informazioni su <CODE>udelay()</CODE>, poich&eacute; <CODE>nanosleep()</CODE>
fornisce un modo pi&ugrave; pulito di usarlo.</LI>
<LI>Convertito in Linuxdoc-SGML e riorganizzato.</LI>
<LI>Numerose altre modifiche e aggiunte minori.</LI>
</UL>
<P>
<P>
<H2><A NAME="s2">2. Usare le porte I/O nei programmi C</A></H2>

<H2>2.1 Il metodo normale</H2>

<P>Le routine per accedere alle porte I/O sono in <CODE>/usr/include/asm/io.h</CODE>
(o <CODE>linux/include/asm-i386/io.h</CODE> nella distribuzione del sorgente
del kernel). Tali routine sono delle macro inline, quindi &egrave; sufficiente
usare <CODE>#include &lt;asm/io.h&gt;</CODE>; non vi serve nessuna libreria
aggiuntiva.
<P>A causa di una limitazione in gcc (presente fino alla versione 2.7.2.3)
e in egcs (tutte le versioni), <EM>dovete</EM> compilare qualunque sorgente
che usa tali routine con l'ottimizzazione abilitata (<CODE>gcc -O1</CODE> o
maggiore), oppure <CODE>#define extern</CODE> deve essere vuoto prima di mettere
<CODE>#include &lt;asm/io.h&gt;</CODE>.
<P>Per il debugging potete usare <CODE>gcc -g -O</CODE> (almeno con le ultime
versioni di gcc), sebbene l'ottimizzazione possa causare, a volte, 
un comportamento un po' strano del debugger. Se ci&ograve; vi d&agrave; 
noia, mettete le routine che accedono alla porta I/O in un file sorgente
separato e compilate con l'ottimizzazione abilitata solo quest'ultimo.
<P>Prima di accedere a qualunque porta, dovete dare al vostro programma
il permesso per farlo. Ci&ograve; si fa chiamando la funzione <CODE>ioperm()</CODE>
(dichiarata in <CODE>unistd.h</CODE> e definita nel kernel) da qualche parte
all'inizio del vostro programma (prima di qualunque accesso ad una porta
I/O). La sintassi &egrave; <CODE>ioperm(from, num, turn_on)</CODE>, dove <CODE>from</CODE>
&egrave; il primo numero di porta e <CODE>num</CODE> il numero di porte consecutive a 
cui dare l'accesso. Per esempio, <CODE>ioperm(0x300, 5, 1)</CODE> 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. Potete chiamare pi&ugrave; volte 
<CODE>ioperm()</CODE> per abilitare pi&ugrave; porte non consecutive. Vedere la pagina
di manuale di <CODE>ioperm()</CODE> per i dettagli sulla sintassi.
<P>La chiamata <CODE>ioperm()</CODE> necessita che il vostro programma abbia
privilegi di root; quindi dovete o eseguirlo da root, oppure renderlo
suid root. Dopo che avete effettuato la chiamata <CODE>ioperm</CODE> per 
abilitare le porte che volete usare, potete rinunciare ai privilegi di
root. Alla fine del programma non &egrave; necessario abbandonare 
esplicitamente i privilegi di accesso alle porte con <CODE>ioperm(..., 0)</CODE>, 
ci&ograve; verr&agrave; fatto automaticamente quando il processo esce.
<P>Un <CODE>setuid()</CODE> fatto ad un utente che non &egrave; root non disabilita 
l'accesso alla porta che gli era stato fornito da <CODE>ioperm()</CODE>, invece
un <CODE>fork()</CODE> lo disabilita (il processo figlio non acquisisce l'accesso,
mentre il padre lo mantiene).
<P><CODE>ioperm()</CODE> pu&ograve; fornire l'accesso solo alle porte da 0x000 a 0x3ff;
per porte pi&ugrave; alte dovete usare <CODE>iopl()</CODE> (che, con una sola chiamata,
vi fornisce l'accesso a tutte le porte). Per fornire al vostro programma 
l'accesso a <EM>tutte</EM> le porte usate 3 come argomento di livello (cio&egrave; 
<CODE>iopl(3)</CODE>) (quindi state attenti --  accedere alle porte sbagliate
pu&ograve; provocare un sacco di sgradevoli cose al vostro computer). Di nuovo, 
per effettuare la chiamata a <CODE>iopl()</CODE> dovete avere i privilegi di root. 
Vedere le pagine di manuale di <CODE>iopl(2)</CODE> per maggiori dettagli.
<P>Poi, per accedere realmente alle porte... Per leggere (input) un byte
(8 bit) da una porta, chiamare <CODE>inb(port)</CODE>, che restituisce il byte
presente all'ingresso. Per scrivere (output) un byte, chiamare
<CODE>outb(value, port)</CODE> (osservate l'ordine dei parametri). Per leggere
una word (16 bit) dalle porte <CODE>x</CODE> e <CODE>x+1</CODE> (un byte da ognuna per
formare una word con l'istruzione assembler <CODE>inw</CODE>) chiamare 
<CODE>inw(x)</CODE>. Per scrivere una word sulle due porte si usa 
<CODE>outw(value, x)</CODE>. Se non siete sicuri su quali istruzioni (byte o
word) usare per le porte, probabilmente vi servono <CODE>inb()</CODE> e
<CODE>outb()</CODE> --- la maggior parte dei dispositivi sono progettati per
gestire l'accesso alle porte a livello di byte. Osservate che tutte 
le istruzioni per accedere alle porte richiedono, almeno, un 
microsecondo (circa) per essere eseguite.
<P>Le macro <CODE>inb_p()</CODE>, <CODE>outb_p()</CODE>, <CODE>inw_p()</CODE>, e <CODE>outw_p()</CODE>
funzionano esattamente come le precedenti, ma esse introducono un
ritardo, di circa un microsecondo, dopo l'accesso alla porta; potete
allungare tale ritardo a circa quattro microsecondi mettendo
<CODE>#define REALLY_SLOW_IO</CODE> prima di <CODE>#include &lt;asm/io.h&gt;</CODE>.
Queste macro, di solito (a meno che inserite 
<CODE>#define SLOW_IO_BY_JUMPING</CODE>, che &egrave; probabilmente meno preciso),
effettuano una scrittura sulla porta 0x80 per ottenere il ritardo; 
quindi, prima di usarle, dovete dargli l'accesso alla porta 0x80 con 
<CODE>ioperm()</CODE> (le scritture fatte sulla porta 0x80 non hanno conseguenze 
su nessuna parte del sistema). Per ottenere dei ritardi con sistemi 
pi&ugrave; versatili, continuate a leggere.
<P>Nelle raccolte di pagine di manuale per Linux, nelle versioni
ragionevolmente recenti, ci sono le pagine di manuale per <CODE>ioperm(2)</CODE>,
<CODE>iopl(2)</CODE> e per le suddette macro.
<P>
<P>
<H2>2.2 Un altro metodo: <CODE>/dev/port</CODE></H2>

<P>Un altro modo per accedere alle porte I/O &egrave; quello di aprire, in 
lettura e/o scrittura, con <CODE>open()</CODE>, il dispositivo a caratteri 
<CODE>/dev/port</CODE> (numero primario 1, secondario 4) (le funzioni stdio 
<CODE>f*()</CODE> hanno un buffer interno, quindi evitatele). Poi posizionatevi con un
<CODE>lseek()</CODE> sul byte appropriato nel file (posizione 0 del file = porta 0x00,
posizione 1 del file = porta 0x01 e cos&igrave; via...) e leggete (<CODE>read()</CODE>),
o scrivete (<CODE>write()</CODE>), un byte o una word da, o in, esso.
<P>Ovviamente, perch&eacute; ci&ograve; funzioni, il vostro programma avr&agrave;
bisogno dell'accesso in lettura/scrittura a <CODE>/dev/port</CODE>. Questo metodo 
&egrave; probabilmente pi&ugrave; lento del metodo normale precedentemente 
descritto, ma esso non necessita n&eacute; di ottimizzazioni in compilazione
n&eacute; della funzione <CODE>ioperm()</CODE>. Non vi serve nemmeno di avere l'accesso 
da root, se impostate per <CODE>/dev/port</CODE> l'accesso per utenti, o gruppi,
non root --- ma far questo &egrave; una pessima idea, in termini di sicurezza 
del sistema, poich&eacute; &egrave; possibile danneggiare il sistema, forse 
anche ottenere l'accesso a root, usando <CODE>/dev/port</CODE> per accedere 
direttamente ai dischi rigidi, alle schede di rete, ecc.
<P>
<P>
<H2><A NAME="s3">3. Gli interrupt (IRQ) e l'accesso DMA</A></H2>

<P>Non &egrave; possibile usare gli IRQ o il DMA direttamente da un processo 
in modo utente. Dovete scrivere un driver di kernel; vedere 
<A HREF="http://www.redhat.com:8080/HyperNews/get/khg.html">The Linux Kernel Hacker's Guide</A> per i dettagli e il codice sorgente del
kernel per gli esempi.
<P>Inoltre, non &egrave; possibile disabilitare gli interrupt da un programma
che gira in modo utente.
<P>
<P>
<H2><A NAME="s4">4. Temporizzazione ad elevata precisione</A></H2>

<H2>4.1 Ritardi</H2>

<P>Innanzi tutto devo 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. Il vostro 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 potreste assegnare 
al vostro processo un pi&ugrave; alto valore di priorit&agrave; (vedere 
la pagina di manuale di <CODE>nice(2)</CODE>), oppure potreste usare uno scheduling 
in real time (vedere sotto).
<P>Se volete una temporizzazione pi&ugrave; precisa di quella disponibile 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; vedere la pagina di 
manuale di <CODE>sched_setscheduler(2)</CODE> per i dettagli. C'&egrave; un kernel 
speciale che supporta l'hard real time; per maggiori informazioni vedere 
<A HREF="http://luz.cs.nmt.edu/~rtlinux/">http://luz.cs.nmt.edu/~rtlinux/</A>.
<P>
<H3>Pause: <CODE>sleep()</CODE> e <CODE>usleep()</CODE></H3>

<P>Laciatemi incominciare con le pi&ugrave; semplici chiamate di funzioni di
temporizzazione. Per ritardi di pi&ugrave; secondi la scelta migliore &egrave;,
probabilmente, quella di usare <CODE>sleep()</CODE>. Per ritardi dell'ordine 
delle decine di millisecondi (il ritardo minimo sembra essere di circa 
10 ms) dovrebbe andar bene <CODE>usleep()</CODE>. Tali 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 manuale di
<CODE>sleep(3)</CODE> e <CODE>usleep(3)</CODE>.
<P>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
l'architettura x86) lo scheduler generalmente lavora almeno dai 10 ai 30 
millisecondi prima di restituire il controllo al vostro processo. Per questo 
motivo, per i piccoli ritardi, <CODE>usleep(3)</CODE> in genere ritarda un po' 
pi&ugrave; della quantit&agrave; specificatagli nei parametri, almeno 10 ms 
circa.
<P>
<H3><CODE>nanosleep()</CODE></H3>

<P>Nei kernel di Linux della serie 2.0.x, c'&egrave; una nuova chiamata di 
sistema, <CODE>nanosleep()</CODE> (vedere la pagina di manuale di <CODE>nanosleep(2)</CODE>), 
che vi permette di dormire o ritardare per brevi periodi di tempo (pochi 
microsecondi o poco pi&ugrave;).
<P>Per ritardi fino a 2 ms, se (e solo se) il vostro processo &egrave; impostato
per lo scheduling in soft real time, <CODE>nanosleep()</CODE> usa un ciclo di 
attesa, altrimenti dorme ("sleep"), proprio come <CODE>usleep()</CODE>.
<P>Il ciclo di attesa usa <CODE>udelay()</CODE> (una funzione interna del kernel usata 
da parecchi driver del kernel) 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 <CODE>/usr/include/asm/delay.h</CODE>.
<P>
<H3>Ritardare tramite l'I/O sulle porte</H3>

<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 qualsiasi byte impiega quasi esattamente un microsecondo,
indipendentemente dal tipo e dalla velocit&agrave; del processore. Potete
ripetere tale operazione pi&ugrave; volte per ottenere un'attesa di pochi
microsecondi. La scrittura sulla porta non dovrebbe avere controindicazioni
su una qualsiasi macchina standard (e infatti qualche driver del kernel
usa questa tecnica). Questo &egrave; il metodo normalmente usato da 
<CODE>{in|out}[bw]_p()</CODE> per realizzare il ritardo (vedere <CODE>asm/io.h</CODE>).
<P>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, state accedendo direttamente alla porta parallela,
potete semplicemente effettuare degli <CODE>inb()</CODE> in pi&ugrave; su essa per 
ottenere il ritardo.
<P>
<H3>Ritardare usando le istruzioni assembler</H3>

<P>Se conoscete il tipo e la velocit&agrave; del processore della macchina
su cui girer&agrave; il programma, potete sfruttare un codice basato
sull'hardware, che usa certe istruzioni assembler, per realizzare
dei ritardi molto brevi (ma ricordate, lo scheduler pu&ograve; sospendere 
il vostro processo in qualsiasi momento, quindi i ritardi potrebbero 
essere impredicibilmente pi&ugrave; lunghi del previsto). Nella tabella
sotto, la velocit&agrave; interna del processore determina il numero di
cicli di clock richiesti; cio&egrave;, 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).
<P>
<BLOCKQUOTE><CODE>
<PRE>
Istruzione      cicli di clock      cicli di clock
                  su un i386          su un i486
                  
nop                   3                   1
xchg %ax,%ax          3                   3
or %ax,%ax            2                   1
mov %ax,%ax           2                   1
add %ax,0             2                   1
</PRE>
</CODE></BLOCKQUOTE>
<P>(Mi dispiace ma non conosco quelli dei Pentium, probabilmente sono
vicini a quelli del i486. Non ho trovato una istruzione che impieghi
un ciclo di clock su un i386. Usate le istruzioni che impiegano un
solo ciclo di clock, se possibile, altrimenti le tecniche di pipeling
dei moderni processori potrebbero abbreviare i tempi.)
<P>Le istruzioni <CODE>nop</CODE> e <CODE>xchg</CODE>, 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. <CODE>nop</CODE> &egrave; una buona scelta.
<P>Per usarle nel vostro programma chiamate <CODE>asm(&quot;istruzione&quot;)</CODE>.
La sintassi delle istruzioni &egrave; come nella tabella sopra. Se volete
mettere pi&ugrave; istruzioni in un singolo statement <CODE>asm()</CODE> separatele
con dei punti e virgola. Ad esempio 
<CODE>asm(&quot;nop ; nop ; nop ; nop&quot;)</CODE> esegue quattro istruzioni 
<CODE>nop</CODE>, generando un ritardo di quattro cicli di clock sui processori 
i486 o Pentium (o 12 cicli di clock su un i386).
<P>gcc traduce <CODE>asm()</CODE> in codice assembler inline, per cui si risparmiano 
i tempi per la chiamata di funzione.
<P>Ritardi pi&ugrave; brevi di un ciclo di clock sono impossibili con 
l'architettura Intel x86.
<P>
<H3><CODE>rdtsc</CODE> per i Pentium</H3>

<P>Per i Pentium, potete ottenere il numero di cicli di clock trascorsi
dall'ultimo avvio del sistema con il seguente codice C:
<P>
<BLOCKQUOTE><CODE>
<HR>
<PRE>
   extern __inline__ unsigned long long int rdtsc()
   {
     unsigned long long int x;
     __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
     return x;
   }
</PRE>
<HR>
</CODE></BLOCKQUOTE>
<P>Potete sondare tale valore per ritardare di quanti cicli di clock vi pare.
<P>
<P>
<H2>4.2 Misurare il tempo</H2>

<P>Per tempi della precisione dell'ordine del secondo &egrave; probabilmente 
pi&ugrave; facile usare <CODE>time()</CODE>. Per tempi pi&ugrave; precisi,
<CODE>gettimeofday()</CODE> &egrave; preciso fino a circa un microsecondo (ma vedete 
quanto gi&agrave; detto riguardo lo scheduling). Con i Pentium, il frammento 
di codice sopra (<CODE>rdtsc</CODE>) ha una precisione pari a un ciclo di clock.
<P>Se volete che il vostro processo riceva un segnale dopo un certo quanto 
di tempo, usate <CODE>setitimer()</CODE> o <CODE>alarm()</CODE>. Per i dettagli vedere le 
pagine di manuale delle suddette funzioni.
<P>
<P>
<H2><A NAME="s5">5. Altri liguaggi di programmazione</A></H2>

<P>La descrizione precedente era relativa specificatamente al linguaggio C. 
Dovrebbe valere inalterata per il C++ e l'Objective C. In assembler,
dovete effettuare la chiamata a <CODE>ioperm()</CODE> o <CODE>iopl()</CODE> come in C,
ma dopo di ci&ograve; potete usare direttamente le istruzioni di 
lettura/scrittura per l'I/O nella porta.
<P>In altri linguaggi, a meno che possiate inserire nel programma codice 
assembler o C inline, oppure possiate usare le chiamate di sistema
precedentemente menzionate, &egrave; probabilmente pi&ugrave; facile scrivere 
un semplice file sorgente C contenente le funzioni per l'accesso in I/O alle 
porte o i ritardi che vi servono, e compilarlo e linkarlo con il resto del 
programma. Oppure usare <CODE>/dev/port</CODE> come descritto precedentemente.
<P>
<P>
<H2><A NAME="s6">6. Alcune utili porte</A></H2>

<P>Vengono ora date delle informazioni per la programmazione delle porte 
pi&ugrave; comuni che possono essere usate per l'I/O delle logiche TTL 
(o CMOS) general purpose.
<P>Se volete usare queste o altre porte per il loro scopo originale (cio&egrave;
controllare una normale stampante o un modem), fareste meglio ad 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.
<P>Se volete controllare un dispositivo di largo uso, come uno scanner
(che &egrave; sul mercato gi&agrave; da un po'), cercate se c'&egrave; 
gi&agrave; un driver di Linux che lo riconosce. L'
<A HREF="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Hardware-HOWTO">Hardware-HOWTO</A> &egrave; un buon posto da cui iniziare la ricerca.
<P>
<A HREF="http://www.hut.fi/Misc/Electronics/">http://www.hut.fi/Misc/Electronics/</A> &egrave; una buona fonte
per informazioni sulla connessione di dispositivi ai computer (e sugli 
apparecchi elettronici in generale).
<P>
<P>
<H2>6.1 La porta parallela</H2>

<P>L'indirizzo base della porta parallela (detto ``<CODE>BASE</CODE>'' nel seguito)
&egrave; 0x3bc per <CODE>/dev/lp0</CODE>, 0x378 per <CODE>/dev/lp1</CODE> e 0x278 per
<CODE>/dev/lp2</CODE>. Se volete solo controllare un qualcosa che si comporta
come una normale stampante, vedete il 
<A HREF="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Printing-HOWTO">Printing-HOWTO</A>.
<P>Nella maggior parte delle porte parallele, oltre al modo standard di sola 
scrittura descritto qui sotto, esiste un modo bidirezionale `esteso'.
Per maggiori informazioni su tale argomento e sui nuovi modi ECP/EPP, 
vedere 
<A HREF="http://www.fapo.com/">http://www.fapo.com/</A> e 
<A HREF="http://www.senet.com.au/~cpeacock/parallel.htm">http://www.senet.com.au/~cpeacock/parallel.htm</A>. Ricordate
che poich&eacute; non &egrave; possibile usare gli IRQ o il DMA in un programma 
che gira in modo utente, per usare ECP/EPP dovrete probabilmente scrivere un 
driver kernel. Credo che qualcuno stia gi&agrave; scrivendo un tale driver, ma 
non conosco i dettagli della cosa.
<P>La porta <CODE>BASE+0</CODE> (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 in 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 modo reale esteso.
<P>La porta <CODE>BASE+1</CODE> (porta di Stato) &egrave; di sola lettura e restituisce 
lo stato dei seguenti segnali d'ingresso:
<UL>
<LI>Bit 0 e 1, sono riservati.</LI>
<LI>Bit 2 stato dell'IRQ (non &egrave; un pin, non so come funziona)</LI>
<LI>Bit 3 ERROR (1 = alto)</LI>
<LI>Bit 4 SLCT (1 = alto)</LI>
<LI>Bit 5 PE (1 = alto)</LI>
<LI>Bit 6 ACK (1 = alto)</LI>
<LI>Bit 7 -BUSY (0 = alto)</LI>
</UL>

(Non sono sicuro degli stati alto e basso)
<P>La porta <CODE>BASE+2</CODE> (porta di Controllo) &egrave; di sola scrittura (una 
lettura restituisce gli ultimi dati scritti) e controlla i seguenti segnali 
di stato:
<UL>
<LI>Bit 0 -STROBE (0 = alto)</LI>
<LI>Bit 1 AUTO_FD_XT (1 = alto)</LI>
<LI>Bit 2 -INIT (0 = alto)</LI>
<LI>Bit 3 SLCT_IN (1 = alto)</LI>
<LI>Bit 4, quando impostato ad 1, abilita l'IRQ della porta parallela (che 
si verifica nella transizione da basso ad alto di ACK)</LI>
<LI>Bit 5 controlla la direzione del modo esteso (0 = scrittura, 1 = lettura)
ed &egrave; assolutamente di sola scrittura (una lettura di questo bit 
non restituisce nulla di utile).</LI>
<LI>Bit 6 e 7, sono riservati.</LI>
</UL>

(Di nuovo, non sono sicuro degli stati alto e basso)
<P>Configurazione dei pin (connettore a "D" femmina a 25-pin sulla porta) 
(i = input, ingresso; o = output, uscita):
<BLOCKQUOTE><CODE>
<PRE>
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 (Massa)
</PRE>
</CODE></BLOCKQUOTE>
<P>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 
<A HREF="http://www.hut.fi/Misc/Electronics/circuits/lptpower.html">http://www.hut.fi/Misc/Electronics/circuits/lptpower.html</A>.
<P>In ultimo un avvertimento: state attenti con i collegamenti a massa.
Io 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'; semplicemente
disabilitate le porte di cui non avete bisogno e impostate l'indirizzo
I/O, della porta parallela sulla scheda, ad un indirizzo libero.
Non dovete preoccuparvi dell'IRQ della porta parallela visto che, 
normalmente, non viene usato.)
<P>
<P>
<H2>6.2 La porta giochi (joystick)</H2>

<P>La porta giochi &egrave; situata agli indirizzi 0x200-0x207. Per controllare
i normali joystick c'&egrave; un apposito driver a livello di kernel, vedere
<A HREF="ftp://sunsite.unc.edu/pub/Linux/kernel/patches/">ftp://sunsite.unc.edu/pub/Linux/kernel/patches/</A>, nome del
file <CODE>joystick-*</CODE>.
<P>Configurazione dei pin (connettore a "D" femmina a 15 pin sulla porta):
<UL>
<LI>1, 8, 9, 15: +5 V (alimentazione)</LI>
<LI>4, 5, 12: Massa</LI>
<LI>2, 7, 10, 14: ingressi digitali BA1, BA2, BB1 e BB2, rispettivamente</LI>
<LI>3, 6, 11, 13: ingressi ``analogici'' AX, AY, BX e BY, rispettivamente</LI>
</UL>
<P>I pin +5 V sembra che siano spesso collegati direttamente alle linee di 
alimentazione sulla piastra madre, quindi dovrebbero poter fornire un bel 
po' di potenza, a seconda della piastra madre, dell'alimentatore e 
della porta giochi.
<P>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 e potete 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 un Kohm).
<P>I cos&igrave;detti ingressi analogici in effetti misurano una resistenza. 
La porta giochi ha un quadruplo multivibratore monostabile (un integrato 
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. Un joystick (in senso fisico) ha 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).
<P>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  &egrave; proporzionale alla 
resistenza del potenziometro nel joystick (cio&egrave; alla posizione della 
leva sull'asse corrispondente), secondo la relazione:
<BLOCKQUOTE>
R = (t - 24,2) / 0,011
</BLOCKQUOTE>

dove R &egrave; la resistenza (in ohm) del potenziometro e t la durata dello
stato alto (in secondi).
<P>Quindi, per leggere gli ingressi analogici, dovete 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 
potete controllare costantemente la porta (a meno che usiate un driver a 
livello di kernel e disabilitiate gli interrupt durante il controllo; ma 
cos&igrave; si spreca ancor pi&ugrave; tempo di CPU). Se sapete che il 
segnale impiegher&agrave; parecchio tempo (decine di ms) per tornare basso, 
potete chiamare usleep() prima di cominciare il controllo, dando cos&igrave; 
quel tempo di CPU ad altri processi.
<P>La sola porta di I/O a cui vi serve di accedere &egrave; la porta 0x201 (le
altre porte o si comportano identicamente, o non fanno nulla). Qualsiasi
scrittura su questa porta (non importa cosa scrivete) attiva il 
multivibratore. Una lettura da questa porta restituisce lo stato dei
segnali di ingresso:
<UL>
<LI>Bit 0: AX (stato dell'uscita del multivibratore (1 = alto))</LI>
<LI>Bit 1: AY (stato dell'uscita del multivibratore (1 = alto))</LI>
<LI>Bit 2: BX (stato dell'uscita del multivibratore (1 = alto))</LI>
<LI>Bit 3: BY (stato dell'uscita del multivibratore (1 = alto))</LI>
<LI>Bit 4: BA1 (ingresso digitale, 1 = alto)</LI>
<LI>Bit 5: BA2 (ingresso digitale, 1 = alto)</LI>
<LI>Bit 6: BB1 (ingresso digitale, 1 = alto)</LI>
<LI>Bit 7: BB2 (ingresso digitale, 1 = alto)</LI>
</UL>
<P>
<P>
<H2>6.3 La porta seriale</H2>

<P>Se il dispositivo che vi interessa supporta qualcosa che somiglia alla 
RS-232, allora dovreste poter usare la porta seriale per comunicare con 
esso. Il driver di Linux per le porte seriali dovrebbe andar bene per quasi 
tutte le applicazioni (non dovete programmare direttamente la porta seriale, 
per farlo, probabilmente, dovreste scrivere un driver kernel); &egrave; 
piuttosto versatile, quindi usando velocit&agrave; di trasmissione (bps) 
non standard, o cose del genere, non dovrebbero esserci problemi.
<P>Per maggiori informazioni sulla programmazione delle porte seriali sui
sistemi Unix, vedere la pagina di manuale di <CODE>termios(3)</CODE>, il codice 
sorgente del driver per la porta seriale 
(<CODE>linux/drivers/char/serial.c</CODE>) e 
<A HREF="http://www.easysw.com/~mike/serial/index.html">http://www.easysw.com/~mike/serial/index.html</A>.
<P>
<P>
<H2><A NAME="s7">7. Suggerimenti</A></H2>

<P>Se volete un buon I/O analogico, potete collegare dei chip ADC e/o
DAC alla porta parallela (suggerimento: per l'alimentazione usate 
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 abbiate un dispositivo a basso consumo e possiate 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, pi&ugrave; lente, vengono controllate dalle 
porte I/O). Oppure, se vi accontentate di 1 o 2 canali, non vi d&agrave; 
fastidio l'imprecisione e (probabilmente) uno spostamento di fuori zero, 
dovrebbe bastarvi (ed &egrave; davvero veloce) una scheda audio economica 
che sia supportata dai driver audio di Linux.
<P>Con dispositivi analogici precisi, una cattiva messa a terra pu&ograve; 
generare degli errori negli input o output analogici. Se vi capita qualcosa 
del genere, potreste provare ad isolare elettricamente il vostro dispositivo
dal computer, usando degli accoppiatori ottici (su <EM>tutti</EM> i segnali tra 
il computer ed il dispositivo). Per ottenere un migliore isolamento provate 
a prendere l'alimentazione per gli accoppiatori dal computer (i segnali non 
usati sulla porta potrebbero fornire potenza sufficiente).
<P>Se state cercando un programma (per Linux) per il progetto di circuiti 
stampati, c'&egrave; un'applicazione per X11, chiamata Pcb, che funziona 
piuttosto bene, almeno se non dovete fare niente di molto complesso. 
&Egrave; inclusa in parecchie distribuzioni di Linux ed &egrave; disponibile 
in 
<A HREF="ftp://sunsite.unc.edu/pub/Linux/apps/circuits/">ftp://sunsite.unc.edu/pub/Linux/apps/circuits/</A> (nomefile 
<CODE>pcb-*</CODE>).
<P>
<P>
<H2><A NAME="s8">8. Risoluzione dei problemi</A></H2>

<P>
<DL>
<DT><B>D1.</B><DD><P>Quando accedo alle porte ottengo "segmentation faults"
(errori di segmentazione).
<P>
<DT><B>R1.</B><DD><P>Il tuo programma non ha i privilegi di root, oppure la chiamata
<CODE>ioperm()</CODE> &egrave; fallita per qualche altro motivo. Controlla il 
valore restituito da <CODE>ioperm()</CODE>. Inoltre, assicurati di stare 
accedendo proprio alle porte che hai abilitato con <CODE>ioperm()</CODE> (vedi 
D3). Se stai usando le macro di ritardo (<CODE>inb_p()</CODE>, <CODE>outb_p()</CODE>, e 
via dicendo), ricordati di effettuare una chiamata a <CODE>ioperm()</CODE> per 
ottenere l'accesso anche alla porta 0x80.
<P>
<DT><B>D2.</B><DD><P>Non riesco a trovare le funzioni <CODE>in*()</CODE> e <CODE>out*()</CODE>
definite ovunque, e gcc si lamenta per dei riferimenti non definiti 
(undefined references).
<P>
<DT><B>R2.</B><DD><P>Non hai compilato con l'ottimizzazione abilitata (<CODE>-O</CODE>)
e quindi gcc non riesce a risolvere le macro contenute in <CODE>asm/io.h</CODE>.
Oppure non hai messo affatto <CODE>#include &lt;asm/io.h&gt;</CODE>
<P>
<DT><B>D3.</B><DD><P><CODE>out*()</CODE> non fa nulla, o fa qualcosa di strano.
<P>
<DT><B>R3.</B><DD><P>Controlla l'ordine dei parametri; deve essere 
<CODE>outb(valore, porta)</CODE> e non <CODE>outportb(porta, valore)</CODE> come &egrave; 
in MS-DOS.
<P>
<DT><B>D4.</B><DD><P>Voglio controllare un dispositivo standard RS-232/una stampante
parallela/un joystick...
<P>
<DT><B>R4.</B><DD><P>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 che anche i dispositivi non
standard di solito ci funzionano. Vedere le informazioni precedentemente 
date riguardo le porte standard per indicazioni sulle relative 
documentazioni.
</DL>
<P>
<P>
<H2><A NAME="s9">9. Codice d'esempio</A></H2>

<P>Ecco un pezzo di un semplice codice d'esempio per l'accesso alla porta I/O:
<P>
<BLOCKQUOTE><CODE>
<HR>
<PRE>
/*
 * 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. Compilatelo con
 * `gcc -O2 -o example example.c' ed eseguitelo da root con `./example'.
 */

#include &lt;stdio.h>
#include &lt;unistd.h>
#include &lt;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 po' (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 ci serve piu' */
  if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}

  exit(0);
}

/* fine dell'esempio example.c */
</PRE>
<HR>
</CODE></BLOCKQUOTE>
<P>
<P>
<H2><A NAME="s10">10. Crediti</A></H2>

<P>Ha contribuito troppa gente perch&eacute; io 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.
</BODY>
</HTML>