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 architek
turze 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
______________________________________________________________________
Spis treści
1. Wstęp
2. Używanie rejestrów wejścia/wyjścia w programach napisanych w C
2.1 Metoda zwykła
2.2 Metoda alternatywna: /dev/port
3. Przerwania (IRQ) oraz dostęp DMA
4. Mierzenie czasu z dużą dokładnością
4.1 Opóźnienia
4.1.1 Opóźnianie za pomocą funkcji sleep() i usleep()
4.1.2 Opóźnianie za pomocą funkcji nanosleep()
4.1.3 Opóźnianie za pomocą operacji I/O na porcie
4.1.4 Opóźnianie za pomocą instrukcji asemblerowych
4.1.5 Instrukcja rdtsc w Pentium
4.2 Mierzenie czasu
5. Inne języki programowania
6. Niektóre przydatne porty
6.1 Port równoległy
6.2 Port joysticka/Port gier
6.3 Port szeregowy
7. Porady
8. W razie kłopotów
9. Przykładowy program
10. Wyrazy uznania
11. Od tłumacza
______________________________________________________________________
11.. WWssttęępp
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.
22.. UUżżyywwaanniiee rreejjeessttrróóww wweejjśścciiaa//wwyyjjśścciiaa ww pprrooggrraammaacchh nnaappiissaannyycchh ww CC
22..11.. MMeettooddaa zzwwyykkłłaa
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 ; 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 _m_u_s_z_ą być
kompilowane z włączoną optymalizacją (gcc -O1 lub więcej) lub możesz
też zrobić #define extern bez parametru (puste) zanim włączysz
.
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
_w_s_z_y_s_t_k_i_c_h 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
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.
22..22.. MMeettooddaa aalltteerrnnaattyywwnnaa:: //ddeevv//ppoorrtt
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.
33.. PPrrzzeerrwwaanniiaa ((IIRRQQ)) oorraazz ddoossttęępp DDMMAA
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ć.
44.. MMiieerrzzeenniiee cczzaassuu zz dduużżąą ddookkłłaaddnnoośścciiąą
44..11.. OOppóóźźnniieenniiaa
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
aby uzyskać więcej informacji na ten
temat.
44..11..11.. OOppóóźźnniiaanniiee zzaa ppoommooccąą ffuunnkkccjjii sslleeeepp(()) ii uusslleeeepp(())
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.
44..11..22.. OOppóóźźnniiaanniiee zzaa ppoommooccąą ffuunnkkccjjii nnaannoosslleeeepp(())
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.
44..11..33.. OOppóóźźnniiaanniiee zzaa ppoommooccąą ooppeerraaccjjii II//OO nnaa ppoorrcciiee
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.
44..11..44.. OOppóóźźnniiaanniiee zzaa ppoommooccąą iinnssttrruukkccjjii aasseemmbblleerroowwyycchh
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 prze
ciwnym 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.
44..11..55.. IInnssttrruukkccjjaa rrddttsscc ww PPeennttiiuumm
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.
44..22.. MMiieerrzzeenniiee cczzaassuu
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.
55.. IInnnnee jjęęzzyykkii pprrooggrraammoowwaanniiaa
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.
66.. NNiieekkttóórree pprrzzyyddaattnnee ppoorrttyy
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)
66..11.. PPoorrtt rróówwnnoolleeggłłyy
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)
66..22.. PPoorrtt jjooyyssttiicckkaa//PPoorrtt ggiieerr
Port gier jest umieszczony pod adresami 0x200-0x207. Jeśli chcesz
kontrolować normalny joystick to jest do tego specjalny sterownik w
jÄ…drze, zobacz
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 wysok
iego 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)
66..33.. PPoorrtt sszzeerreeggoowwyy
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
.
77.. PPoorraaddyy
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 _w_s_z_y_s_t_k_i_c_h
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
(plik
pcb-*).
88.. WW rraazziiee kkłłooppoottóóww
PP11..
Dostaje błąd segmentacji pamięci kiedy dobieram się do portów
OO11..
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
PP22..
Nie mogę nigdzie znaleść deklaracji funkcji in*(), out*() i gcc
narzeka na niezdefiniowane referencje.
OO22..
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 do swojego programu.
PP33..
Wywołąnie out*() nie robi nic bądź robi coś dziwnego.
OO33..
Sprawdź kolejność parametrów; powinno być outb(wartość, port), a
nie outportb(port, wartość) co jest popularne w MS-DOS.
PP44..
Chcę sterować standardowym urządzeniem RS-232/portem
równoległym/drukarką/joystickiem
OO44..
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.
99.. PPrrzzyykkłłaaddoowwyy pprrooggrraamm
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
#include
#include
#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 */
______________________________________________________________________
1100.. WWyyrraazzyy uuzznnaanniiaa
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.
1111.. OOdd ttłłuummaacczzaa
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
Wyszukiwarka
Podobne podstrony:
io programming pl 11
io programming pl 1
io programming pl 2
IO Programming pl (3)
IO Programming pl (2)
io programming pl 10
io programming pl 9
io programming pl 3
io programming pl 6
io programming pl 8
io programming pl 7
io programming pl 4
io programming pl 5
TK IO[pdf][PL] Pobrany z torrenty org ® nfo nfo
COMPACT IO PRESENTATION PL
amd102 io pl09
io port programming 3ogqzy3bscrrpgv753q3uywjfexgwwoiiffd46a 3ogqzy3bscrrpgv753q3uywjfexgwwoiiffd46a
acu 250 io pl14
więcej podobnych podstron