PrintWriter (PrintStream) sendet falsche Werte

Status
Nicht offen für weitere Antworten.

wildbret

Aktives Mitglied
Hallo,

ich habe ein fieses kleines Problem.
Ich sende Daten über die serielle Schnittstelle unter Verwendung des Printwriter des Paketes javax.comm.

Das Datenpaket setzt sich wie folgt zusammen: Startflag, Länge, Command, CRC, Endflag (Alles vom Typ short)
Der CRC bildet sich aus dem Command.
Das Problem ist, dass manche Commands funktionieren und bei anderen liefert der u-Controller einen Checksum-Error zurück.
Ich glaube, sobald die Checksumme über 127 (der Byte-Grenze in Java) liegt wird falsch gesendet, weil der PrintWriter intern nur Bytes senden kann.
Auf C++-Seite habe ich keine Probleme damit, nur bei Java weil hier BYTE nur von -127 bis 127 oder so geht und nicht von 0 bis 255. Deshalb bin ich in Java überall auf short gegangen.

Wie kann ich den PrintWriter dazu zwingen in short zu senden oder das Problem irgendwie anders lösen.

Beispiel:
short tabelle = 0x04; ergibt eine Checksumme von 97 und der Befehl wird fehlerfrei von Controller ausgeführt.
short R_start = 0x22; ergibt eine Checksumme von 159 und ich bekomme ein CRC-Fehler-Frame vom Controller, vermutlich weil nicht 159 sondern irgendetwas anderes gesendet wird.

Ich poste hier mal die wichtigsten Ausschnitte aus dem Quellcode.
Sagt bitte bescheid, wenn ihr mehr braucht dann post ich das noch.

Das Senden mit dem PrintWriter steht ganz unten.

Code:
//das Paket für den Aufbau serieller Verbindungen
import javax.comm.*;
...
short STX = 0x02;			//StartFlag
short length = 0x01;		        //Länge 
short ETX = 0x03;  		        //EndFlag
//Auszug aus den Kommandos
short R_start = 0x22;  
short tabelle = 0x04;
...
//Parameter für den Verbindungsaufbau
static CommPortIdentifier portID;
  InputStream in;
  OutputStream out;
  static SerialPort SP;
...
//CRC Methoden
public short CRC_update(short crc,short data) {
	crc = (short) (crc ^ data);		
	for (int i = 0; i < 8; i++) {
		if ((crc&0x01)==0x01) crc = (short) ((crc >> 1) ^ 0x8C); 
		else crc >>= 1;
	}	
	return crc;
}

public short CRC_calculate(short p_Buffer[],short length) {		
	short checksum = 0x00;		
	for (int i=0; i<length; i++) { 
		checksum=CRC_update(checksum,p_Buffer[i]); 
	}	
	System.out.println("Checksumme = "+checksum);
	return checksum;		
}	
...
//Auszug aus dem ActionListener für den Aufbau der seriellen Verbindung
if(command.equals("Verbindung aufbauen")){
			String PortNr = Gruppe.getSelection().getActionCommand();
			try{portID = CommPortIdentifier.getPortIdentifier(PortNr);
	        	SP = (SerialPort) portID.open("Framesend",2000); 
	        	ausgabe.append("\nPort "+PortNr+" geöffnet");
			}
			catch(Exception exc){
				System.out.println("Fehler :"+exc);
				ausgabe.append("\nFehler: ungültige Portauswahl!");
			}		
			try {in = SP.getInputStream();
	    		out = SP.getOutputStream();
	    		SP.addEventListener(new commListener());
			}
			catch (Exception e1) { System.out.println("Fehler: "+e1);}
				SP.notifyOnDataAvailable(true);
				int Baud = Integer.valueOf(Baudrate.getSelectedItem()).intValue();
			try {SP.setSerialPortParams(Baud,//57600 oder 19200
	                                  SerialPort.DATABITS_8,
	                                  SerialPort.STOPBITS_1 ,
	                                  SerialPort.PARITY_NONE);}
			catch (UnsupportedCommOperationException e1) {}
		}
...
//Auszug aus dem ActionListener für das Senden eines Pakets
if(command.equals("Router Start")){				
	short p_Buffer[] = new short[] {R_start};
	short check = CRC_calculate(p_Buffer, length);
	short senden[] = new short[] {STX,length,R_start,check,ETX};
	PrintWriter Sendepuffer = new PrintWriter(out);
	for (int i=0;i<senden.length;i++) Sendepuffer.write(senden[i]);			
	Sendepuffer.flush();
        Sendepuffer.close();
}
 
T

tuxedo

Gast
Wieso nimmst du da den PrintWriter? Wieso nicht einen OutputStream ? PrintWriter ist, wie in der Api-DOC steht, für andere Dinge (hauptsächlich Strings) gut:

http://java.sun.com/j2se/1.5.0/docs/api/java/io/PrintWriter.html hat gesagt.:
Print formatted representations of objects to a text-output stream.

Wenn du Java Typen wie Integer, String oder Short schicken willst nimm den DataOutputStream. Für ganze Java-Objekte die sich serialisieren lassen nimm den ObjectOutputStream. Wenn du nur bytes (bzw. byte[]) verschicken willst nimm den nackigen OutputStream.

Aber vergiss ganz schnell den PrintWriter ...

- Alex
 

wildbret

Aktives Mitglied
Danke dir für deine schnelle Antwort.
Ich habe jetzt ein bischen damit rumprobiert, komme aber leider nicht klar.

Habe versucht meinen OutputStream aus Zeile 15 zum DataOutputStream zu machen, allerdings bekomme ich dann Probleme mit dem SerialPort aus Zeile 49.
Code:
out = SP.getOutputStream();
Fehler: Type mismatch: cannot convert from OutputStream to DataOutputStream
ändere ich Zeile 49 auf
Code:
out = SP.getDataOutputStream();
kommt folgender Fehler:
The method getDataOutputStream() is undefined for the type SerialPort

Scheint so, als mag das javax.comm keinen DataOutputStream :(

Kannst du mir das bitte näher erläutern, denn so versteh ich das nicht?
 
T

tuxedo

Gast
Du kannst Streams "verketten". Hab die javax.comm Lib gerade nicht zur hand, aber hier mal ein exemplarisches Beispiel:

Code:
DataOutputStream dos = new DataOutputStream(SP.getOutputStream());

- Alex
 

wildbret

Aktives Mitglied
Ich habe jetzt versucht das an verschiedene Stelllen zu implementieren und es läuft auch soweit fehlerfrei, scheint aber dennoch nicht zu funktionieren. Bekomme noch immer bei manchen Befehlen einen CRC-Fehler zurück.

Ich habe jetzt global vereinbart
Code:
OutputStream out;
DataOutputStream dos;

Zeile 49
Code:
out = SP.getOutputStream();
habe ich ersetzt durch
Code:
dos = new DataOutputStream(SP.getOutputStream());

und beim Senden habe ich nun
Code:
if(command.equals("Router Start")){				
			short p_Buffer[] = new short[] {R_start};
			short check = CRC_calculate(p_Buffer, length);			
			short senden[] = new short[] {STX,length,R_start,check,ETX};			
			PrintWriter Sendepuffer = new PrintWriter(dos);
			for (int i=0;i<senden.length;i++) Sendepuffer.write(senden[i]);			
			Sendepuffer.flush();			
		    Sendepuffer.close();
		}

Du sagtest ich soll den PrintWriter vergessen, nur womit schreibe ich sonst auf den Stream?
Habe ich deine Ideen falsch oder an der falschen Stelle umgesetzt?

Hier der Link zum javax.comm falls er dir hilft mir zu helfen :)
http://java.sun.com/products/javacomm/reference/api/javax/comm/package-summary.html

Danke
 
T

tuxedo

Gast
Ich sagte "vergessen" nicht "weiterhin benutzen"...

Und das heisst:

Code:
if(command.equals("Router Start")){            
         short p_Buffer[] = new short[] {R_start};
         short check = CRC_calculate(p_Buffer, length);         
         short senden[] = new short[] {STX,length,R_start,check,ETX};         

         for (int i=0;i<senden.length;i++) {
  dos.writeShort(senden[i]);         
}
         dos.flush();         
          dos.close();
      }

Auf der Empfangsseite arbeitest du dann logischerweise mit einen DataInputStream und machst ein "readShort()" ...

Du musst mächtig auf dem Schlauch gestanden haben... ;-)
 

wildbret

Aktives Mitglied
Guten Morgen bzw Mittag,

Danke für deine Hilfe aber leider funktioniert es noch immer nicht.
Ich habe es nun so gemacht wie du gesagt hast, aber bekomme immernoch den gleichen Fehler und es gibt jetzt noch einen Weiteren.
Ich bekomme nur noch bei jedem 7. Mal Senden überhaupt eine Antwort.

Code:
if(command.equals("Router Start")){				
			short p_Buffer[] = new short[] {R_start};
			short check = CRC_calculate(p_Buffer, length);			
			
			try {
				dos.writeShort(STX);
				dos.writeShort(length);
				dos.writeShort(R_start);
				dos.writeShort(check);
				dos.writeShort(ETX);
                                dos.flush();
				dos.close();
			} catch (IOException e1) {
				System.out.println("Fehler: dos.writeShort "+e1);
				e1.printStackTrace();
			}		
}

Die Schleife hab ich mal rausgenommen, machte die Sache unnötig kompliziert.

Ich hoffe dir fällt noch was ein.
 
T

tuxedo

Gast
Bist du mal auf die Idee gekommen dass es nicht am "senden" sondern am "empfangen" liegen könnte? Was hängt denn am anderen Ende der seriellen Schnittstelle? wenn da auch Java dran hängt: Wie liest du?

- Alex
 

wildbret

Aktives Mitglied
Am anderen Ende hängt ein Microcontroller auf dem ein in C geschriebener Stack läuft.
Ich habe das C nicht selbst geschrieben, aber soweit mir der Ersteller beschrieben hat empfängt der Controller byteweise.

Sobald der Controller 0x02 empfängt erkennt er, dass es sich um das Start-Flag handelt, danach kommt die Länge.
Falls diese 0x01 ist weiss er, dass das Paket keine Daten sondern nur ein Command enthält.
Danach kommt das Command selbst, auch 1 Byte z.B. 0x22.
Jetzt kommt der CRC-Wert (auch ein Byte) welcher nachweislich richtig gebilded wird.
Durch 0x03 erkennt er das Ende des Frames, er rechnet mit den gleichen Methoden den CRC nach und wenn er stimmt kommt die richtige Antwort. Falls der CRC falsch ist, kommt folgendes zurück:
0x02(Start) 0x01(länge) 0x1B(CRC-Error) 0x??(CRC für 1B) 0x03(Ende)


Was ich versuche in Java abzubilden ist ein Terminal was Befehle an den Controller senden kann und die Antworten ausgibt. Dieses Programm gibt es schon mit C++ geschrieben und ich möchte das in Java nachbilden.

Es ist 99% sicher, dass auf Controller-Seite alles richtig funktioniert, weil mit dem in C++ geschriebenen Programm alles richtig funktioniert.

Selbst wenn ich mit einem Hyper-Terminal manuell die Bytes sende antwortet der Controller korrekt.
Die Commands sind alle kleiner als 127, aber sobald der CRC-Wert eines Commandos über 127 liegt, kommt am Microcontroller etwas anderes, falsches an.

Ich habe so einen Verdacht warum er jetzt nur noch bei jedem 7. mal Senden antwortet
Ich glaube, dass writeShort nicht ganz das richtige ist, weil er da 2 Bytes schickt.
writeShort(int v)
Writes a short to the underlying output stream as two bytes, high byte first.

Ich versuche mal die anderen write... die es für den Dataoutputstream noch gibt.
 
T

tuxedo

Gast
Naja, das hättest du auch gleich sagen können.

Ich bin davon ausgegangen dass du einen Short senden willst. Und Short besteht nunmal aus 2 Bytes (zumindest in Java).

Wenn du jetzt also nur rein byteweise arbeiten willst dann reicht ein OutputStream und ein InputStream (die Sache mit Data... ist nur interessant wenn man Java Datentypen versenden will, also Integer, Double, Short, ...).

Und mit dem Input und OutputStream reicht die einfache write(int) und read(int) Methode wenn du je ein Byte lesen und auch schreiben willst. Ansonsten nimm halt write(byte[]) und read(byte[]) ..

- Alex
 

wildbret

Aktives Mitglied
Aber geht ein Byte in Java nicht nur von -127 bis 128?
In dem C-Code geht Byte von 0 bis 255.

Und möchte ich einen CRC >128 senden sprenge ich den Java Datentyp Byte.
Das ist der Grund, weshalb ich im gesamten Programm überall von Byte auf Short gegangen bin.

Danke dir für deine Zeit und sorry das ich dich nicht immer gleich verstehe. Bis auf die wenigen C und Java Vorlesungen die ich gehört habe bin ich ein Anfänger, der ins kalte Wasser geworfen wurde. :)
 
T

tuxedo

Gast
Tja, das ist der Trick an der Sache...C nutzt unsigned bytes, und Java halt signed bytes ...

0..255 (unsigned byte) entspricht -127..128 (signed byte)...

Du musst das ganze halt konvertieren. Wird dir nix anderes übrig bleiben. Hab ich für mein jPMdbc auch machen müssen. Hab dazu sogar ne kleine Doku verfasst was die Umrechnung betrifft. Guckst du hier:
https://jpmdbc.dev.java.net/servlets/ProjectProcess?tab=1 (runterscrollen bis "Converting unsigned byte to integer")

- Alex
 

wildbret

Aktives Mitglied
Hallo,

ich habe mein Program jetzt komplett auf byte umgestellt, die shorts entsorgt und deine Konvertierung durchdacht und sie eingebaut.
Daraufhin habe ich meine CRC-Methode überarbeitet und jeden einzelnen Schritt auf Papier selbst ausgerechnet und stellte erfreut fest, dass die Werte stimmen.

Aber leider habe ich seit dem ich den PrintWriter nicht mehr verwende und direkt mit Write in den Stream reinschreibe ein anderes Problem.

Starte ich mein Program und sende per Buttondruck ein Command bekomme ich keine Antwort.
Sende ich ein weiteres Command (das Gleiche oder ein Anderes ist egal) kommt die Antwort auf das
erste Command.
Sende ich ein drittes kommt die Antwort auf das zweite usw.

Ich habe mit einem anderen Microcontroller, der einfach nur ein Echo zurückliefert bewiesen, dass auch beim ersten Buttondruck schon Daten rausgehen.
Programmiere ich den Controller bei z.B. Empfang von 0x4e eine LED anzuschalten funktioniert das auch sofort wenn ich 0x4e sende.
Sende ich mit dem Hyperterminal ein korrektes Paket manuell kommt die Antwort auch sofort und unverzögert.

Ich kann mir diese Verzögerung nicht erklären, denn laut meinen Tests wird sofort und richtig gesendet.

Nun müsste das wohl am Empfänger liegen, aber diesen habe ich ja in keinster weise verändert.
Das Problem tritt auf seit dem ich direkt auf den Outputstream schreibe und den PrintWriter nicht mehr verwende.

Ich poste mal die geänderten Stellen, von denen ich glaube dass sie wichtig sind.
Ich schreib auch mal die empfangende Funktion dazu, die ich nicht selbst geschrieben und noch nie was dran geändert habe dazu (ganz am Ende des Codes).

-connectionListener registriert ButtonDruck und übergibt sein Command an die Methode Sender
-Sender übergibt das Command an die beiden CRC-Methoden
-die Checksumme wird am Ende mit deiner Konvertierung korrigiert und an Sender zurückgeliefert.
-Sender sendet das Paket.

Code:
InputStream in;
  OutputStream out;
...
public static int unsignedByteToInt(int b) {
	  return (int) b & 0xFF;  
  }
public static int CRC_update(int crc,byte data) {
	crc =  crc ^ data;		
	for (int i = 0; i < 8; i++) {
		if ((crc&0x01)==0x01) crc = (byte) ((crc >> 1) ^ 0x8C); 
		else crc >>= 1;
		crc = unsignedByteToInt(crc);
		System.out.println("Zwischenergebnis i="+i+":  "+crc);
		}	
		System.out.println("\n");
	return crc;		
}
public int CRC_calculate(byte p_Buffer[],byte length) {		
	int checksum = 0x00;		
	for (int i=0; i<length; i++) { 
		checksum=CRC_update(checksum,p_Buffer[i]); 
	}		
	return checksum;		
}
public void Sender (byte command) {
	  byte p_Buffer[] = new byte[] {command};
	  int check = CRC_calculate(p_Buffer, length);		  
	  try {
		  out.write(STX);
		  out.write(length);
		  out.write(command);
		  out.write(check);
		  out.write(ETX);
		  out.flush();
		  out.close();
	  } catch (IOException e1) {
		  System.out.println("Schreibfehler "+e1);
		  e1.printStackTrace();
		}				
}
...
//hier nochmal der Aufbau der Verbindung und die Streams
public class connectionListener implements ActionListener{	
	public void actionPerformed(ActionEvent e) {
		String command = e.getActionCommand();	
		
		if(command.equals("Verbindung aufbauen")){
			String PortNr = Gruppe.getSelection().getActionCommand();
			try{portID = CommPortIdentifier.getPortIdentifier(PortNr);
	        	SP = (SerialPort) portID.open("Framesend",2000); 
	        	ausgabe.append("\nPort "+PortNr+" geöffnet");
			}
			catch(Exception exc){
				System.out.println("Fehler :"+exc);
				ausgabe.append("\nFehler: ungültige Portauswahl!");
			}		
			try {in = SP.getInputStream();
				out = SP.getOutputStream();						
	    		SP.addEventListener(new commListener());
			}
			catch (Exception e1) { System.out.println("Fehler: "+e1);}
				SP.notifyOnDataAvailable(true);
				int Baud = Integer.valueOf(Baudrate.getSelectedItem()).intValue();
			try {SP.setSerialPortParams(Baud,
	                                  SerialPort.DATABITS_8,
	                                  SerialPort.STOPBITS_1 ,
	                                  SerialPort.PARITY_NONE);}
			catch (UnsupportedCommOperationException e1) {}
	}
      if(command.equals("Router Start")){				
			Sender(R_start);
	      }		
      if(command.equals("Router Stop")){				
			Sender(R_stop);
	      }		
//Empfänger
  public class commListener implements SerialPortEventListener{
    public void serialEvent(SerialPortEvent event) {    
      if(event.getEventType()==SerialPortEvent.DATA_AVAILABLE){
        byte[] readBuffer = new byte[256];
        try {
          while (in.available() > 0) {in.read(readBuffer);}            
          String nachricht = new String(readBuffer);
          ausgabe.append("\n"+nachricht);            
        }
        catch (IOException e) {System.out.println("Fehler: "+e);}
      }
    }
  }
 

wildbret

Aktives Mitglied
Habe den PrintWriter grade noch mal im neuen Program getestet.

Ergebnis:

-PrintWriter sendet den CRC falsch, hat aber die Verzögerung nicht

Code:
byte p_Buffer[] = new byte[] {command};
	  int check = CRC_calculate(p_Buffer, length);	
     PrintWriter Sendepuffer = new PrintWriter(out);
	  Sendepuffer.write(STX);	
	  Sendepuffer.write(length);	
	  Sendepuffer.write(command);	
	  Sendepuffer.write(check);	
	  Sendepuffer.write(ETX);	
	  Sendepuffer.flush();
	  Sendepuffer.close();

-Direkt auf den Stream schreiben sendet den CRC richtig, hat aber die seltsame Verzögerung.

Code:
byte p_Buffer[] = new byte[] {command};
int check = CRC_calculate(p_Buffer, length);
try {
		  out.write(STX);
		  out.write(length);
		  out.write(command);
		  out.write(check);
		  out.write(ETX);
		  out.flush();
		  out.close();
	  } catch (IOException e1) {
		  System.out.println("Schreibfehler "+e1);
		  e1.printStackTrace();
		}
 
T

tuxedo

Gast
So wie du das beschreibt tritt die Verzögerum beim Empfangen auf... Das heisst der InputStream vom dem du die Antwort des uC liest, Puffert die Daten.

Schau mal hier:
http://java.sun.com/products/javacomm/reference/api/javax/comm/CommPort.html

Dein SerialPort-Objekt erbt von dieser Klasse. Also hast du da auch die Methoden:

setInputBufferSize(int size)
Sets the input buffer size.

setOutputBufferSize(int size)
Sets the output buffer size.

Du solltest die Puffer etwas kleiner als die zu übertragenden Datensätze machen.

Wenn du wirklich nur seeehr wenig Überträgst, also wirklich nur wenige Byte große Steuerkommandos und deren Antwort, kannst du den Puffer auch jeweils auf 1 setzen.

- Alex
 

wildbret

Aktives Mitglied
Hi,

es funktioniert jetzt alles so wie es soll dank deiner Hilfe.
Das Problem mit der Verzögerung bin ich los geworden, indem ich den OutPutStream mit einem BufferedOutputStream verkettet habe.
Ich bin mir zwar nicht sicher, warum es funktioniert aber hauptsache es geht :)

Code:
InputStream in;
BufferedOutputStream out;
OutputStream aut;
...
try {in = SP.getInputStream();
	aut = SP.getOutputStream();
	out = new BufferedOutputStream(aut);
	SP.addEventListener(new commListener());
}

Aber ich werde etInputBufferSize(int size) und setOutputBufferSize(int size) trotzdem nochmal versuchen, weil ich schon gerne wüsste, ob es auch ohne den BufferedOutputStream geht.

Und nächste Woche nehm ich die Empfangsmehtode auseinander, so dass die Bytes ordentlich angezeigt werden.
Ich möchte die ankommenden Daten in nem Buffer speichern, den Buffer nach dem Startflag 0x02 durchsuchen und auf diese Weise die Nutzdaten herausholen.
Nach dem Startflag kommt ja die Länge, dann weiss ich was alles Daten sind, etc...

Hauptproblem bei meinem Plan ist, dass Java die Bytes nicht direkt ausgibt, sondern irgendwelche zugehörigen Zeichen.

Wenn vom Controller 02 01 09 9F 03 kommt soll das auch so oder so ähnlich ausgegeben werden und nicht für jedes der 5 Bytes ein unverständliches Zeichen.
Mal googlen wie das geht. Ich hab schon was mit ner Schleife und IntegertoHexString aber das is ne doofe Lösung und richtig langsam.

Wenn du wirklich nur seeehr wenig Überträgst, also wirklich nur wenige Byte große Steuerkommandos und deren Antwort, kannst du den Puffer auch jeweils auf 1 setzen.

Richtung Controller übertrage ich nur sehr wenig, um genau zu sein immer nur ein Steuerkommando, also ein 5 Byte großes Paket.
Aber empfangen können muss ich wesentlich größere Pakete. Die Länge gibt an wieviele Bytes.
Ist die Lännge bildet sich aus Bytesumme(command+data).
Ist Länge 0x01 dann gibt es nur das Command.
Bei größerer Länge sieht das Paket so aus.
start | länge | command | data | crc | ende Länge-1 = Anzahl der DatenBytes
 
T

tuxedo

Gast
Naja, wie gesagt, den Buffer solltest du halt geschickt wählen. Beim senden kann man ja noch "flush()" machen. Aber beim empfangen gibts sowas glaub nicht.

- Alex
 

wildbret

Aktives Mitglied
Hallo,

ich habe nochmal über die UnsignedByte Konvertierung nachgedacht.
Kann es sein, dass in deiner Dokumentation ein kleiner Fehler drin ist?

0xFF FF FF FE16 & 0xFF FF FF FF16 == 0x00 00 00 FE16

Now we have eliminated the leading FF and have our 25410 represented in 4 byte two's complement, aka. Java's integer ... And here is the java-code (in a short variant with 0xFF instead of 0xFF FF FF FF, it's all the same):

Müsste die Maske nicht eigentlich 0x00 00 00 FF sein, um die führenden Einsen zu entfernen?
Weil wenn man mit FF Und-verknüpft bleibt ja genau das stehn, was vorher auch da stand.

(in a short variant with 0xFF instead of 0xFF FF FF FF, it's all the same) <->
(in a short variant with 0xFF instead of 0x00 00 00 FF, it's all the same)
 
T

tuxedo

Gast
Tatsache. Da ist der Fehlerteufel drin. Muss ich mal eben korrigieren. Danke für den Hinweis.

[update]
*korrigiert*

- Alex
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben