LPT w Win9x

tjanusz

Tekst opisuje sposób sterowania portem równoległym komputera – LPT. Uprzedzam, że jest to metoda niezgodna z zaleceniami MałoMiękkiego, ponieważ aplikacja odwołuje się bezpośrednio do sprzętu, z pominięciem sterowników systemu, więc używasz jej na własną odpowiedzialność. Niemniej jednak pod Windowsami będącymi nakładkami na DOS’a (ME i starsze) działa poprawnie. Na dodatek szybkość komunikacji ze światem jest taka sama, jak w programie napisanym w Pascalu i działającym pod DOS’em – wbrew temu co piszą na wielu stronach o powolności takich rozwiązań.

Nie nadaje się dla systemów opartych na NT.

To czego nie da się uniknąć (o sprzęcie).

        Port LPT to nie tylko 8 równoległych linii do przesyłania danych, ale także dodatkowych 5 pracujących jako wejścia (przez nie peryferia informują komputer  o postępie  w transmisji danych) i kolejnych 4 jako wyjścia portu (komputer używa ich do sterowania transmisją) . Linie danych są dwukierunkowe – mogą być zarówno wejściem jak i wyjściem.

        Procesor widzi LPT poprzez kilka portów „wewnętrznych”. 

Podstawowy to rejestr danych, posiadający zazwyczaj adres 378h (AdresBazowy). Zapisywanie do niego powoduje włączanie napięć na odpowiednich liniach danych. Jest to port jedno bajtowy i każdy bit jest przypisany do linii danych : bit0 do linii D0 ... bit7 do linii D7. Jeśli bit ma wartość 1, na odpowiadającej mu linii włączane jest napięcie +5V. Podobne zależności występują przy odczycie z tym, że jeśli port jest odłączony od urządzenia peryferyjnego i ustawiony do odczytu, na wszystkich liniach jest napięcie i wszystkie bity mają wartość 1 – odczytywana jest liczba 255. Zwarcie jakiejkolwiek linii danych do masy spowoduje zanik napięcia i wyzerowanie odpowiedniego bitu (tak zresztą działają wszystkie normalne wejścia LPT).

Kolejny port to rejestr stanu (kontroli) o adresie AdresBazowy+1. Jest on jednobajtowy i tylko do odczytu. Poszczególnym bitom liczby czytanej z tego portu odpowiadają stany napięć na wspomnianych wcześniej dodatkowych pięciu wejściach ( oczywiście nie wszystkie bity mają swoją linię). I tu się pojawia ciekawostka : linia Busy jest zanegowana tzn. jeśli jest na niej napięcie, to odpowiadający jej bit (siódmy – najważniejszy) ma wartość 0, jeśli natomiast linia zostanie zwarta do masy, bit stanie się jedynką. Pozostałe normalnie – nie ma napięcia, nie ma bitu.

Jakakolwiek próba zapisu do portu będzie ignorowana.

I wreszcie ostatni (z tych potrzebnych) – rejestr sterowania z adresem AdresBazowy+2.

Jest to port w zasadzie do transmisji danych z komputera, chociaż możliwe jest czytanie bajtów z tego portu. Bity tego portu związane są z dodatkowymi czterema liniami wyjścia. Nie polecam wprowadzania danych do komputera przez te linie. Nie wiem czy elektronika wytrzymałaby zwarcie ich do masy, a możliwość czytania przydatna jest w programikach sprawdzających stan portu – funkcja czysto informacyjna.

        Tu także nie wszystkie bity mają swoje linie, ale niektóre wykorzystywane są do sterowania pracą portu i tak : bit5 steruje kierunkiem przepływu danych przez port danych. Jeśli ma wartość 0, port danych wysyła bity „w świat” – dane wpisywane do portu przenoszone są na linie. Dla wartości 1 tegoż bitu port jest ustawiony do odczytu danych – tym razem to bity czytanej liczby są ustawiane odpowiednio do napięć na liniach. 

        Kolejnym sterującym jest bit4  - ustawia generowanie przerwań. Nigdy nie zajmowałem się tym i nie będę się o tym rozpisywał.

        Wśród bitów powiązanych z liniami portu tylko bit2 ( linia InitPrinter) jest „prosta”. Pozostałe trzy są zanegowane.

        Dla formalności wspomnę, że współczesne porty wykonywane są w standardzie ECP (powyżej opisane rejestry dotyczą najstarszego SPP – całkowicie kontrolowanego przez oprogramowanie). ECP oczywiście obsługuje starsze standardy EPP i interesujący nas SPP, ale lepszym rozwiązaniem wydaje się przełączenie portu ECP na SPP. W tym celu należy ustawić bit5 na 1, bit6 i bit7 na zero w porcie pod adresem AdresBazowy+1026. Odpowiednia procedurka jest w załączonych przykładach.

I niech się stanie ..

        Mając już podstawową wiedzę można zacząć pisać program. Podstawą obsługi portu jest jedna funkcja i jedna procedura – bardzo niskiego poziomu :
  1.   Czytanie z dowolnego portu :
    
Function PortWy(Adres:Word):Byte;

   asm

     mov DX,AX {ustawienie danych we właściwych miejscach}

     in  AL,DX     {odczyt portu, w AL pojawi się czytany bajt}

   end;

 
  1.   Zapisywanie do portu
    
Procedure PortWe( Dana:byte; Adres:word);

   asm

    out DX,AL

   end;
        I właśnie te fragmenty kodu nie są zalecane przez MałoMiękkiego. Pierwsza funkcja zwraca bajt odczytany z portu o adresie podanym w parametrze Adres typu Word. Druga procedura wysyła liczbę poprzez parametr Dana, do portu o podanym adresie. 

        UWAGA!  Powyższe deklaracje zostały napisane dla kompilatora Delphi 5. Istotny jest zarówno typ parametrów jak i kolejność ich deklaracji, dlatego zalecam sprawdzenie pod debuggerem w jaki sposób kompilator przekazuje parametry funkcji i procedur do rejestrów procesora. Docelowo adres powinien znaleźć się w DX (stąd mov DX,AX w funkcji PortWy), dane natomiast w Al (w PortWe załatwia to kolejność deklaracji).

Procedurki te zawarte są w module LPT.pas (dział Download). Dodatkowo zamieściłem w nim nazwy bitów i adresy odpowiednich portów. A oto i sam moduł :

unit LPT;

 

interface

 

 type  {rejestry są rekordami, których pola noszą nazwy linii portu}

      TPortStanu = record     {port stanu - status port}

                    lBusy,lAck,lPaperOut,lSelectIn,

                    lError,lIRQ:byte; { Linie tego portu}

                    Adres:word;

                  end;

      TPortSter = record   {port sterowania - control port}

                   EbdP, {Enable bi-directional Port - bez styku!}

                   EIRQVA, { Enable IRQ Via Ack Line - bez styku!}

                   lSelPri, {Select Printer}

                   lInitPri, { Initialize Printer }

                   lAutoLin,  {Auto Linefeed }

                   lStrobe    {Strobe}

                           :byte;

                   Adres:word;

                  end;

      TECR      = record   { ECR trybu ECP. Nazwy poprzedzone „m” to tryby pracy portu ECP – obecnie montowanego na płytach }

                   mStandard, mByte,

                   mPPFIFO, {paralel Port FIFO Mode}

                   mECPFIFO, { ECP FIFO Mode}

                   mEPP, mFIFOTest, mConfig,

                   bECPI, { ECP Inerrtpt Bit}

                   bDMAE,  {DMA Enable Bit}

                   bECPS,  { ECP Service Bit}

                   FIFOFull, FIFOEmpty : byte;

                   Adres:word;

                  end;

     

 Const AdresBazowy:word =$378;   {adres portu danych. Jeśli twój komputer używa innego adresu, należy zmienić właśnie tą liczbę.}

 

{dalej liniom portów przypisywane są wartości wynikające z położenia odpowiadających im bitów}

 

       PortStanu: TPortStanu = (lBusy:128;lAck:64;lPaperOut:32;

                                lSelectIn:16;lError:8;lIRQ:4;

                                Adres:0);

       PortSter: TPortSter = (EbdP:32; EIRQVA:16; lSelPri:8;

                              lInitPri:4; lAutoLin:2; lStrobe:1;

                              Adres:0);

       ECR :TECR = (mStandard:0; mByte:32; mPPFIFO:64;

                    mECPFIFO:96; mEPP:128; mFIFOTest:192;

                    mConfig:224; bECPI:16; bDMAE:8; bECPS:4;

                    FIFOFull:2; FIFOEmpty:1 ;

                    Adres:0);

       { dla trybów pracy - oznaczone m..., istotne są aż

trzy MSB i stąd te "dziwne", nie będące potęgą 2  liczby.}

 

{i wreszcie nazwy udostępnianych funkcji i procedur}

 

 Procedure PortWe(Dana:byte; Adres:word); {wpisuje do portu}

 Function  PortWy(Adres:Word):Byte;       {czyta z portu}

 Procedure WybierzTryb(x:byte); { ustawia tryb w ECR}

implementation

 

{te dwie opisałem wcześniej}

 

  Function PortWy(Adres:Word):Byte;

   asm

     mov DX,AX

     in  AL,DX        

   end;

 

 Procedure PortWe( Dana:byte; Adres:word);

   asm

    out DX,AL

   end;

 

{i trzecia ustawiająca tryb pracy w rejestrze portu ECP.}

 

Procedure WybierzTryb(x:byte); {x – wybrany tryb : mStandard, mByte,                                      mPPFIFO,  mECPFIFO, mEPP, mFIFOTest lub mConfig. Nas interesował będzie wyłącznie mByte – dwukierunkowa praca w standardzie SPP}

var pmc:byte;

begin

{ponieważ nie można dowolnie zmieniać wszystkich bitów rejestru,          więc najpierw odczyt bajtu}

 pmc:=PortWy(ECR.Adres);

{ potem wyzerowanie trzech MSB, decydujących o trybie pracy}

 pmc:=(pmc and 31); 

{i w końcu ustawienie nowego trybu, bez zmiany pozostałych bitów}

 pmc:= (pmc or x ); 

{tak zmienioną liczbę z powrotem wpisuje do portu}

 PortWe(pmc,ECR.Adres );{teraz pracuje w trybie wskazanym przez x}

end;

 

{i już na sam koniec obliczenie adresów rejestrów na podstawie adresu bazowego (kompilator nie chciał dodawania w deklaracji stałych). Ponieważ adresy są polami w stałych rekordach, więc nie można wykonać dodawania bezpośrednio na nich. Ale od czego są wskaźniki?}

 

Var wsk:^word;

begin

wsk:=@PortStanu.Adres;

wsk^:=AdresBazowy+1;

wsk:=@PortSter.Adres;

wsk^:=Adresbazowy+2;

Wsk:=@ECR.Adres;

wsk^:=AdresBazowy+1026;

end.

Teraz już tylko wystarczy w sekcji uses pisanego programu dodać nazwę LPT i już można pisać i czytać do portu, używając niewątpliwie przyjemniejszych nazw, zamiast ciągu cyferek.

Nie muszę chyba dodawać, że dla wydobycia lub zmienienia dowolnego bitu z liczby odczytanej z któregokolwiek rejestru należy wykonywać operacje logiczne ( or, and, not), ale to już nie temat tego tekstu.

Programując port w standardzie SPP, ma się pełną kontrolę nad nim. To programista decyduje co i kiedy będzie włączane. Ma to też złe strony. Każde, najdrobniejsze nawet zdarzenie musi zostać oprogramowane. Jeśli jakaś linia zostanie włączona, trzeba pamiętać, że sama się nie wyłączy. Zupełnie inaczej jest w standardzie EPP, gdzie większość sygnałów generowana jest przez sprzęt, a ingerencja programu ogranicza się do wpisania lub odczytania bajtu z portu, ale to raczej temat na forum o elektronice.

Przykładem wykorzystania powyższego modułu jest dołączony programik „PortLPT”. Może daruję sobie zamieszczania całego kodu, starałem się pisać stosowne komentarze w kodzie źródłowym. Opiszę tylko co ten program robi.

Przede wszystkim odczytuje opisywane wcześniej rejestry i wyświetla wartości jako etykiety (Label.Caption). Dodatkowo sprawdza bity w odczytanych liczbach i również je wyświetla. Po uruchomieniu programu uwagę zwraca duża ilość CheckBox’ów. Otóż informują one o stanie napięć na wszystkich liniach portu. Zaznaczony CheckBox oznacza, że na odpowiedniej linii jest napięcie +5V, odznaczony – 0V. Właśnie w ten sposób można zauważyć negację niektórych bitów – CheckBox jest zaznaczony, pomimo zerowej wartości odpowiadającego mu bitu.

Procedurka sprawdzania rejestrów podpięta jest pod zdarzenie OnTimer.

Obecność CheckBox’ów pozwala także na ręczne sterowanie liniami portu. Zaznaczenie dowolnego z nich spowoduje włączenie napięcia na odpowiedniej linii portu, odznaczenie wyłącza napięcie (o ile linia pełni funkcję wyjścia, w przeciwnym wypadku nie da zmienić się stanu CheckBox’a. Z tego powodu nie oprogramowałem kliknięć na CheckBox’y rejestru stanu).

Jedynie CheckBox „Odczyt” w rejestrze sterowania nie jest związany z żadną linią. Przełącza on kierunek przepływu danych. Przy odznaczonym linie danych są wyjściami, przy zaznaczonym – wejściami.

Poza tym kod źródłowy jest gotowcem pokazującym jak sprawdzać i zmieniać wszystkie bity w LPT.

Ponieważ program zmienia rejestry sterujące portem ( ECR i PortSterowania), więc w czasie uruchamiania odczytuje bajty z tych rejestrów, przechowuje w pamięci i ponownie wpisuje w czasie wyłączania.

Uf – tochę się tego zrobiło, a zaledwie liznąłem temat. Jeśli masz jakieś pytania lub wątpliwości postaram się na nie odpowiedzieć. Moje adresy to :

turek.fm@poczta.fm lub janusz@orange.ichtj.waw.pl

Miłej zabawy!

          Janusz Turek

6 komentarzy

Wcale nie trzeba własnego, ani czyjegoś sterownika pod XP ( na NT i 2000 nie miałem możliwości sprawdzić )
w Delphim to mniej więcej tak by leciało:
var
hLPT : THandle; { uchwyt naszegoportu }
ret : dword; { potrzebne na dane 'zwrotne' }
data : integer; { dana do wystawienia }
begin
hLPT := CreateFile('LPT1', GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0); {pobieramy uchwyt do portu :}
WriteFile(hLPT, data, 1, ret, nil); {wypisujemy daną :}
CloseHandle( hLPT ); {zwalniamy uchwyt :}
end;

tylko w porcie trzeba zewrzeć razem piny 10,11,12,25

@Infor_mat_ik: Poczytaj artykuł o słowie kluczowym asm

Należałoby zamienić te encje znakowe na ich odpowiedniki (ja aktualnie nie mogę tego zrobić)

w funkcjach jest DUUŻY BŁĄD: Mianowicie, nie ma nigdzie "result:=", a to przecież PODSTAWA FUNCKJI.

Bardzo mi sie przydal ten artykul, ma tylko pytanie, gdzie moge sciągnąc wspomniany programik?

Nie widziałem potrzeby pisania na wysokim poziomie, bo poruszane problemy są bardzo proste, a mimo to wokół tematu narosło wiele niepozozumień (nawet na "elektrodzie" można przeczytać wiele nieprawdziwych informacji nt.). Ja postanowiłem zrobic to najprościej.
Myślalem nad opisem obsługi NT/2000/XP, ale postanowiłem tego nie robić ponieważ.

  1. Wymagałoby to napisania własnego sterownika, co uznałem za "gre nie warta świeczki", lub
  2. uzycia jednej z wieeeelu bibliotek udostępniających port pod tymi windami. Wtedy zrobiłbym z siebie lamusa przechwalającego się nie swoim kodem.
    Więc chyba zostanie tak jak jest. Tym bardziej, że linia windoz NT-pochodnych raczej nie nadaje się do sterowania kompem. Oczywiście jest to możliwe, ale ich sterowniki to programistyczne potworki. Po co kombinować jeśli na odpowiednim systemie można pracować łatwiej i wydajniej.

3mcie się!

Artykułowi dałem ocenę 4. Kierowałem się tutaj głównie tym, że całość jest pięknie opisana, krok po kroku. Niestety dałem tylko 4 ponieważ poziom artykułu nie wydaje mi się specjalnie wysoki. Przydałby się opis obsługi tego pod WinNT (a to już jest bardzo skomplikowane).
Ogólnie - fajnie zrobiony materiał :)