Forum: PC-Programmierung Linux, Puffer serielle Schnittstelle bzw pollen


von snowyrain (Gast)


Lesenswert?

Hallo,

ich habe Probleme bei dem Ansprechen einer seriellen Schnittstelle 
(Linux, SUSE 10.3).
Es handelt sich hier um eine Motoransteuerung, es kommen 6Byte von der 
Motorsteuerung und es werden zwei Byte zur Motorsteuerung gesendet 
(19200N2). Der Datenstrom von der Motoransteuerung kommt kontinuierlich.

Das Senden und Empfangen klappt erstmal zuverlässig. Allerdings bekomme 
ich immer 24Byte auf einmal. Ich polle auf die serielle Schnittstelle 
und meist liegen 0Byte im Empfangsspeicher und dann auf einmal 24Byte. 
Ich habe es schon den Realtime-Kernel, der die Timeslots verkleinert 
hat, versucht. Keine wirkliche Besserung (Hier liegen dann 16Byte im 
Speicher). Unter Dos klappt es hervorragend. ;-)

Kann ich irgendwie die serielle Schnittstelle Parametrieren, dass sie 
mir ab einer bestimmten Anzahl von empfangenen Bytes diese Daten zur 
Verfügung stellt?

Gruß

Samuel Schmidt

von yalu (Gast)


Lesenswert?

Das Problem bei den 6-Byte-Nachrichten besteht darin, dass der
16550-Schnittstellenbaustein mit seinen 16-Byte-Hardwarepuffer
normalerweise so konfiguriert ist, dass er erst bei fast vollem
Hardwarepuffer, d.h. bei einem Füllstand von 14 Bytes einen Interrupt
auslöst. Dies bedeutet, dass frühestens während des Eintreffens der
dritten 6-Byte-Nachricht ein Interrupt ausgelöst wird.

Um zu vermeiden, dass einzelne Bytes ewig ungelesen im Hardwarepuffer
verbleiben, fragt der Treiber zusätzlich alle etwa 10 ms den Puffer
ab. Dies bedeutet aber, dass kurze Nachrichten im ungünstigsten Fall
erst nach 10 ms an die Anwendung übergeben werden.

Will man nicht so lange warten, kann man das Low-Latency-Flag im
Treiber setzen. Dann wird die Abfrageperiode auf etwa 1 ms reduziert.
Die Reaktionszeiten werden dadurch besser, allerdings wächst die
Prozessorbelastung durch die häufigeren Abfragen. Das Low-Latency-Flag
kann entweder in der Anwendung oder mit dem Setserial-Tool gesetzt
werden:

  setserial /dev/ttyS0 low_latency

Eine andere Möglichkeit besteht darin, den Pufferfüllstand, bei dem
ein Interrupt ausgelöst wird, von 14 auf einen kleineren Wert
herabzusetzen. Der 16550 unterstützt Werte von 14, 8, 4 und 1. Wie man
dies auf anständige Weise umstellt, weiß ich nicht. Der Parameter
rx_trigger von setserial funktioniert für den 16550 leider nicht.

Ein Work-Around besteht darin, dem Treiber einen 8250 mit 1-Byte-
Puffer statt des 16550 vorzugaukeln. Dies geht mit

  setserial /dev/ttyS0 uart 8250

Damit wird schon beim ersten empfangenen Byte ein Interrupt ausgelöst,
so dass die Anwendung augenblicklich reagieren kann.

Ein Nachteil besteht in der höheren Anzahl ausgelöster Interrupts, die
bei großen übertragenen Datenmengen die Prozessorbelastung merklich
erhöhen können, was aber bei deiner Anwendung wahrscheinlich keine
große Rolle spielt.

Dadurch, dass durch den 8250-Trick der Empfangspuffer von 16 auf 1
Byte reduziert wird, steigt die Gefahr, dass eintreffende Datenbytes
verloren gehen, wenn die Interrupts nicht rechtzeitig bearbeitet
werden können, weil bspw. gleichzeitig viele andere Interrupts (Timer,
Ethernet usw.) eintreffen. Aber auch das sollte bei den heutigen
schnellen Prozessoren und üblichen Baudraten bis 115200 nicht zu
Problemen führen.

Ich würde an deiner Stelle einfach die beiden genannten Optionen
jeweils alleine und in Kombination ausprobieren und schauen, wie die
Ergebnisse ausfallen.

von snowyrain (Gast)


Lesenswert?

Hi,

vielen Dank für die ausführliche Antwort. Werde versuchen das morgen 
Abend umzusetzen und werden dann berichten ob es klappt.

Viele Grüße

Samuel

von Bernhard M. (boregard)


Lesenswert?

Warum die serielle Schnittstelle pollen? Das erzeugt nur unnötig 
Prozessorlast.
Unter Linux/Unix macht man sowas mit "select"...

von ragosti (Gast)


Lesenswert?

@Bernhard
[quote]Unter Linux/Unix macht man sowas mit "select"[\quote]

Äh, geht's etwas ausführlicher?
und wenn es bloss nen link ist, der einem weiterhilft - weil
"google doch mal nach SELECT"
iss glaub ich nicht wirklich witzig...

ragosti

von Bernhard M. (boregard)


Lesenswert?

Hi,

"select" ist ein Standard Unix Systemaufruf (mache mal "man select"). 
Damit kann man auf ein Ereignis bei einem (oder mehreren) Filedeskriptor 
warten, und gleichzeitig einen Timeout angeben (und nachdem in Unix 
praktisch alles ein File ist, geht der fast immer...).
Das Programm "steht" dann solange, bis ein Ereignis eintrifft oder der 
Timeout abgelaufen ist, dann kehrt der "select" Aufruf zurück.
Der Vorteil gegenüber polling ist, daß es keine Rechenzeit braucht, da 
der Kernel die Kontrolle in der Zeit hat.

Hier mal ein Beispiel aus einem Programm von mir, das mit einem 8051 
Kontrollerboard über RS232 kommuniziert und dabei einzelne Tastendrücke 
und die serielle Kommunikation abhandelt. D.h. in der Routine wartete 
die "select" auf V24 und auf Tastendruck und reagiert entsprechend....
Ich könnte Dir auch das ganze Programm schicken, das müsste ich nur erst 
etwas aufräumen (die ganzen "#if 0" Blöcke rausnehmen...).

Gruß,
Bernhard
1
static void do_v24 (int iFd)
2
{
3
    int                 iOldTimeout;
4
    int                 iStdio;
5
    int                 ok;
6
    int                 iMaxSelect;
7
    struct timeval      tTimeout;
8
    fd_set              fdSet;
9
    int                 iSelRet;
10
11
    /* "open" terminal -> get filedescriptor to it */
12
    gfStdio = fopen (ctermid (NULL), "r+");
13
    if (gfStdio == NULL) {
14
        fprintf (stderr, "\nCan not open terminal (errno: %s) !\n", strerror (errno));
15
        return;
16
    }
17
18
    iStdio = fileno (gfStdio);
19
    if (iStdio < 0)
20
    {
21
        fprintf (stderr, "Error: Invalid stream for terminal (errno %d: %s)!\n",
22
                 errno, strerror (errno));
23
        return;
24
    }
25
26
    tcgetattr (fileno (gfStdio), &gstTermOld);
27
28
    gstTerminal = gstTermOld;
29
    gstTerminal.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON);
30
31
    /* Set maximum wait time on input */
32
    gstTerminal.c_cc[VTIME] = 0;
33
34
    /* Set minimum bytes to read */
35
    gstTerminal.c_cc[VMIN]  = 0;
36
37
    tcsetattr (fileno (gfStdio), TCSAFLUSH, &gstTerminal);
38
39
    /* set new timeout on V24, we want responsive system */
40
    iOldTimeout = fnSetTimeout (iFd, 0);   /* no wait */
41
42
    ok = TRUE;
43
44
    do
45
    {
46
        FD_ZERO (&fdSet);
47
        FD_SET (iFd, &fdSet);
48
        FD_SET (iStdio, &fdSet);
49
        iMaxSelect = (iFd > iStdio) ? iFd : iStdio;
50
51
        /* -- set max. waittime -- */
52
        tTimeout.tv_sec  = 1;
53
        tTimeout.tv_usec = 500000;
54
55
        errno = 0;
56
57
        iSelRet = select (iMaxSelect + 1, &fdSet, NULL, NULL, &tTimeout);
58
59
        if (iSelRet > 0)
60
        {
61
            /* -- we got something on stdin ?? -- */
62
            if (FD_ISSET (iStdio, &fdSet))
63
            {
64
                /* stdin: someone hacked the keyboard */
65
                ok = fnHandleKeyboard (gfStdio, iFd);
66
            }
67
68
            /* -- we got something from serial line -- */
69
            if (FD_ISSET (iFd, &fdSet))
70
            {
71
                /* handle V24 input here */
72
                fnHandleInput (iFd, gfStdio);
73
            }
74
        }
75
        else if (iSelRet < 0)
76
        {
77
            /*
78
            ** Check if SIGINT was received. This is handled further down,
79
            ** so we ignore the error from select in that case.
80
            */
81
            if (errno != EINTR)
82
            {
83
                fprintf (stderr, "Error reading from select: (%d) %s\n",
84
                         errno, strerror (errno));
85
                break;
86
            }
87
        }
88
        else
89
        {
90
            /* just timeout */
91
            wasEsc = 0;
92
        }
93
    } while (ok && gRun);
94
95
    /* reset old timeout */
96
    fnSetTimeout (iFd, iOldTimeout);
97
98
    tcsetattr (iStdio, TCSAFLUSH, &gstTermOld);
99
    fclose (gfStdio);
100
}

von FBI (Gast)


Lesenswert?

Oder man benutzt poll ("man poll"), was im Gegensatz zu seinem Namen 
auch nicht pollt, sondern so ziemlich das selbe macht wie select.

CU

von Bernhard M. (boregard)


Lesenswert?

bzw. nur ein Wrapper um select ist....aber sobald man auf mehrere 
Ereignisse reagieren will braucht man eh select.

von FBI (Gast)


Lesenswert?

> Wrapper
??? Das ist aber schon ne Ewigkeit her.

> mehrere Ereignisse
??? Wo ist das Problem?

http://www.rt.com/man/poll.2.html

von Bernhard M. (boregard)


Lesenswert?

Irgendwie hatte ich die poll-syntax anders im Hinterkopf....
Na ja, nie selbst benutzt...
Hat das einen Vorteil gegenüber select, ausser daß der Code vielleicht 
kürzer wird? Wie ist das Verhalten bei Signalen? Gibts das auf allen 
Unixen (ich muß normalerweise konform zu Linux, HP-UX, OSF1 und AIX 
arbeiten)?

von Εrnst B. (ernst)


Lesenswert?

Bernhard M. wrote:
> bzw. nur ein Wrapper um select ist....aber sobald man auf mehrere
> Ereignisse reagieren will braucht man eh select.

Ist eher anders herum... poll unterstützt beliebig viele Handles auf die 
gleichzeitig gewartet werden kann, select ist da wg. den 
Bitlisten-Parametern meistens auf handles < 1024 beschränkt.

Und inzwischen (seit kernel 2.5.44) gibts noch "epoll", was nochmal ne 
ecke besser als poll sein soll...

von Bobby (Gast)


Lesenswert?

Wie kommt es zu den 24 Bytes, die zu Beginn
von snowyrain als Blockgröße genannt wurden ?

von Snowyrain (Gast)


Lesenswert?

Hallo

vielen Dank für die vielen Antworten. Ich habe bei dem jetzigen Problem 
mit "setserial /dev/ttyS0 low_latency" gearbeitet. Sehe allerdings ein, 
das ich mich ganz dringend mit dem Select Befehl auseinandersetzen muss. 
Das wichtigen Zeilen aus dem Beispiel von "Bernhard M." meine ich 
Verstanden zuhaben. Vielleicht werde ich meinen jetzigen Code auch 
umbauen, aber ich muss zwischendurch auch noch arbeiten ;-).

Die 24Byte kann ich mir auch nicht erklären. Mein Linux System ist 
allerdings sehr schlecht von mir administriert. Vielleicht greifen da 
andere Tasks zwischen. Ich habe keine wirkliche Ahnung von Linux und 
dessen genauen Interna.

Vielen Dank für super Antworten

Gruß

Snowyrain

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.