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):
inb_p
/outb_p
oraz portu 0x80udelay()
, jako że nanosleep()
zapewnia jaśniejszy sposób jego użycia.
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.
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.
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ć.
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.
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.
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.
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.
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.
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.
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.
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.
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)
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.
Port BASE+2
(Port kontrolny) jest tylko do zapisu (odczyt zwraca ostatnio zapisaną wartość)
i kontroluje poniższe sygnały kontrolne:
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)
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 +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.
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.
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-*
).
Dostaje błąd segmentacji pamięci kiedy dobieram się do portów
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
Nie mogę nigdzie znaleść deklaracji funkcji in*()
, out*()
i gcc narzeka na niezdefiniowane referencje.
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.
Wywołąnie out*()
nie robi nic bądź robi coś dziwnego.
Sprawdź kolejność parametrów; powinno być
outb(wartość, port)
, a nie outportb(port, wartość)
co jest popularne w MS-DOS.
Chcę sterować standardowym urządzeniem RS-232/portem równoległym/drukarką/joystickiem
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.
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 */
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.
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