www.mikrocontroller.net

Forum: PC-Programmierung Serielle Schnittstelle Linux -- Baudrate?


Autor: LynUx (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo liebe Leute!

Da in Linux ja alles ein "File" ist, habe ich soeben erste Erfahrungen 
gemacht, eine serielle Schnittstelle mittels fopen() anzusteuern, nach 
dem Prinzip, in eine Datei zu schreiben.

Hier der kleine Beispielcode:
#include <stdio.h>

int main (void)
{

  FILE * pFile;
  pFile = fopen("/dev/ttyS0","w+b");
  fprintf(pFile,"Test");
  fclose(pFile);
}

Allerdings gibt es ja noch einige Möglichkeiten mehr, als einfach was 
auf "Gut Glück" rauszuschicken.
Da denke ich zum Beispiel an die Baudrate. Wie kann ich in C eine 
Baudrate einstellen, bzw. die ganzen "Terminaleinstellungen" wie 
"Hardware-Handshake ON/OFF" usw. programmieren?

Oder gibt es eine elegantere Möglichkeit, mit der Seriellen 
Schnittstelle zu kommunizieren?

Das Ganze soll unter Linux (Ubuntu) laufen, wie ihr es ja schon am 
"dev/ttyS0" sehen könnt :)

Vielen Dank!

Autor: g457 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
man tc[gs]etattr; man cfset[io]speed;

HTH

Autor: mar IO (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich schaue immer bei dieser Seite nach:

http://www.easysw.com/~mike/serial/serial.html

Autor: Bernhard M. (boregard)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich mach das so (das device beim öffnen ist z.B. der string 
"/dev/tty0"):
/*****************************************************************************
 *
 *      Open and initialise serial port
 *
 ****************************************************************************/
static int fnInitV24 (const char * device)
{
    int                 iFd;
    int                 modelines = 0;
    struct termios      settings;

    /* open and check filedescriptor */
    iFd = open (device, O_RDWR | O_NOCTTY);

    if (iFd < 0)
    {
        fprintf (stderr, "Can not open device %s (errno: %s)!\n",
                 device, strerror (errno));
        return (iFd);
    }

    /* Check if filedescriptor belongs to a terminal */
    if (!isatty (iFd))
    {
        fprintf (stderr, "Device %s is not a terminal device (errno: %s)!\n",
                 device, strerror (errno));
        close (iFd);
        return (-1);
    }

    /* Set input flags */
    settings.c_iflag =  IGNBRK          /* Ignore BREAKS on Input */
                     |  IGNPAR;         /* No Parity              */

    /* Set output flags */
    settings.c_oflag = 0;

    /* Set controlflags */
    settings.c_cflag = CS8              /* 8 bits per byte */
                     | CREAD            /* characters may be read */
                     | CLOCAL;          /* ignore modem state, local connection */

    /* Set local flags */
    settings.c_lflag = 0;

    /* Set maximum wait time on input */
    settings.c_cc[VTIME] = 10;

    /* Set minimum bytes to read */
    settings.c_cc[VMIN]  = 0;

    /* set speed */
    if (cfsetspeed (&settings, B9600) != 0)
    {
        fprintf (stderr,
                 "Can not set communication speed to %s (errno: %s)!\n",
                 device, strerror (errno));
        return (-1);
    }

    /* transfer setup to interface */
    if (tcsetattr (iFd, TCSANOW, &settings) < 0)
    {
        fprintf (stderr, "Error setting terminal attributes for %s (errno: %s)!\n",
                 device, strerror (errno));
        return (-1);
    }

    /* Read IO parameter */
    if (ioctl (iFd, TIOCMGET, &modelines) < 0)
    {
        fprintf (stderr, "Error getting IO parameter from %s (errno: %s)!\n",
                 device, strerror (errno));
        return (-1);
    }

    /* Set RTS-Signal */
    modelines &= ~TIOCM_RTS;
    if (ioctl (iFd, TIOCMSET, &modelines) < 0)
    {
        fprintf (stderr, "Error setting RTS on %s (errno: %s)!\n",
                 device, strerror (errno));
        return(-1);
    }
    return (iFd);
}

/*****************************************************************************
 *
 *      Close serial port
 *
 ****************************************************************************/
static int fnCloseV24 (int iFd)
{
    return (close (iFd));
}

/*****************************************************************************
 *
 *      Write to serial port
 *
 ****************************************************************************/
static int fnWriteV24 (int      iFd,
                       char     *pszOut,
                       size_t   tLen)
{
    return (write (iFd, pszOut, tLen));
}


/*****************************************************************************
 *
 *      Read from serial port
 *
 ****************************************************************************/
static int fnReadV24 (int       iFd,
                      char      *pszIn,
                      size_t    tLen)
{
    int                 iNrRead;
    iNrRead = read (iFd, pszIn, tLen);

    return (iNrRead);
}


/*****************************************************************************
 *
 *      Set timeout on tty input to new value, returns old timeout
 *
 ****************************************************************************/
static int fnSetTimeout (int    iFd,
                         int    iTimeout)
{
    struct termios      stTerminal;
    int                 iOldTimeout;

    /* get current settings */
    tcgetattr (iFd, &stTerminal);

    /* get old timeout */
    iOldTimeout = stTerminal.c_cc[VTIME];

    /* set timeout in 10th of seconds */
    stTerminal.c_cc[VTIME] = iTimeout;

    /* set new status */
    tcsetattr (iFd, TCSANOW, &stTerminal);

    return (iOldTimeout);
}


Autor: LynUx (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ok Danke,

schreiben kann ich schonmal auf die Schnittstelle :)


Hat Linux einen Art Eingangspuffer, falls ich nicht genau in dem Moment 
die Schnittstelle abfrage, wo Daten reinkommen?

Gibt es einen Art Interrupt,sobald Linux was empfangen hat?

Wenn ja, wie kann ich den in C auswerten?


Danke!

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das würde man Unix-gemäß meist so machen, daß man einfach liest.

Solange hängt das Programm - falls das nicht gewünscht ist, macht
man einen zusätzlichen Thread auf.
Der eine liest und blockiert ggf. solange, der andere läuft weiter.

Eine Variante ist select(), aber die meisten Leute mögen das nicht
von Anfang an :-)

Autor: Hc Zimmerer (mizch)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sendung und Empfang gehen über Interrupt-Puffer.  Voraussetzung, dass 
gepuffert wird, ist allerdings, dass die Schnittstelle geöffnet ist. 
Jeder close/open leert den Puffer.

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> Das würde man Unix-gemäß meist so machen, daß man einfach liest.

Unix-Stil ist wohl eher nur ein Thread, der mit select() alle offenen 
Files ueberwacht...

> Eine Variante ist select(), aber die meisten Leute mögen das nicht
> von Anfang an :-)

Muss man auch nicht moegen, beisst sich ueber kurz oder lang ziemlich 
schnelle mit Abstraktion und OO.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Stegemann schrieb:
> Unix-Stil ist wohl eher nur ein Thread, der mit select() alle offenen
> Files ueberwacht...

archaischer Unix-Stil; seit Kurzem werden Threads auch schon
mal verwendet :-)

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:

> archaischer Unix-Stil; seit Kurzem werden Threads auch schon
> mal verwendet :-)

Vor ein paar Monaten sprach mich ein Arbeitskollege an und erklaerte, 
Threads seien eine veraltete Technologie der 80er Jahre. Ich kriege 
gelegentlich noch ein paar Geschichten erzaehlt, wie "unser" Projekt 
unter seiner Hand nun laeuft, aber ich bin zum Glueck weit weg...

Was hier Linux aber definitv fehlt, ist eine Moeglichkeit die serielle 
Schnittstelle als normales File zu benutzen aber trotzdem die Parameter 
einzustellen. Die "Everything is a file"-Philosophie so richtig ausleben 
koennte man, wenn man z.B. "/dev/serx/115200/8/n/1" oeffnen koennte.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Stegemann schrieb:
> ... als normales File ...

Damit meinst du FILE* statt int fd?

Das geht doch auch, indem man sie als FILE * öffnet (z.B.
als "/dev/ttyS0" etc.) und sich den fd dazu mit fileno() holt.
Über den kann man dann die ioctl(), tcsetattr() etc. machen.

Oder umgekehrt erst mit open() öffnen, und dann einen Stream
mit fdopen() daraus machen.

Sollte beides gehen.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter Stegemann schrieb:
> Vor ein paar Monaten sprach mich ein Arbeitskollege an und erklaerte,
> Threads seien eine veraltete Technologie der 80er Jahre.

Wenn er ohne Threads eine CPU mit 4 oder 8 Kernen sinnvoll
auslastet, mag er recht haben.

Wahrscheinlich unter Windows kein Problem, weil der oder die
Virenscanner, Windows selbst und allerlei Blinkkram und Services
für jeden Pups genug Last erzeugen.

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> Peter Stegemann schrieb:
>> ... als normales File ...
> Damit meinst du FILE* statt int fd?

Ob FILE* oder int fd ist egal.

> Das geht doch auch, indem man sie als FILE * öffnet (z.B.
> als "/dev/ttyS0" etc.) und sich den fd dazu mit fileno() holt.
> Über den kann man dann die ioctl(), tcsetattr() etc. machen.

Das hat aber nichts mit einem Zugriff zu tun, bei dem die Parameter 
ueber den Pfad uebergeben werden. Denn das wuerde z.B. auch mit cat 
funktionieren, das ueberhaupt keine seriellen Optionen kennt.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ach so, jetzt verstehe ich.

Dann müsste es zumindest mit einem vorgeschickten stty gehen.
Über den Namen wäre zugegebenermaßen fauler, aber da sind die
möglichen Einstellungen doch vielleicht etwas begrenzt.
Soviele Namen kann man kaum erzeugen, wie man bei RS232
Einstellungen hat.

Autor: Bernhard M. (boregard)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
o.k., hier ein weiterer Teil, der das lesen per select macht.
Auch wenn das von einigen als archaisch angesehen wird (o.k. das hier 
ist auch schon >10 Jahre alt...), erspart es meiner Meinung nach bei 
kleinen Programmen wie hier den ganzen Thread-Aufwand, und man hat nicht 
die Probleme, die man sich mit Threads einhandelt...
/*****************************************************************************
 *
 *      Handle keyboard input
 *
 ****************************************************************************/
static int fnHandleKeyboard (FILE  *fInput,
                             int   iOutput)
{
    static char fname[1024+1];
    char        cOut;
    int         iInChar = EOF;
    int         iInDesc = fileno (fInput);

    if (iInDesc < 0)
    {
        fprintf (stderr, "Error: Invalid stream for input (keyboard?) (errno %d: %s)!\n",
                 errno, strerror (errno));
        return (FALSE);
    }

    while (EOF != (iInChar = getc (fInput)))
    {
        switch (iInChar)
        {
            case '\r':
                /* ignore... */
                break;

            case '\n':
                fnWriteV24 (iOutput, "\r", 1);
                break;

            case CTRLF:
                tcsetattr (iInDesc, TCSAFLUSH, &gstTermOld);
                printf ("\nEnter Filename: ");
                scanf ("%s", fname);
                printf ("\nstart DOWNLOAD with CTRL D...");
                tcsetattr (iInDesc, TCSAFLUSH, &gstTerminal);
                break;

            case CTRLD:
                tcsetattr (iInDesc, TCSAFLUSH, &gstTermOld);
                download (iOutput, fname);
                tcsetattr (iInDesc, TCSAFLUSH, &gstTerminal);
                break;

            case EOF:
                break;

            case CTRLC:
                return (FALSE);
                break;

            default:
                cOut = (char) iInChar;
                fnWriteV24 (iOutput, &cOut, 1);
                break;
        }
    }

    return (TRUE);
}


/*****************************************************************************
 *
 *      Program loop
 *
 ****************************************************************************/
static void do_v24 (int iFd)
{
    int                 iOldTimeout;
    int                 iStdio;
    int                 ok;
    int                 iMaxSelect;
    struct timeval      tTimeout;
    fd_set              fdSet;
    int                 iSelRet;

    /* "open" terminal -> get filedescriptor to it */
    gfStdio = fopen (ctermid (NULL), "r+");
    if (gfStdio == NULL) {
        fprintf (stderr, "\nCan not open terminal (errno: %s) !\n", strerror (errno));
        return;
    }

    iStdio = fileno (gfStdio);
    if (iStdio < 0)
    {
        fprintf (stderr, "Error: Invalid stream for terminal (errno %d: %s)!\n",
                 errno, strerror (errno));
        return;
    }

    tcgetattr (fileno (gfStdio), &gstTermOld);

    gstTerminal = gstTermOld;
    gstTerminal.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON);

    /* Set maximum wait time on input */
    gstTerminal.c_cc[VTIME] = 0;

    /* Set minimum bytes to read */
    gstTerminal.c_cc[VMIN]  = 0;

    tcsetattr (fileno (gfStdio), TCSAFLUSH, &gstTerminal);

    /* set new timeout on V24, we want responsive system */
    iOldTimeout = fnSetTimeout (iFd, 0);   /* no wait */

    ok = TRUE;

    do
    {
        FD_ZERO (&fdSet);
        FD_SET (iFd, &fdSet);
        FD_SET (iStdio, &fdSet);
        iMaxSelect = (iFd > iStdio) ? iFd : iStdio;

        /* -- set max. waittime -- */
        tTimeout.tv_sec  = 1;
        tTimeout.tv_usec = 500000;

        errno = 0;

        iSelRet = select (iMaxSelect + 1, &fdSet, NULL, NULL, &tTimeout);

        if (iSelRet > 0)
        {
            /* -- we got something on stdin ?? -- */
            if (FD_ISSET (iStdio, &fdSet))
            {
                /* stdin: someone hacked the keyboard */
                ok = fnHandleKeyboard (gfStdio, iFd);
            }

            /* -- we got something from serial line -- */
            if (FD_ISSET (iFd, &fdSet))
            {
                /* handle V24 input here */
                fnHandleInput (iFd, gfStdio);
            }
        }
        else if (iSelRet < 0)
        {
            /*
            ** Check if SIGINT was received. This is handled further down,
            ** so we ignore the error from select in that case.
            */
            if (errno != EINTR)
            {
                fprintf (stderr, "Error reading from select: (%d) %s\n",
                         errno, strerror (errno));
                break;
            }
        }
        else
        {
            /* just timeout */
            wasEsc = 0;
        }
    } while (ok && gRun);

    /* reset old timeout */
    fnSetTimeout (iFd, iOldTimeout);

    tcsetattr (iStdio, TCSAFLUSH, &gstTermOld);
    fclose (gfStdio);
}

Autor: LynUx (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Guten morgen Jungs!

Ich habe es jetzt erstmal mit der <termios.h> gemacht.

Die Funktion write() erfüllt schon mal ihre Dienste (schreibt mir auf 
die virtuelle Schnittstelle).

mit read() habe ich wiegesagt noch ein Verständnisproblem.

Gibt es jetzt in Linux einen Eingangspuffer für die RS232 oder nicht?

Ich würde dann einfach einen Thread programmieren, der alle paar hundert 
Millisekunden mal auf den Empfangspuffer lauscht.




DAnke!

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
LynUx schrieb:
> Gibt es jetzt in Linux einen Eingangspuffer für die RS232 oder nicht?

Nein, zumindest nicht mit nennenswerter Größe.

Du solltest in dem zusätzlichen Thread nicht alle soundsolang
nachschauen, sondern einfach lesen, was geht und wegschreiben
(globaler Puffer, oder was du halt machen willst).
Ob du wartest oder gleich blockierend liest, ist doch egal.
Es hängt so oder so dieser Thread.

Autor: LynUx (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also mal im Pseudocode zum Verständnis:

globale Variable Input;

Threadfunktion 
{
   Endlosschleife
   {
       Input = read();
   }

   warte([wartezeit]);

}



Danke!

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wozu das Warten?
Noch dazu nach einer Endlosschleife, wo du nie ankommst?

Mach dir eher Gedanken, wie man Kollisionen beim Zugriff
auf die globale Variable vermeidet.
Solange das Abspeichern nicht atomar ist, sollte das restliche
Programm nicht lesen, während der Thread schreibt.

Das könnte dann so aussehen:
globale Variable Input;
Mutex für Input;

Threadfunktion 
{
   while( immerundewig )
   {
       lokale Variable tmp;
       tmp = read();
       Mutex für Input setzen;
       Input = tmp;
       Mutex für Input freigeben;
   }
}

Das Warten passiert automatisch im read(), wenn nichts da ist.
(Es sei denn, man setzt den fd explizit auf non blocking, aber
das macht kaum Sinn.)

Autor: LynUx (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mutex bedeutet doch, dass ich während ich mit meinem Thread auf die 
glob. Variable zugreife, theoretisch von einem anderen Thread (oder 
Routine) darauf zugreifen kann und es dabei crasht richtig?

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> ... und es dabei crasht richtig?

Mit einem Mutex versucht man genau das zu verhindern.

Beispiel ohne:
int variable;

void thread1()
{
  while( true )
  {
    variable = variable + 3*( variable - 5 );
    printf( "%d\n", variable );
    sleep( 1 );
  }
}

void thread2()
{
  while( true )
  {
    if( es_ist_Zeit_fuer_einen_Neustart )
    {
      variable = 1;
    }
  }
}
Hier könnte mit etwas Pech die Variable von thread2() zu 1
gesetzt werden, während thread1() mitten in der Berechnung ist.
Dann wäre der ausgegebene Wert ein Mischmasch aus neuem und altem
Wert.

Das gilt es zu vermeiden!

Stell dir den Mutex einfach als globales Flag vor, der auf true oder
false  bzw. 1 oder 0 stehen kann.
Man vereinbart jetzt einfach, daß das Flag auf 0 (false) stehen soll,
solange niemand auf die Variable zugreift, und auf 1 (true), wenn
jemand zugreift.
Weiterhin hält sich jeder einfach an die Regel, nicht einfach
zuzugreifen, sondern zu warten bis das Flag auf 0 ist, es dann
auf 1 setzt, jetzt erst die Variable anfasst und dann wieder das
Flag zu 0 setzt:
int variable;
bool flag_fuer_variable;

void thread1()
{
  while( true )
  {

    // Mutex setzen:
    while( flag_fuer_variable )
    {
      // nichts tun, bis keiner auf variable zugreift
    }
    flag_fuer_variable = true;

    // jetzt die variable verwenden (möglichst schnell, um den Mutex
    // bzw. das Flag nicht zu lange zu blockieren):
    variable = variable + 3*( variable - 5 );
    printf( "%d\n", variable );

    // Mutex wieder freigeben:
    flag_fuer_variable = false;

    // jetzt könnte wieder jemand anders auf variable zugreifen...

    sleep( 1 );
  }
}

void thread2()
{
  while( true )
  {
    if( es_ist_Zeit_fuer_einen_Neustart )
    {
      // Mutex setzen:
      while( flag_fuer_variable )
      {
        // nichts tun, bis keiner auf variable zugreift
      }
      flag_fuer_variable = true;

      // jetzt die variable verwenden (möglichst schnell, um den Mutex
      // bzw. das Flag nicht zu lange zu blockieren):
      variable = 1;

      // Mutex wieder freigeben:
      flag_fuer_variable = false;
    }
  }
}

Damit hat man das Problem umgangen. Jeder der Threads wartet
vor dem Zugriff auf die Variable höflich darauf, daß der andere
damit fertig ist.
Zumindest glaubt man, das Problem umgangen zu haben - solange
man nicht genau hinsieht.
Dann würde man nämlich feststellen, daß man das Problem nur
etwas verschoben hat: es könnte thread 1() laufen, in der
Schleife feststellen, daß das Flag gerade frei ist und
mit etwas Pech genau jetzt unterbrochen werden von thread2().
Der stellt auch fest, daß es frei ist und macht also weiter,
ebenso wie thread1(), wenn er wieder Rechenzeit bekommt.

Es muß also dafür gesorgt werden, daß die Abfrage "ist der
Mutex gerade frei" und das Setzen, falls ja, in einem
atomaren Vorgang stattfindet, also nicht von einem anderen
Thread unterbrochen werden kann.

Weil es dazu in C oder C++ keinen sicheren portablen Weg
gibt, braucht man die mithilfe des OS.
Es gibt dazu passende Bibliotheken, ich verwende meist
für Threads die libpthread mit den Posix-Threads und
darin ist auch gleich ein Mutex enthalten.

Dahinter steckt nichts weiter als ein logisches Flag
ja/nein und die Möglichkeit, das atomar abzufragen und zu
setzen.
Nur heißt es dann nicht mehr Flag, sondern Mutex.
Das Setzen heißt Sperren und das Löschen heißt Freigeben.

Autor: LynUx (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ok Danke für die gute Erklärung, habs soweit verstanden :)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.