Forum: PC-Programmierung Linux: Serielle Schnittstelle programmieren


von Tobias P. (hubertus)


Lesenswert?

Hallo

Ich habe mir einen GPIB-USB Adapter gekauft. Auf meinem Windows-Rechner, 
an dem ich zur Zeit sitze, funktioniert der super: er meldet sich als 
COM6 an und ich kann z.B. mit TeraTerm auf den GPIB Bus zugreifen und 
Kommandos absetzen und bekomme die erwarteten Antworten. Mit Linux (hier 
in der VM) geht es auch, wenn ich es so mache:

http://heliosoph.mit-links.info/gpib-on-debian-linux-the-easy-way/

ich will jetzt aber eine eigene Applikation schreiben, welche auf den 
GPIB zugreift, um ein bestimmtes Gerät zu steuern. Daher versuche ich 
jetzt in der Linux-VM (es soll später mal auf Linux laufen, habe aber 
grade keinen Linux Rechner zur Hand) ein C-Programm zu schreiben, 
welches den Port ansteuert, also Kommandos absetzt und die Antworten 
auswertet. Leider funktioniert das nicht wie erwartet, es werden immer 
wieder ganze Textteile verschluckt, und der Code hängt sich teilweise 
auf. Ist für mich nicht reproduzierbar, wieso. Kann einer einen tipp 
geben? bis jetzt sieht mein Code so aus:
1
#include <unistd.h>
2
#include <fcntl.h>
3
#include <termios.h>
4
#include <string.h>
5
#include <stdio.h>
6
7
void gpib_write(int fd, const char* str)
8
{
9
  write(fd, str, strlen(str));
10
}
11
12
int gpib_read(int fd, char* buf)
13
{
14
  int i;
15
  int len = 0;
16
  int rep = 1;
17
  while(rep)
18
  {
19
    i = read(fd, buf, 1);
20
    if(i==0)
21
    {
22
      rep = 0;
23
    }
24
    buf++;
25
    len++;
26
    *buf = 0;
27
  }
28
  return len;
29
}
30
31
int main(void)
32
{
33
  int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
34
  if(fd == -1)
35
  {
36
    printf("failed to open /dev/ttyS0");
37
  }
38
  
39
  struct termios options;
40
  tcgetattr(fd, &options);
41
  options.c_cflag = B9600 | CS8 | CLOCAL | CREAD;
42
  options.c_lflag &= ~ICANON; /* Set non-canonical mode */
43
  options.c_cc[VTIME] = 10; /* Set timeout of 10.0 seconds */
44
  tcflush(fd, TCIFLUSH);
45
  tcsetattr(fd, TCSANOW, &options);
46
47
  gpib_write(fd, "++ver\r\n");
48
  char testtest[1000];
49
  gpib_read(fd, testtest);
50
  printf("%s", testtest);
51
  
52
  close(fd);
53
  return 0;
54
}

Der ++ver-Befehl z.B. funktioniert, er liefert:
1
Prologix GPIB-USB Controller version 6.101

Aber z.B. der ++help Befehl funktioniert nur mit TeraTerm auf Windows: 
Er sollte
1
The following commands are available:
2
++addr 0-30 [96-126]  -- specify GPIB address
3
++addr                -- query GPIB address
4
++auto 0|1            -- enable (1) or disable (0) read-after-write
5
++auto                -- query read-after-write setting
6
++clr                 -- issue device clear
7
++eoi 0|1             -- enable (1) or disable (0) EOI with last byte
8
++eoi                 -- query eoi setting
9
++eos 0|1|2|3         -- EOS terminator - 0:CR+LF, 1:CR, 2:LF, 3:None
10
++eos                 -- query eos setting
11
++eot_enable 0|1      -- enable (1) or disable (0) appending eot_char on EOI
12
++eot_enable          -- query eot_enable setting
13
++eot_char <char>     -- specify eot character in decimal
14
++eot_char            -- query eot_char character
15
++ifc                 -- issue interface clear
16
++loc                 -- set device to local
17
++lon                 -- enable (1) or disable (0) listen only mode
18
++mode 0|1            -- set mode - 0:DEVICE, 1:CONTROLLER
19
++mode                -- query current mode
20
++read [eoi|<char>]   -- read until EOI, <char>, or timeout
21
++read_tmo_ms 1-3000  -- set read timeout in millisec
22
++read_tmo_ms         -- query timeout
23
++rst                 -- reset controller
24
++savecfg 0|1         -- enable (1) or disable (0) saving configuration to EPROM
25
++savecfg             -- query savecfg setting
26
++spoll               -- serial poll currently addressed device
27
++spoll 0-30 [96-126] -- serial poll device at specified address
28
++srq                 -- query SRQ status
29
++status 0-255        -- specify serial poll status byte
30
++status              -- query serial poll status byte
31
++trg                 -- issue device trigger
32
++ver                 -- query controller version
33
++help                -- display this help

liefern, ich bekomme aber nur immer einzelne Zeilen oder garnichts. Was 
mache ich falsch?

von Tux (Gast)


Lesenswert?

Vielleicht ist deine read-Methode zu langsam und der Puffer der 
Schnittstelle läuft über. Warum liest du immer nur 1 Byte? Lies größere 
Blöcke so wie es sich gehört.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Tux schrieb:
> Lies größere Blöcke so wie es sich gehört.
Wo ist das eingemeisselt, das sich das so gehoert? Das kann jeder 
benutzen, wie er lustig ist, deswegen gibt es ja die moeglichkeit, 
anzugeben, wie viel man lesen moechte. Es gibt in diesem falle kein 
"richtig" oder "falsch" und somit auch kein "wie es sich gehoert!".

Tobias Plüss schrieb:
> int gpib_read(int fd, char* buf)
> {
>   int i;
>   int len = 0;
>   int rep = 1;
>   while(rep)
>   {
>     i = read(fd, buf, 1);
>     if(i==0)
>     {
>       rep = 0;
>     }
>     buf++;
>     len++;
>     *buf = 0;
>   }
Du solltest lieber gucken, ob ein bestimmtes Zeichen empfangen wurde, 
dass das Ende der Uebertragung signalisiert, z.B. das Null-Byte, ein 
Semikolon, oder was auch immer die gegenstelle halt als letztes Zeichen 
sendet. Nur weil read eine 0 zurueck gibt, heisst das ja nicht, das die 
uebertragung vorbei ist.
1
Upon successful completion, these functions shall return a non-negative integer indicating
2
the number of bytes actually read. Otherwise, the functions shall return -1 and set errno
3
to indicate the error.
Sprich: wenn read eine 0 liefert, heisst das lediglich, das gerade keine 
Daten gelesen wurden, vielleicht weil noch keine neuen Daten da waren.

Meine Konfiguration der Seriellen schnittstelle sieht etwas anders aus, 
als deine:
1
#include <fcntl.h>
2
#include <stdint.h>
3
#include <stdlib.h>
4
#include <string.h>
5
#include <termios.h>
6
#include <unistd.h>
7
8
struct termios config;
9
10
int fd = open("/dev/deinDevice", O_RDWR | O_NOCTTY | O_NONBLOCK);
11
12
memset(&config, 0, sizeof(config));
13
config.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
14
config.c_oflag = 0;
15
config.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
16
config.c_cflag &= ~(CSIZE | PARENB);
17
config.c_cflag |= CS8;
18
config.c_cc[VMIN]  = 1;
19
config.c_cc[VTIME] = 0;
20
21
cfsetispeed(&config, B115200);
22
cfsetospeed(&config, B115200);
23
24
char c;
25
while(c != ';')
26
{
27
  if( read(fd, &c, 1) > 0 )
28
  {
29
    // mach was mit dem Zeichen
30
  }
31
}
Mit der Konfiguration und dem Lesen hab ich unter Linux keine Probleme.

Schau mal hier:
https://en.wikibooks.org/wiki/Serial_Programming/Serial_Linux
https://en.wikibooks.org/wiki/Serial_Programming/termios

von Tobias P. (hubertus)


Lesenswert?

Hi Kaj,

habe das bei mir ausprobiert. Der Code funktioniert schon, aber er hat 
eigentlich das selbe Problem wie mein Code:
ich rufe zuerst write auf und sende einen Befehl, und auf der nächsten 
Zeile will ich mit read die Empfangsdaten holen. Aber die werden mir 
einfach zerstückelt, ich krieg das nicht gebacken hier. Entweder müsste 
man noch was  konfigurieren oder aber es geht so nicht. Gibt es 
allenfalls eine Library?

von Karl H. (kbuchegg)


Lesenswert?

Tobias Plüss schrieb:
> Hi Kaj,
>
> habe das bei mir ausprobiert. Der Code funktioniert schon, aber er hat
> eigentlich das selbe Problem wie mein Code:
> ich rufe zuerst write auf und sende einen Befehl, und auf der nächsten
> Zeile will ich mit read die Empfangsdaten holen. Aber die werden mir
> einfach zerstückelt,

Genau damit musst du rechnen.
Ein read kann nur das aus dem Buffer der Seriellen Schnittstelle 
auslesen, was schon an Daten eingelaufen ist!

Dein Rechner arbeitet aber um Zehnerpotenzen schneller als die 
Datenübertragung dauert. D.h. du wirfst einen Brief in den Postkasten, 
auf dass ihn der Briefträger mitnimmt und 10 Sekunden nach dem Einwerfen 
siehst du im Briefkasten nach, ob schon Antwort da ist. Das kann nicht 
funktionieren! Du musst dem Zeug schon Zeit geben, damit
* dein Kommando an das Gerät übertragen wird
* das Gerät das Kommando ausführen kann
* die Antwort auch zurückünermittelt werden kann
* damit rechnen, dass du nicht die komplette Antwort in einem einzigen
read Aufruf kriegst.

> ich krieg das nicht gebacken hier.

Wie schliesst denn das Gerät jede Antwort ab?
Da hier ASCII im Spiel ist, würde ich mal vermuten, dass das Gerät eine 
Angtwort mit einem \r\n abschliesst, genauso wie du dein Kommando mit 
einem \r\n abschliessen musstest.

Du musst also solange lesen (*), bis du ein \n als Antwort kriegst. 
Selbst wenn da read Aufrufe zwischendurch dabei waren, die 0 Bytes 
gelesen haben. Die haben deswegen 0 Bytes gelesen, weil die Antwort oder 
Teile davon noch unterwegs waren (bzw, noch gar nicht vom Gerät 
abgesendet wurden) zum Zeitpunkt an dem du read aufgerufen hast.

> Entweder müsste
> man noch was  konfigurieren

Das hat nichts mit der Konfiguration zu tun, sondern damit das Dinge 
Zeit brauchen und dein Computer rasend schnell dein Programm ausführen 
kann. Viel schneller als Daten übertragen werden können.


> oder aber es geht so nicht. Gibt es
> allenfalls eine Library?

Kopfschüttel.
Denkst du nicht, das es besser wäre die Dinge zu verstehen und richtig 
zu programmieren, anstatt immer nach einer Lib zu rufen? Das ist alles 
kein Hexenwerk sondern das kleine EinmalEins der Datenkommunikation. 
Wenn du so willst, das 'Seepferdchen' in der Ansteuerung externer 
Geräte.


(*) und das jeweils Gelesene natürlich zu einem komplettn String 
zusammensetzen.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

1
int gpib_read(int fd, char* buf)
2
{
3
  int i;
4
  int len = 0;
5
  char c = ' ';
6
7
  do {
8
    i = read(fd, &c, 1);
9
    if( i != 0 ) {
10
      *buf++ = c;
11
      len++;
12
    }
13
  } while( c != '\n' );
14
15
  *buf = '\0';
16
17
  return len;
18
}

Zur Fehlerbehandlung solltest du noch die Länge des Buffers in die 
Funktion mit reingeben und in der Schleife potentielle Array Overflows 
abfangen.

von Tobias P. (hubertus)


Lesenswert?

Hallo zusammen

deinen Einwand, Karl Heinz, dass es besser wäre, die Dinge zu verstehen, 
teile ich natürlich absolut. Ich selbst bin auch jeweils dieser Ansicht. 
In diesem Fall ist jedoch die Serielle Schnittstelle nur ein 
Nebenkriegsschauplatz, es geht gar nicht um diese, sondern es geht halt 
dann darum, dass ich die GPIB-Kommunikation darüber abwickeln will. :-)
Aber du hast schon recht.

Ich schau mir deinen Code gleich mal durch. Das dumme beim GPIB:
ich habe bisher noch kein System erkennen können, wie das Gerät die 
Antwort abschliesst. Einige Befehle liefern in der Tat nur eine einzige 
Zeile zurück, die mit CR + LF endet, und einige spezielle Befehle 
liefern aber mehrere Strings zurück. Wenn ich z.B. vom Oszilloskop die 
Messpunkte über den GPIB ausgeben lasse, dann kommt da natürlich eine 
zweispaltige Liste mit der Zeit in der ersten und der Spannung in der 
zweiten Spalte, und das über mehrere Zeilen.

von Karl H. (kbuchegg)


Lesenswert?

Tobias Plüss schrieb:

> Messpunkte über den GPIB ausgeben lasse, dann kommt da natürlich eine
> zweispaltige Liste mit der Zeit in der ersten und der Spannung in der
> zweiten Spalte, und das über mehrere Zeilen.

Wobei jede Zeile wieder mit einem CR+LF abgeschlossen ist.
Das trickreiche wird jetzt sein: woher wissen wir, wieviele Zeilen das 
sind?

Gibt es da keine Header Zeile, in der die Anzahl der folgenden 
Tabellen-Zeilen drinn steht?
Manchmal gibt es bei Geräten auch ein eigenes Kommando, mit dem man im 
Vorfeld abfragen kann, wie gross die eigentliche Antwort dann sein wird.


Eine Zeile lesen zu können ist ja erst der Anfang. Niemand sagt, dass 
das bereits alles wäre um eine Kommunikation zustande zu kriegen.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Übrigens sollte man beim read einen Return Wert von -1 auch extra 
behandeln. Denn
1
The function returns the number of bytes read. On end-of-file, 0
2
is returned, on error it returns -1, setting errno to indicate the
3
type of error that occurred.

Lass dich nicht von 'End Of File' verwirren. Das ist für echte Files 
gedacht. Bei einer Seriellen abstrahiert das zu einem einfachen: Ich hab 
momentan nichts mehr, was man auslesen könnte.
'Momentan' bedeutet aber nicht, dass das ein paar Millisekunden später 
nicht ganz anders sein könnte.

von Tobias P. (hubertus)


Lesenswert?

Hmm ich stelle grade fest, dass das Problem vielleicht woanders liegt. 
Ob ich eventuell an meinem BS noch was konfigurieren muss? es scheint 
generell Probleme zu huaben mit USB/Seriell Wandlern.

Interessant: auf dem Büro-Notebook läuft alles, unter Windows. Dort in 
der Linux-VM läuft es halbwegs. Jetzt bin ich zu Hause an meinem 
Privat-Notebook, und dort geht es gar nicht, nicht mal mit Matlab mit 
den Befehlen serial(), fprintf() und fgets(). Irgendwas ist mit dieser 
Schnittstelle faul.

von Gerd B. (bertr2d2) Benutzerseite


Lesenswert?

Hallo,

warum benutzt Du eigentlich 'non-canonical' Modus wobei doch
die Daten zeilenbasiert sind ?
Das Lesen einzelner Zeichen ist zudem ineffizient - es ist besser
so viele Zeichen auf einmal zu Lesen wie vorhanden sind. Man sollte
nicht gegen die Optimierungen des Betriebsystems arbeiten.

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

BTW: Richard Stevens hat ein sehr empfehlenswertes Buch geschrieben
in dem er u.a. auf die verschiedenen Methoden zum Lesen von Devices
eingeht:
"Advanced Programming in the UNIX Environment"

Gruß

Gerd

von A. H. (ah8)


Lesenswert?

Kaj G. schrieb:
1
int fd = open("/dev/deinDevice", O_RDWR | O_NOCTTY | O_NONBLOCK);
2
3
...
4
5
char c;
6
while(c != ';')
7
{
8
  if( read(fd, &c, 1) > 0 )
9
  {
10
    // mach was mit dem Zeichen
11
  }
12
}

Warum öffnest Du denn den Port im non-blocking Mode, nur um dann in 
einer Schleife ständig abzufragen, ob ein Zeichen da ist? Das ist reine 
Beschäftigungstherapie für den Rechner und dürfte Dir völlig 
unnötigerweise eine CPU-Auslastung von 100% bescheren (zumindest auf dem 
Kern, auf dem der Prozess läuft). Was spricht dagegen, die CPU für 
andere Dinge abzugeben, während man wartet. Nichts anderes heißt 
'blocking'.

Ist das Fehlen der Fehlerbehandlung beabsichtigt? Mir ist keine Stelle 
im Manual bekannt, die garantiert, dass der Puffer im Fehlerfall noch 
einen definierten Inhalt hat. Ihn auszuwerten ohne auf Fehler geprüft zu 
haben würde ich daher als Fehler betrachten, mindestens aber als 
schlechten Stil.

von mar IO (Gast)


Lesenswert?

Kaj G. schrieb:
1
memset(&config, 0, sizeof(config));
2
3
  config.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
4
  config.c_oflag = 0;
5
  config.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
6
  config.c_cflag &= ~(CSIZE | PARENB);
7
8
config.c_cflag |= CS8;
9
config.c_cc[VMIN]  = 1;
10
11
  config.c_cc[VTIME] = 0;

Du hast wohl kein Vertrauen in memset()...

von Tobias P. (hubertus)


Lesenswert?

Hallo zusammen

ganz vergessen, das hier zu schreiben. Es funktioniert jetzt, eigentlich 
ganz zuverlässig. Ich konnte meine Messgeräte ganz ordentlich ansteuern.

http://www.kooltek.net/programmieren/gpib-unter-linux

Gruss

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.