H-TRONIC USB 12-Bit-Datenerfassungssystem (HB627) mit JAVA steuern
Update 18.03.2012: Absturzursache behoben (falls nicht alle Bytes empfangen wurden und die ArrayList nicht die erwartete Länge bekam). Der Code hier im Artikel wurde dementsprechend auch aktualisiert.
Mit dieser Platine kann man 8 verschiedene analoge Signale im Spannungsbereich von 0 bis 4095 mV messen. Dies tut die Karte im Grunde auch sehr zuverlässig, jedoch kann man sich, gerade als JAVA-Nutzer, an einigen Ecken der Lieferung ärgern. Dazu später mehr.
Das Prinzip
Wir senden eine Byte-Folge an die Karte (Befehl) und erhalten die aktuellen Spannungswerte als Antwort zurück. Laut Handbuch geht das 300x in der Sekunde, bei mir läuft die Karte allerdings erst bei höchstens 75 Abfragen in der Sekunde stabil, was aber (im Grunde)* immer noch sehr schnell ist.
Pro-Argumente
- Messungen (im Grunde)* sehr genau
- Arbeitet (im Grunde)* sehr schnell
- Gute Verarbeitung
Contra-Argumente
- (*) Nur unter ideal-Voraussetzungen: Wenn an einem Kontakt kein geschlossener Stromkreis mit einer gewissen Spannung anliegt, so bekommt man für diesen Kontakt einen „Zufallswert“ als Spannung geliefert. So muss man durch eine Messreihe unerwünschte Messergebnisse erfassen und rausfiltern. Das erhöht den Arbeitsaufwand für einen brauchbare Messreihe. Dieser Fehler der Karte ist besonders ärgerlich, wenn man (wie ich) eben messen will, ob ein Stromkreis geschlossen oder offen ist.
- Kein Stecker mitgeliefert
- Karte kann nicht Kaskadiert werden (mehrere in „Reihe“ schalten und über einen USB-Port steuern)
- Handbuch / Dokumentation sehr mager
Anwendung der JAVA-Klasse
Die im folgenden beschriebene JAVA-Klasse braucht KEINE „Idealvoraussetzungen“ (wie unter Contra (*) beschrieben). Der Preis dafür ist lediglich, dass die Abfrage etwas länger dauert. Das Prinzip: Es werden mehrere Messungen (cfg: „newLaps“) unmittelbar hintereinander ausgeführt und verglichen: Sind stark schwankende Werte gemessen worden, so geht die Klasse davon aus, dass dort keine Spannung anliegt (es werden willkürliche Messwerte gesendet, ein Bug der Mess-Karte!). Bewegen sich die Schwankungen allerdings innerhalb eines bestimmten Intervalls (cfg: „newTolerance“), so wird diese Spannung als richtig bzw. zuverlässig interpretiert. Ferner kann man einstellen, welche Spannung mindestens anliegen muss, um als richtig gewertet zu werden. Die im unteren Beispiel verwendete Konfigurationseinstellung hat bei mir mit (wahrscheinlich)** 100%iger Genauigkeit die richtigen Messwerte geliefert.
(** Nach tausenden Messdurchläufen keine Abweichung der Messwerte von den realen Umständen)
Zunächst müssen wir bei Nutzung von Windows 7 die RXTXcomm.jar eingebunden haben. Wie das geht, könnt ihr im Artikel zur Relaiskarte nachlesen. Danach können wir die Klassen verwenden. Hier einen kleinen Crashkurs zu meiner Klasse:
public class Main { public static void main (String args[]) { //VERBINDUNG AUFBAUEN HB627 hb627 = new HB627("COM19"); //KLASSE KONFIGURIEREN //hb627.setCfg(newSleepTime, newLaps, newTolerance, newMinimumMV); //STANDARDEINSTELLUNGEN: //newLaps = 5; //Wieviele Messrunden soll eine Messung (ein Race) surchlaufen, bevor entschieden wird, ob der Port einen zuverlässigen Wert übermittelt hat? (je mehr, desto zuverlässiger); Wert = 1 setzen, wenn keine Messreihe vorgenommen werden soll, sondern im "klassischen" Sinne gemessen werden soll //newTolerance = 25; //in mV. Wie hoch darf die Durchschnitts-Differenz zwischen den Messrunden-Werten eines einzelnen Mess-Ports höchstens sein, um noch als zuverlässiges Ergebnis eingestuft zu werden. //newSleepTime = 16; //Wie lange soll nach jedem Kommando (nach jeder Runde [lap]) gewartet werden? //newMinimumMV = 1500; //Welchen Wert muss die Messung mindestens haben, um ÜBERHAUPT berücksichtigt zu werden? //ALLE PORTS GLEICHZEITIG LESEN / MESSEN //Wo liegt eine konstante Spannung an? boolean[] resultB = hb627.readAll(); //Wie hoch sind die anliegenden Spannungen? (vorher muss "readAll" oder "read" aufgerufen worden sein) int[] voltages = hb627.getVoltage(); //Ergebnisse ausgeben for (int j = 0; j<resultB.length;j++) { System.out.println(resultB[j]); System.out.println(voltages[j]); } //PORTS EINZELN LESEN / MESSEN boolean result = hb627.read(8); int[] voltages = hb627.getVoltage(); System.out.println(result); System.out.println(voltages[0]); } }
Der Quellcode der JAVA-Klasse
import gnu.io.CommPortIdentifier; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.TooManyListenersException; public class HB627 { private CommPortIdentifier commPortIdentifier; private SerialPort serialPort; private OutputStream outputStream; private BufferedOutputStream out; private InputStream inputStream; private int cfg_laps; private int cfg_tolerance; private int sleepTime; private int mVschwelle; private int commando; //letztes durchgeführtes commando private int lap; //aktuelle runde im rennen (race) (jedes race liefert ein Messergebnis in form von true oder false zurück) private ArrayList<Integer> finalResults; private ArrayList<byte[]> respondResults; private boolean[] booleanResults; //KONSTRUKTOR: VERBINDUNG MIT EMPFAENGERKARTE HERSTELLEN (z.B. comName = "COM17" ) HB627(String comName) { booleanResults = new boolean[8]; cfg_laps = 4; //Wieviele Messrunden soll eine Messung (ein Race) surchlaufen, bevor entschieden wird, ob der Port einen zuverlässigen Wert übermittelt hat? (je mehr, desto zuverlässiger) cfg_tolerance = 20; //in mV. Wie hoch darf die Durchschnitts-Differenz zwischen den Messrunden-Werten eines einzelnen Mess-Ports höchstens sein, um noch als zuverlässiges Ergebnis eingestuft zu werden. sleepTime = 15; //Wie lange soll nach jedem Kommando (nach jeder Runde [lap]) gewartet werden? mVschwelle = 1500; //Welchen Wert muss die Messung mindestens haben, um ÜBERHAUPT berücksichtigt zu werden? finalResults = new ArrayList<Integer>(); respondResults = new ArrayList<byte[]>(); //COM-PORT-OPEN if (!Main.simulation) { try { commPortIdentifier = CommPortIdentifier.getPortIdentifier(comName); serialPort = (SerialPort) commPortIdentifier.open(comName,2000); serialPort.setSerialPortParams(19200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); outputStream = serialPort.getOutputStream(); out = new BufferedOutputStream(outputStream); System.out.println("Connected to " + comName + ""); } catch(Exception exc) { System.out.println("Fehler Outputstream :"+exc); } //INPUT-STREAM try { inputStream = serialPort.getInputStream(); System.out.println("Inputstream open " + comName + ""); } catch (IOException exc) { System.out.println("Fehler Inputstream :"+exc); } try { serialPort.addEventListener(new serialPortEventListener()); System.out.println("Add Eventlistener " + comName + ""); } catch (TooManyListenersException e) { System.out.println("TooManyListenersException für Serialport"); } serialPort.notifyOnDataAvailable(true); } } void setCfg(int newSleepTime, int newLaps, int newTolerance, int newMinimumMV) { sleepTime = newSleepTime; cfg_laps = newLaps; cfg_tolerance = newTolerance; mVschwelle = newMinimumMV; } int getSleepTime() { return sleepTime; } //KOMMANDO-METHODE private void cmd(int third) { commando = third; lap++; //KOMMANDOS IN ARRAY SCHREIBEN //Datenpaket schnüren byte[] sendData = new byte[3]; sendData[0] = (byte) 99; //byte0_array[0]; sendData[1] = (byte) 48; //cmd; sendData[2] = (byte) (48+third); //(cmd ^ sendData[0]); //DATEN SENDEN if (!Main.simulation) { try { out.write(sendData, 0, 3); out.flush(); //System.out.println("Data send: First " + sendData[0] + "; second " + sendData[1] + "; third " + sendData[2] + " "); } catch (IOException e) { System.out.println("No data sended"); } //WARTEN try { //System.out.println("wait " + sleepTime + "ms"); Thread.sleep(sleepTime); } catch (InterruptedException e) { System.out.println("Sleeping failed"); } } } //VERBINDUNG ZUR HB627-KARTE BEENDEN void close() { if (!Main.simulation) { serialPort.close(); System.out.println("Connection closed"); } } //EINGEHENDES SIGNAL AUS HIGH-BYTE UND LOW-BYTE UMRECHNEN int parseResponse(byte highByte, byte lowByte) { int intHB = 0; int intLB = 0; //In positive Zahlen parsen if (highByte < 0) { intHB = 256 + highByte; } else { intHB = highByte; } if (lowByte < 0) { intLB = 256 + lowByte; } else { intLB = lowByte; } //IN FERTGE ZAHL UMRECHNEN int result = (int) ((intHB*10) + intLB*0); //LOW-BYTE WIRD zZ IGNORIERT! return result; } //NEUES RENNEN / NEUE MESSUNG: GRUNDLAGE SCHAFFEN (Vergleichswerte zurücksetzen) private void newRace() { respondResults.clear(); finalResults.clear(); lap = 0; for (int i = 0;i<booleanResults.length; i++) { booleanResults[i] = false; } } //EINEN PORT LESEN/MESSEN boolean read(int port) { newRace(); for (int i = 0; i<cfg_laps;i++) { cmd(port); } if (finalResults.size() > 0) { //System.out.println("FR: " + finalResults.get(0)); /*System.out.println(" "); for (int k = 0;k<finalResults.size();k++) { System.out.print(finalResults.get(k) + " "); }*/ //hat der messwert die mindest-volt-schwelle erreicht? //if (finalResults.get(0) >= mVschwelle || true) { int durchschnitt = 0; int differenz = 0; int high; //durchschnitt ausrechnen System.out.println(finalResults.size()+" "+cfg_laps); if (finalResults.size() == (cfg_laps*8)) { for (int i = 0; i<cfg_laps;i++) { durchschnitt += finalResults.get((i*8)); } durchschnitt = durchschnitt/cfg_laps; //Checken, ob höchstdifferenz eingehalten wird for (int i = 0; i<cfg_laps;i++) { high = (i*8); if (durchschnitt > finalResults.get(high)) { differenz += durchschnitt-finalResults.get(high); } else { differenz += finalResults.get(high)-durchschnitt; } } if ((differenz/cfg_laps) > cfg_tolerance ) { System.out.println("!tolerance: " + (differenz/cfg_laps)); return false; } else { System.out.println("tolerance: " + (differenz/cfg_laps)); booleanResults[0] = true; return true; } } else { System.out.println("Notice: (Array-Fehler)"); return false; } } else { return false; } //} else { // return false; //} } //ALLE PORTS LESEN/MESSEN boolean[] readAll() { newRace(); for (int i = 0; i<cfg_laps;i++) { cmd(9); } boolean[] result = {false,false,false,false,false,false,false,false}; if (finalResults.size() > 0) { /*System.out.println(" "); for (int k = 0;k<finalResults.size();k++) { System.out.print(finalResults.get(k) + " "); }*/ for(int i = 0; i<8;i++) { //System.out.println("FR "+(i+1)+": " + finalResults.get(i)); if(finalResults.get(i) > mVschwelle) { int durchschnitt = 0; int differenz = 0; int high; //durchschnitt ausrechnen for (int j = 0; j<cfg_laps;j++) { durchschnitt += finalResults.get((j*8)+i); } durchschnitt = durchschnitt/cfg_laps; //Checken, ob höchstdifferenz eingehalten wird for (int j = 0; j<cfg_laps;j++) { high = (j*8)+i; /*if (i==7) { System.out.println(durchschnitt + " dif " + finalResults.get(high)); }*/ if (durchschnitt > finalResults.get(high)) { differenz += durchschnitt-finalResults.get(high); } else { differenz += finalResults.get(high)-durchschnitt; } } if ((differenz/cfg_laps) > cfg_tolerance ) { //System.out.println("!tolerance: " + (differenz/cfg_laps)); result[i] = false; } else { //System.out.println("tolerance: " + (differenz/cfg_laps)); booleanResults[i] = true; result[i] = true; } } } } return result; } //MESSWERTE IN mV-Ausgeben int[] getVoltage() { int[] result = new int[8]; for (int i = 0;i<8;i++) { if (booleanResults[i]) { result[i] = finalResults.get(i); } else { result[i] = 0; } } return result; } //DATEN EMPFANGEN & VERARBEITEN public void serialPortDataAvailable() { try { byte[] data = new byte[17]; while(inputStream.available() > 0) { inputStream.read(data, 0, data.length); //System.out.println("[] "+data[0]+" "+data[1]+" "+data[2]+" "+data[3]+" "+data[4]+" "+data[5]+" "+data[6]+" "+data[7]+" "+data[8]+" "+data[9]+" "+data[10]+" "+data[11]+" "+data[12]+" "+data[13]+" "+data[14]+" "+data[15]+" "+data[16]+""); byte[] currentbytes = new byte[2]; int currentvoltage; int fb; int sb; for (int i = 0;i<(data.length);i++) { fb = 2+(i*2)-1; sb = 2+(i*2); if (sb < 17) { if (commando == 9) { currentbytes[1] = data[sb]; currentbytes[0] = data[fb]; } else { currentbytes[0] = data[sb]; currentbytes[1] = data[fb]; } currentvoltage = this.parseResponse(currentbytes[0], currentbytes[1]); respondResults.add(currentbytes); finalResults.add(currentvoltage); //System.out.print(currentvoltage + "(" +fb+ "" +sb+ "): " + data[fb] + "/ " + data[sb] +" || "); } } //System.out.println(" "); } } catch (IOException e) { System.out.println("!!! Data could not be recieved !!!"); } } class serialPortEventListener implements SerialPortEventListener { public void serialEvent(SerialPortEvent event) { switch (event.getEventType()) { case SerialPortEvent.DATA_AVAILABLE: serialPortDataAvailable(); break; case SerialPortEvent.BI: case SerialPortEvent.CD: case SerialPortEvent.CTS: case SerialPortEvent.DSR: case SerialPortEvent.FE: case SerialPortEvent.OUTPUT_BUFFER_EMPTY: case SerialPortEvent.PE: case SerialPortEvent.RI: default: } } } }
Bekannte / Mögliche Schwachstellen / Bugs in der Klasse
- Ich habe nicht geprüft (da in meinem Anwendungsbereich unerheblich) ob die gemessenen Spannungswerte („getVoltage()“) den realen Spannungswerten entsprechen. Eventuell ergibt sich die tatsächliche Spannung erst nach durch die Differenz von 4095… oder auch nicht. Ausgeschlossen werden kann ist, dass „getVoltage()“ falsche ergebnisse liefert. Man muss diese eben nur richtig interpretieren können.
- Die Umrechnung von High-Byte und Lowbyte (Antwort von der Empfängerkarte) ist möglicherweise unvollständig, jedoch ausreichend (für meine Zwecke). Für eine Umrechnung unter berücksichtigung des Low-Bytes fehlt mir bisher die korrekte Formel, die sich auch nicht aus dem Handbuch ergibt. Sobald ich in diesem Punkt weiter bin, werde ich das im Kommentarbereich ergänzen.