Forum: PC-Programmierung Linux C: Serielle Daten loggen und verlorene Zeichen


von Hans Gruber (Gast)


Lesenswert?

Guten Tag!

Ich möchte an der seriellen Schnittstelle ankommende Daten empfangen und 
in eine Datei schreiben. Baudrate ist 38400. Es kommen ASCII-Strings mit 
einer Länge zwischen 100 und 150 Bytes und mit einer Frequenz von 5Hz 
an. Jeder String endet mit einem <cr><lf>.

Meiner Ansicht nach ist für meine Anwendung der Canonical Mode mit der 
Erkennung des <lf> am Ende des Strings geeignet.

Hier nun das Problem:
Es passiert immer wieder, das einzelne Zeichen des Strings "verloren" 
gehen und somit auch in der Logdatei fehlen. Das <cr><lf> am Ende jedes 
Strings ist immer vorhanden. Dieser Fehler passiert unregelmässig und 
kann von mir nicht reproduziert werden. Die fehlenden Zeichen sind schon 
bei der Ausgabe mit printf im Terminal sichtbar, also noch vor dem 
Schreiben in die Datei.


Original:
aabbccdd 123456 123456 123456 123456 123456 123456 123456 123456<cr><lf>

Aufgezeichnet:
aabbccdd 123456 12346 123456 123456 123456 123456 123456 123456<cr><lf>
                  ^^^


Hier mein C Programm, das weitgehend von Minicom-Beispiel übernommen 
wurde:
1
#include <sys/types.h>
2
#include <sys/stat.h>
3
#include <fcntl.h>
4
#include <termios.h>
5
#include <stdio.h>
6
7
#define BAUDRATE B38400
8
#define MODEMDEVICE "/dev/ttyS0"
9
#define _POSIX_SOURCE 1
10
11
#define FALSE 0
12
#define TRUE 1
13
14
volatile int STOP=FALSE; 
15
16
main()
17
{
18
  int fd, fh, c, res;
19
  struct termios oldtio,newtio;
20
  char buf[255];
21
  
22
  fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); 
23
  if (fd < 0) 
24
  {
25
    perror(MODEMDEVICE);
26
    exit(-1);
27
  }
28
29
  tcgetattr(fd,&oldtio);
30
  bzero(&newtio, sizeof(newtio));
31
32
  newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
33
  newtio.c_iflag = IGNPAR;
34
  newtio.c_oflag = 0;
35
  newtio.c_lflag = ICANON;
36
37
  newtio.c_cc[VINTR]    = 0;
38
  newtio.c_cc[VQUIT]    = 0;
39
  newtio.c_cc[VERASE]   = 0;
40
  newtio.c_cc[VKILL]    = 0;
41
  newtio.c_cc[VEOF]     = 4;
42
  newtio.c_cc[VTIME]    = 0;
43
  newtio.c_cc[VMIN]     = 1;
44
  newtio.c_cc[VSWTC]    = 0;
45
  newtio.c_cc[VSTART]   = 0;
46
  newtio.c_cc[VSTOP]    = 0;
47
  newtio.c_cc[VSUSP]    = 0;
48
  newtio.c_cc[VEOL]     = 0;
49
  newtio.c_cc[VREPRINT] = 0;
50
  newtio.c_cc[VDISCARD] = 0;
51
  newtio.c_cc[VWERASE]  = 0;
52
  newtio.c_cc[VLNEXT]   = 0;
53
  newtio.c_cc[VEOL2]    = 0;
54
55
  tcflush(fd, TCIFLUSH);
56
  tcsetattr(fd,TCSANOW,&newtio);
57
58
  if((fh=open("logfile.txt", O_RDWR | O_CREAT | O_APPEND)) == -1)
59
  {
60
    perror("Fehler bei open");
61
  }
62
63
64
  while (STOP==FALSE)
65
  {
66
    res = read(fd,buf,255); 
67
    buf[res]=0;
68
    printf("data:%sbytes:%d\n", buf, res);
69
    if (buf[0]=='z') STOP=TRUE;
70
71
    if((write(fh, &buf, res)) == -1)
72
    {
73
      perror("Fehler bei write");
74
    }
75
  
76
  }
77
78
  tcsetattr(fd,TCSANOW,&oldtio);
79
  
80
}

Ich hoffe jemand kann mir helfen das Problem zu lösen.

Grüsse,
Hans

von Andreas B. (andreas_b77)


Lesenswert?

Hans Gruber schrieb:
> char buf[255];
...
>     res = read(fd,buf,255);
>     buf[res]=0;

Hat vermutlich nichts mit dem Problem zu tun, ist aber schon mal ein 
Buffer Overflow falls tatsächlich mal 255 Zeichen in einem Rutsch 
gelesen werden.

Hans Gruber schrieb:
> if((write(fh, &buf, res)) == -1)
>     {
>       perror("Fehler bei write");
>     }

Und falls write() mal weniger als res Bytes schreibt, geht der Rest 
verloren. Außerdem sollte es entweder buf oder &buf[0] sein.

von Mar V. (marvol)


Lesenswert?

Hallo,

ich glaube das Problem hatte ich auch, allerdings weiß ich keine Details 
mehr. Allerdings habe ich das Lesen in einem Thread realisiert, weil ich 
mir nicht sicher war, ob diverse printf oder write mir die Schnittstelle 
blockieren:

..in irgendeiner Initialisierung:

  // Öffnen der seriellen Schnittstelle
  fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY );
  if (fd <0) {perror(MODEMDEVICE); exit(-1); }

  // Sichern der alten Konfiguration
  /// todo Die Konfiguration sollte beim Beenden wird überspielt werden.
  tcgetattr(fd,&oldtio); /* save current port settings */

  // Konfiguration der Schnittstelle
  bzero(&newtio, sizeof(newtio));
  newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
  newtio.c_iflag = IGNPAR;
  newtio.c_oflag = 0;

  // set input mode (non-canonical, no echo,...)
  newtio.c_lflag = 0;
  newtio.c_cc[VTIME]    = 1;   /* inter-character timer unused */
  newtio.c_cc[VMIN]     = 0;   /* blocking read until 5 chars received 
*/

  // Konfiguration übertragen
  tcflush(fd, TCIFLUSH);
  tcsetattr(fd,TCSANOW,&newtio);

..und dann in einem Thread, der alle 20ms aufgerufen wird...

  res = read(fd,buf,255);   /* returns after 5 chars have been input */
  buf[res]=0;               /* so we can printf... */

Spontan hätte ich das Flag [VTIME] in Verdacht:

http://www.unixwiz.net/techtips/termios-vmin-vtime.html

Vielleicht hilft es...

Gruß
Marvol

von Juergen R. (stumpjumper)


Lesenswert?

Andreas B. schrieb:
> Hans Gruber schrieb:
>> char buf[255];
> ...
>>     res = read(fd,buf,255);
>>     buf[res]=0;
>
> Hat vermutlich nichts mit dem Problem zu tun, ist aber schon mal ein
> Buffer Overflow falls tatsächlich mal 255 Zeichen in einem Rutsch
> gelesen werden.
>
> Hans Gruber schrieb:
>> if((write(fh, &buf, res)) == -1)
>>     {
>>       perror("Fehler bei write");
>>     }
>
> Und falls write() mal weniger als res Bytes schreibt, geht der Rest
> verloren. Außerdem sollte es entweder buf oder &buf[0] sein.

Hallo Andreas,
da Du eh schon in C programmierst erlaube ich mir Die nur einen Hinweis 
zu geben was Du mal recherchieren und testen könntest.
Schau Die mal die Funktion "select" in verbindung mit Filedescriptoren 
an.
Ich bin mir ziemlich sicher dass das dein problem löst.

von Hans Gruber (Gast)


Lesenswert?

Danke schonmal für die Antworten!

Ich hab den Code jetzt nochmal umgeschrieben und ein select mit 
eingebaut. Schön langsam komme ich dem Problem näher. Es passiert jetzt 
folgendes:

Die Strings werden in die Datei geschrieben. Nach einiger Zeit kann ich 
an der Console mit Minicom einen unvollständigen String entdecken, 
gefolgt von einem "1 input overrun(s)" oder auch "2 input overrun(s)".

Es scheint also so, das der Hardware Puffer überläuft.

Zur Hardware: Ein Embedded Linux System mit einem ATMEL AT91RM9200. An 
einem Seriellen Port kommen die Daten rein, am anderen hängt der 
Entwicklungsrechner an der Console.

Hier noch der neue Code:
1
#include <sys/time.h>
2
#include <sys/types.h>
3
#include <unistd.h>
4
#include <termios.h>
5
#include <fcntl.h>
6
7
#define MODEMDEVICE "/dev/ttyS0"    // setting port
8
#define BAUDRATE B38400          // setting baudrate
9
10
main()
11
{
12
  int fd1, res;
13
  fd_set readfs; /* file descriptor set */
14
  int maxfd; /* maximum file desciptor used */
15
  int loop=1; /* loop while TRUE */
16
  struct termios oldtio,newtio;
17
  char buf[255];
18
19
  fd1 = open(MODEMDEVICE, O_RDWR | O_NOCTTY); 
20
  if (fd1 < 0) 
21
  {
22
    perror(MODEMDEVICE);
23
    exit(-1);
24
  }
25
26
  tcgetattr(fd1,&oldtio);        // save current serial port settings
27
  bzero(&newtio, sizeof(newtio));    // clear struct for new port settings
28
29
  newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
30
  newtio.c_iflag = IGNPAR;
31
  newtio.c_oflag = 0;
32
  newtio.c_lflag = ICANON;
33
34
  newtio.c_cc[VINTR]    = 0;
35
  newtio.c_cc[VQUIT]    = 0;
36
  newtio.c_cc[VERASE]   = 0;
37
  newtio.c_cc[VKILL]    = 0;
38
  newtio.c_cc[VEOF]     = 4;
39
  newtio.c_cc[VTIME]    = 0;
40
  newtio.c_cc[VMIN]     = 1;
41
  newtio.c_cc[VSWTC]    = 0;
42
  newtio.c_cc[VSTART]   = 0;
43
  newtio.c_cc[VSTOP]    = 0;
44
  newtio.c_cc[VSUSP]    = 0;
45
  newtio.c_cc[VEOL]     = 0;
46
  newtio.c_cc[VREPRINT] = 0;
47
  newtio.c_cc[VDISCARD] = 0;
48
  newtio.c_cc[VWERASE]  = 0;
49
  newtio.c_cc[VLNEXT]   = 0;
50
  newtio.c_cc[VEOL2]    = 0;
51
52
  tcflush(fd1, TCIFLUSH);
53
  tcsetattr(fd1,TCSANOW,&newtio);
54
  
55
56
  int fh;
57
  
58
  if((fh=open("store.txt", O_RDWR | O_CREAT | O_APPEND)) == -1)
59
  {
60
    perror("Fehler bei open()");
61
  }
62
  
63
  /* maximum bit entry (fd) to test */
64
  maxfd = fd1+1;
65
66
  /* loop for input */
67
  while (loop)
68
  {
69
    FD_SET(fd1, &readfs);
70
    select(maxfd, &readfs, NULL, NULL, NULL);
71
72
    if (FD_ISSET(fd1, &readfs))
73
    {
74
      res = read(fd1,buf,254); 
75
      buf[res]=0;        // set end of string, so we can printf
76
      printf("data:%sbytes:%d\n", buf, res);
77
      
78
      write(fh, buf, res);
79
      if (write < 1)
80
      {
81
        perror("Fehler bei write");
82
      }
83
    }
84
  }
85
}

Welche Möglichkeiten hab ich noch das Problem zu lösen? Ich hab schon 
diverse Optionen und Kombinationen des File Descriptors versucht 
(O_NONBLOCK, O_NDELAY, V_TIME, V_MIN), alle ohne Erfolg.

Grüsse,
Hans

von Mar V. (marvol)


Lesenswert?

Hallo,

vielleicht liegt es garnicht an Deinem Programm, sondern an der 
Einstellung von Minicom. Schau mal unter 14.10...

http://www.linuxhaven.de/dlhp/HOWTO/DE-Modem-HOWTO-14.html

Ich nehme einfach mal an, dass Dein Entwicklungsrechner mit Linux läuft.

Gruß
Marvol

von Hans Gruber (Gast)


Lesenswert?

@Mar Vol!

An Minicom kann es nicht liegen. Dies wird ja nur zum Monitoring 
verwendet. Der Empfang und das Schreiben der Daten selbst findet auf dem 
Embedded Linux System. Minicom gibt mir nur zur Kontrolle die Strings 
über die Verbindung zur Console zum Embedded System aus.

Bin echt am Verzweifeln...


@all
Ich habe gestern noch ein paar Versuche gemacht. Ich hab mir ein 
Shell-Script gebastelt, das ebenfalls die Daten empfängt und dann in 
eine Datei schreibt. Hier konnte ich den gleichen Effekt beobachten.

Ich denke also das es an irgendeiner Einstellung für den Port selbst 
liegt. Gibt es eine Möglichkeit in C oder per Shell den Hardware-Puffer 
zu leeren, sobald ein (oder 5) vollständiger String ausgelesen und 
geschrieben wurde? Oder ist das rein vom Timing her nicht machbar? 
Folgende Idee:



Hans

von Andreas B. (andreas_b77)


Lesenswert?

Schreib doch mal in ein tmpfs statt auf einen Datenträger (vermutlich 
eine SD-Karte, oder?). Eventuell blockiert einfach das Schreiben den 
seriellen Empfang, weil der Treiber zu lange Interrupts blockiert und 
der UART viel zu wenig Pufferspeicher hat, um das zu überbrücken.

von Mar V. (marvol)


Lesenswert?

Andreas hat doch ein gute Idee, schreib die einen kleinen Interpreter:

1. Funktion: Minicom schickt die Daten an das Board.
2. Funktion: Das Board speichert die Daten im Speicher.
3. Funktion: Minicom frägt die Speicherdaten ab.

Beispiel mit Interpretation vom ersten Zeichen:

>s aabbccdd 123456 123456 123456 123456 123456 123456 123456 123456<cr><lf>
// Board sieht "s" und speichert den Rest in den Speicher.

>r
// Board sieht "r": Daten werden vom Board zurückgeliefert.

Einfach mal die Schreibzyklen weglassen.

Viel Glück
Marvol

von Juergen R. (stumpjumper)


Lesenswert?

Was ist denn die sendende Gegenstelle, kannst Du mit der ev. ein 
Handshake machen? Hintergedanke wäre dass Du das Handshake z.B. den CTS 
auf 0 setzt so bald Du mit der Verarbeitung von Daten beschäftigt bist 
und somit nicht wirklich empfangsbereit. Wenn der FIFO Puffer des 
Hardwarebausteins z.B 15 Bytes aufnimmt und das Handshake Signal schon 
nach 10 Bytes weggeht könnte ein oder mehrere noch gesendete Bytes immer 
noch vom FIFO aufgenommen werden. Ich muß aber dazu sagen dass ich es 
selbst noch nie gemacht habe die Variante mit den Descriptoren und 
select haben mir eigentlich immer geholfen.

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.