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


von LynUx (Gast)


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:
1
#include <stdio.h>
2
3
int main (void)
4
{
5
6
  FILE * pFile;
7
  pFile = fopen("/dev/ttyS0","w+b");
8
  fprintf(pFile,"Test");
9
  fclose(pFile);
10
}

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!

von g457 (Gast)


Lesenswert?

man tc[gs]etattr; man cfset[io]speed;

HTH

von mar IO (Gast)


Lesenswert?

Ich schaue immer bei dieser Seite nach:

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

von Bernhard M. (boregard)


Lesenswert?

Ich mach das so (das device beim öffnen ist z.B. der string 
"/dev/tty0"):
1
/*****************************************************************************
2
 *
3
 *      Open and initialise serial port
4
 *
5
 ****************************************************************************/
6
static int fnInitV24 (const char * device)
7
{
8
    int                 iFd;
9
    int                 modelines = 0;
10
    struct termios      settings;
11
12
    /* open and check filedescriptor */
13
    iFd = open (device, O_RDWR | O_NOCTTY);
14
15
    if (iFd < 0)
16
    {
17
        fprintf (stderr, "Can not open device %s (errno: %s)!\n",
18
                 device, strerror (errno));
19
        return (iFd);
20
    }
21
22
    /* Check if filedescriptor belongs to a terminal */
23
    if (!isatty (iFd))
24
    {
25
        fprintf (stderr, "Device %s is not a terminal device (errno: %s)!\n",
26
                 device, strerror (errno));
27
        close (iFd);
28
        return (-1);
29
    }
30
31
    /* Set input flags */
32
    settings.c_iflag =  IGNBRK          /* Ignore BREAKS on Input */
33
                     |  IGNPAR;         /* No Parity              */
34
35
    /* Set output flags */
36
    settings.c_oflag = 0;
37
38
    /* Set controlflags */
39
    settings.c_cflag = CS8              /* 8 bits per byte */
40
                     | CREAD            /* characters may be read */
41
                     | CLOCAL;          /* ignore modem state, local connection */
42
43
    /* Set local flags */
44
    settings.c_lflag = 0;
45
46
    /* Set maximum wait time on input */
47
    settings.c_cc[VTIME] = 10;
48
49
    /* Set minimum bytes to read */
50
    settings.c_cc[VMIN]  = 0;
51
52
    /* set speed */
53
    if (cfsetspeed (&settings, B9600) != 0)
54
    {
55
        fprintf (stderr,
56
                 "Can not set communication speed to %s (errno: %s)!\n",
57
                 device, strerror (errno));
58
        return (-1);
59
    }
60
61
    /* transfer setup to interface */
62
    if (tcsetattr (iFd, TCSANOW, &settings) < 0)
63
    {
64
        fprintf (stderr, "Error setting terminal attributes for %s (errno: %s)!\n",
65
                 device, strerror (errno));
66
        return (-1);
67
    }
68
69
    /* Read IO parameter */
70
    if (ioctl (iFd, TIOCMGET, &modelines) < 0)
71
    {
72
        fprintf (stderr, "Error getting IO parameter from %s (errno: %s)!\n",
73
                 device, strerror (errno));
74
        return (-1);
75
    }
76
77
    /* Set RTS-Signal */
78
    modelines &= ~TIOCM_RTS;
79
    if (ioctl (iFd, TIOCMSET, &modelines) < 0)
80
    {
81
        fprintf (stderr, "Error setting RTS on %s (errno: %s)!\n",
82
                 device, strerror (errno));
83
        return(-1);
84
    }
85
    return (iFd);
86
}
87
88
/*****************************************************************************
89
 *
90
 *      Close serial port
91
 *
92
 ****************************************************************************/
93
static int fnCloseV24 (int iFd)
94
{
95
    return (close (iFd));
96
}
97
98
/*****************************************************************************
99
 *
100
 *      Write to serial port
101
 *
102
 ****************************************************************************/
103
static int fnWriteV24 (int      iFd,
104
                       char     *pszOut,
105
                       size_t   tLen)
106
{
107
    return (write (iFd, pszOut, tLen));
108
}
109
110
111
/*****************************************************************************
112
 *
113
 *      Read from serial port
114
 *
115
 ****************************************************************************/
116
static int fnReadV24 (int       iFd,
117
                      char      *pszIn,
118
                      size_t    tLen)
119
{
120
    int                 iNrRead;
121
    iNrRead = read (iFd, pszIn, tLen);
122
123
    return (iNrRead);
124
}
125
126
127
/*****************************************************************************
128
 *
129
 *      Set timeout on tty input to new value, returns old timeout
130
 *
131
 ****************************************************************************/
132
static int fnSetTimeout (int    iFd,
133
                         int    iTimeout)
134
{
135
    struct termios      stTerminal;
136
    int                 iOldTimeout;
137
138
    /* get current settings */
139
    tcgetattr (iFd, &stTerminal);
140
141
    /* get old timeout */
142
    iOldTimeout = stTerminal.c_cc[VTIME];
143
144
    /* set timeout in 10th of seconds */
145
    stTerminal.c_cc[VTIME] = iTimeout;
146
147
    /* set new status */
148
    tcsetattr (iFd, TCSANOW, &stTerminal);
149
150
    return (iOldTimeout);
151
}

von LynUx (Gast)


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!

von Klaus W. (mfgkw)


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 :-)

von Hc Z. (mizch)


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.

von P. S. (Gast)


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.

von Klaus W. (mfgkw)


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 :-)

von P. S. (Gast)


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.

von Klaus W. (mfgkw)


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.

von Klaus W. (mfgkw)


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.

von P. S. (Gast)


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.

von Klaus W. (mfgkw)


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.

von Bernhard M. (boregard)


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...
1
/*****************************************************************************
2
 *
3
 *      Handle keyboard input
4
 *
5
 ****************************************************************************/
6
static int fnHandleKeyboard (FILE  *fInput,
7
                             int   iOutput)
8
{
9
    static char fname[1024+1];
10
    char        cOut;
11
    int         iInChar = EOF;
12
    int         iInDesc = fileno (fInput);
13
14
    if (iInDesc < 0)
15
    {
16
        fprintf (stderr, "Error: Invalid stream for input (keyboard?) (errno %d: %s)!\n",
17
                 errno, strerror (errno));
18
        return (FALSE);
19
    }
20
21
    while (EOF != (iInChar = getc (fInput)))
22
    {
23
        switch (iInChar)
24
        {
25
            case '\r':
26
                /* ignore... */
27
                break;
28
29
            case '\n':
30
                fnWriteV24 (iOutput, "\r", 1);
31
                break;
32
33
            case CTRLF:
34
                tcsetattr (iInDesc, TCSAFLUSH, &gstTermOld);
35
                printf ("\nEnter Filename: ");
36
                scanf ("%s", fname);
37
                printf ("\nstart DOWNLOAD with CTRL D...");
38
                tcsetattr (iInDesc, TCSAFLUSH, &gstTerminal);
39
                break;
40
41
            case CTRLD:
42
                tcsetattr (iInDesc, TCSAFLUSH, &gstTermOld);
43
                download (iOutput, fname);
44
                tcsetattr (iInDesc, TCSAFLUSH, &gstTerminal);
45
                break;
46
47
            case EOF:
48
                break;
49
50
            case CTRLC:
51
                return (FALSE);
52
                break;
53
54
            default:
55
                cOut = (char) iInChar;
56
                fnWriteV24 (iOutput, &cOut, 1);
57
                break;
58
        }
59
    }
60
61
    return (TRUE);
62
}
63
64
65
/*****************************************************************************
66
 *
67
 *      Program loop
68
 *
69
 ****************************************************************************/
70
static void do_v24 (int iFd)
71
{
72
    int                 iOldTimeout;
73
    int                 iStdio;
74
    int                 ok;
75
    int                 iMaxSelect;
76
    struct timeval      tTimeout;
77
    fd_set              fdSet;
78
    int                 iSelRet;
79
80
    /* "open" terminal -> get filedescriptor to it */
81
    gfStdio = fopen (ctermid (NULL), "r+");
82
    if (gfStdio == NULL) {
83
        fprintf (stderr, "\nCan not open terminal (errno: %s) !\n", strerror (errno));
84
        return;
85
    }
86
87
    iStdio = fileno (gfStdio);
88
    if (iStdio < 0)
89
    {
90
        fprintf (stderr, "Error: Invalid stream for terminal (errno %d: %s)!\n",
91
                 errno, strerror (errno));
92
        return;
93
    }
94
95
    tcgetattr (fileno (gfStdio), &gstTermOld);
96
97
    gstTerminal = gstTermOld;
98
    gstTerminal.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON);
99
100
    /* Set maximum wait time on input */
101
    gstTerminal.c_cc[VTIME] = 0;
102
103
    /* Set minimum bytes to read */
104
    gstTerminal.c_cc[VMIN]  = 0;
105
106
    tcsetattr (fileno (gfStdio), TCSAFLUSH, &gstTerminal);
107
108
    /* set new timeout on V24, we want responsive system */
109
    iOldTimeout = fnSetTimeout (iFd, 0);   /* no wait */
110
111
    ok = TRUE;
112
113
    do
114
    {
115
        FD_ZERO (&fdSet);
116
        FD_SET (iFd, &fdSet);
117
        FD_SET (iStdio, &fdSet);
118
        iMaxSelect = (iFd > iStdio) ? iFd : iStdio;
119
120
        /* -- set max. waittime -- */
121
        tTimeout.tv_sec  = 1;
122
        tTimeout.tv_usec = 500000;
123
124
        errno = 0;
125
126
        iSelRet = select (iMaxSelect + 1, &fdSet, NULL, NULL, &tTimeout);
127
128
        if (iSelRet > 0)
129
        {
130
            /* -- we got something on stdin ?? -- */
131
            if (FD_ISSET (iStdio, &fdSet))
132
            {
133
                /* stdin: someone hacked the keyboard */
134
                ok = fnHandleKeyboard (gfStdio, iFd);
135
            }
136
137
            /* -- we got something from serial line -- */
138
            if (FD_ISSET (iFd, &fdSet))
139
            {
140
                /* handle V24 input here */
141
                fnHandleInput (iFd, gfStdio);
142
            }
143
        }
144
        else if (iSelRet < 0)
145
        {
146
            /*
147
            ** Check if SIGINT was received. This is handled further down,
148
            ** so we ignore the error from select in that case.
149
            */
150
            if (errno != EINTR)
151
            {
152
                fprintf (stderr, "Error reading from select: (%d) %s\n",
153
                         errno, strerror (errno));
154
                break;
155
            }
156
        }
157
        else
158
        {
159
            /* just timeout */
160
            wasEsc = 0;
161
        }
162
    } while (ok && gRun);
163
164
    /* reset old timeout */
165
    fnSetTimeout (iFd, iOldTimeout);
166
167
    tcsetattr (iStdio, TCSAFLUSH, &gstTermOld);
168
    fclose (gfStdio);
169
}

von LynUx (Gast)


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!

von Klaus W. (mfgkw)


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.

von LynUx (Gast)


Lesenswert?

Also mal im Pseudocode zum Verständnis:
1
globale Variable Input;
2
3
Threadfunktion 
4
{
5
   Endlosschleife
6
   {
7
       Input = read();
8
   }
9
10
   warte([wartezeit]);
11
12
}



Danke!

von Klaus W. (mfgkw)


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:
1
globale Variable Input;
2
Mutex für Input;
3
4
Threadfunktion 
5
{
6
   while( immerundewig )
7
   {
8
       lokale Variable tmp;
9
       tmp = read();
10
       Mutex für Input setzen;
11
       Input = tmp;
12
       Mutex für Input freigeben;
13
   }
14
}

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.)

von LynUx (Gast)


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?

von Klaus W. (mfgkw)


Lesenswert?

> ... und es dabei crasht richtig?

Mit einem Mutex versucht man genau das zu verhindern.

Beispiel ohne:
1
int variable;
2
3
void thread1()
4
{
5
  while( true )
6
  {
7
    variable = variable + 3*( variable - 5 );
8
    printf( "%d\n", variable );
9
    sleep( 1 );
10
  }
11
}
12
13
void thread2()
14
{
15
  while( true )
16
  {
17
    if( es_ist_Zeit_fuer_einen_Neustart )
18
    {
19
      variable = 1;
20
    }
21
  }
22
}
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:
1
int variable;
2
bool flag_fuer_variable;
3
4
void thread1()
5
{
6
  while( true )
7
  {
8
9
    // Mutex setzen:
10
    while( flag_fuer_variable )
11
    {
12
      // nichts tun, bis keiner auf variable zugreift
13
    }
14
    flag_fuer_variable = true;
15
16
    // jetzt die variable verwenden (möglichst schnell, um den Mutex
17
    // bzw. das Flag nicht zu lange zu blockieren):
18
    variable = variable + 3*( variable - 5 );
19
    printf( "%d\n", variable );
20
21
    // Mutex wieder freigeben:
22
    flag_fuer_variable = false;
23
24
    // jetzt könnte wieder jemand anders auf variable zugreifen...
25
26
    sleep( 1 );
27
  }
28
}
29
30
void thread2()
31
{
32
  while( true )
33
  {
34
    if( es_ist_Zeit_fuer_einen_Neustart )
35
    {
36
      // Mutex setzen:
37
      while( flag_fuer_variable )
38
      {
39
        // nichts tun, bis keiner auf variable zugreift
40
      }
41
      flag_fuer_variable = true;
42
43
      // jetzt die variable verwenden (möglichst schnell, um den Mutex
44
      // bzw. das Flag nicht zu lange zu blockieren):
45
      variable = 1;
46
47
      // Mutex wieder freigeben:
48
      flag_fuer_variable = false;
49
    }
50
  }
51
}

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.

von LynUx (Gast)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.