1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792
|
Linux I/O port programming mini-HOWTO
Autore: Riku Saikkonen <Riku.Saikkonen@hut.fi>
v, 28 dicembre 1997
Questo HOWTO descrive la programmazione delle porte I/O e come realiz
zare delle brevi attese di tempo nei programmi che girano in modo
utente, su macchine basate sugli Intel x86. Traduzione di Fabrizio
Stefani, 15 ottobre 1999.
1. Introduzione
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 Copyright 1995-1997 di Riku Saikkonen. Vedere il
Linux HOWTO copyright
<http://sunsite.unc.edu/pub/Linux/docs/HOWTO/COPYRIGHT> per i
dettagli.
Se avete delle correzioni o qualcosa da aggiungere, contattatemi
liberamente via e-mail (NdT: in inglese) presso
(Riku.Saikkonen@hut.fi)...
Cambiamenti rispetto alla versione precedente (30 Mar 1997):
Chiarite le cose a proposito di inb_p/outb_p e la porta 0x80.
Tolte le informazioni su udelay(), poich nanosleep() fornisce un
modo pi pulito di usarlo.
Convertito in Linuxdoc-SGML e riorganizzato.
Numerose altre modifiche e aggiunte minori.
2. Usare le porte I/O nei programmi C
2.1. Il metodo normale
Le routine per accedere alle porte I/O sono in /usr/include/asm/io.h
(o linux/include/asm-i386/io.h nella distribuzione del sorgente del
kernel). Tali routine sono delle macro inline, quindi sufficiente
usare #include <asm/io.h>; non vi serve nessuna libreria aggiuntiva.
A causa di una limitazione in gcc (presente fino alla versione
2.7.2.3) e in egcs (tutte le versioni), dovete compilare qualunque
sorgente che usa tali routine con l'ottimizzazione abilitata (gcc -O1
o maggiore), oppure #define extern deve essere vuoto prima di mettere
#include <asm/io.h>.
Per il debugging potete usare gcc -g -O (almeno con le ultime versioni
di gcc), sebbene l'ottimizzazione possa causare, a volte, un
comportamento un po' strano del debugger. Se ci vi d noia, mettete
le routine che accedono alla porta I/O in un file sorgente separato e
compilate con l'ottimizzazione abilitata solo quest'ultimo.
Prima di accedere a qualunque porta, dovete dare al vostro programma
il permesso per farlo. Ci si fa chiamando la funzione ioperm()
(dichiarata in unistd.h e definita nel kernel) da qualche parte
all'inizio del vostro programma (prima di qualunque accesso ad una
porta I/O). La sintassi ioperm(from, num, turn_on), dove from il
primo numero di porta e num il numero di porte consecutive a cui dare
l'accesso. Per esempio, ioperm(0x300, 5, 1) d l'accesso alle porte da
0x300 a 0x304 (per un totale di 5 porte). L'ultimo argomento un
valore booleano che specifica se dare (true (1)) o togliere (false
(0)) al programma l'accesso alle porte. Potete chiamare pi volte
ioperm() per abilitare pi porte non consecutive. Vedere la pagina di
manuale di ioperm() per i dettagli sulla sintassi.
La chiamata ioperm() 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 ioperm per abilitare le porte
che volete usare, potete rinunciare ai privilegi di root. Alla fine
del programma non necessario abbandonare esplicitamente i privilegi
di accesso alle porte con ioperm(..., 0), ci verr fatto
automaticamente quando il processo esce.
Un setuid() fatto ad un utente che non root non disabilita l'accesso
alla porta che gli era stato fornito da ioperm(), invece un fork() lo
disabilita (il processo figlio non acquisisce l'accesso, mentre il
padre lo mantiene).
ioperm() pu fornire l'accesso solo alle porte da 0x000 a 0x3ff; per
porte pi alte dovete usare iopl() (che, con una sola chiamata, vi
fornisce l'accesso a tutte le porte). Per fornire al vostro programma
l'accesso a tutte le porte usate 3 come argomento di livello (cio
iopl(3)) (quindi state attenti -- accedere alle porte sbagliate pu
provocare un sacco di sgradevoli cose al vostro computer). Di nuovo,
per effettuare la chiamata a iopl() dovete avere i privilegi di root.
Vedere le pagine di manuale di iopl(2) per maggiori dettagli.
Poi, per accedere realmente alle porte... Per leggere (input) un byte
(8 bit) da una porta, chiamare inb(port), che restituisce il byte
presente all'ingresso. Per scrivere (output) un byte, chiamare
outb(value, port) (osservate l'ordine dei parametri). Per leggere una
word (16 bit) dalle porte x e x+1 (un byte da ognuna per formare una
word con l'istruzione assembler inw) chiamare inw(x). Per scrivere una
word sulle due porte si usa outw(value, x). Se non siete sicuri su
quali istruzioni (byte o word) usare per le porte, probabilmente vi
servono inb() e outb() --- 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.
Le macro inb_p(), outb_p(), inw_p(), e outw_p() 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 #define REALLY_SLOW_IO prima di
#include <asm/io.h>. Queste macro, di solito (a meno che inserite
#define SLOW_IO_BY_JUMPING, che 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
ioperm() (le scritture fatte sulla porta 0x80 non hanno conseguenze su
nessuna parte del sistema). Per ottenere dei ritardi con sistemi pi
versatili, continuate a leggere.
Nelle raccolte di pagine di manuale per Linux, nelle versioni
ragionevolmente recenti, ci sono le pagine di manuale per ioperm(2),
iopl(2) e per le suddette macro.
2.2. Un altro metodo: /dev/port
Un altro modo per accedere alle porte I/O quello di aprire, in
lettura e/o scrittura, con open(), il dispositivo a caratteri
/dev/port (numero primario 1, secondario 4) (le funzioni stdio f*()
hanno un buffer interno, quindi evitatele). Poi posizionatevi con un
lseek() sul byte appropriato nel file (posizione 0 del file = porta
0x00, posizione 1 del file = porta 0x01 e cos via...) e leggete
(read()), o scrivete (write()), un byte o una word da, o in, esso.
Ovviamente, perch ci funzioni, il vostro programma avr bisogno
dell'accesso in lettura/scrittura a /dev/port. Questo metodo
probabilmente pi lento del metodo normale precedentemente descritto,
ma esso non necessita n di ottimizzazioni in compilazione n della
funzione ioperm(). Non vi serve nemmeno di avere l'accesso da root, se
impostate per /dev/port l'accesso per utenti, o gruppi, non root ---
ma far questo una pessima idea, in termini di sicurezza del sistema,
poich possibile danneggiare il sistema, forse anche ottenere
l'accesso a root, usando /dev/port per accedere direttamente ai dischi
rigidi, alle schede di rete, ecc.
3. Gli interrupt (IRQ) e l'accesso DMA
Non possibile usare gli IRQ o il DMA direttamente da un processo in
modo utente. Dovete scrivere un driver di kernel; vedere The Linux
Kernel Hacker's Guide
<http://www.redhat.com:8080/HyperNews/get/khg.html> per i dettagli e
il codice sorgente del kernel per gli esempi.
Inoltre, non possibile disabilitare gli interrupt da un programma
che gira in modo utente.
4. Temporizzazione ad elevata precisione
4.1. Ritardi
Innanzi tutto devo precisare che, a causa della natura multitasking di
Linux, non 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 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 non ha importanza. Per minimizzare tale tempo
potreste assegnare al vostro processo un pi alto valore di priorit
(vedere la pagina di manuale di nice(2)), oppure potreste usare uno
scheduling in real time (vedere sotto).
Se volete una temporizzazione pi 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 sched_setscheduler(2) per i dettagli. C' un kernel
speciale che supporta l'hard real time; per maggiori informazioni
vedere <http://luz.cs.nmt.edu/~rtlinux/>.
4.1.1. Pause: sleep() e usleep()
Laciatemi incominciare con le pi semplici chiamate di funzioni di
temporizzazione. Per ritardi di pi secondi la scelta migliore ,
probabilmente, quella di usare sleep(). Per ritardi dell'ordine delle
decine di millisecondi (il ritardo minimo sembra essere di circa 10
ms) dovrebbe andar bene usleep(). 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
sleep(3) e usleep(3).
Per ritardi inferiori a, circa, 50 millisecondi (dipendentemente dal
carico del sistema e dalla velocit del processore e della macchina)
il rilascio della CPU richiede troppo tempo, ci perch (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, usleep(3) in genere ritarda
un po' pi della quantit specificatagli nei parametri, almeno 10 ms
circa.
4.1.2. nanosleep()
Nei kernel di Linux della serie 2.0.x, c' una nuova chiamata di
sistema, nanosleep() (vedere la pagina di manuale di nanosleep(2)),
che vi permette di dormire o ritardare per brevi periodi di tempo
(pochi microsecondi o poco pi).
Per ritardi fino a 2 ms, se (e solo se) il vostro processo impostato
per lo scheduling in soft real time, nanosleep() usa un ciclo di
attesa, altrimenti dorme ("sleep"), proprio come usleep().
Il ciclo di attesa usa udelay() (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 di questo tipo di
cicli di attesa una delle cose che BogoMips misura accuratamente).
Per i dettagli sul funzionamento vedere /usr/include/asm/delay.h.
4.1.3. Ritardare tramite l'I/O sulle porte
Un altro modo per realizzare un ritardo di pochi microsecondi di
effettuare delle operazioni di I/O su una porta. La lettura dalla, o
la scrittura sulla (come si fa stato descritto precedentemente),
porta 0x80 di un qualsiasi byte impiega quasi esattamente un
microsecondo, indipendentemente dal tipo e dalla velocit del
processore. Potete ripetere tale operazione pi 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 il metodo
normalmente usato da {in|out}[bw]_p() per realizzare il ritardo
(vedere 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, state accedendo direttamente alla porta
parallela, potete semplicemente effettuare degli inb() in pi su essa
per ottenere il ritardo.
4.1.4. Ritardare usando le istruzioni assembler
Se conoscete il tipo e la velocit del processore della macchina su
cui girer 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 sospendere il
vostro processo in qualsiasi momento, quindi i ritardi potrebbero
essere impredicibilmente pi lunghi del previsto). Nella tabella
sotto, la velocit interna del processore determina il numero di cicli
di clock richiesti; cio, 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).
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
(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.)
Le istruzioni nop e xchg, indicate nella tabella, non dovrebbero avere
effetti collaterali. Le altre potrebbero modificare i flag dei
registri, ma ci non dovrebbe essere un problema poich gcc dovrebbe
accorgersene. nop una buona scelta.
Per usarle nel vostro programma chiamate asm("istruzione"). La
sintassi delle istruzioni come nella tabella sopra. Se volete
mettere pi istruzioni in un singolo statement asm() separatele con
dei punti e virgola. Ad esempio asm("nop ; nop ; nop ; nop") esegue
quattro istruzioni nop, generando un ritardo di quattro cicli di clock
sui processori i486 o Pentium (o 12 cicli di clock su un i386).
gcc traduce asm() in codice assembler inline, per cui si risparmiano i
tempi per la chiamata di funzione.
Ritardi pi brevi di un ciclo di clock sono impossibili con
l'architettura Intel x86.
4.1.5. rdtsc per i Pentium
Per i Pentium, potete ottenere il numero di cicli di clock trascorsi
dall'ultimo avvio del sistema con il seguente codice C:
______________________________________________________________________
extern __inline__ unsigned long long int rdtsc()
{
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
}
______________________________________________________________________
Potete sondare tale valore per ritardare di quanti cicli di clock vi
pare.
4.2. Misurare il tempo
Per tempi della precisione dell'ordine del secondo probabilmente pi
facile usare time(). Per tempi pi precisi, gettimeofday() preciso
fino a circa un microsecondo (ma vedete quanto gi detto riguardo lo
scheduling). Con i Pentium, il frammento di codice sopra (rdtsc) ha
una precisione pari a un ciclo di clock.
Se volete che il vostro processo riceva un segnale dopo un certo
quanto di tempo, usate setitimer() o alarm(). Per i dettagli vedere le
pagine di manuale delle suddette funzioni.
5. Altri liguaggi di programmazione
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 ioperm() o iopl() come in
C, ma dopo di ci potete usare direttamente le istruzioni di
lettura/scrittura per l'I/O nella porta.
In altri linguaggi, a meno che possiate inserire nel programma codice
assembler o C inline, oppure possiate usare le chiamate di sistema
precedentemente menzionate, probabilmente pi 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 /dev/port come descritto
precedentemente.
6. Alcune utili porte
Vengono ora date delle informazioni per la programmazione delle porte
pi comuni che possono essere usate per l'I/O delle logiche TTL (o
CMOS) general purpose.
Se volete usare queste o altre porte per il loro scopo originale (cio
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 indirizzata a quelli che vogliono connettere alle
porte standard di un PC degli schermi LCD, dei motori passo passo, o
altri componenti specifici.
Se volete controllare un dispositivo di largo uso, come uno scanner
(che sul mercato gi da un po'), cercate se c' gi un driver di
Linux che lo riconosce. L'Hardware-HOWTO
<http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Hardware-HOWTO> un buon
posto da cui iniziare la ricerca.
<http://www.hut.fi/Misc/Electronics/> una buona fonte per
informazioni sulla connessione di dispositivi ai computer (e sugli
apparecchi elettronici in generale).
6.1. La porta parallela
L'indirizzo base della porta parallela (detto ``BASE'' nel seguito)
0x3bc per /dev/lp0, 0x378 per /dev/lp1 e 0x278 per /dev/lp2. Se volete
solo controllare un qualcosa che si comporta come una normale
stampante, vedete il Printing-HOWTO
<http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Printing-HOWTO>.
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 <http://www.fapo.com/> e
<http://www.senet.com.au/~cpeacock/parallel.htm>. Ricordate che poich
non 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 scrivendo un tale driver,
ma non conosco i dettagli della cosa.
La porta 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 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.
La porta BASE+1 (porta di Stato) di sola lettura e restituisce lo
stato dei seguenti segnali d'ingresso:
Bit 0 e 1, sono riservati.
Bit 2 stato dell'IRQ (non un pin, non so come funziona)
Bit 3 ERROR (1 = alto)
Bit 4 SLCT (1 = alto)
Bit 5 PE (1 = alto)
Bit 6 ACK (1 = alto)
Bit 7 -BUSY (0 = alto)
(Non sono sicuro degli stati alto e basso)
La porta BASE+2 (porta di Controllo) di sola scrittura (una lettura
restituisce gli ultimi dati scritti) e controlla i seguenti segnali di
stato:
Bit 0 -STROBE (0 = alto)
Bit 1 AUTO_FD_XT (1 = alto)
Bit 2 -INIT (0 = alto)
Bit 3 SLCT_IN (1 = alto)
Bit 4, quando impostato ad 1, abilita l'IRQ della porta parallela
(che si verifica nella transizione da basso ad alto di ACK)
Bit 5 controlla la direzione del modo esteso (0 = scrittura, 1 =
lettura) ed assolutamente di sola scrittura (una lettura di
questo bit non restituisce nulla di utile).
Bit 6 e 7, sono riservati.
(Di nuovo, non sono sicuro degli stati alto e basso)
Configurazione dei pin (connettore a "D" femmina a 25-pin sulla porta)
(i = input, ingresso; o = output, uscita):
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)
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 di
2,4 V (minimo). Per entrambi, lo stato basso di 0,5 V (massimo). Le
porte parallele non IBM probabilmente si discostano da questo
standard. Per maggiori informazioni a tal riguardo vedere
<http://www.hut.fi/Misc/Electronics/circuits/lptpower.html>.
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 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.)
6.2. La porta giochi (joystick)
La porta giochi situata agli indirizzi 0x200-0x207. Per controllare
i normali joystick c' un apposito driver a livello di kernel, vedere
<ftp://sunsite.unc.edu/pub/Linux/kernel/patches/>, nome del file
joystick-*.
Configurazione dei pin (connettore a "D" femmina a 15 pin sulla
porta):
1, 8, 9, 15: +5 V (alimentazione)
4, 5, 12: Massa
2, 7, 10, 14: ingressi digitali BA1, BA2, BB1 e BB2,
rispettivamente
3, 6, 11, 13: ingressi ``analogici'' AX, AY, BX e BY,
rispettivamente
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.
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 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).
I cosdetti 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' un resistore da 2,2 Kohm
e, fra l'uscita del multivibratore e la massa, c' 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).
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 facendo,
la durata dello stato alto del multivibratore proporzionale alla
resistenza del potenziometro nel joystick (cio alla posizione della
leva sull'asse corrispondente), secondo la relazione:
R = (t - 24,2) / 0,011
dove R la resistenza (in ohm) del potenziometro e t la durata dello
stato alto (in secondi).
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 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 molto
preciso perch 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 si spreca ancor pi tempo di CPU). Se
sapete che il segnale impiegher parecchio tempo (decine di ms) per
tornare basso, potete chiamare usleep() prima di cominciare il
controllo, dando cos quel tempo di CPU ad altri processi.
La sola porta di I/O a cui vi serve di accedere 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:
Bit 0: AX (stato dell'uscita del multivibratore (1 = alto))
Bit 1: AY (stato dell'uscita del multivibratore (1 = alto))
Bit 2: BX (stato dell'uscita del multivibratore (1 = alto))
Bit 3: BY (stato dell'uscita del multivibratore (1 = alto))
Bit 4: BA1 (ingresso digitale, 1 = alto)
Bit 5: BA2 (ingresso digitale, 1 = alto)
Bit 6: BB1 (ingresso digitale, 1 = alto)
Bit 7: BB2 (ingresso digitale, 1 = alto)
6.3. La porta seriale
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); piuttosto versatile, quindi usando velocit di
trasmissione (bps) 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 manuale di termios(3), il codice
sorgente del driver per la porta seriale (linux/drivers/char/serial.c)
e <http://www.easysw.com/~mike/serial/index.html>.
7. Suggerimenti
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 vecchie, pi lente, vengono controllate dalle porte
I/O). Oppure, se vi accontentate di 1 o 2 canali, non vi d fastidio
l'imprecisione e (probabilmente) uno spostamento di fuori zero,
dovrebbe bastarvi (ed davvero veloce) una scheda audio economica che
sia supportata dai driver audio di Linux.
Con dispositivi analogici precisi, una cattiva messa a terra pu
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
tutti 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).
Se state cercando un programma (per Linux) per il progetto di circuiti
stampati, c' un'applicazione per X11, chiamata Pcb, che funziona
piuttosto bene, almeno se non dovete fare niente di molto complesso.
inclusa in parecchie distribuzioni di Linux ed disponibile in
<ftp://sunsite.unc.edu/pub/Linux/apps/circuits/> (nomefile pcb-*).
8. Risoluzione dei problemi
D1.
Quando accedo alle porte ottengo "segmentation faults" (errori
di segmentazione).
R1.
Il tuo programma non ha i privilegi di root, oppure la chiamata
ioperm() fallita per qualche altro motivo. Controlla il valore
restituito da ioperm(). Inoltre, assicurati di stare accedendo
proprio alle porte che hai abilitato con ioperm() (vedi D3). Se
stai usando le macro di ritardo (inb_p(), outb_p(), e via
dicendo), ricordati di effettuare una chiamata a ioperm() per
ottenere l'accesso anche alla porta 0x80.
D2.
Non riesco a trovare le funzioni in*() e out*() definite
ovunque, e gcc si lamenta per dei riferimenti non definiti
(undefined references).
R2.
Non hai compilato con l'ottimizzazione abilitata (-O) e quindi
gcc non riesce a risolvere le macro contenute in asm/io.h.
Oppure non hai messo affatto #include <asm/io.h>
D3.
out*() non fa nulla, o fa qualcosa di strano.
R3.
Controlla l'ordine dei parametri; deve essere outb(valore,
porta) e non outportb(porta, valore) come in MS-DOS.
D4.
Voglio controllare un dispositivo standard RS-232/una stampante
parallela/un joystick...
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 che anche i
dispositivi non standard di solito ci funzionano. Vedere le
informazioni precedentemente date riguardo le porte standard per
indicazioni sulle relative documentazioni.
9. Codice d'esempio
Ecco un pezzo di un semplice codice d'esempio per l'accesso alla porta
I/O:
______________________________________________________________________
/*
* 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 <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 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 */
______________________________________________________________________
10. Crediti
Ha contribuito troppa gente perch 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.
|