# Linux I/O port programming mini-HOWTO

## Autor: Riku Saikkonen Riku.Saikkonen@hut.fi
v, 28 Grudnia 1997
Wersja polska: Michał Szwaczko michalsz@lena.zsg.lublin.pl
v1.0, 1 Marca 2000

Ten dokument HOWTO opisuje programowanie sprzętowych rejestrów wejścia/wyjścia oraz zatrzymywanie na niewielkie okresy czasu pracy programów użytkownika działających na Linuxie pracującym w architekturze Intel x86. Dokument został napisany w standardzie ISO-8859-2. Oryginał tego dokumentu znajduje się pod adresem ftp://ftp.icm.edu.pl/pub/Linux/sunsite/docs/HOWTO/mini

## 1. Wstęp

Ten dokument HOWTO opisuje programowanie sprzętowych rejestrów wejścia/wyjścia oraz zatrzymywanie na niewielkie okresy czasu pracy programów użytkownika działających na Linuxie pracującym w architekturze Intel x86. Dokument ten jest następcą bardzo małego IO-port mini-HOWTO tego samego autora. Dokument został napisany przez Riku Saikkonen. Copyright 1995-1997 Riku Saikkonen. Po szczegóły odnośnie praw autorskich zobacz Linux HOWTO copyright. Jeśli chcesz coś poprawić lub dodać to śmiało możesz do mnie pisać (`Riku.Saikkonen@hut.fi`)...

Zmiany w stosunku do poprzedniej wersji (30 Marca 1997):

- Wyjaśnienie kwestii odnoszących się do `inb_p`/`outb_p` oraz portu 0x80
- Usunięcie informacji o `udelay()`, jako że `nanosleep()` zapewnia jaśniejszy sposób jego użycia.
- Przekształcenie dokumentu na format SGML-Linuxdoc oraz niewielka reorganizacja.
- Wiele mniejszych dodatków i modyfikacji.

## 2. Używanie rejestrów wejścia/wyjścia w programach napisanych w C

## 2.1 Metoda zwykła

Procedury dostępu do portów (rejestrów) we/wy umieszczone są w `/usr/include/asm/io.h` (lub w `linux/include/asm-i386/io.h` w źródłach jądra) Procedury te występują w formie makr do wstawienia a więc wystarczy że zrobisz `#include <asm/io.h>`; nie potrzebujesz już żadnych dodatkowych bibliotek.

Z powodu ograniczeń w gcc (obecna wersja 2.7.2.3 i poniżej) oraz egcs (wszystkie wersje) programy wykorzystujące te procedury muszą być kompilowane z włączoną optymalizacją (`gcc -O1` lub więcej) lub możesz też zrobić `#define extern` bez parametru (puste) zanim włączysz `<asm/io.h>`.

Do odpluskwiania (debugging) możesz użyć `gcc -g -O` (przynajmniej w nowoczesnych wersjach gcc), chociaż optymalizacja może spowodować, iż debugger będzie zachowywał się trochę dziwnie. Jeśli to sprawia Ci kłopot, procedury korzystające z rejestrów we/wy wstaw do osobnego pliku i tylko ten plik poddaj optymalizacji podczas kompilacji.

Zanim dostaniesz się do jakiegokolwiek portu we/wy, musisz nadać swemu programowi uprawnienia do tego. Robi się to wywołując funkcję `ioperm()` (zdeklarowaną w `unistd.h` i zdefiniowaną w jądrze) gdzieś w okolicach początku programu (przed jakimkolwiek dostępem do portów) Składnia tego polecenia to `ioperm(skąd,ile,włacz)` gdzie `skąd` jest pierwszym portem do którego chcesz mieć dostęp a `ile` liczbą kolejnych portów do których chcesz mieć dostęp. Dla przykładu `ioperm(0x300, 5, 1)` da ci dostęp do portów od 0x300 do 0x304 (w sumie pięc portów). Ostatni argument to wartośc logiczna mówiąca o tym czy program otrzyma dostęp do portów (1 - prawda) bądź nie (0 - fałsz). Możesz wywoływać `ioperm()` wiele razy aby włączyć wiele nieciągłych obszarów rejestrów. Zajrzyj do `ioperm(2)` w podręczniku systemowym man po szczegóły odnośnie składni.

Wywołanie `ioperm()` wymaga aby twój program miał uprawnienia root'a: wobec czego albo musisz uruchamiać go jako root albo dać mu suid root'a. Po wywołaniu `ioperm()` możesz już zrezygnować z uprawnień root'a. Nie wymaga się abyś w sposób jawny wyłączał uprzywilejowany dostęp do portów (czyli `ioperm(....,0)`) na końcu programu; robi się to automatycznie wraz z zakończeniem procesu.

Funkcja `setuid()` w wypadku użytkownika bez przywilejów root'a nie odbiera dostępu do portów przydzielonego przez `ioperm()` ale funkcja `fork()` już tak. (proces potomny nie dostaje dostępu ale proces-rodzic go zachowuje)

`ioperm()` może jedynie umożliwić dostęp do portów od 0x000 do 0x3FF. Jeśli chcesz używać innych portów (wyższych) musisz użyć funkcji `iopl()` (która daje ci dostęp do wszystkich portów od razu) Użyj argumentu 3 (czyli `iopl(3)`) aby dać swemu programowi dostęp do wszystkich portów (bądź ostrożny - dostęp do niewłaściwych portów może wywołać najróżniejsze usterki komputera). Musisz mieć uprawnienia root'a aby wywołać `iopl()`. Po szczególy zajrzyj do podręcznika systemowego man: `iopl(2)`

Teraz właściwy dostęp do portów.. Aby odczytać bajt (8 bitów) z portu, wywołaj funkcję `inb(port)`, zwraca ona odczytaną wartość. Aby zapisać bajt do portu wywołaj `outb(wartość,port)` (zwróć uwagę na kolejność argumentów) Aby odczytać słowo (16 bitów) z portów `x` i `x+1` (jeden bajt z każdego łączone w słowo za pomocą asemblerowej instrukcji `inw`) wywołaj `inw(x)` Aby zapisać słowo do tych dwóch portów użyj `outw(wartość,x)` Jeśli nie jesteś pewien której instrukcji użyć (operującej bajtem czy słowem) prawdopodobnie będziesz chciał użyć `inb()` i `outb()` - większość urządzeń zaprojektowana jest z bajtowo-zorientowanym dostępem do portów. Zauważ, że wykonanie każdej instrukcji operującej na portach zajmuje co najmniej mikrosekundę.

Makra `inb_p()`,`outb_p()`,`inw_p()` i `outw_p()` działają identycznie z tymi powyżej ale dodatkowo wykonują opóźnienie (około 1 mikrosekundy) po dostępie do portu. Możesz spowodować że opóźnienie będzie wartości ok 4 mikrosekund jeśli zrobisz `#define REALLY_SLOW_IO` zanim włączysz `<asm/io.h>` Makra te zwykle (chyba że zrobisz `#define SLOW_IO_BY_JUMPING`, które jest raczej mniej dokładne) używają zapisu do portu 0x80 zby uzyskać opóźnienie, wobec czego musisz najpierw dać dostęp do portu 0x80 za pomocą `ioperm()`. Zapisy do portu 0x80 nie powinny mieć wpływu na żadną częsć systemu. Jeśli chcesz poznać bardziej wszechstronne metody dostępu do portów czytaj dalej.

W stosunkowo nowych dystrybucjach podręcznika systemowego man znajdują się strony `ioperm(2)`, `iopl(2)` oraz powyższych makr.

## 2.2 Metoda alternatywna: /dev/port

Innym sposobem dostępu do rejestrów I/O jest otworzenie urządzenia `/dev/port` do zapisu lub/i odczytu za pomocą funkcji `open()` (`/dev/port` to urządzenie znakowe, numer główny 1, poboczny 4) (Funkcje `f*()` z biblioteki stdio mają wewnętrzne buforowanie więc ich unikaj) Następnie wywołaj `lseek()` do odpowiedniego bajtu w tym pliku (pozycja 0 = port 0x00 pozycja 1 = port 0x01 itd) i czytaj (`read()`) lub zapisuj (`write()`) bajt lub słowo.

Oczywiście aby to zadziałało twój program musi mieć możliwośc czytania i zapisywania do `/dev/port.` Metoda ta jest prawdopodobnie wolniejsza od metody normalnej pokazanej powyżej ale nie potrzebuje ani optymalizacji programu ani wywoływania `ioperm()`. Nie potrzebuje też uprawnień root'a jeśli nadasz zwykłym użytkownikom lub jakiejś grupie prawa dostępu do `/dev/port` - jest to jednak nieporządane z punktu widzenia bezpieczeństwa systemu, jako że możliwe jest wtedy uszkodzenie systemu, a może nawet zdobycie przywilejów root'a przez bezpośredni dostęp za pomocą `/dev/port` do dysków, kart sieciowych itp.

## 3. Przerwania (IRQ) oraz dostęp DMA

Nie można bezpośrednio korzystać z IRQ lub DMA w programach użytkownika. Musisz napisać sterownik/moduł do jądra; zajrzyj do The Linux Kernel Hacker's Guide po szczegóły a także do źródeł jądra po przykłady. W programach użytkownika nie można też przerwań wyłączać.

## 4. Mierzenie czasu z dużą dokładnością

## 4.1 Opóźnienia

Przede wszystkim trzeba stwierdzić że z powodu wielozadaniowej natury Linuxa nie można zagwarantować że procesy w trybie użytkownika będą mieć dokładną kontrolę czasu. Twój proces może zostać rozpoczęty w każdej chwili i może mu to zająć od 10 milisekund do kilku sekund (na bardzo obciążonych systemach). Jednakże, dla większości aplikacji używających portów I/O nie ma to większego znaczenia. Aby zminimalizować to zjawisko możesz nadać swemu procesowi wyższy priorytet (zobacz `nice(2)` w podręczniku man) lub używać zarządzania procesami w czasie rzeczywistym (patrz niżej)

Jeśli chcesz berdziej precyzyjnie odmierzać czas niż pozwalają ci na to procesy w trybie użytkownika, możesz skorzystać ze wsparcia dla procesów użytkownika w czasie rzeczywistym. Jądra 2.x.x mają niewielkie wsparcie dla czasu rzeczywistego; zobacz `sched_setscheduler(2)` w podręczniku man. Jest również specjalne jądro które ma zaawansowane wsparcie dla czasu rzeczywistego. Zobacz http://luz.cs.nmt.edu/~rtlinux aby uzyskać więcej informacji na ten temat.

### Opóźnianie za pomocą funkcji sleep() i usleep()

Zacznijmy teraz od łatwiejszych wywołań. Jeśli chcesz opóźnień rzędu sekund to najpewniej jest użyć `sleep()`. Dla opóźnień rzędu przynajmniej dziesiątek milisekund (10ms wydaje się być najmniejszym możliwym opóźnieniem) powinieneś użyć `usleep()`. Funkcje te oddają czas innym procesom a więc czas procesora nie jest marnowany. Zajrzyj do `sleep(3)` i `usleep(3)` w podręczniku man po szczegóły dotyczące tych funkcji.

Jeśli potrzebujesz opóźnień mniejszych niż 50 milisekund (w zależności od prędkości procesora i samego komputera oraz obciążenia systemu) oddawanie czasu procesora zajmuje zbyt wiele czasu ponieważ linuxowy zarządca procesów (w architekturze x86) zwykle zabiera około 10-30 milisekund zanim zwróci kontrolę do twojego procesu. Z tego powodu dla małych opóźnień `usleep()` zwykle opóźnia o trochę więcej czasu niż podajesz w parametrze a przynajmniej około 10 ms.

### Opóźnianie za pomocą funkcji nanosleep()

W serii jąder 2.0.x znajduje się nowa funkcja systemowa `nanosleep()` (zobacz `nanosleep(2)` w podręczniku man) która pozwala opóźniać lub zatrzymywać proces na krótkie okresy czasu (kilka mikrosekund lub więcej).

Dla uzyskania opóźnień <=2ms, jeśli (i tylko wtedy) twój proces jest ustawiony na zarządzanie z wsparciem dla czasu rzeczywistego (poprzez `sched_setscheduler()`), `nanosleep()` używa pętli opóźniającej; w przeciwnym razie zatrzymuje proces tak jak `usleep()`.

Pętla opóźniająca używa `udelay()` (wewnętrznej funkcji jądra użtwanej przez wiele jego modułów) a długość pętli jest obliczana na podstawie wartości BogoMips (prędkość tego rodzaju pętli opóźniającej jest jedną z rzeczy którą BogoMips dokładnie mierzy) Zobacz `/usr/include/asm/delay.h` po szczegóły odnośnie działania tego mechanizmu.

### Opóźnianie za pomocą operacji I/O na porcie

Inną metodą opóźniania o niewielkie ilości mikrosekund są operacje I/O na portach. Czytanie/zapisywanie jakiegokolwiek bajtu z/do portu 0x80 (patrz wyżej jak to się robi) powinno dać opóźnienie prawie dokładnie 1 mikrosekundy bez względu na typ procesora i jego prędkość. Możesz tak robić wiele razy aby uzyskać opóźnienie rzędu kilku mikrosekund. Sposób ten nie powinien wywoływać żadnych szkodliwych efektów ubocznych na żadnym standardowym komputerze (nawet niektóre moduły jądra go używają). W ten sposób uzyskują opóźnienie funkcje `{in|out}[bw]_p()` (zobacz `asm/io.h`).

W zasadzie, instrukcje I/O na większości portów w obszarze 0-0x3ff zbierają prawie dokładnie 1 mikrosekundę, więc jeśli na przykład używasz bezpośrednio portu równoległego po prostu wykonaj kilka razy `inb()` z tego portu aby uzyskać opóźnienie.

### Opóźnianie za pomocą instrukcji asemblerowych

Jeśli znasz typ procesora i prędkośc zegara w komputerze na którym będzie działał twój program, możesz na stałe uzyskać krótsze opóźnienia poprzez zastosowanie pewnych rozkazów asemblerowych (pamiętaj jednak że twój proces może się zacząć w dowolnym momencie wobec czego opóźnienia te mogą być większe od czasu do czasu) W tabeli poniżej wewnętrzna prędkość procesora określa liczbę cykli np dla procesora 50MHz (np. 486DX-50 lub 486DX2-50) jeden cykl zegara zajmuje 1/50000000 sekundy. (200 nanosekund).

> `

>     Instrukcja   cykle zegara na 386    cykle zegara na 486
>     nop                   3                   1
>     xchg %ax,%ax          3                   3
>     or %ax,%ax            2                   1
>     mov %ax,%ax           2                   1
>     add %ax,0             2                   1

> `

(Przykro mi ale niewiele wiem o Pentium. Pewnie podobnie do 486. Nie mogę znaleźć żadnej instrukcji która zajmowała by tylko jeden cykl zegara na 386. Jeśli możesz, używaj instrukcji jednocyklowych, w przeciwnym razie architektura potokowa (pipelining) używana w nowoczesnych procesorach może dać jeszcze krótsze czasy)

Rozkazy `nop` i `xchg` z tabeli powyżej nie powinny powodować żadnych skutków ubocznych. Reszta może modyfikować rejestr znaczników ale nie powinno mieć to znaczenia bo gcc powinno to wykryć. Użycie `nop` jest dobrym wyborem.

Aby skorzystać z powyższego, trzeba w swoim programie wywołać funkcję `asm("instrukcja")` Składnia instrukcji jest taka sama jak w tabeli powyżej. Jeśli chcesz użyć kilku instrukcji w jednym wywołaniu funkcji `asm` rozdziel je średnikami. Na przykład `asm("nop ; nop ; nop ; nop")` wywoła cztery razy instrukcję `nop` opóźniając nasz program o 4 cykle na 486 lub Pentium (lub 12 cykli na 386)

Funkcja `asm()` jest przez gcc tłumaczona na wstawkę w asemblerze a więc nie ma zagrożenia przekroczenia limitu wywołań funkcji.

Opóźnienia krótsze niż jeden cykl zegara nie są możliwe w architekturze Intel i386.

### Instrukcja rdtsc w Pentium

Jeśli masz Pentium, możesz odczytać ilość cykli zegara które upłynęły od ostatniego uruchomienia komputera. Robi się to za pomocą takiego kodu w C:

> `

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

> `

Możesz odczytywać tą wartość w celu opóźniania o dowolną ilość cykli.

## 4.2 Mierzenie czasu

Dla czasów rzędu sekundy prawdopodobnie najłatwiej będzie użyć funkcji `time()`. Dla bardziej dokładnych pomiarów: `gettimeofday()` jest dokładne co do mikrosekundy (ale zobacz wyżej o zarządzaniu procesami) Dla Pentium fragment kodu `rdtsc` powyżej jest dokładny co do cykla zegarowego.

Jeśli chcesz aby twój proces dostawał jakiś sygnał po jakimś czasie, użyj `settimer()` lub `alarm()`. Zajrzyj do stron podręcznika man dotyczących tych funkcji.

## 5. Inne języki programowania

Opis powyżej koncentruje się na języku C. Powinien bezpośrednio odnośić się też do C++ i Objective C. W asemblerze musisz wywołać `ioperm()` lub `iopl()` tak jak w C ale potem możesz już używać instrukcji czytania/zapisywania portów bezpośrednio.

W innych językach, jeśli nie możesz wstawiać do programu wstawek w asemblerze lub C bądź jeśli nie możesz użyć funkcji systemowych opisanych powyżej, najłatwiej będzie napisać osobny program w C ze wszystkimi operacjami na portach I/O i wszystkimi opóźnieniami których potrzbujesz po czym skompilować go i zlinkować z resztą twojego programu. Możesz też użyć `/dev/port` jak to opisano powyżej.

## 6. Niektóre przydatne porty

Teraz trochę informacji programistycznych dotyczących zwykłych portów które mogą być bezpośrednio użyte do operacji I/O w logice TTL (lub CMOS).

Jeśli chcesz używać tych lub innych zwykłych portów zgodnie z ich normalnym przeznaczeniem (np chcesz sterować normalną drukarką bądź modemem) powinieneś najpewniej użyć istniejących sterowników (zwykle dołączonych do jądra) zamiast programować porty bezpośrednio jak to opisuje ten dokument. Ten rozdział jest dla tych którzy chcą podłączyć do standardowych portów komputera wyświetlacze LCD, silniki krokowe lub inne niestandardowe urządzenia elektroniczne.

Jeśli chcesz sterować jakimś urżądzeniem produkowanym na rynek masowy, np. skanerem (które to urządzenie jest już na rynku jakiś czas) poszukaj raczej istniejącego sterownika Linuxowego. Dobrym miejscem aby zacząć jego poszukiwania jest Hardware-HOWTO

www.hut.fi/Misc/Electronics jest dobrym źródłem informacji dotyczących podłączania urządzeń do komputera (jak i samej elektroniki w ogóle)

## 6.1 Port równoległy

Adres bazowy portu równoległego (zwany poniżej `>BASE`) to 0x3bc dla `/dev/lp0`, 0x378 dla `/dev/lp1` i 0x278 dla `/dev/lp2`. Jeśli chcesz sterować tylko czymś co działa jak normalna drukarka powinieneś zapoznać się z Printing-HOWTO.

Oprócz standardowego trybu tylko-do-zapisu opisanego powyżej, w większości portów równoległych istnieje jeszcze rozszeżony tryb dwukierunkowy. Po informacje na ten temat oraz na temat nowych trybów ECP/EPP (jak i samego standardu IEEE 1284 w ogóle) zajrzyj na http://www.fapo.com oraz na http://www.senet.com.au/~cpeacock/parallel.htm Pamiętaj, że skoro nie możesz używać DMA i IRQ w programach użytkownika, aby użyć trybów ECP/EPP będziesz prawdopodobnie musiał napisać własny sterownik do jądra. Zdaje się, że ktoś już pisze taki sterownik ale nie znam szczegółów.

Port `BASE+0` (port danych) kontroluje sygnały danych portu (D0 do D7 dla bitów od 0 do 7) Stany: 0=niski (0 V), 1=wysoki (5 V). Zapis do tego portu zatrzaskuje dane na pinach. Odczyt zwraca ostatnio zapisaną daną w trybie normalnym bądź rozszeżonym lub dane z innego urządzenia w rozszeżonym trybie odczytu.

Port `BASE+1` (port statusu) jest tylko-do-odczytu i zwraca stan poniższych sygnałów wejsciowych.

- Bity 0 i 1 są zarezerwowane.
- Bit 2 Status IRQ (nie jest to sygnał z pina - nie wiem w jaki sposób to działa)
- Bit 3 ERROR (1=stan wysoki)
- Bit 4 SLCT (1=stan wysoki)
- Bit 5 PE (1=stan wysoki)
- Bit 6 ACK (1=stan wysoki)
- Bit 7 -BUSY (0=stan wysoki)

(Nie jestem pewien stanów tych bitów.)

Port `BASE+2` (Port kontrolny) jest tylko do zapisu (odczyt zwraca ostatnio zapisaną wartość) i kontroluje poniższe sygnały kontrolne:

- Bit 0 -STROBE (0=stan wysoki)
- Bit 1 AUTO_FD_XT (1=stan wysoki)
- Bit 2 -INIT (0=stan wysoki)
- Bit 3 SLCT_IN (1=stan wysoki)
- Bit 4 gdy ustawiony na 1 - włącza IRQ portu równoległego (co ma miejsce przy przejsciu sygnału ACK ze stanu niskiego do wysokiego)
- Bit 5 kontroluje kierunek danych w trybie rozszeżonym (0 = zapis, 1 = odczyt) i jest całkowicie tylko-do-zapisu. (Odczyt nie zwraca niczego użytecznego dla tego bitu).
- Bity 6 i 7 są zarezerwowane.

(Znowu nie jestem pewien stanów.)

Rozkład wyprowadzeń. (25 pinowe żeńskie gniazdo typu D) (i=wejscie, o=wyjscie):

> `

>     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

> `

Specyfikacja IBM twierdzi że piny 1,14,16 i 17 (wyjscia kontrolne) mają otwarte kolektory podpięte do +5V przez 4.7 kiloomowe oporniki (dostarcza 20 mA, pobór 0.55 mA, wyjscie w stanie wysokim +5V minus to co podpięte) Reszta pinów pobiera 24 mA, dostarcza 15 mA a wyjscie w stanie wysokim to minimum 2.4V. Stan niski dla wszystkich to maximum 0.5V. Porty równoległe inne niż IBM prawdopodobnie odbiegają od tego standardu. Po więcej informacji na ten temat zajrzyj na http://www.hut.fi/Misc/Electronics/circuits/lptpower.html

Na koniec jeszcze ostrzeżenie: Uważaj z uziemieniem. Zepsułem już parę portów przez podłączanie się do nich gdy komputer był włączony. Dobrze jest w takim wypadku używać portów równoległych nie zintegrowanych z płytą główną (zwykle można dodać drugi port równoległy do komputera za pomocą taniej standardowej karty I/O (tzw ajołki - tłum); po prostu wyłącz porty których nie potrzebujesz i ustaw adres portu na karcie IO na jakiś wolny adres. Nie musisz się martwić o IRQ dla portu równoległego gdyż normalnie się go nie używa)

## 6.2 Port joysticka/Port gier

Port gier jest umieszczony pod adresami 0x200-0x207. Jeśli chcesz kontrolować normalny joystick to jest do tego specjalny sterownik w jądrze, zobacz ftp://sunsite.icm.edu.pl/sunsite/pub/Linux/kernel/patches Plik nazywa się joystick-*.

Rozkłąd wyprowadzeń od stony portu (15-pinowe żeńskie gniazdo typu D-shell):

- piny 1,8,9,15: +5 V (zasilanie)
- piny 4,5,12: Masa
- piny 2,7,10,14: Wejscia cyfrowe (Odpowiednio: BA1, BA2, BB1, i BB2)
- piny 3,6,11,13: Wejscia ``analogowe'' (Odpowiednio: AX, AY, BX, i BY)

Piny +5V zwykle są podłączane bezpośrednio do linii zasilania na płycie głównej, więc, w zależności od płyty, zasilacza i portu ,powinny dawać całkiem sporo mocy. Cyfrowe wejścia są używane dla przycisków joysticków które możesz sobie podłączyć do portu (joystick A i B, dwa przyciski każdy) Wejścia te powinny być na poziomach TTL a ich stan możesz odczytać z portu statusu (zobacz poniżej) Prawdziwy joystick zwraca stan niski (0V) kiedy przycisk jest naciśnięty a stan wysoki (+5V z pinów zasilających przez jednokiloomowy rezystor) kiedy jest zwolniony.

Tak-zwane wejścia analogowe w istocie mierzą opór. Port joysticka ma przyłączony do tych 4 wejść poczwórny multiwibrator (quad one-shot multivibrator) (scalak 558) Na każdym wejściu mamy rezystor 2.2k pomiędzy pinem a wejściem multiwibratora oraz kondensator 0.01uF pomiędzy wyjściem multiwibratora a masą. Prawdziwy joystick ma jeszcze potencjometr dla każdej z osi (X i Y) umieszczony pomiędzy +5V a właściwym pinem wejsćiowym (AX lub AY dla joysticka A oraz BX lub BY dla joysticka B)

Kiedy jest aktywny, multiwibrator ustawia swoje wyjścia w stan wysoki (5V) i oczekuje aż każdy z kondensatorów osiągnie 3.3V po czym ustawia w stan niski odpowiednie linie wyjściowe. Dlatego właśnie czas trwania okresu kiedy multivibrator jest w stanie wysokim jest proporcjonalny do oporu potencjometru joysticka. (czyli pozycji joysticka na odpowiedniej osi) Relacja wygląda tak:

> R = (t - 24.2) / 0.011,

gdzie R to opór potencjometru w omach a t to czas trwania stanu wysokiego w sekundach.

Wobec tego aby odczytać wejśćia analogowe musisz najpierw uaktywnić multiwibrator (za pomocą zapisu do odpowiedniego portu - patrz niżej) po czym odczytywać stan czterech osi (za pomocą następujących po sobie odczytów z portów) aż zmienią stan z wysokiego na niski po czym mierzysz czas trwania stanu wysokiego. Odczytywanie takie zużywa sporo czasu procesora, co w systemie wielozadaniowym takim jak Linux nie będącym systemem czasu rzeczywistego powoduje niedokłądność rezultatów gdyż nie można odczytywać portu stale (chyba że użyjesz sterownika niskopoziomowego i wyłączysz przerwania na czas odczytów - ale to zabiera jeszcze więcej czasu procesora). Jeśli wiesz że przejście do stanu niskiego zajmie sygnałowi dłużśzy czas (rzędu 10 ms) możesz użyć usleep() przed odczytem oddając w ten sposób czas procesora innym procesom.

Jedynym portem do którego potrzebujesz mieć dostęp to port 0x201 (inne porty zachowują się identycznie bądź nie robią nic). Każdy zapis (nie ważne czego) do tego portu uaktywnia multiwibrator. Odczyt z tego portu zwraca stan poszczególnych sygnałów wejściowych.

- Bit 0: AX (stan (1=stan wysoki) wyjśćia multiwibratora)
- Bit 1: AY (stan (1=stan wysoki) wyjśćia multiwibratora)
- Bit 2: BX (stan (1=stan wysoki) wyjśćia multiwibratora)
- Bit 3: BY (stan (1=stan wysoki) wyjśćia multiwibratora)
- Bit 4: BA1 (wejśćie cyfrowe, 1=stan wysoki)
- Bit 5: BA2 (wejśćie cyfrowe, 1=stan wysoki)
- Bit 6: BB1 (wejśćie cyfrowe, 1=stan wysoki)
- Bit 7: BB2 (wejśćie cyfrowe, 1=stan wysoki)

## 6.3 Port szeregowy

Jeśli urządzenie z którym się komunikujesz przypomina coś co działą jak RS-232 możęsz do tego celu użyć portu szeregowego. Linuxowy sterownik portu szeregowego powinien wystarczyć w prawie wszystkich zastosowaniach (nie powinienneś mieć potrzeby programować port bezpośrednio, a nawet jeśli chciałbyś to robić prawdopodobnie musiałbyś napisać własny moduł do jądra.) Sterownik Linuxowy jest całkiem wszechstronny a więc używanie na przykład niestandardowych prędkości portu nie powinno być problemem.

Jeśli chcesz dowiedzieć się więcej o programowaniu portu szeregowego w Linuxie zobacz stronę `termios(3)` w podręczniku systemowym man, źródła sterownika (`linux/drivers/char/serial.c`), i http://www.easysw.com/~mike/serial/index.html.

## 7. Porady

Jeśli zależy Ci na dobrej obsłudze analogowej transmisji I/O, możesz podłączyć do portu równoległego przetworniki analogowo-cyfrowe bądż cyfrowo-analogowe (ADC,DAC).(Podpowiedź: weż zasilanie z portu joysticka bądż z wolnej wtyczki zasilania dysku twardego wyprowadzonej na zewnątrz obudowy, chyba że masz urządzenie nie wymagające dużej mocy, wtedy możesz wziąść zasilanie z samego portu. Możesz też użyć zewnętrznego zasilacza.) Możesz też kupić kartę przetworników analogowo-cyfrowych i cyfrowo analogowych (AD/DA) (większość starszych/wolniejszych takich kart jest sterowana za pomocą portów I/O) Jeśli nie przeszkadza ci małą ilość kanałów (1 lub 2) niedokładność oraz (możliwe) złe zerowanie, dobra (i szybka) będzie tania karta dżwiękowa wspierana przez Linuxowy sterownik.

W czułych urządzeniach analogowych niewłaściwe uziemienie możę spowodować błędy na wejściach bądź wyjściach. Jeśli przytrafi Ci się coś takiego, możesz spróbować odizolować elektrycznie urządzenie od komputera przez użycie transoptorów (optocouplers) na wszystkich sygnałach pomiędzy Twoim urządzeniem a komputerem. Aby zapewnić lepszą izolację zasilanie do transoptorów spróbuj wziąść z komputera (wolne sygnały na porcie mogą dać wystarczającą ilość mocy)

Jeśli szukasz oprogramowania do projektowania płytek drukowanych na Linuxa to jest darmowa aplikacja która nazywa się Pcb i powinna sprawiać się dobrze, przynajmniej wtedy kiedy nie robisz czegoś bardzo skomplikowanego. Załączona jest ona do wielu dystrybucji Linuxa a dostępna na ftp://sunsite.icm.edu.pl/pub/Linux/sunsite/apps/circuits/ (plik `pcb-*`).

## 8. W razie kłopotów

P1.

  Dostaje błąd segmentacji pamięci kiedy dobieram się do portów

O1.

  Albo twój program nie ma uprawnień root'a bądź wywołanie `ioperm()` nie powiodło się z jakiegoś innego powodu. Sprawdź wartość powrotną funkcji `ioperm()`. Sprawdź również czy rzeczywiśćie operujesz na portach do których uzyskałeś dostęp za pomocą `ioperm()` (zobacz P3). Jeśli używasz makr opóźniających (`inb_p()`, `outb_p()`, itd), pamiętaj aby wywołać `ioperm()` również wtedy jeśli chcesz uzyskać dostęp do portu 0x80

P2.

  Nie mogę nigdzie znaleść deklaracji funkcji `in*()`, `out*()` i gcc narzeka na niezdefiniowane referencje.

O2.

  Nie kompilowałeś z włączoną optymalizacją (`-O`), i w ten sposób gcc nie mógł odnaleźć makr w katalogu `asm/io.h`. Albo nie włączyłeś w ogóle `<asm/io.h>` do swojego programu.

P3.

  Wywołąnie `out*()` nie robi nic bądź robi coś dziwnego.

O3.

  Sprawdź kolejność parametrów; powinno być `outb(wartość, port)`, a nie `outportb(port, wartość)` co jest popularne w MS-DOS.

P4.

  Chcę sterować standardowym urządzeniem RS-232/portem równoległym/drukarką/joystickiem

O4.

  Lepiej będzie jak użyjesz istniejących sterowników z jądra, X serwera lub czegoś innego. Sterowniki te są zazwyczaj dosyć wszechstronne więc nawet lekko niestandardowe urządzenia z nimi współpracują. Zobacz wyżej informacje o zwykłych portach, są tam odnośniki do stosownej dokumentacji.

## 9. Przykładowy program

Oto kawałek prostego przykładowego programu demonstrującego dostęp do rejestrów I/O.

> `

>     /*
>      * example.c: bardzo prosty przykład dostępu do portów I/O
>      *
>      * Program ten nie robi nic użytecznego, zapisuje do portu, czeka i
>      * odczytuje z portu. Kompilacja: gcc -O2 -o example example.c
>      * Uruchamiac jako ./example będąc root'em
>     /

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

>     #define BASEPORT 0x378 /* lp1 */

>     int main()
>     {
>       /* Uzyskaj dostęp do portów */
>       if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);}

>       /* Ustaw wszystkie bity danych (D0-D7) w stan niski (0) */
>       outb(0, BASEPORT);

>       /* Zaczekaj chwilkę (100 ms) */
>       usleep(100000);

>       /* Odczytaj z rejestru statusowego (BASE+1) i wyświetl rezultat */
>       printf("status: %d\n", inb(BASEPORT + 1));

>       /* Już nie potrzebujemy portów */
>       if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}

>       exit(0);
>     }
>     /* koniec example.c */

> `

## 10. Wyrazy uznania

Pomogło mi zbyt wiele osób aby je tutaj wszystkie wymienić, ale wszystkim bardzo dziękuję. Nie odpowiedziałem im wszystkim; przepraszam za to i jeszcze raz dzięki za pomoc.

## 11. Od tłumacza

Niniejsze tłumaczenie objęte jest prawami autorskimi Michała Szwaczko. Dozwolone jest rozpowszechnianie i dystrybucja na prawach takich samych jak dokument oryginalny.(Zobacz HOWTO-Copyright)

Zmiany wprowadzone przeze mnie do dokumentu polegają na zastąpieniu adresów niektórych serwerów ftp przez ich mirrory w Polsce.

Zdaję sobie sprawę że tłumaczenie nie jest wolne od wad i błędów. Jeśli znajdziesz jakieś, proszę napisz do mnie:
michalsz@lena.zsg.lublin.pl

Oficjalną stroną grupy tłumaczy HOWTO jest http://www.jtz.org.pl