AngoLinux

Chiamata di funzioni assembler dal C

- A cura del Prof. Stefano Salvi -


Introduciamo la gestione vera dei pulsanti, con il debounche.

Il testo dell'esercizio è il seguente:

Scrivere un programma in C con funzioni 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).
Le funzioni assembler dovranno avere i seguenti prototipi ed uso:
void lpshow (char conf);
Visaulizza la configurazione passata come parametro inviandola alla porta 0x278
void lpinit (void);
Inizializza la parallela: esegue la 'ioparm' e setta la porta di controllo
char lpget (void);
Legge un dato dalla parallela: Lo stato dei tasti connessi viene ritornato nei bit da 0 a 4, 1 se il tasto e' premuto, 0 se e' rilasciato (per ogni tasto).

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
  • Il valore di ritorno di una funzione và lasciato nel registro %eax.
  • Per accedere ai parametri occorre usare un indirizzamento indicizzato nello stack. Per questo si copia il valore di %esp in %ebp e, tramite questo, si accede ai parametri. Naturalmente, visto che anche la funzione chiamante usa %ebp alla stessa maniera, occorrerà prima salvare nello stack il vecchio valore di %ebp e ripristinarlo prima di terminare.
    Prima dei parametri troviamo quindi il valore di ritorno ed il %ebp appena salvato, quindi il primo parametro sarà all'offset 8.
  • Il programma chiamante si aspetta di ritrovare intatti i registri %ebp, %esi, %edi, %ebx, quindi se questi vengono modificati vanno prima salvati e poi ripristinati.

Una possibile soluzione è la seguente:
Modulo delle funzioni assembler
# lps.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.
# Parte delle funzioni scritte in assembler
#.text parte codice
#.data dati inizializzati
#.bss dati non inizializzati

# Costanti
__NR_ioperm	=	101

KERNEL	=	0x80

PARDOUT	=	0x378
PARDIN	=	0x379
PARCTL	=	0x37a
NUMPORT	=	4
NUMCONF	=	8

.text
.globl lpshow, lpget, lpinit

################################################################################
#
# Visaulizza la configurazione passata come parametro
# inviandola alla porta 0x278
#
# prototipo:
#	void lpshow (char conf);
#
################################################################################
lpshow:
	pushl	%ebp		# Salva il puntatore al frame
	movl	%esp,%ebp	# punta al nuovo frame

        movb    8(%ebp,1),%al   # metto in al il primo parametro
        movw    $PARDOUT,%dx    # dx punta alla porta dati
        outb    %al,%dx         # mando fuori la configurazione

	popl	%ebp		# ripristina il frame pointer

	ret			# termina la procedura


################################################################################
#
# Inizializza la parallela:
# esegue la 'ioparm' e setta la porta di controllo
#
# prototipo:
#	void lpinit (void);
#
################################################################################
lpinit:
	pushal			# salva tutti i registri nello stack

        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    $0x4,%al        # metto tutti 1 in a
        movw    $PARCTL,%dx     # dx punta alla porta dati
        outb    %al,%dx         # mando fuori il dato per alimentare i tasti

	popal			# ripristina tutti i registri
	ret			# termina la procedura
        
################################################################################
#
# Legge un dato dalla parallela:
# Lo stato dei tasti connessi viene ritornato nei bit da 0 a 4,
# 1 se il tasto e' premuto, 0 se e' rilasciato (per ogni tasto).
#
# prototipo:
#	char lpget (void);
#
################################################################################
lpget:
#	Legge i tasti
        movw    $PARDIN,%dx     # dx punta alla porta di ingresso
	inb     %dx,%al		# leggo la tastiera
	shrl	$3,%eax		# sposta i bit letti alla posizione piu' bassa
	andl	$0x1f,%eax	# elimina i bit inutili
	xorl	$0xf,%eax	# inverte i tasti non invertiti (tutti 1 se premuti)

	# Il dato di ritorno e' gia' in %eax, quinid non devo fare niente.
	ret			# termina la procedura
Modulo principale in C
/* lpc.c
 * 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.
 * Parte principale scritta in C
 */

#include <time.h>

#define	NUMCONF		6
#define	STOPK		0x10
#define	AVANTIK		0x08
#define	INDIETROK	0x04

// Funzioni contenute nel modulo assembler lps.s
/*
 * Visaulizza la configurazione passata come parametro
 * inviandola alla porta 0x278
 */
void lpshow (char conf);
/*
 * Inizializza la parallela:
 * esegue la 'ioparm' e setta la porta di controllo
 */
void lpinit (void);
/*
 * Legge un dato dalla parallela:
 * Lo stato dei tasti connessi viene ritornato nei bit da 0 a 4,
 * 1 se il tasto e' premuto, 0 se e' rilasciato (per ogni tasto).
 */
char lpget (void);

// Funzione scritta qui
/* Attende che tutti i tasti siano rilasciati
 */
void waitreleased (void);


char tabella [NUMCONF] = {
    0x81,
    0x42,
    0x24,
    0x18,
    0x24,
    0x42,
};

void delay ()
{
struct timespec req = { 0, 50000000 };	// Costante a 50 ms.
  nanosleep ( &req, 0);	// Chiama la nanosleep senza struttura per il restante.
}

int main ()
{
int i = 0;
char keys;
  lpinit ();			// Inizializza ioparm ed i tasti
  lpshow (tabella [i]);		// Visualizza il primo dato

  while (1)			// Loop infinito (verra' interroto dal tasto)
  {
    delay ();
    keys = lpget ();
    if (keys & STOPK)		// Tasto piu' a sinistra premuto (ex bit 7)
    {
      return 0;			// Termina programma
    }
    else if (keys & AVANTIK)	// Tasto per avanza
    {
      delay ();			// Attende un poco
      if (lpget () & AVANTIK)	// Se e' ancora premuto, non e' un rimbalzo
      {
        if (++i >= NUMCONF)	// Incrementa la configurazione e se e' fuori
        {
          i = 0;		// la rimette a 0
        }
        lpshow (tabella [i]);	// Visualizza il primo dato
        waitreleased ();	// attende che il tasto (ogni tasto) sia rilasciato
      }
    }
    else if (keys & INDIETROK)	// Tasto per arretra
    {
      delay ();			// Attende un poco
      if (lpget () & INDIETROK)	 // Se e' ancora premuto, non e' un rimbalzo
      {
        if (i)			// se la configurazione non e' la prima
        {
          i--;			// passa alla precedente
        } else {
          i = NUMCONF - 1;	// ricomincia dall'ultima
        }
        lpshow (tabella [i]);	// Visualizza il primo dato
        waitreleased ();	// attende che il tasto (ogni tasto) sia rilasciato
      }
    }
  }
}

/* Attende che tutti i tasti siano rilasciati
 */
void waitreleased (void)
{
  while (lpget ())
  {
    delay ();	// Ritardo per non sovraccaricare la CPU
  }
}
Makefile per compilare e collegare il programma
# Makefile
# Stefano Salvi 8/9/02

all:	lpc

lpc:	lpc.o lps.o
	cc -o lpc lpc.o lps.o

lpc.o:	lpc.c
	cc -c -o lpc.o lpc.c

lps.o:	lps.s
	cc -c -o lps.o lps.s


I sorgenti del programma sono lps.s, lpc.c e Makefile.

Per provare il programma, lo si deve compilare, usando il comando make, che assemblerà, compilerà e linkerà i vari muduli a seconda delle necessità in base a quanto indicato dal Makefile, e quindi mandare in esecuzione con il comando ./lpc.
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