PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : C: Grundsätzliches zum Auslesen einer seriellen Schnittstelle unter UNIX



Stefan Feeser
25-07-2006, 15:36
Hallo,

mich würde interessieren wie der grundsätzliche Ablauf beim Auslesen einer seriellen Schnittstelle funktioniert und was dabei passiert.

Mich interessiert insbesondere:
- was muss systemseitig vorliegen (Characterdevice, Objektdateien???)
- wie ist das technische Zusammenspiel zwischen dem UNIX-Characterdevice und C bzw. anderen Sprachen (was läuft da ab - ich kenne Major und Minornummern und weiss grob dass diese letztlich die Verbindung zum Treiber herstellen, was aber genau passiert wüsste ich gerne)

Vielleicht hat jemand ja auch einen Tipp zur erläuternden Literatur oder einen Link.

Als Anmerkung dazu, ich habe in Perl schon oft Dateien ausgelesen, ist das mit dem Vorgehen in C vergleichbar?

Gruss und Danke

Stefan

PS: Hintergrund ist, dass ich z. B. einen Smartkartenleser und ein GPS-Gerät habe und mit diesen gerne ein wenig herumbasteln würde.

gorba
26-07-2006, 08:08
Hi Stefan

Das kommt total darauf an, wo du dich befindest (kernel- oder user-space) und wie du deine schnittstelle öffnest. Ich hab 3 Tage gebraucht nur um meine schnittstelle richtig zu konfigurieren.

Hoffe das hilft dir ein wenig, wenn noch fragen sind, stell sie nur.

Zuerst musst du mal deine schnittstelle öffnen:



extern int open_file(char *pathname){
int retval;

if(-2 == the_fd){
retval = open(pathname, O_RDWR | O_NOCTTY | O_NONBLOCK);
if(0 > retval){
printf("fil_des.c -> open_file -- open() return Wert == -1 : ABBRUCH\n");
exit(0);
}
the_fd = retval;
}

else{
retval = the_fd;
}

return retval;
}


Wie du sehen kannst, habe ich mich für eine nicht blockierende variante entschieden. Evt. müsstest du also (je nach anwendung) das flag O_NONBLOCK entfernen.

Danach solltest (musst) du deine Schnittstelle konfigurieren:



/*! \brief Initialisierungen der Schnittstelle
*
* Führt alle initialisierungen für die Übertragungsflags der Schnittstelle aus.
*
* \return file descriptor
* \retval -1: Error
* \retval andere: Success
*/
extern int initialize_tty(){
int val;
struct sigaction saio;
struct termios oldtio,newtio;

wait_flag = TRUE;
saio.sa_handler = signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
read_fd = return_open_file(); // get da fd

fcntl(read_fd, F_SETOWN, getpid());
// fcntl(read_fd, F_SETFL, FASYNC | O_NONBLOCK );
// fcntl(read_fd, F_SETFL, FASYNC );


tcgetattr(read_fd,&oldtio);

/* Set ttyS0 to 19200 8N1 */
newtio.c_cflag &= ~PARENB;
newtio.c_cflag &= ~CSTOPB;
newtio.c_cflag &= ~CSIZE;
// newtio.c_cflag |= BAUDRATE | CS8;
newtio.c_cflag = BAUDRATE | CS8;

/* Enable Receiver and Local line */
newtio.c_cflag |= CLOCAL | CREAD;

/* Disable Hardware Flow Control */
newtio.c_cflag &= ~CRTSCTS;

/* Setting to raw Input */
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

/* Do not translate CR to NL, do not translate NL to CR, do not ignore CR on input, do not LOWC to UPC */
// newtio.c_iflag &= ~(INLCR | IGNCR | ICRNL | IUCLC);

/* Ignore Parity Errors */
newtio.c_iflag |= IGNPAR;

/* Disable Software Flow Control */
newtio.c_iflag &= ~(IXON | IXOFF | IXANY);

/* Setting to raw Output */
newtio.c_oflag &= ~OPOST;
/*
newtio.c_cflag = BAUDRATE | CS8;
newtio.c_iflag = IGNBRK;
newtio.c_lflag = 0;
newtio.c_oflag = 0;
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cc[VMIN] = 1;
newtio.c_cc[VTIME] = 0;
newtio.c_cflag |= PARENB;
*/
/********************* Old Port Settings ************************
* original: BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD; *
* original: [vi]dd *
* original: [vi]dd *
* original: IGNPAR | ICRNL; *
* newtio.c_oflag = 0; *
* newtio.c_lflag = ICANON; *
* newtio.c_cc[VMIN]=1; *
* newtio.c_cc[VTIME]=0; *
************************************************** *************/

tcflush(read_fd, TCIFLUSH);
tcsetattr(read_fd,TCSANOW,&newtio);

}


Hier musst du noch einiges anpassen, da ich alle handshakes (HW + SW) und fehlerkorekturen entfernt habe. man tcsetattr sollte dir allerdings alles sagen, was du dazu wissen musst.

Viel spass

ps: ich sollte dir noch sagen, dass das ganze nicht POSIX konform ist, allerdings haben die POSIX funktionen auf meiner kiste nichts bewirkt. Auf der Konsole kannst du mit: stty -F SERIAL_DEVICE -a überprüfen, ob die änderungen auf der Schnittstelle gemacht wurde. du muss noch SERIAL_DEVICE mit dem Pfad deines seriellen gerätes ausgleichen (z.B. /dev/ttyS0)

pps: du solltest bevor du deine schnittstelle neu konfigurierst, die alte konfiguration in einem struct ablegen, um sie am ende des programms wider auf die schnittstelle schreiben zu können (--> das währe gegenüber anderen programmen etc. sehr freundlich und sollte gemacht werden).

Stefan Feeser
26-07-2006, 13:29
Hi,

vielen Dank für die ausführliche Beschreibung. Da ich noch C-Anfänger bin habe ich in der Tat noch ein paar Fragen.

Die Konfigurationsparameter in Deiner Initialisierungsfunktion sind mir, zumindest grossteils klar. Den Teil der mir noch etwas unverständlich ist, denke ich dass ich durch probieren rausbekomme.

Die erste Funktion gibt mir allerdings einige Rätsel auf. Wo deklarierst Du beispielsweise the_fd. Welche Bibliotheken muss man dafür einbinden? Ausserdem verstehe ich die Funktion so, dass Du eine Verbindung zu dem Device mit den Parametern "No Blockdevice", "Keine Konsole" und als ReadWrite öffnest. Wenn sich diese Verbindung herstellen läßt gibst Du als Returnwert die "SessionID" (hier weiss ich nicht ob das der korrekte Name für den Wert den Du zurückgibst ist) zurück.

Wie Du hier aber die ganzen Initialisierungsparameter übergibst und wie Du dann was auslesen kannst und zuletzt diesen "Pipe" wieder zu machst kapier ich noch nicht.

Kannst Du vielleicht erst mal kurz ohne Code beschreiben wie die Verbindung abläuft? Ganz nach dem Motto "Wat isn Dampfmaschin":D

Ach ja, Du sagtest, dass es ganz darauf ankommt ob Du im Userspace oder im Kernelspace arbeitest. Vielleicht kannst Du die Unterschiede mal kurz skizzieren. Ich verstehe das derzeit so, dass Du meinst, dass ich im Kernel ja verschiedene Treiber geladen habe und damit die dort zur Verfügung gestellten Funktionen nutzen könnte. Im User Arbeitsspeicher hingegen muss ich alle Funktionen durch das Einbinden von Libs, etc. selbst laden. Auch hier wäre es klasse wenn Du kurz beschreiben könntest wie das Charakterdevice in Verbindung zu Kernelmodulen bzw. zu Treibern (so-Dateien?!?) steht, wie hier Major und Minornumber ins Spiel kommen, etc.

Ich lese mir gerne auch Literatur (wie gesagt mit Systemprogrammierung von Herold hatte ich schon begonnen, aber da sind soviele Funktionen beschrieben, dass das eher mehr verwirrt) durch, ich weiss aber momentan nicht so recht wo ich anfangen soll. Ich möchte das ganze verstehen und daher erst mal mit einem Beispiel anfangen.

Gruß und nochmal vielen Dank

Stefan

gorba
26-07-2006, 14:41
ok literatur (beantwortet schon viele deiner fragen):
man open
man tcsetattr

the_fd is nix anderes als ne modulglobale variable. Die Funktion is einigermassen fehlersicher programmiert, da sie zuerst meine Variable überprüft:
if(-2 == the_fd)
ob in dieser -2 steht (wurde mit -2 initialisiert) und erst dann wir ein open aufs file gemacht. Da ansonsten der fd ja schon in the_fd steht und somit das device nicht noch einmal geöffnet werden muss.

O_RDWR, is n flag das mir gestattet, auf das device zu lesen und zu schrieben.
Die anderen beiden brauchen dich erst nicht zu interessieren, kannste getrost rauslöschen.

rausgelesen wird mit: read( int fd, void *buf, size_t count);
geschrieben wird mit: write( int fd, const void *buf, size_t count);

Wobei beim read jez das nonblock oder eben block ne rolle spielt, beim nonblock, retourniert die funktion auch wenn gar keine oder nur ein teil der im parameter size_t count angegeben menge auszulesender bytes tatsächlich ausgelesen wurde. beim block read würde die funktion so lange blockieren bis die anzahl bytes tatsächlich gelesen wurde.
Ein Thema das rauf und runter diskutiert wurde. Ich musste auf so ne unschöne variantre zurückgreifen wil select() mit dem kernel 2.6 mist zurückgeliefert hatt... Naja...

Die ganze sache user- kernelspace kann ich dir net so genau erklären. allerdings müsstest du im kernel space dich selber um deine tty Buffer kümmern, was natürlich ein risen vorteil ist, da dir kein (VERKRÜPPELTER!!) serieller treiber n strich durch die rechnung macht. allerdings ist das auch ganz schön umständlich, wenn du die serielle kommunikation als block device konfiguriert hast....

Sonst noch unklarheiten?

ach ja... das ding geschlossen wird mit: close(int fd);

Stefan Feeser
26-07-2006, 15:05
Thx,

das sollte erst mal reichen. Ich lese mir mal die Manpages durch.

Gruss und schon mal einen schönen Abend

Stefan