Forum: PC-Programmierung Java EventListener zu langsam für RS232?


von Christian B. (mech)


Lesenswert?

Hallo,

ich habe ein altes DOS Programm nach Java portiert. Das Programm soll 
ein  ein Taktsignal von einem RDS Empfänger (1,187 kbit/s) über die 
RS232 lesen und gleichzeitig einzelne Bits senden. Dazu speise ich das 
Taktsignal über den Carrier Detect (Pin1, DCD) des COM Ports ein.
Aber der EventListener überprüft den COM Port scheinbar nur etwa 10 mal 
pro Sekunde (auf einem Core2 E8500!) Das ist viel zu langsam.
Ich verwende die Bibliothek RXTX für Java.
Das DOS Programm auf einem 386er, welches in Turbopascal programmiert 
ist, schafft das ohne Probleme!
Handelt es sich hier um ein Windows Problem (Handler, Threads...), oder 
sollte ich besser C++ verwenden?


Kleiner Auszug aus dem Java Code:
1
serialPort.notifyOnCarrierDetect(true); //wenn das Bit am DCD wechselt -> event
2
serialPort.addEventListener(new mySerialPortEventListener());
3
4
class mySerialPortEventListener implements SerialPortEventListener {
5
   public void serialEvent(SerialPortEvent event) { 
6
        //System.out.println("DCD changed");
7
        i++;
8
        if(i%1000==0)System.out.println("i= "+i); //i ist nach 1 Sekunde erst bei 10 angekommen... sollte aber bei 1187 sein! 
9
    }
10
}

VG

Christian

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Christian B. schrieb:
> Handelt es sich hier um ein Windows Problem (Handler, Threads...), oder
> sollte ich besser C++ verwenden?
Echte RS232 oder USB Wandler? Diese sind nämlich an das Datenprotokoll 
von USB gebunden was nur (etwa) alle 10ms eine Datenpaket zulässt.

Christian B. schrieb:
> Das DOS Programm auf einem 386er, welches in Turbopascal programmiert
> ist, schafft das ohne Probleme!
Unter DOS ist man halt auch allein auf der Welt und damals war sowieso 
alles besser ;)

von Peter (Gast)


Lesenswert?

Christian B. schrieb:
> Handelt es sich hier um ein Windows Problem (Handler, Threads...), oder
> sollte ich besser C++ verwenden?

ja und nein, bei Dos hattest du die CPU für dich. Jetzt hast du ein 
Betriebssystem dazwischen welches die CPU aufteilt. Dir wirst es wohl 
nicht schaffen mehr als 1000mal pro Sekunde etwas von einem IO-Pin 
gleichmäßig einzulesen.
Wenn es überhaupt geht, dann nur wenn du einen Treiber für das System 
schreibst (dann aber auch nicht mit C++ sondern mit C).

Sinnvoller ist es aber das ganze extern über einen kleinen µC zu 
erledigen. Dieser kann das ganze auch mit 8Mhz ohne Probleme, er gibt 
dann die Daten über Seriell an den PC (das geht dann auch mit einem USB 
Adapter)

von Christian B. (mech)


Lesenswert?

Läubi .. schrieb:
> Echte RS232 oder USB Wandler? Diese sind nämlich an das Datenprotokoll
> von USB gebunden was nur (etwa) alle 10ms eine Datenpaket zulässt.
>
> Unter DOS ist man halt auch allein auf der Welt und damals war sowieso
> alles besser ;)

Es ist ein echter RS232.
Ich bin jetzt schon schockiert, dass man mit modernen Betriebssystemen 
so viel schlechter fährt als damals mit DOS...
Gibt es heutzutage ein "modernes" DOS, womit man die CPU für sich allein 
hat? Irgend eine schmalspur Linux Distribution o. Ä.

von Peter (Gast)


Lesenswert?

Christian B. schrieb:
> Ich bin jetzt schon schockiert, dass man mit modernen Betriebssystemen
> so viel schlechter fährt als damals mit DOS.

Du kannst auch mit einen Akkuschrauber kein Nagel reindrehen, auch wenn 
er noch so modern ist. Ein PC mit seinen aktuellen Schnittstellen und 
Betriebssystem ist für den Zweck ebend nicht das richtige.

Warum willst du es nicht über einen externen µC machen?

Wenn du es unbedingt am PC machen willst, kannst du dir ja ein 
Logic-Analyser kaufen und dann damit die Daten auswerten.

von Christian B. (mech)


Lesenswert?

Die uC Variante ist halt mit mehr Aufwand verbunden, als etwas Code zu 
tippen. Und ich brauche 5 Stück davon. Aber 5 uCs sind natürlich 
billiger, als 5 386er kaufen... ich bin gerade vom uC überzeugt worden 
;)

von qwertzuiopü (Gast)


Lesenswert?

wie wärs wenn du im event-listener nur einen buffer füllst und bei jedem 
10. oder 100. event eine weiterverarbeitung anstößt?

von Christian B. (mech)


Lesenswert?

qwertzuiopü schrieb:
> wie wärs wenn du im event-listener nur einen buffer füllst und bei jedem
> 10. oder 100. event eine weiterverarbeitung anstößt?

Das grundlegende Problem ist, dass ich eine Ausgabetaktrate von 1,1875 
kbit/s verwirklichen muss, welche kein Standard für RS232 ist. Die 
Bitdauer bis zum jeweils nächsten Bit ist 0,842 Millisekunden, aber ein 
Thread.sleep() akzeptiert nur ganze Millisekunden. Oder gibt es da 
andere Möglichkeiten?
Denn die Ausgabe über Setzen von Bits funktioniert schnell genug (nur 
der EventListener ist zu langsam).

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Christian B. schrieb:
> Denn die Ausgabe über Setzen von Bits funktioniert schnell genug (nur
> der EventListener ist zu langsam).
Hast du das gemessen oder glaubst du das nur?

Du kannst einfach unter Windows dein gewünschtes Zeitverhalten nicht 
erreichen, eventuell mit einem (Linux)RTOS, der Empfang der Daten ist 
einfach eine DLL welche in C oder C++ geschrieben ist, trotzdem bringt 
dir das nix, da du nicht die ungeteilte Aufmerksamkeit des 
Betriebssystems erhälts. Gerade das auslesen von Statusbits ist 
sicherlich nicht dafür ausgelegt dermaßen häufige Statuswechsel zu 
detektieren, eher im Gegenteil.

Christian B. schrieb:
> Denn die Ausgabe über Setzen von Bits funktioniert schnell genug
definiere Schnell...

von Christian B. (mech)


Lesenswert?

Läubi .. schrieb:
> Hast du das gemessen oder glaubst du das nur?

Wenn man einen Zähler einprogrammiert, sieht man, dass etwa 20 kbit/s am 
Com Port möglich sind.

Ich habs endlich geschafft. Es funktioniert, wenn man einen eigenen 
Thread schreibt, der mit der Methode serialPort.isCD() in einer 
Endosschleife den Zustand des DCD überwacht. Die Abtastrate beträgt etwa 
20.000mal pro Sekunde, was für mein Problem mehr als ausreichend ist.

Kurzer Auszug des Thread Codes:
1
class send extends Thread{
2
    public void run(){
3
      while (!isInterrupted()){
4
      merken  = serialPort.isCD();
5
    // noch mehr Code
6
    }
7
}
Der "Nachteil" ist natürlich, dass man die CPU jetzt wie gewünscht für 
sich allein hat, sprich 99% Auslastung ;)
Eine Dauerlösung ist das somit nicht.

von Markus (Gast)


Lesenswert?

Würdest du mal dein Programm komplett hochladen? würde mich mal 
interessieren.

von Christian B. (mech)


Lesenswert?

Markus schrieb:
> Würdest du mal dein Programm komplett hochladen? würde mich mal
> interessieren.
Gern. Hier ist die DataOut Klasse, welche den RDS Code generiert und 
sendet
1
package RDS_Package;
2
3
import gnu.io.CommPortIdentifier;
4
import gnu.io.SerialPort;
5
6
7
public class DataOut{
8
  int j = 0; //Hilfsvariablen für Taktsteuerung
9
  int reg, x; //Daten von 32 Bit laenge
10
  int[] code;
11
  SerialPort serialPort;
12
  send sendThread;
13
  
14
  
15
  /*** Konstruktor ***/
16
  public DataOut(int[] data){
17
    code = coding(data);
18
    try {
19
            CommPortIdentifier myPortIdentifier = CommPortIdentifier.getPortIdentifier(JDialog_Hauptfenster.portName);
20
            serialPort = (SerialPort)myPortIdentifier.open("", 500);
21
            serialPort.disableReceiveTimeout();    
22
            
23
        } catch (Exception e) {
24
            e.printStackTrace();
25
            System.exit(1);
26
        }
27
        sendThread = new send();
28
        sendThread.start();
29
  }   
30
  
31
  
32
  //Methode stop schliesst den COM-Port
33
  public void stopp(){
34
    sendThread.interrupt();
35
    serialPort.close();
36
37
  }
38
  
39
  //Methode coding erzeugt aus data[] den code[], der über die serielle Schnittstelle gesendet wird
40
  public int[] coding(int[] data){
41
    
42
    int[] code = new int[16];
43
    int h1, h2, h3;
44
    
45
    // *** Generatormatrix *** Aufbau: (16x0 | Prüfmatrix A | 6x0)
46
    int[] matrix = new int[16];          
47
    matrix[15] = 0x00001DC0;
48
    matrix[14] = 0x0000B9C0;
49
    matrix[13] = 0x0000EBC0;
50
    matrix[12] = 0x0000C2C0;
51
    matrix[11] = 0x0000D640;
52
    matrix[10] = 0x0000DC00;
53
    matrix[9]  = 0x00006E00;
54
    matrix[8]  = 0x00003700;
55
    matrix[7]  = 0x00001B80;
56
    matrix[6]  = 0x00000DC0;
57
    matrix[5]  = 0x0000B1C0;
58
    matrix[4]  = 0x0000EFC0;
59
    matrix[3]  = 0x0000C0C0;
60
    matrix[2]  = 0x0000D740;
61
    matrix[1]  = 0x0000DC80;
62
    matrix[0]  = 0x00006E40;
63
    
64
    int[] offsetwort = new int[4]; // *** Offsetworte ***
65
    offsetwort[0] = 0x00003F00; // A
66
    offsetwort[1] = 0x00006600; // B
67
    offsetwort[2] = 0x0000D400; // C`
68
    offsetwort[3] = 0x00006D00; // D
69
    
70
    
71
    
72
    for (int y = 0; y<16; y++){
73
      h3 = 0x00000000;            //Hilfsregister initialisieren
74
      h2 = 0x00010000;
75
      for (int x = 0; x<16; x++){
76
        h1 = data[y] & h2;          //Bit ausmaskieren
77
        if (h1 != 0) h3 = matrix[x] ^ h3;  // ^ ist XOR 
78
        h2 = h2 << 1;            //nächst groesseres Bit (Rechtsshift ist in Java unbrauchbar, wegen Vorzeichenmitnahme)
79
      }
80
      h3 = h3 ^ offsetwort[y%4];        //Offset hinzufügen
81
      code[y] = data[y] ^ h3;          //Daten & Code & Offset
82
    }
83
    return code;
84
  }
85
  
86
  //Klasse send ueberwacht Pin1 (Carrier Detect, DCD) des Com Ports auf den Takt und sendet den code  
87
  class send extends Thread{
88
    public void run(){
89
      System.out.println("sendThread gestartet");
90
      
91
      //Hilfsvariablen fuer Taktwechselerkennung
92
        boolean merkenA  = true;  
93
        boolean merkenB  = true;
94
        boolean schalter = true;
95
      
96
      while (!isInterrupted()){            //Endlosschleife, bis der Thread gestoppt
97
        schalter = schalter^true;          //Bit vertauschen
98
        if (schalter == true)  merkenA  = serialPort.isCD();
99
        else           merkenB  = serialPort.isCD();
100
        if(merkenA^merkenB && serialPort.isCD()){//Taktwechsel erkannt && Zustand = 1
101
              
102
             if (j == 415 ){
103
              j=0;                //Zaehler auf 0 setzen, da code durchlaufen wurde 
104
              serialPort.setDTR(false);    //Triggerausgabe
105
            }
106
            
107
            if (j%26 == 0){      //neuer Block
108
              reg = code[j/26];  //neuen Block kopieren
109
            }
110
            
111
            x = reg & 0x80000000;         //MSB Bit selektieren
112
            
113
            if (x == 0) {
114
                serialPort.setRTS(false);     //RDS Daten 0 ausgeben
115
                //System.out.print("0"); 
116
                serialPort.setDTR(true);     //Trigger ausgeben
117
            }
118
            else {
119
                serialPort.setRTS(true);     //RDS Daten 1 ausgeben
120
                //System.out.print("1");
121
                serialPort.setDTR(true);     //Trigger ausgeben
122
            }
123
            reg <<=1;                //reg um 1 linksschieben
124
            j++;
125
        }
126
      }
127
    }
128
  }
129
  
130
  //Methode showbits zum Debuggen. Bits als 1en und 0en anzeigen
131
  private void showbits(int bits){
132
    System.out.println("");
133
    for (int i=0; i<26; i++){
134
      int bool = bits & 0x80000000;
135
      if (bool == 0x80000000)
136
      System.out.print("1");
137
      else System.out.print("0");
138
      bits<<= 1;
139
    }
140
  }
141
}

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Christian B. schrieb:
> Wenn man einen Zähler einprogrammiert
Naja vermutlich wird einfach der Comport nicht so oft abgefragt um dies
Christian B. schrieb:
> sich allein hat, sprich 99% Auslastung
zu verhindern, das hat aber nix mit dem Eventlistener zu tun!

Mal ein paar Hinweise zu deinem Code:
- Klassennamen schreibt man i. A. groß und gibt ihnen sinnvolle 
Namen(send ist doch etwa allgemein ;)
- Um einen neuen Thread zu starten, reicht es das Interface Runnable zu 
implementieren und dem Thread im Konstruktor zu übergeben, Thread zu 
erweitern nur um die run Methode zu überschreiben ist "bad practice"
- Arrays könen in Java auch folgendermaßen initialisiert werden:
1
 int[] matrix = { 0x00001DC0, 0x0000B9C0, ...};
- Auch sollte man nicht alle Variablen auf Vorrat "am Anfang" 
deklarieren, damit handelt man sich nur Pobleme ein
1
for (int y = 0; y<16; y++){
2
      int h3 = 0x00000000;            //Hilfsregister initialisieren
3
      int h2 = 0x00010000;
4
      for (int x = 0; x<16; x++){
5
        int h1 = data[y] & h2;          //Bit ausmaskieren
6
        if (h1 != 0) h3 = matrix[x] ^ h3;  // ^ ist XOR 
7
        h2 = h2 << 1;            //nächst groesseres Bit (Rechtsshift ist in Java unbrauchbar, wegen Vorzeichenmitnahme)
8
      }
9
      h3 = h3 ^ offsetwort[y%4];        //Offset hinzufügen
10
      code[y] = data[y] ^ h3;          //Daten & Code & Offset
11
    }
- Gleiches gilt für Membervariablen, wieso ist "reg" ein Member von 
DataOut? Nicht nur das es den code unübersichtlich macht (man muss jetzt 
erstmal schauen ob irgendwer anders den Member nicht doch noch 
verwendet) dadurch das du diesen package private deklariert hast, darf 
der Compiler den nicht mal wegoptimieren und der Zugriff auf diesen ist 
langsamer.
- Eingaben sollte man am besten auch in Java prüfen ;)

Habe das mal etwas umgestellt.
1
package test;
2
3
import gnu.io.CommPortIdentifier;
4
import gnu.io.SerialPort;
5
6
public class DataOut implements Runnable {
7
  private int[] code;
8
  private SerialPort serialPort;
9
  private Thread sendThread;
10
11
  /*** Konstruktor ***/
12
  public DataOut(int[] data) {
13
    code = coding(data);
14
    try {
15
      CommPortIdentifier myPortIdentifier = CommPortIdentifier.getPortIdentifier("COM1");
16
      serialPort = (SerialPort) myPortIdentifier.open("", 500);
17
      serialPort.disableReceiveTimeout();
18
19
    } catch (Exception e) {
20
      e.printStackTrace();
21
      System.exit(1);
22
    }
23
    sendThread = new Thread(this);
24
    sendThread.start();
25
  }
26
27
  //Methode stop schliesst den COM-Port
28
  public void stopp() {
29
    sendThread.interrupt();
30
    serialPort.close();
31
32
  }
33
34
  //Methode coding erzeugt aus data[] den code[], der über die serielle Schnittstelle gesendet wird
35
  public int[] coding(int[] data) {
36
    if (data.length != 16) {
37
      throw new IllegalArgumentException("data must have a length of 16 but was "
38
          + data.length);
39
    }
40
    int[] code = new int[16];
41
42
    // *** Generatormatrix *** Aufbau: (16x0 | Prüfmatrix A | 6x0)
43
    int[] matrix = {
44
        0x00001DC0,
45
        0x0000B9C0,
46
        0x0000EBC0,
47
        0x0000C2C0,
48
        0x0000D640,
49
        0x0000DC00,
50
        0x00006E00,
51
        0x00003700,
52
        0x00001B80,
53
        0x00000DC0,
54
        0x0000B1C0,
55
        0x0000EFC0,
56
        0x0000C0C0,
57
        0x0000D740,
58
        0x0000DC80,
59
        0x00006E40 };
60
61
    int[] offsetwort = { 0x00003F00, // A
62
        0x00006600, // B
63
        0x0000D400, // C
64
        0x00006D00 // D
65
    };
66
67
    for (int y = 0; y < 16; y++) {
68
      int h3 = 0x00000000; //Hilfsregister initialisieren
69
      int h2 = 0x00010000;
70
      for (int x = 0; x < 16; x++) {
71
        int h1 = data[y] & h2; //Bit ausmaskieren
72
        if (h1 != 0) {
73
          h3 = matrix[x] ^ h3; // ^ ist XOR
74
        }
75
        h2 = h2 << 1; //nächst groesseres Bit (Rechtsshift ist in Java unbrauchbar, wegen Vorzeichenmitnahme)
76
      }
77
      h3 = h3 ^ offsetwort[y % 4]; //Offset hinzufügen
78
      code[y] = data[y] ^ h3; //Daten & Code & Offset
79
    }
80
    return code;
81
  }
82
83
  //ueberwacht Pin1 (Carrier Detect, DCD) des Com Ports auf den Takt und sendet den code  
84
  public void run() {
85
    System.out.println("sendThread gestartet");
86
87
    //Hilfsvariablen fuer Taktwechselerkennung
88
    boolean merkenA = true;
89
    boolean merkenB = true;
90
    boolean schalter = true;
91
    int j = 0;
92
    int reg = 0;
93
    while (!Thread.currentThread().isInterrupted()) { //Endlosschleife, bis der Thread gestoppt
94
      schalter = schalter ^ true; //Bit vertauschen
95
      if (schalter == true)
96
        merkenA = serialPort.isCD();
97
      else
98
        merkenB = serialPort.isCD();
99
      if (merkenA ^ merkenB && serialPort.isCD()) {//Taktwechsel erkannt && Zustand = 1
100
101
        if (j == 415) {
102
          j = 0; //Zaehler auf 0 setzen, da code durchlaufen wurde 
103
          serialPort.setDTR(false); //Triggerausgabe
104
        }
105
106
        if (j % 26 == 0) { //neuer Block
107
          reg = code[j / 26]; //neuen Block kopieren
108
        }
109
110
        int x = reg & 0x80000000; //MSB Bit selektieren
111
112
        if (x == 0) {
113
          serialPort.setRTS(false); //RDS Daten 0 ausgeben
114
          serialPort.setDTR(true); //Trigger ausgeben
115
        } else {
116
          serialPort.setRTS(true); //RDS Daten 1 ausgeben
117
          serialPort.setDTR(true); //Trigger ausgeben
118
        }
119
        reg <<= 1; //reg um 1 linksschieben
120
        j++;
121
      }
122
    }
123
  }
124
125
}

von Christian B. (mech)


Lesenswert?

Läubi .. schrieb:
> das hat aber nix mit dem Eventlistener zu tun!

Stimmt, das Problem liegt dann wohl bei der Eventquelle. Scheinbar kann 
der Com Port keinen Interrupt auslösen oder das wurde von der RXTX 
Bibliothek nicht implementiert.
Vielen Dank für die Hinweise. Das ist mein erstes Java Programm.
Problem ist erstmal gelöst.

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.