AngoLinux

Visualizzazione di configurazioni rotative sui LED connessi alla parallela, con reazione ai tasti

- A cura del Prof. Stefano Salvi -


Introduciamo la gestione vera dei pulsanti, con il debounche.

Il testo dell'esercizio è il seguente:

Scrivere un programma Assembler per 8086 che visalizzi su 8 LED connessi ai dati della porta parallela (indirizzo 0x378) una configurazione con due LED accesi che scorrono in direzioni opposte.
Premendo un tasto connesso ad un ingresso della porta parallela (porta 0x379, bit da 3 a 7) la configurazione avanzerà di un passo, premendone un'altro arretrerà di un passo.
Il ciclo verrà terminato dalla pressione di un terzo tasto connesso ad un ingresso della porta parallela (porta 0x379, bit da 3 a 7).

NOTE:

  • La codifica delle istruzioni in formato AT&T, per l'assemblatore di Linux sono disponibili nel Reference delle istruzioni 80386 in formato AT&T (in inglese)
  • Chiamate di sistema al Kernel di Linux descrive come chiamare il sistema per ottenere servizi e quali sono i servizi che possono essere ottenuti.
  • L'esecuzione di un programma in Linux avviene a partire dalla label _start. Questo simbolo devenir dichiarato .global, in modo che possa venir utilizzato dal sistema.
  • Per garantire uno stato stabile per i pulsanti, essi vengono alimentati da uno dei bit di controllo della porta parallela (porta 0x37a). Per un corretto funzionamento dei tasti occorre che le uscite di controllo della porta siano tutte ad 1. I bit che non sono connessi alle uscite vanno a 0, perchè impostano il modo di funzionamento. Per ottenere questo occorre scrivere 0x04 sulla porta 0x37a prima dell'uso dei tasti.
  • I tasti sono connessi tra i piedino di ingresso della porta 0x379 e la massa, quindi un tasto rilasciato darà un valore 1 sul relativo ingresso ed un tasto premuto un valore 0.
    Questo è valido per tutti i tasti, salvo quello connesso al bit 7 , che ha un livello invertito (0 per rialsciato, 1 per premuto).
  • Quando un tasto viene premuto o rilasciato, i contatti rimbalzano, quindi, dopo la chiusura iniziale del tasto, per un certo periodo il tasto si riapre e si richiude, per poi restare definitivamente chiuso.
    Altrettanto, quando lo si rilascia, si apre, poi si richiude alcune volte, fino a restare definitivamente aperto.
    Per evitare di rispondere più di una volta ad una singola pressione di un tasto, occorre provvedere o un circuito elettronico di antirimbalzo (debounce), oppure una routine che esegua la stessa funzione. Nel nostro caso il circuito manca, quindi dovremo provvedere con una routine.
    Potremo, per esempio, dopo aver rilevato la prima pressione, attendere un certo tempo (un decimo di secondo, ad esempio) e controllare se il tasto è ancora premuto. Se non lo è più, abbiamo rilevato un rimbalzo, quindi facciamo finta che nulla sia successo. Se invece, dopo il ritardo il tasto è ancora premuto, eseguiremo l'azione.
  • Dato che vogliamo che ad ogni pressione venga eseguita una sola azione, dovremo preoccuparci di attendere che il tasto premuto venga rilasciato, altrimenti, con il tasto premuto avremmo un avanzamento/arretramento continuo.
    Non ci dovremo preoccupare dei rimbalzi al rilascio, perchè saranno gesiti dalla routine di antirimbalzo alla pressione.
  • Per evitare di intasare il PC, non è opportuno controllare con continuità lo stato dei tasti, in attesa della prima pressione. Sarà opportuno invece introdurre un ritardo tra un controllo e l'altro, per non consumare troppo tempo di CPU durante l'attesa.
  • Le intruzioni inb ed outb sono istruzioni privilegiate, che non possono essere eseguite da un programma, se prima il sistema non ha riservato le porte su cui operare al programma stesso.
    Per riservare delle porte al programma occorre utilizzare la chiamata di sistema ioperm (costante __NR_ioperm, codice 101).
    Questa chiamata può essere eseguita solo dall'utente root. Se viene eseguita da un'altro utente, il programma genererà un'errore di vario tipo, come ad esempio Segmentation fault
  • Per introdurre un ritardo nell'esecuzione è possibile eseguire un ciclo di operazioni inutili per perdere tempo. Questa soluzione carica inutilmente il sistema e produce ritardi non precisi.
    Una migliore soluzione è quella di invocare la funzione di sistema nanosleep (costante __NR_nanosleep, codice 162).
    Questa funzione richiede due parametri. Il primo, che và messo in %ebx, è l'indirizzo della prima di due variabili, contenente il numero di secondi di attesa, la seguente contenete il numero di nanosecondi.
    Il secondo parametro, da porre in %ecx, è l'indirizzo di una simile coppia di variabili, che potrebbe contenere il ritardo rimanente, nel caso l'attesa venisse interrotta. Dato che questo dato non ci interessa, potremo mettere 0 in %ecx.
  • Per terminare il programma occorre fare una chiamata alla funzione di sistema _exit (costante __NR_exit, codice 1).
    Questa funzione richiede il codice di uscita del programma, che và messo in %ebx.

Una possibile soluzione è la seguente:
# lp2.s
# Stefano Salvi - 8/9/02
# Programma che visualizza delle configurazioni sui LED connessi alla porta parallela
# Le configurazioni avanzano premendo un tasto, arretrano premendone un'altro.
# Termina quando viene premuto un tasto a scelta, connesso agli ingressi della parallela.
#.text parte codice
#.data dati inizializzati
#.bss dati non inizializzati

# Costanti
__NR_ioperm	=	101
__NR_nanosleep	=	162
__NR_exit	=	1


KERNEL	=	0x80

PARDOUT	=	0x378
PARDIN	=	0x379
PARCTL	=	0x37a
STOPK	=	0x80
AVANTIK	=	0x40
INDIETROK =	0x20
ENTRAMBIK =	AVANTIK | INDIETROK
NUMPORT	=	4
NUMCONF	=	6

.data
# Dati da visualizzare
tab:
    .byte       0x81
    .byte       0x42
    .byte       0x24
    .byte       0x18
    .byte       0x24
    .byte       0x42
#struttura in input per nanosleep
deli:
    .long       0               #secondi
    .long       50000000        #nanosecondi 50 000 000 = 0,05 S

i:    .long     0		# Configurazione corrente

.text
.globl _start

################################################################################
#
# Routine di ritardo
# Chiama 'nanosleep'
# INGRESSO:
#	nessuno
# USCITA:
#	nessuno
# USA:
#	tutti i registri (chiamata di sistema)
#
################################################################################
delay:
        movl    $__NR_nanosleep,%eax # nanosleep
        movl    $deli,%ebx      # Ritardo richiesto (input)
        movl    $0,%ecx         # Ritardo rimanente (output) non mi serve
        int     $KERNEL         # chiamo il kernel

	ret			# Termina dopo il ritardo

################################################################################
#
# Visaulizza la configurazione il cui indice e' in %bx
# inviandola alla porta 0x278
# INGRESSO:
#	nessuno
# USCITA:
#	nessuno
# USA:
#	%bx, %dx, %al
#
################################################################################
show:
        movl    i,%ebx          # Recupero la variabile 'i'

        movb    tab(%ebx,1),%al # metto in al tab[i]
        movw    $PARDOUT,%dx    # dx punta alla porta dati
        outb    %al,%dx         # mando fuori la configurazione

	ret			# termina la procedura


################################################################################
#
# Programma principale
#
################################################################################
_start:
        movl    $__NR_ioperm,%eax # funzione
        movl    $PARDOUT,%ebx   # port iniziale
        movl    $NUMPORT,%ecx   # numero port
        movl    $0x1,%edx       # azione -> 'riserva'
        int     $KERNEL         # Chiamo il kernel

        nop
        nop
        movb    $0x04,%al       # metto il dato per accendere tutti i LED
        movw    $PARCTL,%dx     # dx punta alla porta dati
        outb    %al,%dx         # mando fuori il dato per alimentare i tasti

	call	show		# visualizza la prima configurazione
        
lp:
#	Per prima cosa, fa' un ritardo
	call	delay
	
#	Legge i tasti
        movw    $PARDIN,%dx     # dx punta alla porta di ingresso
	inb     %dx,%al		# leggo la tastiera

	testb	$STOPK,%al	# Controllo il bit 7 (uno dei tasti)
	jnz	fine		# Se lo trovo premuto (e' invertito, quindi premuto ->1)
				# termino

	testb	$AVANTIK,%al	# Controlla un'altro tasto
	jz	avanza		# va' all'avanzamento della configurazione

	testb	$INDIETROK,%al	# Controlla un'altro tasto ancora
	jz	arretra		# va' all'arretramento

	jmp	lp		# ciclo di attesa tasti

# Gestione dell'avanzamento
avanza:
	call	delay		# attende un ritardo
        movw    $PARDIN,%dx     # dx punta alla porta di ingresso
	inb	%dx,%al		# rilegge tastiera
	testb	$AVANTIK,%al	# controlla il tasto dell'avanzamento
	jnz	lp		# il tasto non e' piu' premuto, quindi niente

        
    
        incl    i               # incremento indice configurazione
        cmpl    $NUMCONF,i      # e' arrivara a 8?
        jne     movok           # se no, prosegue
        movl    $0,i            # se 8 -> riporto i a 0

movok:
	call	show		# visualizza la nuova configurazione

movlp:
	#adesso deve attendere il rilascio del tasto (uno dei due
	call	delay		# ritardo, per non sovraccaricare
        movw    $PARDIN,%dx     # dx punta alla porta di ingresso
	inb	%dx,%al		# rilegge tastiera
	andb	$ENTRAMBIK,%al	# controlla entrambi i tasti
	cmpb	$ENTRAMBIK,%al	# devono essere entrambi rilasciati (ad 1)
	jnz	movlp		# uno e' ancora premuto, continua ad attendere
        
        jmp     lp              # torna al principale

# Gestione dell'arretramento
arretra:
	call	delay		# attende un ritardo
        movw    $PARDIN,%dx     # dx punta alla porta di ingresso
	inb	%dx,%al		# rilegge tastiera
	testb	$INDIETROK,%al	# controlla il tasto dell'arretramento
	jnz	lp		# il tasto non e' piu' premuto, quindi niente
    
        cmpl    $0,i      	# e' arrivara a 0?
        je      arrciclo        # se si, va' a rimettere a 7
        decl    i               # decrementa indice configurazione
	jmp	movok		# Continua normalmente

arrciclo:
        movl    $NUMCONF-1,i    # se 0 -> riporto i a 7
	jmp	movok		# Continua normalmente

fine:
        movl    $0,%ebx         # first argument: exit code
        movl    $__NR_exit,%eax # system call number (sys_exit)
        int     $KERNEL         # call kernel

Il sorgente del programma è lp2.s.

Per provare il programma, lo si deve assemblare con il comando as -o lp2.o lp2.s, linkare con ld -o lp2 lp2.o e quindi mandato in esecuzione con il comando ./lp2.
Dato che il programma, per utilizzare le porte di I/O, deve invocare la funzione priviligiata IOPERM, deve essere eseguito dal superutente root. Per fare questo, o si lancia il programma da una console nella quale si è fatto login come root, oppure in una finestra terminale aperta normalmente come utente si esegue il comando su root, per eseguire il terminale come root. Verrà richiesta la password di root.


[Home Page dell'ITIS "Fermi"] [Indice Quarta] [Precedente] [Successivo]

© Ing. Stefano Salvi - Released under GPL licence