www.mikrocontroller.net

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


Autor: Christian B. (mech)
Datum:

Bewertung
0 lesenswert
nicht 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:
serialPort.notifyOnCarrierDetect(true); //wenn das Bit am DCD wechselt -> event
serialPort.addEventListener(new mySerialPortEventListener());

class mySerialPortEventListener implements SerialPortEventListener {
   public void serialEvent(SerialPortEvent event) { 
        //System.out.println("DCD changed");
        i++;
        if(i%1000==0)System.out.println("i= "+i); //i ist nach 1 Sekunde erst bei 10 angekommen... sollte aber bei 1187 sein! 
    }
}

VG

Christian

Autor: Läubi .. (laeubi) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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 ;)

Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)

Autor: Christian B. (mech)
Datum:

Bewertung
0 lesenswert
nicht 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. Ä.

Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Christian B. (mech)
Datum:

Bewertung
0 lesenswert
nicht 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 
;)

Autor: qwertzuiopü (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Christian B. (mech)
Datum:

Bewertung
0 lesenswert
nicht 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).

Autor: Läubi .. (laeubi) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Christian B. (mech)
Datum:

Bewertung
0 lesenswert
nicht 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:
class send extends Thread{
    public void run(){
      while (!isInterrupted()){
      merken  = serialPort.isCD();
    // noch mehr Code
    }
}


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.

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Würdest du mal dein Programm komplett hochladen? würde mich mal 
interessieren.

Autor: Christian B. (mech)
Datum:

Bewertung
0 lesenswert
nicht 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
package RDS_Package;

import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;


public class DataOut{
  int j = 0; //Hilfsvariablen für Taktsteuerung
  int reg, x; //Daten von 32 Bit laenge
  int[] code;
  SerialPort serialPort;
  send sendThread;
  
  
  /*** Konstruktor ***/
  public DataOut(int[] data){
    code = coding(data);
    try {
            CommPortIdentifier myPortIdentifier = CommPortIdentifier.getPortIdentifier(JDialog_Hauptfenster.portName);
            serialPort = (SerialPort)myPortIdentifier.open("", 500);
            serialPort.disableReceiveTimeout();    
            
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        sendThread = new send();
        sendThread.start();
  }   
  
  
  //Methode stop schliesst den COM-Port
  public void stopp(){
    sendThread.interrupt();
    serialPort.close();

  }
  
  //Methode coding erzeugt aus data[] den code[], der über die serielle Schnittstelle gesendet wird
  public int[] coding(int[] data){
    
    int[] code = new int[16];
    int h1, h2, h3;
    
    // *** Generatormatrix *** Aufbau: (16x0 | Prüfmatrix A | 6x0)
    int[] matrix = new int[16];          
    matrix[15] = 0x00001DC0;
    matrix[14] = 0x0000B9C0;
    matrix[13] = 0x0000EBC0;
    matrix[12] = 0x0000C2C0;
    matrix[11] = 0x0000D640;
    matrix[10] = 0x0000DC00;
    matrix[9]  = 0x00006E00;
    matrix[8]  = 0x00003700;
    matrix[7]  = 0x00001B80;
    matrix[6]  = 0x00000DC0;
    matrix[5]  = 0x0000B1C0;
    matrix[4]  = 0x0000EFC0;
    matrix[3]  = 0x0000C0C0;
    matrix[2]  = 0x0000D740;
    matrix[1]  = 0x0000DC80;
    matrix[0]  = 0x00006E40;
    
    int[] offsetwort = new int[4]; // *** Offsetworte ***
    offsetwort[0] = 0x00003F00; // A
    offsetwort[1] = 0x00006600; // B
    offsetwort[2] = 0x0000D400; // C`
    offsetwort[3] = 0x00006D00; // D
    
    
    
    for (int y = 0; y<16; y++){
      h3 = 0x00000000;            //Hilfsregister initialisieren
      h2 = 0x00010000;
      for (int x = 0; x<16; x++){
        h1 = data[y] & h2;          //Bit ausmaskieren
        if (h1 != 0) h3 = matrix[x] ^ h3;  // ^ ist XOR 
        h2 = h2 << 1;            //nächst groesseres Bit (Rechtsshift ist in Java unbrauchbar, wegen Vorzeichenmitnahme)
      }
      h3 = h3 ^ offsetwort[y%4];        //Offset hinzufügen
      code[y] = data[y] ^ h3;          //Daten & Code & Offset
    }
    return code;
  }
  
  //Klasse send ueberwacht Pin1 (Carrier Detect, DCD) des Com Ports auf den Takt und sendet den code  
  class send extends Thread{
    public void run(){
      System.out.println("sendThread gestartet");
      
      //Hilfsvariablen fuer Taktwechselerkennung
        boolean merkenA  = true;  
        boolean merkenB  = true;
        boolean schalter = true;
      
      while (!isInterrupted()){            //Endlosschleife, bis der Thread gestoppt
        schalter = schalter^true;          //Bit vertauschen
        if (schalter == true)  merkenA  = serialPort.isCD();
        else           merkenB  = serialPort.isCD();
        if(merkenA^merkenB && serialPort.isCD()){//Taktwechsel erkannt && Zustand = 1
              
             if (j == 415 ){
              j=0;                //Zaehler auf 0 setzen, da code durchlaufen wurde 
              serialPort.setDTR(false);    //Triggerausgabe
            }
            
            if (j%26 == 0){      //neuer Block
              reg = code[j/26];  //neuen Block kopieren
            }
            
            x = reg & 0x80000000;         //MSB Bit selektieren
            
            if (x == 0) {
                serialPort.setRTS(false);     //RDS Daten 0 ausgeben
                //System.out.print("0"); 
                serialPort.setDTR(true);     //Trigger ausgeben
            }
            else {
                serialPort.setRTS(true);     //RDS Daten 1 ausgeben
                //System.out.print("1");
                serialPort.setDTR(true);     //Trigger ausgeben
            }
            reg <<=1;                //reg um 1 linksschieben
            j++;
        }
      }
    }
  }
  
  //Methode showbits zum Debuggen. Bits als 1en und 0en anzeigen
  private void showbits(int bits){
    System.out.println("");
    for (int i=0; i<26; i++){
      int bool = bits & 0x80000000;
      if (bool == 0x80000000)
      System.out.print("1");
      else System.out.print("0");
      bits<<= 1;
    }
  }
}


Autor: Läubi .. (laeubi) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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:
 int[] matrix = { 0x00001DC0, 0x0000B9C0, ...};
- Auch sollte man nicht alle Variablen auf Vorrat "am Anfang" 
deklarieren, damit handelt man sich nur Pobleme ein
for (int y = 0; y<16; y++){
      int h3 = 0x00000000;            //Hilfsregister initialisieren
      int h2 = 0x00010000;
      for (int x = 0; x<16; x++){
        int h1 = data[y] & h2;          //Bit ausmaskieren
        if (h1 != 0) h3 = matrix[x] ^ h3;  // ^ ist XOR 
        h2 = h2 << 1;            //nächst groesseres Bit (Rechtsshift ist in Java unbrauchbar, wegen Vorzeichenmitnahme)
      }
      h3 = h3 ^ offsetwort[y%4];        //Offset hinzufügen
      code[y] = data[y] ^ h3;          //Daten & Code & Offset
    }
- 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.
package test;

import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;

public class DataOut implements Runnable {
  private int[] code;
  private SerialPort serialPort;
  private Thread sendThread;

  /*** Konstruktor ***/
  public DataOut(int[] data) {
    code = coding(data);
    try {
      CommPortIdentifier myPortIdentifier = CommPortIdentifier.getPortIdentifier("COM1");
      serialPort = (SerialPort) myPortIdentifier.open("", 500);
      serialPort.disableReceiveTimeout();

    } catch (Exception e) {
      e.printStackTrace();
      System.exit(1);
    }
    sendThread = new Thread(this);
    sendThread.start();
  }

  //Methode stop schliesst den COM-Port
  public void stopp() {
    sendThread.interrupt();
    serialPort.close();

  }

  //Methode coding erzeugt aus data[] den code[], der über die serielle Schnittstelle gesendet wird
  public int[] coding(int[] data) {
    if (data.length != 16) {
      throw new IllegalArgumentException("data must have a length of 16 but was "
          + data.length);
    }
    int[] code = new int[16];

    // *** Generatormatrix *** Aufbau: (16x0 | Prüfmatrix A | 6x0)
    int[] matrix = {
        0x00001DC0,
        0x0000B9C0,
        0x0000EBC0,
        0x0000C2C0,
        0x0000D640,
        0x0000DC00,
        0x00006E00,
        0x00003700,
        0x00001B80,
        0x00000DC0,
        0x0000B1C0,
        0x0000EFC0,
        0x0000C0C0,
        0x0000D740,
        0x0000DC80,
        0x00006E40 };

    int[] offsetwort = { 0x00003F00, // A
        0x00006600, // B
        0x0000D400, // C
        0x00006D00 // D
    };

    for (int y = 0; y < 16; y++) {
      int h3 = 0x00000000; //Hilfsregister initialisieren
      int h2 = 0x00010000;
      for (int x = 0; x < 16; x++) {
        int h1 = data[y] & h2; //Bit ausmaskieren
        if (h1 != 0) {
          h3 = matrix[x] ^ h3; // ^ ist XOR
        }
        h2 = h2 << 1; //nächst groesseres Bit (Rechtsshift ist in Java unbrauchbar, wegen Vorzeichenmitnahme)
      }
      h3 = h3 ^ offsetwort[y % 4]; //Offset hinzufügen
      code[y] = data[y] ^ h3; //Daten & Code & Offset
    }
    return code;
  }

  //ueberwacht Pin1 (Carrier Detect, DCD) des Com Ports auf den Takt und sendet den code  
  public void run() {
    System.out.println("sendThread gestartet");

    //Hilfsvariablen fuer Taktwechselerkennung
    boolean merkenA = true;
    boolean merkenB = true;
    boolean schalter = true;
    int j = 0;
    int reg = 0;
    while (!Thread.currentThread().isInterrupted()) { //Endlosschleife, bis der Thread gestoppt
      schalter = schalter ^ true; //Bit vertauschen
      if (schalter == true)
        merkenA = serialPort.isCD();
      else
        merkenB = serialPort.isCD();
      if (merkenA ^ merkenB && serialPort.isCD()) {//Taktwechsel erkannt && Zustand = 1

        if (j == 415) {
          j = 0; //Zaehler auf 0 setzen, da code durchlaufen wurde 
          serialPort.setDTR(false); //Triggerausgabe
        }

        if (j % 26 == 0) { //neuer Block
          reg = code[j / 26]; //neuen Block kopieren
        }

        int x = reg & 0x80000000; //MSB Bit selektieren

        if (x == 0) {
          serialPort.setRTS(false); //RDS Daten 0 ausgeben
          serialPort.setDTR(true); //Trigger ausgeben
        } else {
          serialPort.setRTS(true); //RDS Daten 1 ausgeben
          serialPort.setDTR(true); //Trigger ausgeben
        }
        reg <<= 1; //reg um 1 linksschieben
        j++;
      }
    }
  }

}

Autor: Christian B. (mech)
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.