|
Cominciamo a fare programmi che utilizzano la comunicazione seriale.
Il testo dell'esercizio è il seguente:
Scrivere un programma in C che implementio una semplice comunicazione seriale. Il
programma dividerà lo schermo in tre finestre:
- Una finestra di circa mezzo schermo in alto nella quale verranno visualizzati i
caratteri ricevuti dall'interfaccia seriale
- Una finestra un poco più piccola che visualizzerà i caratteri digitati da
tastiera, che verranno trasmessi tramite la stessa interfaccia
- Una finestra di una sola riga in fondo allo schermo, che viualizzerà data ed
ora, aggiornati almeno una volta al secondo.
Il programma imposterà la prima interfaccia seriale (/dev/tyS1) in
modo noncanonical a 9600 Baud.
Per terminare il programma si dovrà premere il tasto Esc.
Non si useranno né processi né thread.
NOTE:
- Per poter utilizzare un'interfaccia seriale come utente normale occorre che
l'utente in questione sia associato al gruppo dialout. Per associare l'utente
pippo al gruppo dialout occorre digitare il seguente comando, con
privilegio di root (notare il prompt #):
# gpasswd -a pippo dialout
- Per la visualizzazione si useranno le funzioni di ncurses, come
spiegate nel relativo tutorial.
- È opportuno scrivere una funzione che crei una finestra con cornice.
Questa funzione creerà prima una finestra leggermente più grande, alla quale
disegnerà la cornice con la funzione box, quindi
distruggerà questa finestra e ne creerà una della dimensione scelta,
nella sola area utile della cornice. La funzione ritornerà il puntatore alla finestra
utile creata.
- Le interfacce seriali corrispondono ai dispositivi /dev/ttyS0
(COM1 sotto DOS) e /dev/ttys1 (COM2
sotto DOS). Possono venire aperti come normali file, ma il sistema operativo
esegue una serie di filtraggi sui dati inviati e ricevuti (modo canoncal).
Occorre quindi indicare al sistema di sospendere questi filtraggi (ponendo l'interfaccia
nel cosidetto modo noncanonical). Occorre inoltre impostare correttamente le
caratteristiche della comuinicazione. Riporto, dalla documentazione info su libc ("Low-Level Terminal Interface"->"Noncanonical Mode Example"):
Noncanonical Mode Example
Here is an example program that shows how you can set up a terminal
device to read single characters in noncanonical input mode, without
echo.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
/* Use this variable to remember original terminal attributes. */
struct termios saved_attributes;
void
reset_input_mode (void)
{
tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}
void
set_input_mode (void)
{
struct termios tattr;
char *name;
/* Make sure stdin is a terminal. */
if (!isatty (STDIN_FILENO))
{
fprintf (stderr, "Not a terminal.\n");
exit (EXIT_FAILURE);
}
/* Save the terminal attributes so we can restore them later. */
tcgetattr (STDIN_FILENO, &saved_attributes);
atexit (reset_input_mode);
/* Set the funny terminal modes. */
tcgetattr (STDIN_FILENO, &tattr);
tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tattr.c_cc[VMIN] = 1;
/* Set the funny terminal modes. */
tcgetattr (STDIN_FILENO, &tattr);
tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tattr.c_cc[VMIN] = 1;
tattr.c_cc[VTIME] = 0;
tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}
int
main (void)
{
char c;
set_input_mode ();
while (1)
{
read (STDIN_FILENO, &c, 1);
if (c == '\004') /* `C-d' */
break;
else
putchar (c);
}
return EXIT_SUCCESS;
} |
This program is careful to restore the original terminal modes before
exiting or terminating with a signal. It uses the `atexit' function
(*note Cleanups on Exit) to make sure this is done by `exit'.
The shell is supposed to take care of resetting the terminal modes
when a process is stopped or continued; see *Note Job Control. But
some existing shells do not actually do this, so you may wish to
establish handlers for job control signals that reset terminal modes.
The above example does so.
|
- È opportuno porre l'inizializzazione della seriale in una funzione
indipendente, alla quale passeremo il nome del dispositivo e ci ritornerà
il descrittore del file aperto.
- Il nostro programma dovrà verificare la presenza di caratteri su due
dispositivi, rappresentati dai realtivi file:
- La tastiera (file stdin, descrittore fileno(stdin))
- La seriale (il descrittore ritornato dalla nostra funzione di inizializzazione)
Per evitare di porre i due file in modalità non bloccante e sovraccaricare il
sistema continuando a verificarli a turno (polling), possiamo utilizzare la
funzione select. Questa funzione richiede tre gruppi di descritori, uno
da controllare per dati in ingresso, uno per spazio nella coda di trasmissione ed
un terzo per errori.
Quando richiamiamo la select, essa attenderà che avvenga un evneto
in uno dei tre gruppi di file, prima di ritornare. Essa consente anche, con un quarto parametro,
di indicare un timeout, trascorso il quale la funzione ritornerà anche se non
sono accaduti eventi. Per una trattazione più competa della funzione
select, consultare la sezione La funzione select
nell'esercizio "L'esperienza di Quinta".
- Per ottenere data ed ora corrente, si userà la funzione
time (NULL);. questa funzione ritorna il numero di secondi trascorsi dal
1^ Gennaio 1970 (data chiamata the Epoch, con un tipo time_t.
- Per convertire in stringa, in formato leggibile, il numero ritornato dalla funzione
time (NULL);, si può passare l'indirizzo di una variabile
contenete il numero di secondi ritornato dalla funzione alla funzione
char *ctime(const time_t *timep); che convertirà il dato, lo
formatterà come data ed ora e lascerà il risultato in un buffer statico
del quale ritornerà l'indirizzo. Potremo passare l'indirizzo ritornato
direttamente alla funzione di stampa.
Una possibile soluzione è la seguente:
/* seriale.c
* Stefano Salvi - 26/10/2002
* Programma che realizza una semplice 'chat' seriale tra due cumputer.
* Utilizzando curses divide il video in due parti.
* Nella parte superiore verranno visualizzati i caratteri ricevuti dalla seriale.
* Nella parte inferiore verranno visualizzati i caratteri premuti sulla tastiera,
* che verranno contemporaneamente spediti.
* Il programma termina quando si preme il tasto di escape
* La seriale verra' programmata a 9600 baud.
*/
#include <stdio.h>
#include <termios.h>
#include <ncurses.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#define ESC 27 /* Carattere per terminare */
int init(char *comname); // Funzione che apre una seriale e la inizializza
// Funzione che crea una finestra e le disegna una cornice intorno
WINDOW *framedWin (int h, int w, int y, int x);
int main()
{
WINDOW *fi; // Finestra di NCurses - dati ricevuti
WINDOW *fo; // Finestra di NCurses - dati trasmessi
WINDOW *ft; // Finestra di NCurses - orologo (ultima riga)
int com; // handle del file della seriale
int uscita;
fd_set set; // set di descrittori per 'select'
struct timeval timeout; // Timeout per 'select'
char input,output; // Caratteri ricevuti e da trasmettere
time_t t; // Data ed ora
com = init("/dev/ttyS0"); // Apre il canale seriale
initscr(); // Inizializza la finestra con curses
start_color(); // Inizializza anche i colori
noecho(); // Imposta la tastiera senza echo
cbreak(); // Restituisce ogni singolo carattere appena premuto
clear(); // Pulisce lo schermo
fi=framedWin(10,78,1,1); // Finestra superiore - caratteri ricevuti
fo=framedWin(9,78,13,1); // Finestra inferiore - caratteri trasmessi
ft=newwin(1,80,23,0); // Ultima riga - Orologio
wclear(ft); // Pulisce lo schermo
do {
FD_ZERO(&set); // Svuota il set
FD_SET(com,&set); // Ci aggiunge la seriale
FD_SET(fileno(stdin),&set); // Ci aggiunge anche la tastiera (standard input)
timeout.tv_sec=1; // Imposta un timeout (1 secondi)
timeout.tv_usec=0; // 0 microsecondi
uscita = select(FD_SETSIZE, &set, NULL, NULL, &timeout);
if(FD_ISSET(com,&set)) // La seriale e' nel set (ha un evento)
{
read(com,&input,1); // Leggo il carattere dalla seriale
waddch(fi,input); // Lo stampo sulla finestra
wrefresh(fi); // Aggiorno la finestra
}
if(FD_ISSET(fileno(stdin),&set)) // Se c'e' un carattere nello standard input
{
output=wgetch(fo); // Lo leggo dalla tastiera (nella finestra)
if (output==ESC) // Se il carattere e' 'Escape'
{
uscita=-1; // Segnalo fine ciclo
} else { // Altrimenti
write(com,&output,1); // Lo spedisco sulla seriale
waddch(fo,output); // Lo aggiungo alla finestra
// wrefresh(fo); // Non rinfresco la finestra - lo faccio dopo aver scritto l'ora
}
}
/* Aggiorna l'orologio */
t = time (NULL);
mvwaddstr (ft,0,0,ctime (&t));// Legge l'ora (in secondi)
wrefresh (ft);
wrefresh(fo); // Riposiziona il cursore nella finestra in input
} while (uscita!=-1); // Finche' il flag non disce di uscire
close(com); // Chiudo il canale seriale
delwin(fi); // Cancello la finestra superiore
delwin(fo); // Cancello la finestra inferiore
endwin (); // Rimette tutto a posto
return 0;
}
/* Funzione che crea una finestra e le disegna una cornice intorno
* La finestra ha altezza 'h', larghezza 'w', e' posizionata ad 'x', 'y'.
* La cornice e' esterna alla finestra (crea una finestra temporanea per disegnarla)
* La cornice e' gialla su sfondo blu.
* La finesra e' blu, con testo bianco.
*/
WINDOW *framedWin (int h, int w, int y, int x)
{
WINDOW *fi; // Rinestra di NCurses
init_pair(1,COLOR_YELLOW,COLOR_BLUE);
init_pair(2,COLOR_WHITE,COLOR_BLUE);
fi=newwin(h+2,w+2,y-1,x-1); // Finestra esterna (per cornice)
wbkgd(fi,COLOR_PAIR(1)); // Cornice gialla su fondo blu
box(fi,ACS_VLINE,ACS_HLINE); // Disegna la cornice intorno alla finestra
wrefresh(fi); // Aggiorna la finestra (disegna la cornice)
delwin(fi); // Cancella la finestra della cornice
fi=newwin(h,w,y,x); // Finestra della dimesione data
wbkgd(fi,COLOR_PAIR(2)); // Testo bianco su sfondo blu
wclear(fi); // Pulisce lo schermo
scrollok(fi,1); // Abilita lo scroll
wrefresh(fi); // Aggiorna la finestra (la cancella)
return fi;
}
int init(char *comname)
{
int com; // Il file per la seriale
struct termios tattr; // Struttura per impostare le caratteristiche della seriale
com = open (comname,O_RDWR | O_SYNC); // Apre il dispositivo (Lettura/scrittura, sincrono)
if (com == -1) // Open ritorna errore
{
perror ("Non posso aprire la seriale"); // Stampa il messaggio di errore
exit (2); // E termina drasticamente con errore
}
/* Impostazione della seriale
* fonte info libc->"Low-Level Terminal Interface"->"Noncanonical Mode Example" */
tcgetattr (com, &tattr); // Recupera gli attributi correnti della seriale
// Modi di ingresso - spegne i flag indicati
tattr.c_iflag &= ~(INLCR|IGNCR|ICRNL|IXON);
// Modi di uscita - spegne i flag indicati
tattr.c_oflag &= ~(OPOST|ONLCR|OCRNL|ONLRET);
/* Modi di controllo - imposta 8 bit (CS8), abilita ricezione (CREAD),
*ignora segnali di controllo (CLOCAL) */
tattr.c_cflag = CS8 | CREAD | CLOCAL;
tattr.c_lflag &= ~(ICANON|ECHO); // elimina traduzioni di carateri (ICANON) ed ECHO.
tattr.c_cc[VMIN] = 1; // restituisce dati dopo aver ricevuto 1 carattere
tattr.c_cc[VTIME] = 0; // nessun timeout
cfsetispeed (&tattr, B9600); // Imposta la velocita' di ricezione
cfsetospeed (&tattr, B9600); // Imposta la velocita' di trasmissione
tcsetattr (com, TCSAFLUSH, &tattr); // Imposta i nuovo attributi
return com; // Ritrona l'handle del file aperto
} |
Per provare il programma, scaricare il sorgente,
compilarlo con il comando cc seriale.c -o seriale -lncurses ed eseguirlo
con il comando ./seriale.
[Home Page dell'ITIS "Fermi"]
[Indice Quinta]
[Precedente]
[Successivo]
© Ing. Stefano Salvi - Released under GPL licence
|