• 1
  • info@lungolagocapodimonte.it

    La Porta Parallela Scrivere software per usare la porta parallela i registri di base Windows NT 2000 XP Linux il data register lo status register il control register Arduino Uno Duemilanove

    La Porta Parallela - Scrivere Software per Usare la Porta Parallela - Parte 3°




    Scrivere Software per Usare la Porta Parallela


    I PC hanno normalmente una sola porta parallela, indicata come LPT1 oppure PRN. Il BIOS permette però di estenderne il numero fino a tre (su alcune macchine, soprattutto vecchie, fino a quattro). Le porte aggiuntive sono indicate come LPT2 e LPT3. Se è indispensabile e a condizione di trovare schede adatte, il numero potrebbe essere ulteriormente elevato.

    Ciascuna porta parallela è vista dal processore come una serie di indirizzi nello spazio di I/O (Input/Output), occupando un minimo di quattro indirizzi consecutivi; il primo di questi indirizzi è chiamato indirizzo di base.

    In genere LPT1 ha indirizzo di base 0x378 (usando la sintassi C, con 0x378 si intende il numero esadecimale 378, indicato con 378h oppure $378 se si usano altri linguaggi; in decimale l'indirizzo corrispondente è 888), LPT2 ha indirizzo 0x278 ed LPT3 0x3BC.

    Gli indirizzi di tali porte non sono però rigorosamente fissi ma dipendono dall'hardware installato e da come il BIOS (o il sistema operativo) effettua la ricerca, causando spesso problemi nell'interpretazione corretta delle informazioni. Per esempio la porta con indirizzo 0x3BC è indicata sulle vecchie macchine come LPT1.

    Due sono i modi per conoscere gli indirizzi effettivi:

    • Durante la fase di boot del PC occorre avere l'occhio sveglio e leggere sulla prima schermata gli indirizzi delle porte parallele che il BIOS individua, almeno nei PC che mostrano questa informazione.
    • Nei sistemi che utilizzano il BIOS del PC (per esempio i vari DOS e Windows), leggere il contenuto delle locazioni di memoria 0x408 e seguenti ed interpretare secondo la tabella seguente (gli indirizzi di memoria sono in esadecimale, nella sintassi segmento:offset):
    Indirizzo di memoria Contenuto (2 byte)
    0000:0408 Indirizzo di base di LPT1
    0000:040A Indirizzo di base di LPT2
    0000:040C Indirizzo di base di LPT3
    0000:040E Indirizzo di base di LPT4

    Su macchine moderne, in linea di massima successive agli IBM PS2 prima serie, l' indirizzo corrispondente alla LPT4 è usato per altri scopi e quindi ne sconsiglio l'uso e raccomando una particolare attenzione nell'interpretazione dei dati letti.

    Di seguito un frammento di codice che legge l'indirizzo di LPT1 in Turbo C in ambiente DOS.

    unsigned int far *pAddr; // Puntatore ai vettori di indirizzo
    unsigned int IOaddr;     // Indirizzo di LPT1
    {
    pAddr = (unsigned int far *) 0x408;
    IOaddr = *pAddr;
    printf("LPT1 base address is 0x%X\n", IOaddr);
    }

    In TurboPascal l'indirizzo di LPT1 si ricava con il seguente codice.

    Var addr:word; {Indirizzo di LPT1}
    Begin
    addr := MemW[$0000:$0408];
    writeln("LPT1 base address is", addr);
    End

    Per le altre porte il codice è lo stesso, cambiando ovviamente l'area di memoria da cui leggere.

    Si noti che spesso le porte parallele su bus PCI hanno indirizzi maggiori di 0x400 e non sono riconosciute dal BIOS: per conoscerne l'indirizzo è utile verificare le informazioni fornite dal proprio sistema operativo (se ovviamente riconosce la porta)

    Il sistema operativo Linux (c) utilizza convenzioni diverse, assegnando il nome di lpt1, lpt2 ed lpt3 rispettivamente alle porte che individua agli indirizzi 0x378, 0x278 e 0x3BC.

    I registri di base

    Ciascuna porta parallela configurata come SPP è controllabile attraverso tre registri consecutivi nello spazio di I/O del processore, chiamati data register, status register e control register, ciascuno di 8 bit.

    Per scrivere un byte in questi registri è necessario eseguire una istruzione di output verso l'indirizzo interessato. Per leggere un byte è necessario effettuare una istruzione di input. Non è possibile leggere o scrivere un solo bit alla volta, indipendentemente dal linguaggio adottato; inoltre non è in genere possibile utilizzare le istruzioni corrispondenti alle SET dell'assembler. Infine non è possibile leggere un dato da un registro di sola scrittura o scrivere in una porta a sola lettura (l'operazione non genera nessun errore ma il dato letto o scritto è inconsistente).

    Una precisazione: quanto detto vale solo in ambiente MS-DOS o derivati.

    Usando il BorlandC o il TurboC la sintassi è la seguente (tale sintassi verrà adottata nel seguito del tutorial):

    unsigned char dato;
    unsigned int addr;
    outportb (addr, dato);
    dato = inportb (addr);

    dove dato è il byte da leggere o da scrivere (una variabile di otto bit, senza segno: normalmente di tipo unsigned char) e addr è l'indirizzo del registro (una variabile o una costante a 16 bit, senza segno).

    Altri compilatori C usano una sintassi simile ma non è prevista nell'ANSI C la scrittura diretta su dispositivi hardware: occorre quindi verificare sul manuale del proprio compilatore.

    Usando la sintassi TurboPascal le stesse istruzioni diventano:

    Port[addr] := dato;
    dato := Port[addr];

    Dove Port è una matrice predefinita di 65k elementi il cui l'indice corrisponde all'indirizzo della porta da utilizzare.

    Usando la sintassi GW-Basic o altri basic per DOS:

    OUT addr,dato
    dato = INP (addr)

    Usando la sintassi assembler:

    MOV DX,ADDR
    MOV AL,DATO
    OUT DX,AL

    MOV DX,ADDR
    IN AL,DX
    MOV DATO,AL

    Usando sistemi operativi diversi dal DOS le cose cambiano in quanto ai programmi utente è impedito l'accesso diretto alle risorse hardware. La descrizione approfondita va oltre lo scopo di questo tutorial e mi limiterò quindi ad un breve accenno.

    Windows NT / 2000 / XP

    Questo sistema operativo implementa due modalità operative: il ring0 ed il ring3. Le istruzioni di I/O sono possibili senza limitazioni solo nel ring 0, quello del kernel del sistema operativo. Nel ring3 (il livello delle normali applicazioni utente) sono in teoria possibili istruzioni di ingresso e uscita solo su particolari indirizzi attraverso la manipolazione della cosiddetta I/O permission table, modificabile solo all'interno del ring0.

    Eseguendo una applicazione DOS viene automaticamente generata da WindowsNT una macchina virtuale (la NTVDM: NT virtual dos machine) che emula le periferiche standard ed in particolare la parallela permettendo in qualche caso il corretto funzionamento dei programmi anche se con notevolissimi rallentamenti.

    Nei compilatori nativi in ambiente Windows normalmente le istruzioni di IN e OUT non sono neppure implementate: ciò è vero in particolare per VisualBasic, BorlandC e Delphi.

    La soluzione a questi problemi è quella di scrivere un device driver che, essendo eseguito nel ring0, permette la gestione diretta dell'hardware; purtroppo l'uso della DDK (driver development kit) di Microsoft è tutt'altro che semplice... Forse potrebbe esservi utile WinDriver disponibile in versione demo su http://www.jungo.com

    In alternativa  è possibile usare driver generici che mettono a disposizione apposite funzioni attraverso DLL o VBX. Spesso questi prodotti sono disponibili gratuitamente su internet: posso citare DriverLINX, per Win9x e WinNT e PortTalk. Alcune funzioni sono disponibile all'interno del tool VVIO, del quale rendo disponibile sia il codice sorgente sia l'eseguibile.

    Analoghe considerazioni valgono anche in Win95/98/Me, anche se questi OS sono molto più laschi nella protezione dell'hardware.

    Linux

    Anche in ambiente Linux l'I/O diretto a livello di applicazione utente non è possibile. Chi volesse fare esperimenti con questo sistema operativo deve utilizzare la funzione C

    ioperm(from, num, on_or_off)

    Essa permette di rendere accessibile in lettura/scrittura una serie di registri ad una applicazione qualunque. Per usare un programma che invoca questa funzione occorre però essere l'utente root.

    In alternativa occorre scrivere applicazioni in kernel mode; per questo potrebbe essere utile per esempio il pacchetto GPL short.

    Il data register

    Il data register è un registro di sola scrittura direttamente connesso agli otto pin di dato presenti sul connettore esterno della parallela. L'indirizzo è quello di base della porta.

    Per impostare un pin alto o basso basta scrivere nel bit corrispondente di questo registro rispettivamente 1 oppure 0. Il bit meno significativo del registro corrisponde al pin Data0.

    Se per esempio si vogliono impostare i pin in modo tale che i pin 2 (bit 0) e 7 (bit 5) siano alti ed i pin 3, 4, 5, 6, 8 e 9 bassi, occorre eseguire la seguente riga di codice (la costante DATA indica l'indirizzo del registro dei dati della porta interessata, per esempio 0x278):

    #define DATA 0x278
    outportb (DATA, 0x21); // 0x21 = 00100001 in binario

    Occorre infine notare che una volta scritto il byte nel registro, i pin di uscita non cambiano più il loro valore fino alla scrittura successiva in quanto il circuito di uscita della porta è formato da otto flip-flop.

    Lo status register

    Questo registro è di sola lettura ed ha indirizzo BASE + 1.

    Quando il processore effettua la lettura di questo registro vengono riportati i valori dei cinque pin in ingresso, secondo la tabella di seguito riportata.

    Indirizzo Bit Nome Pin (DB25) Invertito
    BASE +1 Status 7 Busy 11 Si
    Status 6 Ack 10 No
    Status 5 Paper Out 12 No
    Status 4 Select In 13 No
    Status 3 Error 15 No
    Status 2 IRQ -
    Status 1 Riservato -
    Status 0 Riservato -

    L'ultima colonna indica se è presente una porta not all'ingresso della porta parallela: nel caso ci sia un SI, vuol dire che un livello logico alto viene letto come 0 logico. Se invece si trova scritto NO, un livello logico alto viene letto come 1 logico.

    Se voglio conoscere il valore dei pin 11 e 12 devo eseguire il seguente codice

    #define STATUS (DATA+1)
    unsigned char data, busy, po // Tre variabili ad 8 bit, senza segno
    data = inportb (STATUS); // Leggo gli 8 bit di stato
    data = data ^ 0x80; // Inverto il bit più significativo con uno xor
    busy = (data & 0x80) >> 7; // Estraggo il valore di busy (bit 7)
    po = (data & 0x20) >> 5; // Estraggo il valore di Paper O
    u
    t (bit 5)

    Il bit IRQ viene resettato dall'hardware in caso di interrupt attivato dalla linea Ack. Vale invece 1 qualora non si sia attivata nessuna interrupt.

    Il control register

    Questo registro è primariamente un registro di scrittura anche se, in alcuni casi, possibile l'utilizzo in lettura. L'indirizzo è BASE + 2.

    Il processore può scrivere in questo registro per impostare il valore di quattro pin di uscita, secondo la seguente tabella. Altri due bit sono per usi interni.

    Indirizzo

    Bit

    Nome

    Pin (DB25)

    Invertito

    BASE + 2

    Control  7

    Riservato

    -

    -


    Control 6

    Riservato

    -

    -


    Control 5

    Bidirezionale

    -

    -


    Control 4

    Abilitazione interrupt

    -

    -


    Control 3

    Select-in

    17

    SI


    Control 2

    Inizialize

    16

    NO


    Control 1

    Linefeed

    14

    SI


    Control 0

    Strobe

    1

    SI

    Nel caso dei tre bit invertiti, la scrittura di un 1 causa sull'uscita una tensione di 0V.

    Per esempio per portare tutti i quattro pin della LPT2 ad un livello logico alto devo eseguire la seguente istruzione

    #define CONTROL (DATA+2)
    outportb (CONTROL, 0x04);
    // 0x04 = 00000100, cioè ricordando che 3 bit sono invertiti, 00001111

    Ho accennato al fatto che i quattro segnali connessi al registro di controllo possono essere in alcuni casi utilizzati anche per l'input.

    Per fare ciò è necessario porre prima tutti i bit di uscita a livello logico alto e quindi leggere il byte:

    outportb (CONTROL, 0x04); // tutti i pin alti
    data = inportb (CONTROL) & 0x0F;

    Questa possibilità è subordinata al fatto che queste uscite siano a collettore aperto (open collector), possibilità prevista nell'originaria porta del PC XT ma non in tutte le porte parallele moderne, soprattutto se configurate come EPP o ECP. Per verificare se la porta funziona secondo questa modalità provate a collegare una resistenza da 1 kohm tra uno dei pin associati al registro di controllo e massa: se viene letto uno 0 le uscite sono open-collector e quindi possono essere usate come ingressi.

    Personalmente sconsiglio l'uso di tale modalità anche se funzionante in quanto potrebbe portare alla distruzione della porta nel caso di porte senza uscita a collettore aperto, cioè praticamente per tutte le porte dei PC moderni correttamente configurati.

    Il bit 5 del registro di controllo permette di attivare la modalità bidirezionale delle porte che ne sono provviste: ponendo a 1 questo bit è possibile leggere i dati presenti sugli otto pin 2...9, attraverso la lettura del registro dei dati, come di seguito mostrato

    outportb (CONTROL, 0x20); // 0x20 = 0010 000 setto in ingresso
    data = inportb (DATA);

    Se il bit vale 0, è possibile scrivere nel registro dati, come precedentemente descritto.

    Non tutte le porte hanno questa possibilità ed alcune funzionano con modalità diverse. Dalla mia esperienza posso dire che le tutte le porte configurate come EPP hanno questo funzionamento. Non l'hanno invece le porte del PC XT originale ed in genere le vecchie macchine. Per effettuare il test che verifica se la propria porta parallela è bidirezionale o meno è possibile usare la seguente procedura:

    • Collegare una resistenza da 1k tra il pin D0 e la massa.
    • Scrivere un byte qualunque nel registro dati, possibilmente mischiando alcuni 0 ed alcuni 1 (personalmente uso il byte 0xAA oppure 0x55, formati entrambi dall'alternarsi di 0 e 1).
    • Configurare la porta in ingresso settando il bit bidirezionale del registro di controllo.
    • Leggere il contenuto del registro data.

    Se viene letto il numero binario 11111110 (0xFE) la porta supporta la modalità bidirezionale. Se viene letto il dato preventivamente scritto, la porta non supporta la modalità bidirezionale, perlomeno secondo lo schema che ho descritto: molte schede parallele hanno infatti capacità bidirezionali di tipo proprietario e quindi incompatibili con la procedura descritta.

    Il bit 4 permette di abilitare le interrupt alla ricezione di un fronte sul pin Ack. Se l'interrupt è abilitato e la porta stampante connessa ad una linea del controllore di interrupt (p.e. attraverso l'apposito ponticello presente su molte schede,), una transizione sul pin Ack causa un'interrupt. Il fronte attivo può cambiare da scheda a scheda. L'uso di tale possibilità richiede la conoscenza delle problematiche correlate alla gestione delle interruzioni, argomento che va oltre lo scopo di questo tutorial.


    La Porta Parallela - Introduzione - Parte 1°

    La Porta Parallela - Come Individuare la Porta Parallela - Parte 2°

    La Porta Parallela - Scrivere Software per Usare la Porta Parallela - Parte 3°

    La Porta Parallela - Circuiti Applicativi - Parte 4°

    Aggiungi commento


    Codice di sicurezza
    Aggiorna