Serielle Schnittstelle Linux -- Baudrate?

Gast #1881439
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!
#1881940
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
}
Gast #1883752
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!
#1884000
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 :-)
Gast #1884026
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.
Gast #1884233
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.
#1884256
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.
#1884271
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.
Gast #1884387
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.
#1884400
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.
#1884448
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
}
Gast #1884690
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!
#1884698
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.
#1884742
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.)
#1884780
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren