Linux I/O port programming mini-HOWTO
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.htmlNa 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
Wyszukiwarka
Podobne podstrony:
io programming pl 11io programming pl 1io programming pl 2IO Programming pl (3)io programming pl 10io programming pl 9io programming pl 3io programming pl 6io programming pl 8io programming pl 7IO Programming plio programming pl 4io programming pl 5TK IO[pdf][PL] Pobrany z torrenty org ® nfo nfoCOMPACT IO PRESENTATION PLamd102 io pl09io port programming 3ogqzy3bscrrpgv753q3uywjfexgwwoiiffd46a 3ogqzy3bscrrpgv753q3uywjfexgwwoiiffd46aacu 250 io pl14więcej podobnych podstron