Java-CRC-Portierung: Probleme mit byte/unsigned



  • Hallo zusammen,

    ich habe ganz arge Probleme, die Modbus-CRC-Berechnung in Java zu programmieren. Info: Modbus ist ein Protokoll, wo halt eine CRC-Berechnung durchgeführt werden muss, die definiert beschrieben ist.

    Jetzt habe ich ein Gerät (Blackbox), dass nur auf Modbus-Telegramme mit gültiger CRC-Antwort reagiert. Folgenden Code habe ich gefunden:

    private int calculateCRC(int buf[], int len) {
    		int crc = 0xFFFF;
    
    		for (int pos = 0; pos < len; pos++) {
    			crc ^= buf[pos]; // XOR byte into least sig. byte of crc
    
    			for (int i = 8; i != 0; i--) { // Loop over each bit
    				if ((crc & 0x0001) != 0) { // If the LSB is set
    					crc >>= 1; // Shift right and XOR 0xA001
    					crc ^= 0xA001;
    				} else {
    					// Else LSB is not set
    					crc >>= 1; // Just shift right
    				}
    			}
    		}
    
    		System.out.println("Berechnete CRC: LW / HW:");
    		System.out.println("\t" + crc);
    		System.out.println("\t" + Integer.toHexString(crc & 0xff));
    		System.out.println("\t" + Integer.toHexString(crc >> 8));
    		System.out.println("-------------------");
    		return crc;
    	}
    

    Quelle: http://www.ccontrolsys.com/w/How_to_Compute_the_Modbus_RTU_Message_CRC

    Wenn man genau hinsieht habe ich den Typ des Arrays von "byte" auf "int" geändert, da Java keine unsigned Datentypen kennt und ich so z.B. nicht den Wert 212 in einem Byte speichern konnte.

    Die Ergebnisse dieser Funktion sind auch in 95% der Fälle korrekt. Manchmal ist "crc" (Zeile 19) jedoch negativ und dann stimmt die Checksumme (LW und HW) nicht bzw. das Gerät reagiert nicht.

    Das verstehe ich nicht 😕, klar int ist auch nicht "unsigned", aber es sollte doch egal sein, ob das Vorzeichenbit gesetzt wird oder nicht, da ja auf einer ganz anderen Ebene nur die Bits der Variable betrachtet werden. Oder muss ich statt "int crc" auf "long crc" umsteigen, damit der Wertebereich groß genug ist 😕 Mein Kopf ist voll, hat jemand von euch eine Idee?



  • Ich hab das jetzt nicht vollständig durchdacht, aber prüf mal, ob du nicht die arithmetischen Shifts durch logische Shifts ersetzt, also >>> statt >>, >>>= statt >>=.



  • Bashar schrieb:

    Ich hab das jetzt nicht vollständig durchdacht, aber prüf mal, ob du nicht die arithmetischen Shifts durch logische Shifts ersetzt, also >>> statt >>, >>>= statt >>=.

    Mir war gar nicht klar das es in Java solche Operatoren gibt, aber nachdem ich das nachgelesen habe ist deine Idee erstmal nicht die schlechteste. Ich kann das erst in ein paar Stunden testen, gebe dann aber Feedback.



  • KopfExplodiert schrieb:

    Wenn man genau hinsieht habe ich den Typ des Arrays von "byte" auf "int" geändert, da Java keine unsigned Datentypen kennt und ich so z.B. nicht den Wert 212 in einem Byte speichern konnte.

    Verstehe ich nicht..
    Wenn Du 212 durch den Algorithmus jagen willst musst Du den int eben als ein byte Array der Länge vier übergeben.



  • Erstmal: Das wechseln von arithmetisch zu logisch sorgt dafür, dass keine negativen Zahlen mehr auftauchen, das Problem besteht jedoch weiterhin. Ich muss mich da nochmal dransetzen 😞

    byte[] schrieb:

    KopfExplodiert schrieb:

    Wenn man genau hinsieht habe ich den Typ des Arrays von "byte" auf "int" geändert, da Java keine unsigned Datentypen kennt und ich so z.B. nicht den Wert 212 in einem Byte speichern konnte.

    Verstehe ich nicht..
    Wenn Du 212 durch den Algorithmus jagen willst musst Du den int eben als ein byte Array der Länge vier übergeben.

    Auf Gegenseite lauscht eine C Anwendung, wo ein Byte einem unsigned char entsprechen, also der Wertebereich von 0bis255.
    Da kann ich jetzt kein java int für nehmen der in 4 Bytes zerlegt wird



  • KopfExplodiert schrieb:

    Auf Gegenseite lauscht eine C Anwendung, wo ein Byte einem unsigned char entsprechen, also der Wertebereich von 0bis255.
    Da kann ich jetzt kein java int für nehmen der in 4 Bytes zerlegt wird

    Dann mach aus dem int ein byte und schick das.
    Es geht ja dabei nur darum, dass Dein Wert in 8 Bit dargestellt werden kann. Ob die 8 Bit dann als signed oder unsigned interpretiert werden ist egal.



  • byte[] schrieb:

    KopfExplodiert schrieb:

    Auf Gegenseite lauscht eine C Anwendung, wo ein Byte einem unsigned char entsprechen, also der Wertebereich von 0bis255.
    Da kann ich jetzt kein java int für nehmen der in 4 Bytes zerlegt wird

    Dann mach aus dem int ein byte und schick das.
    Es geht ja dabei nur darum, dass Dein Wert in 8 Bit dargestellt werden kann. Ob die 8 Bit dann als signed oder unsigned interpretiert werden ist egal.

    Vlt. ist es ja einfacher als ich denke. Du meinst die Funktion aus der Quelle so übernehmen wie sie ist und die Integerwerte per (Byte)(intVal&0xff) casten?



  • KopfExplodiert schrieb:

    byte[] schrieb:

    KopfExplodiert schrieb:

    Auf Gegenseite lauscht eine C Anwendung, wo ein Byte einem unsigned char entsprechen, also der Wertebereich von 0bis255.
    Da kann ich jetzt kein java int für nehmen der in 4 Bytes zerlegt wird

    Dann mach aus dem int ein byte und schick das.
    Es geht ja dabei nur darum, dass Dein Wert in 8 Bit dargestellt werden kann. Ob die 8 Bit dann als signed oder unsigned interpretiert werden ist egal.

    Vlt. ist es ja einfacher als ich denke. Du meinst die Funktion aus der Quelle so übernehmen wie sie ist und die Integerwerte per (Byte)(intVal&0xff) casten?

    Ja genau.



  • Ok es geht jetzt, aber ich muss zugeben ich verstehe es nicht.
    Meine einzige Änderung ist das "&0xff" beim Zugriff auf "buf[pos]":

    private int calculateCRC(int buf[], int len) {
    		int crc = 0xFFFF;
    
    		for (int pos = 0; pos < len; pos++) {
    			crc ^= buf[pos]&0xff; // XOR byte into least sig. byte of crc
    	//		crc ^= buf[pos]; // XOR byte into least sig. byte of crc
    
    			for (int i = 8; i != 0; i--) { // Loop over each bit
    				if ((crc & 0x0001) != 0) { // If the LSB is set
    					crc >>= 1; // Shift right and XOR 0xA001
    					crc ^= 0xA001;
    				} else {
    					// Else LSB is not set
    					crc >>= 1; // Just shift right
    				}
    			}
    		}
    
    		return crc;
    	}
    

    Jetzt scheint die Checksummenberechnung immer zu stimmen. Was ich nicht verstehe ist, wieso folgender Wrapper nicht funktioniert:

    // Nimmt ein Int-array und ruft die Byte-Arrayfunktion auf
    	private int calculateCRC(int buf[], int len) {
    		Byte byteBuf[] = new Byte[len];
    
    		for(int i=0;i<len; ++i) {
    			byteBuf[i] = (byte) (buf[i]&0xff);
    		}
    
    		return calculateCRC(byteBuf, len);
    	}
    
            private int calculateCRC(Byte buf[], int len) {
    		int crc = 0xFFFF;
    
    		for (int pos = 0; pos < len; pos++) {
    			crc ^= buf[pos]; // XOR byte into least sig. byte of crc
    
    			for (int i = 8; i != 0; i--) { // Loop over each bit
    				if ((crc & 0x0001) != 0) { // If the LSB is set
    					crc >>= 1; // Shift right and XOR 0xA001
    					crc ^= 0xA001;
    				} else {
    					// Else LSB is not set
    					crc >>= 1; // Just shift right
    				}
    			}
    		}
    
    		return crc;
    	}
    

    Erläuterung: Hier ist die Funktion wie aus der Quelle 1:1 übernommen. Die obere Funktion nimmt ein int-Array, macht daraus ein Byte-Array (was sicher noch eleganter ginge) und reicht das an die Originalfunktion weiter. In meinen Augen dasselbe wie die zuerst genannte Variante, liefert scheinbar aber nicht die richtigen Ergebnisse.

    Jetzt freue ich mich darüber das ich durch experimentieren den Fehler beseitigt habe weiß aber nicht wo der Unterschied ist 😕



  • KopfExplodiert schrieb:

    Erläuterung: Hier ist die Funktion wie aus der Quelle 1:1 übernommen. Die obere Funktion nimmt ein int-Array, macht daraus ein Byte-Array (was sicher noch eleganter ginge) und reicht das an die Originalfunktion weiter. In meinen Augen dasselbe wie die zuerst genannte Variante, liefert scheinbar aber nicht die richtigen Ergebnisse.

    Aber in der von Dir verlinkten Quelle wird doch auch nur mit 16bit exklusiv-verodert (mittels Cast nach UInt16). Das fehlt in der unteren Version.
    Kein Wunder also, dass die Ergebnisse unterschiedlich sind.



  • Einen Unsigned-Typen gibt es aber doch in Java: char ist ein 16-bittiger, vorzeichenloser Integer. Als ich mich vor einiger Zeit mal mit Java gegeißelt habe, habe ich einfach den genommen.


Log in to reply