Forum: PC-Programmierung Com Port unter Linux auslesen für Verarbeitung in C


von Christian J. (Gast)


Lesenswert?

Hallo,

kann mir jemand eine Starthilfe geben, wie ich einen virtuellen USB Com 
Port am PC, den ich zum Zeitpunkt des Programmlaufes noch nicht kenne 
auslesen kann?

Konkreter formuliert:

Ich schreibe ein normales Konsolenprogramm mit dem GCC, welches die 
Ausgabe eines Arduino, der über einen USB Konverter an den PC 
angeschlossen ist auslesen soll als Textstrings, um diese dann zu 
verarbeiten. Mit minicom habe ich soweit allesüberprüft, dass es stimmt, 
was der Arduino sendet, jetzt geht es an die Auswertung des 
Datenstromes.

Stecke ich den USB Stecker rein kriege ich mit lsusb die Meldung:
1
 4697.072510] usb 7-2.1.4: new full-speed USB device number 7 using xhci_hcd
2
[ 4697.093256] usb 7-2.1.4: New USB device found, idVendor=067b, idProduct=2303
3
[ 4697.093260] usb 7-2.1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=0
4
[ 4697.093263] usb 7-2.1.4: Product: USB-Serial Controller
5
[ 4697.093265] usb 7-2.1.4: Manufacturer: Prolific Technology Inc.
6
[ 4697.139723] usbcore: registered new interface driver usbserial
7
[ 4697.140385] usbcore: registered new interface driver usbserial_generic
8
[ 4697.140522] usbserial: USB Serial support registered for generic
9
[ 4697.149413] usbcore: registered new interface driver pl2303
10
[ 4697.149512] usbserial: USB Serial support registered for pl2303
11
[ 4697.149546] pl2303 7-2.1.4:1.0: pl2303 converter detected
12
[ 4697.157138] usb 7-2.1.4: pl2303 converter now attached to ttyUSB0

d.h. das Ding hat das Device File /dev/ttyUSB0

Stecke ich noch einen rein kriegt deer ttyUSB1 usw.

Welche Routinen bietet mir Linux, damit ich das auslesen kann? Bzw. da 
ich nicht unbedingt pollen will vielleicht einen Interrupt, wenn zeichen 
da sind, eine Info wie viele usw.

De Arduino sendet unregelmässig Strings, die Buchstaben und Zahlen 
enthalten, Start ist eine Zeichenkette "AD:.....<Datensatz>" getrennt 
durch Komma und abgeschlossen mit \0.

Bei einer CD ROM bindet man einfach den cdrom.h ein, erhält eine 
Struktur für den Gerätetreiber und kann loslegen mit fest definierten 
Schlüsselbegriffen, die ich mir aus man ioctl_list geholt habe:
1
 <include/linux/cdrom.h>
2
3
       0x00005301   CDROMPAUSE        void
4
       0x00005302   CDROMRESUME       void
5
       0x00005303   CDROMPLAYMSF      const struct cdrom_msf *
6
       0x00005304   CDROMPLAYTRKIND   const struct cdrom_ti *
7
       0x00005305   CDROMREADTOCHDR   struct cdrom_tochdr *
8
9
       0x00005306   CDROMREADTOCENTRY   struct cdrom_tocentry *   // I-O
10
11
       0x00005307   CDROMSTOP        void
12
       0x00005308   CDROMSTART       void
13
       0x00005309   CDROMEJECT       void
14
       0x0000530A   CDROMVOLCTRL     const struct cdrom_volctrl *
15
       0x0000530B   CDROMSUBCHNL     struct cdrom_subchnl *            // I-O
16
       0x0000530C   CDROMREADMODE2   const struct cdrom_msf *          // MORE
17
       0x0000530D   CDROMREADMODE1   const struct cdrom_msf *          // MORE
18
       0x0000530E   CDROMREADAUDIO   const struct cdrom_read_audio *   // MORE
19
       0x0000530F   CDROMEJECT_SW    int
1
...
2
#define CDROM "/dev/cdrom"
3
...
4
static int open_cdrom (void) {
5
   int fd = open (CDROM, O_RDONLY | O_NONBLOCK);
6
   if (fd == -1) {
7
      if (errno == ENOMEDIUM)
8
         printf ("Keine CD im Laufwerk!\n");
9
      else
10
         perror ("Fehler bei open()");
11
      exit (EXIT_FAILURE);
12
   }
13
   return fd;
14
}

Brauche da nur etwas Starthilfe, danke!

von g457 (Gast)


Lesenswert?

> Brauche da nur etwas Starthilfe, danke!

man 3 termios

HTH

von PittyJ (Gast)


Lesenswert?

fopen("/dev/ttyUSB0","r");
fread(...);

geht nicht?

von Norbert (Gast)


Lesenswert?

PittyJ schrieb:
> fopen("/dev/ttyUSB0","r");
> fread(...);
>
> geht nicht?

und eventuell vorher mit stty -F /dev/ttyUSB0 <baudrate>
die Geschwindigkeit setzen.

Genauere Parameter bei:
(man 1 stty)

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

PittyJ schrieb:
> fopen("/dev/ttyUSB0","r");
> fread(...);

Naja, ein bissi komplizierter ist es schon... Locking sollte man auch 
machen.

Ich verwends in etwa so:
1
    if ((pid = serial_lock_port(Port)) != 0) {
2
        if (pid == -1)
3
            error("port %s could not be locked", Port);
4
        else
5
            error("port %s is locked by process %d", Port, pid);
6
        return -1;
7
    }
8
9
    fd = open(Port, O_RDWR | O_NOCTTY | O_NDELAY);
10
    if (fd == -1) {
11
        error("open(%s) failed: %s", Port, strerror(errno));
12
        serial_unlock_port(Port);
13
        return -1;
14
    }
15
16
    if (tcgetattr(fd, &portset) == -1) {
17
        error("tcgetattr(%s) failed: %s", Port, strerror(errno));
18
        serial_unlock_port(Port);
19
        return -1;
20
    }
21
22
    cfmakeraw(&portset);
23
    portset.c_cflag |= flags;
24
    cfsetispeed(&portset, Speed);
25
    cfsetospeed(&portset, Speed);
26
    if (tcsetattr(fd, TCSANOW, &portset) == -1) {
27
        error("tcsetattr(%s) failed: %s", Port, strerror(errno));
28
        serial_unlock_port(Port);
29
        return -1;
30
    }

Bei bedarf kann ich gerne meine "serial-mini-lib" (serial.c/serial.h) 
posten

von PittyJ (Gast)


Lesenswert?

Alternativ auch direkt an USB mit LibUsb

#define VENDOR_ID  9025
#define PRODUCT_ID  1
....

  s_DeviceHandle = libusb_open_device_with_vid_pid(
          NULL,
          VENDOR_ID,
          PRODUCT_ID);
...

  i_Result = libusb_bulk_transfer(
                s_DeviceHandle,  // device
           0x83,    // endpoint
          (unsigned char *) ca_QuestionBuffer,   // pointer to data 
buffer
    PACKET_LENGHT,    // length of data buffer
    &i_Transferred,  // int * transferred
    TIMEOUT    // timeout in milliseconds
    );

....

Das geht dann ohne tty Device.
Da muss man aber wissen, wie das auf USB-Ebene alles geht.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

PittyJ schrieb:
> Alternativ auch direkt an USB mit LibUsb
> Da muss man aber wissen, wie das auf USB-Ebene alles geht.

ich denke das kannst du vergessen - allein, wie wählst du eine Baud-rate 
aus, ohne die "Innereien" des USB-Konverters zu kennen? und wenn du von 
einem Konverter-Typ zum nächsten wechselst, kannst von vorne beginnen? 
Abgesehen davon, dass du vorher den Kernel-ttyUSB-Treiber abhängen 
müsstest.

Was glaubst du warum es das ttyUSB-Subsystem im kernel gibt?

von Moritz A. (moritz_a)


Lesenswert?

Ich muss ja zugeben dass ich es mir hier einfach mache und 
http://qt-project.org/doc/qt-5/qserialport.html verwende. Hat den 
Vorteil, dass es auch direkt unter Windows oder MacOsX funktioniert, 
falls es mal notwendig sein sollte. Ist aber halt c++.

Taugt aber auch so zum Spicken, welche Infos er aus dem System (zB 
sysfs) dahinter zieht werden: 
https://qt.gitorious.org/qt/qtserialport/source/4d887c58eddc922c8fbab37b54c2825e2a05bcea:src/serialport

: Bearbeitet durch User
von Christian J. (Gast)


Lesenswert?

Hallo,

danke, den Startpunkt habe ich ja nun gefunden :-) Auch hier:

http://www.tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html#AEN125

Dass es mit einem Serial.open(9600) wie bei Ard.... <Jehova!> nicht 
getan sein wird ist mir klar.

Nochwas: Wie wird das mit dem Interrupt gelöst, so dass ich nicht pollen 
muss, vor allem wegen Bufferüberlauf? Kann ich dem Kernel mitteilen, 
dass er die INTs auf meine eigene Routine umleiten soll? Komme aus der 
uC Welt, mit PC mache ich grad meine ersten erfahrungen unter Linux.

@Michael Reinelt: Kannst du die Lib vielleicht mal posten, wenn die die 
Basisfunktionen vielleicht schon gekapselt hat? Ich würde mir das ganze 
ohnehin auf 4,5 Routinen runterschreiben, denn mehr als initserial, 
putchar und getchar braucht es ja nicht.

Ach ja, das Device Locking! :-( Wenn ein Programm abstürzt gibt er das 
Device oft nicht frei, auch unter Windows ist das nervig bei der Ard... 
IDE. Startet man das Programm neu ist das Device dann blocked. Kann man 
es von der Konsole evtl unblocken?

von Michael R. (Firma: Brainit GmbH) (fisa)


Angehängte Dateien:

Lesenswert?

Christian J. schrieb:
> Nochwas: Wie wird das mit dem Interrupt gelöst, so dass ich nicht pollen
> muss, vor allem wegen Bufferüberlauf? Kann ich dem Kernel mitteilen,
> dass er die INTs auf meine eigene Routine umleiten soll? Komme aus der
> uC Welt, mit PC mache ich grad meine ersten erfahrungen unter Linux.

Das musst du nicht, das macht alles der Kernel für dich. Interrupts im 
Userspace gibts nicht (und wenn dann nur sehr sehr sehr selten)

Christian J. schrieb:
> @Michael Reinelt: Kannst du die Lib vielleicht mal posten, wenn die die
> Basisfunktionen vielleicht schon gekapselt hat? Ich würde mir das ganze
> ohnehin auf 4,5 Routinen runterschreiben, denn mehr als putchar und
> getchar braucht es ja nicht.

here you are...

'flags' für serial_open sind im normalfall "keine", also 0 (null)

von Christian J. (Gast)


Lesenswert?

Michael Reinelt schrieb:
> Das musst du nicht, das macht alles der Kernel für dich. Interrupts im
> Userspace gibts nicht (und wenn dann nur sehr sehr sehr selten)

Hi,

das verstehe ich nicht. Mein Programm muss doch auf externe Ereignisese 
reagieren. Da gibs sogar bei Windows Programmierung einen Handler, den 
ich umleiten kann auf meine eigene Routine.

Das Programm saust zwar nur einer Loop herum aber wie kriege ich denn 
mit, wenn was da ist?

PS: Die Lib ist ja bombig !!!!!

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Christian J. schrieb:
> Das Programm saust zwar nur einer Loop herum aber wie kriege ich denn
> mit, wenn was da ist?

"meine" serial_read() Funktion wartet auf Daten von der seriellen 
Schnittstelle, wenn keine kommen gibts einen Timeout-Fehler. Die 
solltest also in der Form nur verwenden, wenn du aktuell bzw. regelmäßig 
Daten erwartest.

Tust du das nicht, sondern die Daten kommen "irgendwann", müsstest du 
das anders aufbauen. serial_read() ist aber nicht allzu komplex, basis 
ist die read() funktion, und die liefert 0 wenns nix zu lesen gibt, <0 
im Fehlerfall, wobei du den EAGAIN-Fehler noch speziell behandeln 
solltest.

Wenn du nur auf die serielle warten wolltest, wäre select() dein Freund.

von PittyJ (Gast)


Lesenswert?

Michael Reinelt schrieb:
> ich denke das kannst du vergessen - allein, wie wählst du eine Baud-rate
> aus, ohne die "Innereien" des USB-Konverters zu kennen? und wenn du von
> einem Konverter-Typ zum nächsten wechselst, kannst von vorne beginnen?
> Abgesehen davon, dass du vorher den Kernel-ttyUSB-Treiber abhängen
> müsstest.
>
> Was glaubst du warum es das ttyUSB-Subsystem im kernel gibt?

Ich habe mit libusb die Kommunikation realisiert. Ich kann den 
kompletten Code schicken. Baudrate war einfach, kurz mit Wireshark 
nachgeschaut, wie das geht.
Mein alter Linux-PowerPC hat leider kein ttyUSB, da ging nur diese 
Lösung, um mit dem Arduino zu sprechen.
Läuft 24/7, das ganze Jahr.

von Christian J. (Gast)


Lesenswert?

Michael Reinelt schrieb:

> Christian J. schrieb:
>> @Michael Reinelt: Kannst du die Lib vielleicht mal posten, wenn die die
>> Basisfunktionen vielleicht schon gekapselt hat? Ich würde mir das ganze
>> ohnehin auf 4,5 Routinen runterschreiben, denn mehr als putchar und
>> getchar braucht es ja nicht.
>
> here you are...
>
> 'flags' für serial_open sind im normalfall "keine", also 0 (null)

Hi Michael,

da du den Code nicht kommentiert hast, könntest du evtl, wenn du Zeit 
hast in groben Zügen die init Routine mal erklären? Nur ganz grob. Da 
ist einiges mit Tempfiles, Strincopy Funktionen usw. und ich bin grad 
dabei das alles an meinen "Stil" anzupassen und einiges zu verändern.

Finde den Codeaufwand hoch für "Lock", daher frage ich:
1
char lockfile[256];
2
    char tempfile[256];
3
    char buffer[16];
4
    char *port, *p;
5
    int fd, len, pid;
6
7
    if (strncmp(Port, "/dev/", 5) == 0) {
8
  port = strdup(Port + 5);
9
    } else {
10
  port = strdup(Port);
11
    }
12
13
    while ((p = strchr(port, '/')) != NULL) {
14
  *p = '_';
15
    }
16
17
    snprintf(lockfile, sizeof(lockfile), LOCK, port);
18
    snprintf(tempfile, sizeof(tempfile), LOCK, "TMP.XXXXXX");
19
20
    free(port);
21
22
    if ((fd = mkstemp(tempfile)) == -1) {
23
  error("mkstemp(%s) failed: %s", tempfile, strerror(errno));
24
  return -1;
25
    }
26
27
    if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) {
28
  error("fchmod(%s) failed: %s", tempfile, strerror(errno));
29
  close(fd);
30
  unlink(tempfile);
31
  return -1;
32
    }
33
34
    snprintf(buffer, sizeof(buffer), "%10d\n", (int) getpid());
35
    len = strlen(buffer);
36
    if (write(fd, buffer, len) != len) {
37
  error("write(%s) failed: %s", tempfile, strerror(errno));
38
  close(fd);
39
  unlink(tempfile);
40
  return -1;
41
    }
42
    close(fd);
43
44
45
    while (link(tempfile, lockfile) == -1) {
46
47
  if (errno != EEXIST) {
48
      error("link(%s, %s) failed: %s", tempfile, lockfile, strerror(errno));
49
      unlink(tempfile);
50
      return -1;
51
  }
52
53
  if ((fd = open(lockfile, O_RDONLY)) == -1) {
54
      if (errno == ENOENT)
55
    continue;  /* lockfile disappared */
56
      error("open(%s) failed: %s", lockfile, strerror(errno));
57
      unlink(tempfile);
58
      return -1;
59
  }
60
61
  len = read(fd, buffer, sizeof(buffer) - 1);
62
  if (len < 0) {
63
      error("read(%s) failed: %s", lockfile, strerror(errno));
64
      unlink(tempfile);
65
      return -1;
66
  }
67
68
  buffer[len] = '\0';
69
  if (sscanf(buffer, "%d", &pid) != 1 || pid == 0) {
70
      error("scan(%s) failed.", lockfile);
71
      unlink(tempfile);
72
      return -1;
73
  }
74
75
  if (pid == getpid()) {
76
      error("%s already locked by us. uh-oh...", lockfile);
77
      unlink(tempfile);
78
      return 0;
79
  }
80
81
  if ((kill(pid, 0) == -1) && errno == ESRCH) {
82
      error("removing stale lockfile %s", lockfile);
83
      if (unlink(lockfile) == -1 && errno != ENOENT) {
84
    error("unlink(%s) failed: %s", lockfile, strerror(errno));
85
    unlink(tempfile);
86
    return pid;
87
      }
88
      continue;
89
  }
90
  unlink(tempfile);
91
  return pid;
92
    }
93
94
    unlink(tempfile);
95
    return 0;

Gruss,
Christian

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Hallo Christian,

erstmal zur Info: der Code ist mehr als 10 Jahre alt, kann gut sein dass 
man gewisse Dinge heute anders macht, oder es fertige Libs dafür gibt. 
Gab es damals nicht oder ich konnte sie nicht verwenden, da unter 
uClinux nicht/schlecht verfügbar. Der Code wird in der Form auch in 
lcd4linux verwendet, und verrichtet dort seit vielen Jahren klaglos 
seinen Dienst, also ganz falsch kann er nicht sein...

Ja, das Locking ist kompliziert, aber das passt so, weil Locking ist 
kompliziert :-) Ist halt "zu Fuß" ausprogrammiert (genauso wie eine 
saubere daemonize-Routine in lcd4linux)

Als erstes wird der Name des Lockfiles ermittelt. Wenn unter dev dann 
dev weglassen. Für Subdirectories (damals gab es noch sowas wie 
/dev/tty/USB0) wird der slash nach underscore gewandelt (tty/USB0 => 
tty_USB0)

"Erzeugt" wird das lockfile zuerst unter einem temporären Namen 
(tempfile), mkstemp() liefert dir einen garantiert unbenutzten 
temporären Namen.

Ins tempfile wird dann die aktuelle Process-Id geschrieben, und das 
Tempfile wieder geschlossen.

Danach wird versucht, vom Tempfile einen Hardlink auf das eigentliche 
Lockfile zu erzeugen. Da das eine atomare Operation ist, kann auch 
theoretisch keine Überschneidung mit einem anderen Prozess erfolgen 
(zwei Prozesse hätten dann dasselbe Lockfile).

Der Rename-Trick darf hier nicht verwendet werden, weil ich das alte 
lockfile noch gerne verwendet hätte (zumindest um die PID auszulesen und 
ins Logfile zu schreiben)

Wenn link() fehlschlägt, bedeutet dies dass das lockfile schon 
existiert. Dann wird das bereits bestehende Lockfile geöffnet und die 
PID des sperrenden Prozesses rausgelesen.

Ist diese PID unsere, hat der Programmierer einen Bock geschossen und 
lock_port zweimal aufgerufen ("already locked by us. uh-oh...")

mit kill (pid, 0) wird überprüft ob der sperrende Prozess noch 
existiert. Wenn ja, ist der Port tatsächlich von einem anderen Prozess 
gesperrt, und wir geben auf.

Wenn nicht, handelt es sich um ein "übriggebliebenes" Lockfile 
(vermutlich eh von uns selbst, von einem vorhergehenden Programmlauf, 
der "hart" abgebrochen wurde, und deshalb keine Gelegenheit mehr hatte 
sein Lockfile sauber zu löschen)

die restliche serial_open() funktionalität ist hier recht gut 
beschrieben: https://www.cmrr.umn.edu/~strupp/serial.html#2_5_2


Christian J. schrieb:
> und ich bin grad
> dabei das alles an meinen "Stil" anzupassen und einiges zu verändern.

Das kannst du natürlich gerne (übrigens, vergessen: der Code unterliegt 
der GPL), ich gebe nur zu bedenken: Der Code ist (mit lcd4linux) schon 
von vielen Augen reviewt worden, und sollte daher einigermaßen korrekt, 
robust, frei von Bugs und Race-Conditions sein. Jede (gröbere) Änderung 
birgt die Gefahr einer Regression...

Natürlich kannst du sagen "das ist alles viel zu kompliziert, das brauch 
ich alles nicht", spätestens wenn du parallel zu deiner Anwendung mit 
einem Terminal-Programm auf den Port gehst (um was auszuprobieren) bis 
du froh um funktionierendes, standardkonformes Locking.

Anyway, wenn du änderst, wäre ich an deinen Änderungen interessiert.

lg Michi

: Bearbeitet durch User
von Christian J. (Gast)


Lesenswert?

Hallo Michel,

erstmal danke für deine Mühe das alles zu erklären. Ich werde mich der 
Sache später nochmal widmen, da ich aktuell an einem Z80 Projekt dran 
bin für das diese Routinen gebraucht werden. Ich bn noch zu neu in der 
Linux programmierung um das alles auf einmal zu verstehen. Da muss ich 
mich erst allein mit befassen und bombensicheren Code muss ich nicht 
schreiben, da nur ich das progamm verwenen werde um mit dem Z80 zu 
reden.

So, erstmal hier linken, damit ich es auch wiederfinde.

Gruss,
Christian

von Blubber (Gast)


Lesenswert?

@fisa

Ich bedanke mich auch für die Erklärung, wie du das Lock erzeugst.
Ich dachte immer 'mv' würde reichen.
Da habe ich wieder was dazugelernt ;-)

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.